Cisco APIC ML2 mechanism driver, part 2

This set of changes introduces a mechanism driver for the
    Cisco APIC. This is the second and final part of a 2 part commit.
    Please see the blueprint for more information.

    The review is submitted in two parts:
    - Part 1 (Posted earlier, required for Part 2)
        o APIC REST Client
        o APIC data model and migration script
        o APIC configurations
    - Part 2 (this commit)
        o APIC mechanism driver
        o APIC manager

    Partially implements: blueprint ml2-cisco-apic-mechanism-driver

Change-Id: I5ed3ac133146635083e2d0093057b43b64f347fe
This commit is contained in:
Arvind Somya 2014-02-13 09:57:50 -08:00
parent fc832a0cd7
commit 26cf7e2f94
7 changed files with 1643 additions and 2 deletions

View File

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

@ -50,3 +50,10 @@ class ApicHostNotConfigured(exceptions.NotAuthorized):
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

@ -0,0 +1,150 @@
# 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 netaddr
from oslo.config import cfg
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
LOG = log.getLogger(__name__)
class APICMechanismDriver(api.MechanismDriver):
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.ensure_infra_created_on_apic()
def _perform_port_operations(self, context):
# Get tenant details from port context
tenant_id = context.current['tenant_id']
# Get network
network = context.network.current['id']
net_name = context.network.current['name']
# Get port
port = context.current
# Get segmentation id
if not context.bound_segment:
LOG.debug(_("Port %s is not bound to a segment"), port)
return
seg = None
if (context.bound_segment.get(api.NETWORK_TYPE) in
[constants.TYPE_VLAN]):
seg = context.bound_segment.get(api.SEGMENTATION_ID)
# Check if a compute port
if not port['device_owner'].startswith('compute'):
# Not a compute port, return
return
host = port.get(portbindings.HOST_ID)
# Check host that the dhcp agent is running on
filters = {'device_owner': 'network:dhcp',
'network_id': network}
dhcp_ports = context._plugin.get_ports(context._plugin_context,
filters=filters)
dhcp_hosts = []
for dhcp_port in dhcp_ports:
dhcp_hosts.append(dhcp_port.get(portbindings.HOST_ID))
# Create a static path attachment for this host/epg/switchport combo
self.apic_manager.ensure_tenant_created_on_apic(tenant_id)
if dhcp_hosts:
for dhcp_host in dhcp_hosts:
self.apic_manager.ensure_path_created_for_port(tenant_id,
network,
dhcp_host, seg,
net_name)
if host not in dhcp_hosts:
self.apic_manager.ensure_path_created_for_port(tenant_id, network,
host, seg, net_name)
def create_port_postcommit(self, context):
self._perform_port_operations(context)
def update_port_postcommit(self, context):
self._perform_port_operations(context)
def create_network_postcommit(self, context):
net_id = context.current['id']
tenant_id = context.current['tenant_id']
net_name = context.current['name']
self.apic_manager.ensure_bd_created_on_apic(tenant_id, net_id)
# Create EPG for this network
self.apic_manager.ensure_epg_created_for_network(tenant_id, net_id,
net_name)
def delete_network_postcommit(self, context):
net_id = context.current['id']
tenant_id = context.current['tenant_id']
self.apic_manager.delete_bd_on_apic(tenant_id, net_id)
self.apic_manager.delete_epg_for_network(tenant_id, net_id)
def create_subnet_postcommit(self, context):
tenant_id = context.current['tenant_id']
network_id = context.current['network_id']
gateway_ip = context.current['gateway_ip']
cidr = netaddr.IPNetwork(context.current['cidr'])
netmask = str(cidr.prefixlen)
gateway_ip = gateway_ip + '/' + netmask
self.apic_manager.ensure_subnet_created_on_apic(tenant_id, network_id,
gateway_ip)

View File

@ -23,7 +23,7 @@ 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.unit import test_api_v2
from neutron.tests import base
OK = requests.codes.ok
@ -169,7 +169,7 @@ class ConfigMixin(object):
def set_up_mocks(self):
# Mock the configuration file
args = ['--config-file', test_api_v2.etcdir('neutron.conf.test')]
args = ['--config-file', base.etcdir('neutron.conf.test')]
neutron_config.parse(args=args)
# Configure the ML2 mechanism drivers and network types

View File

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

@ -0,0 +1,226 @@
# 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 oslo.config import cfg
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)
HOST_ID1 = 'ubuntu'
HOST_ID2 = 'rhel'
ENCAP = '101'
SUBNET_GATEWAY = '10.3.2.1'
SUBNET_CIDR = '10.3.1.0/24'
SUBNET_NETMASK = '24'
TEST_SEGMENT1 = 'test-segment1'
TEST_SEGMENT2 = 'test-segment2'
class TestCiscoApicMechDriver(base.BaseTestCase,
mocked.ControllerMixin,
mocked.ConfigMixin,
mocked.DbModelMixin):
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()
self.driver.vif_type = 'test-vif_type'
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()
self.driver.initialize()
self.session = self.driver.apic_manager.apic.session
self.assert_responses_drained()
def test_update_port_postcommit(self):
net_ctx = self._get_network_context(mocked.APIC_TENANT,
mocked.APIC_NETWORK,
TEST_SEGMENT1)
port_ctx = self._get_port_context(mocked.APIC_TENANT,
mocked.APIC_NETWORK,
'vm1', net_ctx, HOST_ID1)
mgr = self.driver.apic_manager = mock.Mock()
self.driver.update_port_postcommit(port_ctx)
mgr.ensure_tenant_created_on_apic.assert_called_once_with(
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')
def test_create_network_postcommit(self):
ctx = self._get_network_context(mocked.APIC_TENANT,
mocked.APIC_NETWORK,
TEST_SEGMENT1)
mgr = self.driver.apic_manager = mock.Mock()
self.driver.create_network_postcommit(ctx)
mgr.ensure_bd_created_on_apic.assert_called_once_with(
mocked.APIC_TENANT, mocked.APIC_NETWORK)
mgr.ensure_epg_created_for_network.assert_called_once_with(
mocked.APIC_TENANT, mocked.APIC_NETWORK,
mocked.APIC_NETWORK + '-name')
def test_delete_network_postcommit(self):
ctx = self._get_network_context(mocked.APIC_TENANT,
mocked.APIC_NETWORK,
TEST_SEGMENT1)
mgr = self.driver.apic_manager = mock.Mock()
self.driver.delete_network_postcommit(ctx)
mgr.delete_bd_on_apic.assert_called_once_with(
mocked.APIC_TENANT, mocked.APIC_NETWORK)
mgr.delete_epg_for_network.assert_called_once_with(
mocked.APIC_TENANT, mocked.APIC_NETWORK)
def test_create_subnet_postcommit(self):
net_ctx = self._get_network_context(mocked.APIC_TENANT,
mocked.APIC_NETWORK,
TEST_SEGMENT1)
subnet_ctx = self._get_subnet_context(SUBNET_GATEWAY,
SUBNET_CIDR,
net_ctx)
mgr = self.driver.apic_manager = mock.Mock()
self.driver.create_subnet_postcommit(subnet_ctx)
mgr.ensure_subnet_created_on_apic.assert_called_once_with(
mocked.APIC_TENANT, mocked.APIC_NETWORK,
'%s/%s' % (SUBNET_GATEWAY, SUBNET_NETMASK))
def _get_network_context(self, tenant_id, net_id, seg_id=None,
seg_type='vlan'):
network = {'id': net_id,
'name': net_id + '-name',
'tenant_id': tenant_id,
'provider:segmentation_id': seg_id}
if seg_id:
network_segments = [{'id': seg_id,
'segmentation_id': ENCAP,
'network_type': seg_type,
'physical_network': 'physnet1'}]
else:
network_segments = []
return FakeNetworkContext(network, network_segments)
def _get_subnet_context(self, gateway_ip, cidr, network):
subnet = {'tenant_id': network.current['tenant_id'],
'network_id': network.current['id'],
'id': '[%s/%s]' % (gateway_ip, cidr),
'gateway_ip': gateway_ip,
'cidr': cidr}
return FakeSubnetContext(subnet, network)
def _get_port_context(self, tenant_id, net_id, vm_id, network, host):
port = {'device_id': vm_id,
'device_owner': 'compute',
'binding:host_id': host,
'tenant_id': tenant_id,
'id': mocked.APIC_PORT,
'name': mocked.APIC_PORT,
'network_id': net_id}
return FakePortContext(port, network)
class FakeNetworkContext(object):
"""To generate network context for testing purposes only."""
def __init__(self, network, segments):
self._network = network
self._segments = segments
@property
def current(self):
return self._network
@property
def network_segments(self):
return self._segments
class FakeSubnetContext(object):
"""To generate subnet context for testing purposes only."""
def __init__(self, subnet, network):
self._subnet = subnet
self._network = network
@property
def current(self):
return self._subnet
@property
def network(self):
return self._network
class FakePortContext(object):
"""To generate port context for testing purposes only."""
def __init__(self, port, network):
self._fake_plugin = mock.Mock()
self._fake_plugin.get_ports.return_value = []
self._fake_plugin_context = None
self._port = port
self._network = network
if network.network_segments:
self._bound_segment = network.network_segments[0]
else:
self._bound_segment = None
@property
def current(self):
return self._port
@property
def _plugin(self):
return self._fake_plugin
@property
def _plugin_context(self):
return self._fake_plugin_context
@property
def network(self):
return self._network
@property
def bound_segment(self):
return self._bound_segment
def set_binding(self, segment_id, vif_type, cap_port_filter):
pass

View File

@ -150,6 +150,7 @@ neutron.ml2.mechanism_drivers =
ncs = neutron.plugins.ml2.drivers.mechanism_ncs:NCSMechanismDriver
arista = neutron.plugins.ml2.drivers.mech_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
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