]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Apic drivers enhancements (second approach): L2 refactor
authorIvar Lazzaro <ivarlazzaro@gmail.com>
Fri, 22 Aug 2014 01:17:03 +0000 (18:17 -0700)
committerIvar Lazzaro <ivarlazzaro@gmail.com>
Sat, 30 Aug 2014 07:09:58 +0000 (00:09 -0700)
- refactor to leverage Client's transactional capabilities
- General refactor to improve the driver's reliability

Implements blueprint: apic-driver-enhancements

Change-Id: I4deb171381e62e70818218957d82b5e27954aeb9

etc/neutron/plugins/ml2/ml2_conf_cisco.ini
neutron/db/migration/alembic_migrations/versions/32f3915891fd_cisco_apic_driver_update.py [new file with mode: 0644]
neutron/db/migration/alembic_migrations/versions/HEAD
neutron/plugins/ml2/drivers/cisco/__init__.py
neutron/plugins/ml2/drivers/cisco/apic/apic_model.py
neutron/plugins/ml2/drivers/cisco/apic/config.py
neutron/plugins/ml2/drivers/cisco/apic/mechanism_apic.py
neutron/tests/unit/ml2/drivers/cisco/apic/test_cisco_apic_common.py
neutron/tests/unit/ml2/drivers/cisco/apic/test_cisco_apic_mechanism_driver.py
neutron/tests/unit/services/l3_router/test_l3_apic_plugin.py

index bb2dadbed4f520a39af5cea69ea81c027b521f84..ea361d046f3a2323b802a0b3dc31525759944b53 100644 (file)
 # Password for the APIC controller
 # apic_password=password
 
+# Whether use SSl for connecting to the APIC controller or not
+# apic_use_ssl=True
+
+# How to map names to APIC: use_uuid or use_name
+# apic_name_mapping=use_name
+
 # Names for APIC objects used by Neutron
 # Note: When deploying multiple clouds against one APIC,
 #       these names must be unique between the clouds.
 # apic_node_profile=openstack_profile
 # apic_entity_profile=openstack_entity
 # apic_function_profile=openstack_function
-
-# The following flag will cause all the node profiles on the APIC to
-# be cleared when neutron-server starts. This is typically used only
-# for test environments that require clean-slate startup conditions.
-# apic_clear_node_profiles=False
+# apic_app_profile_name=openstack_app
 
 # Specify your network topology.
 # This section indicates how your compute nodes are connected to the fabric's
 # switches and ports. The format is as follows:
 #
-# [switch:<swich_id_from_the_apic>]
+# [apic_switch:<swich_id_from_the_apic>]
 # <compute_host>,<compute_host>=<switchport_the_host(s)_are_connected_to>
 #
 # You can have multiple sections, one for each switch in your fabric that is
 # participating in Openstack. e.g.
 #
-# [switch:17]
+# [apic_switch:17]
 # ubuntu,ubuntu1=1/10
 # ubuntu2,ubuntu3=1/11
 #
-# [switch:18]
+# [apic_switch:18]
 # ubuntu5,ubuntu6=1/1
 # ubuntu7,ubuntu8=1/2
+
+# Describe external connectivity.
+# In this section you can specify the external network configuration in order
+# for the plugin to be able to teach the fabric how to route the internal
+# traffic to the outside world. The external connectivity configuration
+# format is as follows:
+#
+# [apic_external_network:<externalNetworkName>]
+# switch=<switch_id_from_the_apic>
+# port=<switchport_the_external_router_is_connected_to>
+# encap=<encapsulation>
+# cidr_exposed=<cidr_exposed_to_the_external_router>
+# gateway_ip=<ip_of_the_external_gateway>
+#
+# An example follows:
+# [apic_external_network:network_ext]
+# switch=203
+# port=1/34
+# encap=vlan-100
+# cidr_exposed=10.10.40.2/16
+# gateway_ip=10.10.40.1
diff --git a/neutron/db/migration/alembic_migrations/versions/32f3915891fd_cisco_apic_driver_update.py b/neutron/db/migration/alembic_migrations/versions/32f3915891fd_cisco_apic_driver_update.py
new file mode 100644 (file)
index 0000000..09d914c
--- /dev/null
@@ -0,0 +1,66 @@
+# Copyright 2014 OpenStack Foundation
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+#
+
+""" cisco_apic_driver_update
+
+Revision ID: 32f3915891fd
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '32f3915891fd'
+down_revision = 'aae5706a396'
+
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade(active_plugins=None, options=None):
+
+    op.drop_table('cisco_ml2_apic_port_profiles')
+
+    op.create_table(
+        'cisco_ml2_apic_host_links',
+        sa.Column('host', sa.String(length=255), nullable=False),
+        sa.Column('ifname', sa.String(length=64), nullable=False),
+        sa.Column('ifmac', sa.String(length=32), nullable=True),
+        sa.Column('swid', sa.String(length=32), nullable=False),
+        sa.Column('module', sa.String(length=32), nullable=False),
+        sa.Column('port', sa.String(length=32), nullable=False),
+        sa.PrimaryKeyConstraint('host', 'ifname'))
+
+    op.create_table(
+        'cisco_ml2_apic_names',
+        sa.Column('neutron_id', sa.String(length=36), nullable=False),
+        sa.Column('neutron_type', sa.String(length=32), nullable=False),
+        sa.Column('apic_name', sa.String(length=255), nullable=False),
+        sa.PrimaryKeyConstraint('neutron_id', 'neutron_type'))
+
+
+def downgrade(active_plugins=None, options=None):
+
+    op.drop_table('cisco_ml2_apic_names')
+    op.drop_table('cisco_ml2_apic_host_links')
+
+    op.create_table(
+        'cisco_ml2_apic_port_profiles',
+        sa.Column('node_id', sa.String(length=255), nullable=False),
+        sa.Column('profile_id', sa.String(length=64), nullable=False),
+        sa.Column('hpselc_id', sa.String(length=64), nullable=False),
+        sa.Column('module', sa.String(length=10), nullable=False),
+        sa.Column('from_port', sa.Integer(), nullable=False),
+        sa.Column('to_port', sa.Integer(), nullable=False),
+        sa.PrimaryKeyConstraint('node_id'))
index b340f2714ce235539ab62a0b59d6e7065e51924d..b3519602ad1bcc24a021f6f85391fdfe5b3978d7 100644 (file)
@@ -1 +1 @@
-aae5706a396
+32f3915891fd
index 788cea1f70b47f06d847d01fd575a864fa3721e0..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 (file)
@@ -1,14 +0,0 @@
-# Copyright (c) 2013 OpenStack Foundation
-# 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.
index 19774fd46d1fd1e351e592a2bb9351db7f5fc08b..9111b1546145d493dfda3c90875eebf52c528918 100644 (file)
 # @author: Arvind Somya (asomya@cisco.com), Cisco Systems Inc.
 
 import sqlalchemy as sa
+from sqlalchemy import orm
 from sqlalchemy import sql
 
 from neutron.db import api as db_api
 from neutron.db import model_base
 
+from neutron.db import models_v2
+from neutron.plugins.ml2 import models as models_ml2
+
 
 class NetworkEPG(model_base.BASEV2):
 
@@ -35,20 +39,6 @@ class NetworkEPG(model_base.BASEV2):
                          nullable=False)
 
 
-class PortProfile(model_base.BASEV2):
-
-    """Port profiles created on the APIC."""
-
-    __tablename__ = 'cisco_ml2_apic_port_profiles'
-
-    node_id = sa.Column(sa.String(255), nullable=False, primary_key=True)
-    profile_id = sa.Column(sa.String(64), nullable=False)
-    hpselc_id = sa.Column(sa.String(64), nullable=False)
-    module = sa.Column(sa.String(10), nullable=False)
-    from_port = sa.Column(sa.Integer(), nullable=False)
-    to_port = sa.Column(sa.Integer(), nullable=False)
-
-
 class TenantContract(model_base.BASEV2):
 
     """Contracts (and Filters) created on the APIC."""
@@ -61,6 +51,30 @@ class TenantContract(model_base.BASEV2):
     filter_id = sa.Column(sa.String(64), nullable=False)
 
 
+class HostLink(model_base.BASEV2):
+
+    """Connectivity of host links."""
+
+    __tablename__ = 'cisco_ml2_apic_host_links'
+
+    host = sa.Column(sa.String(255), nullable=False, primary_key=True)
+    ifname = sa.Column(sa.String(64), nullable=False, primary_key=True)
+    ifmac = sa.Column(sa.String(32), nullable=True)
+    swid = sa.Column(sa.String(32), nullable=False)
+    module = sa.Column(sa.String(32), nullable=False)
+    port = sa.Column(sa.String(32), nullable=False)
+
+
+class ApicName(model_base.BASEV2):
+    """Mapping of names created on the APIC."""
+
+    __tablename__ = 'cisco_ml2_apic_names'
+
+    neutron_id = sa.Column(sa.String(36), nullable=False, primary_key=True)
+    neutron_type = sa.Column(sa.String(32), nullable=False, primary_key=True)
+    apic_name = sa.Column(sa.String(255), nullable=False)
+
+
 class ApicDbModel(object):
 
     """DB Model to manage all APIC DB interactions."""
@@ -68,42 +82,6 @@ class ApicDbModel(object):
     def __init__(self):
         self.session = db_api.get_session()
 
-    def get_port_profile_for_node(self, node_id):
-        """Returns a port profile for a switch if found in the DB."""
-        return self.session.query(PortProfile).filter_by(
-            node_id=node_id).first()
-
-    def get_profile_for_module_and_ports(self, node_id, profile_id,
-                                         module, from_port, to_port):
-        """Returns profile for module and ports.
-
-        Grabs the profile row from the DB for the specified switch,
-        module (linecard) and from/to port combination.
-        """
-        return self.session.query(PortProfile).filter_by(
-            node_id=node_id,
-            module=module,
-            profile_id=profile_id,
-            from_port=from_port,
-            to_port=to_port).first()
-
-    def get_profile_for_module(self, node_id, profile_id, module):
-        """Returns the first profile for a switch module from the DB."""
-        return self.session.query(PortProfile).filter_by(
-            node_id=node_id,
-            profile_id=profile_id,
-            module=module).first()
-
-    def add_profile_for_module_and_ports(self, node_id, profile_id,
-                                         hpselc_id, module,
-                                         from_port, to_port):
-        """Adds a profile for switch, module and port range."""
-        row = PortProfile(node_id=node_id, profile_id=profile_id,
-                          hpselc_id=hpselc_id, module=module,
-                          from_port=from_port, to_port=to_port)
-        self.session.add(row)
-        self.session.flush()
-
     def get_provider_contract(self):
         """Returns  provider EPG from the DB if found."""
         return self.session.query(NetworkEPG).filter_by(
@@ -170,10 +148,92 @@ class ApicDbModel(object):
 
         return contract
 
-    def delete_profile_for_node(self, node_id):
-        """Deletes the port profile for a node."""
-        profile = self.session.query(PortProfile).filter_by(
-            node_id=node_id).first()
-        if profile:
-            self.session.delete(profile)
-            self.session.flush()
+    def add_hostlink(self, host, ifname, ifmac, swid, module, port):
+        link = HostLink(host=host, ifname=ifname, ifmac=ifmac,
+                       swid=swid, module=module, port=port)
+        with self.session.begin(subtransactions=True):
+            self.session.merge(link)
+
+    def get_hostlinks(self):
+        return self.session.query(HostLink).all()
+
+    def get_hostlink(self, host, ifname):
+        return self.session.query(HostLink).filter_by(
+            host=host, ifname=ifname).first()
+
+    def get_hostlinks_for_host(self, host):
+        return self.session.query(HostLink).filter_by(
+            host=host).all()
+
+    def get_hostlinks_for_host_switchport(self, host, swid, module, port):
+        return self.session.query(HostLink).filter_by(
+            host=host, swid=swid, module=module, port=port).all()
+
+    def get_hostlinks_for_switchport(self, swid, module, port):
+        return self.session.query(HostLink).filter_by(
+            swid=swid, module=module, port=port).all()
+
+    def delete_hostlink(self, host, ifname):
+        with self.session.begin(subtransactions=True):
+            try:
+                self.session.query(HostLink).filter_by(host=host,
+                                                       ifname=ifname).delete()
+            except orm.exc.NoResultFound:
+                return
+
+    def get_switches(self):
+        return self.session.query(HostLink.swid).distinct()
+
+    def get_modules_for_switch(self, swid):
+        return self.session.query(
+            HostLink.module).filter_by(swid=swid).distinct()
+
+    def get_ports_for_switch_module(self, swid, module):
+        return self.session.query(
+            HostLink.port).filter_by(swid=swid, module=module).distinct()
+
+    def get_switch_and_port_for_host(self, host):
+        return self.session.query(
+            HostLink.swid, HostLink.module, HostLink.port).filter_by(
+                host=host).distinct()
+
+    def get_tenant_network_vlan_for_host(self, host):
+        pb = models_ml2.PortBinding
+        po = models_v2.Port
+        ns = models_ml2.NetworkSegment
+        return self.session.query(
+            po.tenant_id, ns.network_id, ns.segmentation_id).filter(
+            po.id == pb.port_id).filter(pb.host == host).filter(
+                po.network_id == ns.network_id).distinct()
+
+    def add_apic_name(self, neutron_id, neutron_type, apic_name):
+        name = ApicName(neutron_id=neutron_id,
+                        neutron_type=neutron_type,
+                        apic_name=apic_name)
+        with self.session.begin(subtransactions=True):
+            self.session.add(name)
+
+    def update_apic_name(self, neutron_id, neutron_type, apic_name):
+        with self.session.begin(subtransactions=True):
+            name = self.session.query(ApicName).filter_by(
+                neutron_id=neutron_id, neutron_type=neutron_type).first()
+            if name:
+                name.apic_name = apic_name
+                self.session.merge(name)
+            else:
+                self.add_apic_name(neutron_id, neutron_type, apic_name)
+
+    def get_apic_names(self):
+        return self.session.query(ApicName).all()
+
+    def get_apic_name(self, neutron_id, neutron_type):
+        return self.session.query(ApicName.apic_name).filter_by(
+            neutron_id=neutron_id, neutron_type=neutron_type).first()
+
+    def delete_apic_name(self, neutron_id):
+        with self.session.begin(subtransactions=True):
+            try:
+                self.session.query(ApicName).filter_by(
+                    neutron_id=neutron_id).delete()
+            except orm.exc.NoResultFound:
+                return
index 2b922c1bcacfd2e24f9ac4906ca53a67515930aa..0309cd8966e11b54e79551fe398159b8017d45e4 100644 (file)
 from oslo.config import cfg
 
 
+DEFAULT_ROOT_HELPER = ('sudo /usr/local/bin/neutron-rootwrap '
+                       '/etc/neutron/rootwrap.conf')
+
+
+# oslo.config limits ${var} expansion to global variables
+# That is why apic_system_id as a global variable
+global_opts = [
+    cfg.StrOpt('apic_system_id',
+               default='openstack',
+               help=_("Prefix for APIC domain/names/profiles created")),
+]
+
+
+cfg.CONF.register_opts(global_opts)
+
+
 apic_opts = [
     cfg.ListOpt('apic_hosts',
                 default=[],
@@ -27,58 +43,96 @@ apic_opts = [
                help=_("Username for the APIC controller")),
     cfg.StrOpt('apic_password',
                help=_("Password for the APIC controller"), secret=True),
+    cfg.StrOpt('apic_name_mapping',
+               default='use_name',
+               help=_("Name mapping strategy to use: use_uuid | use_name")),
     cfg.BoolOpt('apic_use_ssl', default=True,
                 help=_("Use SSL to connect to the APIC controller")),
-    cfg.StrOpt('apic_vmm_provider', default='VMware',
-               help=_("Name for the VMM domain provider")),
-    cfg.StrOpt('apic_vmm_domain', default='openstack',
-               help=_("Name for the VMM domain to be created for Openstack")),
-    cfg.StrOpt('apic_vlan_ns_name', default='openstack_ns',
-               help=_("Name for the vlan namespace to be used for openstack")),
-    cfg.StrOpt('apic_vlan_range', default='2:4093',
-               help=_("Range of VLAN's to be used for Openstack")),
-    cfg.StrOpt('apic_node_profile', default='openstack_profile',
+    cfg.StrOpt('apic_domain_name',
+               default='${apic_system_id}',
+               help=_("Name for the domain created on APIC")),
+    cfg.StrOpt('apic_app_profile_name',
+               default='${apic_system_id}_app',
+               help=_("Name for the app profile used for Openstack")),
+    cfg.StrOpt('apic_vlan_ns_name',
+               default='${apic_system_id}_vlan_ns',
+               help=_("Name for the vlan namespace to be used for Openstack")),
+    cfg.StrOpt('apic_node_profile',
+               default='${apic_system_id}_node_profile',
                help=_("Name of the node profile to be created")),
-    cfg.StrOpt('apic_entity_profile', default='openstack_entity',
+    cfg.StrOpt('apic_entity_profile',
+               default='${apic_system_id}_entity_profile',
                help=_("Name of the entity profile to be created")),
-    cfg.StrOpt('apic_function_profile', default='openstack_function',
+    cfg.StrOpt('apic_function_profile',
+               default='${apic_system_id}_function_profile',
                help=_("Name of the function profile to be created")),
-    cfg.BoolOpt('apic_clear_node_profiles', default=False,
-                help=_("Clear the node profiles on the APIC at startup "
-                       "(mainly used for testing)")),
+    cfg.StrOpt('apic_lacp_profile',
+               default='${apic_system_id}_lacp_profile',
+               help=_("Name of the LACP profile to be created")),
+    cfg.ListOpt('apic_host_uplink_ports',
+                default=[],
+                help=_('The uplink ports to check for ACI connectivity')),
+    cfg.ListOpt('apic_vpc_pairs',
+                default=[],
+                help=_('The switch pairs for VPC connectivity')),
+    cfg.StrOpt('apic_vlan_range',
+               default='2:4093',
+               help=_("Range of VLAN's to be used for Openstack")),
+    cfg.StrOpt('root_helper',
+               default=DEFAULT_ROOT_HELPER,
+               help=_("Setup root helper as rootwrap or sudo")),
 ]
 
 
 cfg.CONF.register_opts(apic_opts, "ml2_cisco_apic")
 
 
-def get_switch_and_port_for_host(host_id):
-    for switch, connected in _switch_dict.items():
-        for port, hosts in connected.items():
-            if host_id in hosts:
-                return switch, port
+def _get_specific_config(prefix):
+    """retrieve config in the format [<prefix>:<value>]."""
+    conf_dict = {}
+    multi_parser = cfg.MultiConfigParser()
+    multi_parser.read(cfg.CONF.config_file)
+    for parsed_file in multi_parser.parsed:
+        for parsed_item in parsed_file.keys():
+            if parsed_item.startswith(prefix):
+                switch, switch_id = parsed_item.split(':')
+                if switch.lower() == prefix:
+                    conf_dict[switch_id] = parsed_file[parsed_item].items()
+    return conf_dict
 
 
-_switch_dict = {}
+def create_switch_dictionary():
+    switch_dict = {}
+    conf = _get_specific_config('apic_switch')
+    for switch_id in conf:
+        switch_dict[switch_id] = switch_dict.get(switch_id, {})
+        for host_list, port in conf[switch_id]:
+            hosts = host_list.split(',')
+            port = port[0]
+            switch_dict[switch_id][port] = (
+                switch_dict[switch_id].get(port, []) + hosts)
+    return switch_dict
 
 
-def create_switch_dictionary():
-    multi_parser = cfg.MultiConfigParser()
-    read_ok = multi_parser.read(cfg.CONF.config_file)
+def create_vpc_dictionary():
+    vpc_dict = {}
+    for pair in cfg.CONF.ml2_cisco_apic.apic_vpc_pairs:
+        pair_tuple = pair.split(':')
+        if (len(pair_tuple) != 2 or
+                any(map(lambda x: not x.isdigit(), pair_tuple))):
+            # Validation error, ignore this item
+            continue
+        vpc_dict[pair_tuple[0]] = pair_tuple[1]
+        vpc_dict[pair_tuple[1]] = pair_tuple[0]
+    return vpc_dict
 
-    if len(read_ok) != len(cfg.CONF.config_file):
-        raise cfg.Error(_("Some config files were not parsed properly"))
 
-    for parsed_file in multi_parser.parsed:
-        for parsed_item in parsed_file.keys():
-            if parsed_item.startswith('apic_switch'):
-                switch, switch_id = parsed_item.split(':')
-                if switch.lower() == 'apic_switch':
-                    _switch_dict[switch_id] = {}
-                    port_cfg = parsed_file[parsed_item].items()
-                    for host_list, port in port_cfg:
-                        hosts = host_list.split(',')
-                        port = port[0]
-                        _switch_dict[switch_id][port] = hosts
-
-    return _switch_dict
+def create_external_network_dictionary():
+    router_dict = {}
+    conf = _get_specific_config('apic_external_network')
+    for net_id in conf:
+        router_dict[net_id] = router_dict.get(net_id, {})
+        for key, value in conf[net_id]:
+            router_dict[net_id][key] = value[0] if value else None
+
+    return router_dict
index 58bbfb9ab08f4a72b4af81cafc4e3f072431df03..3d063e4fe3326f9a66a7249b38803da2cf507e3c 100644 (file)
 # @author: Arvind Somya (asomya@cisco.com), Cisco Systems Inc.
 
 from apicapi import apic_manager
+from apicapi import exceptions as exc
+from keystoneclient.v2_0 import client as keyclient
 import netaddr
-
 from oslo.config import cfg
 
-from neutron.extensions import portbindings
+from neutron.common import constants as n_constants
+from neutron.openstack.common import lockutils
 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_model
 from neutron.plugins.ml2.drivers.cisco.apic import config
+from neutron.plugins.ml2 import models
 
 
 LOG = log.getLogger(__name__)
@@ -34,94 +37,232 @@ LOG = log.getLogger(__name__)
 class APICMechanismDriver(api.MechanismDriver):
 
     @staticmethod
-    def get_apic_manager():
+    def get_apic_manager(client=True):
         apic_config = cfg.CONF.ml2_cisco_apic
         network_config = {
             'vlan_ranges': cfg.CONF.ml2_type_vlan.network_vlan_ranges,
             'switch_dict': config.create_switch_dictionary(),
+            'vpc_dict': config.create_vpc_dictionary(),
+            'external_network_dict':
+                config.create_external_network_dictionary(),
         }
+        apic_system_id = cfg.CONF.apic_system_id
+        keyclient_param = keyclient if client else None
+        keystone_authtoken = cfg.CONF.keystone_authtoken if client else None
         return apic_manager.APICManager(apic_model.ApicDbModel(), log,
-                                        network_config, apic_config)
+                                        network_config, apic_config,
+                                        keyclient_param, keystone_authtoken,
+                                        apic_system_id)
 
     def initialize(self):
+        # initialize apic
         self.apic_manager = APICMechanismDriver.get_apic_manager()
+        self.name_mapper = self.apic_manager.apic_mapper
         self.apic_manager.ensure_infra_created_on_apic()
+        self.apic_manager.ensure_bgp_pod_policy_created_on_apic()
 
-    def _perform_port_operations(self, context):
+    @lockutils.synchronized('apic-portlock')
+    def _perform_path_port_operations(self, context, port):
+        # Get network
+        network_id = context.network.current['id']
+        anetwork_id = self.name_mapper.network(context, network_id)
         # Get tenant details from port context
         tenant_id = context.current['tenant_id']
-
-        # Get network
-        network = context.network.current['id']
-
-        # Get port
-        port = context.current
+        tenant_id = self.name_mapper.tenant(context, tenant_id)
 
         # Get segmentation id
         if not context.bound_segment:
-            LOG.debug(_("Port %s is not bound to a segment"), port)
+            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]):
+        if (context.bound_segment.get(api.NETWORK_TYPE)
+                in [constants.TYPE_VLAN]):
             seg = context.bound_segment.get(api.SEGMENTATION_ID)
+        # hosts on which this vlan is provisioned
+        host = context.host
+        # Create a static path attachment for the host/epg/switchport combo
+        with self.apic_manager.apic.transaction() as trs:
+            self.apic_manager.ensure_path_created_for_port(
+                tenant_id, anetwork_id, host, seg, transaction=trs)
 
+    def _perform_gw_port_operations(self, context, port):
+        router_id = port.get('device_id')
+        network = context.network.current
+        anetwork_id = self.name_mapper.network(context, network['id'])
+        router_info = self.apic_manager.ext_net_dict.get(network['name'])
+
+        if router_id and router_info:
+            address = router_info['cidr_exposed']
+            next_hop = router_info['gateway_ip']
+            encap = router_info.get('encap')  # No encap if None
+            switch = router_info['switch']
+            module, sport = router_info['port'].split('/')
+            with self.apic_manager.apic.transaction() as trs:
+                # Get/Create contract
+                arouter_id = self.name_mapper.router(context, router_id)
+                cid = self.apic_manager.get_router_contract(arouter_id)
+                # Ensure that the external ctx exists
+                self.apic_manager.ensure_context_enforced()
+                # Create External Routed Network and configure it
+                self.apic_manager.ensure_external_routed_network_created(
+                    anetwork_id, transaction=trs)
+                self.apic_manager.ensure_logical_node_profile_created(
+                    anetwork_id, switch, module, sport, encap,
+                    address, transaction=trs)
+                self.apic_manager.ensure_static_route_created(
+                    anetwork_id, switch, next_hop, transaction=trs)
+                self.apic_manager.ensure_external_epg_created(
+                    anetwork_id, transaction=trs)
+                self.apic_manager.ensure_external_epg_consumed_contract(
+                    anetwork_id, cid, transaction=trs)
+                self.apic_manager.ensure_external_epg_provided_contract(
+                    anetwork_id, cid, transaction=trs)
+
+    def _perform_port_operations(self, context):
+        # Get port
+        port = context.current
         # Check if a compute port
-        if not port['device_owner'].startswith('compute'):
-            # Not a compute port, return
-            return
+        if port.get('device_owner', '').startswith('compute'):
+            self._perform_path_port_operations(context, port)
+        elif port.get('device_owner') == n_constants.DEVICE_OWNER_ROUTER_GW:
+            self._perform_gw_port_operations(context, port)
+        elif port.get('device_owner') == n_constants.DEVICE_OWNER_DHCP:
+            self._perform_path_port_operations(context, port)
 
-        host = context.host
-        # 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)
-        if host not in dhcp_hosts:
-            self.apic_manager.ensure_path_created_for_port(tenant_id, network,
-                                                           host, seg)
+    def _delete_contract(self, context):
+        port = context.current
+        network_id = self.name_mapper.network(
+            context, context.network.current['id'])
+        arouter_id = self.name_mapper.router(context,
+                                             port.get('device_id'))
+        self.apic_manager.delete_external_epg_contract(arouter_id,
+                                                       network_id)
+
+    def _get_active_path_count(self, context):
+        return context._plugin_context.session.query(
+            models.PortBinding).filter_by(
+                host=context.host, segment=context._binding.segment).count()
+
+    @lockutils.synchronized('apic-portlock')
+    def _delete_port_path(self, context, atenant_id, anetwork_id):
+        if not self._get_active_path_count(context):
+            self.apic_manager.ensure_path_deleted_for_port(
+                atenant_id, anetwork_id,
+                context.host)
+
+    def _delete_path_if_last(self, context):
+        if not self._get_active_path_count(context):
+            tenant_id = context.current['tenant_id']
+            atenant_id = self.name_mapper.tenant(context, tenant_id)
+            network_id = context.network.current['id']
+            anetwork_id = self.name_mapper.network(context, network_id)
+            self._delete_port_path(context, atenant_id, anetwork_id)
+
+    def _get_subnet_info(self, context, subnet):
+        tenant_id = subnet['tenant_id']
+        network_id = subnet['network_id']
+        network = context._plugin.get_network(context._plugin_context,
+                                              network_id)
+        if not network.get('router:external'):
+            cidr = netaddr.IPNetwork(subnet['cidr'])
+            gateway_ip = '%s/%s' % (subnet['gateway_ip'], str(cidr.prefixlen))
+
+            # Convert to APIC IDs
+            tenant_id = self.name_mapper.tenant(context, tenant_id)
+            network_id = self.name_mapper.network(context, network_id)
+            return tenant_id, network_id, gateway_ip
 
     def create_port_postcommit(self, context):
         self._perform_port_operations(context)
 
+    def update_port_precommit(self, context):
+        orig = context.original
+        curr = context.current
+        if (orig['device_owner'] != curr['device_owner']
+                or orig['device_id'] != curr['device_id']):
+            raise exc.ApicOperationNotSupported(
+                resource='Port', msg='Port device owner and id cannot be '
+                                     'changed.')
+
     def update_port_postcommit(self, context):
         self._perform_port_operations(context)
 
+    def delete_port_postcommit(self, context):
+        port = context.current
+        # Check if a compute port
+        if port.get('device_owner', '').startswith('compute'):
+            self._delete_path_if_last(context)
+        elif port.get('device_owner') == n_constants.DEVICE_OWNER_ROUTER_GW:
+            self._delete_contract(context)
+        elif port.get('device_owner') == n_constants.DEVICE_OWNER_DHCP:
+            self._delete_path_if_last(context)
+
     def create_network_postcommit(self, context):
-        net_id = context.current['id']
-        tenant_id = context.current['tenant_id']
-        net_name = context.current['name']
+        if not context.current.get('router:external'):
+            tenant_id = context.current['tenant_id']
+            network_id = context.current['id']
 
-        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)
+            # Convert to APIC IDs
+            tenant_id = self.name_mapper.tenant(context, tenant_id)
+            network_id = self.name_mapper.network(context, network_id)
+
+            # Create BD and EPG for this network
+            with self.apic_manager.apic.transaction() as trs:
+                self.apic_manager.ensure_bd_created_on_apic(tenant_id,
+                                                            network_id,
+                                                            transaction=trs)
+                self.apic_manager.ensure_epg_created(
+                    tenant_id, network_id, transaction=trs)
 
     def delete_network_postcommit(self, context):
-        net_id = context.current['id']
-        tenant_id = context.current['tenant_id']
+        if not context.current.get('router:external'):
+            tenant_id = context.current['tenant_id']
+            network_id = context.current['id']
 
-        self.apic_manager.delete_bd_on_apic(tenant_id, net_id)
-        self.apic_manager.delete_epg_for_network(tenant_id, net_id)
+            # Convert to APIC IDs
+            tenant_id = self.name_mapper.tenant(context, tenant_id)
+            network_id = self.name_mapper.network(context, network_id)
+
+            # Delete BD and EPG for this network
+            with self.apic_manager.apic.transaction() as trs:
+                self.apic_manager.delete_epg_for_network(tenant_id, network_id,
+                                                         transaction=trs)
+                self.apic_manager.delete_bd_on_apic(tenant_id, network_id,
+                                                    transaction=trs)
+        else:
+            network_name = context.current['name']
+            if self.apic_manager.ext_net_dict.get(network_name):
+                network_id = self.name_mapper.network(context,
+                                                      context.current['id'])
+                self.apic_manager.delete_external_routed_network(network_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)
+        info = self._get_subnet_info(context, context.current)
+        if info:
+            tenant_id, network_id, gateway_ip = info
+            # Create subnet on BD
+            self.apic_manager.ensure_subnet_created_on_apic(
+                tenant_id, network_id, gateway_ip)
+
+    def update_subnet_postcommit(self, context):
+        if context.current['gateway_ip'] != context.original['gateway_ip']:
+            with self.apic_manager.apic.transaction() as trs:
+                info = self._get_subnet_info(context, context.original)
+                if info:
+                    tenant_id, network_id, gateway_ip = info
+                    # Delete subnet
+                    self.apic_manager.ensure_subnet_deleted_on_apic(
+                        tenant_id, network_id, gateway_ip, transaction=trs)
+                info = self._get_subnet_info(context, context.current)
+                if info:
+                    tenant_id, network_id, gateway_ip = info
+                    # Create subnet
+                    self.apic_manager.ensure_subnet_created_on_apic(
+                        tenant_id, network_id, gateway_ip, transaction=trs)
+
+    def delete_subnet_postcommit(self, context):
+        info = self._get_subnet_info(context, context.current)
+        if info:
+            tenant_id, network_id, gateway_ip = info
+            self.apic_manager.ensure_subnet_deleted_on_apic(
+                tenant_id, network_id, gateway_ip)
index 12318a2d7597921dec126bc8a4b76aa14e5ce0b1..7e2e9c4a4e1456f7d82a5d954a7cea2220937cb0 100644 (file)
 #
 # @author: Henry Gessau, Cisco Systems
 
-import mock
+import contextlib
 import requests
 
+import mock
 from oslo.config import cfg
 
-from neutron.common import config as neutron_config
-from neutron.plugins.ml2 import config as ml2_config
 from neutron.tests import base
 
 
@@ -45,9 +44,8 @@ APIC_SUBJECT = 'testSubject'
 APIC_FILTER = 'carbonFilter'
 APIC_ENTRY = 'forcedEntry'
 
-APIC_VMMP = 'OpenStack'
+APIC_SYSTEM_ID = 'sysid'
 APIC_DOMAIN = 'cumuloNimbus'
-APIC_PDOM = 'rainStorm'
 
 APIC_NODE_PROF = 'red'
 APIC_LEAF = 'green'
@@ -68,6 +66,19 @@ APIC_VLANID_TO = 2999
 APIC_VLAN_FROM = 'vlan-%d' % APIC_VLANID_FROM
 APIC_VLAN_TO = 'vlan-%d' % APIC_VLANID_TO
 
+APIC_ROUTER = 'router_id'
+
+APIC_EXT_SWITCH = '203'
+APIC_EXT_MODULE = '1'
+APIC_EXT_PORT = '34'
+APIC_EXT_ENCAP = 'vlan-100'
+APIC_EXT_CIDR_EXPOSED = '10.10.40.2/16'
+APIC_EXT_GATEWAY_IP = '10.10.40.1'
+
+APIC_KEY = 'key'
+
+KEYSTONE_TOKEN = '123Token123'
+
 
 class ControllerMixin(object):
 
@@ -114,6 +125,10 @@ class ControllerMixin(object):
         self.mock_response_for_post('aaaLogin', userName=APIC_USR,
                                     token='ok', refreshTimeoutSeconds=timeout)
 
+    @contextlib.contextmanager
+    def fake_transaction(self, *args, **kwargs):
+        yield 'transaction'
+
 
 class ConfigMixin(object):
 
@@ -124,8 +139,13 @@ class ConfigMixin(object):
 
     def set_up_mocks(self):
         # Mock the configuration file
-        args = ['--config-file', base.etcdir('neutron.conf.test')]
-        neutron_config.init(args=args)
+        base.BaseTestCase.config_parse()
+
+        # Configure global option apic_system_id
+        cfg.CONF.set_override('apic_system_id', APIC_SYSTEM_ID)
+
+        # Configure option keystone_authtoken
+        cfg.CONF.keystone_authtoken = KEYSTONE_TOKEN
 
         # Configure the ML2 mechanism drivers and network types
         ml2_opts = {
@@ -133,14 +153,23 @@ class ConfigMixin(object):
             'tenant_network_types': ['vlan'],
         }
         for opt, val in ml2_opts.items():
-                ml2_config.cfg.CONF.set_override(opt, val, 'ml2')
+                cfg.CONF.set_override(opt, val, 'ml2')
+
+        # Configure the ML2 type_vlan opts
+        ml2_type_vlan_opts = {
+            'vlan_ranges': ['physnet1:100:199'],
+        }
+        cfg.CONF.set_override('network_vlan_ranges',
+                              ml2_type_vlan_opts['vlan_ranges'],
+                              'ml2_type_vlan')
+        self.vlan_ranges = ml2_type_vlan_opts['vlan_ranges']
 
         # Configure the Cisco APIC mechanism driver
         apic_test_config = {
             'apic_hosts': APIC_HOSTS,
             'apic_username': APIC_USR,
             'apic_password': APIC_PWD,
-            'apic_vmm_domain': APIC_DOMAIN,
+            'apic_domain_name': APIC_SYSTEM_ID,
             'apic_vlan_ns_name': APIC_VLAN_NAME,
             'apic_vlan_range': '%d:%d' % (APIC_VLANID_FROM, APIC_VLANID_TO),
             'apic_node_profile': APIC_NODE_PROF,
@@ -149,13 +178,43 @@ class ConfigMixin(object):
         }
         for opt, val in apic_test_config.items():
             cfg.CONF.set_override(opt, val, 'ml2_cisco_apic')
+        self.apic_config = cfg.CONF.ml2_cisco_apic
 
+        # Configure switch topology
         apic_switch_cfg = {
-            'apic_switch:east01': {'ubuntu1,ubuntu2': ['3/11']},
-            'apic_switch:east02': {'rhel01,rhel02': ['4/21'],
-                                   'rhel03': ['4/22']},
+            'apic_switch:101': {'ubuntu1,ubuntu2': ['3/11']},
+            'apic_switch:102': {'rhel01,rhel02': ['4/21'],
+                                'rhel03': ['4/22']},
+        }
+        self.switch_dict = {
+            '101': {
+                '3/11': ['ubuntu1', 'ubuntu2'],
+            },
+            '102': {
+                '4/21': ['rhel01', 'rhel02'],
+                '4/22': ['rhel03'],
+            },
+        }
+        self.vpc_dict = {
+            '201': '202',
+            '202': '201',
         }
-        self.mocked_parser = mock.patch.object(cfg,
-                                               'MultiConfigParser').start()
+        self.external_network_dict = {
+            APIC_NETWORK + '-name': {
+                'switch': APIC_EXT_SWITCH,
+                'port': APIC_EXT_MODULE + '/' + APIC_EXT_PORT,
+                'encap': APIC_EXT_ENCAP,
+                'cidr_exposed': APIC_EXT_CIDR_EXPOSED,
+                'gateway_ip': APIC_EXT_GATEWAY_IP,
+            },
+        }
+        self.mocked_parser = mock.patch.object(
+            cfg, 'MultiConfigParser').start()
         self.mocked_parser.return_value.read.return_value = [apic_switch_cfg]
         self.mocked_parser.return_value.parsed = [apic_switch_cfg]
+
+
+class FakeDbContract(object):
+
+    def __init__(self, contract_id):
+        self.contract_id = contract_id
index 8526132e0525f0996fae3f55f543d6c20ea2b45e..33cc3ce2236014e9dc1b88f5a8bb2b1c5956043e 100644 (file)
 #
 # @author: Henry Gessau, Cisco Systems
 
-import mock
 import sys
 
+import mock
+
 sys.modules["apicapi"] = mock.Mock()
 
+from neutron.common import constants as n_constants
 from neutron.extensions import portbindings
 from neutron.plugins.ml2.drivers.cisco.apic import mechanism_apic as md
+from neutron.plugins.ml2.drivers import type_vlan  # noqa
 from neutron.tests import base
 from neutron.tests.unit.ml2.drivers.cisco.apic import (
     test_cisco_apic_common as mocked)
@@ -50,13 +53,27 @@ class TestCiscoApicMechDriver(base.BaseTestCase,
 
         self.mock_apic_manager_login_responses()
         self.driver = md.APICMechanismDriver()
+        self.driver.synchronizer = None
+        md.APICMechanismDriver.get_base_synchronizer = mock.Mock()
         self.driver.vif_type = 'test-vif_type'
         self.driver.cap_port_filter = 'test-cap_port_filter'
+        self.driver.name_mapper = mock.Mock()
+        self.driver.name_mapper.tenant.return_value = mocked.APIC_TENANT
+        self.driver.name_mapper.network.return_value = mocked.APIC_NETWORK
+        self.driver.name_mapper.subnet.return_value = mocked.APIC_SUBNET
+        self.driver.name_mapper.port.return_value = mocked.APIC_PORT
+        self.driver.name_mapper.router.return_value = mocked.APIC_ROUTER
+        self.driver.name_mapper.app_profile.return_value = mocked.APIC_AP
+        self.driver.apic_manager = mock.Mock(
+            name_mapper=mock.Mock(), ext_net_dict=self.external_network_dict)
+
+        self.driver.apic_manager.apic.transaction = self.fake_transaction
 
     def test_initialize(self):
-        mgr = self.driver.apic_manager = mock.Mock()
+        mgr = self.driver.apic_manager
         self.driver.initialize()
         mgr.ensure_infra_created_on_apic.assert_called_once()
+        mgr.ensure_bgp_pod_policy_created_on_apic.assert_called_once()
 
     def test_update_port_postcommit(self):
         net_ctx = self._get_network_context(mocked.APIC_TENANT,
@@ -65,36 +82,95 @@ class TestCiscoApicMechDriver(base.BaseTestCase,
         port_ctx = self._get_port_context(mocked.APIC_TENANT,
                                           mocked.APIC_NETWORK,
                                           'vm1', net_ctx, HOST_ID1)
-        mgr = self.driver.apic_manager = mock.Mock()
+        mgr = self.driver.apic_manager
         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)
+            ENCAP, transaction='transaction')
+
+    def test_update_gw_port_postcommit(self):
+        net_ctx = self._get_network_context(mocked.APIC_TENANT,
+                                            mocked.APIC_NETWORK,
+                                            TEST_SEGMENT1, external=True)
+        port_ctx = self._get_port_context(mocked.APIC_TENANT,
+                                          mocked.APIC_NETWORK,
+                                          'vm1', net_ctx, HOST_ID1, gw=True)
+        mgr = self.driver.apic_manager
+        mgr.get_router_contract.return_value = mocked.FakeDbContract(
+            mocked.APIC_CONTRACT)
+        self.driver.update_port_postcommit(port_ctx)
+        mgr.get_router_contract.assert_called_once_with(
+            port_ctx.current['device_id'])
+        mgr.ensure_context_enforced.assert_called_once()
+        mgr.ensure_external_routed_network_created.assert_called_once_with(
+            mocked.APIC_NETWORK, transaction='transaction')
+        mgr.ensure_logical_node_profile_created.assert_called_once_with(
+            mocked.APIC_NETWORK, mocked.APIC_EXT_SWITCH,
+            mocked.APIC_EXT_MODULE, mocked.APIC_EXT_PORT,
+            mocked.APIC_EXT_ENCAP, mocked.APIC_EXT_CIDR_EXPOSED,
+            transaction='transaction')
+        mgr.ensure_static_route_created.assert_called_once_with(
+            mocked.APIC_NETWORK, mocked.APIC_EXT_SWITCH,
+            mocked.APIC_EXT_GATEWAY_IP, transaction='transaction')
+        mgr.ensure_external_epg_created.assert_called_once_with(
+            mocked.APIC_NETWORK, transaction='transaction')
+        mgr.ensure_external_epg_consumed_contract.assert_called_once_with(
+            mocked.APIC_NETWORK, mgr.get_router_contract.return_value,
+            transaction='transaction')
+        mgr.ensure_external_epg_provided_contract.assert_called_once_with(
+            mocked.APIC_NETWORK, mgr.get_router_contract.return_value,
+            transaction='transaction')
+
+    def test_update_gw_port_postcommit_fail_contract_create(self):
+        net_ctx = self._get_network_context(mocked.APIC_TENANT,
+                                            mocked.APIC_NETWORK,
+                                            TEST_SEGMENT1, external=True)
+        port_ctx = self._get_port_context(mocked.APIC_TENANT,
+                                          mocked.APIC_NETWORK,
+                                          'vm1', net_ctx, HOST_ID1, gw=True)
+        mgr = self.driver.apic_manager
+        self.driver.update_port_postcommit(port_ctx)
+        mgr.ensure_external_routed_network_deleted.assert_called_once()
 
     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()
+        mgr = self.driver.apic_manager
         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')
+            mocked.APIC_TENANT, mocked.APIC_NETWORK, transaction='transaction')
+        mgr.ensure_epg_created.assert_called_once_with(
+            mocked.APIC_TENANT, mocked.APIC_NETWORK, transaction='transaction')
+
+    def test_create_external_network_postcommit(self):
+        ctx = self._get_network_context(mocked.APIC_TENANT,
+                                        mocked.APIC_NETWORK,
+                                        TEST_SEGMENT1, external=True)
+        mgr = self.driver.apic_manager
+        self.driver.create_network_postcommit(ctx)
+        self.assertFalse(mgr.ensure_bd_created_on_apic.called)
+        self.assertFalse(mgr.ensure_epg_created.called)
 
     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()
+        mgr = self.driver.apic_manager
         self.driver.delete_network_postcommit(ctx)
         mgr.delete_bd_on_apic.assert_called_once_with(
-            mocked.APIC_TENANT, mocked.APIC_NETWORK)
+            mocked.APIC_TENANT, mocked.APIC_NETWORK, transaction='transaction')
         mgr.delete_epg_for_network.assert_called_once_with(
-            mocked.APIC_TENANT, mocked.APIC_NETWORK)
+            mocked.APIC_TENANT, mocked.APIC_NETWORK, transaction='transaction')
+
+    def test_delete_external_network_postcommit(self):
+        ctx = self._get_network_context(mocked.APIC_TENANT,
+                                        mocked.APIC_NETWORK,
+                                        TEST_SEGMENT1, external=True)
+        mgr = self.driver.apic_manager
+        self.driver.delete_network_postcommit(ctx)
+        mgr.delete_external_routed_network.assert_called_once_with(
+            mocked.APIC_NETWORK)
 
     def test_create_subnet_postcommit(self):
         net_ctx = self._get_network_context(mocked.APIC_TENANT,
@@ -103,18 +179,20 @@ class TestCiscoApicMechDriver(base.BaseTestCase,
         subnet_ctx = self._get_subnet_context(SUBNET_GATEWAY,
                                               SUBNET_CIDR,
                                               net_ctx)
-        mgr = self.driver.apic_manager = mock.Mock()
+        mgr = self.driver.apic_manager
         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'):
+                             seg_type='vlan', external=False):
         network = {'id': net_id,
                    'name': net_id + '-name',
                    'tenant_id': tenant_id,
                    'provider:segmentation_id': seg_id}
+        if external:
+            network['router:external'] = True
         if seg_id:
             network_segments = [{'id': seg_id,
                                  'segmentation_id': ENCAP,
@@ -132,7 +210,8 @@ class TestCiscoApicMechDriver(base.BaseTestCase,
                   'cidr': cidr}
         return FakeSubnetContext(subnet, network)
 
-    def _get_port_context(self, tenant_id, net_id, vm_id, network, host):
+    def _get_port_context(self, tenant_id, net_id, vm_id, network, host,
+                          gw=False):
         port = {'device_id': vm_id,
                 'device_owner': 'compute',
                 'binding:host_id': host,
@@ -140,6 +219,9 @@ class TestCiscoApicMechDriver(base.BaseTestCase,
                 'id': mocked.APIC_PORT,
                 'name': mocked.APIC_PORT,
                 'network_id': net_id}
+        if gw:
+            port['device_owner'] = n_constants.DEVICE_OWNER_ROUTER_GW
+            port['device_id'] = mocked.APIC_ROUTER
         return FakePortContext(port, network)
 
 
@@ -165,6 +247,9 @@ class FakeSubnetContext(object):
     def __init__(self, subnet, network):
         self._subnet = subnet
         self._network = network
+        self._plugin = mock.Mock()
+        self._plugin_context = mock.Mock()
+        self._plugin.get_network.return_value = {}
 
     @property
     def current(self):
@@ -179,11 +264,11 @@ 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
+        self._plugin = mock.Mock()
+        self._plugin_context = mock.Mock()
+        self._plugin.get_ports.return_value = []
         if network.network_segments:
             self._bound_segment = network.network_segments[0]
         else:
@@ -193,14 +278,6 @@ class FakePortContext(object):
     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
index f05b7cdacc48bea3d20496e94e226d5105a42533..8d829a5ea918cd707a47ae4b0da94aa5146a81b0 100644 (file)
 #
 # @author: Arvind Somya (asomya@cisco.com), Cisco Systems
 
-import mock
 import sys
 
+import mock
+
 sys.modules["apicapi"] = mock.Mock()
 
 from neutron.services.l3_router import l3_apic
+from neutron.tests.unit.ml2.drivers.cisco.apic import (
+    test_cisco_apic_common as mocked)
 from neutron.tests.unit import testlib_api
 
 TENANT = 'tenant1'
@@ -58,10 +61,14 @@ class FakePort(object):
         self.subnet_id = SUBNET
 
 
-class TestCiscoApicL3Plugin(testlib_api.SqlTestCase):
+class TestCiscoApicL3Plugin(testlib_api.SqlTestCase,
+                            mocked.ControllerMixin,
+                            mocked.ConfigMixin):
 
     def setUp(self):
         super(TestCiscoApicL3Plugin, self).setUp()
+        mocked.ControllerMixin.set_up_mocks(self)
+        mocked.ConfigMixin.set_up_mocks(self)
         self.plugin = l3_apic.ApicL3ServicePlugin()
         self.context = FakeContext()
         self.context.tenant_id = TENANT