diff --git a/quark/db/api.py b/quark/db/api.py index 576854a..d69f08d 100644 --- a/quark/db/api.py +++ b/quark/db/api.py @@ -299,6 +299,12 @@ def ip_address_delete(context, addr): context.session.delete(addr) +def ip_address_deallocate(context, address, **kwargs): + kwargs["_deallocated"] = 1 + kwargs["deallocated_at"] = timeutils.utcnow() + return ip_address_update(context, address, **kwargs) + + @scoped def ip_address_find(context, lock_mode=False, **filters): query = context.session.query(models.IPAddress) @@ -898,12 +904,11 @@ def floating_ip_find(context, lock_mode=False, limit=None, sorts=None, limit, sorts, marker) -def floating_ip_associate_fixed_ip(context, floating_ip, fixed_ip, - enable=True): - assoc = models.FloatingToFixedIPAssociation() - assoc.floating_ip_address_id = floating_ip.id - assoc.fixed_ip_address_id = fixed_ip.id - assoc.enabled = enable - context.session.add(assoc) +def floating_ip_associate_fixed_ip(context, floating_ip, fixed_ip): floating_ip.fixed_ip = fixed_ip return floating_ip + + +def floating_ip_disassociate_fixed_ip(context, floating_ip): + floating_ip.fixed_ip = None + return floating_ip diff --git a/quark/drivers/unicorn_driver.py b/quark/drivers/unicorn_driver.py index 5af0ef7..8ae5a4d 100644 --- a/quark/drivers/unicorn_driver.py +++ b/quark/drivers/unicorn_driver.py @@ -59,8 +59,19 @@ class UnicornDriver(object): LOG.error("register_floating_ip: %s" % msg) raise ex.RegisterFloatingIpFailure(id=floating_ip.id) - def update_floating_ip(self, floating_ip): - pass + def update_floating_ip(self, floating_ip, port, fixed_ip): + url = "%s/%s" % (CONF.QUARK.floating_ip_base_url, + floating_ip["address_readable"]) + req = self._build_request_body(floating_ip, port, fixed_ip) + + LOG.info("Calling unicorn to register floating ip: %s %s" % (url, req)) + r = requests.put(url, data=json.dumps(req)) + + if r.status_code != 200: + msg = "Unexpected status from unicorn API: Status Code %s, " \ + "Message: %s" % (r.status_code, r.json()) + LOG.error("register_floating_ip: %s" % msg) + raise ex.RegisterFloatingIpFailure(id=floating_ip.id) def remove_floating_ip(self, floating_ip): url = "%s/%s" % (CONF.QUARK.floating_ip_base_url, @@ -69,7 +80,10 @@ class UnicornDriver(object): LOG.info("Calling unicorn to remove floating ip: %s" % url) r = requests.delete(url) - if r.status_code != 204: + if r.status_code == 404: + LOG.warn("The floating IP %s does not exist in the unicorn system." + % floating_ip.address_readable) + elif r.status_code != 204: msg = "Unexpected status from unicorn API: Status Code %s, " \ "Message: %s" % (r.status_code, r.json()) LOG.error("remove_floating_ip: %s" % msg) diff --git a/quark/exceptions.py b/quark/exceptions.py index c1d1b62..1903f3e 100644 --- a/quark/exceptions.py +++ b/quark/exceptions.py @@ -167,3 +167,13 @@ class NoAvailableFixedIPsForPort(exceptions.Conflict): class PortDoesNotHaveAGateway(exceptions.Conflict): message = _("Port %(port_id) does not have a gateway") + + +class PortAlreadyAssociatedToFloatingIP(exceptions.BadRequest): + message = _("Port %(port_id) is already associated with " + "floating IP %(flip_id)") + + +class FloatingIPUpdateNoPortIdSupplied(exceptions.BadRequest): + message = _("When no port is currently associated to the floating if, " + "port_id is required but was not supplied") diff --git a/quark/plugin.py b/quark/plugin.py index c1f531d..f2f1d45 100644 --- a/quark/plugin.py +++ b/quark/plugin.py @@ -413,7 +413,8 @@ class Plugin(neutron_plugin_base_v2.NeutronPluginBaseV2, @sessioned def update_floatingip(self, context, id, floatingip): - return floating_ips.update_floatingip(context, id, floatingip) + return floating_ips.update_floatingip(context, id, + floatingip["floatingip"]) @sessioned def get_floatingip(self, context, id, fields=None): diff --git a/quark/plugin_modules/floating_ips.py b/quark/plugin_modules/floating_ips.py index 6428c5b..d8cb6aa 100644 --- a/quark/plugin_modules/floating_ips.py +++ b/quark/plugin_modules/floating_ips.py @@ -20,7 +20,7 @@ from oslo_log import log as logging from quark.db import api as db_api from quark.db import ip_types from quark.drivers import floating_ip_registry as registry -from quark import exceptions as quark_exceptions +from quark import exceptions as qex from quark import ipam from quark import plugin_views as v @@ -40,6 +40,17 @@ CONF.register_opts(quark_router_opts, "QUARK") def create_floatingip(context, content): + """Allocate or reallocate a floating IP. + + :param context: neutron api request context. + :param content: dictionary describing the floating ip, with keys + as listed in the RESOURCE_ATTRIBUTE_MAP object in + neutron/api/v2/attributes.py. All keys will be populated. + + :returns: Dictionary containing details for the new floating IP. If values + are declared in the fields parameter, then only those keys will be + present. + """ LOG.info("create_floatingip %s for tenant %s and body %s" % (id, context.tenant_id, content)) tenant_id = content.get("tenant_id") @@ -69,12 +80,12 @@ def create_floatingip(context, content): raise exceptions.PortNotFound(port_id=port_id) if not port.ip_addresses or len(port.ip_addresses) == 0: - raise quark_exceptions.NoAvailableFixedIPsForPort(port_id=port_id) + raise qex.NoAvailableFixedIPsForPort(port_id=port_id) if not fixed_ip_address: fixed_ip = _get_next_available_fixed_ip(port) if not fixed_ip: - raise quark_exceptions.NoAvailableFixedIPsForPort( + raise qex.NoAvailableFixedIPsForPort( port_id=port_id) else: fixed_ip = next((ip for ip in port.ip_addresses @@ -83,13 +94,13 @@ def create_floatingip(context, content): None) if not fixed_ip: - raise quark_exceptions.FixedIpDoesNotExistsForPort( + raise qex.FixedIpDoesNotExistsForPort( fixed_ip=fixed_ip_address, port_id=port_id) if any(ip for ip in port.ip_addresses if (ip.get("address_type") == ip_types.FLOATING and ip.fixed_ip["address_readable"] == fixed_ip_address)): - raise quark_exceptions.PortAlreadyContainsFloatingIp( + raise qex.PortAlreadyContainsFloatingIp( port_id=port_id) new_addresses = [] @@ -106,55 +117,145 @@ def create_floatingip(context, content): ip_addresses=ip_addresses, address_type=ip_types.FLOATING) - floating_ip = new_addresses[0] + flip = new_addresses[0] if fixed_ip and port: with context.session.begin(): - floating_ip = db_api.floating_ip_associate_fixed_ip(context, - floating_ip, - fixed_ip) + flip = db_api.port_associate_ip(context, [port], flip, [port_id]) + flip = db_api.floating_ip_associate_fixed_ip(context, flip, + fixed_ip) + flip_driver_type = CONF.QUARK.default_floating_ip_driver + flip_driver = registry.DRIVER_REGISTRY.get_driver(flip_driver_type) + + flip_driver.register_floating_ip(flip, port, fixed_ip) + + return v._make_floating_ip_dict(flip, port_id) + + +def update_floatingip(context, id, content): + """Update an existing floating IP. + + :param context: neutron api request context. + :param id: id of the floating ip + :param content: dictionary with keys indicating fields to update. + valid keys are those that have a value of True for 'allow_put' + as listed in the RESOURCE_ATTRIBUTE_MAP object in + neutron/api/v2/attributes.py. + + :returns: Dictionary containing details for the new floating IP. If values + are declared in the fields parameter, then only those keys will be + present. + """ + + LOG.info("update_floatingip %s for tenant %s and body %s" % + (id, context.tenant_id, content)) + + if "port_id" not in content: + raise exceptions.BadRequest(resource="floating_ip", + msg="port_id is required.") + + port_id = content.get("port_id") + port = None + fixed_ip = None + current_port = None + flip = None + + with context.session.begin(): + flip = db_api.floating_ip_find(context, id=id, scope=db_api.ONE) + + current_ports = flip.ports + + if current_ports and len(current_ports) > 0: + current_port = current_ports[0] + + if not port_id and not current_port: + raise qex.FloatingIPUpdateNoPortIdSupplied() + + if port_id: + port = db_api.port_find(context, id=port_id, scope=db_api.ONE) + if not port: + raise exceptions.PortNotFound(port_id=port_id) + + if current_port and current_port.id == port_id: + d = dict(flip_id=id, port_id=port_id) + raise qex.PortAlreadyAssociatedToFloatingIP(**d) + + fixed_ip = _get_next_available_fixed_ip(port) + LOG.info("new fixed ip: %s" % fixed_ip) + if not fixed_ip: + raise qex.NoAvailableFixedIPsForPort(port_id=port_id) + + LOG.info("current ports: %s" % current_ports) + + if current_port: + flip = db_api.port_disassociate_ip(context, [current_port], flip) + + if flip.fixed_ip: + flip = db_api.floating_ip_disassociate_fixed_ip(context, flip) + + if port: + flip = db_api.port_associate_ip(context, [port], flip, [port_id]) + flip = db_api.floating_ip_associate_fixed_ip(context, flip, + fixed_ip) flip_driver_type = CONF.QUARK.default_floating_ip_driver flip_driver = registry.DRIVER_REGISTRY.get_driver(flip_driver_type) - flip_driver.register_floating_ip(floating_ip, port, fixed_ip) + if port: + if current_port: + flip_driver.update_floating_ip(flip, port, fixed_ip) + else: + flip_driver.register_floating_ip(flip, port, fixed_ip) + else: + flip_driver.remove_floating_ip(flip) - return v._make_floating_ip_dict(floating_ip) + # Note(alanquillin) The ports parameters on the model is not + # properly getting cleaned up when removed. Manually cleaning them up. + # Need to fix the db api to correctly update the model. + if not port: + flip.ports = [] - -def update_floatingip(context, id, body): - LOG.info("update_floatingip %s for tenant %s and body %s" % - (id, context.tenant_id, body)) - - # floating_ip_dict = body.get("ip_address") - # - # if "port_id" not in floating_ip_dict: - # raise exceptions.BadRequest(resource="floating_ip", - # msg="port_id is required.") - - # port_id = floating_ip_dict.get("port_id") - - raise NotImplementedError() + return v._make_floating_ip_dict(flip) def delete_floatingip(context, id): + """deallocate a floating IP. + + :param context: neutron api request context. + :param id: id of the floating ip + """ + LOG.info("delete_floatingip %s for tenant %s" % (id, context.tenant_id)) filters = {"address_type": ip_types.FLOATING, "_deallocated": False} - floating_ip = db_api.floating_ip_find(context, id=id, scope=db_api.ONE, - **filters) - if not floating_ip: - raise quark_exceptions.FloatingIpNotFound(id=id) + flip = db_api.floating_ip_find(context, id=id, scope=db_api.ONE, **filters) + if not flip: + raise qex.FloatingIpNotFound(id=id) - driver_type = CONF.QUARK.default_floating_ip_driver - driver = registry.DRIVER_REGISTRY.get_driver(driver_type) + current_ports = flip.ports + current_port = None - driver.remove_floating_ip(floating_ip) + if current_ports and len(current_ports) > 0: + current_port = current_ports[0] - strategy_name = floating_ip.network.get("ipam_strategy") - ipam_driver = ipam.IPAM_REGISTRY.get_strategy(strategy_name) - ipam_driver.deallocate_ip_address(context, floating_ip) + with context.session.begin(): + strategy_name = flip.network.get("ipam_strategy") + ipam_driver = ipam.IPAM_REGISTRY.get_strategy(strategy_name) + ipam_driver.deallocate_ip_address(context, flip) + + if current_port: + flip = db_api.port_disassociate_ip(context, [current_port], + flip) + if flip.fixed_ip: + flip = db_api.floating_ip_disassociate_fixed_ip(context, flip) + + db_api.ip_address_deallocate(context, flip) + + if flip.fixed_ip: + driver_type = CONF.QUARK.default_floating_ip_driver + driver = registry.DRIVER_REGISTRY.get_driver(driver_type) + driver.remove_floating_ip(flip) def get_floatingip(context, id, fields=None): @@ -179,7 +280,7 @@ def get_floatingip(context, id, fields=None): **filters) if not floating_ip: - raise quark_exceptions.FloatingIpNotFound(id=id) + raise qex.FloatingIpNotFound(id=id) return v._make_floating_ip_dict(floating_ip) diff --git a/quark/plugin_views.py b/quark/plugin_views.py index e07d50f..dde777f 100644 --- a/quark/plugin_views.py +++ b/quark/plugin_views.py @@ -276,13 +276,15 @@ def _make_ip_policy_dict(ipp): "exclude": [ippc["cidr"] for ippc in ipp["exclude"]]} -def _make_floating_ip_dict(flip): - ports = flip.ports - port_id = None - if ports and len(ports) > 0: - port_id = None if not ports[0] else ports[0].id +def _make_floating_ip_dict(flip, port_id=None): + if not port_id: + ports = flip.ports + port_id = None + if ports and len(ports) > 0: + port_id = None if not ports[0] else ports[0].id fixed_ip = flip.fixed_ip + return {"id": flip.get("id"), "floating_network_id": flip.get("network_id"), "router_id": CONF.QUARK.floating_ip_router_id, diff --git a/quark/tests/plugin_modules/test_floating_ips.py b/quark/tests/plugin_modules/test_floating_ips.py index 2ea9070..41af0db 100644 --- a/quark/tests/plugin_modules/test_floating_ips.py +++ b/quark/tests/plugin_modules/test_floating_ips.py @@ -18,10 +18,10 @@ import contextlib import datetime import mock import netaddr -from neutron.common import exceptions +from neutron.common import exceptions as ex from quark.db import models -from quark import exceptions as quark_exceptions +from quark import exceptions as q_ex from quark.plugin_modules import floating_ips from quark.tests import test_quark_plugin @@ -36,10 +36,14 @@ class TestRemoveFloatingIPs(test_quark_plugin.TestQuarkPlugin): with contextlib.nested( mock.patch("quark.db.api.floating_ip_find"), + mock.patch("quark.db.api.floating_ip_disassociate_fixed_ip"), + mock.patch("quark.db.api.port_disassociate_ip"), + mock.patch("quark.db.api.ip_address_deallocate"), mock.patch("quark.ipam.QuarkIpam.deallocate_ip_address"), mock.patch("quark.drivers.unicorn_driver.UnicornDriver" ".remove_floating_ip") - ) as (flip_find, mock_dealloc, mock_remove_flip): + ) as (flip_find, db_fixed_ip_disassoc, db_port_disassoc, db_dealloc, + mock_dealloc, mock_remove_flip): flip_find.return_value = flip_model yield @@ -52,7 +56,7 @@ class TestRemoveFloatingIPs(test_quark_plugin.TestQuarkPlugin): def test_delete_floating_by_when_ip_address_does_not_exists_fails(self): with self._stubs(): - with self.assertRaises(quark_exceptions.FloatingIpNotFound): + with self.assertRaises(q_ex.FloatingIpNotFound): self.plugin.delete_floatingip(self.context, 1) @@ -213,6 +217,10 @@ class TestCreateFloatingIPs(test_quark_plugin.TestQuarkPlugin): def _alloc_ip(context, new_addr, net_id, port_m, *args, **kwargs): new_addr.append(flip_model) + def _port_assoc(context, ports, addr, enable_port=None): + addr.ports = ports + return addr + def _flip_fixed_ip_assoc(context, addr, fixed_ip): addr.fixed_ip = fixed_ip return addr @@ -224,13 +232,16 @@ class TestCreateFloatingIPs(test_quark_plugin.TestQuarkPlugin): mock.patch("quark.ipam.QuarkIpam.allocate_ip_address"), mock.patch("quark.drivers.unicorn_driver.UnicornDriver" ".register_floating_ip"), + mock.patch("quark.db.api.port_associate_ip"), mock.patch("quark.db.api.floating_ip_associate_fixed_ip") - ) as (flip_find, net_find, port_find, alloc_ip, mock_reg_flip, assoc): + ) as (flip_find, net_find, port_find, alloc_ip, mock_reg_flip, + port_assoc, fixed_ip_assoc): flip_find.return_value = flip_model net_find.return_value = net_model port_find.return_value = port_model alloc_ip.side_effect = _alloc_ip - assoc.side_effect = _flip_fixed_ip_assoc + port_assoc.side_effect = _port_assoc + fixed_ip_assoc.side_effect = _flip_fixed_ip_assoc yield def test_create_with_a_port(self): @@ -329,14 +340,14 @@ class TestCreateFloatingIPs(test_quark_plugin.TestQuarkPlugin): def test_create_without_network_id_fails(self): with self._stubs(): - with self.assertRaises(exceptions.BadRequest): + with self.assertRaises(ex.BadRequest): request = dict(port_id=2, floating_ip_address="10.0.0.1") self.plugin.create_floatingip(self.context, dict(floatingip=request)) def test_create_with_invalid_network_fails(self): with self._stubs(): - with self.assertRaises(exceptions.NetworkNotFound): + with self.assertRaises(ex.NetworkNotFound): request = dict(floating_network_id=123, port_id=2, floating_ip_address="10.0.0.1") self.plugin.create_floatingip(self.context, @@ -347,7 +358,7 @@ class TestCreateFloatingIPs(test_quark_plugin.TestQuarkPlugin): ipam_strategy="ANY") with self._stubs(network=network): - with self.assertRaises(exceptions.PortNotFound): + with self.assertRaises(ex.PortNotFound): request = dict(floating_network_id=network["id"], port_id=2, floating_ip_address="10.0.0.1") self.plugin.create_floatingip(self.context, @@ -367,7 +378,7 @@ class TestCreateFloatingIPs(test_quark_plugin.TestQuarkPlugin): with self._stubs(port=port, ips=fixed_ips, network=network): with self.assertRaises( - quark_exceptions.FixedIpDoesNotExistsForPort): + q_ex.FixedIpDoesNotExistsForPort): request = dict(floating_network_id=network["id"], port_id=port["id"], fixed_ip_address="192.168.0.2") @@ -400,7 +411,7 @@ class TestCreateFloatingIPs(test_quark_plugin.TestQuarkPlugin): with self._stubs(port=port, ips=ips, network=network): with self.assertRaises( - quark_exceptions.PortAlreadyContainsFloatingIp): + q_ex.PortAlreadyContainsFloatingIp): request = dict(floating_network_id=network["id"], port_id=port["id"], fixed_ip_address="192.168.0.1") @@ -415,7 +426,7 @@ class TestCreateFloatingIPs(test_quark_plugin.TestQuarkPlugin): with self._stubs(port=port, network=network): with self.assertRaises( - quark_exceptions.NoAvailableFixedIPsForPort): + q_ex.NoAvailableFixedIPsForPort): request = dict(floating_network_id=network["id"], port_id=port["id"]) self.plugin.create_floatingip(self.context, @@ -444,8 +455,186 @@ class TestCreateFloatingIPs(test_quark_plugin.TestQuarkPlugin): with self._stubs(port=port, ips=ips, network=network): with self.assertRaises( - quark_exceptions.NoAvailableFixedIPsForPort): + q_ex.NoAvailableFixedIPsForPort): request = dict(floating_network_id=network["id"], port_id=port["id"]) self.plugin.create_floatingip(self.context, dict(floatingip=request)) + + +class TestUpdateFloatingIPs(test_quark_plugin.TestQuarkPlugin): + @contextlib.contextmanager + def _stubs(self, flip=None, curr_port=None, new_port=None): + curr_port_model = None + if curr_port: + curr_port_model = models.Port() + curr_port_model.update(curr_port) + + new_port_model = None + if new_port: + new_port_model = models.Port() + new_port_model.update(new_port) + fixed_ips = new_port.get("fixed_ips") + if fixed_ips: + new_port_model.ip_addresses = [] + for ip in fixed_ips: + addr = netaddr.IPAddress(ip) + fixed_ip_model = models.IPAddress() + fixed_ip_model.update(dict(address_readable=ip, + address=int(addr), version=4, + address_type="fixed")) + new_port_model.ip_addresses.append(fixed_ip_model) + + flip_model = None + if flip: + flip_model = models.IPAddress() + flip_model.update(flip) + if curr_port_model: + flip_model.ports = [curr_port_model] + fixed_ip = flip.get("fixed_ip_address") + if fixed_ip: + addr = netaddr.IPAddress(fixed_ip) + fixed_ip_model = models.IPAddress() + fixed_ip_model.update(dict(address_readable=fixed_ip, + address=int(addr), version=4, + address_type="fixed")) + flip_model.fixed_ip = fixed_ip_model + + def _find_port(context, id, **kwargs): + return (curr_port_model if (curr_port_model and + id == curr_port_model.id) + else new_port_model) + + def _flip_assoc(context, addr, fixed_ip): + addr.fixed_ip = fixed_ip + return addr + + def _flip_disassoc(context, addr): + addr.fixed_ip = None + return addr + + def _port_assoc(context, ports, addr, enable_ports=None): + addr.ports = ports + return addr + + def _port_dessoc(context, ports, addr): + addr.associations = [] + addr.ports = [] + return addr + + with contextlib.nested( + mock.patch("quark.db.api.floating_ip_find"), + mock.patch("quark.db.api.port_find"), + mock.patch("quark.drivers.unicorn_driver.UnicornDriver" + ".register_floating_ip"), + mock.patch("quark.drivers.unicorn_driver.UnicornDriver" + ".update_floating_ip"), + mock.patch("quark.drivers.unicorn_driver.UnicornDriver" + ".remove_floating_ip"), + mock.patch("quark.db.api.port_associate_ip"), + mock.patch("quark.db.api.port_disassociate_ip"), + mock.patch("quark.db.api.floating_ip_associate_fixed_ip"), + mock.patch("quark.db.api.floating_ip_disassociate_fixed_ip") + ) as (flip_find, port_find, reg_flip, update_flip, rem_flip, + port_assoc, port_dessoc, flip_assoc, flip_dessoc): + flip_find.return_value = flip_model + port_find.side_effect = _find_port + port_assoc.side_effect = _port_assoc + port_dessoc.side_effect = _port_dessoc + flip_assoc.side_effect = _flip_assoc + flip_dessoc.side_effect = _flip_disassoc + yield + + def test_update_with_new_port_and_no_previous_port(self): + new_port = dict(id="2", fixed_ips=["192.168.0.1"]) + addr = netaddr.IPAddress("10.0.0.1") + flip = dict(id="3", fixed_ip_address="172.16.1.1", address=int(addr), + address_readable=str(addr)) + + with self._stubs(flip=flip, new_port=new_port): + content = dict(port_id=new_port["id"]) + ret = self.plugin.update_floatingip(self.context, flip["id"], + dict(floatingip=content)) + self.assertEqual(ret["fixed_ip_address"], "192.168.0.1") + self.assertEqual(ret["port_id"], new_port["id"]) + + def test_update_with_new_port(self): + curr_port = dict(id="1") + new_port = dict(id="2", fixed_ips=["192.168.0.1"]) + addr = netaddr.IPAddress("10.0.0.1") + flip = dict(id="3", fixed_ip_address="172.16.1.1", address=int(addr), + address_readable=str(addr)) + + with self._stubs(flip=flip, curr_port=curr_port, new_port=new_port): + content = dict(port_id=new_port["id"]) + ret = self.plugin.update_floatingip(self.context, flip["id"], + dict(floatingip=content)) + self.assertEqual(ret["fixed_ip_address"], "192.168.0.1") + self.assertEqual(ret["port_id"], new_port["id"]) + + def test_update_with_no_port(self): + curr_port = dict(id="1") + addr = netaddr.IPAddress("10.0.0.1") + flip = dict(id="3", fixed_ip_address="172.16.1.1", address=int(addr), + address_readable=str(addr)) + + with self._stubs(flip=flip, curr_port=curr_port): + content = dict(port_id=None) + ret = self.plugin.update_floatingip(self.context, flip["id"], + dict(floatingip=content)) + self.assertEqual(ret.get("fixed_ip_address"), None) + self.assertEqual(ret.get("port_id"), None) + + def test_update_with_non_existent_port_should_fail(self): + addr = netaddr.IPAddress("10.0.0.1") + flip = dict(id="3", fixed_ip_address="172.16.1.1", address=int(addr), + address_readable=str(addr)) + + with self._stubs(flip=flip): + with self.assertRaises(ex.PortNotFound): + content = dict(port_id="123") + self.plugin.update_floatingip(self.context, flip["id"], + dict(floatingip=content)) + + def test_update_with_port_with_no_fixed_ip_avail_should_fail(self): + new_port = dict(id="123") + addr = netaddr.IPAddress("10.0.0.1") + flip = dict(id="3", fixed_ip_address="172.16.1.1", address=int(addr), + address_readable=str(addr)) + + with self._stubs(flip=flip, new_port=new_port): + with self.assertRaises(q_ex.NoAvailableFixedIPsForPort): + content = dict(port_id="123") + self.plugin.update_floatingip(self.context, flip["id"], + dict(floatingip=content)) + + def test_update_with_same_port_should_fail(self): + new_port = dict(id="123") + curr_port = dict(id="123") + addr = netaddr.IPAddress("10.0.0.1") + flip = dict(id="3", fixed_ip_address="172.16.1.1", address=int(addr), + address_readable=str(addr)) + + with self._stubs(flip=flip, new_port=new_port, curr_port=curr_port): + with self.assertRaises(q_ex.PortAlreadyAssociatedToFloatingIP): + content = dict(port_id="123") + self.plugin.update_floatingip(self.context, flip["id"], + dict(floatingip=content)) + + def test_update_with_no_port_and_no_previous_port_should_fail(self): + addr = netaddr.IPAddress("10.0.0.1") + flip = dict(id="3", fixed_ip_address="172.16.1.1", address=int(addr), + address_readable=str(addr)) + + with self._stubs(flip=flip): + with self.assertRaises(q_ex.FloatingIPUpdateNoPortIdSupplied): + content = dict(port_id=None) + self.plugin.update_floatingip(self.context, flip["id"], + dict(floatingip=content)) + + def test_update_with_missing_port_id_param_should_fail(self): + with self._stubs(): + with self.assertRaises(ex.BadRequest): + content = {} + self.plugin.update_floatingip(self.context, "123", + dict(floatingip=content))