diff --git a/neutron/db/dns_db.py b/neutron/db/dns_db.py index 64c8c302e09..69f08cc7890 100644 --- a/neutron/db/dns_db.py +++ b/neutron/db/dns_db.py @@ -207,9 +207,10 @@ class DNSDbMixin(object): def _delete_floatingip_from_external_dns_service(self, context, dns_domain, dns_name, records): + ips = [str(r) for r in records] try: self.dns_driver.delete_record_set(context, dns_domain, dns_name, - records) + ips) except (dns_exc.DNSDomainNotFound, dns_exc.DuplicateRecordSet) as e: LOG.exception("Error deleting Floating IP data from external " "DNS service. Name: '%(name)s'. Domain: " @@ -218,7 +219,7 @@ class DNSDbMixin(object): {"name": dns_name, "domain": dns_domain, "message": e.msg, - "ips": ', '.join(records)}) + "ips": ', '.join(ips)}) def _get_requested_state_for_external_dns_service_create(self, context, floatingip_data, @@ -238,9 +239,10 @@ class DNSDbMixin(object): def _add_ips_to_external_dns_service(self, context, dns_domain, dns_name, records): + ips = [str(r) for r in records] try: self.dns_driver.create_record_set(context, dns_domain, dns_name, - records) + ips) except (dns_exc.DNSDomainNotFound, dns_exc.DuplicateRecordSet) as e: LOG.exception("Error publishing floating IP data in external " "DNS service. Name: '%(name)s'. Domain: " diff --git a/neutron/db/l3_db.py b/neutron/db/l3_db.py index 0b02d0d31d3..f84f20d0b3e 100644 --- a/neutron/db/l3_db.py +++ b/neutron/db/l3_db.py @@ -13,7 +13,6 @@ # under the License. import functools -import itertools import random import netaddr @@ -31,7 +30,6 @@ from neutron_lib.plugins import directory from neutron_lib.services import base as base_services from oslo_log import log as logging from oslo_utils import uuidutils -import six from sqlalchemy import orm from sqlalchemy.orm import exc @@ -50,6 +48,7 @@ from neutron.db import models_v2 from neutron.db import standardattrdescription_db as st_attr from neutron.extensions import l3 from neutron.extensions import qos_fip +from neutron.objects import base as base_obj from neutron.objects import ports as port_obj from neutron.objects import router as l3_obj from neutron.plugins.common import utils as p_utils @@ -158,14 +157,15 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, port_id) def _fix_or_kill_floating_port(self, context, port_id): - fip = (context.session.query(l3_models.FloatingIP). - filter_by(floating_port_id=port_id).first()) - if fip: + pager = base_obj.Pager(limit=1) + fips = l3_obj.FloatingIP.get_objects( + context, _pager=pager, floating_port_id=port_id) + if fips: LOG.warning("Found incorrect device_id on floating port " "%(pid)s, correcting to %(fip)s.", - {'pid': port_id, 'fip': fip.id}) + {'pid': port_id, 'fip': fips[0].id}) self._core_plugin.update_port( - context, port_id, {'port': {'device_id': fip.id}}) + context, port_id, {'port': {'device_id': fips[0].id}}) else: LOG.warning("Found floating IP port %s without floating IP, " "deleting.", port_id) @@ -912,7 +912,6 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, subnet_id): subnet = self._core_plugin.get_subnet(context, subnet_id) subnet_cidr = netaddr.IPNetwork(subnet['cidr']) - fip_qry = context.session.query(l3_models.FloatingIP) try: kwargs = {'context': context, 'router_id': router_id, 'subnet_id': subnet_id} @@ -924,8 +923,9 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, if len(e.errors) == 1: raise e.errors[0].error raise l3.RouterInUse(router_id=router_id, reason=e) - for fip_db in fip_qry.filter_by(router_id=router_id): - if netaddr.IPAddress(fip_db['fixed_ip_address']) in subnet_cidr: + fip_objs = l3_obj.FloatingIP.get_objects(context, router_id=router_id) + for fip_obj in fip_objs: + if fip_obj.fixed_ip_address in subnet_cidr: raise l3.RouterInterfaceInUseByFloatingIP( router_id=router_id, subnet_id=subnet_id) @@ -1039,28 +1039,32 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, subnets]) def _get_floatingip(self, context, id): - try: - floatingip = model_query.get_by_id( - context, l3_models.FloatingIP, id) - except exc.NoResultFound: + floatingip = l3_obj.FloatingIP.get_object(context, id=id) + if not floatingip: raise l3.FloatingIPNotFound(floatingip_id=id) return floatingip def _make_floatingip_dict(self, floatingip, fields=None, process_extensions=True): - res = {'id': floatingip['id'], - 'tenant_id': floatingip['tenant_id'], - 'floating_ip_address': floatingip['floating_ip_address'], - 'floating_network_id': floatingip['floating_network_id'], - 'router_id': floatingip['router_id'], - 'port_id': floatingip['fixed_port_id'], - 'fixed_ip_address': floatingip['fixed_ip_address'], - 'status': floatingip['status']} + floating_ip_address = (str(floatingip.floating_ip_address) + if floatingip.floating_ip_address else None) + fixed_ip_address = (str(floatingip.fixed_ip_address) + if floatingip.fixed_ip_address else None) + res = {'id': floatingip.id, + 'tenant_id': floatingip.project_id, + 'floating_ip_address': floating_ip_address, + 'floating_network_id': floatingip.floating_network_id, + 'router_id': floatingip.router_id, + 'port_id': floatingip.fixed_port_id, + 'fixed_ip_address': fixed_ip_address, + 'status': floatingip.status} # NOTE(mlavalle): The following assumes this mixin is used in a # class inheriting from CommonDbMixin, which is true for all existing # plugins. + # TODO(lujinluo): Change floatingip.db_obj to floatingip once all + # codes are migrated to use Floating IP OVO object. if process_extensions: - resource_extend.apply_funcs(l3.FLOATINGIPS, res, floatingip) + resource_extend.apply_funcs(l3.FLOATINGIPS, res, floatingip.db_obj) return db_utils.resource_fields(res, fields) def _get_router_for_floatingip(self, context, internal_port, @@ -1166,7 +1170,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, internal_subnet_id = ipv4_fixed_ips[0]['subnet_id'] return internal_port, internal_subnet_id, internal_ip_address - def _get_assoc_data(self, context, fip, floatingip_db): + def _get_assoc_data(self, context, fip, floatingip_obj): """Determine/extract data associated with the internal port. When a floating IP is associated with an internal port, @@ -1177,14 +1181,14 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, """ (internal_port, internal_subnet_id, internal_ip_address) = self._internal_fip_assoc_data( - context, fip, floatingip_db['tenant_id']) + context, fip, floatingip_obj.project_id) router_id = self._get_router_for_floatingip( context, internal_port, - internal_subnet_id, floatingip_db['floating_network_id']) + internal_subnet_id, floatingip_obj.floating_network_id) return (fip['port_id'], internal_ip_address, router_id) - def _check_and_get_fip_assoc(self, context, fip, floatingip_db): + def _check_and_get_fip_assoc(self, context, fip, floatingip_obj): port_id = internal_ip_address = router_id = None if fip.get('fixed_ip_address') and not fip.get('port_id'): msg = _("fixed_ip_address cannot be specified without a port_id") @@ -1193,57 +1197,60 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, port_id, internal_ip_address, router_id = self._get_assoc_data( context, fip, - floatingip_db) + floatingip_obj) - if port_id == floatingip_db.fixed_port_id: + if port_id == floatingip_obj.fixed_port_id: # Floating IP association is not changed. return port_id, internal_ip_address, router_id - fip_qry = context.session.query(l3_models.FloatingIP) - try: - fip_qry.filter_by( + fip_exists = l3_obj.FloatingIP.objects_exist( + context, fixed_port_id=fip['port_id'], - floating_network_id=floatingip_db['floating_network_id'], - fixed_ip_address=internal_ip_address).one() + floating_network_id=floatingip_obj.floating_network_id, + fixed_ip_address=netaddr.IPAddress(internal_ip_address)) + if fip_exists: + floating_ip_address = (str(floatingip_obj.floating_ip_address) + if floatingip_obj.floating_ip_address else None) raise l3.FloatingIPPortAlreadyAssociated( port_id=fip['port_id'], - fip_id=floatingip_db['id'], - floating_ip_address=floatingip_db['floating_ip_address'], + fip_id=floatingip_obj.id, + floating_ip_address=floating_ip_address, fixed_ip=internal_ip_address, - net_id=floatingip_db['floating_network_id']) - except exc.NoResultFound: - pass + net_id=floatingip_obj.floating_network_id) - if fip and 'port_id' not in fip and floatingip_db.fixed_port_id: + if fip and 'port_id' not in fip and floatingip_obj.fixed_port_id: # NOTE(liuyulong): without the fix of bug #1610045 here could # also let floating IP can be dissociated with an empty # updating dict. - fip['port_id'] = floatingip_db.fixed_port_id + fip['port_id'] = floatingip_obj.fixed_port_id port_id, internal_ip_address, router_id = self._get_assoc_data( - context, fip, floatingip_db) + context, fip, floatingip_obj) # After all upper conditions, if updating API dict is submitted with # {'port_id': null}, then the floating IP cloud also be dissociated. return port_id, internal_ip_address, router_id - def _update_fip_assoc(self, context, fip, floatingip_db, external_port): - previous_router_id = floatingip_db.router_id + def _update_fip_assoc(self, context, fip, floatingip_obj, external_port): + previous_router_id = floatingip_obj.router_id port_id, internal_ip_address, router_id = ( - self._check_and_get_fip_assoc(context, fip, floatingip_db)) - update = {'fixed_ip_address': internal_ip_address, - 'fixed_port_id': port_id, - 'router_id': router_id, - 'last_known_router_id': previous_router_id} + self._check_and_get_fip_assoc(context, fip, floatingip_obj)) + floatingip_obj.fixed_ip_address = ( + netaddr.IPAddress(internal_ip_address) + if internal_ip_address else None) + floatingip_obj.fixed_port_id = port_id + floatingip_obj.router_id = router_id + floatingip_obj.last_known_router_id = previous_router_id if 'description' in fip: - update['description'] = fip['description'] - floatingip_db.update(update) + floatingip_obj.description = fip['description'] + floating_ip_address = (str(floatingip_obj.floating_ip_address) + if floatingip_obj.floating_ip_address else None) return {'fixed_ip_address': internal_ip_address, 'fixed_port_id': port_id, 'router_id': router_id, 'last_known_router_id': previous_router_id, - 'floating_ip_address': floatingip_db.floating_ip_address, - 'floating_network_id': floatingip_db.floating_network_id, - 'floating_ip_id': floatingip_db.id, + 'floating_ip_address': floating_ip_address, + 'floating_network_id': floatingip_obj.floating_network_id, + 'floating_ip_id': floatingip_obj.id, 'context': context} def _is_ipv4_network(self, context, net_id): @@ -1301,9 +1308,10 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, floating_fixed_ip = external_ipv4_ips[0] floating_ip_address = floating_fixed_ip['ip_address'] - floatingip_db = l3_models.FloatingIP( + floatingip_obj = l3_obj.FloatingIP( + context, id=fip_id, - tenant_id=fip['tenant_id'], + project_id=fip['tenant_id'], status=initial_status, floating_network_id=fip['floating_network_id'], floating_ip_address=floating_ip_address, @@ -1311,16 +1319,19 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, description=fip.get('description')) # Update association with internal port # and define external IP address - assoc_result = self._update_fip_assoc(context, fip, - floatingip_db, external_port) - context.session.add(floatingip_db) + assoc_result = self._update_fip_assoc( + context, fip, floatingip_obj, external_port) + floatingip_obj.create() floatingip_dict = self._make_floatingip_dict( - floatingip_db, process_extensions=False) + floatingip_obj, process_extensions=False) if self._is_dns_integration_supported: dns_data = self._process_dns_floatingip_create_precommit( context, floatingip_dict, fip) if self._is_fip_qos_supported: self._process_extra_fip_qos_create(context, fip_id, fip) + floatingip_obj = l3_obj.FloatingIP.get_object( + context, id=floatingip_obj.id) + floatingip_db = floatingip_obj.db_obj registry.notify(resources.FLOATING_IP, events.PRECOMMIT_CREATE, self, context=context, floatingip=fip, @@ -1338,6 +1349,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, self._process_dns_floatingip_create_postcommit(context, floatingip_dict, dns_data) + # TODO(lujinluo): Change floatingip_db to floatingip_obj once all + # codes are migrated to use Floating IP OVO object. resource_extend.apply_funcs(l3.FLOATINGIPS, floatingip_dict, floatingip_db) return floatingip_dict @@ -1350,21 +1363,26 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, def _update_floatingip(self, context, id, floatingip): fip = floatingip['floatingip'] with context.session.begin(subtransactions=True): - floatingip_db = self._get_floatingip(context, id) - old_floatingip = self._make_floatingip_dict(floatingip_db) - fip_port_id = floatingip_db['floating_port_id'] + floatingip_obj = self._get_floatingip(context, id) + old_floatingip = self._make_floatingip_dict(floatingip_obj) + fip_port_id = floatingip_obj.floating_port_id assoc_result = self._update_fip_assoc( - context, fip, floatingip_db, + context, fip, floatingip_obj, self._core_plugin.get_port(context.elevated(), fip_port_id)) - floatingip_dict = self._make_floatingip_dict(floatingip_db) + floatingip_obj.update() + floatingip_dict = self._make_floatingip_dict(floatingip_obj) if self._is_dns_integration_supported: dns_data = self._process_dns_floatingip_update_precommit( context, floatingip_dict) if self._is_fip_qos_supported: self._process_extra_fip_qos_update(context, - floatingip_db, + floatingip_obj, fip, old_floatingip) + floatingip_obj = l3_obj.FloatingIP.get_object( + context, id=floatingip_obj.id) + floatingip_db = floatingip_obj.db_obj + registry.notify(resources.FLOATING_IP, events.AFTER_UPDATE, self._update_fip_assoc, @@ -1374,6 +1392,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, self._process_dns_floatingip_update_postcommit(context, floatingip_dict, dns_data) + # TODO(lujinluo): Change floatingip_db to floatingip_obj once all + # codes are migrated to use Floating IP OVO object. resource_extend.apply_funcs(l3.FLOATINGIPS, floatingip_dict, floatingip_db) return old_floatingip, floatingip_dict @@ -1392,10 +1412,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, @db_api.retry_if_session_inactive() def update_floatingip_status(self, context, floatingip_id, status): """Update operational status for floating IP in neutron DB.""" - fip_query = model_query.query_with_hooks( - context, l3_models.FloatingIP).filter( - l3_models.FloatingIP.id == floatingip_id) - fip_query.update({'status': status}, synchronize_session=False) + l3_obj.FloatingIP.update_objects( + context, {'status': status}, id=floatingip_id) def _delete_floatingip(self, context, id): floatingip = self._get_floatingip(context, id) @@ -1407,7 +1425,7 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, # a transaction first to remove it ourselves because the delete_port # method will yield in its post-commit activities. self._core_plugin.delete_port(context.elevated(), - floatingip['floating_port_id'], + floatingip.floating_port_id, l3_port_check=False) return floatingip_dict @@ -1424,34 +1442,32 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, def get_floatingips(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): - marker_obj = db_utils.get_marker_obj(self, context, 'floatingip', - limit, marker) - if filters is not None: - for key, val in API_TO_DB_COLUMN_MAP.items(): - if key in filters: - filters[val] = filters.pop(key) - - return model_query.get_collection(context, l3_models.FloatingIP, - self._make_floatingip_dict, - filters=filters, fields=fields, - sorts=sorts, - limit=limit, - marker_obj=marker_obj, - page_reverse=page_reverse) + pager = base_obj.Pager(sorts, limit, page_reverse, marker) + filters = filters or {} + for key, val in API_TO_DB_COLUMN_MAP.items(): + if key in filters: + filters[val] = filters.pop(key) + floatingip_objs = l3_obj.FloatingIP.get_objects( + context, _pager=pager, validate_filters=False, **filters) + floatingip_dicts = [ + self._make_floatingip_dict(floatingip_obj, fields) + for floatingip_obj in floatingip_objs + ] + return floatingip_dicts @db_api.retry_if_session_inactive() def delete_disassociated_floatingips(self, context, network_id): - query = model_query.query_with_hooks(context, l3_models.FloatingIP) - query = query.filter_by(floating_network_id=network_id, - fixed_port_id=None, - router_id=None) - for fip in query: + fip_objs = l3_obj.FloatingIP.get_objects( + context, + floating_network_id=network_id, router_id=None, fixed_port_id=None) + + for fip in fip_objs: self.delete_floatingip(context, fip.id) @db_api.retry_if_session_inactive() def get_floatingips_count(self, context, filters=None): - return model_query.get_collection_count(context, l3_models.FloatingIP, - filters=filters) + filters = filters or {} + return l3_obj.FloatingIP.count(context, **filters) def _router_exists(self, context, router_id): try: @@ -1460,13 +1476,6 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, except l3.RouterNotFound: return False - def _floating_ip_exists(self, context, floating_ip_id): - try: - self.get_floatingip(context, floating_ip_id) - return True - except l3.FloatingIPNotFound: - return False - def prevent_l3_port_deletion(self, context, port_id): """Checks to make sure a port is allowed to be deleted. @@ -1498,7 +1507,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, # error during deletion. # Elevated context in case router is owned by another tenant if port['device_owner'] == DEVICE_OWNER_FLOATINGIP: - if not self._floating_ip_exists(context, port['device_id']): + if not l3_obj.FloatingIP.objects_exist( + context, id=port['device_id']): LOG.debug("Floating IP %(f_id)s corresponding to port " "%(port_id)s no longer exists, allowing deletion.", {'f_id': port['device_id'], 'port_id': port['id']}) @@ -1523,21 +1533,20 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, This parameter is ignored. @return: set of router-ids that require notification updates """ - router_ids = set() - with context.session.begin(subtransactions=True): - for floating_ip in self._get_floatingips_by_port_id( - context, port_id): - router_ids.add(floating_ip['router_id']) - floating_ip.update({'fixed_port_id': None, - 'fixed_ip_address': None, - 'router_id': None}) - return router_ids + floating_ip_objs = l3_obj.FloatingIP.get_objects( + context, fixed_port_id=port_id) + router_ids = {fip.router_id for fip in floating_ip_objs} + values = {'fixed_port_id': None, + 'fixed_ip_address': None, + 'router_id': None} + l3_obj.FloatingIP.update_objects( + context, values, fixed_port_id=port_id) + return router_ids def _get_floatingips_by_port_id(self, context, port_id): """Helper function to retrieve the fips associated with a port_id.""" - fip_qry = context.session.query(l3_models.FloatingIP) - return fip_qry.filter_by(fixed_port_id=port_id).all() + return l3_obj.FloatingIP.get_objects(context, fixed_port_id=port_id) def _build_routers_list(self, context, routers, gw_ports): """Subclasses can override this to add extra gateway info""" @@ -1576,20 +1585,8 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, if r.get('gw_port')) return self._build_routers_list(context, router_dicts, gw_ports) - @staticmethod - def _unique_floatingip_iterator(query): - """Iterates over only one row per floating ip. Ignores others.""" - # Group rows by fip id. They must be sorted by same. - q = query.order_by(l3_models.FloatingIP.id) - keyfunc = lambda row: row[0]['id'] - group_iterator = itertools.groupby(q, keyfunc) - - # Just hit the first row of each group - for key, value in group_iterator: - yield six.next(value) - - def _make_floatingip_dict_with_scope(self, floatingip_db, scope_id): - d = self._make_floatingip_dict(floatingip_db) + def _make_floatingip_dict_with_scope(self, floatingip_obj, scope_id): + d = self._make_floatingip_dict(floatingip_obj) d['fixed_ip_address_scope'] = scope_id return d @@ -1606,22 +1603,11 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase, if not router_ids: return [] - query = context.session.query(l3_models.FloatingIP, - models_v2.SubnetPool.address_scope_id) - query = query.join(models_v2.Port, - l3_models.FloatingIP.fixed_port_id == models_v2.Port.id) - # Outer join of Subnet can cause each ip to have more than one row. - query = query.outerjoin(models_v2.Subnet, - models_v2.Subnet.network_id == models_v2.Port.network_id) - query = query.filter(models_v2.Subnet.ip_version == 4) - query = query.outerjoin(models_v2.SubnetPool, - models_v2.Subnet.subnetpool_id == models_v2.SubnetPool.id) - - # Filter out on router_ids - query = query.filter(l3_models.FloatingIP.router_id.in_(router_ids)) - - return [self._make_floatingip_dict_with_scope(*row) - for row in self._unique_floatingip_iterator(query)] + return [ + self._make_floatingip_dict_with_scope(*scoped_fip) + for scoped_fip in l3_obj.FloatingIP.get_scoped_floating_ips( + context, router_ids) + ] def _get_sync_interfaces(self, context, router_ids, device_owners=None): """Query router interfaces that relate to list of router_ids.""" diff --git a/neutron/db/l3_dvr_db.py b/neutron/db/l3_dvr_db.py index f4f1aec2aa3..553b1832578 100644 --- a/neutron/db/l3_dvr_db.py +++ b/neutron/db/l3_dvr_db.py @@ -13,6 +13,7 @@ # under the License. import collections +import netaddr from neutron_lib.api.definitions import portbindings from neutron_lib.api import validators from neutron_lib.callbacks import events @@ -37,11 +38,11 @@ from neutron.db import api as db_api from neutron.db import l3_attrs_db from neutron.db import l3_db from neutron.db.models import allowed_address_pair as aap_models -from neutron.db.models import l3 as l3_models from neutron.db import models_v2 from neutron.extensions import l3 from neutron.ipam import utils as ipam_utils from neutron.objects import agent as ag_obj +from neutron.objects import base as base_obj from neutron.objects import l3agent as rb_obj from neutron.objects import router as l3_obj from neutron.plugins.common import utils as p_utils @@ -959,11 +960,11 @@ class _DVRAgentInterfaceMixin(object): (port_dict['status'] == const.PORT_STATUS_ACTIVE)) if not port_valid_state: return - query = context.session.query(l3_models.FloatingIP).filter( - l3_models.FloatingIP.fixed_ip_address == port_addr_pair_ip) - fip = query.first() + fips = l3_obj.FloatingIP.get_objects( + context, _pager=base_obj.Pager(limit=1), + fixed_ip_address=netaddr.IPAddress(port_addr_pair_ip)) return self._core_plugin.get_port( - context, fip.fixed_port_id) if fip else None + context, fips[0].fixed_port_id) if fips else None class L3_NAT_with_dvr_db_mixin(_DVRAgentInterfaceMixin, diff --git a/neutron/db/l3_fip_qos.py b/neutron/db/l3_fip_qos.py index ff191e8f3a5..a58b6cb7e31 100644 --- a/neutron/db/l3_fip_qos.py +++ b/neutron/db/l3_fip_qos.py @@ -59,7 +59,7 @@ class FloatingQoSDbMixin(object): self._create_fip_qos_db(context, fip_id, qos_policy_id) def _process_extra_fip_qos_update( - self, context, floatingip_db, fip, old_floatingip): + self, context, floatingip_obj, fip, old_floatingip): if qos_consts.QOS_POLICY_ID not in fip: # No qos_policy_id in API input, do nothing return @@ -71,15 +71,16 @@ class FloatingQoSDbMixin(object): return if old_qos_policy_id: self._delete_fip_qos_db(context, - floatingip_db['id'], + floatingip_obj['id'], old_qos_policy_id) - if floatingip_db.qos_policy_binding: - floatingip_db.qos_policy_binding['policy_id'] = new_qos_policy_id + if floatingip_obj.db_obj.qos_policy_binding: + floatingip_obj.db_obj.qos_policy_binding['policy_id'] = ( + new_qos_policy_id) if not new_qos_policy_id: return qos_policy_binding = self._create_fip_qos_db( context, - floatingip_db['id'], + floatingip_obj['id'], new_qos_policy_id) - if not floatingip_db.qos_policy_binding: - floatingip_db.qos_policy_binding = qos_policy_binding + if not floatingip_obj.db_obj.qos_policy_binding: + floatingip_obj.db_obj.qos_policy_binding = qos_policy_binding diff --git a/neutron/objects/floatingip.py b/neutron/objects/floatingip.py index 885899f496b..2bd082b992e 100644 --- a/neutron/objects/floatingip.py +++ b/neutron/objects/floatingip.py @@ -26,6 +26,7 @@ class FloatingIPDNS(base.NeutronDbObject): db_model = models.FloatingIPDNS primary_keys = ['floatingip_id'] + foreign_keys = {'FloatingIP': {'floatingip_id': 'id'}} fields = { 'floatingip_id': common_types.UUIDField(), diff --git a/neutron/objects/router.py b/neutron/objects/router.py index 2c87e3f4f2c..e7feaedb87d 100644 --- a/neutron/objects/router.py +++ b/neutron/objects/router.py @@ -10,11 +10,14 @@ # License for the specific language governing permissions and limitations # under the License. +import itertools + import netaddr from neutron_lib.api.definitions import availability_zone as az_def from neutron_lib.api.validators import availability_zone as az_validator from oslo_versionedobjects import fields as obj_fields +import six from sqlalchemy import func from neutron.common import constants as n_const @@ -222,9 +225,11 @@ class FloatingIP(base.NeutronDbObject): 'router_id': common_types.UUIDField(nullable=True), 'last_known_router_id': common_types.UUIDField(nullable=True), 'status': common_types.FloatingIPStatusEnumField(nullable=True), + 'dns': obj_fields.ObjectField('FloatingIPDNS', nullable=True), } fields_no_update = ['project_id', 'floating_ip_address', 'floating_network_id', 'floating_port_id'] + synthetic_fields = ['dns'] @classmethod def modify_fields_from_db(cls, db_obj): @@ -241,9 +246,40 @@ class FloatingIP(base.NeutronDbObject): def modify_fields_to_db(cls, fields): result = super(FloatingIP, cls).modify_fields_to_db(fields) if 'fixed_ip_address' in result: - result['fixed_ip_address'] = cls.filter_to_str( - result['fixed_ip_address']) + if result['fixed_ip_address'] is not None: + result['fixed_ip_address'] = cls.filter_to_str( + result['fixed_ip_address']) if 'floating_ip_address' in result: result['floating_ip_address'] = cls.filter_to_str( result['floating_ip_address']) return result + + @classmethod + def get_scoped_floating_ips(cls, context, router_ids): + query = context.session.query(l3.FloatingIP, + models_v2.SubnetPool.address_scope_id) + query = query.join(models_v2.Port, + l3.FloatingIP.fixed_port_id == models_v2.Port.id) + # Outer join of Subnet can cause each ip to have more than one row. + query = query.outerjoin(models_v2.Subnet, + models_v2.Subnet.network_id == models_v2.Port.network_id) + query = query.filter(models_v2.Subnet.ip_version == 4) + query = query.outerjoin(models_v2.SubnetPool, + models_v2.Subnet.subnetpool_id == models_v2.SubnetPool.id) + + # Filter out on router_ids + query = query.filter(l3.FloatingIP.router_id.in_(router_ids)) + return cls._unique_floatingip_iterator(context, query) + + @classmethod + def _unique_floatingip_iterator(cls, context, query): + """Iterates over only one row per floating ip. Ignores others.""" + # Group rows by fip id. They must be sorted by same. + q = query.order_by(l3.FloatingIP.id) + keyfunc = lambda row: row[0]['id'] + group_iterator = itertools.groupby(q, keyfunc) + + # Just hit the first row of each group + for key, value in group_iterator: + row = [r for r in six.next(value)] + yield (cls._load_object(context, row[0]), row[1]) diff --git a/neutron/tests/unit/db/test_db_base_plugin_v2.py b/neutron/tests/unit/db/test_db_base_plugin_v2.py index ce87e55ad94..dc5eeb28e5f 100644 --- a/neutron/tests/unit/db/test_db_base_plugin_v2.py +++ b/neutron/tests/unit/db/test_db_base_plugin_v2.py @@ -6276,11 +6276,11 @@ class DbModelMixin(object): return sg, rule def _make_floating_ip(self, ctx, port_id): - with db_api.context_manager.writer.using(ctx): - flip = l3_models.FloatingIP(floating_ip_address='1.2.3.4', - floating_network_id='somenet', - floating_port_id=port_id) - ctx.session.add(flip) + flip = l3_obj.FloatingIP( + ctx, floating_ip_address=netaddr.IPAddress('1.2.3.4'), + floating_network_id=uuidutils.generate_uuid(), + floating_port_id=port_id) + flip.create() return flip def _make_router(self, ctx): @@ -6343,7 +6343,7 @@ class DbModelMixin(object): port = self._make_port(ctx, network.id) flip = self._make_floating_ip(ctx, port.id) self._test_staledata_error_on_concurrent_object_update( - l3_models.FloatingIP, flip['id']) + flip.db_model, flip.id) def test_staledata_error_on_concurrent_object_update_sg(self): ctx = context.get_admin_context() @@ -6434,7 +6434,9 @@ class DbModelMixin(object): network = self._make_network(ctx) port = self._make_port(ctx, network.id) flip = self._make_floating_ip(ctx, port.id) - self._test_standardattr_removed_on_obj_delete(ctx, flip) + # TODO(lujinluo): Change flip.db_obj to flip once all + # codes are migrated to use Floating IP OVO object. + self._test_standardattr_removed_on_obj_delete(ctx, flip.db_obj) def test_standardattr_removed_on_router_delete(self): ctx = context.get_admin_context() diff --git a/neutron/tests/unit/db/test_l3_db.py b/neutron/tests/unit/db/test_l3_db.py index fd5abce3f88..7fd939cb93c 100644 --- a/neutron/tests/unit/db/test_l3_db.py +++ b/neutron/tests/unit/db/test_l3_db.py @@ -14,17 +14,21 @@ # limitations under the License. import mock +import netaddr from neutron_lib.callbacks import events from neutron_lib.callbacks import registry from neutron_lib.callbacks import resources from neutron_lib import constants as n_const +from neutron_lib import context from neutron_lib import exceptions as n_exc from neutron_lib.plugins import directory +from oslo_utils import uuidutils import testtools from neutron.db import l3_db from neutron.db.models import l3 as l3_models from neutron.extensions import l3 +from neutron.objects import router as l3_obj from neutron.tests import base @@ -131,6 +135,7 @@ class TestL3_NAT_dbonly_mixin(base.BaseTestCase): 'id': mock.sentinel.fip_ip}, result) def test__unique_floatingip_iterator(self): + context = mock.MagicMock() query = mock.MagicMock() query.order_by().__iter__.return_value = [ ({'id': 'id1'}, 'scope1'), @@ -140,12 +145,15 @@ class TestL3_NAT_dbonly_mixin(base.BaseTestCase): ({'id': 'id2'}, 'scope2'), ({'id': 'id3'}, 'scope3')] query.reset_mock() - result = list( - l3_db.L3_NAT_dbonly_mixin._unique_floatingip_iterator(query)) - query.order_by.assert_called_once_with(l3_models.FloatingIP.id) - self.assertEqual([({'id': 'id1'}, 'scope1'), - ({'id': 'id2'}, 'scope2'), - ({'id': 'id3'}, 'scope3')], result) + with mock.patch.object( + l3_obj.FloatingIP, '_load_object', + side_effect=({'id': 'id1'}, {'id': 'id2'}, {'id': 'id3'})): + result = list( + l3_obj.FloatingIP._unique_floatingip_iterator(context, query)) + query.order_by.assert_called_once_with(l3_models.FloatingIP.id) + self.assertEqual([({'id': 'id1'}, 'scope1'), + ({'id': 'id2'}, 'scope2'), + ({'id': 'id3'}, 'scope3')], result) @mock.patch.object(directory, 'get_plugin') def test_prevent_l3_port_deletion_port_not_found(self, gp): @@ -191,13 +199,16 @@ class TestL3_NAT_dbonly_mixin(base.BaseTestCase): @mock.patch.object(directory, 'get_plugin') def test_prevent_l3_port_existing_floating_ip(self, gp): + ctx = context.get_admin_context() gp.return_value.get_port.return_value = { 'device_owner': n_const.DEVICE_OWNER_FLOATINGIP, 'device_id': 'some_flip', 'id': 'f', 'fixed_ips': [{'ip_address': '1.1.1.1', 'subnet_id': '4'}]} - self.db.get_floatingip = mock.Mock() - with testtools.ExpectedException(n_exc.ServicePortInUse): - self.db.prevent_l3_port_deletion(mock.Mock(), None) + with mock.patch.object(l3_obj.FloatingIP, 'objects_exist', + return_value=mock.Mock()),\ + testtools.ExpectedException(n_exc.ServicePortInUse): + + self.db.prevent_l3_port_deletion(ctx, None) @mock.patch.object(directory, 'get_plugin') def test_subscribe_address_scope_of_subnetpool(self, gp): @@ -211,20 +222,21 @@ class TestL3_NAT_dbonly_mixin(base.BaseTestCase): def test__check_and_get_fip_assoc_with_extra_association_no_change(self): fip = {'extra_key': 'value'} context = mock.MagicMock() - floatingip_db = l3_models.FloatingIP( - id='fake_fip_id', - floating_network_id='fake_floating_network_id', - floating_ip_address='8.8.8.8', - fixed_port_id='fake_fixed_port_id', - floating_port_id='fake_floating_port_id') + floatingip_obj = l3_obj.FloatingIP( + context, + id=uuidutils.generate_uuid(), + floating_network_id=uuidutils.generate_uuid(), + floating_ip_address=netaddr.IPAddress('8.8.8.8'), + fixed_port_id=uuidutils.generate_uuid(), + floating_port_id=uuidutils.generate_uuid()) with mock.patch.object( l3_db.L3_NAT_dbonly_mixin, '_get_assoc_data', return_value=('1', '2', '3')) as mock_get_assoc_data: - self.db._check_and_get_fip_assoc(context, fip, floatingip_db) + self.db._check_and_get_fip_assoc(context, fip, floatingip_obj) context.session.query.assert_not_called() mock_get_assoc_data.assert_called_once_with( - mock.ANY, fip, floatingip_db) + mock.ANY, fip, floatingip_obj) def test__notify_attaching_interface(self): with mock.patch.object(l3_db.registry, 'notify') as mock_notify: diff --git a/neutron/tests/unit/extensions/test_l3.py b/neutron/tests/unit/extensions/test_l3.py index b27bdb8642a..98f9549c682 100644 --- a/neutron/tests/unit/extensions/test_l3.py +++ b/neutron/tests/unit/extensions/test_l3.py @@ -3905,22 +3905,6 @@ class TestL3DbOperationBounds(test_db_base_plugin_v2.DbOperationBoundMixin, self._assert_object_list_queries_constant(router_maker, 'routers') - def test_floatingip_list_queries_constant(self): - with self.floatingip_with_assoc(**self.kwargs) as flip: - internal_port = self._show('ports', flip['floatingip']['port_id']) - internal_net_id = internal_port['port']['network_id'] - - def float_maker(): - port = self._make_port( - self.fmt, internal_net_id, **self.kwargs) - return self._make_floatingip( - self.fmt, flip['floatingip']['floating_network_id'], - port_id=port['port']['id'], - **self.kwargs) - - self._assert_object_list_queries_constant(float_maker, - 'floatingips') - class TestL3DbOperationBoundsTenant(TestL3DbOperationBounds): admin = False diff --git a/neutron/tests/unit/extensions/test_l3_ext_gw_mode.py b/neutron/tests/unit/extensions/test_l3_ext_gw_mode.py index a7780e79763..5d7522ce822 100644 --- a/neutron/tests/unit/extensions/test_l3_ext_gw_mode.py +++ b/neutron/tests/unit/extensions/test_l3_ext_gw_mode.py @@ -37,6 +37,7 @@ from neutron.db.models import l3 as l3_models from neutron.extensions import l3 from neutron.objects import network as net_obj from neutron.objects import ports as port_obj +from neutron.objects import router as l3_obj from neutron.objects import subnet as subnet_obj from neutron.tests import base from neutron.tests.unit.db import test_db_base_plugin_v2 @@ -226,9 +227,10 @@ class TestL3GwModeMixin(testlib_api.SqlTestCase): network_id=self.int_net.id, subnet_id=self.int_sub_id, ip_address='3.3.3.3') - self.fip = l3_models.FloatingIP( + self.fip = l3_obj.FloatingIP( + self.context, id=_uuid(), - floating_ip_address='1.1.1.2', + floating_ip_address=netaddr.IPAddress('1.1.1.2'), floating_network_id=self.ext_net_id, floating_port_id=FAKE_FIP_EXT_PORT_ID, fixed_port_id=None, @@ -236,7 +238,7 @@ class TestL3GwModeMixin(testlib_api.SqlTestCase): router_id=None) self.fip_int_port.create() self.fip_int_ip_info.create() - self.context.session.add(self.fip) + self.fip.create() self.context.session.flush() self.context.session.expire_all() self.fip_request = {'port_id': FAKE_FIP_INT_PORT_ID, diff --git a/neutron/tests/unit/objects/test_base.py b/neutron/tests/unit/objects/test_base.py index 6d4fccc926e..1f8c9364ba8 100644 --- a/neutron/tests/unit/objects/test_base.py +++ b/neutron/tests/unit/objects/test_base.py @@ -32,7 +32,6 @@ from oslo_versionedobjects import fields as obj_fields import testtools from neutron.db import _model_query as model_query -from neutron.db.models import l3 as l3_model from neutron.db import standard_attr from neutron import objects from neutron.objects import agent @@ -1448,16 +1447,17 @@ class BaseDbObjectTestCase(_BaseObjectTestCase, def _create_test_fip_id(self, fip_id=None): fake_fip = '172.23.3.0' ext_net_id = self._create_external_network_id() - # TODO(manjeets) replace this with fip ovo - # once it is implemented - values = {'floating_ip_address': fake_fip, - 'floating_network_id': ext_net_id, - 'floating_port_id': self._create_test_port_id( - network_id=ext_net_id)} + values = { + 'floating_ip_address': netaddr.IPAddress(fake_fip), + 'floating_network_id': ext_net_id, + 'floating_port_id': self._create_test_port_id( + network_id=ext_net_id) + } if fip_id: values['id'] = fip_id - return obj_db_api.create_object( - self.context, l3_model.FloatingIP, values).id + fip_obj = router.FloatingIP(self.context, **values) + fip_obj.create() + return fip_obj.id def _create_test_subnet_id(self, network_id=None): if not network_id: diff --git a/neutron/tests/unit/objects/test_objects.py b/neutron/tests/unit/objects/test_objects.py index c955c3e9102..8a16b083e40 100644 --- a/neutron/tests/unit/objects/test_objects.py +++ b/neutron/tests/unit/objects/test_objects.py @@ -39,7 +39,7 @@ object_data = { 'FlatAllocation': '1.0-bf666f24f4642b047eeca62311fbcb41', 'Flavor': '1.0-82194de5c9aafce08e8527bb7977f5c6', 'FlavorServiceProfileBinding': '1.0-a2c8731e16cefdac4571f80abf1f8930', - 'FloatingIP': '1.0-ea69515cfe08b5efc0600e6446efe64f', + 'FloatingIP': '1.0-0205cc99ec79e8089d641ed1b565ddae', 'FloatingIPDNS': '1.0-ee3db848500fa1825235f701828c06d5', 'GeneveAllocation': '1.0-d5f76e8eac60a778914d61dd8e23e90f', 'GeneveEndpoint': '1.0-040f026996b5952e2ae4ccd40ac61ca6',