diff -r 0ca3f3d6c919 -r 5bd484384122 components/openstack/neutron/files/evs/plugin.py --- a/components/openstack/neutron/files/evs/plugin.py Fri Mar 20 03:13:26 2015 -0700 +++ b/components/openstack/neutron/files/evs/plugin.py Thu Mar 19 14:41:20 2015 -0700 @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 -# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved. # # 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 @@ -16,7 +16,6 @@ # # @author: Girish Moodalbail, Oracle, Inc. -import netaddr import rad.client as radcli import rad.connect as radcon import rad.bindings.com.oracle.solaris.rad.evscntl_1 as evsbind @@ -25,25 +24,27 @@ 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.v2 import attributes -from neutron.common import constants as l3_constants +from neutron.common import constants from neutron.common import exceptions -from neutron.common import rpc as q_rpc +from neutron.common import rpc as n_rpc from neutron.common import topics +from neutron.db import agents_db +from neutron.db import agentschedulers_db +from neutron.db import api as db from neutron.db import db_base_plugin_v2 -from neutron.db import dhcp_rpc_base from neutron.db import external_net_db -from neutron.db import l3_rpc_base +from neutron.db import l3_gwmode_db +from neutron.db import model_base +from neutron.db import quota_db from neutron.extensions import external_net from neutron.extensions import providernet +from neutron.openstack.common import importutils from neutron.openstack.common import lockutils from neutron.openstack.common import log as logging -from neutron.openstack.common import rpc from neutron.plugins.common import constants as svc_constants -from neutron.plugins.evs.db import api as evs_db -from neutron.plugins.evs.db import l3nat as evs_l3nat -from neutron.plugins.evs.db import quotas_db - LOG = logging.getLogger(__name__) @@ -52,44 +53,7 @@ help=_("An URI that specifies an EVS controller")) ] -evs_database_opts = [ - cfg.StrOpt('sql_connection', - default='sqlite:////var/lib/neutron/neutron.sqlite', - help=_("An URI that specifies SQL connectionr")), -] - cfg.CONF.register_opts(evs_controller_opts, "EVS") -cfg.CONF.register_opts(evs_database_opts, "DATABASE") - -# Maps OpenStack network resource attributes to EVS properties -NETWORK_EVS_ATTRIBUTE_MAP = { - 'tenant_id': 'tenant', - 'network_id': 'evs', - 'id': 'evs', - 'name': 'evs', - external_net.EXTERNAL: 'OpenStack:' + external_net.EXTERNAL, -} - -# Maps OpenStack subnet resource attributes to EVS' IPnet properties -SUBNET_IPNET_ATTRIBUTE_MAP = { - 'tenant_id': 'tenant', - 'network_id': 'evs', - 'id': 'ipnet', - 'name': 'ipnet', - 'enable_dhcp': 'OpenStack:enable_dhcp', - 'dns_nameservers': 'OpenStack:dns_nameservers', - 'host_routes': 'OpenStack:host_routes', -} - -# Maps OpenStack port resource attributes to EVS' VPort properties -PORT_VPORT_ATTRIBUTE_MAP = { - 'tenant_id': 'tenant', - 'network_id': 'evs', - 'id': 'vport', - 'name': 'vport', - 'device_id': 'OpenStack:device_id', - 'device_owner': 'OpenStack:device_owner', -} class EVSControllerError(exceptions.NeutronException): @@ -106,29 +70,14 @@ super(EVSOpNotSupported, self).__init__(opname=evs_errmsg) -class EVSRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin, - l3_rpc_base.L3RpcCallbackMixin): - RPC_API_VERSION = '1.1' - - def create_rpc_dispatcher(self): - '''Get the rpc dispatcher for this manager. - - If a manager would like to set an rpc API version, or support more than - one class as the target of rpc messages, override this method. - ''' - return q_rpc.PluginRpcDispatcher([self]) - - def report_state(self, context, **kwargs): - # TODO(gmoodalb): This method is currently no-op and is included - # here to avoid Python traceback thrown every time Neutron L3/DHCP - # agent is restarted. When we support Network Agents Information, - # we will implement this function - pass +class EVSNotFound(exceptions.NeutronException): + message = _("Network %(net_id)s could not be found in EVS") class EVSNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, + agentschedulers_db.DhcpAgentSchedulerDbMixin, external_net_db.External_net_db_mixin, - evs_l3nat.EVS_L3_NAT_db_mixin): + l3_gwmode_db.L3_NAT_db_mixin): """Implements v2 Neutron Plug-in API specification. All the neutron API calls to create/delete/retrieve Network/Subnet/Port @@ -190,23 +139,25 @@ |---------------------+------------------+------------------------------| """ - # These attribute specifies whether the plugin supports or not - # bulk/pagination/sorting operations. - __native_bulk_support = False - __native_pagination_support = False - __native_sorting_support = False - _supported_extension_aliases = ["provider", "external-net", "router", - "quotas"] + "ext-gw-mode", "quotas", "agent", + "dhcp_agent_scheduler"] def __init__(self): - # Since EVS Framework does not support router and floatingip - # resources, the plugin itself will maintain a DB for these - # two resources - evs_db.configure_db() + engine = db.get_engine() + model_base.BASEV2.metadata.create_all(engine) + + self.network_scheduler = importutils.import_object( + cfg.CONF.network_scheduler_driver + ) - # Since there is no connect_uri() yet, we need to do this ourselves - # parse ssh://user@hostname + self._setup_rpc() + self._rad_connection = None + + @property + def rad_connection(self): + # Since there is no connect_uri() yet, we need to do + # parsing of ssh://user@hostname ourselves suh = cfg.CONF.EVS.evs_controller.split('://') if len(suh) != 2 or suh[0] != 'ssh' or not suh[1].strip(): raise SystemExit(_("Specified evs_controller is invalid")) @@ -215,165 +166,89 @@ raise SystemExit(_("'user' and 'hostname' need to be specified " "for evs_controller")) - # TODO(gmoodalb): - try few times before you give up - self._rc = radcon.connect_ssh(uh[1], user=uh[0]) - self._evsc = self._rc.get_object(evsbind.EVSController()) - self._setup_rpc() + if (self._rad_connection is not None and + self._rad_connection._closed is None): + return self._rad_connection + + LOG.debug(_("Connecting to EVS Controller at %s as %s") % + (uh[1], uh[0])) + self._rad_connection = radcon.connect_ssh(uh[1], user=uh[0]) + return self._rad_connection def _setup_rpc(self): # RPC support self.service_topics = {svc_constants.CORE: topics.PLUGIN, svc_constants.L3_ROUTER_NAT: topics.L3PLUGIN} - self.conn = rpc.create_connection(new=True) - self.callbacks = EVSRpcCallbacks() - self.dispatcher = self.callbacks.create_rpc_dispatcher() + self.conn = n_rpc.create_connection(new=True) + self.endpoints = [dhcp_rpc.DhcpRpcCallback(), + l3_rpc.L3RpcCallback(), + agents_db.AgentExtRpcCallback()] for svc_topic in self.service_topics.values(): - self.conn.create_consumer(svc_topic, self.dispatcher, fanout=False) + self.conn.create_consumer(svc_topic, self.endpoints, fanout=False) # Consume from all consumers in a thread - self.conn.consume_in_thread() + self.conn.consume_in_threads() self.dhcp_agent_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI() + self.l3_agent_notifier = l3_rpc_agent_api.L3AgentNotifyAPI() + + # needed by AgentSchedulerDbMixin() + self.agent_notifiers[constants.AGENT_TYPE_DHCP] = \ + self.dhcp_agent_notifier + self.agent_notifiers[constants.AGENT_TYPE_L3] = \ + self.l3_agent_notifier @property def supported_extension_aliases(self): return self._supported_extension_aliases - def _convert_evs_to_network(self, evs): - """Converts an EVS structure into Neutron Network structure.""" - - networkdict = dict() - networkdict['name'] = evs.name - networkdict['id'] = evs.uuid - networkdict['subnets'] = ([ipnet.uuid for ipnet in evs.ipnets] - if evs.ipnets else []) - networkdict['tenant_id'] = evs.tenantname - networkdict[external_net.EXTERNAL] = False - for prop in evs.props: - if prop.name == 'l2-type': - networkdict[providernet.NETWORK_TYPE] = prop.value - elif prop.name == 'vlanid' or prop.name == 'vni': - networkdict[providernet.SEGMENTATION_ID] = int(prop.value) - elif prop.name == NETWORK_EVS_ATTRIBUTE_MAP[external_net.EXTERNAL]: - networkdict[external_net.EXTERNAL] = \ - (True if prop.value == 'True' else False) - # fixed values as EVS framework doesn't support this - networkdict['admin_state_up'] = True - networkdict['status'] = 'ACTIVE' - networkdict['shared'] = False - - return networkdict - - def _convert_ipnet_to_subnet(self, ipnet): - """Converts an EVS IPnet structure into Neutron Subnet structure.""" - - subnetdict = dict() - subnetdict['name'] = ipnet.name - subnetdict['network_id'] = ipnet.evsuuid - subnetdict['id'] = ipnet.uuid - subnetdict['tenant_id'] = ipnet.tenantname - subnetdict['ip_version'] = \ - (4 if ipnet.ipvers == evsbind.IPVersion.IPV4 else 6) - # assign default values to some subnet attributes - subnetdict['dns_nameservers'] = [] - subnetdict['host_routes'] = [] - subnetdict['enable_dhcp'] = False - for prop in ipnet.props: - if prop.name == 'defrouter': - subnetdict['gateway_ip'] = prop.value - elif prop.name == 'subnet': - subnetdict['cidr'] = prop.value - elif prop.name == SUBNET_IPNET_ATTRIBUTE_MAP['enable_dhcp']: - subnetdict['enable_dhcp'] = \ - (True if prop.value == 'True' else False) - elif prop.name == SUBNET_IPNET_ATTRIBUTE_MAP['dns_nameservers']: - subnetdict['dns_nameservers'] = prop.value.split(',') - elif prop.name == SUBNET_IPNET_ATTRIBUTE_MAP['host_routes']: - hrlist = [] - vlist = prop.value.split(',') - for i in range(0, len(vlist), 2): - hrlist.append({vlist[i]: vlist[i + 1]}) - subnetdict['host_routes'] = hrlist - elif prop.name == 'pool': - poollist = [] - for pool in prop.value.split(','): - if '-' not in pool: - start = end = pool - else: - start, end = pool.split('-') - poollist.append(dict(start=start, end=end)) - subnetdict['allocation_pools'] = poollist - subnetdict['shared'] = False + @lockutils.synchronized('evs-plugin', 'neutron-') + def _evs_controller_addIPnet(self, tenantname, evsname, ipnetname, + propstr): + LOG.debug(_("Adding IPnet: %s with properties: %s for tenant: %s " + "and for evs: %s") % + (ipnetname, propstr, tenantname, evsname)) - return subnetdict - - def _convert_vport_to_port(self, context, vport): - """Converts an EVS VPort structure into Neutron port structure.""" - - portdict = dict() - portdict['admin_state_up'] = True - portdict['id'] = vport.uuid - portdict['name'] = vport.name - portdict['network_id'] = vport.evsuuid - # TODO(gmoodalb): set to host/zonename/vnicname? - portdict['device_id'] = '' - portdict['device_owner'] = '' - for prop in vport.props: - if (prop.name == 'macaddr'): - portdict['mac_address'] = prop.value - elif (prop.name == 'ipaddr'): - evs = self.get_network(context, vport.evsuuid) - portdict['fixed_ips'] = \ - [{ - 'ip_address': prop.value.split('/')[0], - 'subnet_id': evs['subnets'][0], - }] - elif (prop.name == 'OpenStack:device_id'): - portdict['device_id'] = prop.value - elif (prop.name == 'OpenStack:device_owner'): - portdict['device_owner'] = prop.value - portdict['security_groups'] = [] - portdict['status'] = 'ACTIVE' - portdict['tenant_id'] = vport.tenantname - - return portdict - - def _apply_rsrc_props_filter(self, rsrclist, filters): - # if all of the filter values are None, then return - if all([value is None for value in filters.values()]): - return - - rsrc_to_remove = [] - for rsrc in rsrclist: - propdict = dict((prop.name, prop.value) for prop in rsrc.props) - for key, value in filters.iteritems(): - if value is None: - continue - if key not in propdict: - rsrc_to_remove.append(rsrc) - break - elif isinstance(value, list): - strlist = [str(v) for v in value] - if propdict[key] not in strlist: - rsrc_to_remove.append(rsrc) - break - # TODO(gmoodalb): - check if it's an instance of basestring? - elif propdict[key] != str(value): - rsrc_to_remove.append(rsrc) - break - - for rsrc in rsrc_to_remove: - rsrclist.remove(rsrc) - - @lockutils.synchronized('evs-plugin', 'neutron-') - def evs_controller_addIPnet(self, tenantname, evsname, ipnetname, propstr): + pat = radcli.ADRGlobPattern({'name': evsname, + 'tenant': tenantname}) try: - pat = radcli.ADRGlobPattern( - {'name': evsname, 'tenant': tenantname}) - evs = self._rc.get_object(evsbind.EVS(), pat) + evs = self.rad_connection.get_object(evsbind.EVS(), pat) ipnet = evs.addIPnet(propstr, ipnetname) except radcli.ObjectError as oe: raise EVSControllerError(oe.get_payload().errmsg) return ipnet + @lockutils.synchronized('evs-plugin', 'neutron-') + def _evs_controller_updateIPnet(self, ipnetuuid, propstr): + LOG.debug(_("Updating IPnet with id: %s with property string: %s") % + (ipnetuuid, propstr)) + pat = radcli.ADRGlobPattern({'uuid': ipnetuuid}) + try: + ipnetlist = self.rad_connection.list_objects(evsbind.IPnet(), pat) + if not ipnetlist: + return + assert len(ipnetlist) == 1 + ipnet = self.rad_connection.get_object(ipnetlist[0]) + ipnet.setProperty(propstr) + except radcli.ObjectError as oe: + raise EVSControllerError(oe.get_payload().errmsg) + + def _subnet_pool_to_evs_pool(self, subnet): + poolstr = "" + # obtain the optional allocation pool + pools = subnet.get('allocation_pools') + if not pools or pools is attributes.ATTR_NOT_SPECIFIED: + return poolstr + + for pool in pools: + if poolstr: + poolstr += "," + # if start and end address is same, EVS expects the address + # to be provided as-is instead of x.x.x.x-x.x.x.x + if pool['start'] == pool['end']: + poolstr += pool['start'] + else: + poolstr += "%s-%s" % (pool['start'], pool['end']) + return poolstr + def create_subnet(self, context, subnet): """Creates a subnet(IPnet) for a given network(EVS). @@ -383,93 +258,66 @@ connect to the EVS, through a VPort, will get an IP address from the IPnet associated with the EVS. """ - ipnetname = subnet['subnet']['name'] - if not ipnetname: - ipnetname = None - proplist = ['subnet=%s' % (subnet['subnet']['cidr'])] - - # obtain the optional default router - defrouter = subnet['subnet']['gateway_ip'] - if defrouter is None: - # user specified --no-gateway and we don't support it - raise EVSOpNotSupported(_("cannot use --no-gateway")) - - if defrouter is not attributes.ATTR_NOT_SPECIFIED: - proplist.append('defrouter=%s' % (defrouter)) + # TODO(gmoodalb): Take care of this now that we have pool. + # Even though EVS does not support allocation pools, it is OK for an + # user to specify --allocation-pool because allocation pool management + # is done by neutron-server and is transparent to EVS framework. - # obtain the optional allocation pool - pools = subnet['subnet']['allocation_pools'] - if pools is not attributes.ATTR_NOT_SPECIFIED: - poolstr = "" - for pool in pools: - if poolstr: - poolstr += "," - # if start and end address is same, EVS expects the address - # to be provided as-is instead of x.x.x.x-x.x.x.x - if pool['start'] == pool['end']: - poolstr += pool['start'] - else: - poolstr += "%s-%s" % (pool['start'], pool['end']) - proplist.append('pool=%s' % (poolstr)) + # user specified --no-gateway, and we don't support it + if subnet['subnet']['gateway_ip'] is None: + raise EVSOpNotSupported(_("setting --no-gateway for a subnet " + "not supported")) + if (subnet['subnet']['host_routes'] is not + attributes.ATTR_NOT_SPECIFIED): + raise EVSOpNotSupported(_("setting --host-route for a subnet " + "not supported")) + + poolstr = self._subnet_pool_to_evs_pool(subnet['subnet']) - # obtain the optional DNS nameservers - nameservers = subnet['subnet']['dns_nameservers'] - if attributes.is_attr_set(nameservers): - proplist.append('%s=%s' % - (SUBNET_IPNET_ATTRIBUTE_MAP['dns_nameservers'], - ','.join(nameservers))) - - # obtain the host routes - hostroutes = subnet['subnet']['host_routes'] - if attributes.is_attr_set(hostroutes): - hrlist = ['%s,%s' % (destination, nexthop) - for destination, nexthop in hostroutes] - proplist.append('%s=%s' % - (SUBNET_IPNET_ATTRIBUTE_MAP['host_routes'], - ",".join(hrlist))) - - enable_dhcp = subnet['subnet']['enable_dhcp'] - proplist.append('%s=%s' % - (SUBNET_IPNET_ATTRIBUTE_MAP['enable_dhcp'], - enable_dhcp)) - - propstr = None - if proplist: - propstr = ",".join(proplist) - - evsname = subnet['subnet']['network_id'] - tenantname = self._get_tenant_id_for_create(context, subnet['subnet']) - ipnet = self.evs_controller_addIPnet(tenantname, evsname, ipnetname, - propstr) - retval = self._convert_ipnet_to_subnet(ipnet) + with context.session.begin(subtransactions=True): + # create the subnet in the DB + db_subnet = super(EVSNeutronPluginV2, self).create_subnet(context, + subnet) + ipnetname = db_subnet['name'] + if not ipnetname: + ipnetname = None + evsname = db_subnet['network_id'] + tenantname = db_subnet['tenant_id'] + proplist = ['subnet=%s' % db_subnet['cidr']] + proplist.append('defrouter=%s' % db_subnet['gateway_ip']) + proplist.append('uuid=%s' % db_subnet['id']) + if poolstr: + proplist.append('pool=%s' % (poolstr)) + self._evs_controller_addIPnet(tenantname, evsname, ipnetname, + ",".join(proplist)) # notify dhcp agent of subnet creation - self.dhcp_agent_notifier.notify(context, {'subnet': retval}, + self.dhcp_agent_notifier.notify(context, {'subnet': db_subnet}, 'subnet.create.end') - return retval - - @lockutils.synchronized('evs-plugin', 'neutron-') - def evs_controller_updateIPnet(self, ipnetuuid, propstr): - try: - pat = radcli.ADRGlobPattern({'uuid': ipnetuuid}) - ipnetlist = self._rc.list_objects(evsbind.IPnet(), pat) - assert len(ipnetlist) == 1 - ipnet = self._rc.get_object(ipnetlist[0]) - ipnet.setProperty(propstr) - except radcli.ObjectError as oe: - raise EVSControllerError(oe.get_payload().errmsg) + return db_subnet def update_subnet(self, context, id, subnet): + LOG.debug(_("Updating Subnet: %s with %s") % (id, subnet)) evs_rpccall_sync = subnet.pop('evs_rpccall_sync', False) - if not (set(subnet['subnet'].keys()) == set(('enable_dhcp',))): - raise EVSOpNotSupported(_("only subnets with enable_dhcp " - "set can be updated")) + if (set(subnet['subnet'].keys()) - set(('enable_dhcp', + 'allocation_pools', + 'ipv6_address_mode', + 'ipv6_ra_mode'))): + raise EVSOpNotSupported(_("only following subnet attributes " + "enable-dhcp, allocation-pool, " + "ipv6-address-mode, and " + "ipv6-ra-mode can be updated")) - propstr = "%s=%s" % (SUBNET_IPNET_ATTRIBUTE_MAP['enable_dhcp'], - subnet['subnet']['enable_dhcp']) - self.evs_controller_updateIPnet(id, propstr) - retval = self.get_subnet(context, id) + poolstr = self._subnet_pool_to_evs_pool(subnet['subnet']) + + with context.session.begin(subtransactions=True): + # update subnet in DB + retval = super(EVSNeutronPluginV2, self).\ + update_subnet(context, id, subnet) + # update EVS IPnet with allocation pool info + if poolstr: + self._evs_controller_updateIPnet(id, "pool=%s" % poolstr) # notify dhcp agent of subnet update methodname = 'subnet.update.end' @@ -483,77 +331,21 @@ topic=topics.DHCP_AGENT) return retval - @lockutils.synchronized('evs-plugin', 'neutron-') - def evs_controller_getIPnet(self, ipnetuuid): - try: - ipnetlist = self._evsc.getIPnetInfo('ipnet=%s' % (ipnetuuid)) - except radcli.ObjectError as oe: - raise EVSControllerError(oe.get_payload().errmsg) - return (ipnetlist[0] if ipnetlist else None) - def get_subnet(self, context, id, fields=None): - ipnet = self.evs_controller_getIPnet(id) - if not ipnet: - return {} - subnetdict = self._convert_ipnet_to_subnet(ipnet) - return self._fields(subnetdict, fields) - - @lockutils.synchronized('evs-plugin', 'neutron-') - def evs_controller_getIPnetList(self, filterstr): - try: - ipnetlist = self._evsc.getIPnetInfo(filterstr) - except radcli.ObjectError as oe: - raise EVSControllerError(oe.get_payload().errmsg) - return ipnetlist + LOG.debug(_("Getting subnet: %s"), id) + subnet = super(EVSNeutronPluginV2, self).get_subnet(context, id, None) + return self._fields(subnet, fields) def get_subnets(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): - - filterstr = None - # EVS desn't support filtering of resource based on - # properties, so we will have to filter ourselves - ipnet_props = {'OpenStack:enable_dhcp': None} - if filters is not None: - filterlist = [] - for key, value in filters.items(): - if key == 'shared': - if any(value): - return [] - continue - if key == 'verbose': - continue - if key == 'enable_dhcp': - ipnet_props[SUBNET_IPNET_ATTRIBUTE_MAP[key]] = value - continue - key = SUBNET_IPNET_ATTRIBUTE_MAP.get(key, key) - if isinstance(value, list): - value = ",".join(map(str, set(value))) - if not value: - continue - filterlist.append("%s=%s" % (key, value)) - - if filterlist: - filterstr = ",".join(filterlist) - - LOG.debug(_("calling ListIPnet from get_subnets() filterstr: '%s'") - % (filterstr)) - - ipnetlist = self.evs_controller_getIPnetList(filterstr) - self._apply_rsrc_props_filter(ipnetlist, ipnet_props) - - retme = [] - for ipnet in ipnetlist: - subnetdict = self._convert_ipnet_to_subnet(ipnet) - retme.append(self._fields(subnetdict, fields)) - - return retme - - def get_subnets_count(self, context, filters=None): - return len(self.get_subnets(context, filters)) + subnets = super(EVSNeutronPluginV2, self).\ + get_subnets(context, filters, None, sorts, limit, marker, + page_reverse) + return [self._fields(subnet, fields) for subnet in subnets] def _release_subnet_dhcp_port(self, context, subnet, delete_network): """Release any dhcp port associated with the subnet""" - filters = dict(evs=subnet['network_id']) + filters = dict(network_id=[subnet['network_id']]) portlist = self.get_ports(context, filters) if delete_network: @@ -567,6 +359,16 @@ # port happens to be a DHCP port. update_subnet = len(portlist) == 1 if update_subnet: + # For IPv6 we need to first reset the IPv6 attributes + if subnet['ip_version'] == 6: + if (attributes.is_attr_set(subnet.get('ipv6_address_mode'))): + subnet_update = {'subnet': + {'ipv6_address_mode': None, + 'ipv6_ra_mode': None + }, + 'evs_rpccall_sync': True + } + self.update_subnet(context, subnet['id'], subnet_update) # the lone port is a dhcp port created by dhcp agent # it must be released before we can delete the subnet subnet_update = {'subnet': {'enable_dhcp': False}, @@ -574,12 +376,20 @@ self.update_subnet(context, subnet['id'], subnet_update) @lockutils.synchronized('evs-plugin', 'neutron-') - def evs_controller_removeIPnet(self, tenantname, evsname, ipnetuuid): + def _evs_controller_removeIPnet(self, tenantname, evsname, ipnetuuid): + LOG.debug(_("Removing IPnet with id: %s for tenant: %s for evs: %s") % + (ipnetuuid, tenantname, evsname)) pat = radcli.ADRGlobPattern({'name': evsname, 'tenant': tenantname}) try: - evs = self._rc.get_object(evsbind.EVS(), pat) + evs = self.rad_connection.get_object(evsbind.EVS(), pat) evs.removeIPnet(ipnetuuid) except radcli.ObjectError as oe: + # '42' corresponds to EVS' EVS_ENOENT_IPNET error code + if oe.get_payload().err == 42: + # EVS doesn't have that IPnet, return success to delete + # the IPnet from Neutron DB. + LOG.debug(_("IPnet could not be found in EVS.")) + return raise EVSControllerError(oe.get_payload().errmsg) def delete_subnet(self, context, id): @@ -596,8 +406,11 @@ # results in DHCP agent releasing the port. if subnet['enable_dhcp']: self._release_subnet_dhcp_port(context, subnet, False) - self.evs_controller_removeIPnet(subnet['tenant_id'], - subnet['network_id'], id) + with context.session.begin(subtransactions=True): + # delete subnet in DB + super(EVSNeutronPluginV2, self).delete_subnet(context, id) + self._evs_controller_removeIPnet(subnet['tenant_id'], + subnet['network_id'], id) # notify dhcp agent payload = { @@ -609,13 +422,24 @@ self.dhcp_agent_notifier.notify(context, payload, 'subnet.delete.end') @lockutils.synchronized('evs-plugin', 'neutron-') - def evs_controller_createEVS(self, tenantname, evsname, propstr): + def _evs_controller_createEVS(self, tenantname, evsname, propstr): + LOG.debug(_("Adding EVS: %s with properties: %s for tenant: %s") % + (evsname, propstr, tenantname)) try: - evs = self._evsc.createEVS(propstr, tenantname, evsname) + evs = self.rad_connection.\ + get_object(evsbind.EVSController()).\ + createEVS(propstr, tenantname, evsname) except radcli.ObjectError as oe: raise EVSControllerError(oe.get_payload().errmsg) return evs + def _extend_network_dict(self, network, evs): + for prop in evs.props: + if prop.name == 'l2-type': + network[providernet.NETWORK_TYPE] = prop.value + elif prop.name == 'vlanid' or prop.name == 'vni': + network[providernet.SEGMENTATION_ID] = int(prop.value) + def create_network(self, context, network): """Creates a network(EVS) for a given tenant. @@ -626,6 +450,15 @@ Machines connected to it. There are two main resources associated with an EVS: IPnet and VPort. """ + + if network['network']['admin_state_up'] is False: + raise EVSOpNotSupported(_("setting admin_state_up=False for a " + "network not supported")) + + if network['network']['shared'] is True: + raise EVSOpNotSupported(_("setting shared=True for a " + "network not supported")) + evsname = network['network']['name'] if not evsname: evsname = None @@ -654,125 +487,125 @@ "provider:network_type '%s' not " "supported") % network_type) - router_external = network['network'][external_net.EXTERNAL] - if attributes.is_attr_set(router_external): - proplist.append("%s=%s" % - (NETWORK_EVS_ATTRIBUTE_MAP[external_net.EXTERNAL], - router_external)) - propstr = None if proplist: propstr = ",".join(proplist) - evs = self.evs_controller_createEVS(tenantname, evsname, propstr) - return self._convert_evs_to_network(evs) + with context.session.begin(subtransactions=True): + # create the network in DB + net = super(EVSNeutronPluginV2, self).create_network(context, + network) + self._process_l3_create(context, net, network['network']) + # if --router:external is not set, the above function does + # not update net with router:external set to False + if net.get(external_net.EXTERNAL) is None: + net[external_net.EXTERNAL] = False + + # create EVS on the EVS controller + if propstr: + propstr += ",uuid=%s" % net['id'] + else: + propstr = "uuid=%s" % net['id'] + evs = self._evs_controller_createEVS(tenantname, evsname, propstr) + + # add provider information into net + self._extend_network_dict(net, evs) + + return net def update_network(self, context, id, network): raise EVSOpNotSupported(_("net-update")) @lockutils.synchronized('evs-plugin', 'neutron-') - def evs_controller_getEVS(self, evsuuid): + def _evs_controller_getEVS(self, evsuuid): + LOG.debug(_("Getting EVS: %s"), evsuuid) try: - evslist = self._evsc.getEVSInfo('evs=%s' % evsuuid) + evslist = self.rad_connection.\ + get_object(evsbind.EVSController()).\ + getEVSInfo('evs=%s' % evsuuid) except radcli.ObjectError as oe: raise EVSControllerError(oe.getpayload().errmsg) - return (evslist[0] if evslist else None) + if not evslist: + LOG.error(_("EVS framework does not have Neutron network " + "'%s' defined"), evsuuid) + return None + return evslist[0] def get_network(self, context, id, fields=None): - evs = self.evs_controller_getEVS(id) - if not evs: - return {} - networkdict = self._convert_evs_to_network(evs) - return self._fields(networkdict, fields) - - @lockutils.synchronized('evs-plugin', 'neutron-') - def evs_controller_getEVSList(self, filterstr): - try: - evslist = self._evsc.getEVSInfo(filterstr) - except radcli.ObjectError as oe: - raise EVSControllerError(oe.get_payload().errmsg) - return evslist + with context.session.begin(subtransactions=True): + net = super(EVSNeutronPluginV2, self).get_network(context, + id, None) + # call EVS controller to get provider network information + evs = self._evs_controller_getEVS(net['id']) + if evs: + self._extend_network_dict(net, evs) + return self._fields(net, fields) def get_networks(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): - filterstr = None - # EVS desn't support filtering of resource based on - # properties, so we will have to filter ourselves - evs_props = {'OpenStack:router:external': None} - if filters is not None: - filterlist = [] - for key, value in filters.items(): - if key == 'shared': - if any(value): - # EVS doesn't support shared networks - return [] - continue - if key in ('admin_state_up', 'verbose'): - continue - if key == 'router:external': - evs_props[NETWORK_EVS_ATTRIBUTE_MAP[key]] = value - continue - key = NETWORK_EVS_ATTRIBUTE_MAP.get(key, key) - if isinstance(value, list): - value = ",".join(map(str, set(value))) - if not value: - continue - filterlist.append("%s=%s" % (key, value)) - - if filterlist: - filterstr = ",".join(filterlist) - - LOG.debug(_("calling ListEVswitch from get_networks(): '%s'") - % (filterstr)) - evslist = self.evs_controller_getEVSList(filterstr) - self._apply_rsrc_props_filter(evslist, evs_props) - - retme = [] - for evs in evslist: - networkdict = self._convert_evs_to_network(evs) - retme.append(self._fields(networkdict, fields)) - - return retme - - def get_networks_count(self, context, filters=None): - return len(self.get_networks(context, filters)) + with context.session.begin(subtransactions=True): + nets = super(EVSNeutronPluginV2, self).\ + get_networks(context, filters, None, sorts, limit, marker, + page_reverse) + for net in nets: + evs = self._evs_controller_getEVS(net['id']) + if evs: + self._extend_network_dict(net, evs) + return [self._fields(net, fields) for net in nets] @lockutils.synchronized('evs-plugin', 'neutron-') - def evs_controller_deleteEVS(self, tenantname, evsuuid): + def _evs_controller_deleteEVS(self, tenantname, evsuuid): + LOG.debug(_("Removing EVS with id: %s for tenant: %s") % + (evsuuid, tenantname)) try: - self._evsc.deleteEVS(evsuuid, tenantname) + self.rad_connection.\ + get_object(evsbind.EVSController()).\ + deleteEVS(evsuuid, tenantname) except radcli.ObjectError as oe: + # '41' corresponds to EVS' EVS_ENOENT_EVS error code + if oe.get_payload().err == 41: + # EVS doesn't have that EVS, return success to delete + # the EVS from Neutron DB. + LOG.debug(_("EVS could not be found in EVS backend.")) + return raise EVSControllerError(oe.get_payload().errmsg) def delete_network(self, context, id): # Check if it is an external network and whether addresses in that # network are being used for floating ips - evs = self.get_network(context, id) - if evs[external_net.EXTERNAL]: - filters = dict(evs=id) + net = self.get_network(context, id) + if net[external_net.EXTERNAL]: + filters = dict(network_id=[id]) portlist = self.get_ports(context, filters) ports_with_deviceid = [port for port in portlist if port['device_id'] != ''] if ports_with_deviceid: raise exceptions.NetworkInUse(net_id=id) - filters = dict(network_id=id) + filters = dict(network_id=[id]) subnets = self.get_subnets(context, filters=filters) dhcp_subnets = [s for s in subnets if s['enable_dhcp']] for subnet in dhcp_subnets: self._release_subnet_dhcp_port(context, subnet, True) - self.evs_controller_deleteEVS(context.tenant_id, id) + with context.session.begin(subtransactions=True): + super(EVSNeutronPluginV2, self).delete_network(context, id) + self._evs_controller_deleteEVS(net['tenant_id'], id) # notify dhcp agent of network deletion self.dhcp_agent_notifier.notify(context, {'network': {'id': id}}, 'network.delete.end') @lockutils.synchronized('evs-plugin', 'neutron-') - def evs_controller_addVPort(self, tenantname, evsname, vportname, propstr): + def _evs_controller_addVPort(self, tenantname, evsname, vportname, + propstr): + LOG.debug(_("Adding VPort: %s with properties: %s for tenant: %s " + "and for evs: %s") % + (vportname, propstr, tenantname, evsname)) + try: pat = radcli.ADRGlobPattern({'name': evsname, 'tenant': tenantname}) - evs = self._rc.get_object(evsbind.EVS(), pat) + evs = self.rad_connection.get_object(evsbind.EVS(), pat) vport = evs.addVPort(propstr, vportname) except radcli.ObjectError as oe: raise EVSControllerError(oe.get_payload().errmsg) @@ -789,179 +622,93 @@ This configuration is inherited by the VNIC when it connects to the VPort. """ - vportname = port['port']['name'] - if not vportname: - vportname = None - - proplist = [] - macaddr = port['port']['mac_address'] - if attributes.is_attr_set(macaddr): - proplist.append('macaddr=%s' % (macaddr)) - - fixed_ips = port['port']['fixed_ips'] - if attributes.is_attr_set(fixed_ips): - # we only support one subnet - ipaddr = fixed_ips[0].get('ip_address') - if ipaddr is not None: - proplist.append('ipaddr=%s' % ipaddr) + if port['port']['admin_state_up'] is False: + raise EVSOpNotSupported(_("setting admin_state_up=False for a " + "port not supported")) - # retrieve device_id and device_owner - device_id = port['port']['device_id'] - if attributes.is_attr_set(device_id) and device_id: - proplist.append("%s=%s" % - (PORT_VPORT_ATTRIBUTE_MAP['device_id'], device_id)) - - device_owner = port['port']['device_owner'] - if attributes.is_attr_set(device_owner) and device_owner: - proplist.append("%s=%s" % - (PORT_VPORT_ATTRIBUTE_MAP['device_owner'], - device_owner)) + with context.session.begin(subtransactions=True): + # for external gateway ports and floating ips, tenant_id + # is not set, but EVS does not like it. + tenant_id = self._get_tenant_id_for_create(context, port['port']) + if not tenant_id: + network = self.get_network(context, port['port']['network_id']) + port['port']['tenant_id'] = network['tenant_id'] + # create the port in the DB + db_port = super(EVSNeutronPluginV2, self).create_port(context, + port) - propstr = None - if proplist: - propstr = ",".join(proplist) + tenantname = db_port['tenant_id'] + vportname = db_port['name'] + if not vportname: + vportname = None + evs_id = db_port['network_id'] + proplist = ['macaddr=%s' % db_port['mac_address']] + proplist.append('ipaddr=%s' % + db_port['fixed_ips'][0].get('ip_address')) + proplist.append('uuid=%s' % db_port['id']) - evsname = port['port']['network_id'] - tenantname = self._get_tenant_id_for_create(context, port['port']) - if not tenantname: - network = self.get_network(context, evsname) - tenantname = network['tenant_id'] - vport = self.evs_controller_addVPort(tenantname, evsname, vportname, - propstr) - retval = self._convert_vport_to_port(context, vport) + self._evs_controller_addVPort(tenantname, evs_id, vportname, + ",".join(proplist)) # notify dhcp agent of port creation - self.dhcp_agent_notifier.notify(context, {'port': retval}, + self.dhcp_agent_notifier.notify(context, {'port': db_port}, 'port.create.end') - return retval - - @lockutils.synchronized('evs-plugin', 'neutron-') - def evs_controller_updateVPort(self, vportuuid, proplist): - try: - pat = radcli.ADRGlobPattern({'uuid': vportuuid}) - vportlist = self._rc.list_objects(evsbind.VPort(), pat) - assert len(vportlist) == 1 - vport = self._rc.get_object(vportlist[0]) - for prop in proplist: - vport.setProperty(prop) - except radcli.ObjectError as oe: - raise EVSControllerError(oe.get_payload().errmsg) + return db_port def update_port(self, context, id, port): - # EVS does not allow updating certain attributes - if not (set(port['port'].keys()) <= - set(('device_id', 'device_owner'))): - raise EVSOpNotSupported(_("only device_id and " - "device_owner supported")) + # EVS does not allow updating certain attributes, so check for it + state = port['port'].get('admin_state_up') + if state and state is False: + raise EVSOpNotSupported(_("updating port's admin_state_up to " + "False is not supported")) - proplist = [] - device_id = port['port'].get('device_id') - if device_id is not None: - # EVS expects property values to be non-zero length - if len(device_id) == 0: - device_id = " " - proplist.append("%s=%s" % - (PORT_VPORT_ATTRIBUTE_MAP['device_id'], device_id)) - - device_owner = port['port'].get('device_owner') - if device_owner is not None: - if len(device_owner) == 0: - device_owner = " " - proplist.append("%s=%s" % - (PORT_VPORT_ATTRIBUTE_MAP['device_owner'], - device_owner)) - - if not proplist: - return dict() - - self.evs_controller_updateVPort(id, proplist) - retval = self.get_port(context, id) + # Get the original port and fail if any attempt is being made + # to change fixed_ips of the port since EVS doesn't support it + original_port = super(EVSNeutronPluginV2, self).get_port(context, id) + original_ips = original_port['fixed_ips'] + update_ips = port['port'].get('fixed_ips') + if (update_ips and + (len(update_ips) != 1 or + update_ips[0]['subnet_id'] != original_ips[0]['subnet_id'] or + update_ips[0]['ip_address'] != original_ips[0]['ip_address'])): + raise EVSOpNotSupported(_("updating port's fixed_ips " + "is not supported")) + LOG.debug(_("Updating port %s with %s") % (id, port)) + db_port = super(EVSNeutronPluginV2, self).update_port(context, + id, port) # notify dhcp agent of port update - self.dhcp_agent_notifier.notify(context, {'port': retval}, + self.dhcp_agent_notifier.notify(context, {'port': db_port}, 'port.update.end') - return retval - - @lockutils.synchronized('evs-plugin', 'neutron-') - def evs_controller_getVPort(self, vportuuid): - try: - vportlist = self._evsc.getVPortInfo('vport=%s' % (vportuuid)) - except radcli.ObjectError as oe: - raise EVSControllerError(oe.get_payload().errmsg) - return (vportlist[0] if vportlist else None) + return db_port def get_port(self, context, id, fields=None): - vport = self.evs_controller_getVPort(id) - if not vport: - return {} - portdict = self._convert_vport_to_port(context, vport) - return self._fields(portdict, fields) - - @lockutils.synchronized('evs-plugin', 'neutron-') - def evs_controller_getVPortList(self, filterstr): - try: - vportlist = self._evsc.getVPortInfo(filterstr) - except radcli.ObjectError as oe: - raise EVSControllerError(oe.get_payload().errmsg) - return vportlist + LOG.debug(_("Getting port: %s"), id) + port = super(EVSNeutronPluginV2, self).get_port(context, id, None) + return self._fields(port, fields) def get_ports(self, context, filters=None, fields=None, sorts=None, limit=None, marker=None, page_reverse=False): - LOG.debug(_("inside the get_ports() method: filters: '%s'") % - str(filters)) + ports = super(EVSNeutronPluginV2, self).\ + get_ports(context, filters, None, sorts, limit, marker, + page_reverse) + return [self._fields(port, fields) for port in ports] - filterstr = None - # EVS desn't support filtering of resource based on - # properties, so we will have to filter ourselves - vport_props = {'OpenStack:device_id': None, - 'OpenStack:device_owner': None} - if filters is not None: - filterlist = [] - for key, value in filters.items(): - if key == 'shared': - if any(value): - return [] - continue - if key == 'admin_state_up': - continue - if key in ('device_id', 'device_owner'): - vport_props[PORT_VPORT_ATTRIBUTE_MAP[key]] = value - continue - key = PORT_VPORT_ATTRIBUTE_MAP.get(key, key) - if isinstance(value, list): - value = ",".join(map(str, set(value))) - if not value: - continue - filterlist.append("%s=%s" % (key, value)) + def notify_l3agent(self, context, port): + """ If an L3 agent is using this port, then we need to send + a notification to the L3 agent so that it can remove the EVS VPort + associated with the Neutron Port. In that case, the EVS Plugin will + only remove the Neutron port from the DB, so return False. - if filterlist: - filterstr = ",".join(filterlist) - - LOG.debug(_("calling getVPortInfo from get_ports(): '%s'") % - (filterstr)) - vportlist = self.evs_controller_getVPortList(filterstr) - self._apply_rsrc_props_filter(vportlist, vport_props) - - retme = [] - for vport in vportlist: - portdict = self._convert_vport_to_port(context, vport) - retme.append(self._fields(portdict, fields)) - - return retme - - def get_ports_count(self, context, filters=None): - return len(self.get_ports(context, filters)) - - def _release_l3agent_internal_port(self, context, port): - """ If an L3 agent is using this port, then we need to send - a notification to L3 agent to release the port before we can - delete the port""" + If the port is not used by the L3 agent, then the EVS plugin + will remove both the Neutron port and EVS VPort, so return True. + """ device_owner = port['device_owner'] - if device_owner not in [l3_constants.DEVICE_OWNER_ROUTER_INTF, - l3_constants.DEVICE_OWNER_ROUTER_GW, - l3_constants.DEVICE_OWNER_FLOATINGIP]: - return + if device_owner not in [constants.DEVICE_OWNER_ROUTER_INTF, + constants.DEVICE_OWNER_ROUTER_GW, + constants.DEVICE_OWNER_FLOATINGIP]: + return True router_id = port['device_id'] port_update = { 'port': { @@ -970,20 +717,20 @@ } } self.update_port(context, port['id'], port_update) - if device_owner in [l3_constants.DEVICE_OWNER_ROUTER_INTF, - l3_constants.DEVICE_OWNER_ROUTER_GW]: - msg = l3_rpc_agent_api.L3AgentNotify.make_msg("routers_updated", - routers=[router_id]) - l3_rpc_agent_api.L3AgentNotify.call(context, msg, - topic=topics.L3_AGENT) + if device_owner in [constants.DEVICE_OWNER_ROUTER_INTF, + constants.DEVICE_OWNER_ROUTER_GW]: + self.l3_agent_notifier.routers_updated(context, [router_id]) + return False + return True @lockutils.synchronized('evs-plugin', 'neutron-') - def evs_controller_removeVPort(self, tenantname, evsname, vportuuid, - vportname): + def _evs_controller_removeVPort(self, tenantname, evsname, vportuuid): + LOG.debug(_("Removing VPort with id: %s for tenant: %s for evs: %s") % + (vportuuid, tenantname, evsname)) pat = radcli.ADRGlobPattern({'name': evsname, 'tenant': tenantname}) try: - evs = self._rc.get_object(evsbind.EVS(), pat) + evs = self.rad_connection.get_object(evsbind.EVS(), pat) evs.removeVPort(vportuuid) except radcli.ObjectError as oe: # '7' corresponds to EVS' EVS_EBUSY_VPORT error code @@ -991,8 +738,10 @@ # It is possible that the VM is destroyed, but EVS is unaware # of it. So, try to reset the vport. If it succeeds, then call # removeVPort() again. + LOG.debug(_("EVS VPort is busy. We will need to reset " + "and then remove")) try: - evs.resetVPort(vportname) + evs.resetVPort(vportuuid) evs.removeVPort(vportuuid) except: # we failed one of the above operations, just return @@ -1001,6 +750,12 @@ else: # the reset and remove succeeded, just return. return + # '43' corresponds to EVS' EVS_ENOENT_VPORT error code + elif oe.get_payload().err == 43: + # EVS doesn't have that VPort, return success to delete + # the VPort from Neutron DB. + LOG.debug(_("VPort could not be found in EVS.")) + return raise EVSControllerError(oe.get_payload().errmsg) def delete_port(self, context, id, l3_port_check=True): @@ -1010,10 +765,13 @@ port = self.get_port(context, id) if not port: return - if not l3_port_check: - self._release_l3agent_internal_port(context, port) - self.evs_controller_removeVPort(port['tenant_id'], port['network_id'], - id, port['name']) + del_vport = l3_port_check or self.notify_l3agent(context, port) + with context.session.begin(subtransactions=True): + super(EVSNeutronPluginV2, self).delete_port(context, id) + if del_vport: + self._evs_controller_removeVPort(port['tenant_id'], + port['network_id'], + port['id']) # notify dhcp agent of port deletion payload = { @@ -1023,31 +781,3 @@ } } self.dhcp_agent_notifier.notify(context, payload, 'port.delete.end') - - # needed for DHCP agent support - def update_fixed_ip_lease_expiration(self, context, network_id, - ip_address, lease_remaining): - pass - - # needed for L3 agent support - def _get_network(self, context, network_id): - return self.get_network(context, network_id) - - def _get_subnet(self, context, subnet_id): - return self.get_subnet(context, subnet_id) - - def _get_port(self, context, port_id): - return self.get_port(context, port_id) - - def _delete_port(self, context, port_id): - return self.delete_port(context, port_id) - - def _get_subnets_by_network(self, context, network_id): - return self.get_subnets(context, filters={'network_id': network_id}) - - def _network_is_external(self, context, net_id): - try: - evs = self.get_network(context, net_id) - return evs[external_net.EXTERNAL] - except: - return False