# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import netaddr from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api from neutron.api.rpc.handlers import dhcp_rpc from neutron.api.rpc.handlers import l3_rpc from neutron.api.rpc.handlers import metadata_rpc from neutron.api.v2 import attributes as attr from neutron.callbacks import events from neutron.callbacks import registry from neutron.callbacks import resources from neutron.common import exceptions as n_common_exc from neutron.common import rpc as n_rpc from neutron.common import topics from neutron.common import utils from neutron.db import agents_db from neutron.db import agentschedulers_db from neutron.db import allowedaddresspairs_db as addr_pair_db from neutron.db import db_base_plugin_v2 from neutron.db import external_net_db from neutron.db import extradhcpopt_db from neutron.db import extraroute_db from neutron.db import l3_agentschedulers_db from neutron.db import l3_attrs_db from neutron.db import l3_db from neutron.db import l3_gwmode_db from neutron.db.models import securitygroup as sg_models from neutron.db import models_v2 from neutron.db import netmtu_db from neutron.db import portbindings_db from neutron.db import portsecurity_db_common from neutron.db import securitygroups_db from neutron.extensions import allowedaddresspairs as addr_pair from neutron.extensions import extra_dhcp_opt as edo_ext from neutron.extensions import portbindings from neutron.extensions import portsecurity as psec from neutron.extensions import providernet as pnet from neutron.quota import resource_registry from neutron_lib import constants as const from neutron_lib import exceptions as n_exc from oslo_config import cfg from oslo_log import log from oslo_utils import excutils from oslo_utils import importutils import six from sqlalchemy.orm import exc as sa_exc from dragonflow._i18n import _, _LE, _LI from dragonflow.common import common_params from dragonflow.common import constants as df_common_const from dragonflow.common import exceptions as df_exceptions from dragonflow.common import extensions from dragonflow.common import utils as df_utils from dragonflow.db import api_nb from dragonflow.db.neutron import lockedobjects_db as lock_db from dragonflow.db.neutron import versionobjects_db as version_db from dragonflow.neutron.common import constants as df_const LOG = log.getLogger(__name__) cfg.CONF.register_opts(common_params.DF_OPTS, 'df') router_distributed_opts = [ cfg.BoolOpt('router_distributed', default=False, help=_("System-wide flag to determine the type of router " "that tenants can create. Only admin can override.")), ] cfg.CONF.register_opts(router_distributed_opts) class DFPlugin(db_base_plugin_v2.NeutronDbPluginV2, securitygroups_db.SecurityGroupDbMixin, l3_agentschedulers_db.L3AgentSchedulerDbMixin, l3_gwmode_db.L3_NAT_db_mixin, l3_attrs_db.ExtraAttributesMixin, external_net_db.External_net_db_mixin, portbindings_db.PortBindingMixin, portsecurity_db_common.PortSecurityDbCommon, addr_pair_db.AllowedAddressPairsMixin, extradhcpopt_db.ExtraDhcpOptMixin, extraroute_db.ExtraRoute_db_mixin, agentschedulers_db.DhcpAgentSchedulerDbMixin, netmtu_db.Netmtu_db_mixin): __native_bulk_support = True __native_pagination_support = True __native_sorting_support = True supported_extension_aliases = extensions.SUPPORTED_API_EXTENSIONS extra_attributes = ( l3_attrs_db.ExtraAttributesMixin.extra_attributes + [{ 'name': "distributed", 'default': cfg.CONF.router_distributed }]) @resource_registry.tracked_resources( network=models_v2.Network, port=models_v2.Port, subnet=models_v2.Subnet, subnetpool=models_v2.SubnetPool, security_group=sg_models.SecurityGroup, security_group_rule=sg_models.SecurityGroupRule, router=l3_db.Router, floatingip=l3_db.FloatingIP) def __init__(self): self.router_scheduler = importutils.import_object( cfg.CONF.router_scheduler_driver) super(DFPlugin, self).__init__() LOG.info(_LI("Starting DFPlugin")) self.vif_type = portbindings.VIF_TYPE_OVS self._set_base_port_binding() # When set to True, Nova plugs the VIF directly into the ovs bridge # instead of using the hybrid mode. self.vif_details = {portbindings.CAP_PORT_FILTER: True} registry.subscribe(self.post_fork_initialize, resources.PROCESS, events.AFTER_INIT) self._setup_dhcp() self._start_rpc_notifiers() def post_fork_initialize(self, resource, event, trigger, **kwargs): nb_driver = df_utils.load_driver( cfg.CONF.df.nb_db_class, df_utils.DF_NB_DB_DRIVER_NAMESPACE) self.nb_api = api_nb.NbApi( nb_driver, use_pubsub=cfg.CONF.df.enable_df_pub_sub, is_neutron_server=True) self.nb_api.initialize(db_ip=cfg.CONF.df.remote_db_ip, db_port=cfg.CONF.df.remote_db_port) self._set_base_port_binding() def _set_base_port_binding(self): if cfg.CONF.df.vif_type == portbindings.VIF_TYPE_VHOST_USER: self.base_binding_dict = { portbindings.VIF_TYPE: portbindings.VIF_TYPE_VHOST_USER, portbindings.VIF_DETAILS: { # TODO(nick-ma-z): VIF security is disabled for vhu port. # This will be revisited if the function is supported by # OVS upstream. portbindings.CAP_PORT_FILTER: False, portbindings.VHOST_USER_MODE: portbindings.VHOST_USER_MODE_CLIENT, portbindings.VHOST_USER_OVS_PLUG: True, } } else: self.base_binding_dict = { portbindings.VIF_TYPE: portbindings.VIF_TYPE_OVS, portbindings.VIF_DETAILS: { # TODO(rkukura): Replace with new VIF security details portbindings.CAP_PORT_FILTER: 'security-group' in self.supported_extension_aliases}} def _update_port_binding(self, port_res): port_res[portbindings.VNIC_TYPE] = portbindings.VNIC_NORMAL if cfg.CONF.df.vif_type == portbindings.VIF_TYPE_VHOST_USER: port_res[portbindings.VIF_DETAILS].update({ portbindings.VHOST_USER_SOCKET: utils.get_vhu_sockpath( cfg.CONF.df.vhost_sock_dir, port_res['id'] ) }) def _setup_dhcp(self): """Initialize components to support DHCP.""" if cfg.CONF.df.use_centralized_ipv6_DHCP: self.network_scheduler = importutils.import_object( cfg.CONF.network_scheduler_driver ) self.start_periodic_dhcp_agent_status_check() def _setup_rpc(self): self.endpoints = [l3_rpc.L3RpcCallback(), agents_db.AgentExtRpcCallback(), metadata_rpc.MetadataRpcCallback()] if cfg.CONF.df.use_centralized_ipv6_DHCP: self.endpoints.append(dhcp_rpc.DhcpRpcCallback()) def _start_rpc_notifiers(self): """Initialize RPC notifiers for agents.""" if cfg.CONF.df.use_centralized_ipv6_DHCP: self.agent_notifiers[const.AGENT_TYPE_DHCP] = ( dhcp_rpc_agent_api.DhcpAgentNotifyAPI() ) self.agent_notifiers[const.AGENT_TYPE_L3] = ( l3_rpc_agent_api.L3AgentNotifyAPI() ) def start_rpc_listeners(self): self._setup_rpc() self.conn = n_rpc.create_connection() self.conn.create_consumer(topics.PLUGIN, self.endpoints, fanout=False) self.conn.create_consumer(topics.L3PLUGIN, self.endpoints, fanout=False) # topics.REPORTS was added for the Mitaka release, therefore, to # work with stable/liberty, check to see if topics.REPORTS exists # if it does, use it. if hasattr(topics, 'REPORTS'): self.conn.create_consumer( topics.REPORTS, [agents_db.AgentExtRpcCallback()], fanout=False) return self.conn.consume_in_threads() def _delete_ports(self, context, ports): for port in ports: try: self.delete_port(context, port.id) except (n_exc.PortNotFound, sa_exc.ObjectDeletedError): context.session.expunge(port) # concurrent port deletion can be performed by # release_dhcp_port caused by concurrent subnet_delete LOG.info(_LI("Port %s was deleted concurrently"), port.id) except Exception: with excutils.save_and_reraise_exception(): LOG.exception(_LE("Exception auto-deleting port %s"), port.id) @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def create_security_group(self, context, security_group, default_sg=False): with context.session.begin(subtransactions=True): sg_db = super(DFPlugin, self).create_security_group(context, security_group, default_sg) sg_version = version_db._create_db_version_row( context.session, sg_db['id']) sg_id = sg_db['id'] sg_name = sg_db.get('name', df_const.DF_SG_DEFAULT_NAME) tenant_id = sg_db['tenant_id'] rules = sg_db.get('security_group_rules') for rule in rules: rule['topic'] = rule.get('tenant_id') del rule['tenant_id'] self.nb_api.create_security_group(id=sg_id, topic=tenant_id, name=sg_name, rules=rules, version=sg_version) return sg_db @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def update_security_group(self, context, sg_id, security_group): with context.session.begin(subtransactions=True): sg_db = super(DFPlugin, self).update_security_group(context, sg_id, security_group) sg_version = version_db._update_db_version_row( context.session, sg_id) sg_name = sg_db.get('name', df_const.DF_SG_DEFAULT_NAME) tenant_id = sg_db['tenant_id'] rules = sg_db.get('security_group_rules') self.nb_api.update_security_group(id=sg_id, topic=tenant_id, name=sg_name, rules=rules, version=sg_version) return sg_db @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def create_security_group_rule(self, context, security_group_rule): with context.session.begin(subtransactions=True): sg_rule = super(DFPlugin, self).create_security_group_rule( context, security_group_rule) sg_id = sg_rule['security_group_id'] sg_version_id = version_db._update_db_version_row( context.session, sg_id) sg_group = self.get_security_group(context, sg_id) sg_rule['topic'] = sg_rule.get('tenant_id') del sg_rule['tenant_id'] self.nb_api.add_security_group_rules(sg_id, sg_group['tenant_id'], sg_rules=[sg_rule], sg_version=sg_version_id) return sg_rule @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def delete_security_group_rule(self, context, id): with context.session.begin(subtransactions=True): security_group_rule = self.get_security_group_rule(context, id) sg_id = security_group_rule['security_group_id'] sg_group = self.get_security_group(context, sg_id) super(DFPlugin, self).delete_security_group_rule(context, id) sg_version_id = version_db._update_db_version_row( context.session, sg_id) self.nb_api.delete_security_group_rule(sg_id, id, sg_group['tenant_id'], sg_version=sg_version_id) @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def delete_security_group(self, context, sg_id): sg = self.get_security_group(context, sg_id) tenant_id = sg['tenant_id'] with context.session.begin(subtransactions=True): super(DFPlugin, self).delete_security_group(context, sg_id) version_db._delete_db_version_row( context.session, sg_id) self.nb_api.delete_security_group(sg_id, topic=tenant_id) @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def create_subnet(self, context, subnet): net_id = subnet['subnet']['network_id'] new_subnet = None dhcp_ip = None dhcp_port = None network_version = None try: with context.session.begin(subtransactions=True): # create subnet in DB new_subnet = super(DFPlugin, self).create_subnet(context, subnet) dhcp_ip, dhcp_port = self._handle_create_subnet_dhcp( context, new_subnet) network_version = version_db._update_db_version_row( context.session, net_id) except Exception: with excutils.save_and_reraise_exception() as ctxt: ctxt.reraise = True # delete the stale dhcp port try: if dhcp_port: self.nb_api.delete_lport(dhcp_port['id'], dhcp_port['tenant_id']) except df_exceptions.DBKeyNotFound: pass if new_subnet: self.nb_api.add_subnet( new_subnet['id'], net_id, new_subnet['tenant_id'], name=new_subnet.get('name', df_const.DF_SUBNET_DEFAULT_NAME), nw_version=network_version, enable_dhcp=new_subnet['enable_dhcp'], cidr=new_subnet['cidr'], dhcp_ip=dhcp_ip, gateway_ip=new_subnet['gateway_ip'], dns_nameservers=new_subnet.get('dns_nameservers', []), host_routes=new_subnet.get('host_routes', [])) return new_subnet @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def update_subnet(self, context, id, subnet): dhcp_ip = None dhcp_port = None new_subnet = None net_id = None network_version = None try: with context.session.begin(subtransactions=True): # update subnet in DB original_subnet = super(DFPlugin, self).get_subnet(context, id) new_subnet = super(DFPlugin, self).update_subnet(context, id, subnet) net_id = new_subnet['network_id'] dhcp_ip, dhcp_port = self._update_subnet_dhcp( context, original_subnet, new_subnet) network_version = version_db._update_db_version_row( context.session, net_id) except Exception: with excutils.save_and_reraise_exception() as ctxt: ctxt.reraise = True # delete the stale dhcp port try: if dhcp_port: self.nb_api.delete_lport(dhcp_port['id'], dhcp_port['tenant_id']) except df_exceptions.DBKeyNotFound: pass if new_subnet and net_id: # update df controller with subnet self.nb_api.update_subnet( new_subnet['id'], net_id, new_subnet['tenant_id'], name=new_subnet.get('name', df_const.DF_SUBNET_DEFAULT_NAME), nw_version=network_version, enable_dhcp=new_subnet['enable_dhcp'], cidr=new_subnet['cidr'], dhcp_ip=dhcp_ip, gateway_ip=new_subnet['gateway_ip'], dns_nameservers=new_subnet.get('dns_nameservers', []), host_routes=new_subnet.get('host_routes', [])) return new_subnet @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def delete_subnet(self, context, id): orig_subnet = super(DFPlugin, self).get_subnet(context, id) net_id = orig_subnet['network_id'] with context.session.begin(subtransactions=True): # delete subnet in DB super(DFPlugin, self).delete_subnet(context, id) network_version = version_db._update_db_version_row( context.session, net_id) # update df controller with subnet delete if net_id: try: self.nb_api.delete_subnet(id, net_id, orig_subnet['tenant_id'], nw_version=network_version) except df_exceptions.DBKeyNotFound: LOG.debug("network %s is not found in DB, might have " "been deleted concurrently" % net_id) @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def create_network(self, context, network): with context.session.begin(subtransactions=True): result = super(DFPlugin, self).create_network(context, network) data = network['network'] if psec.PORTSECURITY not in data: data[psec.PORTSECURITY] = \ (psec.EXTENDED_ATTRIBUTES_2_0['networks'] [psec.PORTSECURITY]['default']) self._process_network_port_security_create(context, data, result) self._process_l3_create(context, result, data) nw_version = version_db._create_db_version_row( context.session, result['id']) self._create_network_nb_api(context, result, nw_version) return result def _create_network_nb_api(self, context, network, nw_version): nw_name = network.get('name', df_const.DF_NETWORK_DEFAULT_NAME) self.nb_api.create_lswitch(id=network['id'], topic=network['tenant_id'], name=nw_name, router_external=network['router:external'], mtu=network['mtu'], version=nw_version, subnets=[]) return network @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def delete_network(self, context, network_id): dhcp_ports = [] with context.session.begin(subtransactions=True): network = self.get_network(context, network_id) tenant_id = network['tenant_id'] try: lswitch = self.nb_api.get_lswitch(id=network_id, topic=tenant_id) except df_exceptions.DBKeyNotFound: LOG.debug("lswitch %s is not found in DF DB, might have " "been deleted concurrently" % network_id) if lswitch is not None: subnets = [subnet.get_id() for subnet in lswitch.get_subnets()] dhcp_ports = self._get_ports_by_subnets_and_owners( context, subnets, [const.DEVICE_OWNER_DHCP]) super(DFPlugin, self).delete_network(context, network_id) version_db._delete_db_version_row(context.session, network_id) for port in dhcp_ports: try: self.nb_api.delete_lport(id=port['id'], topic=port['tenant_id']) except df_exceptions.DBKeyNotFound: LOG.debug("port %s is not found in DB, might have" "been deleted concurrently" % port['id']) try: self.nb_api.delete_lswitch(id=network_id, topic=tenant_id) except df_exceptions.DBKeyNotFound: LOG.debug("lswitch %s is not found in DF DB, might have " "been deleted concurrently" % network_id) @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def update_network(self, context, network_id, network): pnet._raise_if_updates_provider_attributes(network['network']) with context.session.begin(subtransactions=True): result = super(DFPlugin, self).update_network(context, network_id, network) if psec.PORTSECURITY in network['network']: self._process_network_port_security_update(context, network['network'], result) self._process_l3_update(context, result, network['network']) network_version = version_db._update_db_version_row( context.session, network_id) self.nb_api.update_lswitch(id=network_id, topic=result['tenant_id'], name=result.get( 'name', df_const.DF_NETWORK_DEFAULT_NAME), router_external=result['router:external'], version=network_version) return result @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def update_port(self, context, id, port): with context.session.begin(subtransactions=True): parent_name, tag = self._get_data_from_binding_profile( context, port['port']) original_port = self.get_port(context, id) updated_port = super(DFPlugin, self).update_port(context, id, port) # TODO(yuanwei): in ML2 plugin, security_groups and # allow_address_pairs configuration depend on portsec switch is # enabled. port_security_value = None if psec.PORTSECURITY in port['port']: self._process_port_port_security_update( context, port['port'], updated_port) port_security_value = updated_port[psec.PORTSECURITY] else: original_port_security = original_port.get(psec.PORTSECURITY) if original_port_security is not None: updated_port[psec.PORTSECURITY] = original_port_security port_security_value = original_port_security else: # if the port-security-enabled field was not set in the # original port, we should remain this field of the # logical port in the DF DB unchanged. port_security_value = const.ATTR_NOT_SPECIFIED self.update_security_group_on_port( context, id, port, original_port, updated_port) address_pairs_updated = False if addr_pair.ADDRESS_PAIRS in port['port']: address_pairs_updated = self.update_address_pairs_on_port( context, id, port, original_port, updated_port) if not address_pairs_updated: updated_port[addr_pair.ADDRESS_PAIRS] = original_port.get( addr_pair.ADDRESS_PAIRS, []) self._update_extra_dhcp_opts_on_port( context, id, port, updated_port=updated_port) port_version = version_db._update_db_version_row( context.session, id) setattr(context, 'GUARD_TRANSACTION', False) self._process_portbindings_create_and_update(context, port['port'], updated_port) ips = [ip['ip_address'] for ip in updated_port.get('fixed_ips', [])] subnets = [ip['subnet_id'] for ip in updated_port.get('fixed_ips', [])] chassis = None if 'binding:host_id' in updated_port: chassis = updated_port['binding:host_id'] # Router GW ports are not needed by dragonflow controller and # they currently cause error as they couldnt be mapped to # a valid ofport (or location) if updated_port.get('device_owner') == const.DEVICE_OWNER_ROUTER_GW: chassis = None updated_security_groups = updated_port.get('security_groups') if updated_security_groups == []: security_groups = None else: security_groups = updated_security_groups port_name = updated_port.get('name', df_const.DF_PORT_DEFAULT_NAME) self.nb_api.update_lport(id=updated_port['id'], topic=updated_port['tenant_id'], macs=[updated_port['mac_address']], ips=ips, subnets=subnets, name=port_name, parent_name=parent_name, tag=tag, enabled=updated_port['admin_' 'state_up'], chassis=chassis, device_owner=updated_port.get( 'device_owner', None), device_id=updated_port.get( 'device_id', None), security_groups=security_groups, port_security_enabled=port_security_value, allowed_address_pairs=updated_port[ addr_pair.ADDRESS_PAIRS], binding_profile=updated_port.get( portbindings.PROFILE, None), binding_vnic_type=updated_port.get( portbindings.VNIC_TYPE, None), version=port_version) return updated_port def _get_data_from_binding_profile(self, context, port): if (df_const.DF_PORT_BINDING_PROFILE not in port or not attr.is_attr_set( port[df_const.DF_PORT_BINDING_PROFILE])): return None, None parent_name = ( port[df_const.DF_PORT_BINDING_PROFILE].get('parent_name')) tag = port[df_const.DF_PORT_BINDING_PROFILE].get('tag') if not any((parent_name, tag)): # An empty profile is fine. return None, None if not all((parent_name, tag)): # If one is set, they both must be set. msg = _('Invalid binding:profile. parent_name and tag are ' 'both required.') raise n_exc.InvalidInput(error_message=msg) if not isinstance(parent_name, six.string_types): msg = _('Invalid binding:profile. parent_name "%s" must be ' 'a string.') % parent_name raise n_exc.InvalidInput(error_message=msg) try: tag = int(tag) if tag < 0 or tag > 4095: raise ValueError except ValueError: msg = _('Invalid binding:profile. tag "%s" must be ' 'an int between 1 and 4096, inclusive.') % tag raise n_exc.InvalidInput(error_message=msg) # Make sure we can successfully look up the port indicated by # parent_name. Just let it raise the right exception if there is a # problem. self.get_port(context, parent_name) return parent_name, tag def _determine_port_security(self, context, port): """Returns a boolean (port_security_enabled). Port_security is the value associated with the port if one is present otherwise the value associated with the network is returned. """ if port.get('device_owner') and utils.is_port_trusted(port): return False if attr.is_attr_set(port.get(psec.PORTSECURITY)): port_security_enabled = port[psec.PORTSECURITY] else: port_security_enabled = self._get_network_security_binding( context, port['network_id']) return port_security_enabled @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def create_port(self, context, port): with context.session.begin(subtransactions=True): parent_name, tag = self._get_data_from_binding_profile( context, port['port']) dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, []) db_port = super(DFPlugin, self).create_port(context, port) # TODO(yuanwei): in ML2 plugin, security_groups and # allow_address_pairs configuration depend on portsec switch is # enabled. portsec_tmp = { psec.PORTSECURITY: self._determine_port_security(context, port['port']) } self._process_port_port_security_create(context, portsec_tmp, db_port) sgids = self._get_security_groups_on_port(context, port) self._process_port_create_security_group(context, db_port, sgids) self._process_portbindings_create_and_update(context, port['port'], db_port) self._update_port_binding(db_port) if (df_const.DF_PORT_BINDING_PROFILE in port['port'] and attr.is_attr_set( port['port'][df_const.DF_PORT_BINDING_PROFILE])): db_port[df_const.DF_PORT_BINDING_PROFILE] = ( port['port'][df_const.DF_PORT_BINDING_PROFILE]) db_port[addr_pair.ADDRESS_PAIRS] = ( self._process_create_allowed_address_pairs( context, db_port, port['port'].get(addr_pair.ADDRESS_PAIRS))) self._process_port_create_extra_dhcp_opts(context, db_port, dhcp_opts) port_version = version_db._create_db_version_row( context.session, db_port['id']) # This extra lookup is necessary to get the latest db model # for the extension functions. port_model = self._get_port(context, db_port['id']) self._apply_dict_extend_functions('ports', db_port, port_model) return self._create_port_in_nb_api(db_port, parent_name, tag, port_version) def _create_port_in_nb_api(self, port, parent_name, tag, port_version): # The port name *must* be port['id']. It must match the iface-id set # in the Interfaces table of the Open_vSwitch database, which nova sets # to be the port ID. ips = [ip['ip_address'] for ip in port.get('fixed_ips', [])] subnets = [ip['subnet_id'] for ip in port.get('fixed_ips', [])] chassis = None if 'binding:host_id' in port: chassis = port['binding:host_id'] tunnel_key = self.nb_api.allocate_tunnel_key() # Router GW ports are not needed by dragonflow controller and # they currently cause error as they couldnt be mapped to # a valid ofport (or location) if port.get('device_owner') == const.DEVICE_OWNER_ROUTER_GW: chassis = None security_groups = port.get('security_groups') if security_groups == []: sgs = None else: sgs = security_groups self.nb_api.create_lport( id=port['id'], lswitch_id=port['network_id'], topic=port['tenant_id'], macs=[port['mac_address']], ips=ips, subnets=subnets, name=port.get('name', df_const.DF_PORT_DEFAULT_NAME), parent_name=parent_name, tag=tag, enabled=port.get('admin_state_up', None), chassis=chassis, tunnel_key=tunnel_key, version=port_version, device_id=port.get('device_id', None), device_owner=port.get('device_owner', None), security_groups=sgs, port_security_enabled=port[psec.PORTSECURITY], binding_profile=port.get('binding:profile', None), binding_vnic_type=port.get('binding:vnic_type', None), allowed_address_pairs=port[addr_pair.ADDRESS_PAIRS]) return port def _pre_delete_port(self, port, port_check): """Do some preliminary operations before deleting the port.""" LOG.debug("Deleting port %s", port['id']) if not port_check: return if port['device_owner'] in ['network:router_interface', 'network:router_gateway', 'network:floatingip']: fixed_ips = port['fixed_ips'] if fixed_ips: reason = _('has device owner %s') % port['device_owner'] raise n_exc.ServicePortInUse(port_id=port['id'], reason=reason) else: LOG.debug("Port %(port_id)s has owner %(port_owner)s, but " "no IP address, so it can be deleted", {'port_id': port['id'], 'port_owner': port['device_owner']}) @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def delete_port(self, context, port_id, l3_port_check=True): port = self.get_port(context, port_id) self._pre_delete_port(port, l3_port_check) topic = port['tenant_id'] with context.session.begin(subtransactions=True): self.disassociate_floatingips(context, port_id) super(DFPlugin, self).delete_port(context, port_id) version_db._delete_db_version_row(context.session, port_id) try: self.nb_api.delete_lport(id=port_id, topic=topic) except df_exceptions.DBKeyNotFound: LOG.debug("port %s is not found in DF DB, might have " "been deleted concurrently" % port_id) def extend_port_dict_binding(self, port_res, port_db): self._update_port_binding(port_res) super(DFPlugin, self).extend_port_dict_binding(port_res, port_db) def _create_router_db(self, context, router, tenant_id): """Create a router db object with dvr additions.""" router['distributed'] = is_distributed_router(router) with context.session.begin(subtransactions=True): router_db = super( DFPlugin, self)._create_router_db( context, router, tenant_id) self._process_extra_attr_router_create(context, router_db, router) return router_db @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def create_router(self, context, router): with context.session.begin(subtransactions=True): router = super(DFPlugin, self).create_router( context, router) router_version = version_db._create_db_version_row( context.session, router['id']) router_id = router['id'] tenant_id = router['tenant_id'] router_name = router.get('name', df_const.DF_ROUTER_DEFAULT_NAME) self.nb_api.create_lrouter(router_id, topic=tenant_id, name=router_name, version=router_version, ports=[]) return router @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def update_router(self, context, id, router): with context.session.begin(subtransactions=True): router = super(DFPlugin, self).update_router( context, id, router) router_version = version_db._update_db_version_row( context.session, id) tenant_id = router['tenant_id'] router_name = router.get('name', df_const.DF_ROUTER_DEFAULT_NAME) self.nb_api.update_lrouter(id, topic=tenant_id, name=router_name, version=router_version) return router @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def delete_router(self, context, router_id): router = self.get_router(context, router_id) with context.session.begin(subtransactions=True): ret_val = super(DFPlugin, self).delete_router(context, router_id) version_db._delete_db_version_row(context.session, router_id) try: self.nb_api.delete_lrouter(id=router_id, topic=router['tenant_id']) except df_exceptions.DBKeyNotFound: LOG.debug("router %s is not found in DF DB, might have " "been deleted concurrently" % router_id) return ret_val @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def add_router_interface(self, context, router_id, interface_info): add_by_port, add_by_sub = self._validate_interface_info( interface_info) if add_by_sub: subnet = self.get_subnet(context, interface_info['subnet_id']) port = {'port': {'tenant_id': subnet['tenant_id'], 'network_id': subnet['network_id'], 'name': '', 'admin_state_up': True, 'device_id': '', 'device_owner': l3_db.DEVICE_OWNER_ROUTER_INTF, 'mac_address': attr.ATTR_NOT_SPECIFIED, 'fixed_ips': [{'subnet_id': subnet['id'], 'ip_address': subnet['gateway_ip']}]}} port = self.create_port(context, port) elif add_by_port: port = self.get_port(context, interface_info['port_id']) subnet_id = port['fixed_ips'][0]['subnet_id'] subnet = self.get_subnet(context, subnet_id) lswitch_id = subnet['network_id'] cidr = netaddr.IPNetwork(subnet['cidr']) network = "%s/%s" % (port['fixed_ips'][0]['ip_address'], str(cidr.prefixlen)) logical_port = self.nb_api.get_logical_port(port['id'], port['tenant_id']) interface_info['port_id'] = port['id'] if 'subnet_id' in interface_info: del interface_info['subnet_id'] with context.session.begin(subtransactions=True): result = super(DFPlugin, self).add_router_interface( context, router_id, interface_info) router_version = version_db._update_db_version_row( context.session, router_id) self.nb_api.add_lrouter_port(port['id'], router_id, lswitch_id, port['tenant_id'], router_version=router_version, mac=port['mac_address'], network=network, tunnel_key=logical_port.get_tunnel_key()) return result @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def remove_router_interface(self, context, router_id, interface_info): with context.session.begin(subtransactions=True): router_port_info = super(DFPlugin, self).remove_router_interface( context, router_id, interface_info) router_version = version_db._update_db_version_row( context.session, router_id) try: self.nb_api.delete_lrouter_port(router_port_info['port_id'], router_id, router_port_info['tenant_id'], router_version=router_version) except df_exceptions.DBKeyNotFound: LOG.debug("logical router %s is not found in DF DB, " "suppressing delete_lrouter_port " "exception" % router_id) return router_port_info def _create_dhcp_server_port(self, context, subnet): """Create and return dhcp port information. If an expected failure occurs, a None port is returned. """ port = {'port': {'tenant_id': context.tenant_id, 'network_id': subnet['network_id'], 'name': '', 'binding:host_id': ( df_common_const.DRAGONFLOW_VIRTUAL_PORT), 'admin_state_up': True, 'device_id': '', 'device_owner': const.DEVICE_OWNER_DHCP, 'mac_address': attr.ATTR_NOT_SPECIFIED, 'fixed_ips': [{'subnet_id': subnet['id']}]}} port = self.create_port(context, port) return port def _get_ports_by_subnets_and_owners(self, context, subnet_ids, device_owners): """Used to get all port in a subnet by the device owner""" LOG.debug("Dragonflow : subnet_ids: %s", subnet_ids) filters = {'fixed_ips': {'subnet_id': subnet_ids}, 'device_owner': device_owners} return self.get_ports(context, filters=filters) def _get_dhcp_port_for_subnet(self, context, subnet_id): ports = self._get_ports_by_subnets_and_owners( context, [subnet_id], [const.DEVICE_OWNER_DHCP]) try: return ports[0] except IndexError: return None def _get_ip_from_port(self, port): """Get The first Ip address from the port. Returns the first fixed_ip address for a port """ if not port: return None for fixed_ip in port['fixed_ips']: if "ip_address" in fixed_ip: return fixed_ip['ip_address'] def _update_subnet_dhcp_centralized(self, context, subnet): """Update the dhcp configuration for the subnet Returns the dhcp server ip address if configured """ if subnet['enable_dhcp']: port = self._get_dhcp_port_for_subnet( context, subnet['id']) return self._get_ip_from_port(port) else: return subnet['allocation_pools'][0]['start'] def _update_subnet_dhcp(self, context, old_subnet, new_subnet): """Update the dhcp configuration for. Returns the dhcp ip if exists and optionaly value of dhcp server port if this port was created. """ dhcp_ip = None if cfg.CONF.df.use_centralized_ipv6_DHCP: dhcp_ip = self._update_subnet_dhcp_centralized(context, new_subnet) return dhcp_ip, None if old_subnet['enable_dhcp']: port = self._get_dhcp_port_for_subnet( context, old_subnet['id']) if not new_subnet['enable_dhcp']: if old_subnet['enable_dhcp']: if port: self.delete_port(context, port['id']) return None, None if new_subnet['enable_dhcp'] and not old_subnet['enable_dhcp']: port = self._create_dhcp_server_port(context, new_subnet) dhcp_ip = self._get_ip_from_port(port) return dhcp_ip, port if port: dhcp_ip = self._get_ip_from_port(port) return dhcp_ip, None def _handle_create_subnet_dhcp(self, context, subnet): """Create the dhcp configuration for the subnet if required. Returns the dhcp ip and dhcp server port (if created). """ if subnet['enable_dhcp']: if cfg.CONF.df.use_centralized_ipv6_DHCP: return subnet['allocation_pools'][0]['start'], None else: dhcp_port = self._create_dhcp_server_port(context, subnet) dhcp_ip = self._get_ip_from_port(dhcp_port) return dhcp_ip, dhcp_port return None, None def _get_floatingip_port(self, context, floatingip_id): filters = {'device_id': [floatingip_id]} floating_ports = self.get_ports(context, filters=filters) if floating_ports: return floating_ports[0] return None def _get_floatingip_subnet(self, context, subnet_id): gateway_subnet = self.get_subnet(context, subnet_id) if gateway_subnet['ip_version'] == 4: return gateway_subnet return None @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def create_floatingip(self, context, floatingip): try: floatingip_port = None with context.session.begin(subtransactions=True): floatingip_dict = super(DFPlugin, self).create_floatingip( context, floatingip, initial_status=const.FLOATINGIP_STATUS_ACTIVE) floatingip_port = self._get_floatingip_port( context, floatingip_dict['id']) if not floatingip_port: raise n_common_exc.DeviceNotFoundError( device_name=floatingip_dict['id']) subnet_id = floatingip_port['fixed_ips'][0]['subnet_id'] floatingip_subnet = self._get_floatingip_subnet( context, subnet_id) if floatingip_subnet is None: raise n_exc.SubnetNotFound(subnet_id=subnet_id) fip_version = version_db._create_db_version_row( context.session, floatingip_dict['id']) except Exception: with excutils.save_and_reraise_exception() as ctxt: ctxt.reraise = True # delete the stale floatingip port try: if floatingip_port: self.nb_api.delete_lport(floatingip_port['id'], floatingip_port['tenant_id']) except df_exceptions.DBKeyNotFound: pass self.nb_api.create_floatingip( id=floatingip_dict['id'], topic=floatingip_dict['tenant_id'], name=floatingip_dict.get('name', df_const.DF_FIP_DEFAULT_NAME), floating_ip_address=floatingip_dict['floating_ip_address'], floating_network_id=floatingip_dict['floating_network_id'], router_id=floatingip_dict['router_id'], port_id=floatingip_dict['port_id'], fixed_ip_address=floatingip_dict['fixed_ip_address'], status=floatingip_dict['status'], floating_port_id=floatingip_port['id'], floating_mac_address=floatingip_port['mac_address'], external_gateway_ip=floatingip_subnet['gateway_ip'], version=fip_version, external_cidr=floatingip_subnet['cidr']) return floatingip_dict @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def update_floatingip(self, context, id, floatingip): with context.session.begin(subtransactions=True): floatingip_dict = super(DFPlugin, self).update_floatingip( context, id, floatingip) fip_version = version_db._update_db_version_row( context.session, id) self.nb_api.update_floatingip( id=floatingip_dict['id'], topic=floatingip_dict['tenant_id'], notify=True, name=floatingip_dict.get('name', df_const.DF_FIP_DEFAULT_NAME), router_id=floatingip_dict['router_id'], port_id=floatingip_dict['port_id'], version=fip_version, fixed_ip_address=floatingip_dict['fixed_ip_address'], status=floatingip_dict['status']) return floatingip_dict @lock_db.wrap_db_lock(lock_db.RESOURCE_DF_PLUGIN) def delete_floatingip(self, context, id): with context.session.begin(subtransactions=True): floatingip = self.get_floatingip(context, id) super(DFPlugin, self).delete_floatingip(context, id) version_db._delete_db_version_row(context.session, id) try: self.nb_api.delete_floatingip(id=id, topic=floatingip['tenant_id']) except df_exceptions.DBKeyNotFound: LOG.debug("floatingip %s is not found in DF DB, might have " "been deleted concurrently" % id) def get_floatingip(self, context, id, fields=None): with context.session.begin(subtransactions=True): fip = super(DFPlugin, self).get_floatingip(context, id, fields) fip['status'] = self.nb_api.get_floatingip(id).get_status() return fip def is_distributed_router(router): """Return True if router to be handled is distributed.""" try: # See if router is a DB object first requested_router_type = router.extra_attributes.distributed except AttributeError: # if not, try to see if it is a request body requested_router_type = router.get('distributed') if attr.is_attr_set(requested_router_type): return requested_router_type return cfg.CONF.router_distributed