Merge "Apic drivers enhancements (second approach): Backend"

This commit is contained in:
Jenkins 2014-08-29 10:16:00 +00:00 committed by Gerrit Code Review
commit 7186f5664d
12 changed files with 43 additions and 2151 deletions

View File

@ -49,8 +49,8 @@
[ml2_cisco_apic]
# Hostname for the APIC controller
# apic_host=1.1.1.1
# Hostname:port list of APIC controllers
# apic_hosts=1.1.1.1:80, 1.1.1.2:8080, 1.1.1.3:80
# Username for the APIC controller
# apic_username=user
@ -58,9 +58,6 @@
# Password for the APIC controller
# apic_password=password
# Port for the APIC Controller
# apic_port=80
# Names for APIC objects used by Neutron
# Note: When deploying multiple clouds against one APIC,
# these names must be unique between the clouds.

View File

@ -1,416 +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.
#
# @author: Henry Gessau, Cisco Systems
import collections
import time
import requests
import requests.exceptions
from neutron.openstack.common import jsonutils
from neutron.openstack.common import log as logging
from neutron.plugins.ml2.drivers.cisco.apic import exceptions as cexc
LOG = logging.getLogger(__name__)
APIC_CODE_FORBIDDEN = str(requests.codes.forbidden)
# Info about a Managed Object's relative name (RN) and container.
class ManagedObjectName(collections.namedtuple(
'MoPath', ['container', 'rn_fmt', 'can_create'])):
def __new__(cls, container, rn_fmt, can_create=True):
return super(ManagedObjectName, cls).__new__(cls, container, rn_fmt,
can_create)
class ManagedObjectClass(object):
"""Information about a Managed Object (MO) class.
Constructs and keeps track of the distinguished name (DN) and relative
name (RN) of a managed object (MO) class. The DN is the RN of the MO
appended to the recursive RNs of its containers, i.e.:
DN = uni/container-RN/.../container-RN/object-RN
Also keeps track of whether the MO can be created in the APIC, as some
MOs are read-only or used for specifying relationships.
"""
supported_mos = {
'fvTenant': ManagedObjectName(None, 'tn-%s'),
'fvBD': ManagedObjectName('fvTenant', 'BD-%s'),
'fvRsBd': ManagedObjectName('fvAEPg', 'rsbd'),
'fvSubnet': ManagedObjectName('fvBD', 'subnet-[%s]'),
'fvCtx': ManagedObjectName('fvTenant', 'ctx-%s'),
'fvRsCtx': ManagedObjectName('fvBD', 'rsctx'),
'fvAp': ManagedObjectName('fvTenant', 'ap-%s'),
'fvAEPg': ManagedObjectName('fvAp', 'epg-%s'),
'fvRsProv': ManagedObjectName('fvAEPg', 'rsprov-%s'),
'fvRsCons': ManagedObjectName('fvAEPg', 'rscons-%s'),
'fvRsConsIf': ManagedObjectName('fvAEPg', 'rsconsif-%s'),
'fvRsDomAtt': ManagedObjectName('fvAEPg', 'rsdomAtt-[%s]'),
'fvRsPathAtt': ManagedObjectName('fvAEPg', 'rspathAtt-[%s]'),
'vzBrCP': ManagedObjectName('fvTenant', 'brc-%s'),
'vzSubj': ManagedObjectName('vzBrCP', 'subj-%s'),
'vzFilter': ManagedObjectName('fvTenant', 'flt-%s'),
'vzRsFiltAtt': ManagedObjectName('vzSubj', 'rsfiltAtt-%s'),
'vzEntry': ManagedObjectName('vzFilter', 'e-%s'),
'vzInTerm': ManagedObjectName('vzSubj', 'intmnl'),
'vzRsFiltAtt__In': ManagedObjectName('vzInTerm', 'rsfiltAtt-%s'),
'vzOutTerm': ManagedObjectName('vzSubj', 'outtmnl'),
'vzRsFiltAtt__Out': ManagedObjectName('vzOutTerm', 'rsfiltAtt-%s'),
'vzCPIf': ManagedObjectName('fvTenant', 'cif-%s'),
'vzRsIf': ManagedObjectName('vzCPIf', 'rsif'),
'vmmProvP': ManagedObjectName(None, 'vmmp-%s', False),
'vmmDomP': ManagedObjectName('vmmProvP', 'dom-%s'),
'vmmEpPD': ManagedObjectName('vmmDomP', 'eppd-[%s]'),
'physDomP': ManagedObjectName(None, 'phys-%s'),
'infra': ManagedObjectName(None, 'infra'),
'infraNodeP': ManagedObjectName('infra', 'nprof-%s'),
'infraLeafS': ManagedObjectName('infraNodeP', 'leaves-%s-typ-%s'),
'infraNodeBlk': ManagedObjectName('infraLeafS', 'nodeblk-%s'),
'infraRsAccPortP': ManagedObjectName('infraNodeP', 'rsaccPortP-[%s]'),
'infraAccPortP': ManagedObjectName('infra', 'accportprof-%s'),
'infraHPortS': ManagedObjectName('infraAccPortP', 'hports-%s-typ-%s'),
'infraPortBlk': ManagedObjectName('infraHPortS', 'portblk-%s'),
'infraRsAccBaseGrp': ManagedObjectName('infraHPortS', 'rsaccBaseGrp'),
'infraFuncP': ManagedObjectName('infra', 'funcprof'),
'infraAccPortGrp': ManagedObjectName('infraFuncP', 'accportgrp-%s'),
'infraRsAttEntP': ManagedObjectName('infraAccPortGrp', 'rsattEntP'),
'infraAttEntityP': ManagedObjectName('infra', 'attentp-%s'),
'infraRsDomP': ManagedObjectName('infraAttEntityP', 'rsdomP-[%s]'),
'infraRsVlanNs__phys': ManagedObjectName('physDomP', 'rsvlanNs'),
'infraRsVlanNs__vmm': ManagedObjectName('vmmDomP', 'rsvlanNs'),
'fvnsVlanInstP': ManagedObjectName('infra', 'vlanns-%s-%s'),
'fvnsEncapBlk__vlan': ManagedObjectName('fvnsVlanInstP',
'from-%s-to-%s'),
'fvnsVxlanInstP': ManagedObjectName('infra', 'vxlanns-%s'),
'fvnsEncapBlk__vxlan': ManagedObjectName('fvnsVxlanInstP',
'from-%s-to-%s'),
# Read-only
'fabricTopology': ManagedObjectName(None, 'topology', False),
'fabricPod': ManagedObjectName('fabricTopology', 'pod-%s', False),
'fabricPathEpCont': ManagedObjectName('fabricPod', 'paths-%s', False),
'fabricPathEp': ManagedObjectName('fabricPathEpCont', 'pathep-%s',
False),
}
# Note(Henry): The use of a mutable default argument _inst_cache is
# intentional. It persists for the life of MoClass to cache instances.
# noinspection PyDefaultArgument
def __new__(cls, mo_class, _inst_cache={}):
"""Ensure we create only one instance per mo_class."""
try:
return _inst_cache[mo_class]
except KeyError:
new_inst = super(ManagedObjectClass, cls).__new__(cls)
new_inst.__init__(mo_class)
_inst_cache[mo_class] = new_inst
return new_inst
def __init__(self, mo_class):
self.klass = mo_class
self.klass_name = mo_class.split('__')[0]
mo = self.supported_mos[mo_class]
self.container = mo.container
self.rn_fmt = mo.rn_fmt
self.dn_fmt, self.args = self._dn_fmt()
self.arg_count = self.dn_fmt.count('%s')
rn_has_arg = self.rn_fmt.count('%s')
self.can_create = rn_has_arg and mo.can_create
def _dn_fmt(self):
"""Build the distinguished name format using container and RN.
DN = uni/container-RN/.../container-RN/object-RN
Also make a list of the required name arguments.
Note: Call this method only once at init.
"""
arg = [self.klass] if '%s' in self.rn_fmt else []
if self.container:
container = ManagedObjectClass(self.container)
dn_fmt = '%s/%s' % (container.dn_fmt, self.rn_fmt)
args = container.args + arg
return dn_fmt, args
return 'uni/%s' % self.rn_fmt, arg
def dn(self, *args):
"""Return the distinguished name for a managed object."""
return self.dn_fmt % args
class ApicSession(object):
"""Manages a session with the APIC."""
def __init__(self, host, port, usr, pwd, ssl):
protocol = ssl and 'https' or 'http'
self.api_base = '%s://%s:%s/api' % (protocol, host, port)
self.session = requests.Session()
self.session_deadline = 0
self.session_timeout = 0
self.cookie = {}
# Log in
self.authentication = None
self.username = None
self.password = None
if usr and pwd:
self.login(usr, pwd)
@staticmethod
def _make_data(key, **attrs):
"""Build the body for a msg out of a key and some attributes."""
return jsonutils.dumps({key: {'attributes': attrs}})
def _api_url(self, api):
"""Create the URL for a generic API."""
return '%s/%s.json' % (self.api_base, api)
def _mo_url(self, mo, *args):
"""Create a URL for a MO lookup by DN."""
dn = mo.dn(*args)
return '%s/mo/%s.json' % (self.api_base, dn)
def _qry_url(self, mo):
"""Create a URL for a query lookup by MO class."""
return '%s/class/%s.json' % (self.api_base, mo.klass_name)
def _check_session(self):
"""Check that we are logged in and ensure the session is active."""
if not self.authentication:
raise cexc.ApicSessionNotLoggedIn
if time.time() > self.session_deadline:
self.refresh()
def _send(self, request, url, data=None, refreshed=None):
"""Send a request and process the response."""
if data is None:
response = request(url, cookies=self.cookie)
else:
response = request(url, data=data, cookies=self.cookie)
if response is None:
raise cexc.ApicHostNoResponse(url=url)
# Every request refreshes the timeout
self.session_deadline = time.time() + self.session_timeout
if data is None:
request_str = url
else:
request_str = '%s, data=%s' % (url, data)
LOG.debug(_("data = %s"), data)
# imdata is where the APIC returns the useful information
imdata = response.json().get('imdata')
LOG.debug(_("Response: %s"), imdata)
if response.status_code != requests.codes.ok:
try:
err_code = imdata[0]['error']['attributes']['code']
err_text = imdata[0]['error']['attributes']['text']
except (IndexError, KeyError):
err_code = '[code for APIC error not found]'
err_text = '[text for APIC error not found]'
# If invalid token then re-login and retry once
if (not refreshed and err_code == APIC_CODE_FORBIDDEN and
err_text.lower().startswith('token was invalid')):
self.login()
return self._send(request, url, data=data, refreshed=True)
raise cexc.ApicResponseNotOk(request=request_str,
status=response.status_code,
reason=response.reason,
err_text=err_text, err_code=err_code)
return imdata
# REST requests
def get_data(self, request):
"""Retrieve generic data from the server."""
self._check_session()
url = self._api_url(request)
return self._send(self.session.get, url)
def get_mo(self, mo, *args):
"""Retrieve a managed object by its distinguished name."""
self._check_session()
url = self._mo_url(mo, *args) + '?query-target=self'
return self._send(self.session.get, url)
def list_mo(self, mo):
"""Retrieve the list of managed objects for a class."""
self._check_session()
url = self._qry_url(mo)
return self._send(self.session.get, url)
def post_data(self, request, data):
"""Post generic data to the server."""
self._check_session()
url = self._api_url(request)
return self._send(self.session.post, url, data=data)
def post_mo(self, mo, *args, **kwargs):
"""Post data for a managed object to the server."""
self._check_session()
url = self._mo_url(mo, *args)
data = self._make_data(mo.klass_name, **kwargs)
return self._send(self.session.post, url, data=data)
# Session management
def _save_cookie(self, request, response):
"""Save the session cookie and its expiration time."""
imdata = response.json().get('imdata')
if response.status_code == requests.codes.ok:
attributes = imdata[0]['aaaLogin']['attributes']
try:
self.cookie = {'APIC-Cookie': attributes['token']}
except KeyError:
raise cexc.ApicResponseNoCookie(request=request)
timeout = int(attributes['refreshTimeoutSeconds'])
LOG.debug(_("APIC session will expire in %d seconds"), timeout)
# Give ourselves a few seconds to refresh before timing out
self.session_timeout = timeout - 5
self.session_deadline = time.time() + self.session_timeout
else:
attributes = imdata[0]['error']['attributes']
return attributes
def login(self, usr=None, pwd=None):
"""Log in to controller. Save user name and authentication."""
usr = usr or self.username
pwd = pwd or self.password
name_pwd = self._make_data('aaaUser', name=usr, pwd=pwd)
url = self._api_url('aaaLogin')
try:
response = self.session.post(url, data=name_pwd, timeout=10.0)
except requests.exceptions.Timeout:
raise cexc.ApicHostNoResponse(url=url)
attributes = self._save_cookie('aaaLogin', response)
if response.status_code == requests.codes.ok:
self.username = usr
self.password = pwd
self.authentication = attributes
else:
self.authentication = None
raise cexc.ApicResponseNotOk(request=url,
status=response.status_code,
reason=response.reason,
err_text=attributes['text'],
err_code=attributes['code'])
def refresh(self):
"""Called when a session has timed out or almost timed out."""
url = self._api_url('aaaRefresh')
response = self.session.get(url, cookies=self.cookie)
attributes = self._save_cookie('aaaRefresh', response)
if response.status_code == requests.codes.ok:
# We refreshed before the session timed out.
self.authentication = attributes
else:
err_code = attributes['code']
err_text = attributes['text']
if (err_code == APIC_CODE_FORBIDDEN and
err_text.lower().startswith('token was invalid')):
# This means the token timed out, so log in again.
LOG.debug(_("APIC session timed-out, logging in again."))
self.login()
else:
self.authentication = None
raise cexc.ApicResponseNotOk(request=url,
status=response.status_code,
reason=response.reason,
err_text=err_text,
err_code=err_code)
def logout(self):
"""End session with controller."""
if not self.username:
self.authentication = None
if self.authentication:
data = self._make_data('aaaUser', name=self.username)
self.post_data('aaaLogout', data=data)
self.authentication = None
class ManagedObjectAccess(object):
"""CRUD operations on APIC Managed Objects."""
def __init__(self, session, mo_class):
self.session = session
self.mo = ManagedObjectClass(mo_class)
def _create_container(self, *args):
"""Recursively create all container objects."""
if self.mo.container:
container = ManagedObjectAccess(self.session, self.mo.container)
if container.mo.can_create:
container_args = args[0: container.mo.arg_count]
container._create_container(*container_args)
container.session.post_mo(container.mo, *container_args)
def create(self, *args, **kwargs):
self._create_container(*args)
if self.mo.can_create and 'status' not in kwargs:
kwargs['status'] = 'created'
return self.session.post_mo(self.mo, *args, **kwargs)
def _mo_attributes(self, obj_data):
if (self.mo.klass_name in obj_data and
'attributes' in obj_data[self.mo.klass_name]):
return obj_data[self.mo.klass_name]['attributes']
def get(self, *args):
"""Return a dict of the MO's attributes, or None."""
imdata = self.session.get_mo(self.mo, *args)
if imdata:
return self._mo_attributes(imdata[0])
def list_all(self):
imdata = self.session.list_mo(self.mo)
return filter(None, [self._mo_attributes(obj) for obj in imdata])
def list_names(self):
return [obj['name'] for obj in self.list_all()]
def update(self, *args, **kwargs):
return self.session.post_mo(self.mo, *args, **kwargs)
def delete(self, *args):
return self.session.post_mo(self.mo, *args, status='deleted')
class RestClient(ApicSession):
"""APIC REST client for OpenStack Neutron."""
def __init__(self, host, port=80, usr=None, pwd=None, ssl=False):
"""Establish a session with the APIC."""
super(RestClient, self).__init__(host, port, usr, pwd, ssl)
def __getattr__(self, mo_class):
"""Add supported MOs as properties on demand."""
if mo_class not in ManagedObjectClass.supported_mos:
raise cexc.ApicManagedObjectNotSupported(mo_class=mo_class)
self.__dict__[mo_class] = ManagedObjectAccess(self, mo_class)
return self.__dict__[mo_class]

View File

@ -1,559 +0,0 @@
# Copyright (c) 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.
#
# @author: Arvind Somya (asomya@cisco.com), Cisco Systems Inc.
import itertools
import uuid
from oslo.config import cfg
from neutron.openstack.common import excutils
from neutron.plugins.ml2.drivers.cisco.apic import apic_client
from neutron.plugins.ml2.drivers.cisco.apic import apic_model
from neutron.plugins.ml2.drivers.cisco.apic import config
from neutron.plugins.ml2.drivers.cisco.apic import exceptions as cexc
AP_NAME = 'openstack'
CONTEXT_ENFORCED = '1'
CONTEXT_UNENFORCED = '2'
CONTEXT_DEFAULT = 'default'
DN_KEY = 'dn'
PORT_DN_PATH = 'topology/pod-1/paths-%s/pathep-[eth%s]'
SCOPE_GLOBAL = 'global'
SCOPE_TENANT = 'tenant'
TENANT_COMMON = 'common'
def group_by_ranges(i):
"""Group a list of numbers into tuples representing contiguous ranges."""
for a, b in itertools.groupby(enumerate(sorted(i)), lambda (x, y): y - x):
b = list(b)
yield b[0][1], b[-1][1]
class APICManager(object):
"""Class to manage APIC translations and workflow.
This class manages translation from Neutron objects to APIC
managed objects and contains workflows to implement these
translations.
"""
def __init__(self):
self.db = apic_model.ApicDbModel()
apic_conf = cfg.CONF.ml2_cisco_apic
self.switch_dict = config.create_switch_dictionary()
# Connect to the APIC
self.apic = apic_client.RestClient(
apic_conf.apic_host,
apic_conf.apic_port,
apic_conf.apic_username,
apic_conf.apic_password
)
self.port_profiles = {}
self.vmm_domain = None
self.phys_domain = None
self.vlan_ns = None
self.node_profiles = {}
self.entity_profile = None
self.function_profile = None
self.clear_node_profiles = apic_conf.apic_clear_node_profiles
def ensure_infra_created_on_apic(self):
"""Ensure the infrastructure is setup.
Loop over the switch dictionary from the config and
setup profiles for switches, modules and ports
"""
# Loop over switches
for switch in self.switch_dict:
# Create a node profile for this switch
self.ensure_node_profile_created_for_switch(switch)
# Check if a port profile exists for this node
ppname = self.check_infra_port_profiles(switch)
# Gather port ranges for this switch
modules = self.gather_infra_module_ports(switch)
# Setup each module and port range
for module in modules:
profile = self.db.get_profile_for_module(switch, ppname,
module)
if not profile:
# Create host port selector for this module
hname = uuid.uuid4()
try:
self.apic.infraHPortS.create(ppname, hname, 'range')
# Add relation to the function profile
fpdn = self.function_profile[DN_KEY]
self.apic.infraRsAccBaseGrp.create(ppname, hname,
'range', tDn=fpdn)
modules[module].sort()
except (cexc.ApicResponseNotOk, KeyError):
with excutils.save_and_reraise_exception():
self.apic.infraHPortS.delete(ppname, hname,
'range')
else:
hname = profile.hpselc_id
ranges = group_by_ranges(modules[module])
# Add this module and ports to the profile
for prange in ranges:
# Check if this port block is already added to the profile
if not self.db.get_profile_for_module_and_ports(
switch, ppname, module, prange[0], prange[-1]):
# Create port block for this port range
pbname = uuid.uuid4()
self.apic.infraPortBlk.create(ppname, hname, 'range',
pbname, fromCard=module,
toCard=module,
fromPort=str(prange[0]),
toPort=str(prange[-1]))
# Add DB row
self.db.add_profile_for_module_and_ports(
switch, ppname, hname, module,
prange[0], prange[-1])
def check_infra_port_profiles(self, switch):
"""Check and create infra port profiles for a node."""
sprofile = self.db.get_port_profile_for_node(switch)
ppname = None
if not sprofile:
# Generate uuid for port profile name
ppname = uuid.uuid4()
try:
# Create port profile for this switch
pprofile = self.ensure_port_profile_created_on_apic(ppname)
# Add port profile to node profile
ppdn = pprofile[DN_KEY]
self.apic.infraRsAccPortP.create(switch, ppdn)
except (cexc.ApicResponseNotOk, KeyError):
with excutils.save_and_reraise_exception():
# Delete port profile
self.apic.infraAccPortP.delete(ppname)
else:
ppname = sprofile.profile_id
return ppname
def gather_infra_module_ports(self, switch):
"""Build modules and ports per module dictionary."""
ports = self.switch_dict[switch]
# Gather common modules
modules = {}
for port in ports:
module, sw_port = port.split('/')
if module not in modules:
modules[module] = []
modules[module].append(int(sw_port))
return modules
def ensure_context_unenforced(self, tenant_id=TENANT_COMMON,
name=CONTEXT_DEFAULT):
"""Set the specified tenant's context to unenforced."""
ctx = self.apic.fvCtx.get(tenant_id, name)
if not ctx:
self.apic.fvCtx.create(tenant_id, name,
pcEnfPref=CONTEXT_UNENFORCED)
elif ctx['pcEnfPref'] != CONTEXT_UNENFORCED:
self.apic.fvCtx.update(tenant_id, name,
pcEnfPref=CONTEXT_UNENFORCED)
def ensure_context_enforced(self, tenant_id=TENANT_COMMON,
name=CONTEXT_DEFAULT):
"""Set the specified tenant's context to enforced."""
ctx = self.apic.fvCtx.get(tenant_id, name)
if not ctx:
self.apic.fvCtx.create(tenant_id, name, pcEnfPref=CONTEXT_ENFORCED)
elif ctx['pcEnfPref'] != CONTEXT_ENFORCED:
self.apic.fvCtx.update(tenant_id, name, pcEnfPref=CONTEXT_ENFORCED)
def ensure_entity_profile_created_on_apic(self, name):
"""Create the infrastructure entity profile."""
if self.clear_node_profiles:
self.apic.infraAttEntityP.delete(name)
self.entity_profile = self.apic.infraAttEntityP.get(name)
if not self.entity_profile:
try:
phys_dn = self.phys_domain[DN_KEY]
self.apic.infraAttEntityP.create(name)
# Attach phys domain to entity profile
self.apic.infraRsDomP.create(name, phys_dn)
self.entity_profile = self.apic.infraAttEntityP.get(name)
except (cexc.ApicResponseNotOk, KeyError):
with excutils.save_and_reraise_exception():
# Delete the created entity profile
self.apic.infraAttEntityP.delete(name)
def ensure_function_profile_created_on_apic(self, name):
"""Create the infrastructure function profile."""
if self.clear_node_profiles:
self.apic.infraAccPortGrp.delete(name)
self.function_profile = self.apic.infraAccPortGrp.get(name)
if not self.function_profile:
try:
self.apic.infraAccPortGrp.create(name)
# Attach entity profile to function profile
entp_dn = self.entity_profile[DN_KEY]
self.apic.infraRsAttEntP.create(name, tDn=entp_dn)
self.function_profile = self.apic.infraAccPortGrp.get(name)
except (cexc.ApicResponseNotOk, KeyError):
with excutils.save_and_reraise_exception():
# Delete the created function profile
self.apic.infraAccPortGrp.delete(name)
def ensure_node_profile_created_for_switch(self, switch_id):
"""Creates a switch node profile.
Create a node profile for a switch and add a switch
to the leaf node selector
"""
if self.clear_node_profiles:
self.apic.infraNodeP.delete(switch_id)
self.db.delete_profile_for_node(switch_id)
sobj = self.apic.infraNodeP.get(switch_id)
if not sobj:
try:
# Create Node profile
self.apic.infraNodeP.create(switch_id)
# Create leaf selector
lswitch_id = uuid.uuid4()
self.apic.infraLeafS.create(switch_id, lswitch_id, 'range')
# Add leaf nodes to the selector
name = uuid.uuid4()
self.apic.infraNodeBlk.create(switch_id, lswitch_id, 'range',
name, from_=switch_id,
to_=switch_id)
sobj = self.apic.infraNodeP.get(switch_id)
except (cexc.ApicResponseNotOk, KeyError):
with excutils.save_and_reraise_exception():
# Remove the node profile
self.apic.infraNodeP.delete(switch_id)
self.node_profiles[switch_id] = {
'object': sobj
}
def ensure_port_profile_created_on_apic(self, name):
"""Create a port profile."""
try:
self.apic.infraAccPortP.create(name)
return self.apic.infraAccPortP.get(name)
except (cexc.ApicResponseNotOk, KeyError):
with excutils.save_and_reraise_exception():
self.apic.infraAccPortP.delete(name)
def ensure_vmm_domain_created_on_apic(self, vmm_name,
vlan_ns=None, vxlan_ns=None):
"""Create Virtual Machine Manager domain.
Creates the VMM domain on the APIC and adds a VLAN or VXLAN
namespace to that VMM domain.
TODO (asomya): Add VXLAN support
"""
provider = 'VMware'
if self.clear_node_profiles:
self.apic.vmmDomP.delete(provider, vmm_name)
self.vmm_domain = self.apic.vmmDomP.get(provider, vmm_name)
if not self.vmm_domain:
try:
self.apic.vmmDomP.create(provider, vmm_name)
if vlan_ns:
vlan_ns_dn = vlan_ns[DN_KEY]
self.apic.infraRsVlanNs__vmm.create(provider, vmm_name,
tDn=vlan_ns_dn)
self.vmm_domain = self.apic.vmmDomP.get(provider, vmm_name)
except (cexc.ApicResponseNotOk, KeyError):
with excutils.save_and_reraise_exception():
# Delete the VMM domain
self.apic.vmmDomP.delete(provider, vmm_name)
def ensure_phys_domain_created_on_apic(self, phys_name,
vlan_ns=None):
"""Create Virtual Machine Manager domain.
Creates the VMM domain on the APIC and adds a VLAN or VXLAN
namespace to that VMM domain.
TODO (asomya): Add VXLAN support
"""
if self.clear_node_profiles:
self.apic.physDomP.delete(phys_name)
self.phys_domain = self.apic.physDomP.get(phys_name)
if not self.phys_domain:
try:
self.apic.physDomP.create(phys_name)
if vlan_ns:
vlan_ns_dn = vlan_ns[DN_KEY]
self.apic.infraRsVlanNs__phys.create(phys_name,
tDn=vlan_ns_dn)
self.phys_domain = self.apic.physDomP.get(phys_name)
except (cexc.ApicResponseNotOk, KeyError):
with excutils.save_and_reraise_exception():
# Delete the physical domain
self.apic.physDomP.delete(phys_name)
def ensure_vlan_ns_created_on_apic(self, name, vlan_min, vlan_max):
"""Creates a static VLAN namespace with the given vlan range."""
ns_args = name, 'static'
if self.clear_node_profiles:
self.apic.fvnsVlanInstP.delete(name, 'dynamic')
self.apic.fvnsVlanInstP.delete(*ns_args)
self.vlan_ns = self.apic.fvnsVlanInstP.get(*ns_args)
if not self.vlan_ns:
try:
self.apic.fvnsVlanInstP.create(*ns_args)
vlan_min = 'vlan-' + vlan_min
vlan_max = 'vlan-' + vlan_max
ns_blk_args = name, 'static', vlan_min, vlan_max
vlan_encap = self.apic.fvnsEncapBlk__vlan.get(*ns_blk_args)
if not vlan_encap:
ns_kw_args = {
'name': 'encap',
'from': vlan_min,
'to': vlan_max
}
self.apic.fvnsEncapBlk__vlan.create(*ns_blk_args,
**ns_kw_args)
self.vlan_ns = self.apic.fvnsVlanInstP.get(*ns_args)
return self.vlan_ns
except (cexc.ApicResponseNotOk, KeyError):
with excutils.save_and_reraise_exception():
# Delete the vlan namespace
self.apic.fvnsVlanInstP.delete(*ns_args)
def ensure_tenant_created_on_apic(self, tenant_id):
"""Make sure a tenant exists on the APIC."""
if not self.apic.fvTenant.get(tenant_id):
self.apic.fvTenant.create(tenant_id)
def ensure_bd_created_on_apic(self, tenant_id, bd_id):
"""Creates a Bridge Domain on the APIC."""
if not self.apic.fvBD.get(tenant_id, bd_id):
try:
self.apic.fvBD.create(tenant_id, bd_id)
# Add default context to the BD
self.ensure_context_enforced()
self.apic.fvRsCtx.create(tenant_id, bd_id,
tnFvCtxName=CONTEXT_DEFAULT)
except (cexc.ApicResponseNotOk, KeyError):
with excutils.save_and_reraise_exception():
# Delete the bridge domain
self.apic.fvBD.delete(tenant_id, bd_id)
def delete_bd_on_apic(self, tenant_id, bd_id):
"""Deletes a Bridge Domain from the APIC."""
self.apic.fvBD.delete(tenant_id, bd_id)
def ensure_subnet_created_on_apic(self, tenant_id, bd_id, gw_ip):
"""Creates a subnet on the APIC
The gateway ip (gw_ip) should be specified as a CIDR
e.g. 10.0.0.1/24
"""
if not self.apic.fvSubnet.get(tenant_id, bd_id, gw_ip):
self.apic.fvSubnet.create(tenant_id, bd_id, gw_ip)
def ensure_filter_created_on_apic(self, tenant_id, filter_id):
"""Create a filter on the APIC."""
if not self.apic.vzFilter.get(tenant_id, filter_id):
self.apic.vzFilter.create(tenant_id, filter_id)
def ensure_epg_created_for_network(self, tenant_id, network_id, net_name):
"""Creates an End Point Group on the APIC.
Create a new EPG on the APIC for the network spcified. This information
is also tracked in the local DB and associate the bridge domain for the
network with the EPG created.
"""
# Check if an EPG is already present for this network
epg = self.db.get_epg_for_network(network_id)
if epg:
return epg
# Create a new EPG on the APIC
epg_uid = '-'.join([str(net_name), str(uuid.uuid4())])
try:
self.apic.fvAEPg.create(tenant_id, AP_NAME, epg_uid)
# Add bd to EPG
bd = self.apic.fvBD.get(tenant_id, network_id)
bd_name = bd['name']
# Create fvRsBd
self.apic.fvRsBd.create(tenant_id, AP_NAME, epg_uid,
tnFvBDName=bd_name)
# Add EPG to physical domain
phys_dn = self.phys_domain[DN_KEY]
self.apic.fvRsDomAtt.create(tenant_id, AP_NAME, epg_uid, phys_dn)
except (cexc.ApicResponseNotOk, KeyError):
with excutils.save_and_reraise_exception():
# Delete the EPG
self.apic.fvAEPg.delete(tenant_id, AP_NAME, epg_uid)
# Stick it in the DB
epg = self.db.write_epg_for_network(network_id, epg_uid)
return epg
def delete_epg_for_network(self, tenant_id, network_id):
"""Deletes the EPG from the APIC and removes it from the DB."""
# Check if an EPG is already present for this network
epg = self.db.get_epg_for_network(network_id)
if not epg:
return False
# Delete this epg
self.apic.fvAEPg.delete(tenant_id, AP_NAME, epg.epg_id)
# Remove DB row
self.db.delete_epg(epg)
def create_tenant_filter(self, tenant_id):
"""Creates a tenant filter and a generic entry under it."""
fuuid = uuid.uuid4()
try:
# Create a new tenant filter
self.apic.vzFilter.create(tenant_id, fuuid)
# Create a new entry
euuid = uuid.uuid4()
self.apic.vzEntry.create(tenant_id, fuuid, euuid)
return fuuid
except (cexc.ApicResponseNotOk, KeyError):
with excutils.save_and_reraise_exception():
self.apic.vzFilter.delete(tenant_id, fuuid)
def set_contract_for_epg(self, tenant_id, epg_id,
contract_id, provider=False):
"""Set the contract for an EPG.
By default EPGs are consumers to a contract. Set provider flag
for a single EPG to act as a contract provider.
"""
if provider:
try:
self.apic.fvRsProv.create(tenant_id, AP_NAME,
epg_id, contract_id)
self.db.set_provider_contract(epg_id)
self.make_tenant_contract_global(tenant_id)
except (cexc.ApicResponseNotOk, KeyError):
with excutils.save_and_reraise_exception():
self.make_tenant_contract_local(tenant_id)
self.apic.fvRsProv.delete(tenant_id, AP_NAME,
epg_id, contract_id)
else:
self.apic.fvRsCons.create(tenant_id, AP_NAME, epg_id, contract_id)
def delete_contract_for_epg(self, tenant_id, epg_id,
contract_id, provider=False):
"""Delete the contract for an End Point Group.
Check if the EPG was a provider and attempt to grab another contract
consumer from the DB and set that as the new contract provider.
"""
if provider:
self.apic.fvRsProv.delete(tenant_id, AP_NAME, epg_id, contract_id)
self.db.unset_provider_contract(epg_id)
# Pick out another EPG to set as contract provider
epg = self.db.get_an_epg(epg_id)
self.update_contract_for_epg(tenant_id, epg.epg_id,
contract_id, True)
else:
self.apic.fvRsCons.delete(tenant_id, AP_NAME, epg_id, contract_id)
def update_contract_for_epg(self, tenant_id, epg_id,
contract_id, provider=False):
"""Updates the contract for an End Point Group."""
self.apic.fvRsCons.delete(tenant_id, AP_NAME, epg_id, contract_id)
self.set_contract_for_epg(tenant_id, epg_id, contract_id, provider)
def create_tenant_contract(self, tenant_id):
"""Creates a tenant contract.
Create a tenant contract if one doesn't exist. Also create a
subject, filter and entry and set the filters to allow all
protocol traffic on all ports
"""
contract = self.db.get_contract_for_tenant(tenant_id)
if not contract:
cuuid = uuid.uuid4()
try:
# Create contract
self.apic.vzBrCP.create(tenant_id, cuuid, scope=SCOPE_TENANT)
acontract = self.apic.vzBrCP.get(tenant_id, cuuid)
# Create subject
suuid = uuid.uuid4()
self.apic.vzSubj.create(tenant_id, cuuid, suuid)
# Create filter and entry
tfilter = self.create_tenant_filter(tenant_id)
# Create interm and outterm
self.apic.vzInTerm.create(tenant_id, cuuid, suuid)
self.apic.vzRsFiltAtt__In.create(tenant_id, cuuid,
suuid, tfilter)
self.apic.vzOutTerm.create(tenant_id, cuuid, suuid)
self.apic.vzRsFiltAtt__Out.create(tenant_id, cuuid,
suuid, tfilter)
# Create contract interface
iuuid = uuid.uuid4()
self.apic.vzCPIf.create(tenant_id, iuuid)
self.apic.vzRsIf.create(tenant_id, iuuid,
tDn=acontract[DN_KEY])
# Store contract in DB
contract = self.db.write_contract_for_tenant(tenant_id,
cuuid, tfilter)
except (cexc.ApicResponseNotOk, KeyError):
with excutils.save_and_reraise_exception():
# Delete tenant contract
self.apic.vzBrCP.delete(tenant_id, cuuid)
return contract
def make_tenant_contract_global(self, tenant_id):
"""Mark the tenant contract's scope to global."""
contract = self.db.get_contract_for_tenant(tenant_id)
self.apic.vzBrCP.update(tenant_id, contract.contract_id,
scope=SCOPE_GLOBAL)
def make_tenant_contract_local(self, tenant_id):
"""Mark the tenant contract's scope to tenant."""
contract = self.db.get_contract_for_tenant(tenant_id)
self.apic.vzBrCP.update(tenant_id, contract.contract_id,
scope=SCOPE_TENANT)
def ensure_path_created_for_port(self, tenant_id, network_id,
host_id, encap, net_name):
"""Create path attribute for an End Point Group."""
encap = 'vlan-' + str(encap)
epg = self.ensure_epg_created_for_network(tenant_id, network_id,
net_name)
eid = epg.epg_id
# Get attached switch and port for this host
host_config = config.get_switch_and_port_for_host(host_id)
if not host_config:
raise cexc.ApicHostNotConfigured(host=host_id)
switch, port = host_config
pdn = PORT_DN_PATH % (switch, port)
# Check if exists
patt = self.apic.fvRsPathAtt.get(tenant_id, AP_NAME, eid, pdn)
if not patt:
self.apic.fvRsPathAtt.create(tenant_id, AP_NAME, eid, pdn,
encap=encap, mode="regular",
instrImedcy="immediate")

View File

@ -19,14 +19,16 @@ from oslo.config import cfg
apic_opts = [
cfg.StrOpt('apic_host',
help=_("Host name or IP Address of the APIC controller")),
cfg.ListOpt('apic_hosts',
default=[],
help=_("An ordered list of host names or IP addresses of "
"the APIC controller(s).")),
cfg.StrOpt('apic_username',
help=_("Username for the APIC controller")),
cfg.StrOpt('apic_password',
help=_("Password for the APIC controller"), secret=True),
cfg.StrOpt('apic_port',
help=_("Communication port for the APIC controller")),
cfg.BoolOpt('apic_use_ssl', default=True,
help=_("Use SSL to connect to the APIC controller")),
cfg.StrOpt('apic_vmm_provider', default='VMware',
help=_("Name for the VMM domain provider")),
cfg.StrOpt('apic_vmm_domain', default='openstack',

View File

@ -1,59 +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.
#
# @author: Henry Gessau, Cisco Systems
"""Exceptions used by Cisco APIC ML2 mechanism driver."""
from neutron.common import exceptions
class ApicHostNoResponse(exceptions.NotFound):
"""No response from the APIC via the specified URL."""
message = _("No response from APIC at %(url)s")
class ApicResponseNotOk(exceptions.NeutronException):
"""A response from the APIC was not HTTP OK."""
message = _("APIC responded with HTTP status %(status)s: %(reason)s, "
"Request: '%(request)s', "
"APIC error code %(err_code)s: %(err_text)s")
class ApicResponseNoCookie(exceptions.NeutronException):
"""A response from the APIC did not contain an expected cookie."""
message = _("APIC failed to provide cookie for %(request)s request")
class ApicSessionNotLoggedIn(exceptions.NotAuthorized):
"""Attempted APIC operation while not logged in to APIC."""
message = _("Authorized APIC session not established")
class ApicHostNotConfigured(exceptions.NotAuthorized):
"""The switch and port for the specified host are not configured."""
message = _("The switch and port for host '%(host)s' are not configured")
class ApicManagedObjectNotSupported(exceptions.NeutronException):
"""Attempted to use an unsupported Managed Object."""
message = _("Managed Object '%(mo_class)s' is not supported")
class ApicMultipleVlanRanges(exceptions.NeutronException):
"""Multiple VLAN ranges specified."""
message = _("Multiple VLAN ranges are not supported in the APIC plugin. "
"Please specify a single VLAN range. "
"Current config: '%(vlan_ranges)s'")

View File

@ -15,6 +15,7 @@
#
# @author: Arvind Somya (asomya@cisco.com), Cisco Systems Inc.
from apicapi import apic_manager
import netaddr
from oslo.config import cfg
@ -23,8 +24,8 @@ from neutron.extensions import portbindings
from neutron.openstack.common import log
from neutron.plugins.common import constants
from neutron.plugins.ml2 import driver_api as api
from neutron.plugins.ml2.drivers.cisco.apic import apic_manager
from neutron.plugins.ml2.drivers.cisco.apic import exceptions as apic_exc
from neutron.plugins.ml2.drivers.cisco.apic import apic_model
from neutron.plugins.ml2.drivers.cisco.apic import config
LOG = log.getLogger(__name__)
@ -32,40 +33,18 @@ LOG = log.getLogger(__name__)
class APICMechanismDriver(api.MechanismDriver):
@staticmethod
def get_apic_manager():
apic_config = cfg.CONF.ml2_cisco_apic
network_config = {
'vlan_ranges': cfg.CONF.ml2_type_vlan.network_vlan_ranges,
'switch_dict': config.create_switch_dictionary(),
}
return apic_manager.APICManager(apic_model.ApicDbModel(), log,
network_config, apic_config)
def initialize(self):
self.apic_manager = apic_manager.APICManager()
# Create a Phys domain and VLAN namespace
# Get vlan ns name
ns_name = cfg.CONF.ml2_cisco_apic.apic_vlan_ns_name
# Grab vlan ranges
if len(cfg.CONF.ml2_type_vlan.network_vlan_ranges) != 1:
raise apic_exc.ApicMultipleVlanRanges(
cfg.CONF.ml2_type_vlan.network_vlan_ranges)
vlan_ranges = cfg.CONF.ml2_type_vlan.network_vlan_ranges[0]
if ',' in vlan_ranges:
raise apic_exc.ApicMultipleVlanRanges(vlan_ranges)
(vlan_min, vlan_max) = vlan_ranges.split(':')[-2:]
# Create VLAN namespace
vlan_ns = self.apic_manager.ensure_vlan_ns_created_on_apic(ns_name,
vlan_min,
vlan_max)
phys_name = cfg.CONF.ml2_cisco_apic.apic_vmm_domain
# Create Physical domain
self.apic_manager.ensure_phys_domain_created_on_apic(phys_name,
vlan_ns)
# Create entity profile
ent_name = cfg.CONF.ml2_cisco_apic.apic_entity_profile
self.apic_manager.ensure_entity_profile_created_on_apic(ent_name)
# Create function profile
func_name = cfg.CONF.ml2_cisco_apic.apic_function_profile
self.apic_manager.ensure_function_profile_created_on_apic(func_name)
# Create infrastructure on apic
self.apic_manager = APICMechanismDriver.get_apic_manager()
self.apic_manager.ensure_infra_created_on_apic()
def _perform_port_operations(self, context):
@ -74,7 +53,6 @@ class APICMechanismDriver(api.MechanismDriver):
# Get network
network = context.network.current['id']
net_name = context.network.current['name']
# Get port
port = context.current
@ -109,11 +87,10 @@ class APICMechanismDriver(api.MechanismDriver):
for dhcp_host in dhcp_hosts:
self.apic_manager.ensure_path_created_for_port(tenant_id,
network,
dhcp_host, seg,
net_name)
dhcp_host, seg)
if host not in dhcp_hosts:
self.apic_manager.ensure_path_created_for_port(tenant_id, network,
host, seg, net_name)
host, seg)
def create_port_postcommit(self, context):
self._perform_port_operations(context)

View File

@ -21,7 +21,7 @@ from neutron.db import l3_gwmode_db
from neutron.openstack.common import excutils
from neutron.openstack.common import log as logging
from neutron.plugins.common import constants
from neutron.plugins.ml2.drivers.cisco.apic import apic_manager
from neutron.plugins.ml2.drivers.cisco.apic import mechanism_apic as ma
LOG = logging.getLogger(__name__)
@ -39,7 +39,7 @@ class ApicL3ServicePlugin(db_base_plugin_v2.NeutronDbPluginV2,
def __init__(self):
super(ApicL3ServicePlugin, self).__init__()
self.manager = apic_manager.APICManager()
self.manager = ma.APICMechanismDriver.get_apic_manager()
@staticmethod
def get_plugin_type():
@ -100,14 +100,14 @@ class ApicL3ServicePlugin(db_base_plugin_v2.NeutronDbPluginV2,
def remove_router_interface(self, context, router_id, interface_info):
"""Detach a subnet from a router."""
tenant_id = context.tenant_id
subnet_id = interface_info['subnet_id']
LOG.debug("Detaching subnet %(subnet_id)s from "
"router %(router_id)s" % {'subnet_id': subnet_id,
'router_id': router_id})
if 'subnet_id' in interface_info:
subnet = self.get_subnet(context, interface_info['subnet_id'])
network_id = subnet['network_id']
else:
port = self.get_port(context, interface_info['port_id'])
network_id = port['network_id']
# Get network for this subnet
subnet = self.get_subnet(context, subnet_id)
network_id = subnet['network_id']
network = self.get_network(context, network_id)
contract = self.manager.create_tenant_contract(tenant_id)
@ -124,8 +124,5 @@ class ApicL3ServicePlugin(db_base_plugin_v2.NeutronDbPluginV2,
return super(ApicL3ServicePlugin, self).remove_router_interface(
context, router_id, interface_info)
except Exception:
LOG.error(_("Error detaching subnet %(subnet_id)s from "
"router %(router_id)s") % {'subnet_id': subnet_id,
'router_id': router_id})
with excutils.save_and_reraise_exception():
self._add_epg_to_contract(tenant_id, epg, contract)

View File

@ -1,272 +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.
#
# @author: Henry Gessau, Cisco Systems
import mock
import requests
import requests.exceptions
from neutron.plugins.ml2.drivers.cisco.apic import apic_client as apic
from neutron.plugins.ml2.drivers.cisco.apic import exceptions as cexc
from neutron.tests import base
from neutron.tests.unit.ml2.drivers.cisco.apic import (
test_cisco_apic_common as mocked)
class TestCiscoApicClient(base.BaseTestCase, mocked.ControllerMixin):
def setUp(self):
super(TestCiscoApicClient, self).setUp()
self.set_up_mocks()
self.apic = apic.RestClient(mocked.APIC_HOST)
self.addCleanup(mock.patch.stopall)
def _mock_authenticate(self, timeout=300):
self.reset_reponses()
self.mock_apic_manager_login_responses(timeout=timeout)
self.apic.login(mocked.APIC_USR, mocked.APIC_PWD)
def test_login_by_instantiation(self):
self.reset_reponses()
self.mock_apic_manager_login_responses()
apic2 = apic.RestClient(mocked.APIC_HOST,
usr=mocked.APIC_USR, pwd=mocked.APIC_PWD)
self.assertIsNotNone(apic2.authentication)
self.assertEqual(apic2.username, mocked.APIC_USR)
def test_client_session_login_ok(self):
self._mock_authenticate()
self.assertEqual(
self.apic.authentication['userName'], mocked.APIC_USR)
self.assertTrue(self.apic.api_base.startswith('http://'))
self.assertEqual(self.apic.username, mocked.APIC_USR)
self.assertIsNotNone(self.apic.authentication)
self.apic = apic.RestClient(mocked.APIC_HOST, mocked.APIC_PORT,
ssl=True)
self.assertTrue(self.apic.api_base.startswith('https://'))
def test_client_session_login_fail(self):
self.mock_error_post_response(requests.codes.unauthorized,
code='599',
text=u'Fake error')
self.assertRaises(cexc.ApicResponseNotOk, self.apic.login,
mocked.APIC_USR, mocked.APIC_PWD)
def test_client_session_login_timeout(self):
self.response['post'].append(requests.exceptions.Timeout)
self.assertRaises(cexc.ApicHostNoResponse, self.apic.login,
mocked.APIC_USR, mocked.APIC_PWD)
def test_client_session_logout_ok(self):
self.mock_response_for_post('aaaLogout')
self.apic.logout()
self.assertIsNone(self.apic.authentication)
# Multiple signouts should not cause an error
self.apic.logout()
self.assertIsNone(self.apic.authentication)
def test_client_session_logout_fail(self):
self._mock_authenticate()
self.mock_error_post_response(requests.codes.timeout,
code='123', text='failed')
self.assertRaises(cexc.ApicResponseNotOk, self.apic.logout)
def test_query_not_logged_in(self):
self.apic.authentication = None
self.assertRaises(cexc.ApicSessionNotLoggedIn,
self.apic.fvTenant.get, mocked.APIC_TENANT)
def test_query_no_response(self):
self._mock_authenticate()
requests.Session.get = mock.Mock(return_value=None)
self.assertRaises(cexc.ApicHostNoResponse,
self.apic.fvTenant.get, mocked.APIC_TENANT)
def test_query_error_response_no_data(self):
self._mock_authenticate()
self.mock_error_get_response(requests.codes.bad) # No error attrs.
self.assertRaises(cexc.ApicResponseNotOk,
self.apic.fvTenant.get, mocked.APIC_TENANT)
def test_generic_get_data(self):
self._mock_authenticate()
self.mock_response_for_get('topSystem', name='ifc1')
top_system = self.apic.get_data('class/topSystem')
self.assertIsNotNone(top_system)
name = top_system[0]['topSystem']['attributes']['name']
self.assertEqual(name, 'ifc1')
def test_session_timeout_refresh_ok(self):
self._mock_authenticate(timeout=-1)
# Client will do refresh before getting tenant
self.mock_response_for_get('aaaLogin', token='ok',
refreshTimeoutSeconds=300)
self.mock_response_for_get('fvTenant', name=mocked.APIC_TENANT)
tenant = self.apic.fvTenant.get(mocked.APIC_TENANT)
self.assertEqual(tenant['name'], mocked.APIC_TENANT)
def test_session_timeout_refresh_no_cookie(self):
self._mock_authenticate(timeout=-1)
# Client will do refresh before getting tenant
self.mock_response_for_get('aaaLogin', notoken='test')
self.assertRaises(cexc.ApicResponseNoCookie,
self.apic.fvTenant.get, mocked.APIC_TENANT)
def test_session_timeout_refresh_error(self):
self._mock_authenticate(timeout=-1)
self.mock_error_get_response(requests.codes.timeout,
code='503', text=u'timed out')
self.assertRaises(cexc.ApicResponseNotOk,
self.apic.fvTenant.get, mocked.APIC_TENANT)
def test_session_timeout_refresh_timeout_error(self):
self._mock_authenticate(timeout=-1)
# Client will try to get refresh, we fake a refresh error.
self.mock_error_get_response(requests.codes.bad_request,
code='403',
text=u'Token was invalid. Expired.')
# Client will then try to re-login.
self.mock_apic_manager_login_responses()
# Finally the client will try to get the tenant.
self.mock_response_for_get('fvTenant', name=mocked.APIC_TENANT)
tenant = self.apic.fvTenant.get(mocked.APIC_TENANT)
self.assertEqual(tenant['name'], mocked.APIC_TENANT)
def test_lookup_mo_bad_token_retry(self):
self._mock_authenticate()
# For the first get request we mock a bad token.
self.mock_error_get_response(requests.codes.bad_request,
code='403',
text=u'Token was invalid. Expired.')
# Client will then try to re-login.
self.mock_apic_manager_login_responses()
# Then the client will retry to get the tenant.
self.mock_response_for_get('fvTenant', name=mocked.APIC_TENANT)
tenant = self.apic.fvTenant.get(mocked.APIC_TENANT)
self.assertEqual(tenant['name'], mocked.APIC_TENANT)
def test_use_unsupported_managed_object(self):
self._mock_authenticate()
# unittest.assertRaises cannot catch exceptions raised in
# __getattr__, so we need to defer the evaluation using lambda.
self.assertRaises(cexc.ApicManagedObjectNotSupported,
lambda: self.apic.nonexistentObject)
def test_lookup_nonexistant_mo(self):
self._mock_authenticate()
self.mock_response_for_get('fvTenant')
self.assertIsNone(self.apic.fvTenant.get(mocked.APIC_TENANT))
def test_lookup_existing_mo(self):
self._mock_authenticate()
self.mock_response_for_get('fvTenant', name='infra')
tenant = self.apic.fvTenant.get('infra')
self.assertEqual(tenant['name'], 'infra')
def test_list_mos_ok(self):
self._mock_authenticate()
self.mock_response_for_get('fvTenant', name='t1')
self.mock_append_to_response('fvTenant', name='t2')
tlist = self.apic.fvTenant.list_all()
self.assertIsNotNone(tlist)
self.assertEqual(len(tlist), 2)
self.assertIn({'name': 't1'}, tlist)
self.assertIn({'name': 't2'}, tlist)
def test_list_mo_names_ok(self):
self._mock_authenticate()
self.mock_response_for_get('fvTenant', name='t1')
self.mock_append_to_response('fvTenant', name='t2')
tnlist = self.apic.fvTenant.list_names()
self.assertIsNotNone(tnlist)
self.assertEqual(len(tnlist), 2)
self.assertIn('t1', tnlist)
self.assertIn('t2', tnlist)
def test_list_mos_split_class_fail(self):
self._mock_authenticate()
self.mock_response_for_get('fvnsEncapBlk', name='Blk1')
encap_blks = self.apic.fvnsEncapBlk__vlan.list_all()
self.assertEqual(len(encap_blks), 1)
def test_delete_mo_ok(self):
self._mock_authenticate()
self.mock_response_for_post('fvTenant')
self.assertTrue(self.apic.fvTenant.delete(mocked.APIC_TENANT))
def test_create_mo_ok(self):
self._mock_authenticate()
self.mock_response_for_post('fvTenant', name=mocked.APIC_TENANT)
self.mock_response_for_get('fvTenant', name=mocked.APIC_TENANT)
self.apic.fvTenant.create(mocked.APIC_TENANT)
tenant = self.apic.fvTenant.get(mocked.APIC_TENANT)
self.assertEqual(tenant['name'], mocked.APIC_TENANT)
def test_create_mo_already_exists(self):
self._mock_authenticate()
self.mock_error_post_response(requests.codes.bad_request,
code='103',
text=u'Fake 103 error')
self.assertRaises(cexc.ApicResponseNotOk,
self.apic.vmmProvP.create, mocked.APIC_VMMP)
def test_create_mo_with_prereq(self):
self._mock_authenticate()
self.mock_response_for_post('fvTenant', name=mocked.APIC_TENANT)
self.mock_response_for_post('fvBD', name=mocked.APIC_NETWORK)
self.mock_response_for_get('fvBD', name=mocked.APIC_NETWORK)
bd_args = mocked.APIC_TENANT, mocked.APIC_NETWORK
self.apic.fvBD.create(*bd_args)
network = self.apic.fvBD.get(*bd_args)
self.assertEqual(network['name'], mocked.APIC_NETWORK)
def test_create_mo_prereq_exists(self):
self._mock_authenticate()
self.mock_response_for_post('vmmDomP', name=mocked.APIC_DOMAIN)
self.mock_response_for_get('vmmDomP', name=mocked.APIC_DOMAIN)
self.apic.vmmDomP.create(mocked.APIC_VMMP, mocked.APIC_DOMAIN)
dom = self.apic.vmmDomP.get(mocked.APIC_VMMP, mocked.APIC_DOMAIN)
self.assertEqual(dom['name'], mocked.APIC_DOMAIN)
def test_create_mo_fails(self):
self._mock_authenticate()
self.mock_response_for_post('fvTenant', name=mocked.APIC_TENANT)
self.mock_error_post_response(requests.codes.bad_request,
code='not103',
text=u'Fake not103 error')
bd_args = mocked.APIC_TENANT, mocked.APIC_NETWORK
self.assertRaises(cexc.ApicResponseNotOk,
self.apic.fvBD.create, *bd_args)
def test_update_mo(self):
self._mock_authenticate()
self.mock_response_for_post('fvTenant', name=mocked.APIC_TENANT)
self.mock_response_for_get('fvTenant', name=mocked.APIC_TENANT,
more='extra')
self.apic.fvTenant.update(mocked.APIC_TENANT, more='extra')
tenant = self.apic.fvTenant.get(mocked.APIC_TENANT)
self.assertEqual(tenant['name'], mocked.APIC_TENANT)
self.assertEqual(tenant['more'], 'extra')
def test_attr_fail_empty_list(self):
self._mock_authenticate()
self.mock_response_for_get('fvTenant') # No attrs for tenant.
self.assertIsNone(self.apic.fvTenant.get(mocked.APIC_TENANT))
def test_attr_fail_other_obj(self):
self._mock_authenticate()
self.mock_response_for_get('other', name=mocked.APIC_TENANT)
self.assertIsNone(self.apic.fvTenant.get(mocked.APIC_TENANT))

View File

@ -22,13 +22,12 @@ from oslo.config import cfg
from neutron.common import config as neutron_config
from neutron.plugins.ml2 import config as ml2_config
from neutron.plugins.ml2.drivers.cisco.apic import apic_client as apic
from neutron.tests import base
OK = requests.codes.ok
APIC_HOST = 'fake.controller.local'
APIC_HOSTS = ['fake.controller.local']
APIC_PORT = 7580
APIC_USR = 'notadmin'
APIC_PWD = 'topsecret'
@ -103,20 +102,6 @@ class ControllerMixin(object):
attrs['debug_mo'] = mo # useful for debugging
self._stage_mocked_response('post', OK, mo, **attrs)
def mock_response_for_get(self, mo, **attrs):
self._stage_mocked_response('get', OK, mo, **attrs)
def mock_append_to_response(self, mo, **attrs):
# Append a MO to the last get response.
mo_attrs = attrs and {mo: {'attributes': attrs}} or {}
self.response['get'][-1].json.return_value['imdata'].append(mo_attrs)
def mock_error_post_response(self, status, **attrs):
self._stage_mocked_response('post', status, 'error', **attrs)
def mock_error_get_response(self, status, **attrs):
self._stage_mocked_response('get', status, 'error', **attrs)
def _stage_mocked_response(self, req, mock_status, mo, **attrs):
response = mock.MagicMock()
response.status_code = mock_status
@ -124,41 +109,11 @@ class ControllerMixin(object):
response.json.return_value = {'imdata': mo_attrs}
self.response[req].append(response)
def mock_responses_for_create(self, obj):
self._mock_container_responses_for_create(
apic.ManagedObjectClass(obj).container)
name = '-'.join([obj, 'name']) # useful for debugging
self._stage_mocked_response('post', OK, obj, name=name)
def _mock_container_responses_for_create(self, obj):
# Recursively generate responses for creating obj's containers.
if obj:
mo = apic.ManagedObjectClass(obj)
if mo.can_create:
if mo.container:
self._mock_container_responses_for_create(mo.container)
name = '-'.join([obj, 'name']) # useful for debugging
self._stage_mocked_response('post', OK, obj, debug_name=name)
def mock_apic_manager_login_responses(self, timeout=300):
# APIC Manager tests are based on authenticated session
self.mock_response_for_post('aaaLogin', userName=APIC_USR,
token='ok', refreshTimeoutSeconds=timeout)
def assert_responses_drained(self, req=None):
"""Fail if all the expected responses have not been consumed."""
request = {'post': self.session.post, 'get': self.session.get}
reqs = req and [req] or ['post', 'get'] # Both if none specified.
for req in reqs:
try:
request[req]('some url')
except StopIteration:
pass
else:
# User-friendly error message
msg = req + ' response queue not drained'
self.fail(msg=msg)
class ConfigMixin(object):
@ -182,10 +137,9 @@ class ConfigMixin(object):
# Configure the Cisco APIC mechanism driver
apic_test_config = {
'apic_host': APIC_HOST,
'apic_hosts': APIC_HOSTS,
'apic_username': APIC_USR,
'apic_password': APIC_PWD,
'apic_port': APIC_PORT,
'apic_vmm_domain': APIC_DOMAIN,
'apic_vlan_ns_name': APIC_VLAN_NAME,
'apic_vlan_range': '%d:%d' % (APIC_VLANID_FROM, APIC_VLANID_TO),
@ -205,21 +159,3 @@ class ConfigMixin(object):
'MultiConfigParser').start()
self.mocked_parser.return_value.read.return_value = [apic_switch_cfg]
self.mocked_parser.return_value.parsed = [apic_switch_cfg]
class DbModelMixin(object):
"""Mock the DB models for the APIC driver and service unit tests."""
def __init__(self):
self.mocked_session = None
def set_up_mocks(self):
self.mocked_session = mock.Mock()
get_session = mock.patch('neutron.db.api.get_session').start()
get_session.return_value = self.mocked_session
def mock_db_query_filterby_first_return(self, value):
"""Mock db.session.query().filterby().first() to return value."""
query = self.mocked_session.query.return_value
query.filter_by.return_value.first.return_value = value

View File

@ -1,698 +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.
#
# @author: Henry Gessau, Cisco Systems
import mock
from webob import exc as wexc
from neutron.openstack.common import uuidutils
from neutron.plugins.ml2.drivers.cisco.apic import apic_manager
from neutron.plugins.ml2.drivers.cisco.apic import exceptions as cexc
from neutron.tests import base
from neutron.tests.unit.ml2.drivers.cisco.apic import (
test_cisco_apic_common as mocked)
class TestCiscoApicManager(base.BaseTestCase,
mocked.ControllerMixin,
mocked.ConfigMixin,
mocked.DbModelMixin):
def setUp(self):
super(TestCiscoApicManager, self).setUp()
mocked.ControllerMixin.set_up_mocks(self)
mocked.ConfigMixin.set_up_mocks(self)
mocked.DbModelMixin.set_up_mocks(self)
self.mock_apic_manager_login_responses()
self.mgr = apic_manager.APICManager()
self.session = self.mgr.apic.session
self.assert_responses_drained()
self.reset_reponses()
def test_mgr_session_login(self):
login = self.mgr.apic.authentication
self.assertEqual(login['userName'], mocked.APIC_USR)
def test_mgr_session_logout(self):
self.mock_response_for_post('aaaLogout')
self.mgr.apic.logout()
self.assert_responses_drained()
self.assertIsNone(self.mgr.apic.authentication)
def test_to_range(self):
port_list = [4, 2, 3, 1, 7, 8, 10, 20, 6, 22, 21]
expected_ranges = [(1, 4), (6, 8), (10, 10), (20, 22)]
port_ranges = [r for r in apic_manager.group_by_ranges(port_list)]
self.assertEqual(port_ranges, expected_ranges)
def test_get_profiles(self):
self.mock_db_query_filterby_first_return('faked')
self.assertEqual(
self.mgr.db.get_port_profile_for_node('node'),
'faked'
)
self.assertEqual(
self.mgr.db.get_profile_for_module('node', 'prof', 'module'),
'faked'
)
self.assertEqual(
self.mgr.db.get_profile_for_module_and_ports(
'node', 'prof', 'module', 'from', 'to'
),
'faked'
)
def test_add_profile(self):
self.mgr.db.add_profile_for_module_and_ports(
'node', 'prof', 'hpselc', 'module', 'from', 'to')
self.assertTrue(self.mocked_session.add.called)
self.assertTrue(self.mocked_session.flush.called)
def test_ensure_port_profile_created(self):
port_name = mocked.APIC_PORT
self.mock_responses_for_create('infraAccPortP')
self.mock_response_for_get('infraAccPortP', name=port_name)
port = self.mgr.ensure_port_profile_created_on_apic(port_name)
self.assert_responses_drained()
self.assertEqual(port['name'], port_name)
def test_ensure_port_profile_created_exc(self):
port_name = mocked.APIC_PORT
self.mock_error_post_response(wexc.HTTPBadRequest)
self.mock_response_for_post('infraAccPortP')
self.assertRaises(cexc.ApicResponseNotOk,
self.mgr.ensure_port_profile_created_on_apic,
port_name)
self.assert_responses_drained()
def test_ensure_node_profile_created_for_switch_old(self):
old_switch = mocked.APIC_NODE_PROF
self.mock_response_for_get('infraNodeP', name=old_switch)
self.mgr.ensure_node_profile_created_for_switch(old_switch)
self.assert_responses_drained()
old_name = self.mgr.node_profiles[old_switch]['object']['name']
self.assertEqual(old_name, old_switch)
def test_ensure_node_profile_created_for_switch_new(self):
new_switch = mocked.APIC_NODE_PROF
self.mock_response_for_get('infraNodeP')
self.mock_responses_for_create('infraNodeP')
self.mock_responses_for_create('infraLeafS')
self.mock_responses_for_create('infraNodeBlk')
self.mock_response_for_get('infraNodeP', name=new_switch)
self.mgr.ensure_node_profile_created_for_switch(new_switch)
self.assert_responses_drained()
new_name = self.mgr.node_profiles[new_switch]['object']['name']
self.assertEqual(new_name, new_switch)
def test_ensure_node_profile_created_for_switch_new_exc(self):
new_switch = mocked.APIC_NODE_PROF
self.mock_response_for_get('infraNodeP')
self.mock_error_post_response(wexc.HTTPBadRequest)
self.mock_response_for_post('infraNodeP')
self.assertRaises(cexc.ApicResponseNotOk,
self.mgr.ensure_node_profile_created_for_switch,
new_switch)
self.assert_responses_drained()
def test_ensure_vmm_domain_created_old(self):
dom = mocked.APIC_DOMAIN
self.mock_response_for_get('vmmDomP', name=dom)
self.mgr.ensure_vmm_domain_created_on_apic(dom)
self.assert_responses_drained()
old_dom = self.mgr.vmm_domain['name']
self.assertEqual(old_dom, dom)
def _mock_new_vmm_dom_responses(self, dom, seg_type=None):
vmm = mocked.APIC_VMMP
dn = self.mgr.apic.vmmDomP.mo.dn(vmm, dom)
self.mock_response_for_get('vmmDomP')
self.mock_responses_for_create('vmmDomP')
if seg_type:
self.mock_responses_for_create(seg_type)
self.mock_response_for_get('vmmDomP', name=dom, dn=dn)
def test_ensure_vmm_domain_created_new_no_vlan_ns(self):
dom = mocked.APIC_DOMAIN
self._mock_new_vmm_dom_responses(dom)
self.mgr.ensure_vmm_domain_created_on_apic(dom)
self.assert_responses_drained()
new_dom = self.mgr.vmm_domain['name']
self.assertEqual(new_dom, dom)
def test_ensure_vmm_domain_created_new_no_vlan_ns_exc(self):
dom = mocked.APIC_DOMAIN
self.mock_response_for_get('vmmDomP')
self.mock_error_post_response(wexc.HTTPBadRequest)
self.mock_response_for_post('vmmDomP')
self.assertRaises(cexc.ApicResponseNotOk,
self.mgr.ensure_vmm_domain_created_on_apic, dom)
self.assert_responses_drained()
def test_ensure_vmm_domain_created_new_with_vlan_ns(self):
dom = mocked.APIC_DOMAIN
self._mock_new_vmm_dom_responses(dom, seg_type='infraRsVlanNs__vmm')
ns = {'dn': 'test_vlan_ns'}
self.mgr.ensure_vmm_domain_created_on_apic(dom, vlan_ns=ns)
self.assert_responses_drained()
new_dom = self.mgr.vmm_domain['name']
self.assertEqual(new_dom, dom)
def test_ensure_vmm_domain_created_new_with_vxlan_ns(self):
dom = mocked.APIC_DOMAIN
# TODO(Henry): mock seg_type vxlan when vxlan is ready
self._mock_new_vmm_dom_responses(dom, seg_type=None)
ns = {'dn': 'test_vxlan_ns'}
self.mgr.ensure_vmm_domain_created_on_apic(dom, vxlan_ns=ns)
self.assert_responses_drained()
new_dom = self.mgr.vmm_domain['name']
self.assertEqual(new_dom, dom)
def test_ensure_infra_created_no_infra(self):
self.mgr.switch_dict = {}
self.mgr.ensure_infra_created_on_apic()
def _ensure_infra_created_seq1_setup(self):
am = 'neutron.plugins.ml2.drivers.cisco.apic.apic_manager.APICManager'
np_create_for_switch = mock.patch(
am + '.ensure_node_profile_created_for_switch').start()
self.mock_db_query_filterby_first_return(None)
pp_create_for_switch = mock.patch(
am + '.ensure_port_profile_created_on_apic').start()
pp_create_for_switch.return_value = {'dn': 'port_profile_dn'}
return np_create_for_switch, pp_create_for_switch
def test_ensure_infra_created_seq1(self):
np_create_for_switch, pp_create_for_switch = (
self._ensure_infra_created_seq1_setup())
def _profile_for_module(aswitch, ppn, module):
profile = mock.Mock()
profile.ppn = ppn
profile.hpselc_id = '-'.join([aswitch, module, 'hpselc_id'])
return profile
self.mgr.db.get_profile_for_module = mock.Mock(
side_effect=_profile_for_module)
self.mgr.db.get_profile_for_module_and_ports = mock.Mock(
return_value=None)
self.mgr.db.add_profile_for_module_and_ports = mock.Mock()
num_switches = len(self.mgr.switch_dict)
for loop in range(num_switches):
self.mock_responses_for_create('infraRsAccPortP')
self.mock_responses_for_create('infraPortBlk')
self.mgr.ensure_infra_created_on_apic()
self.assert_responses_drained()
self.assertEqual(np_create_for_switch.call_count, num_switches)
self.assertEqual(pp_create_for_switch.call_count, num_switches)
for switch in self.mgr.switch_dict:
np_create_for_switch.assert_any_call(switch)
def test_ensure_infra_created_seq1_exc(self):
np_create_for_switch, __ = self._ensure_infra_created_seq1_setup()
self.mock_error_post_response(wexc.HTTPBadRequest)
self.mock_response_for_post('infraAccPortP')
self.assertRaises(cexc.ApicResponseNotOk,
self.mgr.ensure_infra_created_on_apic)
self.assert_responses_drained()
self.assertTrue(np_create_for_switch.called)
self.assertEqual(np_create_for_switch.call_count, 1)
def _ensure_infra_created_seq2_setup(self):
am = 'neutron.plugins.ml2.drivers.cisco.apic.apic_manager.APICManager'
np_create_for_switch = mock.patch(
am + '.ensure_node_profile_created_for_switch').start()
def _profile_for_node(aswitch):
profile = mock.Mock()
profile.profile_id = '-'.join([aswitch, 'profile_id'])
return profile
self.mgr.db.get_port_profile_for_node = mock.Mock(
side_effect=_profile_for_node)
self.mgr.db.get_profile_for_module = mock.Mock(
return_value=None)
self.mgr.function_profile = {'dn': 'dn'}
self.mgr.db.get_profile_for_module_and_ports = mock.Mock(
return_value=True)
return np_create_for_switch
def test_ensure_infra_created_seq2(self):
np_create_for_switch = self._ensure_infra_created_seq2_setup()
num_switches = len(self.mgr.switch_dict)
for loop in range(num_switches):
self.mock_responses_for_create('infraHPortS')
self.mock_responses_for_create('infraRsAccBaseGrp')
self.mgr.ensure_infra_created_on_apic()
self.assert_responses_drained()
self.assertEqual(np_create_for_switch.call_count, num_switches)
for switch in self.mgr.switch_dict:
np_create_for_switch.assert_any_call(switch)
def test_ensure_infra_created_seq2_exc(self):
np_create_for_switch = self._ensure_infra_created_seq2_setup()
self.mock_error_post_response(wexc.HTTPBadRequest)
self.mock_response_for_post('infraHPortS')
self.assertRaises(cexc.ApicResponseNotOk,
self.mgr.ensure_infra_created_on_apic)
self.assert_responses_drained()
self.assertTrue(np_create_for_switch.called)
self.assertEqual(np_create_for_switch.call_count, 1)
def test_ensure_context_unenforced_new_ctx(self):
self.mock_response_for_get('fvCtx')
self.mock_responses_for_create('fvCtx')
self.mgr.ensure_context_unenforced()
self.assert_responses_drained()
def test_ensure_context_unenforced_pref1(self):
self.mock_response_for_get('fvCtx', pcEnfPref='1')
self.mock_response_for_post('fvCtx')
self.mgr.ensure_context_unenforced()
self.assert_responses_drained()
def test_ensure_context_unenforced_pref2(self):
self.mock_response_for_get('fvCtx', pcEnfPref='2')
self.mgr.ensure_context_unenforced()
self.assert_responses_drained()
def _mock_vmm_dom_prereq(self, dom):
self._mock_new_vmm_dom_responses(dom)
self.mgr.ensure_vmm_domain_created_on_apic(dom)
def _mock_new_phys_dom_responses(self, dom, seg_type=None):
dn = self.mgr.apic.physDomP.mo.dn(dom)
self.mock_response_for_get('physDomP')
self.mock_responses_for_create('physDomP')
if seg_type:
self.mock_responses_for_create(seg_type)
self.mock_response_for_get('physDomP', name=dom, dn=dn)
def _mock_phys_dom_prereq(self, dom):
self._mock_new_phys_dom_responses(dom)
self.mgr.ensure_phys_domain_created_on_apic(dom)
def test_ensure_entity_profile_created_old(self):
ep = mocked.APIC_ATT_ENT_PROF
self.mock_response_for_get('infraAttEntityP', name=ep)
self.mgr.ensure_entity_profile_created_on_apic(ep)
self.assert_responses_drained()
def _mock_new_entity_profile(self, exc=None):
self.mock_response_for_get('infraAttEntityP')
self.mock_responses_for_create('infraAttEntityP')
self.mock_responses_for_create('infraRsDomP')
if exc:
self.mock_error_get_response(exc, code='103', text=u'Fail')
else:
self.mock_response_for_get('infraAttEntityP')
def test_ensure_entity_profile_created_new(self):
self._mock_phys_dom_prereq(mocked.APIC_PDOM)
ep = mocked.APIC_ATT_ENT_PROF
self._mock_new_entity_profile()
self.mgr.ensure_entity_profile_created_on_apic(ep)
self.assert_responses_drained()
def test_ensure_entity_profile_created_new_exc(self):
self._mock_phys_dom_prereq(mocked.APIC_PDOM)
ep = mocked.APIC_ATT_ENT_PROF
self._mock_new_entity_profile(exc=wexc.HTTPBadRequest)
self.mock_response_for_post('infraAttEntityP')
self.assertRaises(cexc.ApicResponseNotOk,
self.mgr.ensure_entity_profile_created_on_apic, ep)
self.assert_responses_drained()
def _mock_entity_profile_preqreq(self):
self._mock_phys_dom_prereq(mocked.APIC_PDOM)
ep = mocked.APIC_ATT_ENT_PROF
self._mock_new_entity_profile()
self.mgr.ensure_entity_profile_created_on_apic(ep)
def test_ensure_function_profile_created_old(self):
self._mock_entity_profile_preqreq()
fp = mocked.APIC_FUNC_PROF
self.mock_response_for_get('infraAccPortGrp', name=fp)
self.mgr.ensure_function_profile_created_on_apic(fp)
self.assert_responses_drained()
old_fp = self.mgr.function_profile['name']
self.assertEqual(old_fp, fp)
def _mock_new_function_profile(self, fp):
dn = self.mgr.apic.infraAttEntityP.mo.dn(fp)
self.mock_responses_for_create('infraAccPortGrp')
self.mock_responses_for_create('infraRsAttEntP')
self.mock_response_for_get('infraAccPortGrp', name=fp, dn=dn)
def test_ensure_function_profile_created_new(self):
fp = mocked.APIC_FUNC_PROF
dn = self.mgr.apic.infraAttEntityP.mo.dn(fp)
self.mgr.entity_profile = {'dn': dn}
self.mock_response_for_get('infraAccPortGrp')
self.mock_responses_for_create('infraAccPortGrp')
self.mock_responses_for_create('infraRsAttEntP')
self.mock_response_for_get('infraAccPortGrp', name=fp, dn=dn)
self.mgr.ensure_function_profile_created_on_apic(fp)
self.assert_responses_drained()
new_fp = self.mgr.function_profile['name']
self.assertEqual(new_fp, fp)
def test_ensure_function_profile_created_new_exc(self):
fp = mocked.APIC_FUNC_PROF
dn = self.mgr.apic.infraAttEntityP.mo.dn(fp)
self.mgr.entity_profile = {'dn': dn}
self.mock_response_for_get('infraAccPortGrp')
self.mock_error_post_response(wexc.HTTPBadRequest)
self.mock_response_for_post('infraAccPortGrp')
self.assertRaises(cexc.ApicResponseNotOk,
self.mgr.ensure_function_profile_created_on_apic, fp)
self.assert_responses_drained()
def test_ensure_vlan_ns_created_old(self):
ns = mocked.APIC_VLAN_NAME
mode = mocked.APIC_VLAN_MODE
self.mock_response_for_get('fvnsVlanInstP', name=ns, mode=mode)
new_ns = self.mgr.ensure_vlan_ns_created_on_apic(ns, '100', '199')
self.assert_responses_drained()
self.assertIsNone(new_ns)
def _mock_new_vlan_instance(self, ns, vlan_encap=None):
self.mock_responses_for_create('fvnsVlanInstP')
if vlan_encap:
self.mock_response_for_get('fvnsEncapBlk', **vlan_encap)
else:
self.mock_response_for_get('fvnsEncapBlk')
self.mock_responses_for_create('fvnsEncapBlk__vlan')
self.mock_response_for_get('fvnsVlanInstP', name=ns)
def test_ensure_vlan_ns_created_new_no_encap(self):
ns = mocked.APIC_VLAN_NAME
self.mock_response_for_get('fvnsVlanInstP')
self._mock_new_vlan_instance(ns)
new_ns = self.mgr.ensure_vlan_ns_created_on_apic(ns, '200', '299')
self.assert_responses_drained()
self.assertEqual(new_ns['name'], ns)
def test_ensure_vlan_ns_created_new_exc(self):
ns = mocked.APIC_VLAN_NAME
self.mock_response_for_get('fvnsVlanInstP')
self.mock_error_post_response(wexc.HTTPBadRequest)
self.mock_response_for_post('fvnsVlanInstP')
self.assertRaises(cexc.ApicResponseNotOk,
self.mgr.ensure_vlan_ns_created_on_apic,
ns, '200', '299')
self.assert_responses_drained()
def test_ensure_vlan_ns_created_new_with_encap(self):
ns = mocked.APIC_VLAN_NAME
self.mock_response_for_get('fvnsVlanInstP')
ns_args = {'name': 'encap', 'from': '300', 'to': '399'}
self._mock_new_vlan_instance(ns, vlan_encap=ns_args)
new_ns = self.mgr.ensure_vlan_ns_created_on_apic(ns, '300', '399')
self.assert_responses_drained()
self.assertEqual(new_ns['name'], ns)
def test_ensure_tenant_created_on_apic(self):
self.mock_response_for_get('fvTenant', name='any')
self.mgr.ensure_tenant_created_on_apic('two')
self.mock_response_for_get('fvTenant')
self.mock_responses_for_create('fvTenant')
self.mgr.ensure_tenant_created_on_apic('four')
self.assert_responses_drained()
def test_ensure_bd_created_existing_bd(self):
self.mock_response_for_get('fvBD', name='BD')
self.mgr.ensure_bd_created_on_apic('t1', 'two')
self.assert_responses_drained()
def test_ensure_bd_created_not_ctx(self):
self.mock_response_for_get('fvBD')
self.mock_responses_for_create('fvBD')
self.mock_response_for_get('fvCtx')
self.mock_responses_for_create('fvCtx')
self.mock_responses_for_create('fvRsCtx')
self.mgr.ensure_bd_created_on_apic('t2', 'three')
self.assert_responses_drained()
def test_ensure_bd_created_exc(self):
self.mock_response_for_get('fvBD')
self.mock_error_post_response(wexc.HTTPBadRequest)
self.mock_response_for_post('fvBD')
self.assertRaises(cexc.ApicResponseNotOk,
self.mgr.ensure_bd_created_on_apic, 't2', 'three')
self.assert_responses_drained()
def test_ensure_bd_created_ctx_pref1(self):
self.mock_response_for_get('fvBD')
self.mock_responses_for_create('fvBD')
self.mock_response_for_get('fvCtx', pcEnfPref='1')
self.mock_responses_for_create('fvRsCtx')
self.mgr.ensure_bd_created_on_apic('t3', 'four')
self.assert_responses_drained()
def test_ensure_bd_created_ctx_pref2(self):
self.mock_response_for_get('fvBD')
self.mock_responses_for_create('fvBD')
self.mock_response_for_get('fvCtx', pcEnfPref='2')
self.mock_response_for_post('fvCtx')
self.mock_responses_for_create('fvRsCtx')
self.mgr.ensure_bd_created_on_apic('t3', 'four')
self.assert_responses_drained()
def test_delete_bd(self):
self.mock_response_for_post('fvBD')
self.mgr.delete_bd_on_apic('t1', 'bd')
self.assert_responses_drained()
def test_ensure_subnet_created(self):
self.mock_response_for_get('fvSubnet', name='sn1')
self.mgr.ensure_subnet_created_on_apic('t0', 'bd1', '2.2.2.2/8')
self.mock_response_for_get('fvSubnet')
self.mock_responses_for_create('fvSubnet')
self.mgr.ensure_subnet_created_on_apic('t2', 'bd3', '4.4.4.4/16')
self.assert_responses_drained()
def test_ensure_filter_created(self):
self.mock_response_for_get('vzFilter', name='f1')
self.mgr.ensure_filter_created_on_apic('t1', 'two')
self.mock_response_for_get('vzFilter')
self.mock_responses_for_create('vzFilter')
self.mgr.ensure_filter_created_on_apic('t2', 'four')
self.assert_responses_drained()
def test_ensure_epg_created_for_network_old(self):
self.mock_db_query_filterby_first_return('faked')
epg = self.mgr.ensure_epg_created_for_network('X', 'Y', 'Z')
self.assertEqual(epg, 'faked')
def test_ensure_epg_created_for_network_new(self):
tenant = mocked.APIC_TENANT
network = mocked.APIC_NETWORK
netname = mocked.APIC_NETNAME
self._mock_phys_dom_prereq(mocked.APIC_PDOM)
self.mock_db_query_filterby_first_return(None)
self.mock_responses_for_create('fvAEPg')
self.mock_response_for_get('fvBD', name=network)
self.mock_responses_for_create('fvRsBd')
self.mock_responses_for_create('fvRsDomAtt')
new_epg = self.mgr.ensure_epg_created_for_network(tenant,
network, netname)
self.assert_responses_drained()
self.assertEqual(new_epg.network_id, network)
self.assertTrue(self.mocked_session.add.called)
self.assertTrue(self.mocked_session.flush.called)
def test_ensure_epg_created_for_network_exc(self):
tenant = mocked.APIC_TENANT
network = mocked.APIC_NETWORK
netname = mocked.APIC_NETNAME
self.mock_db_query_filterby_first_return(None)
self.mock_error_post_response(wexc.HTTPBadRequest)
self.mock_response_for_post('fvAEPg')
self.assertRaises(cexc.ApicResponseNotOk,
self.mgr.ensure_epg_created_for_network,
tenant, network, netname)
self.assert_responses_drained()
def test_delete_epg_for_network_no_epg(self):
self.mock_db_query_filterby_first_return(None)
self.mgr.delete_epg_for_network('tenant', 'network')
def test_delete_epg_for_network(self):
epg = mock.Mock()
epg.epg_id = mocked.APIC_EPG
self.mock_db_query_filterby_first_return(epg)
self.mock_response_for_post('fvAEPg')
self.mgr.delete_epg_for_network('tenant', 'network')
self.assertTrue(self.mocked_session.delete.called)
self.assertTrue(self.mocked_session.flush.called)
def test_ensure_path_created_for_port(self):
epg = mock.Mock()
epg.epg_id = 'epg01'
eepg = mock.Mock(return_value=epg)
apic_manager.APICManager.ensure_epg_created_for_network = eepg
self.mock_response_for_get('fvRsPathAtt', tDn='foo')
self.mgr.ensure_path_created_for_port('tenant', 'network', 'rhel01',
'static', 'netname')
self.assert_responses_drained()
def test_ensure_path_created_for_port_no_path_att(self):
epg = mock.Mock()
epg.epg_id = 'epg2'
eepg = mock.Mock(return_value=epg)
self.mgr.ensure_epg_created_for_network = eepg
self.mock_response_for_get('fvRsPathAtt')
self.mock_responses_for_create('fvRsPathAtt')
self.mgr.ensure_path_created_for_port('tenant', 'network', 'ubuntu2',
'static', 'netname')
self.assert_responses_drained()
def test_ensure_path_created_for_port_unknown_host(self):
epg = mock.Mock()
epg.epg_id = 'epg3'
eepg = mock.Mock(return_value=epg)
apic_manager.APICManager.ensure_epg_created_for_network = eepg
self.mock_response_for_get('fvRsPathAtt', tDn='foo')
self.assertRaises(cexc.ApicHostNotConfigured,
self.mgr.ensure_path_created_for_port,
'tenant', 'network', 'cirros3', 'static', 'netname')
def test_create_tenant_filter(self):
tenant = mocked.APIC_TENANT
self.mock_responses_for_create('vzFilter')
self.mock_responses_for_create('vzEntry')
filter_id = self.mgr.create_tenant_filter(tenant)
self.assert_responses_drained()
self.assertTrue(uuidutils.is_uuid_like(str(filter_id)))
def test_create_tenant_filter_exc(self):
tenant = mocked.APIC_TENANT
self.mock_error_post_response(wexc.HTTPBadRequest)
self.mock_response_for_post('vzFilter')
self.assertRaises(cexc.ApicResponseNotOk,
self.mgr.create_tenant_filter, tenant)
self.assert_responses_drained()
def test_set_contract_for_epg_consumer(self):
tenant = mocked.APIC_TENANT
epg = mocked.APIC_EPG
contract = mocked.APIC_CONTRACT
self.mock_responses_for_create('fvRsCons')
self.mgr.set_contract_for_epg(tenant, epg, contract)
self.assert_responses_drained()
def test_set_contract_for_epg_provider(self):
tenant = mocked.APIC_TENANT
epg = mocked.APIC_EPG
contract = mocked.APIC_CONTRACT
epg_obj = mock.Mock()
epg_obj.epg_id = epg
epg_obj.provider = False
self.mock_db_query_filterby_first_return(epg_obj)
self.mock_responses_for_create('fvRsProv')
self.mock_response_for_post('vzBrCP')
self.mgr.set_contract_for_epg(tenant, epg, contract, provider=True)
self.assert_responses_drained()
self.assertTrue(self.mocked_session.merge.called)
self.assertTrue(self.mocked_session.flush.called)
self.assertTrue(epg_obj.provider)
def test_set_contract_for_epg_provider_exc(self):
tenant = mocked.APIC_TENANT
epg = mocked.APIC_EPG
contract = mocked.APIC_CONTRACT
self.mock_error_post_response(wexc.HTTPBadRequest)
self.mock_response_for_post('vzBrCP')
self.mock_response_for_post('fvRsProv')
self.assertRaises(cexc.ApicResponseNotOk,
self.mgr.set_contract_for_epg,
tenant, epg, contract, provider=True)
self.assert_responses_drained()
def test_delete_contract_for_epg_consumer(self):
tenant = mocked.APIC_TENANT
epg = mocked.APIC_EPG
contract = mocked.APIC_CONTRACT
self.mock_response_for_post('fvRsCons')
self.mgr.delete_contract_for_epg(tenant, epg, contract)
self.assert_responses_drained()
def test_delete_contract_for_epg_provider(self):
tenant = mocked.APIC_TENANT
epg = mocked.APIC_EPG
contract = mocked.APIC_CONTRACT
epg_obj = mock.Mock()
epg_obj.epg_id = epg + '-other'
epg_obj.provider = False
self.mock_db_query_filterby_first_return(epg_obj)
self.mock_response_for_post('fvRsProv')
self.mock_response_for_post('fvRsCons')
self.mock_responses_for_create('fvRsProv')
self.mock_response_for_post('vzBrCP')
self.mgr.delete_contract_for_epg(tenant, epg, contract, provider=True)
self.assert_responses_drained()
self.assertTrue(self.mocked_session.merge.called)
self.assertTrue(self.mocked_session.flush.called)
self.assertTrue(epg_obj.provider)
def test_create_tenant_contract_existing(self):
tenant = mocked.APIC_TENANT
contract = mocked.APIC_CONTRACT
self.mock_db_query_filterby_first_return(contract)
new_contract = self.mgr.create_tenant_contract(tenant)
self.assertEqual(new_contract, contract)
def test_create_tenant_contract_new(self):
tenant = mocked.APIC_TENANT
contract = mocked.APIC_CONTRACT
dn = self.mgr.apic.vzBrCP.mo.dn(tenant, contract)
self.mock_db_query_filterby_first_return(None)
self.mock_responses_for_create('vzBrCP')
self.mock_response_for_get('vzBrCP', dn=dn)
self.mock_responses_for_create('vzSubj')
self.mock_responses_for_create('vzFilter')
self.mock_responses_for_create('vzEntry')
self.mock_responses_for_create('vzInTerm')
self.mock_responses_for_create('vzRsFiltAtt__In')
self.mock_responses_for_create('vzOutTerm')
self.mock_responses_for_create('vzRsFiltAtt__Out')
self.mock_responses_for_create('vzCPIf')
self.mock_responses_for_create('vzRsIf')
new_contract = self.mgr.create_tenant_contract(tenant)
self.assert_responses_drained()
self.assertTrue(self.mocked_session.add.called)
self.assertTrue(self.mocked_session.flush.called)
self.assertEqual(new_contract['tenant_id'], tenant)
def test_create_tenant_contract_exc(self):
tenant = mocked.APIC_TENANT
self.mock_db_query_filterby_first_return(None)
self.mock_error_post_response(wexc.HTTPBadRequest)
self.mock_response_for_post('vzBrCP')
self.assertRaises(cexc.ApicResponseNotOk,
self.mgr.create_tenant_contract, tenant)
self.assert_responses_drained()

View File

@ -16,12 +16,12 @@
# @author: Henry Gessau, Cisco Systems
import mock
import sys
from oslo.config import cfg
sys.modules["apicapi"] = mock.Mock()
from neutron.extensions import portbindings
from neutron.plugins.ml2.drivers.cisco.apic import mechanism_apic as md
from neutron.plugins.ml2.drivers import type_vlan # noqa
from neutron.tests import base
from neutron.tests.unit.ml2.drivers.cisco.apic import (
test_cisco_apic_common as mocked)
@ -41,14 +41,12 @@ TEST_SEGMENT2 = 'test-segment2'
class TestCiscoApicMechDriver(base.BaseTestCase,
mocked.ControllerMixin,
mocked.ConfigMixin,
mocked.DbModelMixin):
mocked.ConfigMixin):
def setUp(self):
super(TestCiscoApicMechDriver, self).setUp()
mocked.ControllerMixin.set_up_mocks(self)
mocked.ConfigMixin.set_up_mocks(self)
mocked.DbModelMixin.set_up_mocks(self)
self.mock_apic_manager_login_responses()
self.driver = md.APICMechanismDriver()
@ -56,21 +54,9 @@ class TestCiscoApicMechDriver(base.BaseTestCase,
self.driver.cap_port_filter = 'test-cap_port_filter'
def test_initialize(self):
cfg.CONF.set_override('network_vlan_ranges', ['physnet1:100:199'],
'ml2_type_vlan')
ns = mocked.APIC_VLAN_NAME
mode = mocked.APIC_VLAN_MODE
self.mock_response_for_get('fvnsVlanInstP', name=ns, mode=mode)
self.mock_response_for_get('physDomP', name=mocked.APIC_DOMAIN)
self.mock_response_for_get('infraAttEntityP',
name=mocked.APIC_ATT_ENT_PROF)
self.mock_response_for_get('infraAccPortGrp',
name=mocked.APIC_ACC_PORT_GRP)
mock.patch('neutron.plugins.ml2.drivers.cisco.apic.apic_manager.'
'APICManager.ensure_infra_created_on_apic').start()
mgr = self.driver.apic_manager = mock.Mock()
self.driver.initialize()
self.session = self.driver.apic_manager.apic.session
self.assert_responses_drained()
mgr.ensure_infra_created_on_apic.assert_called_once()
def test_update_port_postcommit(self):
net_ctx = self._get_network_context(mocked.APIC_TENANT,
@ -85,7 +71,7 @@ class TestCiscoApicMechDriver(base.BaseTestCase,
mocked.APIC_TENANT)
mgr.ensure_path_created_for_port.assert_called_once_with(
mocked.APIC_TENANT, mocked.APIC_NETWORK, HOST_ID1,
ENCAP, mocked.APIC_NETWORK + '-name')
ENCAP)
def test_create_network_postcommit(self):
ctx = self._get_network_context(mocked.APIC_TENANT,

View File

@ -16,6 +16,9 @@
# @author: Arvind Somya (asomya@cisco.com), Cisco Systems
import mock
import sys
sys.modules["apicapi"] = mock.Mock()
from neutron.services.l3_router import l3_apic
from neutron.tests.unit import testlib_api
@ -59,8 +62,6 @@ class TestCiscoApicL3Plugin(testlib_api.SqlTestCase):
def setUp(self):
super(TestCiscoApicL3Plugin, self).setUp()
mock.patch('neutron.plugins.ml2.drivers.cisco.apic.apic_manager.'
'APICManager').start()
self.plugin = l3_apic.ApicL3ServicePlugin()
self.context = FakeContext()
self.context.tenant_id = TENANT