"Fossies" - the Fresh Open Source Software Archive

Member "neutron-14.0.3/neutron/db/l3_db.py" (22 Oct 2019, 98604 Bytes) of package /linux/misc/openstack/neutron-14.0.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "l3_db.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 14.0.2_vs_14.0.3.

    1 # Copyright 2012 VMware, Inc.  All rights reserved.
    2 #
    3 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    4 #    not use this file except in compliance with the License. You may obtain
    5 #    a copy of the License at
    6 #
    7 #         http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 #    Unless required by applicable law or agreed to in writing, software
   10 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   11 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   12 #    License for the specific language governing permissions and limitations
   13 #    under the License.
   14 
   15 import functools
   16 import random
   17 
   18 import netaddr
   19 from neutron_lib.api.definitions import external_net as extnet_apidef
   20 from neutron_lib.api.definitions import l3 as l3_apidef
   21 from neutron_lib.api import extensions
   22 from neutron_lib.api import validators
   23 from neutron_lib.callbacks import events
   24 from neutron_lib.callbacks import exceptions
   25 from neutron_lib.callbacks import registry
   26 from neutron_lib.callbacks import resources
   27 from neutron_lib import constants
   28 from neutron_lib import context as n_ctx
   29 from neutron_lib.db import api as db_api
   30 from neutron_lib.db import model_query
   31 from neutron_lib.db import resource_extend
   32 from neutron_lib.db import utils as lib_db_utils
   33 from neutron_lib import exceptions as n_exc
   34 from neutron_lib.exceptions import l3 as l3_exc
   35 from neutron_lib.plugins import constants as plugin_constants
   36 from neutron_lib.plugins import directory
   37 from neutron_lib.plugins import utils as plugin_utils
   38 from neutron_lib import rpc as n_rpc
   39 from neutron_lib.services import base as base_services
   40 from oslo_log import log as logging
   41 from oslo_utils import uuidutils
   42 from sqlalchemy import orm
   43 from sqlalchemy.orm import exc
   44 
   45 from neutron._i18n import _
   46 from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
   47 from neutron.common import ipv6_utils
   48 from neutron.common import utils
   49 from neutron.db import _utils as db_utils
   50 from neutron.db.models import l3 as l3_models
   51 from neutron.db import models_v2
   52 from neutron.db import standardattrdescription_db as st_attr
   53 from neutron.extensions import l3
   54 from neutron.extensions import qos_fip
   55 from neutron.objects import base as base_obj
   56 from neutron.objects import port_forwarding
   57 from neutron.objects import ports as port_obj
   58 from neutron.objects import router as l3_obj
   59 from neutron import worker as neutron_worker
   60 
   61 LOG = logging.getLogger(__name__)
   62 
   63 
   64 DEVICE_OWNER_HA_REPLICATED_INT = constants.DEVICE_OWNER_HA_REPLICATED_INT
   65 DEVICE_OWNER_ROUTER_INTF = constants.DEVICE_OWNER_ROUTER_INTF
   66 DEVICE_OWNER_ROUTER_GW = constants.DEVICE_OWNER_ROUTER_GW
   67 DEVICE_OWNER_FLOATINGIP = constants.DEVICE_OWNER_FLOATINGIP
   68 EXTERNAL_GW_INFO = l3_apidef.EXTERNAL_GW_INFO
   69 
   70 # Maps API field to DB column
   71 # API parameter name and Database column names may differ.
   72 # Useful to keep the filtering between API and Database.
   73 API_TO_DB_COLUMN_MAP = {'port_id': 'fixed_port_id'}
   74 CORE_ROUTER_ATTRS = ('id', 'name', 'tenant_id', 'admin_state_up', 'status')
   75 
   76 
   77 @registry.has_registry_receivers
   78 class L3_NAT_dbonly_mixin(l3.RouterPluginBase,
   79                           base_services.WorkerBase,
   80                           st_attr.StandardAttrDescriptionMixin):
   81     """Mixin class to add L3/NAT router methods to db_base_plugin_v2."""
   82 
   83     router_device_owners = (
   84         DEVICE_OWNER_HA_REPLICATED_INT,
   85         DEVICE_OWNER_ROUTER_INTF,
   86         DEVICE_OWNER_ROUTER_GW,
   87         DEVICE_OWNER_FLOATINGIP
   88     )
   89 
   90     _dns_integration = None
   91 
   92     _fip_qos = None
   93 
   94     def __new__(cls, *args, **kwargs):
   95         inst = super(L3_NAT_dbonly_mixin, cls).__new__(cls, *args, **kwargs)
   96         inst._start_janitor()
   97         return inst
   98 
   99     @staticmethod
  100     @registry.receives(resources.PORT, [events.BEFORE_DELETE])
  101     def _prevent_l3_port_delete_callback(resource, event,
  102                                          trigger, payload=None):
  103         l3plugin = directory.get_plugin(plugin_constants.L3)
  104         if l3plugin and payload.metadata['port_check']:
  105             l3plugin.prevent_l3_port_deletion(
  106                 payload.context, payload.resource_id)
  107 
  108     @property
  109     def _is_dns_integration_supported(self):
  110         if self._dns_integration is None:
  111             self._dns_integration = (
  112                 extensions.is_extension_supported(
  113                     self._core_plugin, 'dns-integration') or
  114                 extensions.is_extension_supported(
  115                     self._core_plugin, 'dns-domain-ports'))
  116         return self._dns_integration
  117 
  118     @property
  119     def _is_fip_qos_supported(self):
  120         if self._fip_qos is None:
  121             # Check L3 service plugin
  122             self._fip_qos = extensions.is_extension_supported(
  123                 self, qos_fip.FIP_QOS_ALIAS)
  124         return self._fip_qos
  125 
  126     @property
  127     def _core_plugin(self):
  128         return directory.get_plugin()
  129 
  130     def _start_janitor(self):
  131         """Starts the periodic job that cleans up broken complex resources.
  132 
  133         This job will look for things like floating IP ports without an
  134         associated floating IP and delete them 5 minutes after detection.
  135         """
  136         interval = 60 * 5  # only every 5 minutes. cleanups should be rare
  137         initial_delay = random.randint(0, interval)  # splay multiple servers
  138         janitor = neutron_worker.PeriodicWorker(self._clean_garbage, interval,
  139                                                 initial_delay)
  140         self.add_worker(janitor)
  141 
  142     def _clean_garbage(self):
  143         if not hasattr(self, '_candidate_broken_fip_ports'):
  144             self._candidate_broken_fip_ports = set()
  145         context = n_ctx.get_admin_context()
  146         candidates = self._get_dead_floating_port_candidates(context)
  147         # just because a port is in 'candidates' doesn't necessarily mean
  148         # it's broken, we could have just caught it before it was updated.
  149         # We confirm by waiting until the next call of this function to see
  150         # if it persists.
  151         to_cleanup = candidates & self._candidate_broken_fip_ports
  152         self._candidate_broken_fip_ports = candidates - to_cleanup
  153         for port_id in to_cleanup:
  154             # ensure it wasn't just a failure to update device_id before we
  155             # delete it
  156             try:
  157                 self._fix_or_kill_floating_port(context, port_id)
  158             except Exception:
  159                 LOG.exception("Error cleaning up floating IP port: %s",
  160                               port_id)
  161 
  162     def _fix_or_kill_floating_port(self, context, port_id):
  163         pager = base_obj.Pager(limit=1)
  164         fips = l3_obj.FloatingIP.get_objects(
  165             context, _pager=pager, floating_port_id=port_id)
  166         if fips:
  167             LOG.warning("Found incorrect device_id on floating port "
  168                         "%(pid)s, correcting to %(fip)s.",
  169                         {'pid': port_id, 'fip': fips[0].id})
  170             self._core_plugin.update_port(
  171                 context, port_id, {'port': {'device_id': fips[0].id}})
  172         else:
  173             LOG.warning("Found floating IP port %s without floating IP, "
  174                         "deleting.", port_id)
  175             self._core_plugin.delete_port(
  176                 context, port_id, l3_port_check=False)
  177             registry.notify(resources.FLOATING_IP, events.AFTER_DELETE,
  178                             self, context=context, **fips[0])
  179 
  180     def _get_dead_floating_port_candidates(self, context):
  181         filters = {'device_id': ['PENDING'],
  182                    'device_owner': [DEVICE_OWNER_FLOATINGIP]}
  183         return {p['id'] for p in self._core_plugin.get_ports(context, filters)}
  184 
  185     def _get_router(self, context, router_id):
  186         try:
  187             router = model_query.get_by_id(
  188                 context, l3_models.Router, router_id)
  189         except exc.NoResultFound:
  190             raise l3_exc.RouterNotFound(router_id=router_id)
  191         return router
  192 
  193     def _make_router_dict(self, router, fields=None, process_extensions=True):
  194         res = dict((key, router[key]) for key in CORE_ROUTER_ATTRS)
  195         if router['gw_port_id']:
  196             ext_gw_info = {
  197                 'network_id': router.gw_port['network_id'],
  198                 'external_fixed_ips': [{'subnet_id': ip["subnet_id"],
  199                                         'ip_address': ip["ip_address"]}
  200                                        for ip in router.gw_port['fixed_ips']]}
  201         else:
  202             ext_gw_info = None
  203         res.update({
  204             EXTERNAL_GW_INFO: ext_gw_info,
  205             'gw_port_id': router['gw_port_id'],
  206         })
  207         # NOTE(salv-orlando): The following assumes this mixin is used in a
  208         # class inheriting from CommonDbMixin, which is true for all existing
  209         # plugins.
  210         if process_extensions:
  211             resource_extend.apply_funcs(l3_apidef.ROUTERS, res, router)
  212         return lib_db_utils.resource_fields(res, fields)
  213 
  214     def _create_router_db(self, context, router, tenant_id):
  215         """Create the DB object."""
  216         router.setdefault('id', uuidutils.generate_uuid())
  217         router['tenant_id'] = tenant_id
  218         registry.notify(resources.ROUTER, events.BEFORE_CREATE,
  219                         self, context=context, router=router)
  220         with context.session.begin(subtransactions=True):
  221             # pre-generate id so it will be available when
  222             # configuring external gw port
  223             router_db = l3_models.Router(
  224                 id=router['id'],
  225                 tenant_id=router['tenant_id'],
  226                 name=router['name'],
  227                 admin_state_up=router['admin_state_up'],
  228                 status=constants.ACTIVE,
  229                 description=router.get('description'))
  230             context.session.add(router_db)
  231             registry.notify(resources.ROUTER, events.PRECOMMIT_CREATE,
  232                             self, context=context, router=router,
  233                             router_id=router['id'], router_db=router_db)
  234             return router_db
  235 
  236     def _update_gw_for_create_router(self, context, gw_info, router_id):
  237         if gw_info:
  238             router_db = self._get_router(context, router_id)
  239             self._update_router_gw_info(context, router_id,
  240                                         gw_info, router=router_db)
  241 
  242     @db_api.retry_if_session_inactive()
  243     def create_router(self, context, router):
  244         r = router['router']
  245         gw_info = r.pop(EXTERNAL_GW_INFO, None)
  246         create = functools.partial(self._create_router_db, context, r,
  247                                    r['tenant_id'])
  248         delete = functools.partial(self.delete_router, context)
  249         update_gw = functools.partial(self._update_gw_for_create_router,
  250                                       context, gw_info)
  251         router_db, _unused = db_utils.safe_creation(context, create,
  252                                                     delete, update_gw,
  253                                                     transaction=False)
  254         new_router = self._make_router_dict(router_db)
  255         registry.notify(resources.ROUTER, events.AFTER_CREATE, self,
  256                         context=context, router_id=router_db.id,
  257                         router=new_router, request_attrs=r,
  258                         router_db=router_db)
  259         return new_router
  260 
  261     def _update_router_db(self, context, router_id, data):
  262         """Update the DB object."""
  263         with context.session.begin(subtransactions=True):
  264             router_db = self._get_router(context, router_id)
  265             old_router = self._make_router_dict(router_db)
  266             if data:
  267                 router_db.update(data)
  268             registry.publish(resources.ROUTER, events.PRECOMMIT_UPDATE, self,
  269                              payload=events.DBEventPayload(
  270                                  context, request_body=data,
  271                                  states=(old_router,), resource_id=router_id,
  272                                  desired_state=router_db))
  273             return router_db
  274 
  275     @db_api.retry_if_session_inactive()
  276     def update_router(self, context, id, router):
  277         r = router['router']
  278         gw_info = r.pop(EXTERNAL_GW_INFO, constants.ATTR_NOT_SPECIFIED)
  279         original = self.get_router(context, id)
  280         # check whether router needs and can be rescheduled to the proper
  281         # l3 agent (associated with given external network);
  282         # do check before update in DB as an exception will be raised
  283         # in case no proper l3 agent found
  284         if gw_info != constants.ATTR_NOT_SPECIFIED:
  285             candidates = self._check_router_needs_rescheduling(
  286                 context, id, gw_info)
  287             # Update the gateway outside of the DB update since it involves L2
  288             # calls that don't make sense to rollback and may cause deadlocks
  289             # in a transaction.
  290             self._update_router_gw_info(context, id, gw_info)
  291         else:
  292             candidates = None
  293         router_db = self._update_router_db(context, id, r)
  294         if candidates:
  295             l3_plugin = directory.get_plugin(plugin_constants.L3)
  296             l3_plugin.reschedule_router(context, id, candidates)
  297         updated = self._make_router_dict(router_db)
  298         registry.notify(resources.ROUTER, events.AFTER_UPDATE, self,
  299                         context=context, router_id=id, old_router=original,
  300                         router=updated, request_attrs=r, router_db=router_db)
  301         return updated
  302 
  303     def _check_router_needs_rescheduling(self, context, router_id, gw_info):
  304         """Checks whether router's l3 agent can handle the given network
  305 
  306         :return: list of candidate agents if rescheduling needed,
  307         None otherwise; raises exception if there is no eligible l3 agent
  308         associated with target external network
  309         """
  310         # TODO(obondarev): rethink placement of this func as l3 db manager is
  311         # not really a proper place for agent scheduling stuff
  312         network_id = gw_info.get('network_id') if gw_info else None
  313         if not network_id:
  314             return
  315 
  316         nets = self._core_plugin.get_networks(
  317             context, {extnet_apidef.EXTERNAL: [True]})
  318         # nothing to do if there is only one external network
  319         if len(nets) <= 1:
  320             return
  321 
  322         # first get plugin supporting l3 agent scheduling
  323         # (either l3 service plugin or core_plugin)
  324         l3_plugin = directory.get_plugin(plugin_constants.L3)
  325         if (not extensions.is_extension_supported(
  326                 l3_plugin,
  327                 constants.L3_AGENT_SCHEDULER_EXT_ALIAS) or
  328                 l3_plugin.router_scheduler is None):
  329             # that might mean that we are dealing with non-agent-based
  330             # implementation of l3 services
  331             return
  332 
  333         if not l3_plugin.router_supports_scheduling(context, router_id):
  334             return
  335         cur_agents = l3_plugin.list_l3_agents_hosting_router(
  336             context, router_id)['agents']
  337         for agent in cur_agents:
  338             ext_net_id = agent['configurations'].get(
  339                 'gateway_external_network_id')
  340             if ext_net_id == network_id or not ext_net_id:
  341                 return
  342 
  343         # otherwise find l3 agent with matching gateway_external_network_id
  344         active_agents = l3_plugin.get_l3_agents(context, active=True)
  345         router = {
  346             'id': router_id,
  347             'external_gateway_info': {'network_id': network_id}
  348         }
  349         candidates = l3_plugin.get_l3_agent_candidates(context,
  350                                                        router,
  351                                                        active_agents)
  352         if not candidates:
  353             msg = (_('No eligible l3 agent associated with external network '
  354                      '%s found') % network_id)
  355             raise n_exc.BadRequest(resource='router', msg=msg)
  356 
  357         return candidates
  358 
  359     def _create_router_gw_port(self, context, router, network_id, ext_ips):
  360         # Port has no 'tenant-id', as it is hidden from user
  361         port_data = {'tenant_id': '',  # intentionally not set
  362                      'network_id': network_id,
  363                      'fixed_ips': ext_ips or constants.ATTR_NOT_SPECIFIED,
  364                      'device_id': router['id'],
  365                      'device_owner': DEVICE_OWNER_ROUTER_GW,
  366                      'admin_state_up': True,
  367                      'name': ''}
  368         gw_port = plugin_utils.create_port(
  369             self._core_plugin, context.elevated(), {'port': port_data})
  370 
  371         if not gw_port['fixed_ips']:
  372             LOG.debug('No IPs available for external network %s',
  373                       network_id)
  374         with plugin_utils.delete_port_on_error(
  375                 self._core_plugin, context.elevated(), gw_port['id']):
  376             with context.session.begin(subtransactions=True):
  377                 router.gw_port = self._core_plugin._get_port(
  378                     context.elevated(), gw_port['id'])
  379                 router_port = l3_obj.RouterPort(
  380                     context,
  381                     router_id=router.id,
  382                     port_id=gw_port['id'],
  383                     port_type=DEVICE_OWNER_ROUTER_GW
  384                 )
  385                 context.session.add(router)
  386                 router_port.create()
  387 
  388     def _validate_gw_info(self, context, gw_port, info, ext_ips):
  389         network_id = info['network_id'] if info else None
  390         if network_id:
  391             network_db = self._core_plugin._get_network(context, network_id)
  392             if not network_db.external:
  393                 msg = _("Network %s is not an external network") % network_id
  394                 raise n_exc.BadRequest(resource='router', msg=msg)
  395             if ext_ips:
  396                 subnets = self._core_plugin.get_subnets_by_network(context,
  397                                                                    network_id)
  398                 for s in subnets:
  399                     if not s['gateway_ip']:
  400                         continue
  401                     for ext_ip in ext_ips:
  402                         if ext_ip.get('ip_address') == s['gateway_ip']:
  403                             msg = _("External IP %s is the same as the "
  404                                     "gateway IP") % ext_ip.get('ip_address')
  405                             raise n_exc.BadRequest(resource='router', msg=msg)
  406         return network_id
  407 
  408     # NOTE(yamamoto): This method is an override point for plugins
  409     # inheriting this class.  Do not optimize this out.
  410     def router_gw_port_has_floating_ips(self, context, router_id):
  411         """Return True if the router's gateway port is serving floating IPs."""
  412         return bool(self.get_floatingips_count(context,
  413                                                {'router_id': [router_id]}))
  414 
  415     def _delete_current_gw_port(self, context, router_id, router,
  416                                 new_network_id):
  417         """Delete gw port if attached to an old network."""
  418         port_requires_deletion = (
  419             router.gw_port and router.gw_port['network_id'] != new_network_id)
  420         if not port_requires_deletion:
  421             return
  422         admin_ctx = context.elevated()
  423         old_network_id = router.gw_port['network_id']
  424 
  425         if self.router_gw_port_has_floating_ips(admin_ctx, router_id):
  426             raise l3_exc.RouterExternalGatewayInUseByFloatingIp(
  427                 router_id=router_id, net_id=router.gw_port['network_id'])
  428         gw_ips = [x['ip_address'] for x in router.gw_port['fixed_ips']]
  429         gw_port_id = router.gw_port['id']
  430         self._delete_router_gw_port_db(context, router)
  431         self._core_plugin.delete_port(
  432             admin_ctx, gw_port_id, l3_port_check=False)
  433         with context.session.begin(subtransactions=True):
  434             context.session.refresh(router)
  435         registry.notify(resources.ROUTER_GATEWAY,
  436                         events.AFTER_DELETE, self,
  437                         router_id=router_id,
  438                         context=context,
  439                         router=router,
  440                         network_id=old_network_id,
  441                         new_network_id=new_network_id,
  442                         gateway_ips=gw_ips)
  443 
  444     def _delete_router_gw_port_db(self, context, router):
  445         with context.session.begin(subtransactions=True):
  446             router.gw_port = None
  447             if router not in context.session:
  448                 context.session.add(router)
  449             try:
  450                 registry.publish(resources.ROUTER_GATEWAY,
  451                                  events.BEFORE_DELETE, self,
  452                                  payload=events.DBEventPayload(
  453                                      context, states=(router,),
  454                                      resource_id=router.id))
  455             except exceptions.CallbackFailure as e:
  456                 # NOTE(armax): preserve old check's behavior
  457                 if len(e.errors) == 1:
  458                     raise e.errors[0].error
  459                 raise l3_exc.RouterInUse(router_id=router.id, reason=e)
  460 
  461     def _create_gw_port(self, context, router_id, router, new_network_id,
  462                         ext_ips):
  463         new_valid_gw_port_attachment = (
  464             new_network_id and
  465             (not router.gw_port or
  466              router.gw_port['network_id'] != new_network_id))
  467         if new_valid_gw_port_attachment:
  468             subnets = self._core_plugin.get_subnets_by_network(context,
  469                                                                new_network_id)
  470             try:
  471                 kwargs = {'context': context, 'router_id': router_id,
  472                           'network_id': new_network_id, 'subnets': subnets}
  473                 registry.notify(
  474                     resources.ROUTER_GATEWAY, events.BEFORE_CREATE, self,
  475                     **kwargs)
  476             except exceptions.CallbackFailure as e:
  477                 # raise the underlying exception
  478                 raise e.errors[0].error
  479 
  480             self._check_for_dup_router_subnets(context, router,
  481                                                new_network_id,
  482                                                subnets,
  483                                                include_gateway=True)
  484             self._create_router_gw_port(context, router,
  485                                         new_network_id, ext_ips)
  486 
  487             gw_ips = [x['ip_address'] for x in router.gw_port['fixed_ips']]
  488 
  489             registry.notify(resources.ROUTER_GATEWAY,
  490                             events.AFTER_CREATE,
  491                             self._create_gw_port,
  492                             context=context,
  493                             gw_ips=gw_ips,
  494                             network_id=new_network_id,
  495                             router_id=router_id)
  496 
  497     def _update_current_gw_port(self, context, router_id, router, ext_ips):
  498         self._core_plugin.update_port(context, router.gw_port['id'], {'port':
  499                                       {'fixed_ips': ext_ips}})
  500         context.session.expire(router.gw_port)
  501 
  502     def _update_router_gw_info(self, context, router_id, info, router=None):
  503         # TODO(salvatore-orlando): guarantee atomic behavior also across
  504         # operations that span beyond the model classes handled by this
  505         # class (e.g.: delete_port)
  506         router = router or self._get_router(context, router_id)
  507         gw_port = router.gw_port
  508         ext_ips = info.get('external_fixed_ips') if info else []
  509         ext_ip_change = self._check_for_external_ip_change(
  510             context, gw_port, ext_ips)
  511         network_id = self._validate_gw_info(context, gw_port, info, ext_ips)
  512         if gw_port and ext_ip_change and gw_port['network_id'] == network_id:
  513             self._update_current_gw_port(context, router_id, router,
  514                                          ext_ips)
  515         else:
  516             self._delete_current_gw_port(context, router_id, router,
  517                                          network_id)
  518             self._create_gw_port(context, router_id, router, network_id,
  519                                  ext_ips)
  520 
  521     def _check_for_external_ip_change(self, context, gw_port, ext_ips):
  522         # determine if new external IPs differ from the existing fixed_ips
  523         if not ext_ips:
  524             # no external_fixed_ips were included
  525             return False
  526         if not gw_port:
  527             return True
  528 
  529         subnet_ids = set(ip['subnet_id'] for ip in gw_port['fixed_ips'])
  530         new_subnet_ids = set(f['subnet_id'] for f in ext_ips
  531                              if f.get('subnet_id'))
  532         subnet_change = not new_subnet_ids == subnet_ids
  533         if subnet_change:
  534             return True
  535         ip_addresses = set(ip['ip_address'] for ip in gw_port['fixed_ips'])
  536         new_ip_addresses = set(f['ip_address'] for f in ext_ips
  537                                if f.get('ip_address'))
  538         ip_address_change = not ip_addresses == new_ip_addresses
  539         return ip_address_change
  540 
  541     def _ensure_router_not_in_use(self, context, router_id):
  542         """Ensure that no internal network interface is attached
  543         to the router.
  544         """
  545         router = self._get_router(context, router_id)
  546         device_owner = self._get_device_owner(context, router)
  547         if any(rp.port_type == device_owner
  548                for rp in router.attached_ports):
  549             raise l3_exc.RouterInUse(router_id=router_id)
  550         return router
  551 
  552     @db_api.retry_if_session_inactive()
  553     def delete_router(self, context, id):
  554         registry.publish(resources.ROUTER, events.BEFORE_DELETE, self,
  555                          payload=events.DBEventPayload(
  556                              context, resource_id=id))
  557         # TODO(nati) Refactor here when we have router insertion model
  558         router = self._ensure_router_not_in_use(context, id)
  559         original = self._make_router_dict(router)
  560         self._delete_current_gw_port(context, id, router, None)
  561         with context.session.begin(subtransactions=True):
  562             context.session.refresh(router)
  563 
  564         router_ports = router.attached_ports
  565         for rp in router_ports:
  566             self._core_plugin.delete_port(context.elevated(),
  567                                           rp.port.id,
  568                                           l3_port_check=False)
  569         with context.session.begin(subtransactions=True):
  570             context.session.refresh(router)
  571             registry.notify(resources.ROUTER, events.PRECOMMIT_DELETE,
  572                             self, context=context, router_db=router,
  573                             router_id=id)
  574             # we bump the revision even though we are about to delete to throw
  575             # staledataerror if something snuck in with a new interface
  576             router.bump_revision()
  577             context.session.flush()
  578             context.session.delete(router)
  579         registry.notify(resources.ROUTER, events.AFTER_DELETE, self,
  580                         context=context, router_id=id, original=original)
  581 
  582     @db_api.retry_if_session_inactive()
  583     def get_router(self, context, id, fields=None):
  584         router = self._get_router(context, id)
  585         return self._make_router_dict(router, fields)
  586 
  587     @db_api.retry_if_session_inactive()
  588     def get_routers(self, context, filters=None, fields=None,
  589                     sorts=None, limit=None, marker=None,
  590                     page_reverse=False):
  591         marker_obj = lib_db_utils.get_marker_obj(
  592             self, context, 'router', limit, marker)
  593         return model_query.get_collection(context, l3_models.Router,
  594                                           self._make_router_dict,
  595                                           filters=filters, fields=fields,
  596                                           sorts=sorts,
  597                                           limit=limit,
  598                                           marker_obj=marker_obj,
  599                                           page_reverse=page_reverse)
  600 
  601     @db_api.retry_if_session_inactive()
  602     def get_routers_count(self, context, filters=None):
  603         return model_query.get_collection_count(context, l3_models.Router,
  604                                                 filters=filters)
  605 
  606     def _check_for_dup_router_subnets(self, context, router,
  607                                       network_id, new_subnets,
  608                                       include_gateway=False):
  609         # It's possible these ports are on the same network, but
  610         # different subnets.
  611         new_subnet_ids = {s['id'] for s in new_subnets}
  612         router_subnets = []
  613         for p in (rp.port for rp in router.attached_ports):
  614             for ip in p['fixed_ips']:
  615                 if ip['subnet_id'] in new_subnet_ids:
  616                     msg = (_("Router already has a port on subnet %s")
  617                            % ip['subnet_id'])
  618                     raise n_exc.BadRequest(resource='router', msg=msg)
  619                 gw_owner = (p.get('device_owner') == DEVICE_OWNER_ROUTER_GW)
  620                 if include_gateway == gw_owner:
  621                     router_subnets.append(ip['subnet_id'])
  622 
  623         # Ignore temporary Prefix Delegation CIDRs
  624         new_subnets = [s for s in new_subnets
  625                        if s['cidr'] != constants.PROVISIONAL_IPV6_PD_PREFIX]
  626         id_filter = {'id': router_subnets}
  627         subnets = self._core_plugin.get_subnets(context.elevated(),
  628                                                 filters=id_filter)
  629         for sub in subnets:
  630             cidr = sub['cidr']
  631             ipnet = netaddr.IPNetwork(cidr)
  632             for s in new_subnets:
  633                 new_cidr = s['cidr']
  634                 new_ipnet = netaddr.IPNetwork(new_cidr)
  635                 match1 = netaddr.all_matching_cidrs(new_ipnet, [cidr])
  636                 match2 = netaddr.all_matching_cidrs(ipnet, [new_cidr])
  637                 if match1 or match2:
  638                     data = {'subnet_cidr': new_cidr,
  639                             'subnet_id': s['id'],
  640                             'cidr': cidr,
  641                             'sub_id': sub['id']}
  642                     msg = (_("Cidr %(subnet_cidr)s of subnet "
  643                              "%(subnet_id)s overlaps with cidr %(cidr)s "
  644                              "of subnet %(sub_id)s") % data)
  645                     raise n_exc.BadRequest(resource='router', msg=msg)
  646 
  647     def _get_device_owner(self, context, router=None):
  648         """Get device_owner for the specified router."""
  649         # NOTE(armando-migliaccio): in the base case this is invariant
  650         return DEVICE_OWNER_ROUTER_INTF
  651 
  652     def _validate_interface_info(self, interface_info, for_removal=False):
  653         port_id_specified = interface_info and 'port_id' in interface_info
  654         subnet_id_specified = interface_info and 'subnet_id' in interface_info
  655         if not (port_id_specified or subnet_id_specified):
  656             msg = _("Either subnet_id or port_id must be specified")
  657             raise n_exc.BadRequest(resource='router', msg=msg)
  658         for key in ('port_id', 'subnet_id'):
  659             if key not in interface_info:
  660                 continue
  661             err = validators.validate_uuid(interface_info[key])
  662             if err:
  663                 raise n_exc.BadRequest(resource='router', msg=err)
  664         if not for_removal:
  665             if port_id_specified and subnet_id_specified:
  666                 msg = _("Cannot specify both subnet-id and port-id")
  667                 raise n_exc.BadRequest(resource='router', msg=msg)
  668         return port_id_specified, subnet_id_specified
  669 
  670     def _check_router_port(self, context, port_id, device_id):
  671         """Check that a port is available for an attachment to a router
  672 
  673         :param context: The context of the request.
  674         :param port_id: The port to be attached.
  675         :param device_id: This method will check that device_id corresponds to
  676         the device_id of the port. It raises PortInUse exception if it
  677         doesn't.
  678         :returns: The port description returned by the core plugin.
  679         :raises: PortInUse if the device_id is not the same as the port's one.
  680         :raises: BadRequest if the port has no fixed IP.
  681         """
  682         port = self._core_plugin.get_port(context, port_id)
  683         if port['device_id'] != device_id:
  684             raise n_exc.PortInUse(net_id=port['network_id'],
  685                                   port_id=port['id'],
  686                                   device_id=port['device_id'])
  687         if not port['fixed_ips']:
  688             msg = _('Router port must have at least one fixed IP')
  689             raise n_exc.BadRequest(resource='router', msg=msg)
  690         return port
  691 
  692     def _validate_port_in_range_or_admin(self, context, subnets, port):
  693         if context.is_admin:
  694             return
  695         subnets_by_id = {}
  696         for s in subnets:
  697             addr_set = netaddr.IPSet()
  698             for range in s['allocation_pools']:
  699                 addr_set.add(netaddr.IPRange(netaddr.IPAddress(range['start']),
  700                                              netaddr.IPAddress(range['end'])))
  701             subnets_by_id[s['id']] = (addr_set, s['project_id'],)
  702         for subnet_id, ip in [(fix_ip['subnet_id'], fix_ip['ip_address'],)
  703                               for fix_ip in port['fixed_ips']]:
  704             if (ip not in subnets_by_id[subnet_id][0] and
  705                     context.project_id != subnets_by_id[subnet_id][1]):
  706                 msg = (_('Cannot add interface to router because specified '
  707                          'port %(port)s has an IP address out of the '
  708                          'allocation pool of subnet %(subnet)s, which is not '
  709                          'owned by the project making the request') %
  710                        {'port': port['id'], 'subnet': subnet_id})
  711                 raise n_exc.BadRequest(resource='router', msg=msg)
  712 
  713     def _validate_router_port_info(self, context, router, port_id):
  714         with db_api.autonested_transaction(context.session):
  715             # check again within transaction to mitigate race
  716             port = self._check_router_port(context, port_id, router.id)
  717 
  718             # Only allow one router port with IPv6 subnets per network id
  719             if self._port_has_ipv6_address(port):
  720                 for existing_port in (rp.port for rp in router.attached_ports):
  721                     if (existing_port['network_id'] == port['network_id'] and
  722                             self._port_has_ipv6_address(existing_port)):
  723                         msg = _("Cannot have multiple router ports with the "
  724                                 "same network id if both contain IPv6 "
  725                                 "subnets. Existing port %(p)s has IPv6 "
  726                                 "subnet(s) and network id %(nid)s")
  727                         raise n_exc.BadRequest(resource='router', msg=msg % {
  728                             'p': existing_port['id'],
  729                             'nid': existing_port['network_id']})
  730 
  731             fixed_ips = [ip for ip in port['fixed_ips']]
  732             subnets = []
  733             for fixed_ip in fixed_ips:
  734                 subnet = self._core_plugin.get_subnet(context,
  735                                                       fixed_ip['subnet_id'])
  736                 subnets.append(subnet)
  737 
  738             if subnets:
  739                 self._check_for_dup_router_subnets(context, router,
  740                                                    port['network_id'],
  741                                                    subnets)
  742 
  743             # Keep the restriction against multiple IPv4 subnets
  744             if len([s for s in subnets if s['ip_version'] == 4]) > 1:
  745                 msg = _("Cannot have multiple "
  746                         "IPv4 subnets on router port")
  747                 raise n_exc.BadRequest(resource='router', msg=msg)
  748             self._validate_port_in_range_or_admin(context, subnets, port)
  749             return port, subnets
  750 
  751     def _notify_attaching_interface(self, context, router_db, port,
  752                                     interface_info):
  753         """Notify third party code that an interface is being attached to a
  754         router
  755 
  756         :param context: The context of the request.
  757         :param router_db: The router db object having an interface attached.
  758         :param port: The port object being attached to the router.
  759         :param interface_info: The requested interface attachment info passed
  760         to add_router_interface.
  761         :raises: RouterInterfaceAttachmentConflict if a third party code
  762         prevent the port to be attach to the router.
  763         """
  764         try:
  765             registry.notify(resources.ROUTER_INTERFACE,
  766                             events.BEFORE_CREATE,
  767                             self,
  768                             context=context,
  769                             router_db=router_db,
  770                             port=port,
  771                             interface_info=interface_info,
  772                             router_id=router_db.id,
  773                             network_id=port['network_id'])
  774         except exceptions.CallbackFailure as e:
  775             # raise the underlying exception
  776             reason = (_('cannot perform router interface attachment '
  777                         'due to %(reason)s') % {'reason': e})
  778             raise l3_exc.RouterInterfaceAttachmentConflict(reason=reason)
  779 
  780     def _add_interface_by_port(self, context, router, port_id, owner):
  781         # Update owner before actual process in order to avoid the
  782         # case where a port might get attached to a router without the
  783         # owner successfully updating due to an unavailable backend.
  784         self._core_plugin.update_port(
  785             context, port_id, {'port': {'device_id': router.id,
  786                                         'device_owner': owner}})
  787 
  788         return self._validate_router_port_info(context, router, port_id)
  789 
  790     def _port_has_ipv6_address(self, port):
  791         for fixed_ip in port['fixed_ips']:
  792             if netaddr.IPNetwork(fixed_ip['ip_address']).version == 6:
  793                 return True
  794 
  795     def _find_ipv6_router_port_by_network(self, context, router, net_id):
  796         router_dev_owner = self._get_device_owner(context, router)
  797         for port in router.attached_ports:
  798             p = port['port']
  799             if p['device_owner'] != router_dev_owner:
  800                 # we don't want any special purpose internal ports
  801                 continue
  802             if p['network_id'] == net_id and self._port_has_ipv6_address(p):
  803                 return port
  804 
  805     def _add_interface_by_subnet(self, context, router, subnet_id, owner):
  806         subnet = self._core_plugin.get_subnet(context, subnet_id)
  807         if not subnet['gateway_ip']:
  808             msg = _('Subnet for router interface must have a gateway IP')
  809             raise n_exc.BadRequest(resource='router', msg=msg)
  810         if subnet['project_id'] != context.project_id and not context.is_admin:
  811             msg = (_('Cannot add interface to router because subnet %s is not '
  812                      'owned by project making the request') % subnet_id)
  813             raise n_exc.BadRequest(resource='router', msg=msg)
  814         if (subnet['ip_version'] == 6 and subnet['ipv6_ra_mode'] is None and
  815                 subnet['ipv6_address_mode'] is not None):
  816             msg = (_('IPv6 subnet %s configured to receive RAs from an '
  817                    'external router cannot be added to Neutron Router.') %
  818                    subnet['id'])
  819             raise n_exc.BadRequest(resource='router', msg=msg)
  820         self._check_for_dup_router_subnets(context, router,
  821                                            subnet['network_id'], [subnet])
  822         fixed_ip = {'ip_address': subnet['gateway_ip'],
  823                     'subnet_id': subnet['id']}
  824 
  825         if (subnet['ip_version'] == 6 and not
  826                 ipv6_utils.is_ipv6_pd_enabled(subnet)):
  827             # Add new prefix to an existing ipv6 port with the same network id
  828             # if one exists
  829             port = self._find_ipv6_router_port_by_network(context, router,
  830                                                           subnet['network_id'])
  831             if port:
  832                 fixed_ips = list(map(dict, port['port']['fixed_ips']))
  833                 fixed_ips.append(fixed_ip)
  834                 return (self._core_plugin.update_port(
  835                             context, port['port_id'],
  836                             {'port': {'fixed_ips': fixed_ips}}),
  837                         [subnet],
  838                         False)
  839 
  840         port_data = {'tenant_id': router.tenant_id,
  841                      'network_id': subnet['network_id'],
  842                      'fixed_ips': [fixed_ip],
  843                      'admin_state_up': True,
  844                      'device_id': router.id,
  845                      'device_owner': owner,
  846                      'name': ''}
  847         return plugin_utils.create_port(
  848             self._core_plugin, context, {'port': port_data}), [subnet], True
  849 
  850     @staticmethod
  851     def _make_router_interface_info(
  852             router_id, tenant_id, port_id, network_id, subnet_id, subnet_ids):
  853         return {
  854             'id': router_id,
  855             'tenant_id': tenant_id,
  856             'port_id': port_id,
  857             'network_id': network_id,
  858             'subnet_id': subnet_id,  # deprecated by IPv6 multi-prefix
  859             'subnet_ids': subnet_ids
  860         }
  861 
  862     @db_api.retry_if_session_inactive()
  863     def add_router_interface(self, context, router_id, interface_info=None):
  864         router = self._get_router(context, router_id)
  865         add_by_port, add_by_sub = self._validate_interface_info(interface_info)
  866         device_owner = self._get_device_owner(context, router_id)
  867 
  868         # This should be True unless adding an IPv6 prefix to an existing port
  869         new_router_intf = True
  870         cleanup_port = False
  871 
  872         if add_by_port:
  873             port_id = interface_info['port_id']
  874             port = self._check_router_port(context, port_id, '')
  875             revert_value = {'device_id': '',
  876                             'device_owner': port['device_owner']}
  877             with plugin_utils.update_port_on_error(
  878                     self._core_plugin, context, port_id, revert_value):
  879                 port, subnets = self._add_interface_by_port(
  880                     context, router, port_id, device_owner)
  881         # add_by_subnet is not used here, because the validation logic of
  882         # _validate_interface_info ensures that either of add_by_* is True.
  883         else:
  884             port, subnets, new_router_intf = self._add_interface_by_subnet(
  885                     context, router, interface_info['subnet_id'], device_owner)
  886             cleanup_port = new_router_intf  # only cleanup port we created
  887             revert_value = {'device_id': '',
  888                             'device_owner': port['device_owner']}
  889 
  890         if cleanup_port:
  891             mgr = plugin_utils.delete_port_on_error(
  892                 self._core_plugin, context, port['id'])
  893         else:
  894             mgr = plugin_utils.update_port_on_error(
  895                 self._core_plugin, context, port['id'], revert_value)
  896 
  897         if new_router_intf:
  898             with mgr:
  899                 self._notify_attaching_interface(context, router_db=router,
  900                                                  port=port,
  901                                                  interface_info=interface_info)
  902                 self._add_router_port(
  903                     context, port['id'], router.id, device_owner)
  904 
  905         gw_ips = []
  906         gw_network_id = None
  907         if router.gw_port:
  908             gw_network_id = router.gw_port.network_id
  909             gw_ips = [x['ip_address'] for x in router.gw_port.fixed_ips]
  910 
  911         registry.notify(resources.ROUTER_INTERFACE,
  912                         events.AFTER_CREATE,
  913                         self,
  914                         context=context,
  915                         network_id=gw_network_id,
  916                         gateway_ips=gw_ips,
  917                         cidrs=[x['cidr'] for x in subnets],
  918                         subnets=subnets,
  919                         port_id=port['id'],
  920                         router_id=router_id,
  921                         port=port,
  922                         new_interface=new_router_intf,
  923                         interface_info=interface_info)
  924 
  925         with context.session.begin(subtransactions=True):
  926             context.session.refresh(router)
  927         return self._make_router_interface_info(
  928             router.id, port['tenant_id'], port['id'], port['network_id'],
  929             subnets[-1]['id'], [subnet['id'] for subnet in subnets])
  930 
  931     @db_api.retry_if_session_inactive()
  932     def _add_router_port(self, context, port_id, router_id, device_owner):
  933         l3_obj.RouterPort(
  934             context,
  935             port_id=port_id,
  936             router_id=router_id,
  937             port_type=device_owner
  938         ).create()
  939         # Update owner after actual process again in order to
  940         # make sure the records in routerports table and ports
  941         # table are consistent.
  942         self._core_plugin.update_port(
  943             context, port_id, {'port': {'device_id': router_id,
  944                                         'device_owner': device_owner}})
  945 
  946     def _check_router_interface_not_in_use(self, router_id, subnet_id):
  947         context = n_ctx.get_admin_context()
  948         subnet = self._core_plugin.get_subnet(context, subnet_id)
  949         subnet_cidr = netaddr.IPNetwork(subnet['cidr'])
  950 
  951         fip_objs = l3_obj.FloatingIP.get_objects(context, router_id=router_id)
  952         pf_plugin = directory.get_plugin(plugin_constants.PORTFORWARDING)
  953         if pf_plugin:
  954             fip_ids = [fip_obj.id for fip_obj in fip_objs]
  955             pf_objs = port_forwarding.PortForwarding.get_objects(
  956                 context, floatingip_id=fip_ids)
  957             for pf_obj in pf_objs:
  958                 if (pf_obj.internal_ip_address and
  959                         pf_obj.internal_ip_address in subnet_cidr):
  960                     raise l3_exc.RouterInterfaceInUseByFloatingIP(
  961                         router_id=router_id, subnet_id=subnet_id)
  962 
  963         for fip_obj in fip_objs:
  964             if (fip_obj.fixed_ip_address and
  965                     fip_obj.fixed_ip_address in subnet_cidr):
  966                 raise l3_exc.RouterInterfaceInUseByFloatingIP(
  967                     router_id=router_id, subnet_id=subnet_id)
  968 
  969     def _confirm_router_interface_not_in_use(self, context, router_id,
  970                                              subnet_id):
  971         try:
  972             registry.publish(
  973                 resources.ROUTER_INTERFACE,
  974                 events.BEFORE_DELETE, self,
  975                 payload=events.DBEventPayload(
  976                     context, metadata={'subnet_id': subnet_id},
  977                     resource_id=router_id))
  978         except exceptions.CallbackFailure as e:
  979             # NOTE(armax): preserve old check's behavior
  980             if len(e.errors) == 1:
  981                 raise e.errors[0].error
  982             raise l3_exc.RouterInUse(router_id=router_id, reason=e)
  983 
  984         self._check_router_interface_not_in_use(router_id, subnet_id)
  985 
  986     def _remove_interface_by_port(self, context, router_id,
  987                                   port_id, subnet_id, owner):
  988         ports = port_obj.Port.get_ports_by_router_and_port(
  989             context, router_id, owner, port_id)
  990         if len(ports) < 1:
  991             raise l3_exc.RouterInterfaceNotFound(
  992                 router_id=router_id, port_id=port_id)
  993 
  994         port = ports[0]
  995         port_subnet_ids = [fixed_ip['subnet_id']
  996                            for fixed_ip in port['fixed_ips']]
  997         if subnet_id and subnet_id not in port_subnet_ids:
  998             raise n_exc.SubnetMismatchForPort(
  999                 port_id=port_id, subnet_id=subnet_id)
 1000         subnets = [self._core_plugin.get_subnet(context, port_subnet_id)
 1001                    for port_subnet_id in port_subnet_ids]
 1002         for port_subnet_id in port_subnet_ids:
 1003             self._confirm_router_interface_not_in_use(
 1004                     context, router_id, port_subnet_id)
 1005         self._core_plugin.delete_port(context, port['id'],
 1006                                       l3_port_check=False)
 1007         return port, subnets
 1008 
 1009     def _remove_interface_by_subnet(self, context,
 1010                                     router_id, subnet_id, owner):
 1011         self._confirm_router_interface_not_in_use(
 1012             context, router_id, subnet_id)
 1013         subnet = self._core_plugin.get_subnet(context, subnet_id)
 1014         ports = port_obj.Port.get_ports_by_router_and_network(
 1015             context, router_id, owner, subnet['network_id'])
 1016 
 1017         for p in ports:
 1018             try:
 1019                 p = self._core_plugin.get_port(context, p.id)
 1020             except n_exc.PortNotFound:
 1021                 continue
 1022             port_subnets = [fip['subnet_id'] for fip in p['fixed_ips']]
 1023             if subnet_id in port_subnets and len(port_subnets) > 1:
 1024                 # multiple prefix port - delete prefix from port
 1025                 fixed_ips = [dict(fip) for fip in p['fixed_ips']
 1026                              if fip['subnet_id'] != subnet_id]
 1027                 self._core_plugin.update_port(
 1028                     context, p['id'], {'port': {'fixed_ips': fixed_ips}})
 1029                 return (p, [subnet])
 1030             elif subnet_id in port_subnets:
 1031                 # only one subnet on port - delete the port
 1032                 self._core_plugin.delete_port(context, p['id'],
 1033                                               l3_port_check=False)
 1034                 return (p, [subnet])
 1035         raise l3_exc.RouterInterfaceNotFoundForSubnet(
 1036             router_id=router_id, subnet_id=subnet_id)
 1037 
 1038     @db_api.retry_if_session_inactive()
 1039     def remove_router_interface(self, context, router_id, interface_info):
 1040         remove_by_port, _ = self._validate_interface_info(interface_info,
 1041                                                           for_removal=True)
 1042         port_id = interface_info.get('port_id')
 1043         subnet_id = interface_info.get('subnet_id')
 1044         device_owner = self._get_device_owner(context, router_id)
 1045         if remove_by_port:
 1046             port, subnets = self._remove_interface_by_port(context, router_id,
 1047                                                            port_id, subnet_id,
 1048                                                            device_owner)
 1049         else:
 1050             port, subnets = self._remove_interface_by_subnet(
 1051                     context, router_id, subnet_id, device_owner)
 1052 
 1053         gw_network_id = None
 1054         gw_ips = []
 1055         router = self._get_router(context, router_id)
 1056         if router.gw_port:
 1057             gw_network_id = router.gw_port.network_id
 1058             gw_ips = [x['ip_address'] for x in router.gw_port.fixed_ips]
 1059 
 1060         registry.notify(resources.ROUTER_INTERFACE,
 1061                         events.AFTER_DELETE,
 1062                         self,
 1063                         context=context,
 1064                         cidrs=[x['cidr'] for x in subnets],
 1065                         network_id=gw_network_id,
 1066                         gateway_ips=gw_ips,
 1067                         port=port,
 1068                         router_id=router_id,
 1069                         interface_info=interface_info)
 1070         with context.session.begin(subtransactions=True):
 1071             context.session.refresh(router)
 1072         return self._make_router_interface_info(router_id, port['tenant_id'],
 1073                                                 port['id'], port['network_id'],
 1074                                                 subnets[0]['id'],
 1075                                                 [subnet['id'] for subnet in
 1076                                                     subnets])
 1077 
 1078     def _get_floatingip(self, context, id):
 1079         floatingip = l3_obj.FloatingIP.get_object(context, id=id)
 1080         if not floatingip:
 1081             raise l3_exc.FloatingIPNotFound(floatingip_id=id)
 1082         return floatingip
 1083 
 1084     def _make_floatingip_dict(self, floatingip, fields=None,
 1085                               process_extensions=True):
 1086         floating_ip_address = (str(floatingip.floating_ip_address)
 1087                                if floatingip.floating_ip_address else None)
 1088         fixed_ip_address = (str(floatingip.fixed_ip_address)
 1089                             if floatingip.fixed_ip_address else None)
 1090         res = {'id': floatingip.id,
 1091                'tenant_id': floatingip.project_id,
 1092                'floating_ip_address': floating_ip_address,
 1093                'floating_network_id': floatingip.floating_network_id,
 1094                'router_id': floatingip.router_id,
 1095                'port_id': floatingip.fixed_port_id,
 1096                'fixed_ip_address': fixed_ip_address,
 1097                'status': floatingip.status}
 1098         # NOTE(mlavalle): The following assumes this mixin is used in a
 1099         # class inheriting from CommonDbMixin, which is true for all existing
 1100         # plugins.
 1101         # TODO(lujinluo): Change floatingip.db_obj to floatingip once all
 1102         # codes are migrated to use Floating IP OVO object.
 1103         if process_extensions:
 1104             resource_extend.apply_funcs(
 1105                 l3_apidef.FLOATINGIPS, res, floatingip.db_obj)
 1106         return lib_db_utils.resource_fields(res, fields)
 1107 
 1108     def _get_router_for_floatingip(self, context, internal_port,
 1109                                    internal_subnet_id,
 1110                                    external_network_id):
 1111         subnet = self._core_plugin.get_subnet(context, internal_subnet_id)
 1112         return self.get_router_for_floatingip(
 1113             context, internal_port, subnet, external_network_id)
 1114 
 1115     # NOTE(yamamoto): This method is an override point for plugins
 1116     # inheriting this class.  Do not optimize this out.
 1117     def get_router_for_floatingip(self, context, internal_port,
 1118                                   internal_subnet, external_network_id):
 1119         """Find a router to handle the floating-ip association.
 1120 
 1121         :param internal_port: The port for the fixed-ip.
 1122         :param internal_subnet: The subnet for the fixed-ip.
 1123         :param external_network_id: The external network for floating-ip.
 1124 
 1125         :raises: ExternalGatewayForFloatingIPNotFound if no suitable router
 1126                  is found.
 1127         """
 1128 
 1129         # Find routers(with router_id and interface address) that
 1130         # connect given internal subnet and the external network.
 1131         # Among them, if the router's interface address matches
 1132         # with subnet's gateway-ip, return that router.
 1133         # Otherwise return the first router.
 1134         RouterPort = l3_models.RouterPort
 1135         gw_port = orm.aliased(models_v2.Port, name="gw_port")
 1136         # TODO(lujinluo): Need IPAllocation and Port object
 1137         routerport_qry = context.session.query(
 1138             RouterPort.router_id, models_v2.IPAllocation.ip_address).join(
 1139             RouterPort.port, models_v2.Port.fixed_ips).filter(
 1140             models_v2.Port.network_id == internal_port['network_id'],
 1141             RouterPort.port_type.in_(constants.ROUTER_INTERFACE_OWNERS),
 1142             models_v2.IPAllocation.subnet_id == internal_subnet['id']
 1143         ).join(gw_port, gw_port.device_id == RouterPort.router_id).filter(
 1144             gw_port.network_id == external_network_id,
 1145             gw_port.device_owner == DEVICE_OWNER_ROUTER_GW
 1146         ).distinct()
 1147 
 1148         first_router_id = None
 1149         for router_id, interface_ip in routerport_qry:
 1150             if interface_ip == internal_subnet['gateway_ip']:
 1151                 return router_id
 1152             if not first_router_id:
 1153                 first_router_id = router_id
 1154         if first_router_id:
 1155             return first_router_id
 1156 
 1157         raise l3_exc.ExternalGatewayForFloatingIPNotFound(
 1158             subnet_id=internal_subnet['id'],
 1159             external_network_id=external_network_id,
 1160             port_id=internal_port['id'])
 1161 
 1162     def _port_ipv4_fixed_ips(self, port):
 1163         return [ip for ip in port['fixed_ips']
 1164                 if netaddr.IPAddress(ip['ip_address']).version == 4]
 1165 
 1166     def _internal_fip_assoc_data(self, context, fip, tenant_id):
 1167         """Retrieve internal port data for floating IP.
 1168 
 1169         Retrieve information concerning the internal port where
 1170         the floating IP should be associated to.
 1171         """
 1172         internal_port = self._core_plugin.get_port(context, fip['port_id'])
 1173         if internal_port['tenant_id'] != tenant_id and not context.is_admin:
 1174             port_id = fip['port_id']
 1175             msg = (_('Cannot process floating IP association with '
 1176                      'Port %s, since that port is owned by a '
 1177                      'different tenant') % port_id)
 1178             raise n_exc.BadRequest(resource='floatingip', msg=msg)
 1179 
 1180         internal_subnet_id = None
 1181         if not utils.is_fip_serviced(internal_port.get('device_owner')):
 1182             msg = _('Port %(id)s is unable to be assigned a floating IP')
 1183             raise n_exc.BadRequest(resource='floatingip', msg=msg)
 1184         if fip.get('fixed_ip_address'):
 1185             internal_ip_address = fip['fixed_ip_address']
 1186             if netaddr.IPAddress(internal_ip_address).version != 4:
 1187                 msg = (_('Cannot process floating IP association with %s, '
 1188                          'since that is not an IPv4 address') %
 1189                        internal_ip_address)
 1190                 raise n_exc.BadRequest(resource='floatingip', msg=msg)
 1191             for ip in internal_port['fixed_ips']:
 1192                 if ip['ip_address'] == internal_ip_address:
 1193                     internal_subnet_id = ip['subnet_id']
 1194             if not internal_subnet_id:
 1195                 msg = (_('Port %(id)s does not have fixed ip %(address)s') %
 1196                        {'id': internal_port['id'],
 1197                         'address': internal_ip_address})
 1198                 raise n_exc.BadRequest(resource='floatingip', msg=msg)
 1199         else:
 1200             ipv4_fixed_ips = self._port_ipv4_fixed_ips(internal_port)
 1201             if not ipv4_fixed_ips:
 1202                 msg = (_('Cannot add floating IP to port %s that has '
 1203                          'no fixed IPv4 addresses') % internal_port['id'])
 1204                 raise n_exc.BadRequest(resource='floatingip', msg=msg)
 1205             if len(ipv4_fixed_ips) > 1:
 1206                 msg = (_('Port %s has multiple fixed IPv4 addresses.  Must '
 1207                          'provide a specific IPv4 address when assigning a '
 1208                          'floating IP') % internal_port['id'])
 1209                 raise n_exc.BadRequest(resource='floatingip', msg=msg)
 1210             internal_ip_address = ipv4_fixed_ips[0]['ip_address']
 1211             internal_subnet_id = ipv4_fixed_ips[0]['subnet_id']
 1212         return internal_port, internal_subnet_id, internal_ip_address
 1213 
 1214     def _get_assoc_data(self, context, fip, floatingip_obj):
 1215         """Determine/extract data associated with the internal port.
 1216 
 1217         When a floating IP is associated with an internal port,
 1218         we need to extract/determine some data associated with the
 1219         internal port, including the internal_ip_address, and router_id.
 1220         The confirmation of the internal port whether owned by the tenant who
 1221         owns the floating IP will be confirmed by _get_router_for_floatingip.
 1222         """
 1223         (internal_port, internal_subnet_id,
 1224          internal_ip_address) = self._internal_fip_assoc_data(
 1225             context, fip, floatingip_obj.project_id)
 1226         router_id = self._get_router_for_floatingip(
 1227             context, internal_port,
 1228             internal_subnet_id, floatingip_obj.floating_network_id)
 1229 
 1230         if self.is_router_distributed(context, router_id):
 1231             if not plugin_utils.can_port_be_bound_to_virtual_bridge(
 1232                     internal_port):
 1233                 msg = _('Port VNIC type is not valid to associate a FIP in '
 1234                         'DVR mode')
 1235                 raise n_exc.BadRequest(resource='floatingip', msg=msg)
 1236 
 1237         return (fip['port_id'], internal_ip_address, router_id)
 1238 
 1239     def _check_and_get_fip_assoc(self, context, fip, floatingip_obj):
 1240         port_id = internal_ip_address = router_id = None
 1241         if fip.get('fixed_ip_address') and not fip.get('port_id'):
 1242             msg = _("fixed_ip_address cannot be specified without a port_id")
 1243             raise n_exc.BadRequest(resource='floatingip', msg=msg)
 1244         if fip.get('port_id'):
 1245             port_id, internal_ip_address, router_id = self._get_assoc_data(
 1246                 context,
 1247                 fip,
 1248                 floatingip_obj)
 1249 
 1250             if port_id == floatingip_obj.fixed_port_id:
 1251                 # Floating IP association is not changed.
 1252                 return port_id, internal_ip_address, router_id
 1253 
 1254             fip_exists = l3_obj.FloatingIP.objects_exist(
 1255                     context,
 1256                     fixed_port_id=fip['port_id'],
 1257                     floating_network_id=floatingip_obj.floating_network_id,
 1258                     fixed_ip_address=netaddr.IPAddress(internal_ip_address))
 1259             if fip_exists:
 1260                 floating_ip_address = (str(floatingip_obj.floating_ip_address)
 1261                                        if floatingip_obj.floating_ip_address
 1262                                        else None)
 1263                 raise l3_exc.FloatingIPPortAlreadyAssociated(
 1264                     port_id=fip['port_id'],
 1265                     fip_id=floatingip_obj.id,
 1266                     floating_ip_address=floating_ip_address,
 1267                     fixed_ip=internal_ip_address,
 1268                     net_id=floatingip_obj.floating_network_id)
 1269 
 1270         if fip and 'port_id' not in fip and floatingip_obj.fixed_port_id:
 1271             # NOTE(liuyulong): without the fix of bug #1610045 here could
 1272             # also let floating IP can be dissociated with an empty
 1273             # updating dict.
 1274             fip['port_id'] = floatingip_obj.fixed_port_id
 1275             port_id, internal_ip_address, router_id = self._get_assoc_data(
 1276                 context, fip, floatingip_obj)
 1277 
 1278         # Condition for floating IP with binding port forwarding
 1279         if not floatingip_obj.fixed_port_id and floatingip_obj.router_id:
 1280             router_id = floatingip_obj.router_id
 1281 
 1282         # After all upper conditions, if updating API dict is submitted with
 1283         # {'port_id': null}, then the floating IP cloud also be dissociated.
 1284         return port_id, internal_ip_address, router_id
 1285 
 1286     def _update_fip_assoc(self, context, fip, floatingip_obj, external_port):
 1287         previous_router_id = floatingip_obj.router_id
 1288         port_id, internal_ip_address, router_id = (
 1289             self._check_and_get_fip_assoc(context, fip, floatingip_obj))
 1290         floatingip_obj.fixed_ip_address = (
 1291             netaddr.IPAddress(internal_ip_address)
 1292             if internal_ip_address else None)
 1293         floatingip_obj.fixed_port_id = port_id
 1294         floatingip_obj.router_id = router_id
 1295         floatingip_obj.last_known_router_id = previous_router_id
 1296         if 'description' in fip:
 1297             floatingip_obj.description = fip['description']
 1298         floating_ip_address = (str(floatingip_obj.floating_ip_address)
 1299                                if floatingip_obj.floating_ip_address else None)
 1300         return {'fixed_ip_address': internal_ip_address,
 1301                 'fixed_port_id': port_id,
 1302                 'router_id': router_id,
 1303                 'last_known_router_id': previous_router_id,
 1304                 'floating_ip_address': floating_ip_address,
 1305                 'floating_network_id': floatingip_obj.floating_network_id,
 1306                 'floating_ip_id': floatingip_obj.id,
 1307                 'context': context}
 1308 
 1309     def _is_ipv4_network(self, context, net_id):
 1310         net = self._core_plugin._get_network(context, net_id)
 1311         return any(s.ip_version == 4 for s in net.subnets)
 1312 
 1313     def _create_floatingip(self, context, floatingip,
 1314                            initial_status=constants.FLOATINGIP_STATUS_ACTIVE):
 1315         try:
 1316             registry.publish(resources.FLOATING_IP, events.BEFORE_CREATE,
 1317                              self, payload=events.DBEventPayload(
 1318                                  context, request_body=floatingip))
 1319         except exceptions.CallbackFailure as e:
 1320             # raise the underlying exception
 1321             raise e.errors[0].error
 1322 
 1323         fip = floatingip['floatingip']
 1324         fip_id = uuidutils.generate_uuid()
 1325 
 1326         f_net_id = fip['floating_network_id']
 1327         if not self._core_plugin._network_is_external(context, f_net_id):
 1328             msg = _("Network %s is not a valid external network") % f_net_id
 1329             raise n_exc.BadRequest(resource='floatingip', msg=msg)
 1330 
 1331         if not self._is_ipv4_network(context, f_net_id):
 1332             msg = _("Network %s does not contain any IPv4 subnet") % f_net_id
 1333             raise n_exc.BadRequest(resource='floatingip', msg=msg)
 1334 
 1335         # This external port is never exposed to the tenant.
 1336         # it is used purely for internal system and admin use when
 1337         # managing floating IPs.
 1338 
 1339         port = {'tenant_id': '',  # tenant intentionally not set
 1340                 'network_id': f_net_id,
 1341                 'admin_state_up': True,
 1342                 'device_id': 'PENDING',
 1343                 'device_owner': DEVICE_OWNER_FLOATINGIP,
 1344                 'status': constants.PORT_STATUS_NOTAPPLICABLE,
 1345                 'name': ''}
 1346 
 1347         # Both subnet_id and floating_ip_address are accepted, if
 1348         # floating_ip_address is not in the subnet,
 1349         # InvalidIpForSubnet exception will be raised.
 1350         fixed_ip = {}
 1351         if validators.is_attr_set(fip.get('subnet_id')):
 1352             fixed_ip['subnet_id'] = fip['subnet_id']
 1353         if validators.is_attr_set(fip.get('floating_ip_address')):
 1354             fixed_ip['ip_address'] = fip['floating_ip_address']
 1355         if fixed_ip:
 1356             port['fixed_ips'] = [fixed_ip]
 1357 
 1358         # 'status' in port dict could not be updated by default, use
 1359         # check_allow_post to stop the verification of system
 1360         external_port = plugin_utils.create_port(
 1361             self._core_plugin, context.elevated(),
 1362             {'port': port}, check_allow_post=False)
 1363 
 1364         with plugin_utils.delete_port_on_error(
 1365                 self._core_plugin, context.elevated(),
 1366                 external_port['id']),\
 1367                 context.session.begin(subtransactions=True):
 1368             # Ensure IPv4 addresses are allocated on external port
 1369             external_ipv4_ips = self._port_ipv4_fixed_ips(external_port)
 1370             if not external_ipv4_ips:
 1371                 raise n_exc.ExternalIpAddressExhausted(net_id=f_net_id)
 1372 
 1373             floating_fixed_ip = external_ipv4_ips[0]
 1374             floating_ip_address = floating_fixed_ip['ip_address']
 1375             floatingip_obj = l3_obj.FloatingIP(
 1376                 context,
 1377                 id=fip_id,
 1378                 project_id=fip['tenant_id'],
 1379                 status=initial_status,
 1380                 floating_network_id=fip['floating_network_id'],
 1381                 floating_ip_address=floating_ip_address,
 1382                 floating_port_id=external_port['id'],
 1383                 description=fip.get('description'))
 1384             # Update association with internal port
 1385             # and define external IP address
 1386             assoc_result = self._update_fip_assoc(
 1387                 context, fip, floatingip_obj, external_port)
 1388             floatingip_obj.create()
 1389             floatingip_dict = self._make_floatingip_dict(
 1390                 floatingip_obj, process_extensions=False)
 1391             if self._is_dns_integration_supported:
 1392                 dns_data = self._process_dns_floatingip_create_precommit(
 1393                     context, floatingip_dict, fip)
 1394             if self._is_fip_qos_supported:
 1395                 self._process_extra_fip_qos_create(context, fip_id, fip)
 1396             floatingip_obj = l3_obj.FloatingIP.get_object(
 1397                 context, id=floatingip_obj.id)
 1398             floatingip_db = floatingip_obj.db_obj
 1399 
 1400             registry.notify(resources.FLOATING_IP, events.PRECOMMIT_CREATE,
 1401                             self, context=context, floatingip=fip,
 1402                             floatingip_id=fip_id,
 1403                             floatingip_db=floatingip_db)
 1404 
 1405         self._core_plugin.update_port(
 1406             context.elevated(), external_port['id'],
 1407             {'port': {'device_id': fip_id,
 1408                       'project_id': fip['tenant_id']}})
 1409         registry.notify(resources.FLOATING_IP,
 1410                         events.AFTER_UPDATE,
 1411                         self._update_fip_assoc,
 1412                         **assoc_result)
 1413 
 1414         if self._is_dns_integration_supported:
 1415             self._process_dns_floatingip_create_postcommit(context,
 1416                                                            floatingip_dict,
 1417                                                            dns_data)
 1418         # TODO(lujinluo): Change floatingip_db to floatingip_obj once all
 1419         # codes are migrated to use Floating IP OVO object.
 1420         resource_extend.apply_funcs(l3_apidef.FLOATINGIPS, floatingip_dict,
 1421                                     floatingip_db)
 1422         return floatingip_dict
 1423 
 1424     @db_api.retry_if_session_inactive()
 1425     def create_floatingip(self, context, floatingip,
 1426                           initial_status=constants.FLOATINGIP_STATUS_ACTIVE):
 1427         return self._create_floatingip(context, floatingip, initial_status)
 1428 
 1429     def _update_floatingip(self, context, id, floatingip):
 1430         try:
 1431             registry.publish(resources.FLOATING_IP, events.BEFORE_UPDATE,
 1432                              self, payload=events.DBEventPayload(
 1433                                  context, request_body=floatingip,
 1434                                  resource_id=id))
 1435         except exceptions.CallbackFailure as e:
 1436             # raise the underlying exception
 1437             raise e.errors[0].error
 1438 
 1439         fip = floatingip['floatingip']
 1440         with context.session.begin(subtransactions=True):
 1441             floatingip_obj = self._get_floatingip(context, id)
 1442             old_floatingip = self._make_floatingip_dict(floatingip_obj)
 1443             fip_port_id = floatingip_obj.floating_port_id
 1444             assoc_result = self._update_fip_assoc(
 1445                 context, fip, floatingip_obj,
 1446                 self._core_plugin.get_port(context.elevated(), fip_port_id))
 1447             floatingip_obj.update()
 1448             floatingip_dict = self._make_floatingip_dict(floatingip_obj)
 1449             if self._is_dns_integration_supported:
 1450                 dns_data = self._process_dns_floatingip_update_precommit(
 1451                     context, floatingip_dict)
 1452             if self._is_fip_qos_supported:
 1453                 self._process_extra_fip_qos_update(context,
 1454                                                    floatingip_obj,
 1455                                                    fip,
 1456                                                    old_floatingip)
 1457             floatingip_obj = l3_obj.FloatingIP.get_object(
 1458                 context, id=floatingip_obj.id)
 1459             floatingip_db = floatingip_obj.db_obj
 1460             registry.notify(resources.FLOATING_IP,
 1461                             events.PRECOMMIT_UPDATE,
 1462                             self,
 1463                             floatingip=floatingip,
 1464                             floatingip_db=floatingip_db,
 1465                             old_floatingip=old_floatingip,
 1466                             **assoc_result)
 1467 
 1468         registry.notify(resources.FLOATING_IP,
 1469                         events.AFTER_UPDATE,
 1470                         self._update_fip_assoc,
 1471                         **assoc_result)
 1472 
 1473         if self._is_dns_integration_supported:
 1474             self._process_dns_floatingip_update_postcommit(context,
 1475                                                            floatingip_dict,
 1476                                                            dns_data)
 1477         # TODO(lujinluo): Change floatingip_db to floatingip_obj once all
 1478         # codes are migrated to use Floating IP OVO object.
 1479         resource_extend.apply_funcs(l3_apidef.FLOATINGIPS, floatingip_dict,
 1480                                     floatingip_db)
 1481         return old_floatingip, floatingip_dict
 1482 
 1483     def _floatingips_to_router_ids(self, floatingips):
 1484         return list(set([floatingip['router_id']
 1485                          for floatingip in floatingips
 1486                          if floatingip['router_id']]))
 1487 
 1488     @db_api.retry_if_session_inactive()
 1489     def update_floatingip(self, context, id, floatingip):
 1490         _old_floatingip, floatingip = self._update_floatingip(
 1491             context, id, floatingip)
 1492         return floatingip
 1493 
 1494     @db_api.retry_if_session_inactive()
 1495     def update_floatingip_status(self, context, floatingip_id, status):
 1496         """Update operational status for floating IP in neutron DB."""
 1497         return l3_obj.FloatingIP.update_object(
 1498             context, {'status': status}, id=floatingip_id)
 1499 
 1500     @registry.receives(resources.PORT, [events.PRECOMMIT_DELETE])
 1501     def _precommit_delete_port_callback(
 1502             self, resource, event, trigger, **kwargs):
 1503         if (kwargs['port']['device_owner'] ==
 1504                 constants.DEVICE_OWNER_FLOATINGIP):
 1505             registry.notify(resources.FLOATING_IP, events.PRECOMMIT_DELETE,
 1506                             self, **kwargs)
 1507 
 1508     def _delete_floatingip(self, context, id):
 1509         floatingip = self._get_floatingip(context, id)
 1510         floatingip_dict = self._make_floatingip_dict(floatingip)
 1511         if self._is_dns_integration_supported:
 1512             self._process_dns_floatingip_delete(context, floatingip_dict)
 1513         # Foreign key cascade will take care of the removal of the
 1514         # floating IP record once the port is deleted. We can't start
 1515         # a transaction first to remove it ourselves because the delete_port
 1516         # method will yield in its post-commit activities.
 1517         self._core_plugin.delete_port(context.elevated(),
 1518                                       floatingip.floating_port_id,
 1519                                       l3_port_check=False)
 1520         registry.notify(resources.FLOATING_IP, events.AFTER_DELETE,
 1521                         self, context=context, **floatingip_dict)
 1522         return floatingip_dict
 1523 
 1524     @db_api.retry_if_session_inactive()
 1525     def delete_floatingip(self, context, id):
 1526         self._delete_floatingip(context, id)
 1527 
 1528     @db_api.retry_if_session_inactive()
 1529     def get_floatingip(self, context, id, fields=None):
 1530         floatingip = self._get_floatingip(context, id)
 1531         return self._make_floatingip_dict(floatingip, fields)
 1532 
 1533     @db_api.retry_if_session_inactive()
 1534     def get_floatingips(self, context, filters=None, fields=None,
 1535                         sorts=None, limit=None, marker=None,
 1536                         page_reverse=False):
 1537         pager = base_obj.Pager(sorts, limit, page_reverse, marker)
 1538         filters = filters or {}
 1539         for key, val in API_TO_DB_COLUMN_MAP.items():
 1540             if key in filters:
 1541                 filters[val] = filters.pop(key)
 1542         floatingip_objs = l3_obj.FloatingIP.get_objects(
 1543             context, _pager=pager, validate_filters=False, **filters)
 1544         floatingip_dicts = [
 1545             self._make_floatingip_dict(floatingip_obj, fields)
 1546             for floatingip_obj in floatingip_objs
 1547         ]
 1548         return floatingip_dicts
 1549 
 1550     @db_api.retry_if_session_inactive()
 1551     def delete_disassociated_floatingips(self, context, network_id):
 1552         fip_objs = l3_obj.FloatingIP.get_objects(
 1553             context,
 1554             floating_network_id=network_id, router_id=None, fixed_port_id=None)
 1555 
 1556         for fip in fip_objs:
 1557             self.delete_floatingip(context, fip.id)
 1558 
 1559     @db_api.retry_if_session_inactive()
 1560     def get_floatingips_count(self, context, filters=None):
 1561         filters = filters or {}
 1562         return l3_obj.FloatingIP.count(context, **filters)
 1563 
 1564     def _router_exists(self, context, router_id):
 1565         try:
 1566             self.get_router(context.elevated(), router_id)
 1567             return True
 1568         except l3_exc.RouterNotFound:
 1569             return False
 1570 
 1571     def prevent_l3_port_deletion(self, context, port_id):
 1572         """Checks to make sure a port is allowed to be deleted.
 1573 
 1574         Raises an exception if this is not the case.  This should be called by
 1575         any plugin when the API requests the deletion of a port, since some
 1576         ports for L3 are not intended to be deleted directly via a DELETE
 1577         to /ports, but rather via other API calls that perform the proper
 1578         deletion checks.
 1579         """
 1580         try:
 1581             port = self._core_plugin.get_port(context, port_id)
 1582         except n_exc.PortNotFound:
 1583             # non-existent ports don't need to be protected from deletion
 1584             return
 1585         if port['device_owner'] not in self.router_device_owners:
 1586             return
 1587         # Raise port in use only if the port has IP addresses
 1588         # Otherwise it's a stale port that can be removed
 1589         fixed_ips = port['fixed_ips']
 1590         if not fixed_ips:
 1591             LOG.debug("Port %(port_id)s has owner %(port_owner)s, but "
 1592                       "no IP address, so it can be deleted",
 1593                       {'port_id': port['id'],
 1594                        'port_owner': port['device_owner']})
 1595             return
 1596         # NOTE(kevinbenton): we also check to make sure that the
 1597         # router still exists. It's possible for HA router interfaces
 1598         # to remain after the router is deleted if they encounter an
 1599         # error during deletion.
 1600         # Elevated context in case router is owned by another tenant
 1601         if port['device_owner'] == DEVICE_OWNER_FLOATINGIP:
 1602             if not l3_obj.FloatingIP.objects_exist(
 1603                     context, id=port['device_id']):
 1604                 LOG.debug("Floating IP %(f_id)s corresponding to port "
 1605                           "%(port_id)s no longer exists, allowing deletion.",
 1606                           {'f_id': port['device_id'], 'port_id': port['id']})
 1607                 return
 1608         elif not self._router_exists(context, port['device_id']):
 1609             LOG.debug("Router %(router_id)s corresponding to port "
 1610                       "%(port_id)s  no longer exists, allowing deletion.",
 1611                       {'router_id': port['device_id'],
 1612                        'port_id': port['id']})
 1613             return
 1614 
 1615         reason = _('has device owner %s') % port['device_owner']
 1616         raise n_exc.ServicePortInUse(port_id=port['id'],
 1617                                      reason=reason)
 1618 
 1619     @db_api.retry_if_session_inactive()
 1620     def disassociate_floatingips(self, context, port_id, do_notify=True):
 1621         """Disassociate all floating IPs linked to specific port.
 1622 
 1623         @param port_id: ID of the port to disassociate floating IPs.
 1624         @param do_notify: whether we should notify routers right away.
 1625                           This parameter is ignored.
 1626         @return: set of router-ids that require notification updates
 1627         """
 1628         with context.session.begin(subtransactions=True):
 1629             floating_ip_objs = l3_obj.FloatingIP.get_objects(
 1630                 context, fixed_port_id=port_id)
 1631             router_ids = {fip.router_id for fip in floating_ip_objs}
 1632             old_fips = {fip.id: fip.to_dict() for fip in floating_ip_objs}
 1633             values = {'fixed_port_id': None,
 1634                       'fixed_ip_address': None,
 1635                       'router_id': None}
 1636             l3_obj.FloatingIP.update_objects(
 1637                 context, values, fixed_port_id=port_id)
 1638             for fip in floating_ip_objs:
 1639                 registry.notify(resources.FLOATING_IP, events.PRECOMMIT_UPDATE,
 1640                                 self, context=context,
 1641                                 floatingip={l3_apidef.FLOATINGIP: values},
 1642                                 floatingip_db=fip,
 1643                                 old_floatingip=old_fips[fip.id],
 1644                                 router_ids=router_ids)
 1645 
 1646         for fip in floating_ip_objs:
 1647             assoc_result = {
 1648                 'fixed_ip_address': None,
 1649                 'fixed_port_id': None,
 1650                 'router_id': None,
 1651                 'floating_ip_address': fip.floating_ip_address,
 1652                 'floating_network_id': fip.floating_network_id,
 1653                 'floating_ip_id': fip.id,
 1654                 'context': context,
 1655                 'router_ids': router_ids,
 1656             }
 1657             registry.notify(resources.FLOATING_IP, events.AFTER_UPDATE, self,
 1658                             **assoc_result)
 1659         return router_ids
 1660 
 1661     def _get_floatingips_by_port_id(self, context, port_id):
 1662         """Helper function to retrieve the fips associated with a port_id."""
 1663         return l3_obj.FloatingIP.get_objects(context, fixed_port_id=port_id)
 1664 
 1665     def _build_routers_list(self, context, routers, gw_ports):
 1666         """Subclasses can override this to add extra gateway info"""
 1667         return routers
 1668 
 1669     def _make_router_dict_with_gw_port(self, router, fields):
 1670         result = self._make_router_dict(router, fields)
 1671         if router.get('gw_port'):
 1672             result['gw_port'] = self._core_plugin._make_port_dict(
 1673                 router['gw_port'])
 1674         return result
 1675 
 1676     def _get_sync_routers(self, context, router_ids=None, active=None):
 1677         """Query routers and their gw ports for l3 agent.
 1678 
 1679         Query routers with the router_ids. The gateway ports, if any,
 1680         will be queried too.
 1681         l3 agent has an option to deal with only one router id. In addition,
 1682         when we need to notify the agent the data about only one router
 1683         (when modification of router, its interfaces, gw_port and floatingips),
 1684         we will have router_ids.
 1685         @param router_ids: the list of router ids which we want to query.
 1686                            if it is None, all of routers will be queried.
 1687         @return: a list of dicted routers with dicted gw_port populated if any
 1688         """
 1689         filters = {'id': router_ids} if router_ids else {}
 1690         if active is not None:
 1691             filters['admin_state_up'] = [active]
 1692         router_dicts = model_query.get_collection(
 1693             context, l3_models.Router, self._make_router_dict_with_gw_port,
 1694             filters=filters)
 1695         if not router_dicts:
 1696             return []
 1697         gw_ports = dict((r['gw_port']['id'], r['gw_port'])
 1698                         for r in router_dicts
 1699                         if r.get('gw_port'))
 1700         return self._build_routers_list(context, router_dicts, gw_ports)
 1701 
 1702     def _make_floatingip_dict_with_scope(self, floatingip_obj, scope_id):
 1703         d = self._make_floatingip_dict(floatingip_obj)
 1704         d['fixed_ip_address_scope'] = scope_id
 1705         return d
 1706 
 1707     def _get_sync_floating_ips(self, context, router_ids):
 1708         """Query floating_ips that relate to list of router_ids with scope.
 1709 
 1710         This is different than the regular get_floatingips in that it finds the
 1711         address scope of the fixed IP.  The router needs to know this to
 1712         distinguish it from other scopes.
 1713 
 1714         There are a few redirections to go through to discover the address
 1715         scope from the floating ip.
 1716         """
 1717         if not router_ids:
 1718             return []
 1719 
 1720         return [
 1721             self._make_floatingip_dict_with_scope(*scoped_fip)
 1722             for scoped_fip in l3_obj.FloatingIP.get_scoped_floating_ips(
 1723                 context, router_ids)
 1724         ]
 1725 
 1726     def _get_sync_interfaces(self, context, router_ids, device_owners=None):
 1727         """Query router interfaces that relate to list of router_ids."""
 1728         device_owners = device_owners or [DEVICE_OWNER_ROUTER_INTF,
 1729                                           DEVICE_OWNER_HA_REPLICATED_INT]
 1730         if not router_ids:
 1731             return []
 1732         # TODO(lujinluo): Need Port as synthetic field
 1733         objs = l3_obj.RouterPort.get_objects(
 1734             context, router_id=router_ids, port_type=list(device_owners))
 1735 
 1736         interfaces = [self._core_plugin._make_port_dict(rp.db_obj.port)
 1737                       for rp in objs]
 1738         return interfaces
 1739 
 1740     @staticmethod
 1741     def _each_port_having_fixed_ips(ports):
 1742         for port in ports or []:
 1743             fixed_ips = port.get('fixed_ips', [])
 1744             if not fixed_ips:
 1745                 # Skip ports without IPs, which can occur if a subnet
 1746                 # attached to a router is deleted
 1747                 LOG.info("Skipping port %s as no IP is configure on "
 1748                          "it",
 1749                          port['id'])
 1750                 continue
 1751             yield port
 1752 
 1753     def _get_subnets_by_network_list(self, context, network_ids):
 1754         if not network_ids:
 1755             return {}
 1756 
 1757         query = context.session.query(models_v2.Subnet,
 1758                                       models_v2.SubnetPool.address_scope_id)
 1759         query = query.outerjoin(
 1760             models_v2.SubnetPool,
 1761             models_v2.Subnet.subnetpool_id == models_v2.SubnetPool.id)
 1762         query = query.filter(models_v2.Subnet.network_id.in_(network_ids))
 1763 
 1764         fields = ['id', 'cidr', 'gateway_ip', 'dns_nameservers',
 1765                   'network_id', 'ipv6_ra_mode', 'subnetpool_id']
 1766 
 1767         def make_subnet_dict_with_scope(row):
 1768             subnet_db, address_scope_id = row
 1769             subnet = self._core_plugin._make_subnet_dict(
 1770                 subnet_db, fields, context=context)
 1771             subnet['address_scope_id'] = address_scope_id
 1772             return subnet
 1773 
 1774         subnets_by_network = dict((id, []) for id in network_ids)
 1775         for subnet in (make_subnet_dict_with_scope(row) for row in query):
 1776             subnets_by_network[subnet['network_id']].append(subnet)
 1777         return subnets_by_network
 1778 
 1779     def _get_mtus_by_network_list(self, context, network_ids):
 1780         if not network_ids:
 1781             return {}
 1782         filters = {'id': network_ids}
 1783         fields = ['id', 'mtu']
 1784         networks = self._core_plugin.get_networks(context, filters=filters,
 1785                                                   fields=fields)
 1786         mtus_by_network = dict((network['id'], network.get('mtu', 0))
 1787                                for network in networks)
 1788         return mtus_by_network
 1789 
 1790     def _populate_mtu_and_subnets_for_ports(self, context, ports):
 1791         """Populate ports with subnets.
 1792 
 1793         These ports already have fixed_ips populated.
 1794         """
 1795         network_ids = [p['network_id']
 1796                        for p in self._each_port_having_fixed_ips(ports)]
 1797 
 1798         mtus_by_network = self._get_mtus_by_network_list(context, network_ids)
 1799         subnets_by_network = self._get_subnets_by_network_list(
 1800             context, network_ids)
 1801 
 1802         for port in self._each_port_having_fixed_ips(ports):
 1803 
 1804             port['subnets'] = []
 1805             port['extra_subnets'] = []
 1806             port['address_scopes'] = {constants.IP_VERSION_4: None,
 1807                                       constants.IP_VERSION_6: None}
 1808 
 1809             scopes = {}
 1810             for subnet in subnets_by_network[port['network_id']]:
 1811                 scope = subnet['address_scope_id']
 1812                 cidr = netaddr.IPNetwork(subnet['cidr'])
 1813                 scopes[cidr.version] = scope
 1814 
 1815                 # If this subnet is used by the port (has a matching entry
 1816                 # in the port's fixed_ips), then add this subnet to the
 1817                 # port's subnets list, and populate the fixed_ips entry
 1818                 # entry with the subnet's prefix length.
 1819                 subnet_info = {'id': subnet['id'],
 1820                                'cidr': subnet['cidr'],
 1821                                'gateway_ip': subnet['gateway_ip'],
 1822                                'dns_nameservers': subnet['dns_nameservers'],
 1823                                'ipv6_ra_mode': subnet['ipv6_ra_mode'],
 1824                                'subnetpool_id': subnet['subnetpool_id']}
 1825                 for fixed_ip in port['fixed_ips']:
 1826                     if fixed_ip['subnet_id'] == subnet['id']:
 1827                         port['subnets'].append(subnet_info)
 1828                         prefixlen = cidr.prefixlen
 1829                         fixed_ip['prefixlen'] = prefixlen
 1830                         break
 1831                 else:
 1832                     # This subnet is not used by the port.
 1833                     port['extra_subnets'].append(subnet_info)
 1834 
 1835             port['address_scopes'].update(scopes)
 1836             port['mtu'] = mtus_by_network.get(port['network_id'], 0)
 1837 
 1838     def _process_floating_ips(self, context, routers_dict, floating_ips):
 1839         for floating_ip in floating_ips:
 1840             router = routers_dict.get(floating_ip['router_id'])
 1841             if router:
 1842                 router_floatingips = router.get(constants.FLOATINGIP_KEY,
 1843                                                 [])
 1844                 router_floatingips.append(floating_ip)
 1845                 router[constants.FLOATINGIP_KEY] = router_floatingips
 1846 
 1847     def _process_interfaces(self, routers_dict, interfaces):
 1848         for interface in interfaces:
 1849             router = routers_dict.get(interface['device_id'])
 1850             if router:
 1851                 router_interfaces = router.get(constants.INTERFACE_KEY, [])
 1852                 router_interfaces.append(interface)
 1853                 router[constants.INTERFACE_KEY] = router_interfaces
 1854 
 1855     def _get_router_info_list(self, context, router_ids=None, active=None,
 1856                               device_owners=None):
 1857         """Query routers and their related floating_ips, interfaces."""
 1858         with context.session.begin(subtransactions=True):
 1859             routers = self._get_sync_routers(context,
 1860                                              router_ids=router_ids,
 1861                                              active=active)
 1862             router_ids = [router['id'] for router in routers]
 1863             interfaces = self._get_sync_interfaces(
 1864                 context, router_ids, device_owners)
 1865             floating_ips = self._get_sync_floating_ips(context, router_ids)
 1866             return (routers, interfaces, floating_ips)
 1867 
 1868     def get_sync_data(self, context, router_ids=None, active=None):
 1869         routers, interfaces, floating_ips = self._get_router_info_list(
 1870             context, router_ids=router_ids, active=active)
 1871         ports_to_populate = [router['gw_port'] for router in routers
 1872                              if router.get('gw_port')] + interfaces
 1873         self._populate_mtu_and_subnets_for_ports(context, ports_to_populate)
 1874         routers_dict = dict((router['id'], router) for router in routers)
 1875         self._process_floating_ips(context, routers_dict, floating_ips)
 1876         self._process_interfaces(routers_dict, interfaces)
 1877         return list(routers_dict.values())
 1878 
 1879     def is_router_distributed(self, context, router_id):
 1880         """Returns if a router is distributed or not
 1881 
 1882         If DVR extension is not enabled, no router will be distributed. This
 1883         function is overridden in L3_NAT_with_dvr_db_mixin in case the DVR
 1884         extension is loaded.
 1885         """
 1886         return False
 1887 
 1888 
 1889 @registry.has_registry_receivers
 1890 class L3RpcNotifierMixin(object):
 1891     """Mixin class to add rpc notifier attribute to db_base_plugin_v2."""
 1892 
 1893     @staticmethod
 1894     @registry.receives(resources.PORT, [events.AFTER_DELETE])
 1895     def _notify_routers_callback(resource, event, trigger, **kwargs):
 1896         context = kwargs['context']
 1897         router_ids = kwargs['router_ids']
 1898         l3plugin = directory.get_plugin(plugin_constants.L3)
 1899         if l3plugin:
 1900             l3plugin.notify_routers_updated(context, router_ids)
 1901         else:
 1902             LOG.debug('%s not configured', plugin_constants.L3)
 1903 
 1904     @staticmethod
 1905     @registry.receives(resources.SUBNET, [events.AFTER_UPDATE])
 1906     def _notify_subnet_gateway_ip_update(resource, event, trigger, **kwargs):
 1907         l3plugin = directory.get_plugin(plugin_constants.L3)
 1908         if not l3plugin:
 1909             return
 1910         context = kwargs['context']
 1911         orig = kwargs['original_subnet']
 1912         updated = kwargs['subnet']
 1913         if orig['gateway_ip'] == updated['gateway_ip']:
 1914             return
 1915         network_id = updated['network_id']
 1916         subnet_id = updated['id']
 1917         query = context.session.query(models_v2.Port.device_id).filter_by(
 1918                     network_id=network_id,
 1919                     device_owner=DEVICE_OWNER_ROUTER_GW)
 1920         query = query.join(models_v2.Port.fixed_ips).filter(
 1921                     models_v2.IPAllocation.subnet_id == subnet_id)
 1922         router_ids = set(port.device_id for port in query)
 1923         for router_id in router_ids:
 1924             l3plugin.notify_router_updated(context, router_id)
 1925 
 1926     @staticmethod
 1927     @registry.receives(resources.PORT, [events.AFTER_UPDATE])
 1928     def _notify_gateway_port_ip_changed(resource, event, trigger, **kwargs):
 1929         l3plugin = directory.get_plugin(plugin_constants.L3)
 1930         if not l3plugin:
 1931             return
 1932         new_port = kwargs.get('port')
 1933         original_port = kwargs.get('original_port')
 1934 
 1935         if original_port['device_owner'] != constants.DEVICE_OWNER_ROUTER_GW:
 1936             return
 1937 
 1938         if utils.port_ip_changed(new_port, original_port):
 1939             l3plugin.notify_router_updated(kwargs['context'],
 1940                                            new_port['device_id'])
 1941 
 1942     @staticmethod
 1943     @registry.receives(resources.SUBNETPOOL_ADDRESS_SCOPE,
 1944                        [events.AFTER_UPDATE])
 1945     def _notify_subnetpool_address_scope_update(resource, event,
 1946                                                 trigger, payload=None):
 1947         context = payload.context
 1948         subnetpool_id = payload.resource_id
 1949 
 1950         router_ids = l3_obj.RouterPort.get_router_ids_by_subnetpool(
 1951             context, subnetpool_id)
 1952 
 1953         l3plugin = directory.get_plugin(plugin_constants.L3)
 1954         if l3plugin:
 1955             l3plugin.notify_routers_updated(context, router_ids)
 1956         else:
 1957             LOG.debug('%s not configured', plugin_constants.L3)
 1958 
 1959     @property
 1960     def l3_rpc_notifier(self):
 1961         if not hasattr(self, '_l3_rpc_notifier'):
 1962             self._l3_rpc_notifier = l3_rpc_agent_api.L3AgentNotifyAPI()
 1963         return self._l3_rpc_notifier
 1964 
 1965     @l3_rpc_notifier.setter
 1966     def l3_rpc_notifier(self, value):
 1967         self._l3_rpc_notifier = value
 1968 
 1969     def notify_router_updated(self, context, router_id,
 1970                               operation=None):
 1971         if router_id:
 1972             self.l3_rpc_notifier.routers_updated(
 1973                 context, [router_id], operation)
 1974 
 1975     def notify_routers_updated(self, context, router_ids,
 1976                                operation=None, data=None):
 1977         if router_ids:
 1978             self.l3_rpc_notifier.routers_updated(
 1979                 context, router_ids, operation, data)
 1980 
 1981     def notify_router_deleted(self, context, router_id):
 1982         self.l3_rpc_notifier.router_deleted(context, router_id)
 1983 
 1984 
 1985 class L3_NAT_db_mixin(L3_NAT_dbonly_mixin, L3RpcNotifierMixin):
 1986     """Mixin class to add rpc notifier methods to db_base_plugin_v2."""
 1987 
 1988     def create_router(self, context, router):
 1989         router_dict = super(L3_NAT_db_mixin, self).create_router(context,
 1990                                                                  router)
 1991         if router_dict.get('external_gateway_info'):
 1992             self.notify_router_updated(context, router_dict['id'], None)
 1993         return router_dict
 1994 
 1995     def update_router(self, context, id, router):
 1996         router_dict = super(L3_NAT_db_mixin, self).update_router(context,
 1997                                                                  id, router)
 1998         self.notify_router_updated(context, router_dict['id'], None)
 1999         return router_dict
 2000 
 2001     def delete_router(self, context, id):
 2002         super(L3_NAT_db_mixin, self).delete_router(context, id)
 2003         self.notify_router_deleted(context, id)
 2004 
 2005     def notify_router_interface_action(
 2006             self, context, router_interface_info, action):
 2007         l3_method = '%s_router_interface' % action
 2008         super(L3_NAT_db_mixin, self).notify_routers_updated(
 2009             context, [router_interface_info['id']], l3_method,
 2010             {'subnet_id': router_interface_info['subnet_id']})
 2011 
 2012         mapping = {'add': 'create', 'remove': 'delete'}
 2013         notifier = n_rpc.get_notifier('network')
 2014         router_event = 'router.interface.%s' % mapping[action]
 2015         notifier.info(context, router_event,
 2016                       {'router_interface': router_interface_info})
 2017 
 2018     def add_router_interface(self, context, router_id, interface_info=None):
 2019         router_interface_info = super(
 2020             L3_NAT_db_mixin, self).add_router_interface(
 2021                 context, router_id, interface_info)
 2022         self.notify_router_interface_action(
 2023             context, router_interface_info, 'add')
 2024         return router_interface_info
 2025 
 2026     def remove_router_interface(self, context, router_id, interface_info):
 2027         router_interface_info = super(
 2028             L3_NAT_db_mixin, self).remove_router_interface(
 2029                 context, router_id, interface_info)
 2030         self.notify_router_interface_action(
 2031             context, router_interface_info, 'remove')
 2032         return router_interface_info
 2033 
 2034     def create_floatingip(self, context, floatingip,
 2035                           initial_status=constants.FLOATINGIP_STATUS_ACTIVE):
 2036         floatingip_dict = super(L3_NAT_db_mixin, self).create_floatingip(
 2037             context, floatingip, initial_status)
 2038         router_id = floatingip_dict['router_id']
 2039         self.notify_router_updated(context, router_id, 'create_floatingip')
 2040         return floatingip_dict
 2041 
 2042     def update_floatingip(self, context, id, floatingip):
 2043         old_floatingip, floatingip = self._update_floatingip(
 2044             context, id, floatingip)
 2045         router_ids = self._floatingips_to_router_ids(
 2046             [old_floatingip, floatingip])
 2047         super(L3_NAT_db_mixin, self).notify_routers_updated(
 2048             context, router_ids, 'update_floatingip', {})
 2049         return floatingip
 2050 
 2051     def delete_floatingip(self, context, id):
 2052         floating_ip = self._delete_floatingip(context, id)
 2053         self.notify_router_updated(context, floating_ip['router_id'],
 2054                                    'delete_floatingip')
 2055 
 2056     def disassociate_floatingips(self, context, port_id, do_notify=True):
 2057         """Disassociate all floating IPs linked to specific port.
 2058 
 2059         @param port_id: ID of the port to disassociate floating IPs.
 2060         @param do_notify: whether we should notify routers right away.
 2061         @return: set of router-ids that require notification updates
 2062                  if do_notify is False, otherwise None.
 2063         """
 2064         router_ids = super(L3_NAT_db_mixin, self).disassociate_floatingips(
 2065             context, port_id, do_notify)
 2066         if do_notify:
 2067             self.notify_routers_updated(context, router_ids)
 2068             # since caller assumes that we handled notifications on its
 2069             # behalf, return nothing
 2070             return
 2071 
 2072         return router_ids
 2073 
 2074     def notify_routers_updated(self, context, router_ids):
 2075         super(L3_NAT_db_mixin, self).notify_routers_updated(
 2076             context, list(router_ids), 'disassociate_floatingips', {})
 2077 
 2078     def _migrate_router_ports(
 2079         self, context, router_db, old_owner, new_owner):
 2080         """Update the model to support the dvr case of a router."""
 2081         for rp in router_db.attached_ports:
 2082             if rp.port_type == old_owner:
 2083                 rp.port_type = new_owner
 2084                 rp.port.device_owner = new_owner