diff --git a/app/api/network.py b/app/api/network.py index ee16cd0c9fa620efc1525c5c8d5955cd10dce16b..a2932543a11f3f239c9bb84ab17e92c8b4e5b03e 100644 --- a/app/api/network.py +++ b/app/api/network.py @@ -163,7 +163,7 @@ def create_interface(): .. :quickref: Network; Create new interface :jsonparam network: network name - :jsonparam ip: interface IP + :jsonparam ip: (optional) interface IP - IP will be assigned automatically if not given :jsonparam name: interface name :jsonparam host: host name :jsonparam mac: (optional) MAC address @@ -185,7 +185,7 @@ def create_interface(): if not current_user.has_access_to_network(network): raise CSEntryError("User doesn't have the required group", status_code=403) return utils.create_generic_model( - models.Interface, mandatory_fields=("network", "ip", "name", "host") + models.Interface, mandatory_fields=("network", "name", "host") ) diff --git a/app/models.py b/app/models.py index a71ee67c381a1dbc2e6ec34fa23d0c4aae4179a6..5c6d5c33aebf6bbf555d33fd74203da38ed300ef 100644 --- a/app/models.py +++ b/app/models.py @@ -1457,9 +1457,9 @@ class Interface(CreatedMixin, db.Model): try: ip = kwargs.pop("ip") except KeyError: - super().__init__(host=host, **kwargs) - else: - super().__init__(host=host, ip=ip, **kwargs) + # Assign first available IP + ip = str(kwargs["network"].available_ips()[0]) + super().__init__(host=host, ip=ip, **kwargs) @validates("name") def validate_name(self, key, string): diff --git a/tests/functional/test_api.py b/tests/functional/test_api.py index 324645edaf229b86249fd62496b04011df6a41ac..57b6d07d98f69f8b30322b75b06ea5feb300509b 100644 --- a/tests/functional/test_api.py +++ b/tests/functional/test_api.py @@ -181,6 +181,17 @@ def no_login_check_token(request, app): app.config["LOGIN_DISABLED"] = False +@pytest.fixture +def network_192_168_1(network_scope_factory, network_factory): + scope = network_scope_factory(supernet="192.168.0.0/16") + return network_factory( + address="192.168.1.0/24", + first_ip="192.168.1.10", + last_ip="192.168.1.250", + scope=scope, + ) + + def check_response_message(response, msg, status_code=400): assert response.status_code == status_code try: @@ -1124,16 +1135,7 @@ def test_get_interfaces_with_sensitive_network( assert len(response.get_json()) == 1 -def test_create_interface_fails( - client, host, network_scope_factory, network_factory, no_login_check_token -): - scope = network_scope_factory(supernet="192.168.0.0/16") - network = network_factory( - address="192.168.1.0/24", - first_ip="192.168.1.10", - last_ip="192.168.1.250", - scope=scope, - ) +def test_create_interface_fails(client, host, network_192_168_1, no_login_check_token): # check that network_id and ip are mandatory response = post( client, f"{API_URL}/network/interfaces", data={}, token=no_login_check_token @@ -1146,15 +1148,12 @@ def test_create_interface_fails( token=no_login_check_token, ) check_response_message(response, "Missing mandatory field 'network'", 422) - response = post( - client, - f"{API_URL}/network/interfaces", - data={"network": network.address}, - token=no_login_check_token, - ) - check_response_message(response, "Missing mandatory field 'ip'", 422) - data = {"network": network.vlan_name, "ip": "192.168.1.20", "name": "interface1"} + data = { + "network": network_192_168_1.vlan_name, + "ip": "192.168.1.20", + "name": "interface1", + } response = post( client, f"{API_URL}/network/interfaces", data=data, token=no_login_check_token ) @@ -1169,18 +1168,9 @@ def test_create_interface_fails( ) -def test_create_interface( - client, host, network_scope_factory, network_factory, no_login_check_token -): - scope = network_scope_factory(supernet="192.168.0.0/16") - network = network_factory( - address="192.168.1.0/24", - first_ip="192.168.1.10", - last_ip="192.168.1.250", - scope=scope, - ) +def test_create_interface(client, host, network_192_168_1, no_login_check_token): data = { - "network": network.vlan_name, + "network": network_192_168_1.vlan_name, "ip": "192.168.1.20", "name": host.name, "host": host.name, @@ -1190,7 +1180,7 @@ def test_create_interface( ) assert response.status_code == 201 assert INTERFACE_KEYS == set(response.get_json().keys()) - assert response.get_json()["network"] == network.vlan_name + assert response.get_json()["network"] == network_192_168_1.vlan_name assert response.get_json()["ip"] == "192.168.1.20" assert response.get_json()["name"] == host.name # This is the main interface @@ -1198,7 +1188,7 @@ def test_create_interface( # Check that all parameters can be passed data2 = { - "network": network.vlan_name, + "network": network_192_168_1.vlan_name, "ip": "192.168.1.21", "name": host.name + "-2", "host": host.name, @@ -1227,18 +1217,11 @@ def test_create_interface( @pytest.mark.parametrize("ip", ("", "foo", "192.168")) def test_create_interface_invalid_ip( - ip, client, host, network_scope_factory, network_factory, no_login_check_token + ip, client, host, network_192_168_1, no_login_check_token ): - scope = network_scope_factory(supernet="192.168.0.0/16") - network = network_factory( - address="192.168.1.0/24", - first_ip="192.168.1.10", - last_ip="192.168.1.250", - scope=scope, - ) # invalid IP address data = { - "network": network.vlan_name, + "network": network_192_168_1.vlan_name, "ip": ip, "name": host.name, "host": host.name, @@ -1252,18 +1235,11 @@ def test_create_interface_invalid_ip( def test_create_interface_ip_not_in_network( - client, host, network_scope_factory, network_factory, no_login_check_token + client, host, network_192_168_1, no_login_check_token ): - scope = network_scope_factory(supernet="192.168.0.0/16") - network = network_factory( - address="192.168.1.0/24", - first_ip="192.168.1.10", - last_ip="192.168.1.250", - scope=scope, - ) # IP address not in range data = { - "network": network.vlan_name, + "network": network_192_168_1.vlan_name, "ip": "192.168.2.4", "name": host.name, "host": host.name, @@ -1277,18 +1253,11 @@ def test_create_interface_ip_not_in_network( def test_create_interface_ip_not_in_range( - client, host, network_scope_factory, network_factory, no_login_check_token + client, host, network_192_168_1, no_login_check_token ): - scope = network_scope_factory(supernet="192.168.0.0/16") - network = network_factory( - address="192.168.1.0/24", - first_ip="192.168.1.10", - last_ip="192.168.1.250", - scope=scope, - ) # IP address not in range data = { - "network": network.vlan_name, + "network": network_192_168_1.vlan_name, "ip": "192.168.1.4", "name": host.name, "host": host.name, @@ -1304,18 +1273,11 @@ def test_create_interface_ip_not_in_range( def test_create_interface_ip_not_in_range_as_admin( - client, host, network_scope_factory, network_factory, admin_token + client, host, network_192_168_1, admin_token ): - scope = network_scope_factory(supernet="192.168.0.0/16") - network = network_factory( - address="192.168.1.0/24", - first_ip="192.168.1.10", - last_ip="192.168.1.250", - scope=scope, - ) # IP address not in range data = { - "network": network.vlan_name, + "network": network_192_168_1.vlan_name, "ip": "192.168.1.4", "name": host.name, "host": host.name, @@ -1353,6 +1315,37 @@ def test_normal_user_can_create_interface_on_empty_host( assert response.get_json()["is_main"] +def test_create_interface_with_ip( + client, host, network_192_168_1, no_login_check_token +): + data = { + "network": network_192_168_1.vlan_name, + "name": host.name, + "host": host.name, + "ip": "192.168.1.12", + } + response = post( + client, f"{API_URL}/network/interfaces", data=data, token=no_login_check_token + ) + assert response.status_code == 201 + assert response.get_json()["ip"] == "192.168.1.12" + + +def test_create_interface_without_ip( + client, host, network_192_168_1, no_login_check_token +): + data = { + "network": network_192_168_1.vlan_name, + "name": host.name, + "host": host.name, + } + response = post( + client, f"{API_URL}/network/interfaces", data=data, token=no_login_check_token + ) + assert response.status_code == 201 + assert response.get_json()["ip"] == "192.168.1.10" + + def test_delete_interface_invalid_credentials(client, interface_factory, user_token): interface1 = interface_factory() response = delete( @@ -2291,17 +2284,8 @@ def test_patch_interface_mac(client, interface_factory, admin_token): assert updated_interface.mac == data["mac"] -def test_patch_interface_ip( - client, interface_factory, network_scope_factory, network_factory, admin_token -): - scope = network_scope_factory(supernet="192.168.0.0/16") - network = network_factory( - address="192.168.1.0/24", - first_ip="192.168.1.10", - last_ip="192.168.1.250", - scope=scope, - ) - interface = interface_factory(network=network, ip="192.168.1.11") +def test_patch_interface_ip(client, interface_factory, network_192_168_1, admin_token): + interface = interface_factory(network=network_192_168_1, ip="192.168.1.11") data = {"ip": "192.168.2.12"} response = patch( client, @@ -2310,7 +2294,9 @@ def test_patch_interface_ip( token=admin_token, ) check_response_message( - response, f"IP address {data['ip']} is not in network {network.address}", 422 + response, + f"IP address {data['ip']} is not in network {network_192_168_1.address}", + 422, ) data = {"ip": "192.168.1.12"} response = patch(