class VppL3RouterPlugin(common_db_mixin.CommonDbMixin, l3_gwmode_db.L3_NAT_dbonly_mixin): """Implementation of the VPP L3 Router Service Plugin. This class implements a L3 service plugin that provides router and floatingip resources and manages associated request/response. """ supported_extension_aliases = ["router", "ext-gw-mode"] def __init__(self): super(VppL3RouterPlugin, self).__init__() self.communicator = EtcdAgentCommunicator( notify_bound=lambda *args: None) self.l3_host = cfg.CONF.ml2_vpp.l3_host def _floatingip_path(self, fip_id): return (server.LEADIN + '/nodes/' + self.l3_host + '/' + server.ROUTER_FIP_DIR + fip_id) def _process_floatingip(self, context, fip_dict, event_type): port = self._core_plugin.get_port(context, fip_dict['port_id']) external_network = self._core_plugin.get_network( context, fip_dict['floating_network_id']) internal_network = self._core_plugin.get_network( context, port['network_id']) vpp_floatingip_dict = { 'external_physnet': external_network[provider.PHYSICAL_NETWORK], 'external_net_type': external_network[provider.NETWORK_TYPE], 'external_segmentation_id': external_network[provider.SEGMENTATION_ID], 'internal_physnet': internal_network[provider.PHYSICAL_NETWORK], 'internal_net_type': internal_network[provider.NETWORK_TYPE], 'internal_segmentation_id': internal_network[provider.SEGMENTATION_ID], 'fixed_ip_address': fip_dict.get('fixed_ip_address'), 'floating_ip_address': fip_dict.get('floating_ip_address'), 'event': event_type, } db.journal_write(context.session, self._floatingip_path(fip_dict['id']), vpp_floatingip_dict) def _get_router_intf_details(self, context, router_id, interface_info, router_dict): # Returns a router dictionary populated with network and # subnet information for the associated subnet # Get vlan id for this subnet's network subnet = self._core_plugin.get_subnet(context, interface_info['subnet_id']) network = self._core_plugin.get_network(context, subnet['network_id']) router_dict['mtu'] = network['mtu'] router_dict['segmentation_id'] = network[provider.SEGMENTATION_ID] router_dict['net_type'] = network[provider.NETWORK_TYPE] router_dict['physnet'] = network[provider.PHYSICAL_NETWORK] # Get VRF corresponding to the router vrf_id = db.get_router_vrf(context.session, router_id) router_dict['vrf_id'] = vrf_id # Get internal gateway address for this subnet router_dict['gateway_ip'] = subnet['gateway_ip'] # Get prefix and type for this subnet router_dict['is_ipv6'] = False address = ip_network(subnet['cidr']) if address.version == 6: router_dict['is_ipv6'] = True router_dict['prefixlen'] = address.prefixlen def _write_interface_journal(self, context, router_id, router_dict): etcd_dir = (server.LEADIN + '/nodes/' + self.l3_host + '/' + server.ROUTER_INTF_DIR + router_id) db.journal_write(context.session, etcd_dir, router_dict) self.communicator.kick() def _write_router_journal(self, context, router_id, router_dict): etcd_dir = (server.LEADIN + '/nodes/' + self.l3_host + '/' + server.ROUTER_DIR + router_id) router_dict['vrf_id'] = db.get_router_vrf(context.session, router_id) # Get the external network details network = self._core_plugin.get_network( context, router_dict['external_gateway_info']['network_id']) # Grab the external network info router_dict['external_physnet'] = network[provider.PHYSICAL_NETWORK] router_dict['external_segment'] = network[provider.SEGMENTATION_ID] router_dict['external_net_type'] = network[provider.NETWORK_TYPE] # Grab all external subnets' gateway IPs # This is added to the router dictionary in the format: # [(Router's IP Address from the external network's subnet, # External Subnet's prefix)] fixed_ips = router_dict['external_gateway_info']['external_fixed_ips'] gateways = [] for fixed_ip in fixed_ips: subnet = self._core_plugin.get_subnet(context, fixed_ip['subnet_id']) gateways.append( (fixed_ip['ip_address'], ip_network(subnet['cidr']).prefixlen)) router_dict['gateways'] = gateways db.journal_write(context.session, etcd_dir, router_dict) self.communicator.kick() def get_plugin_type(self): # TODO(ijw): not really the right place for backward compatibility... try: return plugin_constants.L3 except Exception: return constants.L3 def get_plugin_description(self): """Returns string description of the plugin.""" return ("L3 Router Service Plugin for basic L3 forwarding " "using VPP.") def create_router(self, context, router): session = db_api.get_session() with session.begin(subtransactions=True): router_dict = super(VppL3RouterPlugin, self).create_router(context, router) # Allocate VRF for this router db.add_router_vrf(context.session, router_dict['id']) if router_dict.get('external_gateway_info', False): self._write_router_journal(context, router_dict['id'], router_dict) return router_dict def update_router(self, context, router_id, router): # Get the old router for comparison old_router = self.get_router(context, router_id) new_router = super(VppL3RouterPlugin, self).update_router(context, router_id, router) # Check if the gateway changed ext_gw = 'external_gateway_info' if old_router[ext_gw] != new_router[ext_gw]: # Check if the gateway has been removed if not new_router[ext_gw]: # Populate values from the old router new_router[ext_gw] = old_router[ext_gw] new_router['delete'] = True # Update dictionary values self._write_router_journal(context, router_id, new_router) return new_router def delete_router(self, context, router_id): session = db_api.get_session() with session.begin(subtransactions=True): router = self.get_router(context, router_id) super(VppL3RouterPlugin, self).delete_router(context, router_id) if router.get('external_gateway_info', False): router['delete'] = True self._write_router_journal(context, router_id, router) # Delete VRF allocation for this router db.delete_router_vrf(context.session, router_id) def create_floatingip(self, context, floatingip): session = db_api.get_session() with session.begin(subtransactions=True): fip_dict = super(VppL3RouterPlugin, self).create_floatingip( context, floatingip, initial_status=constants.FLOATINGIP_STATUS_ACTIVE) if fip_dict.get('port_id') is not None: self._process_floatingip(context, fip_dict, 'associate') if fip_dict.get('port_id') is not None: self.communicator.kick() return fip_dict @kick_communicator_on_end def update_floatingip(self, context, floatingip_id, floatingip): org_fip_dict = self.get_floatingip(context, floatingip_id) session = db_api.get_session() with session.begin(subtransactions=True): fip_dict = super(VppL3RouterPlugin, self).update_floatingip(context, floatingip_id, floatingip) if fip_dict.get('port_id') is not None: event_type = 'associate' vpp_fip_dict = fip_dict else: event_type = 'disassociate' vpp_fip_dict = org_fip_dict self._process_floatingip(context, vpp_fip_dict, event_type) return fip_dict def delete_floatingip(self, context, floatingip_id): org_fip_dict = self.get_floatingip(context, floatingip_id) session = db_api.get_session() with session.begin(subtransactions=True): super(VppL3RouterPlugin, self).delete_floatingip(context, floatingip_id) if org_fip_dict.get('port_id') is not None: self._process_floatingip(context, org_fip_dict, 'disassociate') if org_fip_dict.get('port_id') is not None: self.communicator.kick() @kick_communicator_on_end def disassociate_floatingips(self, context, port_id, do_notify=True): fips = self.get_floatingips(context.elevated(), filters={'port_id': [port_id]}) session = db_api.get_session() with session.begin(subtransactions=True): router_ids = super(VppL3RouterPlugin, self).disassociate_floatingips( context, port_id, do_notify) for fip in fips: self._process_floatingip(context, fip, 'disassociate') return router_ids @kick_communicator_on_end def add_router_interface(self, context, router_id, interface_info): session = db_api.get_session() with session.begin(subtransactions=True): new_router = super(VppL3RouterPlugin, self).add_router_interface( context, router_id, interface_info) router_dict = {} # Get a random mac address for loopback mac = net_utils.get_random_mac(cfg.CONF.base_mac.split(':')) router_dict['loopback_mac'] = mac self._get_router_intf_details(context, router_id, interface_info, router_dict) self._write_interface_journal(context, router_id, router_dict) return new_router @kick_communicator_on_end def remove_router_interface(self, context, router_id, interface_info): session = db_api.get_session() with session.begin(subtransactions=True): new_router = super(VppL3RouterPlugin, self).remove_router_interface( context, router_id, interface_info) router_dict = {} router_dict['delete'] = True self._get_router_intf_details(context, router_id, interface_info, router_dict) self._write_interface_journal(context, router_id, router_dict) return new_router
class VppL3RouterPlugin(common_db_mixin.CommonDbMixin, l3_gwmode_db.L3_NAT_dbonly_mixin): """Implementation of the VPP L3 Router Service Plugin. This class implements a L3 service plugin that provides router and floatingip resources and manages associated request/response. """ supported_extension_aliases = ["router", "ext-gw-mode"] def __init__(self): super(VppL3RouterPlugin, self).__init__() self.communicator = EtcdAgentCommunicator( notify_bound=lambda *args: None) self.l3_hosts = cfg.CONF.ml2_vpp.l3_hosts.replace(' ', '').split(',') self.gpe_physnet = cfg.CONF.ml2_vpp.gpe_locators LOG.info('vpp-router: router_service plugin has initialized') if cfg.CONF.ml2_vpp.enable_l3_ha: LOG.info('vpp-router: L3 HA is enabled') LOG.debug('vpp-router l3_hosts: %s', self.l3_hosts) def _floatingip_path(self, l3_host, fip_id): return (nvpp_const.LEADIN + '/nodes/' + l3_host + '/' + nvpp_const.ROUTER_FIP_DIR + fip_id) @neutron_db_api.context_manager.writer def _process_floatingip(self, context, fip_dict, event_type): if event_type == 'associate': port = self._core_plugin.get_port(context, fip_dict['port_id']) external_network = self._core_plugin.get_network( context, fip_dict['floating_network_id']) internal_network = self._core_plugin.get_network( context, port['network_id']) LOG.debug("Router: Associating floating ip: %s", fip_dict) if internal_network[provider.NETWORK_TYPE] == nvpp_const.TYPE_GPE: internal_physnet = self.gpe_physnet else: internal_physnet = internal_network[ provider.PHYSICAL_NETWORK] vpp_floatingip_dict = { 'external_physnet': external_network[ provider.PHYSICAL_NETWORK], 'external_net_type': external_network[provider.NETWORK_TYPE], 'external_segmentation_id': external_network[provider.SEGMENTATION_ID], 'internal_physnet': internal_physnet, 'internal_net_type': internal_network[provider.NETWORK_TYPE], 'internal_segmentation_id': internal_network[provider.SEGMENTATION_ID], 'fixed_ip_address': fip_dict.get('fixed_ip_address'), 'floating_ip_address': fip_dict.get('floating_ip_address') } else: # Remove etcd key == disassociate floatingip LOG.debug("Router: disassociating floating ip: %s", fip_dict) vpp_floatingip_dict = None for l3_host in self.l3_hosts: db.journal_write(context.session, self._floatingip_path(l3_host, fip_dict['id']), vpp_floatingip_dict) @neutron_db_api.context_manager.reader def _get_vpp_router(self, context, router_id): try: router = self._get_by_id(context, Router, router_id) except Exception: raise n_exc.BadRequest("L3 Router not found for router_id: %s", router_id) return router @neutron_db_api.context_manager.writer def _get_router_interface(self, context, router_id, router_dict): """Populate the param: "router_dict" with values and return. SideEffect: Changes the parameter: router_dict Returns the router_dict populated with the network, subnet gateway ip_address, GPE locators for gpe, network type, segmentation ID, is_ipv6, VRF and prefix-length information so vpp-agent can create the vpp router interface. Arguments:- 1. router_dict: The router dictionary to be populated with data. It must contain the key "port_id", which is used to populate the router dict. 2. router_id: Router ID """ port_id = router_dict['port_id'] port = self._core_plugin.get_port(context, port_id) network = self._core_plugin.get_network(context, port['network_id']) fixed_ips = [ip for ip in port['fixed_ips']] if not fixed_ips: n_exc.BadRequest('vpp-router-service: A router port must ' 'have at least one fixed IP address') # TODO(najoy): Handle multiple fixed-ips on a router port fixed_ip = fixed_ips[0] subnet = self._core_plugin.get_subnet(context, fixed_ip['subnet_id']) router_dict['gateway_ip'] = fixed_ip['ip_address'] router_dict['subnet_id'] = subnet['id'] router_dict['fixed_ips'] = fixed_ips address = ip_network(six.text_type(subnet['cidr'])) router_dict['network_id'] = network['id'] router_dict['is_ipv6'] = True if address.version == 6 else False router_dict['prefixlen'] = address.prefixlen router_dict['mtu'] = network['mtu'] router_dict['segmentation_id'] = network[provider.SEGMENTATION_ID] router_dict['net_type'] = network[provider.NETWORK_TYPE] if router_dict['net_type'] == nvpp_const.TYPE_GPE: router_dict['physnet'] = self.gpe_physnet else: router_dict['physnet'] = network[provider.PHYSICAL_NETWORK] # Get VRF corresponding to the router vrf_id = db.get_router_vrf(context.session, router_id) router_dict['vrf_id'] = vrf_id return router_dict def _get_router_intf_path(self, l3_host, router_id, port_id): return (nvpp_const.LEADIN + '/nodes/' + l3_host + '/' + nvpp_const.ROUTERS_DIR + router_id + '/' + port_id) @neutron_db_api.context_manager.writer def _write_interface_journal(self, context, router_id, router_dict): LOG.info("router-service: writing router interface journal for " "router_id:%s, router_dict:%s", router_id, router_dict) for l3_host in self.l3_hosts: router_intf_path = self._get_router_intf_path( l3_host, router_id, router_dict['port_id']) db.journal_write(context.session, router_intf_path, router_dict) self.communicator.kick() @neutron_db_api.context_manager.writer def _remove_interface_journal(self, context, router_id, port_id): LOG.info("router-service: removing router interface journal for " "router_id:%s, port_id:%s", router_id, port_id) for l3_host in self.l3_hosts: router_intf_path = self._get_router_intf_path( l3_host, router_id, port_id) db.journal_write(context.session, router_intf_path, None) self.communicator.kick() @neutron_db_api.context_manager.writer def _write_router_external_gw_journal(self, context, router_id, router_dict, delete=False): LOG.info("Writing router external gateway using router_dict: %s", router_dict) router_dict['vrf_id'] = db.get_router_vrf(context.session, router_id) # Get the external network details network = self._core_plugin.get_network( context, router_dict['external_gateway_info']['network_id']) LOG.debug("Router external gateway network data: %s", network) # Grab the external network info router_dict['external_physnet'] = network[provider.PHYSICAL_NETWORK] router_dict['external_segmentation_id'] = network[ provider.SEGMENTATION_ID] router_dict['external_net_type'] = network[provider.NETWORK_TYPE] router_dict['mtu'] = network['mtu'] # The Neutron port created for the gateway gateway_port = router_dict['gw_port_id'] # Get the mac-addr of the port to create the port if not delete: gw_port = self._core_plugin.get_port( context.elevated(), gateway_port) router_dict['loopback_mac'] = gw_port['mac_address'] # Grab all external subnets' gateway IPs # This is added to the router dictionary using the key: gateways # The value of gateways is a list of tuples: # (gateway_ip, prefix_len, is_ipv6) fixed_ips = router_dict['external_gateway_info']['external_fixed_ips'] gateways = [] # For each subnet/fixed-ip, write a key to create a gateway uplink # on that subnet with the correct IP address for fixed_ip in fixed_ips: subnet_id = fixed_ip['subnet_id'] subnet = self._core_plugin.get_subnet( context.elevated(), subnet_id) address = ip_network(six.text_type(subnet['cidr'])) is_ipv6 = True if address.version == 6 else False gateways.append((fixed_ip['ip_address'], address.prefixlen, is_ipv6)) # IP addresses to be set on VPP's external interface connecting # to an external gateway router_dict['gateways'] = gateways # This the IP address of the external gateway that functions as # the default gateway for the tenant's router router_dict['external_gateway_ip'] = subnet['gateway_ip'] for l3_host in self.l3_hosts: etcd_key = self._get_router_intf_path(l3_host, router_id, gateway_port) if delete: db.journal_write(context.session, etcd_key, None) else: db.journal_write(context.session, etcd_key, router_dict) self.communicator.kick() def get_plugin_type(self): # TODO(ijw): not really the right place for backward compatibility... try: return plugin_constants.L3 except Exception: return constants.L3 def get_plugin_description(self): """Returns string description of the plugin.""" return ("L3 Router Service Plugin for basic L3 forwarding " "using VPP.") def create_router(self, context, router): router_dict = super(VppL3RouterPlugin, self).create_router( context, router) with neutron_db_api.context_manager.writer.using(context): # Allocate VRF for this router db.add_router_vrf(context.session, router_dict['id']) if router_dict.get('external_gateway_info', False): self._write_router_external_gw_journal( context, router_dict['id'], router_dict) return router_dict def update_router(self, context, router_id, router): # Get the old router for comparison old_router = self.get_router(context, router_id) new_router = super(VppL3RouterPlugin, self).update_router( context, router_id, router) ext_gw = 'external_gateway_info' # If external gateway has changed, delete the old external gateway # states from etcd and write the new states, if we have a new valid # external gateway if old_router[ext_gw] != new_router[ext_gw]: # If the old router has an external gateway delete and then set # the new router's external gateway if old_router[ext_gw]: self._write_router_external_gw_journal(context, router_id, old_router, delete=True) if new_router[ext_gw]: self._write_router_external_gw_journal(context, router_id, new_router) return new_router def delete_router(self, context, router_id): router = self.get_router(context, router_id) super(VppL3RouterPlugin, self).delete_router(context, router_id) with neutron_db_api.context_manager.writer.using(context): # Delete the external gateway key from etcd if router.get('external_gateway_info', False): self._write_router_external_gw_journal(context, router_id, router, delete=True) # Delete the base <router_id> key from etcd, i.e., # /networking-vpp/nodes/<node_name>/routers/<router_id> for l3_host in self.l3_hosts: etcd_key = self._get_router_intf_path(l3_host, router_id, '') db.journal_write(context.session, etcd_key, None) db.delete_router_vrf(context.session, router_id) self.communicator.kick() def create_floatingip(self, context, floatingip): fip_dict = super(VppL3RouterPlugin, self).create_floatingip( context, floatingip, initial_status=constants.FLOATINGIP_STATUS_ACTIVE) if fip_dict.get('port_id') is not None: self._process_floatingip(context, fip_dict, 'associate') self.communicator.kick() return fip_dict @kick_communicator_on_end def update_floatingip(self, context, floatingip_id, floatingip): org_fip_dict = self.get_floatingip(context, floatingip_id) fip_dict = super(VppL3RouterPlugin, self).update_floatingip( context, floatingip_id, floatingip) if fip_dict.get('port_id') is not None: event_type = 'associate' vpp_fip_dict = fip_dict self.update_floatingip_status( context, floatingip_id, constants.FLOATINGIP_STATUS_ACTIVE) else: event_type = 'disassociate' vpp_fip_dict = org_fip_dict self.update_floatingip_status( context, floatingip_id, constants.FLOATINGIP_STATUS_DOWN) self._process_floatingip(context, vpp_fip_dict, event_type) return fip_dict def delete_floatingip(self, context, floatingip_id): org_fip_dict = self.get_floatingip(context, floatingip_id) super(VppL3RouterPlugin, self).delete_floatingip( context, floatingip_id) if org_fip_dict.get('port_id') is not None: self._process_floatingip(context, org_fip_dict, 'disassociate') if org_fip_dict.get('port_id') is not None: self.communicator.kick() @kick_communicator_on_end def disassociate_floatingips(self, context, port_id, do_notify=True): fips = self.get_floatingips(context.elevated(), filters={'port_id': [port_id]}) router_ids = super( VppL3RouterPlugin, self).disassociate_floatingips( context, port_id, do_notify) for fip in fips: self._process_floatingip(context, fip, 'disassociate') return router_ids @neutron_db_api.context_manager.reader def _get_router_port_on_subnet(self, context, router_id, router_dict): filters = {'device_id': [router_id]} router_ports = self._core_plugin.get_ports(context, filters=filters) fixed_ip = router_dict['fixed_ips'][0] subnet_id = fixed_ip['subnet_id'] ip_address = fixed_ip['ip_address'] LOG.info("Router ports on subnet: %s == %s", subnet_id, router_ports) router_port = [port for port in router_ports if port['fixed_ips'][0]['subnet_id'] == subnet_id and port['fixed_ips'][0]['ip_address'] == ip_address] if router_port: return router_port[0]['id'] else: LOG.error("A router port could not be found on subnet: %s", router_dict['subnet_id']) @kick_communicator_on_end def add_router_interface(self, context, router_id, interface_info): """Add a router port to a subnet or bind it to an existing port. There are two ways to add a router interface. The interface_info can provide a subnet using the 'subnet_id' key or a port using the 'port_id' key. """ LOG.info("router_service: interface_info: %s", interface_info) new_router = super(VppL3RouterPlugin, self).add_router_interface( context, router_id, interface_info) LOG.info("Add router interface: New router is: %s", new_router) router_dict = {} port_id = new_router['port_id'] port = self._core_plugin.get_port(context, port_id) router_dict['port_id'] = port_id router_dict['loopback_mac'] = port['mac_address'] router_dict = self._get_router_interface(context, router_id, router_dict ) router = self._get_vpp_router(context, router_id) port_data = {'tenant_id': router.tenant_id, 'network_id': router_dict['network_id'], 'fixed_ips': router_dict['fixed_ips'], 'device_id': router.id, 'device_owner': 'vpp-router' } self._core_plugin.update_port(context, port_id, {'port': port_data}) self._write_interface_journal(context, router_id, router_dict) return new_router @kick_communicator_on_end def remove_router_interface(self, context, router_id, interface_info): new_router = super(VppL3RouterPlugin, self).remove_router_interface( context, router_id, interface_info) LOG.info("Remove router interface: New router is: %s", new_router) port_id = new_router['port_id'] self._core_plugin.delete_port(context, port_id, l3_port_check=False) self._remove_interface_journal(context, router_id, port_id) return new_router
class VppL3RouterPlugin(common_db_mixin.CommonDbMixin, l3_gwmode_db.L3_NAT_dbonly_mixin): """Implementation of the VPP L3 Router Service Plugin. This class implements a L3 service plugin that provides router and floatingip resources and manages associated request/response. """ supported_extension_aliases = ["router", "ext-gw-mode"] def __init__(self): super(VppL3RouterPlugin, self).__init__() self.communicator = EtcdAgentCommunicator( notify_bound=lambda *args: None) self.l3_hosts = cfg.CONF.ml2_vpp.l3_hosts.replace(' ', '').split(',') self.gpe_physnet = cfg.CONF.ml2_vpp.gpe_locators LOG.info('vpp-router: router_service plugin has initialized') if cfg.CONF.ml2_vpp.enable_l3_ha: LOG.info('vpp-router: L3 HA is enabled') LOG.debug('vpp-router l3_hosts: %s', self.l3_hosts) def _floatingip_path(self, l3_host, fip_id): return (nvpp_const.LEADIN + '/nodes/' + l3_host + '/' + nvpp_const.ROUTER_FIP_DIR + fip_id) @neutron_db_api.context_manager.writer def _process_floatingip(self, context, fip_dict, event_type): port = self._core_plugin.get_port(context, fip_dict['port_id']) external_network = self._core_plugin.get_network( context, fip_dict['floating_network_id']) internal_network = self._core_plugin.get_network( context, port['network_id']) if event_type == 'associate': LOG.debug("Router: Associating floating ip: %s", fip_dict) if internal_network[provider.NETWORK_TYPE] == 'vxlan': internal_physnet = self.gpe_physnet else: internal_physnet = internal_network[provider.PHYSICAL_NETWORK] vpp_floatingip_dict = { 'external_physnet': external_network[provider.PHYSICAL_NETWORK], 'external_net_type': external_network[provider.NETWORK_TYPE], 'external_segmentation_id': external_network[provider.SEGMENTATION_ID], 'internal_physnet': internal_physnet, 'internal_net_type': internal_network[provider.NETWORK_TYPE], 'internal_segmentation_id': internal_network[provider.SEGMENTATION_ID], 'fixed_ip_address': fip_dict.get('fixed_ip_address'), 'floating_ip_address': fip_dict.get('floating_ip_address') } else: # Remove etcd key == disassociate floatingip LOG.debug("Router: disassociating floating ip: %s", fip_dict) vpp_floatingip_dict = None for l3_host in self.l3_hosts: db.journal_write(context.session, self._floatingip_path(l3_host, fip_dict['id']), vpp_floatingip_dict) @neutron_db_api.context_manager.reader def _get_vpp_router(self, context, router_id): try: router = self._get_by_id(context, Router, router_id) except Exception: raise n_exc.BadRequest("L3 Router not found for router_id: %s", router_id) return router @neutron_db_api.context_manager.reader def _get_router_interface(self, context, router_id, router_dict): """Populate the param: "router_dict" with values and return. SideEffect: Changes the parameter: router_dict Returns the router_dict populated with the network, subnet gateway ip_address, GPE locators for vxlan, network type, segmentation ID, is_ipv6, VRF and prefix-length information so vpp-agent can create the vpp router interface. Arguments:- 1. router_dict: The router dictionary to be populated with data. It must contain the key "port_id", which is used to populate the router dict. 2. router_id: Router ID """ port_id = router_dict['port_id'] port = self._core_plugin.get_port(context, port_id) network = self._core_plugin.get_network(context, port['network_id']) fixed_ips = [ip for ip in port['fixed_ips']] if not fixed_ips: n_exc.BadRequest('vpp-router-service: A router port must ' 'have at least one fixed IP address') # TODO(najoy): Handle multiple fixed-ips on a router port fixed_ip = fixed_ips[0] subnet = self._core_plugin.get_subnet(context, fixed_ip['subnet_id']) router_dict['gateway_ip'] = fixed_ip['ip_address'] router_dict['subnet_id'] = subnet['id'] router_dict['fixed_ips'] = fixed_ips address = ip_network(subnet['cidr']) router_dict['network_id'] = network['id'] router_dict['is_ipv6'] = True if address.version == 6 else False router_dict['prefixlen'] = address.prefixlen router_dict['mtu'] = network['mtu'] router_dict['segmentation_id'] = network[provider.SEGMENTATION_ID] router_dict['net_type'] = network[provider.NETWORK_TYPE] if router_dict['net_type'] == 'vxlan': router_dict['physnet'] = self.gpe_physnet else: router_dict['physnet'] = network[provider.PHYSICAL_NETWORK] # Get VRF corresponding to the router vrf_id = db.get_router_vrf(context.session, router_id) router_dict['vrf_id'] = vrf_id return router_dict def _get_router_intf_path(self, l3_host, router_id, port_id): return (nvpp_const.LEADIN + '/nodes/' + l3_host + '/' + nvpp_const.ROUTERS_DIR + router_id + '/' + port_id) @neutron_db_api.context_manager.writer def _write_interface_journal(self, context, router_id, router_dict): LOG.info( "router-service: writing router interface journal for " "router_id:%s, router_dict:%s", router_id, router_dict) for l3_host in self.l3_hosts: router_intf_path = self._get_router_intf_path( l3_host, router_id, router_dict['port_id']) db.journal_write(context.session, router_intf_path, router_dict) self.communicator.kick() @neutron_db_api.context_manager.writer def _remove_interface_journal(self, context, router_id, port_id): LOG.info( "router-service: removing router interface journal for " "router_id:%s, port_id:%s", router_id, port_id) for l3_host in self.l3_hosts: router_intf_path = self._get_router_intf_path( l3_host, router_id, port_id) db.journal_write(context.session, router_intf_path, None) self.communicator.kick() @neutron_db_api.context_manager.writer def _write_router_external_gw_journal(self, context, router_id, router_dict, delete=False): LOG.info("Writing router external gateway using router_dict: %s", router_dict) router_dict['vrf_id'] = db.get_router_vrf(context.session, router_id) # Get the external network details network = self._core_plugin.get_network( context, router_dict['external_gateway_info']['network_id']) LOG.debug("Router external gateway network data: %s", network) # Grab the external network info router_dict['external_physnet'] = network[provider.PHYSICAL_NETWORK] router_dict['external_segmentation_id'] = network[ provider.SEGMENTATION_ID] router_dict['external_net_type'] = network[provider.NETWORK_TYPE] router_dict['mtu'] = network['mtu'] # The Neutron port created for the gateway gateway_port = router_dict['gw_port_id'] # Get the mac-addr of the port to create the port if not delete: gw_port = self._core_plugin.get_port(context, gateway_port) router_dict['loopback_mac'] = gw_port['mac_address'] # Grab all external subnets' gateway IPs # This is added to the router dictionary using the key: gateways # The value of gateways is a list of tuples: # (gateway_ip, prefix_len, is_ipv6) fixed_ips = router_dict['external_gateway_info']['external_fixed_ips'] gateways = [] # For each subnet/fixed-ip, write a key to create a gateway uplink # on that subnet with the correct IP address for fixed_ip in fixed_ips: subnet_id = fixed_ip['subnet_id'] subnet = self._core_plugin.get_subnet(context, subnet_id) address = ip_network(subnet['cidr']) is_ipv6 = True if address.version == 6 else False gateways.append( (fixed_ip['ip_address'], address.prefixlen, is_ipv6)) # IP addresses to be set on VPP's external interface connecting # to an external gateway router_dict['gateways'] = gateways # This the IP address of the external gateway that functions as # the default gateway for the tenant's router router_dict['external_gateway_ip'] = subnet['gateway_ip'] for l3_host in self.l3_hosts: etcd_key = self._get_router_intf_path(l3_host, router_id, gateway_port) if delete: db.journal_write(context.session, etcd_key, None) else: db.journal_write(context.session, etcd_key, router_dict) self.communicator.kick() def get_plugin_type(self): # TODO(ijw): not really the right place for backward compatibility... try: return plugin_constants.L3 except Exception: return constants.L3 def get_plugin_description(self): """Returns string description of the plugin.""" return ("L3 Router Service Plugin for basic L3 forwarding " "using VPP.") def create_router(self, context, router): router_dict = super(VppL3RouterPlugin, self).create_router(context, router) with neutron_db_api.context_manager.writer.using(context): # Allocate VRF for this router db.add_router_vrf(context.session, router_dict['id']) if router_dict.get('external_gateway_info', False): self._write_router_external_gw_journal(context, router_dict['id'], router_dict) return router_dict def update_router(self, context, router_id, router): # Get the old router for comparison old_router = self.get_router(context, router_id) new_router = super(VppL3RouterPlugin, self).update_router(context, router_id, router) ext_gw = 'external_gateway_info' # If external gateway has changed, delete the old external gateway # states from etcd and write the new states, if we have a new valid # external gateway if old_router[ext_gw] != new_router[ext_gw]: # If the old router has an external gateway delete and then set # the new router's external gateway if old_router[ext_gw]: self._write_router_external_gw_journal(context, router_id, old_router, delete=True) if new_router[ext_gw]: self._write_router_external_gw_journal(context, router_id, new_router) return new_router def delete_router(self, context, router_id): router = self.get_router(context, router_id) super(VppL3RouterPlugin, self).delete_router(context, router_id) with neutron_db_api.context_manager.writer.using(context): # Delete the external gateway key from etcd if router.get('external_gateway_info', False): self._write_router_external_gw_journal(context, router_id, router, delete=True) db.delete_router_vrf(context.session, router_id) def create_floatingip(self, context, floatingip): fip_dict = super(VppL3RouterPlugin, self).create_floatingip( context, floatingip, initial_status=constants.FLOATINGIP_STATUS_ACTIVE) if fip_dict.get('port_id') is not None: self._process_floatingip(context, fip_dict, 'associate') if fip_dict.get('port_id') is not None: self.communicator.kick() return fip_dict @kick_communicator_on_end def update_floatingip(self, context, floatingip_id, floatingip): org_fip_dict = self.get_floatingip(context, floatingip_id) fip_dict = super(VppL3RouterPlugin, self).update_floatingip(context, floatingip_id, floatingip) if fip_dict.get('port_id') is not None: event_type = 'associate' vpp_fip_dict = fip_dict else: event_type = 'disassociate' vpp_fip_dict = org_fip_dict self._process_floatingip(context, vpp_fip_dict, event_type) return fip_dict def delete_floatingip(self, context, floatingip_id): org_fip_dict = self.get_floatingip(context, floatingip_id) super(VppL3RouterPlugin, self).delete_floatingip(context, floatingip_id) if org_fip_dict.get('port_id') is not None: self._process_floatingip(context, org_fip_dict, 'disassociate') if org_fip_dict.get('port_id') is not None: self.communicator.kick() @kick_communicator_on_end def disassociate_floatingips(self, context, port_id, do_notify=True): fips = self.get_floatingips(context.elevated(), filters={'port_id': [port_id]}) router_ids = super(VppL3RouterPlugin, self).disassociate_floatingips( context, port_id, do_notify) for fip in fips: self._process_floatingip(context, fip, 'disassociate') return router_ids @neutron_db_api.context_manager.reader def _get_router_port_on_subnet(self, context, router_id, router_dict): filters = {'device_id': [router_id]} router_ports = self._core_plugin.get_ports(context, filters=filters) fixed_ip = router_dict['fixed_ips'][0] subnet_id = fixed_ip['subnet_id'] ip_address = fixed_ip['ip_address'] LOG.info("Router ports on subnet: %s == %s", subnet_id, router_ports) router_port = [ port for port in router_ports if port['fixed_ips'][0]['subnet_id'] == subnet_id and port['fixed_ips'][0]['ip_address'] == ip_address ] if router_port: return router_port[0]['id'] else: LOG.error("A router port could not be found on subnet: %s", router_dict['subnet_id']) @kick_communicator_on_end def add_router_interface(self, context, router_id, interface_info): """Add a router port to a subnet or bind it to an existing port. There are two ways to add a router interface. The interface_info can provide a subnet using the 'subnet_id' key or a port using the 'port_id' key. """ LOG.info("router_service: interface_info: %s", interface_info) new_router = super(VppL3RouterPlugin, self).add_router_interface(context, router_id, interface_info) LOG.info("Add router interface: New router is: %s", new_router) router_dict = {} port_id = new_router['port_id'] port = self._core_plugin.get_port(context, port_id) router_dict['port_id'] = port_id router_dict['loopback_mac'] = port['mac_address'] router_dict = self._get_router_interface(context, router_id, router_dict) router = self._get_vpp_router(context, router_id) port_data = { 'tenant_id': router.tenant_id, 'network_id': router_dict['network_id'], 'fixed_ips': router_dict['fixed_ips'], 'device_id': router.id, 'device_owner': 'vpp-router' } self._core_plugin.update_port(context, port_id, {'port': port_data}) self._write_interface_journal(context, router_id, router_dict) return new_router @kick_communicator_on_end def remove_router_interface(self, context, router_id, interface_info): new_router = super(VppL3RouterPlugin, self).remove_router_interface( context, router_id, interface_info) LOG.info("Remove router interface: New router is: %s", new_router) port_id = new_router['port_id'] self._core_plugin.delete_port(context, port_id, l3_port_check=False) self._remove_interface_journal(context, router_id, port_id) return new_router