Set lock_path correctly.
[openstack-build/neutron-build.git] / neutron / db / l3_dvr_db.py
1 # Copyright (c) 2014 OpenStack Foundation.  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 import collections
15
16 from oslo_config import cfg
17 from oslo_log import log as logging
18 from oslo_utils import excutils
19 import six
20
21 from neutron._i18n import _, _LI, _LW
22 from neutron.api.v2 import attributes
23 from neutron.callbacks import events
24 from neutron.callbacks import exceptions
25 from neutron.callbacks import registry
26 from neutron.callbacks import resources
27 from neutron.common import constants as l3_const
28 from neutron.common import exceptions as n_exc
29 from neutron.common import utils as n_utils
30 from neutron.db import l3_attrs_db
31 from neutron.db import l3_db
32 from neutron.db import l3_dvrscheduler_db as l3_dvrsched_db
33 from neutron.extensions import l3
34 from neutron.extensions import portbindings
35 from neutron import manager
36 from neutron.plugins.common import constants
37 from neutron.plugins.common import utils as p_utils
38
39
40 LOG = logging.getLogger(__name__)
41 router_distributed_opts = [
42     cfg.BoolOpt('router_distributed',
43                 default=False,
44                 help=_("System-wide flag to determine the type of router "
45                        "that tenants can create. Only admin can override.")),
46 ]
47 cfg.CONF.register_opts(router_distributed_opts)
48
49
50 class L3_NAT_with_dvr_db_mixin(l3_db.L3_NAT_db_mixin,
51                                l3_attrs_db.ExtraAttributesMixin):
52     """Mixin class to enable DVR support."""
53
54     router_device_owners = (
55         l3_db.L3_NAT_db_mixin.router_device_owners +
56         (l3_const.DEVICE_OWNER_DVR_INTERFACE,
57          l3_const.DEVICE_OWNER_ROUTER_SNAT,
58          l3_const.DEVICE_OWNER_AGENT_GW))
59
60     extra_attributes = (
61         l3_attrs_db.ExtraAttributesMixin.extra_attributes + [{
62             'name': "distributed",
63             'default': cfg.CONF.router_distributed
64         }])
65
66     def _create_router_db(self, context, router, tenant_id):
67         """Create a router db object with dvr additions."""
68         router['distributed'] = is_distributed_router(router)
69         with context.session.begin(subtransactions=True):
70             router_db = super(
71                 L3_NAT_with_dvr_db_mixin, self)._create_router_db(
72                     context, router, tenant_id)
73             self._process_extra_attr_router_create(context, router_db, router)
74             return router_db
75
76     def _validate_router_migration(self, context, router_db, router_res):
77         """Allow centralized -> distributed state transition only."""
78         if (router_db.extra_attributes.distributed and
79             router_res.get('distributed') is False):
80             LOG.info(_LI("Centralizing distributed router %s "
81                          "is not supported"), router_db['id'])
82             raise n_exc.BadRequest(
83                 resource='router',
84                 msg=_("Migration from distributed router to centralized is "
85                       "not supported"))
86         elif (not router_db.extra_attributes.distributed and
87               router_res.get('distributed')):
88             # router should be disabled in order for upgrade
89             if router_db.admin_state_up:
90                 msg = _('Cannot upgrade active router to distributed. Please '
91                         'set router admin_state_up to False prior to upgrade.')
92                 raise n_exc.BadRequest(resource='router', msg=msg)
93
94             # Notify advanced services of the imminent state transition
95             # for the router.
96             try:
97                 kwargs = {'context': context, 'router': router_db}
98                 registry.notify(
99                     resources.ROUTER, events.BEFORE_UPDATE, self, **kwargs)
100             except exceptions.CallbackFailure as e:
101                 with excutils.save_and_reraise_exception():
102                     # NOTE(armax): preserve old check's behavior
103                     if len(e.errors) == 1:
104                         raise e.errors[0].error
105                     raise l3.RouterInUse(router_id=router_db['id'],
106                                          reason=e)
107
108     def _update_distributed_attr(
109         self, context, router_id, router_db, data):
110         """Update the model to support the dvr case of a router."""
111         if data.get('distributed'):
112             old_owner = l3_const.DEVICE_OWNER_ROUTER_INTF
113             new_owner = l3_const.DEVICE_OWNER_DVR_INTERFACE
114             for rp in router_db.attached_ports.filter_by(port_type=old_owner):
115                 rp.port_type = new_owner
116                 rp.port.device_owner = new_owner
117
118     def _update_router_db(self, context, router_id, data):
119         with context.session.begin(subtransactions=True):
120             router_db = super(
121                 L3_NAT_with_dvr_db_mixin, self)._update_router_db(
122                     context, router_id, data)
123             migrating_to_distributed = (
124                 not router_db.extra_attributes.distributed and
125                 data.get('distributed') is True)
126             self._validate_router_migration(context, router_db, data)
127             router_db.extra_attributes.update(data)
128             self._update_distributed_attr(
129                 context, router_id, router_db, data)
130             if migrating_to_distributed:
131                 if router_db['gw_port_id']:
132                     # If the Legacy router is getting migrated to a DVR
133                     # router, make sure to create corresponding
134                     # snat interface ports that are to be consumed by
135                     # the Service Node.
136                     if not self._create_snat_intf_ports_if_not_exists(
137                         context.elevated(), router_db):
138                         LOG.debug("SNAT interface ports not created: %s",
139                                   router_db['id'])
140                 cur_agents = self.list_l3_agents_hosting_router(
141                     context, router_db['id'])['agents']
142                 for agent in cur_agents:
143                     self._unbind_router(context, router_db['id'],
144                                         agent['id'])
145             return router_db
146
147     def _delete_current_gw_port(self, context, router_id, router, new_network):
148         """
149         Overriden here to handle deletion of dvr internal ports.
150
151         If there is a valid router update with gateway port to be deleted,
152         then go ahead and delete the csnat ports and the floatingip
153         agent gateway port associated with the dvr router.
154         """
155
156         gw_ext_net_id = (
157             router.gw_port['network_id'] if router.gw_port else None)
158
159         super(L3_NAT_with_dvr_db_mixin,
160               self)._delete_current_gw_port(context, router_id,
161                                             router, new_network)
162         if (is_distributed_router(router) and
163             gw_ext_net_id != new_network and gw_ext_net_id is not None):
164             self.delete_csnat_router_interface_ports(
165                 context.elevated(), router)
166             # NOTE(Swami): Delete the Floatingip agent gateway port
167             # on all hosts when it is the last gateway port in the
168             # given external network.
169             filters = {'network_id': [gw_ext_net_id],
170                        'device_owner': [l3_const.DEVICE_OWNER_ROUTER_GW]}
171             ext_net_gw_ports = self._core_plugin.get_ports(
172                 context.elevated(), filters)
173             if not ext_net_gw_ports:
174                 self.delete_floatingip_agent_gateway_port(
175                     context.elevated(), None, gw_ext_net_id)
176                 # Send the information to all the L3 Agent hosts
177                 # to clean up the fip namespace as it is no longer required.
178                 self.l3_rpc_notifier.delete_fipnamespace_for_ext_net(
179                     context, gw_ext_net_id)
180
181     def _create_gw_port(self, context, router_id, router, new_network,
182                         ext_ips):
183         super(L3_NAT_with_dvr_db_mixin,
184               self)._create_gw_port(context, router_id, router, new_network,
185                                     ext_ips)
186         # Make sure that the gateway port exists before creating the
187         # snat interface ports for distributed router.
188         if router.extra_attributes.distributed and router.gw_port:
189             snat_p_list = self._create_snat_intf_ports_if_not_exists(
190                 context.elevated(), router)
191             if not snat_p_list:
192                 LOG.debug("SNAT interface ports not created: %s", snat_p_list)
193
194     def _get_device_owner(self, context, router=None):
195         """Get device_owner for the specified router."""
196         router_is_uuid = isinstance(router, six.string_types)
197         if router_is_uuid:
198             router = self._get_router(context, router)
199         if is_distributed_router(router):
200             return l3_const.DEVICE_OWNER_DVR_INTERFACE
201         return super(L3_NAT_with_dvr_db_mixin,
202                      self)._get_device_owner(context, router)
203
204     def _update_fip_assoc(self, context, fip, floatingip_db, external_port):
205         """Override to create floating agent gw port for DVR.
206
207         Floating IP Agent gateway port will be created when a
208         floatingIP association happens.
209         """
210         fip_port = fip.get('port_id')
211         super(L3_NAT_with_dvr_db_mixin, self)._update_fip_assoc(
212             context, fip, floatingip_db, external_port)
213         associate_fip = fip_port and floatingip_db['id']
214         if associate_fip and floatingip_db.get('router_id'):
215             admin_ctx = context.elevated()
216             router_dict = self.get_router(
217                 admin_ctx, floatingip_db['router_id'])
218             # Check if distributed router and then create the
219             # FloatingIP agent gateway port
220             if router_dict.get('distributed'):
221                 hostid = self._get_dvr_service_port_hostid(
222                     context, fip_port)
223                 if hostid:
224                     # FIXME (Swami): This FIP Agent Gateway port should be
225                     # created only once and there should not be a duplicate
226                     # for the same host. Until we find a good solution for
227                     # augmenting multiple server requests we should use the
228                     # existing flow.
229                     fip_agent_port = (
230                         self.create_fip_agent_gw_port_if_not_exists(
231                             admin_ctx, external_port['network_id'],
232                             hostid))
233                     LOG.debug("FIP Agent gateway port: %s", fip_agent_port)
234
235     def _get_floatingip_on_port(self, context, port_id=None):
236         """Helper function to retrieve the fip associated with port."""
237         fip_qry = context.session.query(l3_db.FloatingIP)
238         floating_ip = fip_qry.filter_by(fixed_port_id=port_id)
239         return floating_ip.first()
240
241     def add_router_interface(self, context, router_id, interface_info):
242         add_by_port, add_by_sub = self._validate_interface_info(interface_info)
243         router = self._get_router(context, router_id)
244         device_owner = self._get_device_owner(context, router)
245
246         # This should be True unless adding an IPv6 prefix to an existing port
247         new_port = True
248
249         if add_by_port:
250             port, subnets = self._add_interface_by_port(
251                     context, router, interface_info['port_id'], device_owner)
252         elif add_by_sub:
253             port, subnets, new_port = self._add_interface_by_subnet(
254                     context, router, interface_info['subnet_id'], device_owner)
255
256         subnet = subnets[0]
257
258         if new_port:
259             if router.extra_attributes.distributed and router.gw_port:
260                 try:
261                     admin_context = context.elevated()
262                     self._add_csnat_router_interface_port(
263                         admin_context, router, port['network_id'],
264                         port['fixed_ips'][-1]['subnet_id'])
265                 except Exception:
266                     with excutils.save_and_reraise_exception():
267                         # we need to preserve the original state prior
268                         # the request by rolling back the port creation
269                         # that led to new_port=True
270                         self._core_plugin.delete_port(
271                             admin_context, port['id'])
272
273             with context.session.begin(subtransactions=True):
274                 router_port = l3_db.RouterPort(
275                     port_id=port['id'],
276                     router_id=router.id,
277                     port_type=device_owner
278                 )
279                 context.session.add(router_port)
280
281         # NOTE: For IPv6 additional subnets added to the same
282         # network we need to update the CSNAT port with respective
283         # IPv6 subnet
284         elif subnet and port:
285             fixed_ip = {'subnet_id': subnet['id']}
286             if subnet['ip_version'] == 6:
287                 # Add new prefix to an existing ipv6 csnat port with the
288                 # same network id if one exists
289                 cs_port = self._find_router_port_by_network_and_device_owner(
290                     router, subnet['network_id'],
291                     l3_const.DEVICE_OWNER_ROUTER_SNAT)
292                 if cs_port:
293                     fixed_ips = list(cs_port['port']['fixed_ips'])
294                     fixed_ips.append(fixed_ip)
295                     updated_port = self._core_plugin.update_port(
296                         context.elevated(),
297                         cs_port['port_id'], {'port': {'fixed_ips': fixed_ips}})
298                     LOG.debug("CSNAT port updated for IPv6 subnet: "
299                               "%s", updated_port)
300         router_interface_info = self._make_router_interface_info(
301             router_id, port['tenant_id'], port['id'], subnet['id'],
302             [subnet['id']])
303         self.notify_router_interface_action(
304             context, router_interface_info, 'add')
305         return router_interface_info
306
307     def _port_has_ipv6_address(self, port, csnat_port_check=True):
308         """Overridden to return False if DVR SNAT port."""
309         if csnat_port_check:
310             if port['device_owner'] == l3_const.DEVICE_OWNER_ROUTER_SNAT:
311                 return False
312         return super(L3_NAT_with_dvr_db_mixin,
313                      self)._port_has_ipv6_address(port)
314
315     def _find_router_port_by_network_and_device_owner(
316         self, router, net_id, device_owner):
317         for port in router.attached_ports:
318             p = port['port']
319             if (p['network_id'] == net_id and
320                 p['device_owner'] == device_owner and
321                 self._port_has_ipv6_address(p, csnat_port_check=False)):
322                 return port
323
324     def _check_for_multiprefix_csnat_port_and_update(
325         self, context, router, subnet, port):
326         """Checks if the csnat port contains multiple ipv6 prefixes.
327
328         If the csnat port contains multiple ipv6 prefixes for the given
329         network when a router interface is deleted, make sure we don't
330         delete the port when a single subnet is deleted and just update
331         it with the right fixed_ip.
332         This function returns true if it is a multiprefix port.
333         """
334         subnet_id = subnet['id']
335         if router.gw_port:
336             # If router has a gateway port, check if it has IPV6 subnet
337             cs_port = (
338                 self._find_router_port_by_network_and_device_owner(
339                     router, subnet['network_id'],
340                     l3_const.DEVICE_OWNER_ROUTER_SNAT))
341             if cs_port:
342                 fixed_ips = (
343                     [fixedip for fixedip in
344                         cs_port['port']['fixed_ips']
345                         if fixedip['subnet_id'] != subnet_id])
346                 if fixed_ips:
347                     # multiple prefix port - delete prefix from port
348                     self._core_plugin.update_port(
349                         context.elevated(),
350                         cs_port['port_id'], {'port': {'fixed_ips': fixed_ips}})
351                     return True
352         return False
353
354     def _check_dvr_router_remove_required_and_notify_agent(
355         self, context, router, port, subnet):
356         if router.extra_attributes.distributed:
357             is_multiple_prefix_csport = (
358                 self._check_for_multiprefix_csnat_port_and_update(
359                     context, router, subnet, port))
360             if not is_multiple_prefix_csport:
361                 # Single prefix port - go ahead and delete the port
362                 self.delete_csnat_router_interface_ports(
363                     context.elevated(), router, subnet_id=subnet['id'])
364             plugin = manager.NeutronManager.get_service_plugins().get(
365                         constants.L3_ROUTER_NAT)
366             l3_agents = plugin.get_l3_agents_hosting_routers(context,
367                                                              [router['id']])
368             subnet_ids = plugin.get_subnet_ids_on_router(
369                 context, router['id'])
370             if subnet_ids:
371                 for l3_agent in l3_agents:
372                     if not plugin.check_dvr_serviceable_ports_on_host(
373                         context, l3_agent['host'], subnet_ids):
374                         plugin.remove_router_from_l3_agent(
375                             context, l3_agent['id'], router['id'])
376         router_interface_info = self._make_router_interface_info(
377             router['id'], port['tenant_id'], port['id'], subnet['id'],
378             [subnet['id']])
379         self.notify_router_interface_action(
380             context, router_interface_info, 'remove')
381         return router_interface_info
382
383     def remove_router_interface(self, context, router_id, interface_info):
384         remove_by_port, remove_by_subnet = (
385             self._validate_interface_info(interface_info, for_removal=True)
386         )
387         port_id = interface_info.get('port_id')
388         subnet_id = interface_info.get('subnet_id')
389         router = self._get_router(context, router_id)
390         device_owner = self._get_device_owner(context, router)
391
392         if remove_by_port:
393             port, subnets = self._remove_interface_by_port(
394                     context, router_id, port_id, subnet_id, device_owner)
395
396         # remove_by_subnet is not used here, because the validation logic of
397         # _validate_interface_info ensures that at least one of remote_by_*
398         # is True.
399         else:
400             port, subnets = self._remove_interface_by_subnet(
401                     context, router_id, subnet_id, device_owner)
402
403         subnet = subnets[0]
404         router_interface_info = (
405             self._check_dvr_router_remove_required_and_notify_agent(
406                 context, router, port, subnet))
407         return router_interface_info
408
409     def _get_snat_sync_interfaces(self, context, router_ids):
410         """Query router interfaces that relate to list of router_ids."""
411         if not router_ids:
412             return []
413         qry = context.session.query(l3_db.RouterPort)
414         qry = qry.filter(
415             l3_db.RouterPort.router_id.in_(router_ids),
416             l3_db.RouterPort.port_type == l3_const.DEVICE_OWNER_ROUTER_SNAT
417         )
418         interfaces = collections.defaultdict(list)
419         for rp in qry:
420             interfaces[rp.router_id].append(
421                 self._core_plugin._make_port_dict(rp.port, None))
422         LOG.debug("Return the SNAT ports: %s", interfaces)
423         return interfaces
424
425     def _build_routers_list(self, context, routers, gw_ports):
426         # Perform a single query up front for all routers
427         if not routers:
428             return []
429         router_ids = [r['id'] for r in routers]
430         snat_binding = l3_dvrsched_db.CentralizedSnatL3AgentBinding
431         query = (context.session.query(snat_binding).
432                  filter(snat_binding.router_id.in_(router_ids))).all()
433         bindings = dict((b.router_id, b) for b in query)
434
435         for rtr in routers:
436             gw_port_id = rtr['gw_port_id']
437             # Collect gw ports only if available
438             if gw_port_id and gw_ports.get(gw_port_id):
439                 rtr['gw_port'] = gw_ports[gw_port_id]
440                 if 'enable_snat' in rtr[l3.EXTERNAL_GW_INFO]:
441                     rtr['enable_snat'] = (
442                         rtr[l3.EXTERNAL_GW_INFO]['enable_snat'])
443
444                 binding = bindings.get(rtr['id'])
445                 if not binding:
446                     rtr['gw_port_host'] = None
447                     LOG.debug('No snat is bound to router %s', rtr['id'])
448                     continue
449
450                 rtr['gw_port_host'] = binding.l3_agent.host
451
452         return routers
453
454     def _process_routers(self, context, routers):
455         routers_dict = {}
456         snat_intfs_by_router_id = self._get_snat_sync_interfaces(
457             context, [r['id'] for r in routers])
458         for router in routers:
459             routers_dict[router['id']] = router
460             if router['gw_port_id']:
461                 snat_router_intfs = snat_intfs_by_router_id[router['id']]
462                 LOG.debug("SNAT ports returned: %s ", snat_router_intfs)
463                 router[l3_const.SNAT_ROUTER_INTF_KEY] = snat_router_intfs
464         return routers_dict
465
466     def _process_floating_ips_dvr(self, context, routers_dict,
467                                   floating_ips, host, agent):
468         fip_sync_interfaces = None
469         LOG.debug("FIP Agent : %s ", agent.id)
470         for floating_ip in floating_ips:
471             router = routers_dict.get(floating_ip['router_id'])
472             if router:
473                 router_floatingips = router.get(l3_const.FLOATINGIP_KEY, [])
474                 if router['distributed']:
475                     if floating_ip.get('host', None) != host:
476                         continue
477                     LOG.debug("Floating IP host: %s", floating_ip['host'])
478                 router_floatingips.append(floating_ip)
479                 router[l3_const.FLOATINGIP_KEY] = router_floatingips
480                 if not fip_sync_interfaces:
481                     fip_sync_interfaces = self._get_fip_sync_interfaces(
482                         context, agent.id)
483                     LOG.debug("FIP Agent ports: %s", fip_sync_interfaces)
484                 router[l3_const.FLOATINGIP_AGENT_INTF_KEY] = (
485                     fip_sync_interfaces)
486
487     def _get_fip_sync_interfaces(self, context, fip_agent_id):
488         """Query router interfaces that relate to list of router_ids."""
489         if not fip_agent_id:
490             return []
491         filters = {'device_id': [fip_agent_id],
492                    'device_owner': [l3_const.DEVICE_OWNER_AGENT_GW]}
493         interfaces = self._core_plugin.get_ports(context.elevated(), filters)
494         LOG.debug("Return the FIP ports: %s ", interfaces)
495         return interfaces
496
497     def _get_dvr_sync_data(self, context, host, agent, router_ids=None,
498                           active=None):
499         routers, interfaces, floating_ips = self._get_router_info_list(
500             context, router_ids=router_ids, active=active,
501             device_owners=l3_const.ROUTER_INTERFACE_OWNERS)
502         dvr_router_ids = set(router['id'] for router in routers
503                              if is_distributed_router(router))
504         floating_ip_port_ids = [fip['port_id'] for fip in floating_ips
505                                 if fip['router_id'] in dvr_router_ids]
506         if floating_ip_port_ids:
507             port_filter = {portbindings.HOST_ID: [host],
508                            'id': floating_ip_port_ids}
509             ports = self._core_plugin.get_ports(context, port_filter)
510             port_dict = dict((port['id'], port) for port in ports)
511             # Add the port binding host to the floatingip dictionary
512             for fip in floating_ips:
513                 vm_port = port_dict.get(fip['port_id'], None)
514                 if vm_port:
515                     fip['host'] = self._get_dvr_service_port_hostid(
516                         context, fip['port_id'], port=vm_port)
517         routers_dict = self._process_routers(context, routers)
518         self._process_floating_ips_dvr(context, routers_dict,
519                                        floating_ips, host, agent)
520         ports_to_populate = []
521         for router in routers_dict.values():
522             if router.get('gw_port'):
523                 ports_to_populate.append(router['gw_port'])
524             if router.get(l3_const.FLOATINGIP_AGENT_INTF_KEY):
525                 ports_to_populate += router[l3_const.FLOATINGIP_AGENT_INTF_KEY]
526             if router.get(l3_const.SNAT_ROUTER_INTF_KEY):
527                 ports_to_populate += router[l3_const.SNAT_ROUTER_INTF_KEY]
528         ports_to_populate += interfaces
529         self._populate_subnets_for_ports(context, ports_to_populate)
530         self._process_interfaces(routers_dict, interfaces)
531         return list(routers_dict.values())
532
533     def _get_dvr_service_port_hostid(self, context, port_id, port=None):
534         """Returns the portbinding host_id for dvr service port."""
535         port_db = port or self._core_plugin.get_port(context, port_id)
536         device_owner = port_db['device_owner'] if port_db else ""
537         if (n_utils.is_dvr_serviced(device_owner) or
538             device_owner == l3_const.DEVICE_OWNER_AGENT_GW):
539             return port_db[portbindings.HOST_ID]
540
541     def _get_agent_gw_ports_exist_for_network(
542             self, context, network_id, host, agent_id):
543         """Return agent gw port if exist, or None otherwise."""
544         if not network_id:
545             LOG.debug("Network not specified")
546             return
547
548         filters = {
549             'network_id': [network_id],
550             'device_id': [agent_id],
551             'device_owner': [l3_const.DEVICE_OWNER_AGENT_GW]
552         }
553         ports = self._core_plugin.get_ports(context, filters)
554         if ports:
555             return ports[0]
556
557     def delete_floatingip_agent_gateway_port(
558         self, context, host_id, ext_net_id):
559         """Function to delete FIP gateway port with given ext_net_id."""
560         # delete any fip agent gw port
561         device_filter = {'device_owner': [l3_const.DEVICE_OWNER_AGENT_GW],
562                          'network_id': [ext_net_id]}
563         ports = self._core_plugin.get_ports(context,
564                                             filters=device_filter)
565         for p in ports:
566             if not host_id or p[portbindings.HOST_ID] == host_id:
567                 self._core_plugin.ipam.delete_port(context, p['id'])
568                 if host_id:
569                     return
570
571     def create_fip_agent_gw_port_if_not_exists(
572         self, context, network_id, host):
573         """Function to return the FIP Agent GW port.
574
575         This function will create a FIP Agent GW port
576         if required. If the port already exists, it
577         will return the existing port and will not
578         create a new one.
579         """
580         l3_agent_db = self._get_agent_by_type_and_host(
581             context, l3_const.AGENT_TYPE_L3, host)
582         if l3_agent_db:
583             LOG.debug("Agent ID exists: %s", l3_agent_db['id'])
584             f_port = self._get_agent_gw_ports_exist_for_network(
585                 context, network_id, host, l3_agent_db['id'])
586             if not f_port:
587                 LOG.info(_LI('Agent Gateway port does not exist,'
588                              ' so create one: %s'), f_port)
589                 port_data = {'tenant_id': '',
590                              'network_id': network_id,
591                              'device_id': l3_agent_db['id'],
592                              'device_owner': l3_const.DEVICE_OWNER_AGENT_GW,
593                              portbindings.HOST_ID: host,
594                              'admin_state_up': True,
595                              'name': ''}
596                 agent_port = p_utils.create_port(self._core_plugin, context,
597                                                  {'port': port_data})
598                 if agent_port:
599                     self._populate_subnets_for_ports(context, [agent_port])
600                     return agent_port
601                 msg = _("Unable to create the Agent Gateway Port")
602                 raise n_exc.BadRequest(resource='router', msg=msg)
603             else:
604                 self._populate_subnets_for_ports(context, [f_port])
605                 return f_port
606
607     def _get_snat_interface_ports_for_router(self, context, router_id):
608         """Return all existing snat_router_interface ports."""
609         qry = context.session.query(l3_db.RouterPort)
610         qry = qry.filter_by(
611             router_id=router_id,
612             port_type=l3_const.DEVICE_OWNER_ROUTER_SNAT
613         )
614
615         ports = [self._core_plugin._make_port_dict(rp.port, None)
616                  for rp in qry]
617         return ports
618
619     def _add_csnat_router_interface_port(
620             self, context, router, network_id, subnet_id, do_pop=True):
621         """Add SNAT interface to the specified router and subnet."""
622         port_data = {'tenant_id': '',
623                      'network_id': network_id,
624                      'fixed_ips': [{'subnet_id': subnet_id}],
625                      'device_id': router.id,
626                      'device_owner': l3_const.DEVICE_OWNER_ROUTER_SNAT,
627                      'admin_state_up': True,
628                      'name': ''}
629         snat_port = p_utils.create_port(self._core_plugin, context,
630                                         {'port': port_data})
631         if not snat_port:
632             msg = _("Unable to create the SNAT Interface Port")
633             raise n_exc.BadRequest(resource='router', msg=msg)
634
635         with context.session.begin(subtransactions=True):
636             router_port = l3_db.RouterPort(
637                 port_id=snat_port['id'],
638                 router_id=router.id,
639                 port_type=l3_const.DEVICE_OWNER_ROUTER_SNAT
640             )
641             context.session.add(router_port)
642
643         if do_pop:
644             return self._populate_subnets_for_ports(context, [snat_port])
645         return snat_port
646
647     def _create_snat_intf_ports_if_not_exists(self, context, router):
648         """Function to return the snat interface port list.
649
650         This function will return the snat interface port list
651         if it exists. If the port does not exist it will create
652         new ports and then return the list.
653         """
654         port_list = self._get_snat_interface_ports_for_router(
655             context, router.id)
656         if port_list:
657             self._populate_subnets_for_ports(context, port_list)
658             return port_list
659         port_list = []
660
661         int_ports = (
662             rp.port for rp in
663             router.attached_ports.filter_by(
664                 port_type=l3_const.DEVICE_OWNER_DVR_INTERFACE
665             )
666         )
667         LOG.info(_LI('SNAT interface port list does not exist,'
668                      ' so create one: %s'), port_list)
669         for intf in int_ports:
670             if intf.fixed_ips:
671                 # Passing the subnet for the port to make sure the IP's
672                 # are assigned on the right subnet if multiple subnet
673                 # exists
674                 snat_port = self._add_csnat_router_interface_port(
675                     context, router, intf['network_id'],
676                     intf['fixed_ips'][0]['subnet_id'], do_pop=False)
677                 port_list.append(snat_port)
678         if port_list:
679             self._populate_subnets_for_ports(context, port_list)
680         return port_list
681
682     def _generate_arp_table_and_notify_agent(
683         self, context, fixed_ip, mac_address, notifier):
684         """Generates the arp table entry and notifies the l3 agent."""
685         ip_address = fixed_ip['ip_address']
686         subnet = fixed_ip['subnet_id']
687         filters = {'fixed_ips': {'subnet_id': [subnet]}}
688         ports = self._core_plugin.get_ports(context, filters=filters)
689         for port in ports:
690             if port['device_owner'] == l3_const.DEVICE_OWNER_DVR_INTERFACE:
691                 router_id = port['device_id']
692                 router_dict = self._get_router(context, router_id)
693                 if router_dict.extra_attributes.distributed:
694                     arp_table = {'ip_address': ip_address,
695                                  'mac_address': mac_address,
696                                  'subnet_id': subnet}
697                     notifier(context, router_id, arp_table)
698                     return
699
700     def update_arp_entry_for_dvr_service_port(
701             self, context, port_dict, action):
702         """Notify L3 agents of ARP table entry for dvr service port.
703
704         When a dvr service port goes up or down, look for the DVR
705         router on the port's subnet, and send the ARP details to all
706         L3 agents hosting the router.
707         """
708
709         # Check this is a valid VM or service port
710         if not (n_utils.is_dvr_serviced(port_dict['device_owner']) and
711                 port_dict['fixed_ips']):
712             return
713         changed_fixed_ips = port_dict['fixed_ips']
714         for fixed_ip in changed_fixed_ips:
715             if action == "add":
716                 notifier = self.l3_rpc_notifier.add_arp_entry
717             elif action == "del":
718                 notifier = self.l3_rpc_notifier.del_arp_entry
719             else:
720                 return
721
722             self._generate_arp_table_and_notify_agent(
723                 context, fixed_ip, port_dict['mac_address'], notifier)
724
725     def delete_csnat_router_interface_ports(self, context,
726                                             router, subnet_id=None):
727         # Each csnat router interface port is associated
728         # with a subnet, so we need to pass the subnet id to
729         # delete the right ports.
730
731         # TODO(markmcclain): This is suboptimal but was left to reduce
732         # changeset size since it is late in cycle
733         ports = [
734             rp.port.id for rp in
735             router.attached_ports.filter_by(
736                     port_type=l3_const.DEVICE_OWNER_ROUTER_SNAT)
737             if rp.port
738         ]
739
740         c_snat_ports = self._core_plugin.get_ports(
741             context,
742             filters={'id': ports}
743         )
744         for p in c_snat_ports:
745             if subnet_id is None:
746                 self._core_plugin.delete_port(context,
747                                               p['id'],
748                                               l3_port_check=False)
749             else:
750                 if p['fixed_ips'][0]['subnet_id'] == subnet_id:
751                     LOG.debug("Subnet matches: %s", subnet_id)
752                     self._core_plugin.delete_port(context,
753                                                   p['id'],
754                                                   l3_port_check=False)
755
756     def create_floatingip(self, context, floatingip,
757                           initial_status=l3_const.FLOATINGIP_STATUS_ACTIVE):
758         floating_ip = self._create_floatingip(
759             context, floatingip, initial_status)
760         self._notify_floating_ip_change(context, floating_ip)
761         return floating_ip
762
763     def _notify_floating_ip_change(self, context, floating_ip):
764         router_id = floating_ip['router_id']
765         fixed_port_id = floating_ip['port_id']
766         # we need to notify agents only in case Floating IP is associated
767         if not router_id or not fixed_port_id:
768             return
769
770         try:
771             # using admin context as router may belong to admin tenant
772             router = self._get_router(context.elevated(), router_id)
773         except l3.RouterNotFound:
774             LOG.warning(_LW("Router %s was not found. "
775                             "Skipping agent notification."),
776                         router_id)
777             return
778
779         if is_distributed_router(router):
780             host = self._get_dvr_service_port_hostid(context, fixed_port_id)
781             self.l3_rpc_notifier.routers_updated_on_host(
782                 context, [router_id], host)
783         else:
784             self.notify_router_updated(context, router_id)
785
786     def update_floatingip(self, context, id, floatingip):
787         old_floatingip, floatingip = self._update_floatingip(
788             context, id, floatingip)
789         self._notify_floating_ip_change(context, old_floatingip)
790         if (floatingip['router_id'] != old_floatingip['router_id'] or
791                 floatingip['port_id'] != old_floatingip['port_id']):
792             self._notify_floating_ip_change(context, floatingip)
793         return floatingip
794
795     def delete_floatingip(self, context, id):
796         floating_ip = self._delete_floatingip(context, id)
797         self._notify_floating_ip_change(context, floating_ip)
798
799
800 def is_distributed_router(router):
801     """Return True if router to be handled is distributed."""
802     try:
803         # See if router is a DB object first
804         requested_router_type = router.extra_attributes.distributed
805     except AttributeError:
806         # if not, try to see if it is a request body
807         requested_router_type = router.get('distributed')
808     if attributes.is_attr_set(requested_router_type):
809         return requested_router_type
810     return cfg.CONF.router_distributed