# Example: mechanism_drivers = cisco,logger
# Example: mechanism_drivers = openvswitch,brocade
# Example: mechanism_drivers = linuxbridge,brocade
+# Example: mechanism_drivers = openvswitch,cisco_dfa
[ml2_type_flat]
# (ListOpt) List of physical_network names with which flat networks
# encap=vlan-100
# cidr_exposed=10.10.40.2/16
# gateway_ip=10.10.40.1
+
+[ml2_cisco_dfa]
+# (StrOpt) IP address of Cisco DCNM (Data Center Network Manager).
+# dcnm_ip = 1.1.1.1
+#
+# (StrOpt) User login name for DCNM.
+# dcnm_user = username
+#
+# (StrOpt) Login password for DCNM.
+# dcnm_password = password
+#
+# (StrOpt) Gateway MAC address when forwarding mode in created config profile
+# is proxy mode.
+# gateway_mac = 00:01:02:03:04:05
--- /dev/null
+# Copyright 2014 OpenStack Foundation
+#
+# 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.
+#
+
+"""Cisco DFA Mechanism Driver
+
+Revision ID: 469426cd2173
+Revises: 32f3915891fd
+Create Date: 2014-06-28 01:13:04.152945
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '469426cd2173'
+down_revision = '32f3915891fd'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade(active_plugins=None, options=None):
+ op.create_table(
+ 'cisco_dfa_config_profiles',
+ sa.Column('id', sa.String(36)),
+ sa.Column('name', sa.String(255)),
+ sa.Column('forwarding_mode', sa.String(32)),
+ sa.PrimaryKeyConstraint('id'))
+
+ op.create_table(
+ 'cisco_dfa_config_profile_bindings',
+ sa.Column('network_id', sa.String(36)),
+ sa.Column('cfg_profile_id', sa.String(36)),
+ sa.ForeignKeyConstraint(['network_id'], ['networks.id'],
+ ondelete='CASCADE'),
+ sa.PrimaryKeyConstraint('network_id', 'cfg_profile_id'))
+
+ op.create_table(
+ 'cisco_dfa_project_cache',
+ sa.Column('project_id', sa.String(36)),
+ sa.Column('project_name', sa.String(255)),
+ sa.PrimaryKeyConstraint('project_id'))
+
+
+def downgrade(active_plugins=None, options=None):
+ op.drop_table('cisco_dfa_project_cache')
+ op.drop_table('cisco_dfa_config_profile_bindings')
+ op.drop_table('cisco_dfa_config_profiles')
-32f3915891fd
+469426cd2173
from neutron.plugins.ml2.drivers.brocade.db import ( # noqa
models as ml2_brocade_models)
from neutron.plugins.ml2.drivers.cisco.apic import apic_model # noqa
+from neutron.plugins.ml2.drivers.cisco.dfa import dfa_models_v2 # noqa
from neutron.plugins.ml2.drivers.cisco.nexus import ( # noqa
nexus_models_v2 as ml2_nexus_models_v2)
from neutron.plugins.ml2.drivers import type_flat # noqa
--- /dev/null
+# Copyright 2014 Cisco Systems, Inc.
+# 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
+# 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.
+#
+
+
+from sqlalchemy.orm import exc
+
+from neutron.db import models_v2
+from neutron.plugins.ml2.drivers.cisco.dfa import constants as dfac
+from neutron.plugins.ml2.drivers.cisco.dfa import dfa_exceptions as dexc
+from neutron.plugins.ml2.drivers.cisco.dfa import dfa_models_v2
+
+
+def get_network_profile_binding(session, net_id):
+ """Retrieve network and config profile binding."""
+
+ try:
+ return (session.query(dfa_models_v2.ConfigProfileBinding).
+ filter_by(network_id=net_id).one())
+ except (exc.NoResultFound, exc.MultipleResultsFound):
+ pass
+
+
+def add_dfa_cfg_profile_binding(session, netid, cpid):
+ """Add new entry to the config profile binding database."""
+
+ try:
+ if cpid == dfac.DEFAULT_CFG_PROFILE_ID:
+ # The config profile is not provided when creating network.
+ # Use 'defaultNetworkL2Profile' as default config profile.
+ cfgp_name = 'defaultNetworkL2Profile'
+ cfgp_entry = (session.query(dfa_models_v2.ConfigProfile).
+ filter_by(name=cfgp_name).one())
+ cpid = cfgp_entry.id
+
+ binding = dfa_models_v2.ConfigProfileBinding(network_id=netid,
+ cfg_profile_id=cpid)
+ session.add(binding)
+ except (exc.NoResultFound, exc.MultipleResultsFound):
+ raise dexc.ConfigProfileNotFound(network_id=netid)
+
+
+def get_network_entry(session, netid):
+ """Retrieve network information."""
+
+ try:
+ return (session.query(models_v2.Network).
+ filter_by(id=netid).one())
+ except (exc.NoResultFound, exc.MultipleResultsFound):
+ raise dexc.NetworkNotFound(network_id=netid)
+
+
+def get_config_profile_name(db_session, netid):
+ """Retrieve configuration profile for a network."""
+
+ try:
+ cfgpobj = dfa_models_v2.ConfigProfileBinding
+ cfgp = db_session.query(cfgpobj).filter_by(network_id=netid).one()
+ cfgid = cfgp.cfg_profile_id
+ except (exc.NoResultFound, exc.MultipleResultsFound):
+ raise dexc.ConfigProfileNotFound(network_id=netid)
+ try:
+ cfgp_entry = db_session.query(
+ dfa_models_v2.ConfigProfile).filter_by(id=cfgid).one()
+ except (exc.NoResultFound, exc.MultipleResultsFound):
+ raise dexc.ConfigProfileIdNotFound(profile_id=cfgid)
+ return cfgp_entry.name
+
+
+def get_config_profile_fwd_mode(db_session, network_id):
+ """Retrieve configuration profile for a network."""
+
+ try:
+ cfgp = (db_session.query(dfa_models_v2.ConfigProfileBinding).
+ filter_by(network_id=network_id).one())
+ cfgid = cfgp.cfg_profile_id
+ except (exc.NoResultFound, exc.MultipleResultsFound):
+ raise dexc.ConfigProfileNotFound(network_id=network_id)
+
+ try:
+ cfgp_entry = db_session.query(
+ dfa_models_v2.ConfigProfile).filter_by(id=cfgid).one()
+ return cfgp_entry.forwarding_mode
+ except (exc.NoResultFound, exc.MultipleResultsFound):
+ raise dexc.ConfigProfileIdNotFound(profile_id=cfgid)
+
+
+def delete_dfa_cfg_profile_binding(db_session, network_id):
+ """Delete an entry from the config profile binding database."""
+
+ try:
+ with db_session.begin(subtransactions=True):
+ entry = (db_session.query(dfa_models_v2.ConfigProfileBinding).
+ filter_by(network_id=network_id).one())
+ db_session.delete(entry)
+ except (exc.NoResultFound, exc.MultipleResultsFound):
+ raise dexc.ConfigProfileNotFound(network_id=network_id)
--- /dev/null
+# Copyright 2014 Cisco Systems, Inc.
+# 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
+# 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.
+#
+
+
+from oslo.config import cfg
+import requests
+
+from neutron.openstack.common import jsonutils
+from neutron.openstack.common import log as logging
+from neutron.plugins.ml2.drivers.cisco.dfa import dfa_exceptions as dexc
+
+LOG = logging.getLogger(__name__)
+
+
+class DFARESTClient(object):
+ """DFA client class that provides APIs to interact with DCNM."""
+
+ def __init__(self):
+ self._ip = cfg.CONF.ml2_cisco_dfa.dcnm_ip
+ self._user = cfg.CONF.ml2_cisco_dfa.dcnm_user
+ self._pwd = cfg.CONF.ml2_cisco_dfa.dcnm_password
+ if (not self._ip) or (not self._user) or (not self._pwd):
+ msg = _("[DFARESTClient] Input DCNM IP, user name or password"
+ "parameter is not specified")
+ raise ValueError(msg)
+
+ # url timeout: 10 seconds
+ self._TIMEOUT_RESPONSE = 10
+
+ # urls
+ net_url = 'http://%s/' % self._ip
+ net_url += 'rest/auto-config/organizations/%s/partitions/%s/networks'
+ self._create_network_url = net_url
+ cfg_url = 'http://%s/rest/auto-config/profiles' % self._ip
+ self._cfg_profile_list_url = cfg_url
+ cfg_url += '/%s'
+ self._cfg_profile_get_url = cfg_url
+ self._org_url = 'http://%s/rest/auto-config/organizations' % self._ip
+ tmp_url = 'http://%s/rest/auto-config/organizations/' % self._ip
+ tmp_url += '%s/partitions'
+ self._create_part_url = tmp_url
+ self._del_org_url = self._org_url + '/%s'
+ self._del_part = self._org_url + '/%s/partitions/%s'
+ self._del_network_url = (self._org_url +
+ '/%s/partitions/%s/networks/segment/%s')
+ self._login_url = 'http://%s/rest/logon' % (self._ip)
+ self._logout_url = 'http://%s/rest/logout' % (self._ip)
+ self._exp_time = 100000
+ self._resp_ok = 200
+
+ def _create_network(self, network_info):
+ """Send create network request to DCNM.
+
+ :network_info: network parameters to be created on DCNM
+ """
+ url = self._create_network_url % (network_info['partitionName'],
+ network_info['partitionName'])
+ payload = network_info
+
+ LOG.info(_('url %(url)s payload %(payload)s'),
+ {'url': url, 'payload': payload})
+ return (self._send_request('POST', url, payload, 'network'))
+
+ def _config_profile_get(self, thisprofile):
+ """Get information of a config profile from DCNM.
+
+ :thisprofile: network config profile in request
+ """
+ url = self._cfg_profile_get_url % (thisprofile)
+ payload = {}
+
+ res = self._send_request('GET', url, payload, 'config-profile')
+ return res.json()
+
+ def _config_profile_list(self):
+ """Get list of supported config profile from DCNM."""
+ url = self._cfg_profile_list_url
+ payload = {}
+
+ res = self._send_request('GET', url, payload, 'config-profile')
+ return res.json()
+
+ def _create_org(self, name, desc):
+ """Create organization on the DCNM.
+
+ :name: Name of organization
+ :desc: Description of organization
+ """
+ url = self._org_url
+ payload = {
+ "organizationName": name,
+ "description": name if len(desc) == 0 else desc,
+ "orchestrationSource": "Openstack Controller"}
+
+ return (self._send_request('POST', url, payload, 'organization'))
+
+ def _create_partition(self, org_name, part_name, desc):
+ """Send Create partition request to the DCNM.
+
+ :org_name: name of organization
+ :part_name: name of partition
+ :desc: description of partition
+ """
+ url = self._create_part_url % (org_name)
+ payload = {
+ "partitionName": part_name,
+ "description": part_name if len(desc) == 0 else desc,
+ "organizationName": org_name}
+
+ return (self._send_request('POST', url, payload, 'partition'))
+
+ def _delete_org(self, org_name):
+ """Send organization delete request to DCNM.
+
+ :org_name: name of organization to be deleted
+ """
+ url = self._del_org_url % (org_name)
+ self._send_request('DELETE', url, '', 'organization')
+
+ def _delete_partition(self, org_name, partition_name):
+ """Send partition delete request to DCNM.
+
+ :partition_name: name of partition to be deleted
+ """
+ url = self._del_part % (org_name, partition_name)
+ self._send_request('DELETE', url, '', 'partition')
+
+ def _delete_network(self, network_info):
+ """Send network delete request to DCNM.
+
+ :partition_name: name of partition to be deleted
+ """
+ org_name = network_info.get('organizationName', '')
+ part_name = network_info.get('partitionName', '')
+ segment_id = network_info['segmentId']
+ url = self._del_network_url % (org_name, part_name, segment_id)
+ self._send_request('DELETE', url, '', 'network')
+
+ def _login(self):
+ """Login request to DCNM."""
+ url_login = self._login_url
+ expiration_time = self._exp_time
+
+ payload = {'expirationTime': expiration_time}
+ self._req_headers = {'Accept': 'application/json',
+ 'Content-Type': 'application/json; charset=UTF-8'}
+ res = requests.post(url_login,
+ data=jsonutils.dumps(payload),
+ headers=self._req_headers,
+ auth=(self._user, self._pwd),
+ timeout=self._TIMEOUT_RESPONSE)
+ session_id = ''
+ if res and res.status_code == self._resp_ok:
+ session_id = res.json().get('Dcnm-Token')
+ self._req_headers.update({'Dcnm-Token': session_id})
+
+ def _logout(self):
+ """Logout request to DCNM."""
+ url_logout = self._logout_url
+ requests.post(url_logout,
+ headers=self._req_headers,
+ timeout=self._TIMEOUT_RESPONSE)
+
+ def _send_request(self, operation, url, payload, desc):
+ """Send request to DCNM."""
+ res = None
+ try:
+ payload_json = None
+ if payload and payload != '':
+ payload_json = jsonutils.dumps(payload)
+ self._login()
+ desc_lookup = {'POST': ' creation', 'PUT': ' update',
+ 'DELETE': ' deletion', 'GET': ' get'}
+
+ res = requests.request(operation, url, data=payload_json,
+ headers=self._req_headers,
+ timeout=self._TIMEOUT_RESPONSE)
+ desc += desc_lookup.get(operation, operation.lower())
+ LOG.info(_("DCNM-send_request: %(desc)s %(url)s %(pld)s"),
+ {'desc': desc, 'url': url, 'pld': payload})
+
+ self._logout()
+ except (requests.HTTPError, requests.Timeout,
+ requests.ConnectionError) as e:
+ LOG.exception(_('Error during request'))
+ raise dexc.DFAClientRequestFailed(reason=e)
+
+ return res
+
+ def _check_for_supported_profile(self, thisprofile):
+ """Filter those profiles that are not currently supported."""
+ return (thisprofile.endswith('Ipv4TfProfile') or
+ thisprofile.endswith('Ipv4EfProfile') or
+ 'defaultNetworkL2Profile' in thisprofile)
+
+ def config_profile_list(self):
+ """Return config profile list from DCNM."""
+ profile_list = []
+ these_profiles = []
+ these_profiles = self._config_profile_list()
+ profile_list = [q for p in these_profiles for q in
+ [p.get('profileName')]
+ if self._check_for_supported_profile(q)]
+ return profile_list
+
+ def config_profile_fwding_mode_get(self, profile_name):
+ """Return forwarding mode of given config profile."""
+ profile_params = self._config_profile_get(profile_name)
+ fwd_cli = 'fabric forwarding mode proxy-gateway'
+ if fwd_cli in profile_params['configCommands']:
+ return 'proxy-gateway'
+ else:
+ return 'anycast-gateway'
+
+ def create_network(self, tenant_name, network, subnet):
+ """Create network on the DCNM.
+
+ :tenant_name: name of tenant the network belongs to
+ :network: network parameters
+ :subnet: subnet parameters of the network
+ """
+ network_info = {}
+ seg_id = str(network.provider__segmentation_id)
+ subnet_ip_mask = subnet.cidr.split('/')
+ gw_ip = subnet.gateway_ip
+ cfg_args = [
+ "$segmentId=" + seg_id,
+ "$netMaskLength=" + subnet_ip_mask[1],
+ "$gatewayIpAddress=" + gw_ip,
+ "$networkName=" + network.name,
+ "$vlanId=0",
+ "$vrfName=" + tenant_name + ':' + tenant_name
+ ]
+ cfg_args = ';'.join(cfg_args)
+
+ ip_range = ','.join(["%s-%s" % (p['start'], p['end']) for p in
+ subnet.allocation_pools])
+
+ dhcp_scopes = {'ipRange': ip_range,
+ 'subnet': subnet.cidr,
+ 'gateway': gw_ip}
+
+ network_info = {"segmentId": seg_id,
+ "vlanId": "0",
+ "mobilityDomainId": "None",
+ "profileName": network.config_profile,
+ "networkName": network.name,
+ "configArg": cfg_args,
+ "organizationName": tenant_name,
+ "partitionName": tenant_name,
+ "description": network.name,
+ "dhcpScope": dhcp_scopes}
+ LOG.debug("Create %s network in DCNM." % network_info)
+
+ self._create_network(network_info)
+
+ def delete_network(self, tenant_name, network):
+ """Delete network on the DCNM.
+
+ :tenant_name: name of tenant the network belongs to
+ :network: object that contains network parameters
+ """
+ network_info = {}
+ seg_id = network.provider__segmentation_id
+ network_info = {
+ 'organizationName': tenant_name,
+ 'partitionName': tenant_name,
+ 'segmentId': seg_id,
+ }
+ LOG.debug("Delete %s network in DCNM." % network_info)
+
+ self._delete_network(network_info)
+
+ def delete_tenant(self, tenant_name):
+ """Delete tenant on the DCNM.
+
+ :tenant_name: name of tenant to be deleted.
+ """
+ self._delete_partition(tenant_name, tenant_name)
+ self._delete_org(tenant_name)
+
+ def create_project(self, org_name, desc=None):
+ """Create project on the DCNM.
+
+ :org_name: name of organization to be created
+ :desc: string that describes organization
+ """
+ desc = desc or org_name
+ self._create_org(org_name, desc)
+ self._create_partition(org_name, org_name, desc)
--- /dev/null
+# Copyright 2014 Cisco Systems, Inc.
+# 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
+# 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.
+#
+
+
+from oslo.config import cfg
+
+
+ml2_cisco_dfa_opts = [
+ cfg.StrOpt('dcnm_ip', default='0.0.0.0',
+ help=_("IP address of DCNM.")),
+ cfg.StrOpt('dcnm_user', default='user',
+ help=_("User login name for DCNM.")),
+ cfg.StrOpt('dcnm_password', default='password',
+ secret=True,
+ help=_("Login password for DCNM.")),
+ cfg.StrOpt('gateway_mac', default='00:00:DE:AD:BE:EF',
+ help=_("Gateway mac address when using proxy mode.")),
+]
+
+cfg.CONF.register_opts(ml2_cisco_dfa_opts, "ml2_cisco_dfa")
+
+
+class CiscoDFAConfig(object):
+ """Cisco DFA Mechanism Driver Configuration class."""
+
+ dfa_cfg = {}
+
+ def __init__(self):
+ multi_parser = cfg.MultiConfigParser()
+ read_ok = multi_parser.read(cfg.CONF.config_file)
+
+ if len(read_ok) != len(cfg.CONF.config_file):
+ raise cfg.Error(_("Failed to read config files %(file)s") %
+ {'file': cfg.CONF.config_file})
+
+ for parsed_file in multi_parser.parsed:
+ for parsed_item in parsed_file.keys():
+ for key, value in parsed_file[parsed_item].items():
+ if parsed_item == 'mech_driver_agent':
+ self.dfa_cfg[key] = value
--- /dev/null
+# Copyright 2014 Cisco Systems, Inc.
+# 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
+# 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 uuid
+
+
+CISCO_DFA_MECH_DRVR_NAME = 'cisco_dfa'
+DEFAULT_CFG_PROFILE_ID = str(uuid.UUID(int=0))
+CONFIG_PROFILE_ID = 'dfa:cfg_profile_id'
--- /dev/null
+# Copyright 2014 Cisco Systems, Inc.
+# 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
+# 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.
+#
+
+"""Exceptions used by DFA ML2 mechanism drivers."""
+
+from neutron.common import exceptions
+
+
+class NetworkNotFound(exceptions.NotFound):
+ """Network cannot be found."""
+
+ message = _("Network %(network_id)s could not be found.")
+
+
+class ConfigProfileNotFound(exceptions.NotFound):
+ """Config Profile cannot be found."""
+
+ message = _("Config profile for network %(network_id)s"
+ " could not be found.")
+
+
+class ConfigProfileFwdModeNotFound(exceptions.NotFound):
+ """Config Profile forwarding mode cannot be found."""
+
+ message = _("Forwarding Mode for network %(network_id)s"
+ " could not be found.")
+
+
+class ConfigProfileIdNotFound(exceptions.NotFound):
+ """Config Profile ID cannot be found."""
+
+ message = _("Config Profile %(profile_id)s could not be found.")
+
+
+class ConfigProfileNameNotFound(exceptions.NotFound):
+ """Config Profile name cannot be found."""
+
+ message = _("Config Profile %(name)s could not be found.")
+
+
+class ProjectIdNotFound(exceptions.NotFound):
+ """Project ID cannot be found."""
+
+ message = _("Project ID %(project_id)s could not be found.")
+
+
+class DFAClientRequestFailed(exceptions.ServiceUnavailable):
+ """Request to DCNM failed."""
+
+ message = _("Request to DCNM failed: %(reason)s.")
--- /dev/null
+# Copyright 2014 Cisco Systems, Inc.
+# 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
+# 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.
+#
+
+"""
+This file provides a wrapper to novaclient API, for getting the instacne's
+information such as display_name.
+"""
+
+from keystoneclient.v2_0 import client as keyc
+
+from neutron.openstack.common import log as logging
+from novaclient import exceptions as nexc
+from novaclient.v1_1 import client as nova_client
+
+
+LOG = logging.getLogger(__name__)
+
+
+class DFAInstanceAPI(object):
+ """This class provides API to get information for a given instance."""
+
+ def __init__(self, cfg):
+ self._tenant_name = cfg.CONF.keystone_authtoken.admin_tenant_name
+ self._user_name = cfg.CONF.keystone_authtoken.admin_user
+ self._admin_password = cfg.CONF.keystone_authtoken.admin_password
+ self._TIMEOUT_RESPONSE = 10
+ self._token = None
+ self._project_id = None
+ self._auth_url = None
+ self._token_id = None
+ self._token = None
+ self._novaclnt = None
+ self._url = cfg.CONF.nova_admin_auth_url
+ self._inst_info_cache = {}
+
+ def _create_token(self):
+ """Create new token for using novaclient API."""
+ ks = keyc.Client(username=self._user_name,
+ password=self._admin_password,
+ tenant_name=self._tenant_name,
+ auth_url=self._url)
+ result = ks.authenticate()
+ if result:
+ access = ks.auth_ref
+ token = access.get('token')
+ self._token_id = token['id']
+ self._project_id = token['tenant'].get('id')
+ service_catalog = access.get('serviceCatalog')
+ for sc in service_catalog:
+ if sc['type'] == "compute" and sc['name'] == 'nova':
+ endpoints = sc['endpoints']
+ for endp in endpoints:
+ self._auth_url = endp['adminURL']
+ LOG.info(_('_create_token: token = %s'), token)
+
+ # Create nova client.
+ self._novaclnt = self._create_nova_client()
+
+ return token
+
+ else:
+ # Failed request.
+ LOG.error(_('Failed to send token create request.'))
+
+ def _create_nova_client(self):
+ """Creates nova client object."""
+ try:
+ clnt = nova_client.Client(self._user_name,
+ self._token_id,
+ self._project_id,
+ self._auth_url,
+ insecure=False,
+ cacert=None)
+ clnt.client.auth_token = self._token_id
+ clnt.client.management_url = self._auth_url
+ return clnt
+ except nexc.Unauthorized:
+ thismsg = (_('Failed to get novaclient:Unauthorised '
+ '%(proj)s %(user)s') % {'proj': self.project_id,
+ 'user': self._user_name})
+ raise nexc.ClientException(thismsg)
+
+ except nexc.AuthorizationFailure as err:
+ raise nexc.ClientException(_("Failed to get novaclient %s") % err)
+
+ def _get_instances_for_project(self, project_id):
+ """Return all instances for a given project.
+
+ :project_id: UUID of project (tenant)
+ """
+ search_opts = {'marker': None,
+ 'all_tenants': True,
+ 'project_id': project_id}
+ self._create_token()
+ try:
+ servers = self._novaclnt.servers.list(True, search_opts)
+ LOG.debug('_get_instances_for_project: servers=%s' % servers)
+ return servers
+ except nexc.Unauthorized:
+ emsg = (_('Failed to get novaclient:Unauthorised '
+ 'project_id=%(proj)s user=%(user)s'),
+ {'proj': self.project_id, 'name': self._user_name})
+ LOG.exception(emsg)
+ raise nexc.ClientException(emsg)
+ except nexc.AuthorizationFailure as err:
+ emsg = _("Failed to get novaclient %s")
+ LOG.exception(emsg % err)
+ raise nexc.ClientException(emsg % err)
+
+ def get_instance_for_uuid(self, uuid, project_id):
+ """Return instance name for given uuid of an instance and project.
+
+ :uuid: Instance's UUID
+ :project_id: UUID of project (tenant)
+ """
+ instance_name = None
+ instance_name = self._inst_info_cache.get((uuid, project_id))
+ if instance_name:
+ return instance_name
+ instances = self._get_instances_for_project(project_id)
+ for inst in instances:
+ if inst.id.replace('-', '') == uuid:
+ LOG.debug('get_instance_for_uuid: name=%s' % inst.name)
+ instance_name = inst.name
+ self._inst_info_cache[(uuid, project_id)] = instance_name
+ return instance_name
+ return instance_name
--- /dev/null
+# Copyright 2014 Cisco Systems, Inc.
+# 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
+# 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.
+#
+
+from neutron.common import rpc as n_rpc
+from neutron.common import topics
+
+
+class RpcCallbacks(n_rpc.RpcCallback):
+
+ RPC_API_VERSION = '1.1'
+
+ def __init__(self, notifier):
+ self._nofifier = notifier
+ super(RpcCallbacks, self).__init__()
+
+
+class MechDriversAgentNotifierApi(n_rpc.RpcProxy):
+ """Agent side of the cisco DFA mechanism driver rpc API.
+
+ API version history:
+ 1.0 - Initial version.
+ """
+
+ BASE_RPC_API_VERSION = '1.0'
+
+ def __init__(self, topic, agt_topic_tbl):
+ super(MechDriversAgentNotifierApi, self).__init__(
+ topic=topic, default_version=self.BASE_RPC_API_VERSION)
+ self.topic_dfa_update = topics.get_topic_name(topic,
+ agt_topic_tbl,
+ topics.UPDATE)
+
+ def send_vm_info(self, context, vm_info):
+ self.fanout_cast(context,
+ self.make_msg('send_vm_info', vm_info=vm_info),
+ topic=self.topic_dfa_update)
--- /dev/null
+# Copyright 2014 Cisco Systems, Inc.
+# 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
+# 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.
+#
+
+
+from neutron.db import model_base
+import sqlalchemy as sa
+
+
+class ConfigProfile(model_base.BASEV2):
+ """Cisco DFA network configuration profile.
+
+ 'id' - UUID and is localy generated,
+ 'name' - profile name coming form DCNM.
+ """
+ __tablename__ = 'cisco_dfa_config_profiles'
+
+ id = sa.Column(sa.String(36), primary_key=True)
+ name = sa.Column(sa.String(255))
+ forwarding_mode = sa.Column(sa.String(32))
+
+
+class ConfigProfileBinding(model_base.BASEV2):
+ """Represents a binding of Network to Config Profile.
+
+ netwrok_id - Network UUID,
+ cfg_profile_id - UUID of config profile.
+ """
+ __tablename__ = 'cisco_dfa_config_profile_bindings'
+
+ network_id = sa.Column(sa.String(36),
+ sa.ForeignKey('networks.id', ondelete="CASCADE"),
+ primary_key=True)
+ cfg_profile_id = sa.Column(sa.String(36), primary_key=True)
+
+
+class ProjectNameCache(model_base.BASEV2):
+ """Cache project name and project ID for Cisco DFA.
+
+ project_id - project UUID,
+ project_name - project name.
+ """
+ __tablename__ = 'cisco_dfa_project_cache'
+
+ project_id = sa.Column(sa.String(36),
+ primary_key=True)
+ project_name = sa.Column(sa.String(255))
--- /dev/null
+# Copyright (c) 2014 Cisco Systems
+# 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
+# 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.
+#
+
+
+"""
+ML2 Mechanism Driver for Cisco DFA platforms.
+"""
+
+import eventlet
+from oslo.config import cfg
+
+from neutron.common import exceptions as n_exc
+from neutron.common import rpc as n_rpc
+from neutron.common import topics
+from neutron.extensions import portbindings
+from neutron.openstack.common import log as logging
+from neutron.plugins.ml2.common import exceptions as ml2_exc
+from neutron.plugins.ml2 import driver_api as api
+from neutron.plugins.ml2.drivers.cisco.dfa import cfg_profile_db_v2
+from neutron.plugins.ml2.drivers.cisco.dfa import cisco_dfa_rest
+from neutron.plugins.ml2.drivers.cisco.dfa import config
+from neutron.plugins.ml2.drivers.cisco.dfa import constants as dfa_const
+from neutron.plugins.ml2.drivers.cisco.dfa import dfa_exceptions as dexc
+from neutron.plugins.ml2.drivers.cisco.dfa import dfa_instance_api
+from neutron.plugins.ml2.drivers.cisco.dfa import dfa_mech_driver_rpc as drpc
+from neutron.plugins.ml2.drivers.cisco.dfa import project_events
+from neutron.plugins.ml2.drivers.cisco.dfa import projects_cache_db_v2
+
+
+LOG = logging.getLogger(__name__)
+
+
+class SubnetObj(object):
+ """Represents a subnet object.
+
+ The information in the object will be used when creating a subnet on
+ the DCNM.
+ """
+ def __init__(self, subnet):
+ self.allocation_pools = subnet['allocation_pools']
+ self.host_routes = subnet['host_routes']
+ self.cidr = subnet['cidr']
+ self.id = subnet['id']
+ self.name = subnet['name']
+ self.enable_dhcp = subnet['enable_dhcp']
+ self.network_id = subnet['network_id']
+ self.tenant_id = subnet['tenant_id']
+ self.dns_nameservers = subnet['dns_nameservers']
+ self.gateway_ip = subnet['gateway_ip']
+ self.ip_version = subnet['ip_version']
+ self.shared = subnet['shared']
+
+
+class NetworkObj(object):
+ """Represents a network object.
+
+ The information in this object will be used when creating a network on
+ the DCNM.
+ """
+ def __init__(self, net, segid, cfgp=None):
+ self.provider__segmentation_id = segid
+ self.tenant_id = net['tenant_id']
+ self.name = net['name']
+ self.config_profile = cfgp
+ self.id = net['id']
+
+
+class CiscoDfaMechanismDriver(api.MechanismDriver):
+ """Cisco DFA ML2 Mechanism Driver."""
+
+ def initialize(self):
+ # Initialize the config
+ self._dfa_cfg = config.CiscoDFAConfig().dfa_cfg
+
+ # Initialize DCNM client.
+ self._dcnm_client = cisco_dfa_rest.DFARESTClient()
+
+ # Initialize project creation/deletion events object.
+ # This will be used to get notification from keystone when
+ # a tenant (i.e. project) is created or deleted.
+ self._keys = project_events.EventsHandler('keystone',
+ self._dcnm_client)
+
+ # Spawn a task, to process notification queue for keystone events.
+ eventlet.spawn(self._process_keystone_events)
+
+ # Initialize nova client wrapper. It will be used to get more
+ # information for an instance.
+ self._inst_api = dfa_instance_api.DFAInstanceAPI(cfg)
+
+ # Initialize mechanism driver RPC.
+ self._setup_mechdrv_rpc()
+
+ # Initialize project info object.
+ self.projects_cache_db_v2 = projects_cache_db_v2.ProjectsInfoCache()
+
+ self._ctask_sleep_interval = 60
+
+ def _get_agent_topic(self):
+ """Read the mech_driver_agent section from the config file."""
+ mech_drvr_rpc = self._dfa_cfg.get('mech_driver_rpc')
+ if mech_drvr_rpc is None:
+ return
+ self._agent_topic = ''
+ self._mech_drv_topic = ''
+ for val in mech_drvr_rpc:
+ if len(val) > 0:
+ if val.split(':')[0] != dfa_const.CISCO_DFA_MECH_DRVR_NAME:
+ continue
+ try:
+ self._mech_drv_topic = val.split(':')[1]
+ self._agent_topic = val.split(':')[2]
+ except IndexError:
+ emsg = _('No topics is defined for %s mechanism driver')
+ LOG.error(emsg % dfa_const.CISCO_DFA_MECH_DRVR_NAME)
+ return
+
+ def _setup_mechdrv_rpc(self):
+ """Setup RPC for this mechanism driver."""
+ self._get_agent_topic()
+ if not self._agent_topic or not self._mech_drv_topic:
+ LOG.debug('Mechanism Driver notifer is not initialized')
+ return
+ self.dfa_notifier = drpc.MechDriversAgentNotifierApi(topics.AGENT,
+ self._agent_topic)
+ self.endpoints = [drpc.RpcCallbacks(self.dfa_notifier)]
+ self.topic = self._mech_drv_topic
+ self.conn = n_rpc.create_connection(new=True)
+ self.conn.create_consumer(self.topic, self.endpoints, fanout=False)
+ self.conn.consume_in_threads()
+
+ def _process_keystone_events(self):
+ """Task to process notification from keystone.
+
+ The handler processes events such as creation and deletion of projects
+ sent by keystone.
+ """
+ self._keys.event_handler()
+
+ def create_network_postcommit(self, context):
+ # Check if the tenant is valid.
+ projid = context.current.get('tenant_id')
+ if not self._keys.is_valid_project(projid):
+ return
+
+ # Check if network id exists in the config profile DB. If not,
+ # exception should be raised.
+ net_id = context.current.get('id')
+ res = cfg_profile_db_v2.get_network_profile_binding(
+ context._plugin_context.session, net_id)
+ if not res:
+ cfgp_id = context.current.get(dfa_const.CONFIG_PROFILE_ID)
+ msg = (_("Failed to create network. Config Profile id %s"
+ " does not exist.") % cfgp_id)
+ raise n_exc.BadRequest(resource='network', msg=msg)
+
+ # Get the project name. If project name does not exist, an exception
+ # will be raised.
+ self.projects_cache_db_v2.get_project_name(projid)
+
+ def delete_network_postcommit(self, context):
+ projid = context.current.get('tenant_id')
+ if not self._keys.is_valid_project(projid):
+ return
+
+ segid = context.current.get('provider:segmentation_id')
+ tenant_name = context._plugin_context.tenant_name
+ net = NetworkObj(context.current, segid)
+ try:
+ self._dcnm_client.delete_network(tenant_name, net)
+ except dexc.DFAClientRequestFailed as ex:
+ emsg = _('Failed to create network %(net)s. Error:%(err)s.')
+ LOG.error(emsg % {'net': net.name, 'err': ex})
+ raise ml2_exc.MechanismDriverError
+
+ def create_subnet_postcommit(self, context):
+ projid = context.current.get('tenant_id')
+ if not self._keys.is_valid_project(projid):
+ return
+
+ subnet = context.current
+ if subnet['name'] == 'private-subnet':
+ emsg = _("%s is default subnet and no need to create it in DCNM.")
+ LOG.info(emsg % subnet['name'])
+ return
+
+ session = context._plugin_context.session
+ netid = context.current['network_id']
+ network_entry = cfg_profile_db_v2.get_network_entry(session, netid)
+ tenant_name = context._plugin_context.tenant_name
+ segid = self.projects_cache_db_v2.get_network_segid(netid)
+ cfgp_name = cfg_profile_db_v2.get_config_profile_name(session, netid)
+ snet = SubnetObj(context.current)
+ net = NetworkObj(network_entry, int(segid), cfgp_name)
+ try:
+ self._dcnm_client.create_network(tenant_name, net, snet)
+ except dexc.DFAClientRequestFailed as ex:
+ emsg = _('Failed to create network %(net)s. Error:%(err)s.')
+ LOG.error(emsg % {'net': net.name, 'err': ex})
+ raise ml2_exc.MechanismDriverError
+
+ def update_port_postcommit(self, context):
+ projid = context.current.get('tenant_id')
+ if not self._keys.is_valid_project(projid):
+ return
+
+ session = context._plugin_context.session
+ self.device_id = context.current.get('device_id').replace('-', '')
+ tenant_id = context.current.get('tenant_id')
+ netid = context.current.get('network_id')
+ self.inst_name = self._inst_api.get_instance_for_uuid(self.device_id,
+ tenant_id)
+ self.fwd_mode = cfg_profile_db_v2.get_config_profile_fwd_mode(session,
+ netid)
+ self.segid = self.projects_cache_db_v2.get_network_segid(netid)
+ self.mac = context.current.get('mac_address')
+ self.ip = (context.current.get('fixed_ips')[0]['ip_address']
+ if context.current.get('fixed_ips') else None)
+
+ vm_info = {
+ 'status': 'up',
+ 'ip': self.ip,
+ 'mac': self.mac,
+ 'segid': self.segid,
+ 'inst_name': self.inst_name,
+ 'inst_uuid': self.device_id,
+ 'host': context.current.get(portbindings.HOST_ID),
+ 'port_id': context.current.get('id'),
+ 'network_id': context.current.get('network_id'),
+ 'oui_type': 'cisco',
+ }
+ if self.inst_name:
+ self.dfa_notifier.send_vm_info(context._plugin_context, vm_info)
+ LOG.debug("update_port_postcommit : %s" % vm_info)
+
+ def delete_port_postcommit(self, context):
+ session = context._plugin_context.session
+ self.device_id = context.current.get('device_id').replace('-', '')
+ tenant_id = context.current.get('tenant_id')
+ netid = context.current.get('network_id')
+ self.inst_name = self._inst_api.get_instance_for_uuid(self.device_id,
+ tenant_id)
+ self.fwd_mode = cfg_profile_db_v2.get_config_profile_fwd_mode(session,
+ netid)
+ self.segid = self.projects_cache_db_v2.get_network_segid(netid)
+ self.mac = context.current.get('mac_address')
+ self.ip = (context.current.get('fixed_ips')[0]['ip_address']
+ if context.current.get('fixed_ips') else None)
+
+ vm_info = {
+ 'status': 'down',
+ 'ip': self.ip,
+ 'mac': self.mac,
+ 'segid': self.segid,
+ 'inst_name': self.inst_name,
+ 'inst_uuid': self.device_id,
+ 'host': context.current.get(portbindings.HOST_ID),
+ 'port_id': context.current.get('id'),
+ 'network_id': context.current.get('network_id'),
+ 'oui_type': 'cisco',
+ }
+ if self.inst_name:
+ self.dfa_notifier.send_vm_info(context._plugin_context, vm_info)
+ LOG.debug("delete_port_postcommit : %s" % vm_info)
--- /dev/null
+# Copyright 2014 Cisco Systems, Inc.
+# 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
+# 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.
+#
+
+
+from keystoneclient.v3 import client
+from oslo.config import cfg
+from oslo import messaging
+
+from neutron.openstack.common import excutils
+from neutron.openstack.common import log as logging
+from neutron.plugins.ml2.drivers.cisco.dfa import dfa_exceptions as dexc
+from neutron.plugins.ml2.drivers.cisco.dfa import projects_cache_db_v2
+
+
+LOG = logging.getLogger(__name__)
+
+
+notif_params = {
+ 'keystone': {
+ 'admin_token': 'ADMIN',
+ 'admin_endpoint': 'http://localhost:%(admin_port)s/',
+ 'admin_port': '35357',
+ 'default_notification_level': 'INFO',
+ 'notification_topics': 'notifications',
+ 'control_exchange': 'openstack',
+ }
+}
+
+proj_exceptions_list = [
+ 'admin', 'service', 'invisible_to_admin', 'demo', 'alt_demo']
+
+
+class NotificationEndpoint(object):
+ def __init__(self, evnt_hndlr):
+ self._event_hndlr = evnt_hndlr
+
+ def info(self, ctxt, publisher_id, event_type, payload, metadata):
+ self._event_hndlr.callback(event_type, payload)
+
+
+class EventsHandler(projects_cache_db_v2.ProjectsInfoCache):
+ """This class defines methods to listen and process the project events."""
+
+ def __init__(self, ser_name, dcnm_client):
+ self._keystone = None
+ self._service = ser_name
+ self._notif_params = {}
+ self._set_notif_params()
+ self._dcnm_client = dcnm_client
+ self.events_handler = {
+ 'identity.project.created': self.project_create_event,
+ 'identity.project.deleted': self.project_delete_event,
+ 'identity.user.created': self.no_op_event,
+ 'identity.user.deleted': self.no_op_event,
+ }
+
+ def no_op_event(self, keyc, project_id, dcnmc):
+ pass
+
+ def project_create_event(self, keyc, project_id, dcnmc):
+ """Create a project on the DCNM.
+
+ :param keyc: keystoneclient object
+ :param project_id: UUID of the project
+ :param dcnmc: DCNM client object
+ """
+ proj = keyc.projects.get(project_id)
+ proj_name = proj.name
+ desc = proj.description
+ LOG.debug("project_create_event: %(proj)s %(proj_name)s %(desc)s." %
+ {'proj': proj, 'proj_name': proj_name, 'desc': desc})
+ if proj_name not in proj_exceptions_list:
+ try:
+ dcnmc.create_project(proj_name, desc)
+ except dexc.DFAClientConnectionFailed as ex:
+ with excutils.save_and_reraise_exception():
+ LOG.exception(_('Failed to create %(proj)s. '
+ 'Error:%(err)s.'),
+ {'proj': proj_name, 'err': ex})
+ proj_info = {'project_id': project_id,
+ 'project_name': proj_name}
+ self.create_projects_cache_db(proj_info)
+
+ def project_delete_event(self, keyc, project_id, dcnmc):
+ """Delete a project on the DCNM.
+
+ :param keyc: keystoneclient object
+ :param project_id: UUID of the project
+ :param dcnmc: DCNM client object
+ """
+ try:
+ proj_info = self.delete_projects_cache_db(project_id)
+ LOG.debug("project_delete_event: proj_info: %s." % proj_info)
+ dcnmc.delete_tenant(proj_info.project_name)
+ except dexc.ProjectIdNotFound:
+ with excutils.save_and_reraise_exception():
+ LOG.exception(_("Failed to delete %(id)s"), {'id': project_id})
+ except dexc.DFAClientConnectionFailed:
+ with excutils.save_and_reraise_exception():
+ LOG.exception(_("Failed to delete %(proj)s in DCNM."),
+ {'proj': proj_info.project_name})
+
+ def _set_notif_params(self):
+ """Read notification parameters from the config file."""
+ self._notif_params.update(notif_params[self._service])
+ temp_db = {}
+ cfgfile = cfg.find_config_files(self._service)
+ multi_parser = cfg.MultiConfigParser()
+ cfgr = multi_parser.read(cfgfile)
+ if len(cfgr) == 0:
+ LOG.error(_("Failed to read %s."), cfgfile)
+ return
+ for parsed_file in multi_parser.parsed:
+ for parsed_item in parsed_file.keys():
+ for key, value in parsed_file[parsed_item].items():
+ if key in self._notif_params:
+ val = notif_params[self._service].get(key)
+ if val != value[0]:
+ temp_db[key] = value[0]
+
+ self._notif_params.update(temp_db)
+ self._token = self.get_notif_params().get('admin_token')
+ _endpoint = self.get_notif_params().get('admin_endpoint')
+ self._endpoint_url = _endpoint % self.get_notif_params() + 'v3/'
+ self._keystone = client.Client(token=self._token,
+ endpoint=self._endpoint_url)
+
+ def callback(self, event_type, payload):
+ """Callback method for processing events in notification queue.
+
+ :param event_type: event type in the notification queue such as
+ identity.project.created, identity.project.deleted.
+ :param payload: Contains information of an event
+ """
+ try:
+ event = event_type
+ if event in self.events_handler:
+ project_id = payload['resource_info']
+ self.events_handler[event](self._keystone, project_id,
+ self._dcnm_client)
+ except KeyError:
+ LOG.error(_('event_type %s does not have payload/resource_info '
+ 'key'), event)
+
+ def event_handler(self):
+ """Prepare connection and channels for listenning to the events."""
+ topicname = self.get_notif_params().get('notification_topics')
+ transport = messaging.get_transport(cfg.CONF)
+ targets = [messaging.Target(topic=topicname)]
+ endpoints = [NotificationEndpoint(self)]
+ server = messaging.get_notification_listener(transport, targets,
+ endpoints)
+ server.start()
+ server.wait()
+
+ def get_notif_params(self):
+ """Return notification parameters."""
+ return self._notif_params
+
+ def is_valid_project(self, project_id):
+ """Check the validity of project.
+
+ :param project_id: UUID of project
+ :returns: True if project is valid.
+ """
+ proj = self._keystone.projects.get(project_id)
+ proj_name = proj.name
+ if proj_name in proj_exceptions_list:
+ LOG.debug("Project %s is not created by user." % proj_name)
+ return False
+ return True
--- /dev/null
+# Copyright 2014 Cisco Systems, Inc.
+# 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
+# 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.
+#
+
+
+from sqlalchemy.orm import exc
+
+import neutron.db.api as db
+from neutron.plugins.ml2 import db as ml2db
+from neutron.plugins.ml2.drivers.cisco.dfa import dfa_exceptions as dexc
+from neutron.plugins.ml2.drivers.cisco.dfa import dfa_models_v2
+
+
+class ProjectsInfoCache(object):
+ """Project DB API."""
+
+ def _get_project_entry(self, db_session, pid):
+ """Get a project entry from the table.
+
+ :param db_session: database session object
+ :param pid: project ID
+ """
+ try:
+ return db_session.query(
+ dfa_models_v2.ProjectNameCache).filter_by(project_id=pid).one()
+ except exc.NoResultFound:
+ raise dexc.ProjectIdNotFound(project_id=pid)
+
+ def create_projects_cache_db(self, proj_info):
+ """Create an entry in the database.
+
+ :param proj_info: dictionary that contains information of the project
+ """
+ db_session = db.get_session()
+ with db_session.begin(subtransactions=True):
+ projid = proj_info["project_id"]
+ projname = proj_info["project_name"]
+ thisproj = dfa_models_v2.ProjectNameCache(project_id=projid,
+ project_name=projname)
+ db_session.add(thisproj)
+ return thisproj
+
+ def delete_projects_cache_db(self, proj_id):
+ """Delete a project from the table.
+
+ :param proj_id: UUID of the project
+ """
+ db_session = db.get_session()
+ thisproj = None
+ with db_session.begin(subtransactions=True):
+ thisproj = self._get_project_entry(db_session, proj_id)
+ db_session.delete(thisproj)
+ return thisproj
+
+ def get_project_name(self, proj_id):
+ """Returns project's name.
+
+ :param proj_id: UUID of the project
+ """
+ db_session = db.get_session()
+ with db_session.begin(subtransactions=True):
+ thisproj = self._get_project_entry(db_session, proj_id)
+ return thisproj.project_name
+
+ def update_projects_cache_db(self, pid, proj_info):
+ """Update projects DB.
+
+ :param pid: project ID
+ :param proj_info: dictionary that contains information of the project
+ """
+ db_session = db.get_session()
+ with db_session.begin(subtransactions=True):
+ thisproj = self._get_project_entry(db_session, pid)
+ thisproj.update(proj_info)
+
+ def get_network_segid(self, sid):
+ """Get network segmentation id.
+
+ :param sid: requested segment id
+ """
+ db_session = db.get_session()
+ seg_entry = ml2db.get_network_segments(db_session, sid)
+ return seg_entry[0]['segmentation_id']
--- /dev/null
+# Copyright 2014 Cisco Systems, Inc.
+# 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
+# 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 mock
+from oslo.config import cfg
+
+from neutron.plugins.ml2.drivers.cisco.dfa import cisco_dfa_rest as dc
+from neutron.plugins.ml2.drivers.cisco.dfa import config # noqa
+from neutron.tests import base
+
+
+"""This file includes test cases for cisco_dfa_rest.py."""
+
+FAKE_DCNM_IP = '1.1.1.1'
+FAKE_DCNM_USERNAME = 'dcnmuser'
+FAKE_DCNM_PASSWORD = 'dcnmpass'
+org_url = 'http://%s/rest/auto-config/organizations'
+part_url = 'http://%s/rest/auto-config/organizations/%s/partitions'
+net_url = 'http://%s/rest/auto-config/organizations/%s/partitions/%s/networks'
+del_net_url = ('http://%s/rest/auto-config/organizations/%s/partitions/%s/'
+ 'networks/segment/%s')
+
+
+class TestNetwork(object):
+ provider__segmentation_id = 123456
+ name = 'cisco_test_network'
+ config_profile = 'defaultL2ConfigProfile'
+
+
+class TestCiscoDFAClient(base.BaseTestCase):
+ """Test cases for DFARESTClient."""
+
+ def setUp(self):
+ # Declare the test resource.
+ super(TestCiscoDFAClient, self).setUp()
+
+ dcnm_cfg = {'dcnm_ip': FAKE_DCNM_IP,
+ 'dcnm_user': FAKE_DCNM_USERNAME,
+ 'dcnm_password': FAKE_DCNM_PASSWORD}
+ for k, v in dcnm_cfg.items():
+ cfg.CONF.set_override(k, v, 'ml2_cisco_dfa')
+
+ self.dcnm_client = dc.DFARESTClient()
+ mock.patch.object(self.dcnm_client, '_send_request').start()
+ self.testnetwork = TestNetwork()
+
+ def test_create_org(self):
+ """Test create organization."""
+
+ org_name = 'Test_Project'
+ url = org_url % (cfg.CONF.ml2_cisco_dfa.dcnm_ip)
+ payload = {'organizationName': org_name,
+ 'description': org_name,
+ 'orchestrationSource': 'Openstack Controller'}
+ self.dcnm_client._create_org(org_name, org_name)
+ self.dcnm_client._send_request.assert_called_with('POST', url,
+ payload,
+ 'organization')
+
+ def test_create_partition(self):
+ """Test create partition."""
+
+ org_name = 'Cisco'
+ part_name = 'Lab'
+ url = part_url % (cfg.CONF.ml2_cisco_dfa.dcnm_ip, org_name)
+ payload = {'partitionName': part_name,
+ 'description': org_name,
+ 'organizationName': org_name}
+ self.dcnm_client._create_partition(org_name, part_name, org_name)
+ self.dcnm_client._send_request.assert_called_with('POST', url,
+ payload,
+ 'partition')
+
+ def test_create_project(self):
+ """Test create project."""
+
+ org_name = 'Cisco'
+ self.dcnm_client.create_project(org_name)
+ call_cnt = self.dcnm_client._send_request.call_count
+ self.assertEqual(2, call_cnt)
+
+ def test_create_network(self):
+ """Test create network."""
+
+ network_info = {}
+ cfg_args = []
+ seg_id = str(self.testnetwork.provider__segmentation_id)
+ config_profile = self.testnetwork.config_profile
+ network_name = self.testnetwork.name
+ tenant_name = 'Cisco'
+ url = net_url % (cfg.CONF.ml2_cisco_dfa.dcnm_ip, tenant_name,
+ tenant_name)
+
+ cfg_args.append("$segmentId=" + seg_id)
+ cfg_args.append("$netMaskLength=16")
+ cfg_args.append("$gatewayIpAddress=30.31.32.1")
+ cfg_args.append("$networkName=" + network_name)
+ cfg_args.append("$vlanId=0")
+ cfg_args.append("$vrfName=%s:%s" % (tenant_name, tenant_name))
+ cfg_args = ';'.join(cfg_args)
+
+ dhcp_scopes = {'ipRange': '10.11.12.14-10.11.12.254',
+ 'subnet': '10.11.12.13',
+ 'gateway': '10.11.12.1'}
+
+ network_info = {"segmentId": seg_id,
+ "vlanId": "0",
+ "mobilityDomainId": "None",
+ "profileName": config_profile,
+ "networkName": network_name,
+ "configArg": cfg_args,
+ "organizationName": tenant_name,
+ "partitionName": tenant_name,
+ "description": network_name,
+ "dhcpScope": dhcp_scopes}
+
+ self.dcnm_client._create_network(network_info)
+ self.dcnm_client._send_request.assert_called_with('POST', url,
+ network_info,
+ 'network')
+
+ def test_delete_network(self):
+ """Test delete network."""
+
+ seg_id = self.testnetwork.provider__segmentation_id
+ tenant_name = 'cisco'
+ url = del_net_url % (cfg.CONF.ml2_cisco_dfa.dcnm_ip,
+ tenant_name, tenant_name, seg_id)
+ self.dcnm_client.delete_network(tenant_name, self.testnetwork)
+ self.dcnm_client._send_request.assert_called_with('DELETE', url,
+ '', 'network')
+
+ def test_delete_tenant(self):
+ """Test delete tenant."""
+
+ tenant_name = 'cisco'
+ self.dcnm_client.delete_tenant(tenant_name)
+ call_cnt = self.dcnm_client._send_request.call_count
+ self.assertEqual(2, call_cnt)
--- /dev/null
+# Copyright 2014 Cisco Systems, Inc.
+# 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
+# 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 mock
+from oslo.config import cfg
+import testtools
+
+from neutron.common import exceptions as n_exc
+from neutron.plugins.ml2.drivers.cisco.dfa import cisco_dfa_rest
+from neutron.plugins.ml2.drivers.cisco.dfa import config
+from neutron.plugins.ml2.drivers.cisco.dfa import dfa_exceptions as dexc
+from neutron.plugins.ml2.drivers.cisco.dfa import dfa_instance_api
+from neutron.plugins.ml2.drivers.cisco.dfa import mech_cisco_dfa
+from neutron.plugins.ml2.drivers.cisco.dfa import project_events
+from neutron.plugins.ml2.drivers.cisco.dfa import projects_cache_db_v2
+from neutron.tests import base
+
+
+FAKE_NETWORK_NAME = 'test_dfa_network'
+FAKE_NETWORK_ID = '949fdd05-a26a-4819-a829-9fc2285de6ff'
+FAKE_CFG_PROF_ID = '8c30f360ffe948109c28ab56f69a82e1'
+FAKE_SEG_ID = 12345
+FAKE_PROJECT_NAME = 'test_dfa_project'
+FAKE_PROJECT_ID = 'aee5da7e699444889c662cf7ec1c8de7'
+FAKE_CFG_PROFILE_NAME = 'defaultNetworkL2Profile'
+FAKE_INSTANCE_NAME = 'test_dfa_instance'
+FAKE_SUBNET_ID = '1a3c5ee1-cb92-4fd8-bff1-8312ac295d64'
+FAKE_PORT_ID = 'ea0d92cf-d0cb-4ed2-bbcf-ed7c6aaea4cb'
+FAKE_DEVICE_ID = '20305657-78b7-48f4-a7cd-1edf3edbfcad'
+FAKE_SECURITY_GRP_ID = '4b5b387d-cf21-4594-b926-f5a5c602295f'
+FAKE_MAC_ADDR = 'fa:16:3e:70:15:c4'
+FAKE_IP_ADDR = '23.24.25.4'
+FAKE_GW_ADDR = '23.24.25.1'
+FAKE_DHCP_IP_RANGE_START = '23.24.25.2'
+FAKE_DHCP_IP_RANGE_END = '23.24.25.254'
+FAKE_HOST_ID = 'test_dfa_host'
+FAKE_FWD_MODE = 'proxy-gateway'
+FAKE_DCNM_USER = 'cisco'
+FAKE_DCNM_PASS = 'password'
+FAKE_DCNM_IP = '1.1.2.2'
+
+
+class FakeNetworkContext(object):
+ """Network context for testing purposes only."""
+
+ def __init__(self, network):
+ self._network = network
+ self._session = None
+
+ @property
+ def current(self):
+ return self._network
+
+ @property
+ def original(self):
+ return self._network
+
+
+class FakePortContext(object):
+ """Port context for testing purposes only."""
+
+ def __init__(self, plugin_context, port):
+ self._port = port
+ self._plugin_context = plugin_context
+ self._session = None
+
+ @property
+ def current(self):
+ return self._port
+
+
+class FakeSubnetContext(object):
+ """Subnet context for testing purposes only."""
+
+ def __init__(self, subnet):
+ self._subnet = subnet
+
+ @property
+ def current(self):
+ return self._subnet
+
+
+class TestCiscoDFAMechDriver(base.BaseTestCase):
+ """Test cases for cisco DFA mechanism driver."""
+
+ def setUp(self):
+ super(TestCiscoDFAMechDriver, self).setUp()
+
+ dcnmpatcher = mock.patch(cisco_dfa_rest.__name__ + '.DFARESTClient')
+ self.mdcnm = dcnmpatcher.start()
+
+ # Define retrun values for keystone project.
+ keys_patcher = mock.patch(project_events.__name__ + '.EventsHandler')
+ self.mkeys = keys_patcher.start()
+
+ inst_api_patcher = mock.patch(dfa_instance_api.__name__ +
+ '.DFAInstanceAPI')
+ self.m_inst_api = inst_api_patcher.start()
+
+ proj_patcher = mock.patch(projects_cache_db_v2.__name__ +
+ '.ProjectsInfoCache')
+ self.mock_proj = proj_patcher.start()
+
+ dfa_cfg_patcher = mock.patch(config.__name__ + '.CiscoDFAConfig')
+ self.m_dfa_cfg = dfa_cfg_patcher.start()
+ ml2_cisco_dfa_opts = {'dcnm_password': FAKE_DCNM_PASS,
+ 'dcnm_user': FAKE_DCNM_USER,
+ 'dcnm_ip': FAKE_DCNM_IP}
+ for opt, val in ml2_cisco_dfa_opts.items():
+ cfg.CONF.set_override(opt, val, 'ml2_cisco_dfa')
+
+ self.dfa_mech_drvr = mech_cisco_dfa.CiscoDfaMechanismDriver()
+ self.dfa_mech_drvr.initialize()
+ self.dfa_mech_drvr._keys.is_valid_project.return_value = True
+ self.net_context = self._create_network_context()
+ self.proj_info = projects_cache_db_v2.ProjectsInfoCache()
+
+ def _create_network_context(self):
+ net_info = {'name': FAKE_NETWORK_NAME,
+ 'tenant_id': FAKE_PROJECT_ID,
+ 'dfa:cfg_profile_id': FAKE_CFG_PROF_ID,
+ 'provider:segmentation_id': FAKE_SEG_ID,
+ 'id': FAKE_NETWORK_ID}
+ net_context = FakeNetworkContext(net_info)
+ net_context._plugin_context = mock.MagicMock()
+ net_context._session = net_context._plugin_context.session
+ return net_context
+
+ def _create_subnet_context(self):
+ subnet_info = {
+ 'ipv6_ra_mode': None,
+ 'allocation_pools': [{'start': FAKE_DHCP_IP_RANGE_START,
+ 'end': FAKE_DHCP_IP_RANGE_END}],
+ 'host_routes': [],
+ 'ipv6_address_mode': None,
+ 'cidr': '23.24.25.0/24',
+ 'id': FAKE_SUBNET_ID,
+ 'name': u'',
+ 'enable_dhcp': True,
+ 'network_id': FAKE_NETWORK_ID,
+ 'tenant_id': FAKE_PROJECT_ID,
+ 'dns_nameservers': [],
+ 'gateway_ip': FAKE_GW_ADDR,
+ 'ip_version': 4,
+ 'shared': False}
+ subnet_context = FakeSubnetContext(subnet_info)
+ subnet_context._plugin_context = mock.MagicMock()
+ return subnet_context
+
+ def _create_port_context(self):
+ port_info = {
+ 'status': 'ACTIVE',
+ 'binding:host_id': FAKE_HOST_ID,
+ 'allowed_address_pairs': [],
+ 'extra_dhcp_opts': [],
+ 'device_owner': u'compute:nova',
+ 'binding:profile': {},
+ 'fixed_ips': [{'subnet_id': FAKE_SUBNET_ID,
+ 'ip_address': FAKE_IP_ADDR}],
+ 'id': FAKE_PORT_ID,
+ 'security_groups': [FAKE_SECURITY_GRP_ID],
+ 'device_id': FAKE_DEVICE_ID,
+ 'name': u'',
+ 'admin_state_up': True,
+ 'network_id': FAKE_NETWORK_ID,
+ 'tenant_id': FAKE_PROJECT_ID,
+ 'binding:vif_details': {u'port_filter': True,
+ u'ovs_hybrid_plug': True},
+ 'binding:vnic_type': u'normal',
+ 'binding:vif_type': u'ovs',
+ 'mac_address': FAKE_MAC_ADDR}
+ port_context = FakePortContext(mock.MagicMock(), port_info)
+ port_context._plugin_context = mock.MagicMock()
+ port_context._session = port_context._plugin_context.session
+ return port_context
+
+ def test_create_network_postcommit_no_profile(self):
+ query = self.net_context._session.query.return_value
+ query.filter_by.return_value.one.return_value = None
+ # Profile does not exist, catch the exception.
+ with testtools.ExpectedException(n_exc.BadRequest):
+ self.dfa_mech_drvr.create_network_postcommit(self.net_context)
+
+ def test_create_network_postcommit_no_project(self):
+ self.proj_info.get_project_name.side_effect = (
+ dexc.ProjectIdNotFound(project_id=FAKE_PROJECT_ID))
+ # Project does not exist, catch the exception.
+ with testtools.ExpectedException(dexc.ProjectIdNotFound):
+ self.dfa_mech_drvr.create_network_postcommit(self.net_context)
+
+ def test_delete_network_postcommit(self):
+ self.dfa_mech_drvr.delete_network_postcommit(self.net_context)
+ self.mdcnm.delete_network.return_value = None
+ self.assertTrue(self.dfa_mech_drvr._dcnm_client.delete_network.called)
+
+ def test_create_subnet_postcommit(self):
+ subnet_ctxt = self._create_subnet_context()
+ proj_obj = self.dfa_mech_drvr.projects_cache_db_v2
+ cfgp_mock = mock.MagicMock(return_value=FAKE_CFG_PROFILE_NAME)
+ self.dfa_mech_drvr.get_config_profile_name = cfgp_mock
+ mechdrvr_mock = mock.MagicMock(return_value=self.net_context.current)
+ self.dfa_mech_drvr.get_network_entry = mechdrvr_mock
+ proj_obj.get_network_segid.return_value = FAKE_SEG_ID
+ proj_obj.get_project_name.return_value = FAKE_PROJECT_NAME
+ self.dfa_mech_drvr.create_subnet_postcommit(subnet_ctxt)
+ self.assertTrue(self.dfa_mech_drvr._dcnm_client.create_network.called)
+
+ def test_update_port_postcommit(self):
+ port_ctxt = self._create_port_context()
+ query = port_ctxt._session.query.return_value
+ query.filter_by.return_value.one.return_value.forwarding_mode = (
+ FAKE_FWD_MODE)
+ vm_info = {
+ 'status': 'up',
+ 'ip': port_ctxt.current.get('fixed_ips')[0]['ip_address'],
+ 'mac': port_ctxt.current.get('mac_address'),
+ 'segid': FAKE_SEG_ID,
+ 'inst_name': FAKE_INSTANCE_NAME,
+ 'inst_uuid': port_ctxt.current.get('device_id').replace('-', ''),
+ 'host': FAKE_HOST_ID,
+ 'port_id': port_ctxt.current.get('id'),
+ 'network_id': port_ctxt.current.get('network_id'),
+ 'oui_type': 'cisco',
+ }
+ self.proj_info.get_network_segid.return_value = FAKE_SEG_ID
+ mechdrvr_mock = self.dfa_mech_drvr._inst_api.get_instance_for_uuid
+ mechdrvr_mock.return_value = FAKE_INSTANCE_NAME
+ self.dfa_mech_drvr.dfa_notifier = mock.MagicMock()
+ self.dfa_mech_drvr.update_port_postcommit(port_ctxt)
+ self.assertTrue(self.dfa_mech_drvr.dfa_notifier.send_vm_info.called)
+ self.dfa_mech_drvr.dfa_notifier.send_vm_info.assert_called_with(
+ port_ctxt._plugin_context, vm_info)
+
+ def test_delete_port_postcommit(self):
+ port_ctxt = self._create_port_context()
+ query = port_ctxt._session.query.return_value
+ query.filter_by.return_value.one.return_value.forwarding_mode = (
+ FAKE_FWD_MODE)
+ vm_info = {
+ 'status': 'down',
+ 'ip': port_ctxt.current.get('fixed_ips')[0]['ip_address'],
+ 'mac': port_ctxt.current.get('mac_address'),
+ 'segid': FAKE_SEG_ID,
+ 'inst_name': FAKE_INSTANCE_NAME,
+ 'inst_uuid': port_ctxt.current.get('device_id').replace('-', ''),
+ 'host': FAKE_HOST_ID,
+ 'port_id': port_ctxt.current.get('id'),
+ 'network_id': port_ctxt.current.get('network_id'),
+ 'oui_type': 'cisco',
+ }
+ self.proj_info.get_network_segid.return_value = FAKE_SEG_ID
+ instapi_mock = self.dfa_mech_drvr._inst_api.get_instance_for_uuid
+ instapi_mock.return_value = FAKE_INSTANCE_NAME
+ self.dfa_mech_drvr.dfa_notifier = mock.MagicMock()
+ self.dfa_mech_drvr.delete_port_postcommit(port_ctxt)
+ self.assertTrue(self.dfa_mech_drvr.dfa_notifier.send_vm_info.called)
+ self.dfa_mech_drvr.dfa_notifier.send_vm_info.assert_called_with(
+ port_ctxt._plugin_context, vm_info)
arista = neutron.plugins.ml2.drivers.arista.mechanism_arista:AristaDriver
cisco_nexus = neutron.plugins.ml2.drivers.cisco.nexus.mech_cisco_nexus:CiscoNexusMechanismDriver
cisco_apic = neutron.plugins.ml2.drivers.cisco.apic.mechanism_apic:APICMechanismDriver
+ cisco_dfa = neutron.plugins.ml2.drivers.cisco.dfa.mech_cisco_dfa:CiscoDfaMechanismDriver
l2population = neutron.plugins.ml2.drivers.l2pop.mech_driver:L2populationMechanismDriver
bigswitch = neutron.plugins.ml2.drivers.mech_bigswitch.driver:BigSwitchMechanismDriver
ofagent = neutron.plugins.ml2.drivers.mech_ofagent:OfagentMechanismDriver