Revert "Cisco DFA ML2 Mechanism Driver"

This reverts commit 3680fd61c9.

The reverted patch incorrectly ties with Keystone and should have never been
approved since the approved version only partially addressed review concerns.

This revert also ensure migration timeline integrity.

Change-Id: If5fbdade72c762b21a477676ded816ce5be97ca5
This commit is contained in:
mark mcclain 2014-09-02 20:02:06 +00:00 committed by Mark McClain
parent eab2a21f62
commit 86b6442cac
21 changed files with 2 additions and 1856 deletions

View File

@ -20,7 +20,6 @@
# Example: mechanism_drivers = cisco,logger
# Example: mechanism_drivers = openvswitch,brocade
# Example: mechanism_drivers = linuxbridge,brocade
# Example: mechanism_drivers = openvswitch,cisco_dfa
# (ListOpt) Ordered list of extension driver entrypoints
# to be loaded from the neutron.ml2.extension_drivers namespace.

View File

@ -116,17 +116,3 @@
# 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

View File

@ -1,58 +0,0 @@
# 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')

View File

@ -16,14 +16,14 @@
"""cisco_csr_routing
Revision ID: 58fe87a01143
Revises: 4eba2f05c2f4
Revises: 32f3915891fd
Create Date: 2014-08-18 17:14:12.506356
"""
# revision identifiers, used by Alembic.
revision = '58fe87a01143'
down_revision = '469426cd2173'
down_revision = '32f3915891fd'
from alembic import op
import sqlalchemy as sa

View File

@ -60,7 +60,6 @@ from neutron.plugins.ml2.drivers.arista import db # noqa
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

View File

@ -1,109 +0,0 @@
# 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)

View File

@ -1,303 +0,0 @@
# 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)

View File

@ -1,53 +0,0 @@
# 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

View File

@ -1,22 +0,0 @@
# 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'

View File

@ -1,63 +0,0 @@
# 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.")

View File

@ -1,140 +0,0 @@
# 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

View File

@ -1,49 +0,0 @@
# 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)

View File

@ -1,59 +0,0 @@
# 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))

View File

@ -1,277 +0,0 @@
# 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)

View File

@ -1,184 +0,0 @@
# 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

View File

@ -1,95 +0,0 @@
# 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']

View File

@ -1,153 +0,0 @@
# 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)

View File

@ -1,272 +0,0 @@
# 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)

View File

@ -169,7 +169,6 @@ neutron.ml2.mechanism_drivers =
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