]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Cisco APIC Layer 3 Service plugin
authorArvind Somya <asomya@cisco.com>
Wed, 28 May 2014 15:37:40 +0000 (08:37 -0700)
committerArvind Somya <asomya@cisco.com>
Wed, 4 Jun 2014 15:37:06 +0000 (08:37 -0700)
This plugin provides Layer 3 features for the Cisco APIC.
The service plugin is currently limited to internal gateways
due to limitations in the Hardware.

Change-Id: I81cde4d721f9d72ec67baaf64ab91148b3799d78
Implements: blueprint cisco-apic-l3

neutron/services/l3_router/l3_apic.py [new file with mode: 0644]
neutron/tests/unit/services/l3_router/__init__.py [new file with mode: 0644]
neutron/tests/unit/services/l3_router/test_l3_apic_plugin.py [new file with mode: 0644]

diff --git a/neutron/services/l3_router/l3_apic.py b/neutron/services/l3_router/l3_apic.py
new file mode 100644 (file)
index 0000000..02198e8
--- /dev/null
@@ -0,0 +1,135 @@
+# 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.
+
+from neutron.db import api as qdbapi
+from neutron.db import db_base_plugin_v2
+from neutron.db import extraroute_db
+from neutron.db import l3_gwmode_db
+from neutron.db import model_base
+from neutron.openstack.common import excutils
+from neutron.openstack.common import log as logging
+from neutron.plugins.common import constants
+from neutron.plugins.ml2.drivers.cisco.apic import apic_manager
+
+LOG = logging.getLogger(__name__)
+
+
+class ApicL3ServicePlugin(db_base_plugin_v2.NeutronDbPluginV2,
+                          db_base_plugin_v2.CommonDbMixin,
+                          extraroute_db.ExtraRoute_db_mixin,
+                          l3_gwmode_db.L3_NAT_db_mixin):
+    """Implementation of the APIC L3 Router Service Plugin.
+
+    This class implements a L3 service plugin that provides
+    internal gateway functionality for the Cisco APIC (Application
+    Policy Infrastructure Controller).
+    """
+    supported_extension_aliases = ["router", "ext-gw-mode", "extraroute"]
+
+    def __init__(self):
+        super(ApicL3ServicePlugin, self).__init__()
+        qdbapi.register_models(base=model_base.BASEV2)
+        self.manager = apic_manager.APICManager()
+
+    @staticmethod
+    def get_plugin_type():
+        return constants.L3_ROUTER_NAT
+
+    @staticmethod
+    def get_plugin_description():
+        """Returns string description of the plugin."""
+        return _("L3 Router Service Plugin for basic L3 using the APIC")
+
+    def _add_epg_to_contract(self, tenant_id, epg, contract):
+        """Add an End Point Group(EPG) to a contract as provider/consumer."""
+        if self.manager.db.get_provider_contract():
+            # Set this network's EPG as a consumer
+            self.manager.set_contract_for_epg(tenant_id, epg.epg_id,
+                                              contract.contract_id)
+        else:
+            # Set this network's EPG as a provider
+            self.manager.set_contract_for_epg(tenant_id, epg.epg_id,
+                                              contract.contract_id,
+                                              provider=True)
+
+    def add_router_interface(self, context, router_id, interface_info):
+        """Attach a subnet to a router."""
+        tenant_id = context.tenant_id
+        subnet_id = interface_info['subnet_id']
+        LOG.debug("Attaching subnet %(subnet_id)s to "
+                  "router %(router_id)s" % {'subnet_id': subnet_id,
+                                            'router_id': router_id})
+
+        # Get network for this subnet
+        subnet = self.get_subnet(context, subnet_id)
+        network_id = subnet['network_id']
+        net_name = self.get_network(context, network_id)['name']
+
+        # Setup tenant filters and contracts
+        contract = self.manager.create_tenant_contract(tenant_id)
+
+        # Check for a provider EPG
+        epg = self.manager.ensure_epg_created_for_network(tenant_id,
+                                                          network_id,
+                                                          net_name)
+        self._add_epg_to_contract(tenant_id, epg, contract)
+
+        # Create DB port
+        try:
+            return super(ApicL3ServicePlugin, self).add_router_interface(
+                context, router_id, interface_info)
+        except Exception:
+            LOG.error(_("Error attaching subnet %(subnet_id)s to "
+                        "router %(router_id)s") % {'subnet_id': subnet_id,
+                                                   'router_id': router_id})
+            with excutils.save_and_reraise_exception():
+                self.manager.delete_contract_for_epg(tenant_id, epg.epg_id,
+                                                     contract.contract_id,
+                                                     provider=epg.provider)
+
+    def remove_router_interface(self, context, router_id, interface_info):
+        """Detach a subnet from a router."""
+        tenant_id = context.tenant_id
+        subnet_id = interface_info['subnet_id']
+        LOG.debug("Detaching subnet %(subnet_id)s from "
+                  "router %(router_id)s" % {'subnet_id': subnet_id,
+                                            'router_id': router_id})
+
+        # Get network for this subnet
+        subnet = self.get_subnet(context, subnet_id)
+        network_id = subnet['network_id']
+        network = self.get_network(context, network_id)
+
+        contract = self.manager.create_tenant_contract(tenant_id)
+
+        epg = self.manager.ensure_epg_created_for_network(tenant_id,
+                                                          network_id,
+                                                          network['name'])
+        # Delete contract for this epg
+        self.manager.delete_contract_for_epg(tenant_id, epg.epg_id,
+                                             contract.contract_id,
+                                             provider=epg.provider)
+
+        try:
+            return super(ApicL3ServicePlugin, self).remove_router_interface(
+                context, router_id, interface_info)
+        except Exception:
+            LOG.error(_("Error detaching subnet %(subnet_id)s from "
+                        "router %(router_id)s") % {'subnet_id': subnet_id,
+                                                   'router_id': router_id})
+            with excutils.save_and_reraise_exception():
+                self._add_epg_to_contract(tenant_id, epg, contract)
diff --git a/neutron/tests/unit/services/l3_router/__init__.py b/neutron/tests/unit/services/l3_router/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/tests/unit/services/l3_router/test_l3_apic_plugin.py b/neutron/tests/unit/services/l3_router/test_l3_apic_plugin.py
new file mode 100644 (file)
index 0000000..6bc33ef
--- /dev/null
@@ -0,0 +1,134 @@
+# 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: Arvind Somya (asomya@cisco.com), Cisco Systems
+
+import mock
+
+from neutron.services.l3_router import l3_apic
+from neutron.tests import base
+
+TENANT = 'tenant1'
+TENANT_CONTRACT = 'abcd'
+ROUTER = 'router1'
+SUBNET = 'subnet1'
+NETWORK = 'network1'
+NETWORK_NAME = 'one_network'
+NETWORK_EPG = 'one_network-epg'
+TEST_SEGMENT1 = 'test-segment1'
+SUBNET_GATEWAY = '10.3.2.1'
+SUBNET_CIDR = '10.3.1.0/24'
+SUBNET_NETMASK = '24'
+
+
+class FakeContext(object):
+    def __init__(self):
+        self.tenant_id = None
+
+
+class FakeContract(object):
+    def __init__(self):
+        self.contract_id = '123'
+
+
+class FakeEpg(object):
+    def __init__(self):
+        self.epg_id = 'abcd_epg'
+
+
+class FakePort(object):
+    def __init__(self):
+        self.id = 'Fake_port_id'
+        self.network_id = NETWORK
+        self.subnet_id = SUBNET
+
+
+class TestCiscoApicL3Plugin(base.BaseTestCase):
+
+    def setUp(self):
+        super(TestCiscoApicL3Plugin, self).setUp()
+        mock.patch('neutron.plugins.ml2.drivers.cisco.apic.apic_manager.'
+                   'APICManager').start()
+        self.plugin = l3_apic.ApicL3ServicePlugin()
+        self.context = FakeContext()
+        self.context.tenant_id = TENANT
+        self.interface_info = {'subnet_id': SUBNET, 'network_id': NETWORK,
+                               'name': NETWORK_NAME}
+
+        self.contract = FakeContract()
+        self.plugin.manager.create_tenant_contract = mock.Mock()
+        ctmk = mock.PropertyMock(return_value=self.contract.contract_id)
+        type(self.plugin.manager.create_tenant_contract).contract_id = ctmk
+        self.epg = FakeEpg()
+        self.plugin.manager.ensure_epg_created_for_network = mock.Mock()
+        epmk = mock.PropertyMock(return_value=self.epg.epg_id)
+        type(self.plugin.manager.ensure_epg_created_for_network).epg_id = epmk
+
+        self.plugin.manager.db.get_provider_contract = mock.Mock(
+            return_value=None)
+        self.plugin.manager.set_contract_for_epg = mock.Mock(
+            return_value=True)
+
+        self.plugin.get_subnet = mock.Mock(return_value=self.interface_info)
+        self.plugin.get_network = mock.Mock(return_value=self.interface_info)
+        mock.patch('neutron.db.l3_gwmode_db.L3_NAT_db_mixin.'
+                   '_core_plugin').start()
+        mock.patch('neutron.db.l3_gwmode_db.L3_NAT_db_mixin.'
+                   'add_router_interface').start()
+        mock.patch('neutron.db.l3_gwmode_db.L3_NAT_db_mixin.'
+                   'remove_router_interface').start()
+        mock.patch('neutron.openstack.common.excutils.'
+                   'save_and_reraise_exception').start()
+
+    def test_add_router_interface(self):
+        mgr = self.plugin.manager
+        self.plugin.add_router_interface(self.context, ROUTER,
+                                         self.interface_info)
+        mgr.create_tenant_contract.assert_called_once_with(TENANT)
+        mgr.create_tenant_contract.assertEqual(TENANT_CONTRACT)
+        mgr.ensure_epg_created_for_network.assert_called_once_with(
+            TENANT, NETWORK, NETWORK_NAME)
+        mgr.ensure_epg_created_for_network.assertEqual(NETWORK_EPG)
+        mgr.db.get_provider_contract.assert_called_once()
+        mgr.db.get_provider_contract.assertEqual(None)
+        mgr.set_contract_for_epg.assert_called_once()
+
+    def test_remove_router_interface(self):
+        mgr = self.plugin.manager
+        self.plugin.remove_router_interface(self.context, ROUTER,
+                                            self.interface_info)
+        mgr.create_tenant_contract.assert_called_once_with(TENANT)
+        mgr.ensure_epg_created_for_network.assert_called_once_with(
+            TENANT, NETWORK, NETWORK_NAME)
+        mgr.ensure_epg_created_for_network.assertEqual(NETWORK_EPG)
+        mgr.delete_contract_for_epg.assert_called_once()
+
+    def test_add_router_interface_fail_contract_delete(self):
+        mgr = self.plugin.manager
+        with mock.patch('neutron.db.l3_gwmode_db.L3_NAT_db_mixin.'
+                        'add_router_interface',
+                        side_effect=KeyError()):
+            self.plugin.add_router_interface(self.context, ROUTER,
+                                             self.interface_info)
+            mgr.delete_contract_for_epg.assert_called_once()
+
+    def test_delete_router_interface_fail_contract_create(self):
+        mgr = self.plugin.manager
+        with mock.patch('neutron.db.l3_gwmode_db.L3_NAT_db_mixin.'
+                        'remove_router_interface',
+                        side_effect=KeyError()):
+            self.plugin.remove_router_interface(self.context, ROUTER,
+                                                self.interface_info)
+            mgr.set_contract_for_epg.assert_called_once()