From 4376ed417f68bc03af65bd9e9a3ae8ba8870160c Mon Sep 17 00:00:00 2001 From: Justin Hammond Date: Wed, 29 Jul 2015 11:31:23 -0500 Subject: [PATCH] Fixes for provider net subnet show and IP version Fixes JIRA:NCP-1612 JIRA:NCP-1613 JIRA:NCP-1617 Fixes quotas and disallows fixed_ip create Fixes JIRA:NCP-1626 JIRA:NCP-1389 Bonus: JIRA:NCP-1592 Shared IP general fixes Fixes JIRA:NCP-1389 added quota for total IPs/port/network with new conf: total_ips_allowed_on_port Fixes JIRA:NCP-1444 added quota for shared-ips/network with new conf: shared_ips_allowed_on_network Fixes JIRA:NCP-1592 prevents all fixed ip interactions Fixes JIRA:NCP-1630 will now follow rules for 'service' checking Fixes JIRA:NCP-1631 will now follow rules for 'service' checking Fixes JIRA:NCP-1633 will check the request port length against the found port length Put update_port_for_ip into context block Fixes JIRA:NCP-1637 Conflicts: quark/exceptions.py quark/plugin.py quark/plugin_modules/ip_addresses.py quark/tests/plugin_modules/test_ports.py --- quark/db/api.py | 19 +- quark/db/models.py | 8 +- quark/exceptions.py | 16 + quark/ipam.py | 2 +- quark/plugin.py | 4 +- quark/plugin_modules/ip_addresses.py | 133 +++++- quark/plugin_views.py | 7 - .../functional/mysql/test_ip_addresses.py | 5 - .../plugin_modules/test_shared_ips.py | 409 ++++++++++++++++-- .../tests/plugin_modules/test_ip_addresses.py | 197 ++------- quark/tests/plugin_modules/test_networks.py | 8 +- quark/tests/plugin_modules/test_ports.py | 4 +- quark/tests/test_db_api.py | 5 +- 13 files changed, 577 insertions(+), 240 deletions(-) diff --git a/quark/db/api.py b/quark/db/api.py index 901ca91..9d54a71 100644 --- a/quark/db/api.py +++ b/quark/db/api.py @@ -263,8 +263,15 @@ def port_associate_ip(context, ports, address, enable_port=None): return address +def get_ports_for_address(address): + ports = [] + for assoc in address.associations: + ports.append(assoc.port) + return ports + + def update_port_associations_for_ip(context, ports, address): - assoc_ports = set(address.ports) + assoc_ports = set(get_ports_for_address(address)) new_ports = set(ports) new_address = port_associate_ip(context, new_ports - assoc_ports, address) @@ -546,8 +553,11 @@ def network_find(context, limit=None, sorts=None, marker=None, page_reverse=False, fields=None, **filters): ids = [] defaults = [] + provider_query = False if "id" in filters: ids, defaults = STRATEGY.split_network_ids(context, filters["id"]) + if not ids and defaults and "shared" not in filters: + provider_query = True if ids: filters["id"] = ids else: @@ -565,11 +575,12 @@ def network_find(context, limit=None, sorts=None, marker=None, defaults.insert(0, INVERT_DEFAULTS) filters.pop("shared") return _network_find(context, limit, sorts, marker, page_reverse, fields, - defaults=defaults, **filters) + defaults=defaults, provider_query=provider_query, + **filters) def _network_find(context, limit, sorts, marker, page_reverse, fields, - defaults=None, **filters): + defaults=None, provider_query=False, **filters): query = context.session.query(models.Network) model_filters = _model_query(context, models.Network, filters, query) @@ -581,7 +592,7 @@ def _network_find(context, limit, sorts, marker, page_reverse, fields, if filters and invert_defaults: query = query.filter(and_(not_(models.Network.id.in_(defaults)), and_(*model_filters))) - elif filters and not invert_defaults: + elif not provider_query and filters and not invert_defaults: query = query.filter(or_(models.Network.id.in_(defaults), and_(*model_filters))) diff --git a/quark/db/models.py b/quark/db/models.py index 4366990..ad250e2 100644 --- a/quark/db/models.py +++ b/quark/db/models.py @@ -175,18 +175,12 @@ class IPAddress(BASEV2, models.HasId): def is_shared(self): return self.address_type == ip_types.SHARED - def has_shared_owner(self): + def has_any_shared_owner(self): for assoc in self["associations"]: if assoc.service != 'none' and assoc.service is not None: return True return False - def get_shared_owner(self): - for assoc in self["associations"]: - if assoc.service != 'none' and assoc.service is not None: - return assoc.port - return None - def set_service_for_port(self, port, service): for assoc in self["associations"]: if assoc.port_id == port["id"]: diff --git a/quark/exceptions.py b/quark/exceptions.py index 1903f3e..12d05a1 100644 --- a/quark/exceptions.py +++ b/quark/exceptions.py @@ -177,3 +177,19 @@ class PortAlreadyAssociatedToFloatingIP(exceptions.BadRequest): class FloatingIPUpdateNoPortIdSupplied(exceptions.BadRequest): message = _("When no port is currently associated to the floating if, " "port_id is required but was not supplied") + + +class PortOrDeviceNotFound(exceptions.PortNotFound): + message = _("Suitable port or device could not be found") + + +class NotAllPortOrDeviceFound(exceptions.NotFound): + message = _("Not all ports or devices from request could be found") + + +class CannotAddMoreIPsToPort(exceptions.OverQuota): + message = _("Cannot add more IPs to port") + + +class CannotCreateMoreSharedIPs(exceptions.OverQuota): + message = _("Cannot create more shared IPs on selected network") diff --git a/quark/ipam.py b/quark/ipam.py index 1de318f..8d45a43 100644 --- a/quark/ipam.py +++ b/quark/ipam.py @@ -608,7 +608,7 @@ class QuarkIpam(object): def _try_reallocate_ip_address(ipam_log, ip_addr=None): new_addresses.extend(self.attempt_to_reallocate_ip( - context, net_id, port_id, reuse_after, version=None, + context, net_id, port_id, reuse_after, version=version, ip_address=ip_addr, segment_id=segment_id, subnets=subnets, **kwargs)) diff --git a/quark/plugin.py b/quark/plugin.py index 1c4fbfc..791f717 100644 --- a/quark/plugin.py +++ b/quark/plugin.py @@ -88,8 +88,8 @@ quark_quota_opts = [ default=1, help=_('Maximum v6 subnets per network')), cfg.IntOpt('quota_fixed_ips_per_port', - default=5, - help=_('Maximum number of fixed IPs per port')) + default=6, + help=_('Maximum number of fixed IPs per port')), ] diff --git a/quark/plugin_modules/ip_addresses.py b/quark/plugin_modules/ip_addresses.py index ec6867b..e1f3448 100644 --- a/quark/plugin_modules/ip_addresses.py +++ b/quark/plugin_modules/ip_addresses.py @@ -14,7 +14,6 @@ # under the License. from neutron.common import exceptions -from neutron import quota from oslo_config import cfg from oslo_log import log as logging import webob @@ -28,12 +27,47 @@ from quark import plugin_views as v CONF = cfg.CONF LOG = logging.getLogger(__name__) +# NOTE(roaet): this number includes the assumed 'given ips' and any additional +total_ips_on_port = {'00000000-0000-0000-0000-000000000000': 6, + '11111111-1111-1111-1111-111111111111': 1, + '*': 5} + +# NOTE(roaet): shared ips are treated like any other IP and will consume +# from total_ips_on_port +shared_ip_on_network = {'00000000-0000-0000-0000-000000000000': 5, + '11111111-1111-1111-1111-111111111111': 0, + '*': -1} + +quark_ip_addr_opts = [ + cfg.BoolOpt('ipaddr_allow_fixed_ip', + default=False, + help=_('Controls if /ip_addresses can make fixed IPs or not')), + cfg.DictOpt('total_ips_allowed_on_port', + default=total_ips_on_port, + help=_('Defines how many IPs may be on a port by network in ' + 'key:quantity format')), + cfg.DictOpt('shared_ips_allowed_on_network', + default=shared_ip_on_network, + help=_('Defines how many shared IPs may be made on a network ' + 'in key:quantity format')) +] + +CONF.register_opts(quark_ip_addr_opts, "QUARK") + # NOTE(thomasem): Since IP addresses are only at the subnet level, use # the QuarkIpamANY strategy, due to other IPAM strategies only being # relevant at the network level (port create). ipam_driver = ipam.IPAM_REGISTRY.get_strategy(ipam.QuarkIpamANY.get_name()) +def _can_add_ip_to_port_on_network(context, network_id, db_port): + return True + + +def _can_create_shared_ip_on_network(context, network_id): + return True + + def get_ip_addresses(context, **filters): LOG.info("get_ip_addresses for tenant %s" % context.tenant_id) if not filters: @@ -74,11 +108,25 @@ def validate_and_fetch_segment(ports, network_id): return segment_id -def validate_port_ip_quotas(context, ports): +def validate_port_ip_quotas(context, network, ports): + if network not in CONF.QUARK.total_ips_allowed_on_port: + network = '*' + limit = CONF.QUARK.total_ips_allowed_on_port.get(network) + if limit < 0: + return for port in ports: - addresses = port.get("ip_addresses", []) - quota.QUOTAS.limit_check(context, context.tenant_id, - fixed_ips_per_port=len(addresses) + 1) + if len(port.associations) + 1 > limit: + raise quark_exceptions.CannotAddMoreIPsToPort() + + +def validate_shared_ips_quotas(context, network, addresses): + if network not in CONF.QUARK.shared_ips_allowed_on_network: + network = '*' + limit = CONF.QUARK.shared_ips_allowed_on_network.get(network) + if limit < 0: + return + if len(addresses) + 1 > limit: + raise quark_exceptions.CannotCreateMoreSharedIPs() def _shared_ip_request(ip_address): @@ -102,6 +150,10 @@ def create_ip_address(context, body): LOG.info("create_ip_address for tenant %s" % context.tenant_id) iptype = (ip_types.SHARED if _shared_ip_request(body) else ip_types.FIXED) + if iptype == ip_types.FIXED and not CONF.QUARK.ipaddr_allow_fixed_ip: + raise exceptions.BadRequest(resource="ip_addresses", + msg="Only shared IPs may be made with " + "this resource.") ip_dict = body.get("ip_address") port_ids = ip_dict.get('port_ids', []) network_id = ip_dict.get('network_id') @@ -129,8 +181,10 @@ def create_ip_address(context, body): new_addresses = [] ports = [] + by_device = False with context.session.begin(): if network_id and device_ids: + by_device = True for device_id in device_ids: port = db_api.port_find( context, network_id=network_id, device_id=device_id, @@ -150,8 +204,19 @@ def create_ip_address(context, body): raise exceptions.PortNotFound(port_id=port_ids, net_id=network_id) + if ((by_device and len(device_ids) != len(ports)) or + (not by_device and len(port_ids) != len(ports))): + raise quark_exceptions.NotAllPortOrDeviceFound() + segment_id = validate_and_fetch_segment(ports, network_id) - validate_port_ip_quotas(context, ports) + if iptype == ip_types.SHARED: + old_addresses = db_api.ip_address_find(context, + tenant_id=context.tenant_id, + network_id=network_id, + address_type=ip_types.SHARED, + scope=db_api.ALL) + validate_shared_ips_quotas(context, network_id, old_addresses) + validate_port_ip_quotas(context, network_id, ports) # Shared Ips are only new IPs. Two use cases: if we got device_id # or if we got port_ids. We should check the case where we got port_ids @@ -188,6 +253,7 @@ def _raise_if_shared_and_enabled(address_request, address_model): def update_ip_address(context, id, ip_address): + """Due to NCP-1592 ensure that address_type cannot change after update.""" LOG.info("update_ip_address %s for tenant %s" % (id, context.tenant_id)) ports = [] with context.session.begin(): @@ -196,6 +262,11 @@ def update_ip_address(context, id, ip_address): if not address: raise exceptions.NotFound( message="No IP address found with id=%s" % id) + iptype = address.address_type + if iptype == ip_types.FIXED and not CONF.QUARK.ipaddr_allow_fixed_ip: + raise exceptions.BadRequest( + resource="ip_addresses", + msg="Fixed ips cannot be updated using this interface.") reset = ip_address['ip_address'].get('reset_allocation_time', False) if reset and address['deallocated'] == 1: @@ -207,8 +278,15 @@ def update_ip_address(context, id, ip_address): raise webob.exc.HTTPForbidden(detail=msg) port_ids = ip_address['ip_address'].get('port_ids') + if iptype == ip_types.SHARED: + has_owner = address.has_any_shared_owner() if port_ids: + if iptype == ip_types.FIXED and len(port_ids) > 1: + raise exceptions.BadRequest( + resource="ip_addresses", + msg="Fixed ips cannot be updated with more than one port.") + _raise_if_shared_and_enabled(ip_address, address) ports = db_api.port_find(context, tenant_id=context.tenant_id, id=port_ids, scope=db_api.ALL) @@ -219,7 +297,13 @@ def update_ip_address(context, id, ip_address): message="No ports not found with ids=%s" % port_ids) validate_and_fetch_segment(ports, address["network_id"]) - validate_port_ip_quotas(context, ports) + validate_port_ip_quotas(context, address.network_id, ports) + + if iptype == ip_types.SHARED and has_owner: + for assoc in address.associations: + pid = assoc.port_id + if pid not in port_ids and 'none' != assoc.service: + raise quark_exceptions.PortRequiresDisassociation() LOG.info("Updating IP address, %s, to only be used by the" "following ports: %s" % (address.address_readable, @@ -227,10 +311,10 @@ def update_ip_address(context, id, ip_address): new_address = db_api.update_port_associations_for_ip(context, ports, address) + elif iptype == ip_types.SHARED and has_owner: + raise quark_exceptions.PortRequiresDisassociation() else: - if port_ids is not None: - ipam_driver.deallocate_ip_address( - context, address) + ipam_driver.deallocate_ip_address(context, address) return v._make_ip_dict(address) return v._make_ip_dict(new_address) @@ -242,14 +326,13 @@ def delete_ip_address(context, id): : param id: UUID representing the ip address to delete. """ LOG.info("delete_ip_address %s for tenant %s" % (id, context.tenant_id)) - with context.session.begin(): ip_address = db_api.ip_address_find( context, id=id, tenant_id=context.tenant_id, scope=db_api.ONE) if not ip_address or ip_address.deallocated: raise quark_exceptions.IpAddressNotFound(addr_id=id) - if _shared_ip_and_active(ip_address): + if ip_address.has_any_shared_owner(): raise quark_exceptions.PortRequiresDisassociation() db_api.update_port_associations_for_ip(context, [], ip_address) @@ -334,18 +417,20 @@ def update_port_for_ip_address(context, ip_id, id, port): """ LOG.info("update_port %s for tenant %s" % (id, context.tenant_id)) sanitize_list = ['service'] - addr = db_api.ip_address_find(context, id=ip_id, scope=db_api.ONE) - if not addr: - raise quark_exceptions.IpAddressNotFound(addr_id=ip_id) - port_db = db_api.port_find(context, id=id, scope=db_api.ONE) - if not port_db: - raise quark_exceptions.PortNotFound(port_id=id) - port_dict = {k: port['port'][k] for k in sanitize_list} + with context.session.begin(): + addr = db_api.ip_address_find(context, id=ip_id, scope=db_api.ONE) + if not addr: + raise quark_exceptions.IpAddressNotFound(addr_id=ip_id) + port_db = db_api.port_find(context, id=id, scope=db_api.ONE) + if not port_db: + raise quark_exceptions.PortNotFound(port_id=id) + port_dict = {k: port['port'][k] for k in sanitize_list} - require_da = False - service = port_dict.get('service') + require_da = False + service = port_dict.get('service') - if require_da and _shared_ip_and_active(addr, except_port=id): - raise quark_exceptions.PortRequiresDisassociation() - addr.set_service_for_port(port_db, service) + if require_da and _shared_ip_and_active(addr, except_port=id): + raise quark_exceptions.PortRequiresDisassociation() + addr.set_service_for_port(port_db, service) + context.session.add(addr) return v._make_port_for_ip_dict(addr, port_db) diff --git a/quark/plugin_views.py b/quark/plugin_views.py index c4a253d..aa4b736 100644 --- a/quark/plugin_views.py +++ b/quark/plugin_views.py @@ -42,10 +42,6 @@ quark_view_opts = [ default=True, help=_('Controls whether or not to show ip_policy_id for' 'subnets')), - cfg.BoolOpt('show_port_service', - default=False, - help=_('Controls whether or not to show service for' - 'ports')) ] CONF.register_opts(quark_view_opts, "QUARK") @@ -176,9 +172,6 @@ def _port_dict(port, fields=None): "device_id": port.get("device_id"), "device_owner": port.get("device_owner")} - if CONF.QUARK.show_port_service: - res['service'] = port.get("service") - if "mac_address" in res and res["mac_address"]: mac = str(netaddr.EUI(res["mac_address"])).replace('-', ':') res["mac_address"] = mac diff --git a/quark/tests/functional/mysql/test_ip_addresses.py b/quark/tests/functional/mysql/test_ip_addresses.py index 010dd4f..5115767 100644 --- a/quark/tests/functional/mysql/test_ip_addresses.py +++ b/quark/tests/functional/mysql/test_ip_addresses.py @@ -3,7 +3,6 @@ import netaddr import contextlib -from oslo_config import cfg from quark.db import api as db_api from quark.db import ip_types import quark.ipam @@ -37,13 +36,9 @@ class QuarkSharedIPs(MySqlBaseFunctionalTest): def setUp(self): super(QuarkSharedIPs, self).setUp() - self.old_show_port_service = cfg.CONF.QUARK.show_port_service - cfg.CONF.set_override('show_port_service', True, 'QUARK') def tearDown(self): super(QuarkSharedIPs, self).tearDown() - cfg.CONF.set_override('show_port_service', self.old_show_port_service, - 'QUARK') @contextlib.contextmanager def _stubs(self, network_info, subnet_info, ports_info): diff --git a/quark/tests/functional/plugin_modules/test_shared_ips.py b/quark/tests/functional/plugin_modules/test_shared_ips.py index fd43c93..d535094 100644 --- a/quark/tests/functional/plugin_modules/test_shared_ips.py +++ b/quark/tests/functional/plugin_modules/test_shared_ips.py @@ -18,9 +18,10 @@ import netaddr import contextlib -from oslo_config import cfg +from neutron.common import exceptions from quark.db import ip_types +from quark.db import api as db_api from quark import exceptions as q_exceptions import quark.ipam import quark.plugin @@ -54,13 +55,14 @@ class QuarkSharedIPs(BaseFunctionalTest): def setUp(self): super(QuarkSharedIPs, self).setUp() - self.old_show_port_service = cfg.CONF.QUARK.show_port_service - cfg.CONF.set_override('show_port_service', True, 'QUARK') def tearDown(self): super(QuarkSharedIPs, self).tearDown() - cfg.CONF.set_override('show_port_service', self.old_show_port_service, - 'QUARK') + + def _make_port_body(self, service): + body = dict(service=service) + port_info = {"port": dict(body)} + return port_info @contextlib.contextmanager def _stubs(self, network_info, subnet_info, ports_info): @@ -81,12 +83,207 @@ class QuarkSharedIPs(BaseFunctionalTest): ports.append(port_api.create_port(self.context, port_info)) yield net, sub, ports - def test_create_shared_ips_with_port_ids(self): + def test_delete_ip_with_shared_owner_error(self): - def _make_body(ip): - fix_ip = dict(ip_address=ip, subnet_id=sub['id']) - port_info = {"port": dict(fixed_ips=[fix_ip])} - return port_info + with self._stubs(self.network, self.subnet, self.ports_info2) as ( + net, sub, ports): + + port_ids = [ports[0]['id'], ports[1]['id']] + p_id = ports[0]['id'] + shared_ip = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + ip = ip_api.create_ip_address(self.context, shared_ip) + port_ip_update = ip_api.update_port_for_ip_address + port_ip_update(self.context, ip['id'], p_id, + self._make_port_body('derp')) + + with self.assertRaises(self.disassociate_exception): + ip_api.delete_ip_address(self.context, ip['id']) + + def test_update_shared_ip_with_unowned_ports_is_okay(self): + + with self._stubs(self.network, self.subnet, self.ports_info4) as ( + net, sub, ports): + + port_ids = [ports[0]['id'], ports[1]['id']] + shared_ip = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + ip = ip_api.create_ip_address(self.context, shared_ip) + self.assertEqual(ip_types.SHARED, ip['type']) + port_ids = [ports[0]['id'], ports[3]['id']] + shared_ip = {'ip_address': dict(port_ids=port_ids)} + + ip = ip_api.update_ip_address(self.context, ip['id'], shared_ip) + + ports_ip = ip_api.get_ports_for_ip_address(self.context, ip['id']) + self.assertEqual(2, len(ports_ip)) + for port in ports_ip: + self.assertTrue(port['id'] in port_ids) + + def test_has_shared_owner_detection(self): + with self._stubs(self.network, self.subnet, self.ports_info4) as ( + net, sub, ports): + + port_ids = [ports[0]['id'], ports[1]['id']] + p_id = ports[0]['id'] + + shared_ip = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + ip = ip_api.create_ip_address(self.context, shared_ip) + self.assertEqual(ip_types.SHARED, ip['type']) + ip_db = db_api.ip_address_find(self.context, id=ip['id'], + scope=db_api.ONE) + self.assertFalse(ip_db.has_any_shared_owner()) + + port_ip_update = ip_api.update_port_for_ip_address + port_ip_update(self.context, ip['id'], p_id, + self._make_port_body('derp')) + + self.assertTrue(ip_db.has_any_shared_owner()) + + def test_update_shared_ip_with_owned_port_no_error_if_present(self): + + with self._stubs(self.network, self.subnet, self.ports_info4) as ( + net, sub, ports): + + port_ids = [ports[0]['id'], ports[1]['id']] + p_id = ports[0]['id'] + + shared_ip = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + ip = ip_api.create_ip_address(self.context, shared_ip) + self.assertEqual(ip_types.SHARED, ip['type']) + + port_ip_update = ip_api.update_port_for_ip_address + port_ip_update(self.context, ip['id'], p_id, + self._make_port_body('derp')) + + port_ids = [ports[0]['id'], ports[2]['id']] + shared_ip = {'ip_address': dict(port_ids=port_ids)} + + ip_api.update_ip_address(self.context, ip['id'], shared_ip) + + def test_update_shared_ip_with_owned_port_no_error_if_present_alone(self): + + with self._stubs(self.network, self.subnet, self.ports_info4) as ( + net, sub, ports): + + port_ids = [ports[0]['id'], ports[1]['id']] + p_id = ports[0]['id'] + + shared_ip = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + ip = ip_api.create_ip_address(self.context, shared_ip) + self.assertEqual(ip_types.SHARED, ip['type']) + + port_ip_update = ip_api.update_port_for_ip_address + port_ip_update(self.context, ip['id'], p_id, + self._make_port_body('derp')) + + port_ids = [ports[0]['id']] + shared_ip = {'ip_address': dict(port_ids=port_ids)} + + ip_api.update_ip_address(self.context, ip['id'], shared_ip) + + def test_update_shared_ip_with_owned_port_no_error_if_adding(self): + + with self._stubs(self.network, self.subnet, self.ports_info4) as ( + net, sub, ports): + + port_ids = [ports[0]['id'], ports[1]['id']] + p_id = ports[0]['id'] + + shared_ip = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + ip = ip_api.create_ip_address(self.context, shared_ip) + self.assertEqual(ip_types.SHARED, ip['type']) + + port_ip_update = ip_api.update_port_for_ip_address + port_ip_update(self.context, ip['id'], p_id, + self._make_port_body('derp')) + port_ids = [ports[0]['id'], ports[1]['id'], ports[2]['id']] + + shared_ip = {'ip_address': dict(port_ids=port_ids)} + + ip_api.update_ip_address(self.context, ip['id'], shared_ip) + + def test_update_shared_ip_with_owned_port_no_error_if_same_list(self): + + with self._stubs(self.network, self.subnet, self.ports_info4) as ( + net, sub, ports): + + port_ids = [ports[0]['id'], ports[1]['id']] + p_id = ports[0]['id'] + + shared_ip = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + ip = ip_api.create_ip_address(self.context, shared_ip) + self.assertEqual(ip_types.SHARED, ip['type']) + + port_ip_update = ip_api.update_port_for_ip_address + port_ip_update(self.context, ip['id'], p_id, + self._make_port_body('derp')) + + shared_ip = {'ip_address': dict(port_ids=port_ids)} + + ip_api.update_ip_address(self.context, ip['id'], shared_ip) + + def test_update_shared_ip_with_owned_port_error_deallocate(self): + + with self._stubs(self.network, self.subnet, self.ports_info4) as ( + net, sub, ports): + + port_ids = [ports[0]['id'], ports[1]['id']] + p_id = ports[0]['id'] + + shared_ip = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + ip = ip_api.create_ip_address(self.context, shared_ip) + self.assertEqual(ip_types.SHARED, ip['type']) + + port_ip_update = ip_api.update_port_for_ip_address + port_ip_update(self.context, ip['id'], p_id, + self._make_port_body('derp')) + + port_ids = [] + shared_ip = {'ip_address': dict(port_ids=port_ids)} + + with self.assertRaises(self.disassociate_exception): + ip_api.update_ip_address(self.context, ip['id'], shared_ip) + + def test_update_shared_ip_with_owned_port_error(self): + + with self._stubs(self.network, self.subnet, self.ports_info4) as ( + net, sub, ports): + + port_ids = [ports[0]['id'], ports[1]['id']] + p_id = ports[0]['id'] + + shared_ip = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + ip = ip_api.create_ip_address(self.context, shared_ip) + self.assertEqual(ip_types.SHARED, ip['type']) + + port_ip_update = ip_api.update_port_for_ip_address + port_ip_update(self.context, ip['id'], p_id, + self._make_port_body('derp')) + + port_ids = [ports[2]['id'], ports[1]['id']] + shared_ip = {'ip_address': dict(port_ids=port_ids)} + + with self.assertRaises(self.disassociate_exception): + ip_api.update_ip_address(self.context, ip['id'], shared_ip) + + def test_create_shared_ips_with_port_ids(self): with self._stubs(self.network, self.subnet, self.ports_info2) as ( net, sub, ports): @@ -103,11 +300,6 @@ class QuarkSharedIPs(BaseFunctionalTest): def test_shared_ip_in_fixed_ip_list(self): - def _make_body(service): - body = dict(service=service) - port_info = {"port": dict(body)} - return port_info - with self._stubs(self.network, self.subnet, self.ports_info2) as ( net, sub, ports): @@ -128,7 +320,7 @@ class QuarkSharedIPs(BaseFunctionalTest): port_ip_update = ip_api.update_port_for_ip_address updated_port = port_ip_update(self.context, ip['id'], - p_id, _make_body('derp')) + p_id, self._make_port_body('derp')) self.assertEqual('derp', updated_port.get('service')) port = port_api.get_port(self.context, p_id) @@ -136,11 +328,6 @@ class QuarkSharedIPs(BaseFunctionalTest): def test_ip_port_list_has_services(self): - def _make_body(service): - body = dict(service=service) - port_info = {"port": dict(body)} - return port_info - with self._stubs(self.network, self.subnet, self.ports_info2) as ( net, sub, ports): @@ -154,7 +341,7 @@ class QuarkSharedIPs(BaseFunctionalTest): ip = ip_api.create_ip_address(self.context, shared_ip) port_ip_update = ip_api.update_port_for_ip_address port_ip_update(self.context, ip['id'], - ports[0]['id'], _make_body('derp')) + ports[0]['id'], self._make_port_body('derp')) ports_ip = ip_api.get_ports_for_ip_address(self.context, ip['id']) self.assertEqual(2, len(ports_ip)) @@ -168,11 +355,6 @@ class QuarkSharedIPs(BaseFunctionalTest): def test_can_delete_ip_without_active_port(self): - def _make_body(service): - body = dict(service=service) - port_info = {"port": dict(body)} - return port_info - with self._stubs(self.network, self.subnet, self.ports_info2) as ( net, sub, ports): device_ids = [ports[0]['device_id'], ports[1]['device_id']] @@ -186,11 +368,6 @@ class QuarkSharedIPs(BaseFunctionalTest): def test_cannot_delete_ip_with_active_port(self): - def _make_body(service): - body = dict(service=service) - port_info = {"port": dict(body)} - return port_info - with self._stubs(self.network, self.subnet, self.ports_info2) as ( net, sub, ports): device_ids = [ports[0]['device_id'], ports[1]['device_id']] @@ -200,7 +377,173 @@ class QuarkSharedIPs(BaseFunctionalTest): ip = ip_api.create_ip_address(self.context, shared_ip) port_ip_update = ip_api.update_port_for_ip_address port_ip_update(self.context, ip['id'], - ports[0]['id'], _make_body('derp')) + ports[0]['id'], self._make_port_body('derp')) with self.assertRaises(self.disassociate_exception): ip_api.delete_ip_address(self.context, ip['id']) + + +class QuarkSharedIPsQuotaCheck(BaseFunctionalTest): + def __init__(self, *args, **kwargs): + super(QuarkSharedIPsQuotaCheck, self).__init__(*args, **kwargs) + self.disassociate_exception = q_exceptions.PortRequiresDisassociation + self.cidr = "192.168.2.0/24" + self.ip_network = netaddr.IPNetwork(self.cidr) + network = dict(name="public", tenant_id="fake", network_plugin="BASE") + self.network = {"network": network} + subnet = dict(ip_version=4, next_auto_assign_ip=2, + cidr=self.cidr, first_ip=self.ip_network.first, + last_ip=self.ip_network.last, ip_policy=None, + tenant_id="fake") + self.subnet = {"subnet": subnet} + port1 = {'port': dict(device_id='a')} + port2 = {'port': dict(device_id='b')} + port3 = {'port': dict(device_id='c')} + port4 = {'port': dict(device_id='d')} + self.ports_info2 = [port1, port2] + self.ports_info4 = [port1, port2, port3, port4] + + def setUp(self): + super(QuarkSharedIPsQuotaCheck, self).setUp() + + def tearDown(self): + super(QuarkSharedIPsQuotaCheck, self).tearDown() + + @contextlib.contextmanager + def _stubs(self, network_info, subnet_info, ports_info): + self.ipam = quark.ipam.QuarkIpamANY() + with contextlib.nested( + mock.patch("neutron.common.rpc.get_notifier")): + self.context.is_admin = True + net = network_api.create_network(self.context, network_info) + mac = {'mac_address_range': dict(cidr="AA:BB:CC")} + macrng_api.create_mac_address_range(self.context, mac) + self.context.is_admin = False + subnet_info['subnet']['network_id'] = net['id'] + sub = subnet_api.create_subnet(self.context, subnet_info) + ports = [] + for port_info in ports_info: + port_info['port']['network_id'] = net['id'] + ports.append(port_api.create_port(self.context, port_info)) + yield net, sub, ports + + def test_create_shared_ip_over_public_network_quota(self): + network = dict(name="public", tenant_id="fake", network_plugin="BASE", + id='00000000-0000-0000-0000-000000000000') + network = {"network": network} + + with self._stubs(network, self.subnet, self.ports_info4) as ( + net, sub, ports): + + port_ids = [ports[0]['id'], ports[1]['id']] + shared_ip = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + port_ids = [ports[2]['id'], ports[3]['id']] + shared_ip2 = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + + # NOTE(roaet): this is hardcoded to 5 and will fail after 5 + for i in xrange(5): + # NOTE(roaet): need to do this modulo stuff to not hit IP quota + if i % 2 == 0: + ip_api.create_ip_address(self.context, shared_ip) + else: + ip_api.create_ip_address(self.context, shared_ip2) + + with self.assertRaises(q_exceptions.CannotCreateMoreSharedIPs): + ip_api.create_ip_address(self.context, shared_ip) + + def test_create_shared_ip_over_isolated_network_quota(self): + with self._stubs(self.network, self.subnet, self.ports_info4) as ( + net, sub, ports): + + port_ids = [ports[0]['id'], ports[1]['id']] + shared_ip = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + port_ids = [ports[2]['id'], ports[3]['id']] + shared_ip2 = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + + # NOTE(roaet): this is hardcoded to 5 + for i in xrange(4): + # NOTE(roaet): need to do this modulo stuff to not hit IP quota + if i % 2 == 0: + ip_api.create_ip_address(self.context, shared_ip) + else: + ip_api.create_ip_address(self.context, shared_ip2) + + # NOTE(roaet): this should not fail + ip_api.create_ip_address(self.context, shared_ip) + + def test_create_shared_ip_over_service_network_quota(self): + network = dict(name="service", tenant_id="fake", network_plugin="BASE", + id='11111111-1111-1111-1111-111111111111') + network = {"network": network} + + with self._stubs(network, self.subnet, self.ports_info4) as ( + net, sub, ports): + + port_ids = [ports[0]['id'], ports[1]['id']] + shared_ip = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + + # NOTE(roaet): this is hardcoded to 0 so should fail instantly + with self.assertRaises(q_exceptions.CannotCreateMoreSharedIPs): + ip_api.create_ip_address(self.context, shared_ip) + + def test_create_shared_ip_over_public_total_ip_on_port_quota(self): + network = dict(name="public", tenant_id="fake", network_plugin="BASE", + id='00000000-0000-0000-0000-000000000000') + network = {"network": network} + + with self._stubs(network, self.subnet, self.ports_info2) as ( + net, sub, ports): + + port_ids = [ports[0]['id'], ports[1]['id']] + shared_ip = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + # NOTE(roaet): this is hardcoded to 6 so should fail after 5 + # since a port comes with 1 IP already + for i in xrange(5): + ip_api.create_ip_address(self.context, shared_ip) + + with self.assertRaises(exceptions.OverQuota): + ip_api.create_ip_address(self.context, shared_ip) + + def test_create_shared_ip_over_isolated_total_ip_on_port_quota(self): + with self._stubs(self.network, self.subnet, self.ports_info2) as ( + net, sub, ports): + + port_ids = [ports[0]['id'], ports[1]['id']] + shared_ip = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + # NOTE(roaet): this is hardcoded to 5 should fail after 4 + # since a port comes with 1 IP already + for i in xrange(4): + ip_api.create_ip_address(self.context, shared_ip) + + with self.assertRaises(exceptions.OverQuota): + ip_api.create_ip_address(self.context, shared_ip) + + def test_create_shared_ip_over_service_total_ip_on_port_quota(self): + network = dict(name="service", tenant_id="fake", network_plugin="BASE", + id='11111111-1111-1111-1111-111111111111') + network = {"network": network} + + with self._stubs(network, self.subnet, self.ports_info2) as ( + net, sub, ports): + + port_ids = [ports[0]['id'], ports[1]['id']] + shared_ip = {'ip_address': dict(port_ids=port_ids, + network_id=net['id'], + version=4)} + # NOTE(roaet): this is hardcoded to 1 so should fail immediately + with self.assertRaises(exceptions.OverQuota): + ip_api.create_ip_address(self.context, shared_ip) diff --git a/quark/tests/plugin_modules/test_ip_addresses.py b/quark/tests/plugin_modules/test_ip_addresses.py index f8cdde9..4f19ffd 100644 --- a/quark/tests/plugin_modules/test_ip_addresses.py +++ b/quark/tests/plugin_modules/test_ip_addresses.py @@ -21,6 +21,7 @@ from neutron.common import exceptions from oslo_config import cfg import webob +from quark.db import ip_types from quark.db import models from quark import exceptions as quark_exceptions from quark.plugin_modules import ip_addresses @@ -86,6 +87,8 @@ class TestIpAddresses(test_quark_plugin.TestQuarkPlugin): yield def test_create_ip_address_by_network_and_device(self): + old_cfg = cfg.CONF.QUARK.ipaddr_allow_fixed_ip + cfg.CONF.set_override('ipaddr_allow_fixed_ip', True, "QUARK") port = dict(id=1, network_id=2, ip_addresses=[]) ip = dict(id=1, address=3232235876, address_readable="192.168.1.100", subnet_id=1, network_id=2, version=4, used_by_tenant_id=1) @@ -102,8 +105,11 @@ class TestIpAddresses(test_quark_plugin.TestQuarkPlugin): self.assertEqual(response["version"], 4) self.assertEqual(response["address"], "192.168.1.100") self.assertEqual(response["tenant_id"], 1) + cfg.CONF.set_override('ipaddr_allow_fixed_ip', old_cfg, "QUARK") def test_create_ip_address_with_port(self): + old_cfg = cfg.CONF.QUARK.ipaddr_allow_fixed_ip + cfg.CONF.set_override('ipaddr_allow_fixed_ip', True, "QUARK") port = dict(id=1, network_id=2, ip_addresses=[]) ip = dict(id=1, address=3232235876, address_readable="192.168.1.100", subnet_id=1, network_id=2, version=4) @@ -118,6 +124,22 @@ class TestIpAddresses(test_quark_plugin.TestQuarkPlugin): self.assertEqual(response['network_id'], ip["network_id"]) self.assertEqual(response['port_ids'], [port["id"]]) self.assertEqual(response['subnet_id'], ip['id']) + cfg.CONF.set_override('ipaddr_allow_fixed_ip', old_cfg, "QUARK") + + def test_fail_create_ip_address_with_port_when_disallowed(self): + old_cfg = cfg.CONF.QUARK.ipaddr_allow_fixed_ip + cfg.CONF.set_override('ipaddr_allow_fixed_ip', False, "QUARK") + port = dict(id=1, network_id=2, ip_addresses=[]) + ip = dict(id=1, address=3232235876, address_readable="192.168.1.100", + subnet_id=1, network_id=2, version=4) + with self._stubs(port=port, addr=ip): + ip_address = dict(port_ids=[port["id"]]) + ip_address['version'] = 4 + ip_address['network_id'] = 2 + with self.assertRaises(exceptions.BadRequest): + self.plugin.create_ip_address(self.context, + dict(ip_address=ip_address)) + cfg.CONF.set_override('ipaddr_allow_fixed_ip', old_cfg, "QUARK") def test_create_ip_address_by_device_no_network_fails(self): with self._stubs(port={}, addr=None): @@ -134,6 +156,8 @@ class TestIpAddresses(test_quark_plugin.TestQuarkPlugin): self.plugin.create_ip_address(self.context, ip_address) def test_create_ip_address_invalid_port(self): + old_cfg = cfg.CONF.QUARK.ipaddr_allow_fixed_ip + cfg.CONF.set_override('ipaddr_allow_fixed_ip', True, "QUARK") with self._stubs(port=None, addr=None): with self.assertRaises(exceptions.PortNotFound): ip_address = { @@ -144,46 +168,7 @@ class TestIpAddresses(test_quark_plugin.TestQuarkPlugin): } } self.plugin.create_ip_address(self.context, ip_address) - - -class TestCreateIpAddressQuotaCheck(test_quark_plugin.TestQuarkPlugin): - @contextlib.contextmanager - def _stubs(self, port, addresses): - port_model = models.Port() - port_model.update(port) - - for addr in addresses: - addr_model = models.IPAddress() - addr_model.update(addr) - port_model["ip_addresses"].append(addr_model) - - with contextlib.nested( - mock.patch("quark.db.api.network_find"), - mock.patch("quark.db.api.port_find"), - mock.patch("quark.plugin_modules.ip_addresses.ipam_driver"), - mock.patch("quark.plugin_modules.ip_addresses.db_api" - ".port_associate_ip"), - mock.patch("quark.plugin_modules.ip_addresses" - ".validate_and_fetch_segment") - ) as (net_f, port_find, mock_ipam, mock_port_associate_ip, validate): - port_find.return_value = port_model - yield - - def test_create_ip_address_with_port_over_quota(self): - addresses = [{"id": ip, "address": ip} for ip in xrange(5)] - port = dict(id=1, network_id=2, ip_addresses=[]) - - ip = dict(id=1, address=3232235876, address_readable="192.168.1.100", - subnet_id=1, network_id=2, version=4) - - with self._stubs(port=port, addresses=addresses): - ip_address = dict(port_ids=[port["id"]]) - ip_address['version'] = 4 - ip_address['network_id'] = 2 - - with self.assertRaises(exceptions.OverQuota): - self.plugin.create_ip_address( - self.context, dict(ip_address=ip_address)) + cfg.CONF.set_override('ipaddr_allow_fixed_ip', old_cfg, "QUARK") @mock.patch("quark.plugin_modules.ip_addresses.v") @@ -205,6 +190,8 @@ class TestQuarkSharedIPAddressCreate(test_quark_plugin.TestQuarkPlugin): def test_create_ip_address_calls_port_associate_ip(self, mock_dbapi, mock_ipam, *args): + old_cfg = cfg.CONF.QUARK.ipaddr_allow_fixed_ip + cfg.CONF.set_override('ipaddr_allow_fixed_ip', True, "QUARK") port = dict(id=1, network_id=2, ip_addresses=[]) ip = dict(id=1, address=3232235876, address_readable="192.168.1.100", subnet_id=1, network_id=2, version=4, tenant_id=1) @@ -224,6 +211,7 @@ class TestQuarkSharedIPAddressCreate(test_quark_plugin.TestQuarkPlugin): dict(ip_address=ip_address)) mock_dbapi.port_associate_ip.assert_called_once_with( self.context, [port_model], ip_model) + cfg.CONF.set_override('ipaddr_allow_fixed_ip', old_cfg, "QUARK") def test_create_ip_address_address_type_shared(self, mock_dbapi, mock_ipam, *args): @@ -258,6 +246,8 @@ class TestQuarkSharedIPAddressCreate(test_quark_plugin.TestQuarkPlugin): def test_create_ip_address_address_type_fixed(self, mock_dbapi, mock_ipam, *args): cfg.CONF.set_override('ipam_reuse_after', 100, "QUARK") + old_cfg = cfg.CONF.QUARK.ipaddr_allow_fixed_ip + cfg.CONF.set_override('ipaddr_allow_fixed_ip', True, "QUARK") ports = [dict(id=1, network_id=2, ip_addresses=[])] ip = dict(id=1, address=3232235876, address_readable="192.168.1.100", subnet_id=1, network_id=2, version=4, tenant_id=1) @@ -283,6 +273,7 @@ class TestQuarkSharedIPAddressCreate(test_quark_plugin.TestQuarkPlugin): self.context, [ip_model], ip['network_id'], None, 100, version=ip_address['version'], ip_addresses=[], segment_id=None, address_type="fixed") + cfg.CONF.set_override('ipaddr_allow_fixed_ip', old_cfg, "QUARK") class TestQuarkSharedIPAddressPortsValid(test_quark_plugin.TestQuarkPlugin): @@ -489,6 +480,20 @@ class TestQuarkUpdateIPAddress(test_quark_plugin.TestQuarkPlugin): ip_address) self.assertEqual(response['port_ids'], [port['id']]) + def test_bad_request_fixed_update_multiple_ports(self): + port1 = dict(id=1, network_id=2, ip_addresses=[]) + port2 = dict(id=2, network_id=2, ip_addresses=[]) + ip = dict(id=1, address=3232235876, address_readable="192.168.1.100", + subnet_id=1, network_id=2, version=4, + address_type=ip_types.FIXED) + with self._stubs(ports=[port1, port2], addr=ip): + ip_address = {'ip_address': {'port_ids': [port1['id'], + port2['id']], + 'network_id': 2}} + with self.assertRaises(exceptions.BadRequest): + self.plugin.update_ip_address(self.context, ip['id'], + ip_address) + def _create_patch(self, path): patcher = patch(path) mocked = patcher.start() @@ -560,55 +565,6 @@ class TestQuarkUpdateIPAddress(test_quark_plugin.TestQuarkPlugin): self.assertEqual(response['port_ids'], []) -class TestQuarkUpdateIPAddressQuotaCheck(test_quark_plugin.TestQuarkPlugin): - @contextlib.contextmanager - def _stubs(self, port, addresses): - port_models = [] - addr_model = None - - port_model = models.Port() - port_model.update(port) - port_models.append(port_model) - - for addr in addresses: - addr_model = models.IPAddress() - addr_model.update(addr) - port_model["ip_addresses"].append(addr_model) - - db_mod = "quark.db.api" - with contextlib.nested( - mock.patch("%s.port_find" % db_mod), - mock.patch("%s.ip_address_find" % db_mod), - mock.patch("%s.port_associate_ip" % db_mod), - mock.patch("%s.port_disassociate_ip" % db_mod), - mock.patch("quark.plugin_modules.ip_addresses" - ".validate_and_fetch_segment"), - mock.patch("quark.plugin_modules.ip_addresses.ipam_driver") - ) as (port_find, ip_find, port_associate_ip, port_disassociate_ip, val, - mock_ipam): - port_find.return_value = port_models - ip_find.return_value = addr_model - port_associate_ip.side_effect = _port_associate_stub - port_disassociate_ip.side_effect = _port_disassociate_stub - mock_ipam.deallocate_ip_address.side_effect = ( - _ip_deallocate_stub) - yield - - def test_update_ip_address_port_over_quota(self): - addresses = [{"id": ip, "address": ip} for ip in xrange(5)] - - port = dict(id=1, network_id=2) - ip = dict(id=1, address=3232235876, address_readable="192.168.1.100", - subnet_id=1, network_id=2, version=4, deallocated=1, - deallocated_at='2020-01-01 00:00:00') - - with self._stubs(port=port, addresses=addresses): - ip_address = {'ip_address': {"port_ids": [port]}} - with self.assertRaises(exceptions.OverQuota): - self.plugin.update_ip_address(self.admin_context, ip['id'], - ip_address) - - class TestQuarkGetIpAddress(test_quark_plugin.TestQuarkPlugin): @contextlib.contextmanager def _stubs(self, ips, ports): @@ -814,66 +770,3 @@ class TestQuarkGetIpAddressPort(test_quark_plugin.TestQuarkPlugin): mock_dbapi.port_find.return_value = [] with self.assertRaises(exceptions.PortNotFound): self.plugin.get_port_for_ip_address(self.context, 123, 100) - - def test_update_port_service_inactive_ip(self, mock_dbapi, mock_ipam, - *args): - port = dict(id=100, network_id=2, - backend_key="derp", device_id="y") - port2 = dict(id=101, network_id=2, - backend_key="derp", device_id="x") - ip = dict(id=1, address=3232235876, address_readable="192.168.1.100", - subnet_id=1, network_id=2, version=4, address_type="shared") - port_model = models.Port() - port_model2 = models.Port() - port_model.update(port) - port_model2.update(port2) - ip_model = models.IPAddress() - ip_model.update(ip) - ip_model.ports = [port_model, port_model2] - - ip_mod = 'quark.db.models.IPAddress' - mock_port_update = patch('%s.set_service_for_port' % ip_mod) - self.addCleanup(mock_port_update.stop) - mock_port_update = mock_port_update.start() - - mock_dbapi.port_find.return_value = port_model - mock_dbapi.ip_address_find.return_value = ip_model - mock_ipam.allocate_ip_address.side_effect = ( - self._alloc_stub(ip_model)) - port_update = dict(service='derp') - port_update = {'port': port_update} - self.plugin.update_port_for_ip(self.context, 1, 100, port_update) - self.assertTrue(mock_port_update.called) - self.assertTrue(mock_port_update.called_once_with( - self.context, 100, port_update)) - - def test_update_shared_ip_deactivate(self, mock_dbapi, mock_ipam, *args): - port = dict(id=100, service='compute', network_id=2, - backend_key="derp", device_id="y") - port2 = dict(id=101, network_id=2, - backend_key="derp", device_id="x") - ip = dict(id=1, address=3232235876, address_readable="192.168.1.100", - subnet_id=1, network_id=2, version=4, address_type="shared") - port_model = models.Port() - port_model2 = models.Port() - port_model.update(port) - port_model2.update(port2) - ip_model = models.IPAddress() - ip_model.update(ip) - ip_model.ports = [port_model, port_model2] - - ip_mod = 'quark.db.models.IPAddress' - mock_port_update = patch('%s.set_service_for_port' % ip_mod) - self.addCleanup(mock_port_update.stop) - mock_port_update = mock_port_update.start() - - mock_dbapi.port_find.return_value = port_model - mock_dbapi.ip_address_find.return_value = ip_model - mock_ipam.allocate_ip_address.side_effect = ( - self._alloc_stub(ip_model)) - port_update = dict(service='derp') - port_update = {'port': port_update} - self.plugin.update_port_for_ip(self.context, 1, 100, port_update) - self.assertTrue(mock_port_update.called) - self.assertTrue(mock_port_update.called_once_with( - self.context, 100, port_update)) diff --git a/quark/tests/plugin_modules/test_networks.py b/quark/tests/plugin_modules/test_networks.py index a1c20fc..40ec478 100644 --- a/quark/tests/plugin_modules/test_networks.py +++ b/quark/tests/plugin_modules/test_networks.py @@ -155,7 +155,8 @@ class TestQuarkGetNetworksShared(test_quark_plugin.TestQuarkPlugin): self.assertEqual(1, len(net['subnets'])) net_find.assert_called_with(self.context, None, None, None, False, None, join_subnets=True, - defaults=["public_network"]) + defaults=["public_network"], + provider_query=False) def test_get_networks_shared_false(self): net0 = dict(id='public_network', tenant_id=self.context.tenant_id, @@ -168,7 +169,8 @@ class TestQuarkGetNetworksShared(test_quark_plugin.TestQuarkPlugin): {"shared": [False]}) net_find.assert_called_with(self.context, None, None, None, False, None, join_subnets=True, - defaults=[invert, "public_network"]) + defaults=[invert, "public_network"], + provider_query=False) def test_get_networks_no_shared(self): net0 = dict(id='public_network', tenant_id=self.context.tenant_id, @@ -179,7 +181,7 @@ class TestQuarkGetNetworksShared(test_quark_plugin.TestQuarkPlugin): self.plugin.get_networks(self.context, None, None, None, False) net_find.assert_called_with(self.context, None, None, None, False, None, join_subnets=True, - defaults=[]) + defaults=[], provider_query=False) class TestQuarkGetNetworkCount(test_quark_plugin.TestQuarkPlugin): diff --git a/quark/tests/plugin_modules/test_ports.py b/quark/tests/plugin_modules/test_ports.py index 69794d9..4299550 100644 --- a/quark/tests/plugin_modules/test_ports.py +++ b/quark/tests/plugin_modules/test_ports.py @@ -608,7 +608,8 @@ class TestQuarkPortCreateFixedIpsQuota(test_quark_plugin.TestQuarkPlugin): def test_create_port_fixed_ips_over_quota(self): network = {"id": 1, "tenant_id": self.context.tenant_id} fixed_ips = [{"subnet_id": 1}, {"subnet_id": 1}, {"subnet_id": 1}, - {"subnet_id": 1}, {"subnet_id": 1}, {"subnet_id": 1}] + {"subnet_id": 1}, {"subnet_id": 1}, {"subnet_id": 1}, + {"subnet_id": 1}] port = {"port": {"network_id": 1, "tenant_id": self.context.tenant_id, "device_id": 2, "fixed_ips": fixed_ips}} with self._stubs(network=network): @@ -721,6 +722,7 @@ class TestQuarkUpdatePort(test_quark_plugin.TestQuarkPlugin): {"subnet_id": 1}, {"subnet_id": 1}, {"subnet_id": 1}, + {"subnet_id": 1}, {"subnet_id": 1}]} with self._stubs( port=dict(id=1, name="myport", mac_address="0:0:0:0:0:1") diff --git a/quark/tests/test_db_api.py b/quark/tests/test_db_api.py index 73e4082..50b2a83 100644 --- a/quark/tests/test_db_api.py +++ b/quark/tests/test_db_api.py @@ -256,10 +256,12 @@ class TestDBAPI(BaseFunctionalTest): self.context.session.delete.assert_has_calls( [mock.call(mock_assocs[1]), mock.call(mock_assocs[2])]) + @mock.patch("quark.db.api.get_ports_for_address") @mock.patch("quark.db.api.port_disassociate_ip") @mock.patch("quark.db.api.port_associate_ip") def test_update_port_associations_for_ip(self, associate_mock, - disassociate_mock): + disassociate_mock, + get_associations_mock): self.context.session.add = mock.Mock() self.context.session.delete = mock.Mock() mock_ports = [models.Port(id=str(x), network_id="2", ip_addresses=[]) @@ -272,6 +274,7 @@ class TestDBAPI(BaseFunctionalTest): new_port_list = mock_ports[1:3] new_port_list.append(models.Port(id="4", network_id="2", ip_addresses=[])) + get_associations_mock.return_value = mock_ports # NOTE(thomasem): Should be the new address after associating # any new ports in the list. mock_new_address = associate_mock.return_value