示例#1
0
 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')
     LOG.debug('vpp-router l3_hosts: %s', self.l3_hosts)
示例#2
0
 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)
示例#3
0
 def __init__(self):
     super(VppTrunkPlugin, self).__init__()
     self.communicator = EtcdAgentCommunicator(
         notify_bound=lambda *args: None)
     # Supported segmentation type is VLAN
     self._segmentation_types = {
         trunk_const.VLAN: plugin_utils.is_valid_vlan_tag
     }
     # Subscribe to trunk parent-port binding events
     # We use this event to trigger the etcd trunk key update.
     registry.subscribe(self._trigger_etcd_trunk_update, resources.PORT,
                        events.AFTER_UPDATE)
     registry.notify(trunk_const.TRUNK_PLUGIN, events.AFTER_INIT, self)
     LOG.debug('vpp-trunk: vpp trunk service plugin has initialized')
示例#4
0
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
示例#5
0
 def __init__(self):
     super(VppL3RouterPlugin, self).__init__()
     self.communicator = EtcdAgentCommunicator(
         notify_bound=lambda *args: None)
     self.l3_host = cfg.CONF.ml2_vpp.l3_host
示例#6
0
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
示例#7
0
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