--- /dev/null
+# 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")
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'")
--- /dev/null
+# 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)
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
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
--- /dev/null
+# Copyright (c) 2014 Cisco Systems
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# @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()
--- /dev/null
+# Copyright (c) 2014 Cisco Systems
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# @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
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