]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Cisco APIC ML2 mechanism driver, part 2
authorArvind Somya <asomya@cisco.com>
Thu, 13 Feb 2014 17:57:50 +0000 (09:57 -0800)
committerArvind Somya <asomya@cisco.com>
Thu, 22 May 2014 20:16:28 +0000 (13:16 -0700)
    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

neutron/plugins/ml2/drivers/cisco/apic/apic_manager.py [new file with mode: 0644]
neutron/plugins/ml2/drivers/cisco/apic/exceptions.py
neutron/plugins/ml2/drivers/cisco/apic/mechanism_apic.py [new file with mode: 0644]
neutron/tests/unit/ml2/drivers/cisco/apic/test_cisco_apic_common.py
neutron/tests/unit/ml2/drivers/cisco/apic/test_cisco_apic_manager.py [new file with mode: 0644]
neutron/tests/unit/ml2/drivers/cisco/apic/test_cisco_apic_mechanism_driver.py [new file with mode: 0644]
setup.cfg

diff --git a/neutron/plugins/ml2/drivers/cisco/apic/apic_manager.py b/neutron/plugins/ml2/drivers/cisco/apic/apic_manager.py
new file mode 100644 (file)
index 0000000..f86aa59
--- /dev/null
@@ -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")
index 1c478853b01c50f25246948004cd18a7a28a1251..b33abb17df71f7d35a6456ef7cc979cb6c2e5b1f 100644 (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'")
diff --git a/neutron/plugins/ml2/drivers/cisco/apic/mechanism_apic.py b/neutron/plugins/ml2/drivers/cisco/apic/mechanism_apic.py
new file mode 100644 (file)
index 0000000..d5297df
--- /dev/null
@@ -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)
index 3c42b98aca3283401ac73b2afa8e8c94d943f015..7a8f8e9d5913708f8cf86abfff1e3fe9ad7a3849 100644 (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
diff --git a/neutron/tests/unit/ml2/drivers/cisco/apic/test_cisco_apic_manager.py b/neutron/tests/unit/ml2/drivers/cisco/apic/test_cisco_apic_manager.py
new file mode 100644 (file)
index 0000000..24a2c21
--- /dev/null
@@ -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()
diff --git a/neutron/tests/unit/ml2/drivers/cisco/apic/test_cisco_apic_mechanism_driver.py b/neutron/tests/unit/ml2/drivers/cisco/apic/test_cisco_apic_mechanism_driver.py
new file mode 100644 (file)
index 0000000..6addd43
--- /dev/null
@@ -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
index 4308c71be31c8305114f186babc1d91d34926f71..b44e1b3eabc1a0bffd1dec571206eec1433679b1 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -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