]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
OpenFlow distributed router support in NEC plugin
authorAkihiro MOTOKI <motoki@da.jp.nec.com>
Fri, 23 Aug 2013 06:22:04 +0000 (15:22 +0900)
committerAkihiro MOTOKI <motoki@da.jp.nec.com>
Wed, 4 Sep 2013 08:15:59 +0000 (17:15 +0900)
Implements blueprint nec-distribute-router

Two types of neutron router will be supported: l3-agent and distributed.
A type can be specified through "provider" attribute of a router.
The naming of the attribute "provider" is intentional since I plan to
support the service provider framework for router in the future and
would like to make it easy to migrate.

distributed router in NEC OpenFLow controller now does not support NAT,
so l3-agent and distributed router coexists. To achieve it, l3-agent
scheudler logic is modified in NEC plugin to exclude distributed routers
from candidates of floating IP hosting routers.

To support the above feature, the following related changes are done:
- Adds a new driver to PFC driver which supports OpenFlow based router
  support in NEC OpenFlow products in PFlow v5.
- Update ofc_client to extract detail error message
  from OpenFlow controller

This commit also changes the following outside of NEC plugin:
- Makes L3 agent notifiers configurable.
  l3-agent router and OpenFlow distributed router can coexist.
  Notication to l3-agent should be done only when routers are
  hosted by l3-agent, so we need custom L3 agent notifiers
  to filter non l3-agent routers.
- Split test_agent_scheduler base class (in OVS plugin) into
  the base setup and testcases. By doing so we can implement
  custom testcases related to agent scheduler.

Change-Id: I538201742950a61b92fb05c49a9256bc96ae9014

28 files changed:
etc/neutron/plugins/nec/nec.ini
neutron/db/l3_db.py
neutron/db/migration/alembic_migrations/versions/66a59a7f516_nec_openflow_router.py [new file with mode: 0644]
neutron/plugins/nec/common/config.py
neutron/plugins/nec/common/constants.py [new file with mode: 0644]
neutron/plugins/nec/common/exceptions.py
neutron/plugins/nec/common/ofc_client.py
neutron/plugins/nec/db/api.py
neutron/plugins/nec/db/models.py
neutron/plugins/nec/db/router.py [new file with mode: 0644]
neutron/plugins/nec/drivers/__init__.py
neutron/plugins/nec/drivers/pfc.py
neutron/plugins/nec/drivers/trema.py
neutron/plugins/nec/extensions/router_provider.py [new file with mode: 0644]
neutron/plugins/nec/nec_plugin.py
neutron/plugins/nec/nec_router.py [new file with mode: 0644]
neutron/plugins/nec/ofc_manager.py
neutron/plugins/nec/router_drivers.py [new file with mode: 0644]
neutron/tests/unit/nec/stub_ofc_driver.py
neutron/tests/unit/nec/test_agent_scheduler.py
neutron/tests/unit/nec/test_nec_plugin.py
neutron/tests/unit/nec/test_ofc_client.py [new file with mode: 0644]
neutron/tests/unit/nec/test_ofc_manager.py
neutron/tests/unit/nec/test_pfc_driver.py
neutron/tests/unit/nec/test_router.py [new file with mode: 0644]
neutron/tests/unit/openvswitch/test_agent_scheduler.py
neutron/tests/unit/test_extension_extraroute.py
neutron/tests/unit/test_l3_plugin.py

index cbcc0ec5c46f7da8e0b077e16995230f02776466..cc9d812bfd3ef757428897493b3dd23894e2f95f 100644 (file)
@@ -41,3 +41,9 @@ firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewal
 
 # Certificate file
 # cert_file =
+
+[provider]
+# Default router provider to use.
+# default_router_provider = l3-agent
+# List of enabled router providers.
+# router_providers = l3-agent,openflow
index 7059c00ac477c58bc0b4dbcfbe1b61778d53842d..87cf9dc9cf422103286e4fc3e1396ff703bc2040 100644 (file)
@@ -91,6 +91,8 @@ class FloatingIP(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
 class L3_NAT_db_mixin(l3.RouterPluginBase):
     """Mixin class to add L3/NAT router methods to db_plugin_base_v2."""
 
+    l3_rpc_notifier = l3_rpc_agent_api.L3AgentNotify
+
     def _network_model_hook(self, context, original_model, query):
         query = query.outerjoin(ExternalNetwork,
                                 (original_model.id ==
@@ -186,7 +188,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
             # Ensure we actually have something to update
             if r.keys():
                 router_db.update(r)
-        l3_rpc_agent_api.L3AgentNotify.routers_updated(
+        self.l3_rpc_notifier.routers_updated(
             context, [router_db['id']])
         return self._make_router_dict(router_db)
 
@@ -278,7 +280,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
                 self._delete_port(context.elevated(), ports[0]['id'])
 
             context.session.delete(router)
-        l3_rpc_agent_api.L3AgentNotify.router_deleted(context, id)
+        self.l3_rpc_notifier.router_deleted(context, id)
 
     def get_router(self, context, id, fields=None):
         router = self._get_router(context, id)
@@ -385,7 +387,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
                  'device_owner': DEVICE_OWNER_ROUTER_INTF,
                  'name': ''}})
 
-        l3_rpc_agent_api.L3AgentNotify.routers_updated(
+        self.l3_rpc_notifier.routers_updated(
             context, [router_id], 'add_router_interface')
         info = {'id': router_id,
                 'tenant_id': subnet['tenant_id'],
@@ -457,7 +459,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
             if not found:
                 raise l3.RouterInterfaceNotFoundForSubnet(router_id=router_id,
                                                           subnet_id=subnet_id)
-        l3_rpc_agent_api.L3AgentNotify.routers_updated(
+        self.l3_rpc_notifier.routers_updated(
             context, [router_id], 'remove_router_interface')
         info = {'id': router_id,
                 'tenant_id': subnet['tenant_id'],
@@ -670,7 +672,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
 
         router_id = floatingip_db['router_id']
         if router_id:
-            l3_rpc_agent_api.L3AgentNotify.routers_updated(
+            self.l3_rpc_notifier.routers_updated(
                 context, [router_id],
                 'create_floatingip')
         return self._make_floatingip_dict(floatingip_db)
@@ -693,8 +695,8 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
         if router_id and router_id != before_router_id:
             router_ids.append(router_id)
         if router_ids:
-            l3_rpc_agent_api.L3AgentNotify.routers_updated(context, router_ids,
-                                                           'update_floatingip')
+            self.l3_rpc_notifier.routers_updated(
+                context, router_ids, 'update_floatingip')
         return self._make_floatingip_dict(floatingip_db)
 
     def delete_floatingip(self, context, id):
@@ -706,7 +708,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
                              floatingip['floating_port_id'],
                              l3_port_check=False)
         if router_id:
-            l3_rpc_agent_api.L3AgentNotify.routers_updated(
+            self.l3_rpc_notifier.routers_updated(
                 context, [router_id],
                 'delete_floatingip')
 
@@ -777,7 +779,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
                 raise Exception(_('Multiple floating IPs found for port %s')
                                 % port_id)
         if router_id:
-            l3_rpc_agent_api.L3AgentNotify.routers_updated(
+            self.l3_rpc_notifier.routers_updated(
                 context, [router_id])
 
     def _network_is_external(self, context, net_id):
diff --git a/neutron/db/migration/alembic_migrations/versions/66a59a7f516_nec_openflow_router.py b/neutron/db/migration/alembic_migrations/versions/66a59a7f516_nec_openflow_router.py
new file mode 100644 (file)
index 0000000..ffead11
--- /dev/null
@@ -0,0 +1,68 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 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.
+#
+
+"""NEC OpenFlow Router
+
+Revision ID: 66a59a7f516
+Revises: 32a65f71af51
+Create Date: 2013-09-03 22:16:31.446031
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '66a59a7f516'
+down_revision = '32a65f71af51'
+
+# Change to ['*'] if this migration applies to all plugins
+
+migration_for_plugins = [
+    'neutron.plugins.nec.nec_plugin.NECPluginV2'
+]
+
+from alembic import op
+import sqlalchemy as sa
+
+from neutron.db import migration
+
+
+def upgrade(active_plugins=None, options=None):
+    if not migration.should_run(active_plugins, migration_for_plugins):
+        return
+
+    op.create_table(
+        'ofcroutermappings',
+        sa.Column('ofc_id', sa.String(length=255), nullable=False),
+        sa.Column('quantum_id', sa.String(length=36), nullable=False),
+        sa.PrimaryKeyConstraint('quantum_id'),
+        sa.UniqueConstraint('ofc_id'),
+    )
+    op.create_table(
+        'routerproviders',
+        sa.Column('provider', sa.String(length=255), nullable=True),
+        sa.Column('router_id', sa.String(length=36), nullable=False),
+        sa.ForeignKeyConstraint(['router_id'], ['routers.id'],
+                                ondelete='CASCADE'),
+        sa.PrimaryKeyConstraint('router_id'),
+    )
+
+
+def downgrade(active_plugins=None, options=None):
+    if not migration.should_run(active_plugins, migration_for_plugins):
+        return
+
+    op.drop_table('routerproviders')
+    op.drop_table('ofcroutermappings')
index 669887f5913bc1f9dbfcb8b9c6be8026c022ee9a..bcd4cc8d28608678d5437a3982334e8bdbe84aec 100644 (file)
@@ -19,6 +19,7 @@ from oslo.config import cfg
 
 from neutron.agent.common import config
 from neutron.openstack.common import rpc  # noqa
+from neutron.plugins.nec.common import constants as nconst
 
 
 ovs_opts = [
@@ -49,10 +50,20 @@ ofc_opts = [
                help=_("Certificate file")),
 ]
 
+provider_opts = [
+    cfg.StrOpt('default_router_provider',
+               default=nconst.DEFAULT_ROUTER_PROVIDER,
+               help=_('Default router provider to use.')),
+    cfg.ListOpt('router_providers',
+                default=nconst.DEFAULT_ROUTER_PROVIDERS,
+                help=_('List of enabled router providers.'))
+]
+
 
 cfg.CONF.register_opts(ovs_opts, "OVS")
 cfg.CONF.register_opts(agent_opts, "AGENT")
 cfg.CONF.register_opts(ofc_opts, "OFC")
+cfg.CONF.register_opts(provider_opts, "PROVIDER")
 config.register_agent_state_opts_helper(cfg.CONF)
 config.register_root_helper(cfg.CONF)
 
@@ -61,3 +72,4 @@ CONF = cfg.CONF
 OVS = cfg.CONF.OVS
 AGENT = cfg.CONF.AGENT
 OFC = cfg.CONF.OFC
+PROVIDER = cfg.CONF.PROVIDER
diff --git a/neutron/plugins/nec/common/constants.py b/neutron/plugins/nec/common/constants.py
new file mode 100644 (file)
index 0000000..b1bc7e5
--- /dev/null
@@ -0,0 +1,24 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 NEC Corporation.  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.
+
+ROUTER_PROVIDER_L3AGENT = 'l3-agent'
+ROUTER_PROVIDER_OPENFLOW = 'openflow'
+
+DEFAULT_ROUTER_PROVIDERS = [ROUTER_PROVIDER_L3AGENT, ROUTER_PROVIDER_OPENFLOW]
+DEFAULT_ROUTER_PROVIDER = ROUTER_PROVIDER_L3AGENT
+
+ROUTER_STATUS_ACTIVE = 'ACTIVE'
+ROUTER_STATUS_ERROR = 'ERROR'
index 74e000b5c56f083dc01dc22cc32ee2ecd1ce4a5d..d6b2b3b5003a63d99d16c2434ed167b74b4c3a1e 100644 (file)
@@ -21,6 +21,12 @@ from neutron.common import exceptions as qexc
 class OFCException(qexc.NeutronException):
     message = _("An OFC exception has occurred: %(reason)s")
 
+    def __init__(self, **kwargs):
+        super(OFCException, self).__init__(**kwargs)
+        self.status = kwargs.get('status')
+        self.err_msg = kwargs.get('err_msg')
+        self.err_code = kwargs.get('err_code')
+
 
 class NECDBException(qexc.NeutronException):
     message = _("An exception occurred in NECPluginV2 DB: %(reason)s")
@@ -44,3 +50,22 @@ class ProfilePortInfoInvalidDataPathId(qexc.InvalidInput):
 class ProfilePortInfoInvalidPortNo(qexc.InvalidInput):
     message = _('Invalid input for operation: '
                 'portinfo:port_no should be [0:65535]')
+
+
+class RouterExternalGatewayNotSupported(qexc.BadRequest):
+    message = _("Router (provider=%(provider)s) does not support "
+                "an external network")
+
+
+class ProviderNotFound(qexc.NotFound):
+    message = _("Provider %(provider)s could not be found")
+
+
+class RouterOverLimit(qexc.Conflict):
+    message = _("Cannot create more routers with provider=%(provider)s")
+
+
+class RouterProviderMismatch(qexc.Conflict):
+    message = _("Provider of Router %(router_id)s is %(provider)s. "
+                "This operation is supported only for router provider "
+                "%(expected_provider)s.")
index 44ba4dfd92b59eaed1fb798d17f704c543b1a78b..6df8b4f030419c290e1e9204a49b40705ffa2c73 100644 (file)
@@ -46,12 +46,26 @@ class OFCClient(object):
         self.cert_file = cert_file
         self.connection = None
 
-    def get_connection_type(self):
-        """Returns the proper connection type."""
+    def get_connection(self):
+        """Returns the proper connection."""
         if self.use_ssl:
-            return httplib.HTTPSConnection
+            connection_type = httplib.HTTPSConnection
         else:
-            return httplib.HTTPConnection
+            connection_type = httplib.HTTPConnection
+
+        # Open connection and send request, handling SSL certs
+        certs = {'key_file': self.key_file, 'cert_file': self.cert_file}
+        certs = dict((x, certs[x]) for x in certs if certs[x] is not None)
+        if self.use_ssl and len(certs):
+            conn = connection_type(self.host, self.port, **certs)
+        else:
+            conn = connection_type(self.host, self.port)
+        return conn
+
+    def _format_error_message(self, status, detail):
+        detail = ' ' + detail if detail else ''
+        return (_("Operation on OFC failed: %(status)s%(msg)s") %
+                {'status': status, 'msg': detail})
 
     def do_request(self, method, action, body=None):
         LOG.debug(_("Client request: %(host)s:%(port)s "
@@ -61,32 +75,40 @@ class OFCClient(object):
         if type(body) is dict:
             body = json.dumps(body)
         try:
-            connection_type = self.get_connection_type()
+            conn = self.get_connection()
             headers = {"Content-Type": "application/json"}
-            # Open connection and send request, handling SSL certs
-            certs = {'key_file': self.key_file, 'cert_file': self.cert_file}
-            certs = dict((x, certs[x]) for x in certs if certs[x] is not None)
-            if self.use_ssl and len(certs):
-                conn = connection_type(self.host, self.port, **certs)
-            else:
-                conn = connection_type(self.host, self.port)
             conn.request(method, action, body, headers)
             res = conn.getresponse()
             data = res.read()
             LOG.debug(_("OFC returns [%(status)s:%(data)s]"),
                       {'status': res.status,
                        'data': data})
+
+            # Try to decode JSON data if possible.
+            try:
+                data = json.loads(data)
+            except (ValueError, TypeError):
+                pass
+
             if res.status in (httplib.OK,
                               httplib.CREATED,
                               httplib.ACCEPTED,
                               httplib.NO_CONTENT):
-                if data and len(data) > 1:
-                    return json.loads(data)
+                return data
             else:
-                reason = _("An operation on OFC is failed.")
-                raise nexc.OFCException(reason=reason)
+                LOG.warning(_("Operation on OFC failed: "
+                              "status=%(status), detail=%(detail)"),
+                            {'status': res.status, 'detail': data})
+                params = {'reason': _("Operation on OFC failed"),
+                          'status': res.status}
+                if isinstance(data, dict):
+                    params['err_code'] = data.get('err_code')
+                    params['err_msg'] = data.get('err_msg')
+                else:
+                    params['err_msg'] = data
+                raise nexc.OFCException(**params)
         except (socket.error, IOError) as e:
-            reason = _("Failed to connect OFC : %s") % str(e)
+            reason = _("Failed to connect OFC : %s") % e
             LOG.error(reason)
             raise nexc.OFCException(reason=reason)
 
index 333065122d7614424c60f96411273a9b28e06053..e606861ff2247cdb9298fb66b587cd6c247c4c19 100644 (file)
@@ -36,6 +36,7 @@ OFP_VLAN_NONE = 0xffff
 resource_map = {'ofc_tenant': nmodels.OFCTenantMapping,
                 'ofc_network': nmodels.OFCNetworkMapping,
                 'ofc_port': nmodels.OFCPortMapping,
+                'ofc_router': nmodels.OFCRouterMapping,
                 'ofc_packet_filter': nmodels.OFCFilterMapping}
 
 old_resource_map = {'ofc_tenant': nmodels.OFCTenant,
@@ -48,7 +49,9 @@ old_resource_map = {'ofc_tenant': nmodels.OFCTenant,
 
 def _get_resource_model(resource, old_style):
     if old_style:
-        return old_resource_map[resource]
+        # NOTE: Some new resources are not defined in old_resource_map.
+        # In such case None is returned.
+        return old_resource_map.get(resource)
     else:
         return resource_map[resource]
 
@@ -62,8 +65,10 @@ def clear_db(base=model_base.BASEV2):
 
 
 def get_ofc_item(session, resource, neutron_id, old_style=False):
+    model = _get_resource_model(resource, old_style)
+    if not model:
+        return None
     try:
-        model = _get_resource_model(resource, old_style)
         return session.query(model).filter_by(quantum_id=neutron_id).one()
     except sa.orm.exc.NoResultFound:
         return None
index 7d9cf10691e5b4548e9e7fca7332759d3a5ac0a5..bf2dfea6d4a386cbad2ee6950c65ccb378e6a600 100644 (file)
@@ -47,6 +47,10 @@ class OFCPortMapping(model_base.BASEV2, NeutronId, OFCId):
     """Represents a Port on OpenFlow Network/Controller."""
 
 
+class OFCRouterMapping(model_base.BASEV2, NeutronId, OFCId):
+    """Represents a router on OpenFlow Network/Controller."""
+
+
 class OFCFilterMapping(model_base.BASEV2, NeutronId, OFCId):
     """Represents a Filter on OpenFlow Network/Controller."""
 
diff --git a/neutron/plugins/nec/db/router.py b/neutron/plugins/nec/db/router.py
new file mode 100644 (file)
index 0000000..9659cd7
--- /dev/null
@@ -0,0 +1,92 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation.  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.
+
+import sqlalchemy as sa
+from sqlalchemy import orm
+from sqlalchemy.orm import exc as sa_exc
+
+from neutron.db import l3_db
+from neutron.db import models_v2
+from neutron.openstack.common import log as logging
+
+LOG = logging.getLogger(__name__)
+
+
+class RouterProvider(models_v2.model_base.BASEV2):
+    """Represents a binding of router_id to provider."""
+    provider = sa.Column(sa.String(255))
+    router_id = sa.Column(sa.String(36),
+                          sa.ForeignKey('routers.id', ondelete="CASCADE"),
+                          primary_key=True)
+
+    router = orm.relationship(l3_db.Router, uselist=False,
+                              backref=orm.backref('provider', uselist=False,
+                                                  lazy='joined',
+                                                  cascade='delete'))
+
+
+def _get_router_providers_query(query, provider=None, router_ids=None):
+    if provider:
+        query = query.filter_by(provider=provider)
+    if router_ids:
+        column = RouterProvider.router_id
+        query = query.filter(column.in_(router_ids))
+    return query
+
+
+def get_router_providers(session, provider=None, router_ids=None):
+    """Retrieve a list of a pair of router ID and its provider."""
+    query = session.query(RouterProvider)
+    query = _get_router_providers_query(query, provider, router_ids)
+    return [{'provider': router.provider, 'router_id': router.router_id}
+            for router in query]
+
+
+def get_routers_by_provider(session, provider, router_ids=None):
+    """Retrieve a list of router IDs with the given provider."""
+    query = session.query(RouterProvider.router_id)
+    query = _get_router_providers_query(query, provider, router_ids)
+    return [router[0] for router in query]
+
+
+def get_router_count_by_provider(session, provider, tenant_id=None):
+    """Return the number of routers with the given provider."""
+    query = session.query(RouterProvider).filter_by(provider=provider)
+    if tenant_id:
+        query = (query.join('router').
+                 filter(l3_db.Router.tenant_id == tenant_id))
+    return query.count()
+
+
+def get_provider_by_router(session, router_id):
+    """Retrieve a provider of the given router."""
+    try:
+        binding = (session.query(RouterProvider).
+                   filter_by(router_id=router_id).
+                   one())
+    except sa_exc.NoResultFound:
+        return None
+    return binding.provider
+
+
+def add_router_provider_binding(session, provider, router_id):
+    """Add a router provider association."""
+    LOG.debug(_("Add provider binding "
+                "(router=%(router_id)s, provider=%(provider)s)"),
+              {'router_id': router_id, 'provider': provider})
+    binding = RouterProvider(provider=provider, router_id=router_id)
+    session.add(binding)
+    return binding
index 178b34d34369ddc76ac865a022235d7b46154114..2bb05168557fd1a190962feacec1a0103c89dcb5 100644 (file)
@@ -28,7 +28,9 @@ DRIVER_LIST = {
     'trema_mac': DRIVER_PATH % "trema.TremaMACBaseDriver",
     'pfc': DRIVER_PATH % "pfc.PFCV4Driver",
     'pfc_v3': DRIVER_PATH % "pfc.PFCV3Driver",
-    'pfc_v4': DRIVER_PATH % "pfc.PFCV4Driver"}
+    'pfc_v4': DRIVER_PATH % "pfc.PFCV4Driver",
+    'pfc_v5': DRIVER_PATH % "pfc.PFCV5Driver",
+}
 
 
 def get_driver(driver_name):
index e60bb8907305a109d9d13b7cdfa51dc749aaf60c..e8d23677f5c46f035c4faea4b7d9caf759ba2238 100644 (file)
@@ -33,6 +33,8 @@ class PFCDriverBase(ofc_driver_base.OFCDriverBase):
     The class implements the API for PFC V4.0 or later.
     """
 
+    router_supported = False
+
     def __init__(self, conf_ofc):
         self.client = ofc_client.OFCClient(host=conf_ofc.host,
                                            port=conf_ofc.port,
@@ -73,6 +75,10 @@ class PFCDriverBase(ofc_driver_base.OFCDriverBase):
         """
         return self._generate_pfc_str(desc)[:127]
 
+    def _extract_ofc_network_id(self, ofc_network_id):
+        # ofc_network_id : /tenants/<tenant-id>/networks/<network-id>
+        return ofc_network_id.split('/')[4]
+
     def create_tenant(self, description, tenant_id=None):
         ofc_tenant_id = self._generate_pfc_id(tenant_id)
         body = {'id': ofc_tenant_id}
@@ -136,6 +142,66 @@ class PFCDriverBase(ofc_driver_base.OFCDriverBase):
         return '%(network)s/ports/%(port)s' % params
 
 
+class PFCRouterDriverMixin(object):
+
+    router_supported = True
+    router_nat_supported = False
+
+    def create_router(self, ofc_tenant_id, router_id, description):
+        path = '%s/routers' % ofc_tenant_id
+        res = self.client.post(path, body=None)
+        ofc_router_id = res['id']
+        return path + '/' + ofc_router_id
+
+    def delete_router(self, ofc_router_id):
+        return self.client.delete(ofc_router_id)
+
+    def add_router_interface(self, ofc_router_id, ofc_net_id,
+                             ip_address=None, mac_address=None):
+        # ip_address : <ip_address>/<netmask> (e.g., 10.0.0.0/24)
+        path = '%s/interfaces' % ofc_router_id
+        body = {'net_id': self._extract_ofc_network_id(ofc_net_id)}
+        if ip_address:
+            body['ip_address'] = ip_address
+        if mac_address:
+            body['mac_address'] = mac_address
+        res = self.client.post(path, body=body)
+        return path + '/' + res['id']
+
+    def update_router_interface(self, ofc_router_inf_id,
+                                ip_address=None, mac_address=None):
+        # ip_address : <ip_address>/<netmask> (e.g., 10.0.0.0/24)
+        if not ip_address and not mac_address:
+            return
+        body = {}
+        if ip_address:
+            body['ip_address'] = ip_address
+        if mac_address:
+            body['mac_address'] = mac_address
+        return self.client.put(ofc_router_inf_id, body=body)
+
+    def delete_router_interface(self, ofc_router_inf_id):
+        return self.client.delete(ofc_router_inf_id)
+
+    def list_router_routes(self, ofc_router_id):
+        path = '%s/routes' % ofc_router_id
+        ret = self.client.get(path)
+        # Prepend ofc_router_id to route_id
+        for r in ret['routes']:
+            r['id'] = ofc_router_id + '/routes/' + r['id']
+        return ret['routes']
+
+    def add_router_route(self, ofc_router_id, destination, nexthop):
+        path = '%s/routes' % ofc_router_id
+        body = {'destination': destination,
+                'nexthop': nexthop}
+        ret = self.client.post(path, body=body)
+        return path + '/' + ret['id']
+
+    def delete_router_route(self, ofc_router_route_id):
+        return self.client.delete(ofc_router_route_id)
+
+
 class PFCV3Driver(PFCDriverBase):
 
     def create_tenant(self, description, tenant_id):
@@ -148,3 +214,7 @@ class PFCV3Driver(PFCDriverBase):
 
 class PFCV4Driver(PFCDriverBase):
     pass
+
+
+class PFCV5Driver(PFCRouterDriverMixin, PFCDriverBase):
+    pass
index e4081e1d2a7eb4ca9f8073ef9f9be108ae6ea319..5a2d29524b20796c9bc63445eccbcd56cb327501 100644 (file)
@@ -27,6 +27,8 @@ class TremaDriverBase(ofc_driver_base.OFCDriverBase):
     networks_path = "/networks"
     network_path = "/networks/%s"
 
+    router_supported = False
+
     def __init__(self, conf_ofc):
         # Trema sliceable REST API does not support HTTPS
         self.client = ofc_client.OFCClient(host=conf_ofc.host,
@@ -74,7 +76,7 @@ class TremaDriverBase(ofc_driver_base.OFCDriverBase):
         return self.network_path % ofc_network_id
 
 
-class TremaFilterDriver(object):
+class TremaFilterDriverMixin(object):
     """Trema (Sliceable Switch) PacketFilter Driver Mixin."""
     filters_path = "/filters"
     filter_path = "/filters/%s"
@@ -173,7 +175,7 @@ class TremaFilterDriver(object):
         return self.filter_path % ofc_filter_id
 
 
-class TremaPortBaseDriver(TremaDriverBase, TremaFilterDriver):
+class TremaPortBaseDriver(TremaDriverBase, TremaFilterDriverMixin):
     """Trema (Sliceable Switch) Driver for port base binding.
 
     TremaPortBaseDriver uses port base binding.
@@ -211,7 +213,7 @@ class TremaPortBaseDriver(TremaDriverBase, TremaFilterDriver):
                                  'port': ofc_port_id}
 
 
-class TremaPortMACBaseDriver(TremaDriverBase, TremaFilterDriver):
+class TremaPortMACBaseDriver(TremaDriverBase, TremaFilterDriverMixin):
     """Trema (Sliceable Switch) Driver for port-mac base binding.
 
     TremaPortBaseDriver uses port-mac base binding.
diff --git a/neutron/plugins/nec/extensions/router_provider.py b/neutron/plugins/nec/extensions/router_provider.py
new file mode 100644 (file)
index 0000000..d893a4c
--- /dev/null
@@ -0,0 +1,61 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 NEC Corporation.  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.
+
+from neutron.api import extensions
+from neutron.api.v2 import attributes
+from neutron.openstack.common import log as logging
+
+
+LOG = logging.getLogger(__name__)
+
+ROUTER_PROVIDER = 'provider'
+
+ROUTER_PROVIDER_ATTRIBUTE = {
+    'routers': {ROUTER_PROVIDER:
+                {'allow_post': True,
+                 'allow_put': False,
+                 'is_visible': True,
+                 'default': attributes.ATTR_NOT_SPECIFIED}
+                }
+}
+
+
+class Router_provider(extensions.ExtensionDescriptor):
+    @classmethod
+    def get_name(cls):
+        return "Router Provider"
+
+    @classmethod
+    def get_alias(cls):
+        return "router_provider"
+
+    @classmethod
+    def get_description(cls):
+        return "Router Provider Support"
+
+    @classmethod
+    def get_namespace(cls):
+        return "http://docs.openstack.org/ext/router_provider/api/v1.0"
+
+    @classmethod
+    def get_updated(cls):
+        return "2013-08-20T10:00:00-00:00"
+
+    def get_extended_resources(self, version):
+        if version == "2.0":
+            return ROUTER_PROVIDER_ATTRIBUTE
+        else:
+            return {}
index df3ee1ef628e71a6e2ee0eef4894089258c622a5..26088148d5b40995e9ca683d85985674972f360a 100644 (file)
@@ -18,7 +18,6 @@
 
 from neutron.agent import securitygroups_rpc as sg_rpc
 from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
-from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
 from neutron.api.v2 import attributes as attrs
 from neutron.common import constants as const
 from neutron.common import exceptions as q_exc
@@ -28,8 +27,6 @@ from neutron.db import agents_db
 from neutron.db import agentschedulers_db
 from neutron.db import db_base_plugin_v2
 from neutron.db import dhcp_rpc_base
-from neutron.db import extraroute_db
-from neutron.db import l3_gwmode_db
 from neutron.db import l3_rpc_base
 from neutron.db import portbindings_base
 from neutron.db import portbindings_db
@@ -44,6 +41,8 @@ from neutron.openstack.common import uuidutils
 from neutron.plugins.nec.common import config
 from neutron.plugins.nec.common import exceptions as nexc
 from neutron.plugins.nec.db import api as ndb
+from neutron.plugins.nec.db import router as rdb
+from neutron.plugins.nec import nec_router
 from neutron.plugins.nec import ofc_manager
 from neutron.plugins.nec import packet_filter
 
@@ -51,11 +50,10 @@ LOG = logging.getLogger(__name__)
 
 
 class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
-                  extraroute_db.ExtraRoute_db_mixin,
-                  l3_gwmode_db.L3_NAT_db_mixin,
+                  nec_router.RouterMixin,
                   sg_db_rpc.SecurityGroupServerRpcMixin,
-                  agentschedulers_db.L3AgentSchedulerDbMixin,
                   agentschedulers_db.DhcpAgentSchedulerDbMixin,
+                  nec_router.L3AgentSchedulerDbMixin,
                   packet_filter.PacketFilterMixin,
                   portbindings_db.PortBindingMixin):
     """NECPluginV2 controls an OpenFlow Controller.
@@ -70,12 +68,18 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
     The port binding extension enables an external application relay
     information to and from the plugin.
     """
-    _supported_extension_aliases = ["router", "ext-gw-mode", "quotas",
-                                    "binding", "security-group",
-                                    "extraroute", "agent",
-                                    "l3_agent_scheduler",
+    _supported_extension_aliases = ["agent",
+                                    "binding",
                                     "dhcp_agent_scheduler",
-                                    "packet-filter"]
+                                    "ext-gw-mode",
+                                    "extraroute",
+                                    "l3_agent_scheduler",
+                                    "packet-filter",
+                                    "quotas",
+                                    "router",
+                                    "router_provider",
+                                    "security-group",
+                                    ]
 
     @property
     def supported_extension_aliases(self):
@@ -99,6 +103,7 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
                                      'neutron/plugins/nec/extensions')
 
         self.setup_rpc()
+        self.l3_rpc_notifier = nec_router.L3AgentNotifyAPI()
 
         self.network_scheduler = importutils.import_object(
             config.CONF.network_scheduler_driver
@@ -107,6 +112,20 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
             config.CONF.router_scheduler_driver
         )
 
+        nec_router.load_driver(self, self.ofc)
+        self.port_handlers = {
+            'create': {
+                const.DEVICE_OWNER_ROUTER_GW: self.create_router_port,
+                const.DEVICE_OWNER_ROUTER_INTF: self.create_router_port,
+                'default': self.activate_port_if_ready,
+            },
+            'delete': {
+                const.DEVICE_OWNER_ROUTER_GW: self.delete_router_port,
+                const.DEVICE_OWNER_ROUTER_INTF: self.delete_router_port,
+                'default': self.deactivate_port,
+            }
+        }
+
     def setup_rpc(self):
         self.topic = topics.PLUGIN
         self.conn = rpc.create_connection(new=True)
@@ -115,13 +134,14 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
             dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
         )
         self.agent_notifiers[const.AGENT_TYPE_L3] = (
-            l3_rpc_agent_api.L3AgentNotify
+            nec_router.L3AgentNotifyAPI()
         )
 
         # NOTE: callback_sg is referred to from the sg unit test.
         self.callback_sg = SecurityGroupServerRpcCallback()
         callbacks = [NECPluginV2RPCCallbacks(self),
-                     DhcpRpcCallback(), L3RpcCallback(),
+                     DhcpRpcCallback(),
+                     L3RpcCallback(),
                      self.callback_sg,
                      agents_db.AgentExtRpcCallback()]
         self.dispatcher = q_rpc.PluginRpcDispatcher(callbacks)
@@ -131,10 +151,35 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
 
     def _update_resource_status(self, context, resource, id, status):
         """Update status of specified resource."""
-        request = {}
-        request[resource] = dict(status=status)
-        obj_updater = getattr(super(NECPluginV2, self), "update_%s" % resource)
-        obj_updater(context, id, request)
+        request = {'status': status}
+        obj_getter = getattr(self, '_get_%s' % resource)
+        with context.session.begin(subtransactions=True):
+            obj_db = obj_getter(context, id)
+            obj_db.update(request)
+
+    def _check_ofc_tenant_in_use(self, context, tenant_id):
+        """Check if the specified tenant is used."""
+        # All networks are created on OFC
+        filters = {'tenant_id': [tenant_id]}
+        if self.get_networks_count(context, filters=filters):
+            return True
+        if rdb.get_router_count_by_provider(context.session,
+                                            nec_router.PROVIDER_OPENFLOW,
+                                            tenant_id):
+            return True
+        return False
+
+    def _cleanup_ofc_tenant(self, context, tenant_id):
+        if not self._check_ofc_tenant_in_use(context, tenant_id):
+            try:
+                if self.ofc.exists_ofc_tenant(context, tenant_id):
+                    self.ofc.delete_ofc_tenant(context, tenant_id)
+                else:
+                    LOG.debug(_('_cleanup_ofc_tenant: No OFC tenant for %s'),
+                              tenant_id)
+            except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
+                reason = _("delete_ofc_tenant() failed due to %s") % exc
+                LOG.warn(reason)
 
     def activate_port_if_ready(self, context, port, network=None):
         """Activate port by creating port on OFC if ready.
@@ -315,7 +360,6 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
                 self.delete_packet_filter(context, pf['id'])
 
         try:
-            # 'net' parameter is required to lookup old OFC mapping
             self.ofc.delete_ofc_network(context, id, net)
         except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
             reason = _("delete_network() failed due to %s") % exc
@@ -326,15 +370,7 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
 
         super(NECPluginV2, self).delete_network(context, id)
 
-        # delete unnessary ofc_tenant
-        filters = dict(tenant_id=[tenant_id])
-        nets = super(NECPluginV2, self).get_networks(context, filters=filters)
-        if not nets:
-            try:
-                self.ofc.delete_ofc_tenant(context, tenant_id)
-            except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
-                reason = _("delete_ofc_tenant() failed due to %s") % exc
-                LOG.warn(reason)
+        self._cleanup_ofc_tenant(context, tenant_id)
 
     def _get_base_binding_dict(self):
         binding = {
@@ -449,6 +485,14 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
             context, port_data, port)
         return portinfo_changed
 
+    def _get_port_handler(self, operation, device_owner):
+        handlers = self.port_handlers[operation]
+        handler = handlers.get(device_owner)
+        if handler:
+            return handler
+        else:
+            return handlers['default']
+
     def create_port(self, context, port):
         """Create a new port entry on DB, then try to activate it."""
         LOG.debug(_("NECPluginV2.create_port() called, port=%s ."), port)
@@ -465,7 +509,8 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
                 context, port, sgids)
         self.notify_security_groups_member_updated(context, port)
 
-        return self.activate_port_if_ready(context, port)
+        handler = self._get_port_handler('create', port['device_owner'])
+        return handler(context, port)
 
     def _update_ofc_port_if_required(self, context, old_port, new_port,
                                      portinfo_changed):
@@ -539,7 +584,9 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
         # Thus we need to call self.get_port() instead of super().get_port()
         port = self.get_port(context, id)
 
-        port = self.deactivate_port(context, port)
+        handler = self._get_port_handler('delete', port['device_owner'])
+        port = handler(context, port)
+        # port = self.deactivate_port(context, port)
         if port['status'] == const.PORT_STATUS_ERROR:
             reason = _("Failed to delete port=%s from OFC.") % id
             raise nexc.OFCException(reason=reason)
diff --git a/neutron/plugins/nec/nec_router.py b/neutron/plugins/nec/nec_router.py
new file mode 100644 (file)
index 0000000..be1f0e3
--- /dev/null
@@ -0,0 +1,356 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 NEC Corporation.  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: Akihiro Motoki
+
+from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
+from neutron.api.v2 import attributes as attr
+from neutron.common import exceptions as q_exc
+from neutron.db import agentschedulers_db
+from neutron.db import db_base_plugin_v2
+from neutron.db import extraroute_db
+from neutron.db import l3_db
+from neutron.db import l3_gwmode_db
+from neutron.db import models_v2
+from neutron.extensions import l3
+from neutron.openstack.common import importutils
+from neutron.openstack.common import log as logging
+from neutron.plugins.nec.common import config
+from neutron.plugins.nec.common import constants as nconst
+from neutron.plugins.nec.common import exceptions as nexc
+from neutron.plugins.nec.db import router as rdb
+from neutron.plugins.nec.extensions import router_provider as ext_provider
+
+LOG = logging.getLogger(__name__)
+
+PROVIDER_L3AGENT = nconst.ROUTER_PROVIDER_L3AGENT
+PROVIDER_OPENFLOW = nconst.ROUTER_PROVIDER_OPENFLOW
+
+ROUTER_DRIVER_PATH = 'neutron.plugins.nec.router_drivers.'
+ROUTER_DRIVER_MAP = {
+    PROVIDER_L3AGENT: ROUTER_DRIVER_PATH + 'RouterL3AgentDriver',
+    PROVIDER_OPENFLOW: ROUTER_DRIVER_PATH + 'RouterOpenFlowDriver'
+}
+
+ROUTER_DRIVERS = {}
+
+STATUS_ACTIVE = nconst.ROUTER_STATUS_ACTIVE
+STATUS_ERROR = nconst.ROUTER_STATUS_ERROR
+
+
+class RouterMixin(extraroute_db.ExtraRoute_db_mixin,
+                  l3_gwmode_db.L3_NAT_db_mixin):
+
+    def create_router(self, context, router):
+        """Create a new router entry on DB, and create it on OFC."""
+        LOG.debug(_("RouterMixin.create_router() called, "
+                    "router=%s ."), router)
+        tenant_id = self._get_tenant_id_for_create(context, router['router'])
+
+        provider = get_provider_with_default(
+            router['router'].get(ext_provider.ROUTER_PROVIDER))
+        driver = get_driver_by_provider(provider)
+
+        with context.session.begin(subtransactions=True):
+            new_router = super(RouterMixin, self).create_router(context,
+                                                                router)
+            new_router['gw_port'] = self._get_gw_port_detail(
+                context, driver, new_router['gw_port_id'])
+            rdb.add_router_provider_binding(context.session,
+                                            provider, str(new_router['id']))
+            self._extend_router_dict_provider(new_router, provider)
+
+        # create router on the network controller
+        try:
+            return driver.create_router(context, tenant_id, new_router)
+        except nexc.RouterOverLimit:
+            super(RouterMixin, self).delete_router(context, new_router['id'])
+            raise
+
+    def update_router(self, context, router_id, router):
+        LOG.debug(_("RouterMixin.update_router() called, "
+                    "id=%(id)s, router=%(router)s ."),
+                  {'id': router_id, 'router': router})
+
+        with context.session.begin(subtransactions=True):
+            old_rtr = super(RouterMixin, self).get_router(context, router_id)
+            provider = old_rtr[ext_provider.ROUTER_PROVIDER]
+            driver = get_driver_by_provider(provider)
+            old_rtr['gw_port'] = self._get_gw_port_detail(
+                context, driver, old_rtr['gw_port_id'])
+            new_rtr = super(RouterMixin, self).update_router(
+                context, router_id, router)
+            new_rtr['gw_port'] = self._get_gw_port_detail(
+                context, driver, new_rtr['gw_port_id'])
+        driver.update_router(context, router_id, old_rtr, new_rtr)
+        return new_rtr
+
+    def delete_router(self, context, router_id):
+        LOG.debug(_("RouterMixin.delete_router() called, id=%s."), router_id)
+
+        router = super(RouterMixin, self).get_router(context, router_id)
+        tenant_id = router['tenant_id']
+        # Since l3_db.delete_router() has no interaction with the plugin layer,
+        # we need to check if the router can be deleted first.
+        self._check_router_in_use(context, router_id)
+        driver = self._get_router_driver_by_id(context, router_id)
+        # If gw_port exists, remove it.
+        gw_port = self._get_gw_port(context, router_id)
+        if gw_port:
+            driver.delete_interface(context, router_id, gw_port)
+        driver.delete_router(context, router_id, router)
+
+        super(RouterMixin, self).delete_router(context, router_id)
+
+        self._cleanup_ofc_tenant(context, tenant_id)
+
+    def add_router_interface(self, context, router_id, interface_info):
+        LOG.debug(_("RouterMixin.add_router_interface() called, "
+                    "id=%(id)s, interface=%(interface)s."),
+                  {'id': router_id, 'interface': interface_info})
+        return super(RouterMixin, self).add_router_interface(
+            context, router_id, interface_info)
+
+    def remove_router_interface(self, context, router_id, interface_info):
+        LOG.debug(_("RouterMixin.remove_router_interface() called, "
+                    "id=%(id)s, interface=%(interface)s."),
+                  {'id': router_id, 'interface': interface_info})
+        return super(RouterMixin, self).remove_router_interface(
+            context, router_id, interface_info)
+
+    def create_router_port(self, context, port):
+        # This method is called from plugin.create_port()
+        router_id = port['device_id']
+        driver = self._get_router_driver_by_id(context, router_id)
+        port = driver.add_interface(context, router_id, port)
+        return port
+
+    def delete_router_port(self, context, port):
+        # This method is called from plugin.delete_port()
+        router_id = port['device_id']
+        driver = self._get_router_driver_by_id(context, router_id)
+        return driver.delete_interface(context, router_id, port)
+
+    def _get_gw_port_detail(self, context, driver, gw_port_id):
+        if not gw_port_id or not driver.need_gw_info:
+            return
+        ctx_elevated = context.elevated()
+        gw_port = self._get_port(ctx_elevated, gw_port_id)
+        # At this moment gw_port has been created, so it is guaranteed
+        # that fixed_ip is assigned for the gw_port.
+        ext_subnet_id = gw_port['fixed_ips'][0]['subnet_id']
+        ext_subnet = self._get_subnet(ctx_elevated, ext_subnet_id)
+        gw_info = {'network_id': gw_port['network_id'],
+                   'ip_address': gw_port['fixed_ips'][0]['ip_address'],
+                   'mac_address': gw_port['mac_address'],
+                   'cidr': ext_subnet['cidr'],
+                   'gateway_ip': ext_subnet['gateway_ip']}
+        return gw_info
+
+    def _get_gw_port(self, context, router_id):
+        device_filter = {'device_id': [router_id],
+                         'device_owner': [l3_db.DEVICE_OWNER_ROUTER_GW]}
+        ports = self.get_ports(context.elevated(), filters=device_filter)
+        if ports:
+            return ports[0]
+
+    def _check_router_in_use(self, context, router_id):
+        with context.session.begin(subtransactions=True):
+            # Ensure that the router is not used
+            router_filter = {'router_id': [router_id]}
+            fips = self.get_floatingips_count(context.elevated(),
+                                              filters=router_filter)
+            if fips:
+                raise l3.RouterInUse(router_id=router_id)
+
+            device_filter = {'device_id': [router_id],
+                             'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF]}
+            ports = self.get_ports_count(context.elevated(),
+                                         filters=device_filter)
+            if ports:
+                raise l3.RouterInUse(router_id=router_id)
+
+    def _get_router_for_floatingip(self, context, internal_port,
+                                   internal_subnet_id,
+                                   external_network_id):
+        """Get a router for a requested floating IP.
+
+        OpenFlow vrouter does not support NAT, so we need to exclude them
+        from candidate routers for floating IP association.
+        This method is called in l3_db.get_assoc_data().
+        """
+        subnet_db = self._get_subnet(context, internal_subnet_id)
+        if not subnet_db['gateway_ip']:
+            msg = (_('Cannot add floating IP to port on subnet %s '
+                     'which has no gateway_ip') % internal_subnet_id)
+            raise q_exc.BadRequest(resource='floatingip', msg=msg)
+
+        # find router interface ports on this network
+        router_intf_qry = context.session.query(models_v2.Port)
+        router_intf_ports = router_intf_qry.filter_by(
+            network_id=internal_port['network_id'],
+            device_owner=l3_db.DEVICE_OWNER_ROUTER_INTF)
+
+        for intf_p in router_intf_ports:
+            if intf_p['fixed_ips'][0]['subnet_id'] == internal_subnet_id:
+                router_id = intf_p['device_id']
+                router_gw_qry = context.session.query(models_v2.Port)
+                has_gw_port = router_gw_qry.filter_by(
+                    network_id=external_network_id,
+                    device_id=router_id,
+                    device_owner=l3_db.DEVICE_OWNER_ROUTER_GW).count()
+                driver = self._get_router_driver_by_id(context, router_id)
+                if (has_gw_port and driver.floating_ip_support()):
+                    return router_id
+
+        raise l3.ExternalGatewayForFloatingIPNotFound(
+            subnet_id=internal_subnet_id,
+            external_network_id=external_network_id,
+            port_id=internal_port['id'])
+
+    def _get_sync_routers(self, context, router_ids=None, active=None):
+        """Query routers and their gw ports for l3 agent.
+
+        The difference from the superclass in l3_db is that this method
+        only lists routers hosted on l3-agents.
+        """
+        router_list = super(RouterMixin, self)._get_sync_routers(
+            context, router_ids, active)
+        if router_list:
+            _router_ids = [r['id'] for r in router_list]
+            agent_routers = rdb.get_routers_by_provider(
+                context.session, 'l3-agent',
+                router_ids=_router_ids)
+            router_list = [r for r in router_list
+                           if r['id'] in agent_routers]
+        return router_list
+
+    def _get_router_driver_by_id(self, context, router_id):
+        provider = self._get_provider_by_router_id(context, router_id)
+        return get_driver_by_provider(provider)
+
+    def _get_provider_by_router_id(self, context, router_id):
+        return rdb.get_provider_by_router(context.session, router_id)
+
+    def _extend_router_dict_provider(self, router_res, provider):
+        router_res[ext_provider.ROUTER_PROVIDER] = provider
+
+    def extend_router_dict_provider(self, router_res, router_db):
+        # NOTE: router_db.provider is None just after creating a router,
+        # so we need to skip setting router_provider here.
+        if not router_db.provider:
+            return
+        self._extend_router_dict_provider(router_res,
+                                          router_db.provider['provider'])
+
+    db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+        l3.ROUTERS, [extend_router_dict_provider])
+
+
+class L3AgentSchedulerDbMixin(agentschedulers_db.L3AgentSchedulerDbMixin):
+
+    def auto_schedule_routers(self, context, host, router_ids):
+        router_ids = rdb.get_routers_by_provider(
+            context.session, nconst.ROUTER_PROVIDER_L3AGENT, router_ids)
+        # If no l3-agent hosted router, there is no need to schedule.
+        if not router_ids:
+            return
+        return super(L3AgentSchedulerDbMixin, self).auto_schedule_routers(
+            context, host, router_ids)
+
+    def schedule_router(self, context, router):
+        if (self._get_provider_by_router_id(context, router) ==
+            nconst.ROUTER_PROVIDER_L3AGENT):
+            return super(L3AgentSchedulerDbMixin, self).schedule_router(
+                context, router)
+
+    def add_router_to_l3_agent(self, context, id, router_id):
+        provider = self._get_provider_by_router_id(context, router_id)
+        if provider != nconst.ROUTER_PROVIDER_L3AGENT:
+            raise nexc.RouterProviderMismatch(
+                router_id=router_id, provider=provider,
+                expected_provider=nconst.ROUTER_PROVIDER_L3AGENT)
+        return super(L3AgentSchedulerDbMixin, self).add_router_to_l3_agent(
+            context, id, router_id)
+
+
+class L3AgentNotifyAPI(l3_rpc_agent_api.L3AgentNotifyAPI):
+
+    def _notification(self, context, method, router_ids, operation, data):
+        """Notify all the agents that are hosting the routers.
+
+        _notification() is called in L3 db plugin for all routers regardless
+        the routers are hosted on l3 agents or not. When the routers are
+        not hosted on l3 agents, there is no need to notify.
+        This method filters routers not hosted by l3 agents.
+        """
+        router_ids = rdb.get_routers_by_provider(
+            context.session, nconst.ROUTER_PROVIDER_L3AGENT, router_ids)
+        super(L3AgentNotifyAPI, self)._notification(
+            context, method, router_ids, operation, data)
+
+
+def load_driver(plugin, ofc_manager):
+
+    if (PROVIDER_OPENFLOW in ROUTER_DRIVER_MAP and
+        not ofc_manager.driver.router_supported):
+        LOG.warning(
+            _('OFC does not support router with provider=%(provider)s, '
+              'so removed it from supported provider '
+              '(new router driver map=%(driver_map)s)'),
+            {'provider': PROVIDER_OPENFLOW,
+             'driver_map': ROUTER_DRIVER_MAP})
+        del ROUTER_DRIVER_MAP[PROVIDER_OPENFLOW]
+
+    if config.PROVIDER.default_router_provider not in ROUTER_DRIVER_MAP:
+        LOG.error(_('default_router_provider %(default)s is supported! '
+                    'Please specify one of %(supported)s'),
+                  {'default': config.PROVIDER.default_router_provider,
+                   'supported': ROUTER_DRIVER_MAP.keys()})
+        raise SystemExit(1)
+
+    enabled_providers = (set(config.PROVIDER.router_providers +
+                             [config.PROVIDER.default_router_provider]) &
+                         set(ROUTER_DRIVER_MAP.keys()))
+
+    for driver in enabled_providers:
+        driver_klass = importutils.import_class(ROUTER_DRIVER_MAP[driver])
+        ROUTER_DRIVERS[driver] = driver_klass(plugin, ofc_manager)
+
+    LOG.info(_('Enabled router drivers: %s'), ROUTER_DRIVERS.keys())
+
+    if not ROUTER_DRIVERS:
+        LOG.error(_('No router provider is enabled. neutron-server terminated!'
+                    ' (supported=%(supported)s, configured=%(config)s)'),
+                  {'supported': ROUTER_DRIVER_MAP.keys(),
+                   'config': config.PROVIDER.router_providers})
+        raise SystemExit(1)
+
+
+def get_provider_with_default(provider):
+    if not attr.is_attr_set(provider):
+        provider = config.PROVIDER.default_router_provider
+    elif provider not in ROUTER_DRIVERS:
+        raise nexc.ProviderNotFound(provider=provider)
+    return provider
+
+
+def get_driver_by_provider(provider):
+    if provider is None:
+        provider = config.PROVIDER.default_router_provider
+    elif provider not in ROUTER_DRIVERS:
+        raise nexc.ProviderNotFound(provider=provider)
+    return ROUTER_DRIVERS[provider]
index fd80a00f7909a2596804c947c02a4d5c301f97f7..ff12e72568f4987676cb2c4554b371256a20bbfe 100644 (file)
 # @author: Ryota MIBU
 # @author: Akihiro MOTOKI
 
+import netaddr
+
+from neutron.common import utils
+from neutron.openstack.common import log as logging
 from neutron.plugins.nec.common import config
 from neutron.plugins.nec.common import exceptions as nexc
 from neutron.plugins.nec.db import api as ndb
 from neutron.plugins.nec import drivers
 
 
+LOG = logging.getLogger(__name__)
+
+
 class OFCManager(object):
     """This class manages an OpenFlow Controller and map resources.
 
@@ -50,6 +57,10 @@ class OFCManager(object):
     def _del_ofc_item(self, context, resource, neutron_id):
         ndb.del_ofc_item_lookup_both(context.session, resource, neutron_id)
 
+    def ensure_ofc_tenant(self, context, tenant_id):
+        if not self.exists_ofc_tenant(context, tenant_id):
+            self.create_ofc_tenant(context, tenant_id)
+
     def create_ofc_tenant(self, context, tenant_id):
         desc = "ID=%s at OpenStack." % tenant_id
         ofc_tenant_id = self.driver.create_tenant(desc, tenant_id)
@@ -134,3 +145,60 @@ class OFCManager(object):
 
         self.driver.delete_filter(ofc_pf_id)
         self._del_ofc_item(context, "ofc_packet_filter", filter_id)
+
+    def create_ofc_router(self, context, tenant_id, router_id, name=None):
+        ofc_tenant_id = self._get_ofc_id(context, "ofc_tenant", tenant_id)
+        ofc_tenant_id = self.driver.convert_ofc_tenant_id(
+            context, ofc_tenant_id)
+
+        desc = "ID=%s Name=%s at Neutron." % (router_id, name)
+        ofc_router_id = self.driver.create_router(ofc_tenant_id, router_id,
+                                                  desc)
+        self._add_ofc_item(context, "ofc_router", router_id, ofc_router_id)
+
+    def exists_ofc_router(self, context, router_id):
+        return self._exists_ofc_item(context, "ofc_router", router_id)
+
+    def delete_ofc_router(self, context, router_id, router):
+        ofc_router_id = self._get_ofc_id(context, "ofc_router", router_id)
+        self.driver.delete_router(ofc_router_id)
+        self._del_ofc_item(context, "ofc_router", router_id)
+
+    def add_ofc_router_interface(self, context, router_id, port_id, port):
+        # port must have the following fields:
+        #   network_id, cidr, ip_address, mac_address
+        ofc_router_id = self._get_ofc_id(context, "ofc_router", router_id)
+        ofc_net_id = self._get_ofc_id(context, "ofc_network",
+                                      port['network_id'])
+        ip_address = '%s/%s' % (port['ip_address'],
+                                netaddr.IPNetwork(port['cidr']).prefixlen)
+        mac_address = port['mac_address']
+        ofc_inf_id = self.driver.add_router_interface(
+            ofc_router_id, ofc_net_id, ip_address, mac_address)
+        # Use port mapping table to maintain an interface of OFC router
+        self._add_ofc_item(context, "ofc_port", port_id, ofc_inf_id)
+
+    def delete_ofc_router_interface(self, context, router_id, port_id):
+        # Use port mapping table to maintain an interface of OFC router
+        ofc_inf_id = self._get_ofc_id(context, "ofc_port", port_id)
+        self.driver.delete_router_interface(ofc_inf_id)
+        self._del_ofc_item(context, "ofc_port", port_id)
+
+    def update_ofc_router_route(self, context, router_id, new_routes):
+        ofc_router_id = self._get_ofc_id(context, "ofc_router", router_id)
+        ofc_routes = self.driver.list_router_routes(ofc_router_id)
+        route_dict = {}
+        cur_routes = []
+        for r in ofc_routes:
+            key = ','.join((r['destination'], r['nexthop']))
+            route_dict[key] = r['id']
+            del r['id']
+            cur_routes.append(r)
+        added, removed = utils.diff_list_of_dict(cur_routes, new_routes)
+        for r in removed:
+            key = ','.join((r['destination'], r['nexthop']))
+            route_id = route_dict[key]
+            self.driver.delete_router_route(route_id)
+        for r in added:
+            self.driver.add_router_route(ofc_router_id, r['destination'],
+                                         r['nexthop'])
diff --git a/neutron/plugins/nec/router_drivers.py b/neutron/plugins/nec/router_drivers.py
new file mode 100644 (file)
index 0000000..b16c4d8
--- /dev/null
@@ -0,0 +1,221 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 NEC Corporation.  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: Akihiro Motoki
+
+import abc
+import httplib
+
+from neutron.common import log as call_log
+from neutron.common import utils
+from neutron.openstack.common import excutils
+from neutron.openstack.common import log as logging
+from neutron.plugins.nec.common import constants as nconst
+from neutron.plugins.nec.common import exceptions as nexc
+
+LOG = logging.getLogger(__name__)
+
+PROVIDER_OPENFLOW = nconst.ROUTER_PROVIDER_OPENFLOW
+
+
+class RouterDriverBase(object):
+
+    __metaclass__ = abc.ABCMeta
+
+    def __init__(self, plugin, ofc_manager):
+        self.plugin = plugin
+        self.ofc = ofc_manager
+
+    def floating_ip_support(self):
+        return True
+
+    @abc.abstractmethod
+    def create_router(self, context, tenant_id, router):
+        pass
+
+    @abc.abstractmethod
+    def update_router(self, context, router_id, old_router, new_router):
+        pass
+
+    @abc.abstractmethod
+    def delete_router(self, context, router_id, router):
+        pass
+
+    @abc.abstractmethod
+    def add_interface(self, context, router_id, port):
+        pass
+
+    @abc.abstractmethod
+    def delete_interface(self, context, router_id, port):
+        pass
+
+
+class RouterL3AgentDriver(RouterDriverBase):
+
+    need_gw_info = False
+
+    @call_log.log
+    def create_router(self, context, tenant_id, router):
+        return router
+
+    @call_log.log
+    def update_router(self, context, router_id, old_router, new_router):
+        return new_router
+
+    @call_log.log
+    def delete_router(self, context, router_id, router):
+        pass
+
+    @call_log.log
+    def add_interface(self, context, router_id, port):
+        return self.plugin.activate_port_if_ready(context, port)
+
+    @call_log.log
+    def delete_interface(self, context, router_id, port):
+        return self.plugin.deactivate_port(context, port)
+
+
+class RouterOpenFlowDriver(RouterDriverBase):
+
+    need_gw_info = True
+
+    def floating_ip_support(self):
+        return self.ofc.driver.router_nat_supported
+
+    def _process_gw_port(self, gw_info, routes):
+        if gw_info and gw_info['gateway_ip']:
+            routes.append({'destination': '0.0.0.0/0',
+                           'nexthop': gw_info['gateway_ip']})
+
+    @call_log.log
+    def create_router(self, context, tenant_id, router):
+        try:
+            router_id = router['id']
+            added_routes = []
+            self.ofc.ensure_ofc_tenant(context, tenant_id)
+            self.ofc.create_ofc_router(context, tenant_id, router_id,
+                                       router['name'])
+            self._process_gw_port(router['gw_port'], added_routes)
+            if added_routes:
+                self.ofc.update_ofc_router_route(context, router_id,
+                                                 added_routes, [])
+            new_status = nconst.ROUTER_STATUS_ACTIVE
+            self.plugin._update_resource_status(context, "router",
+                                                router['id'],
+                                                new_status)
+            router['status'] = new_status
+            return router
+        except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
+            with excutils.save_and_reraise_exception():
+                if (isinstance(exc, nexc.OFCException) and
+                    exc.status == httplib.CONFLICT):
+                    raise nexc.RouterOverLimit(provider=PROVIDER_OPENFLOW)
+                reason = _("create_router() failed due to %s") % exc
+                LOG.error(reason)
+                new_status = nconst.ROUTER_STATUS_ERROR
+                self._update_resource_status(context, "router",
+                                             router['id'],
+                                             new_status)
+
+    @call_log.log
+    def update_router(self, context, router_id, old_router, new_router):
+        old_routes = old_router['routes'][:]
+        new_routes = new_router['routes'][:]
+        self._process_gw_port(old_router['gw_port'], old_routes)
+        self._process_gw_port(new_router['gw_port'], new_routes)
+        added, removed = utils.diff_list_of_dict(old_routes, new_routes)
+        if added or removed:
+            try:
+                # NOTE(amotoki): PFC supports one-by-one route update at now.
+                # It means there may be a case where some route is updated but
+                # some not. To allow the next call of failures to sync routes
+                # with Neutron side, we pass the whole new routes here.
+                # PFC should support atomic route update in the future.
+                self.ofc.update_ofc_router_route(context, router_id,
+                                                 new_routes)
+                new_status = nconst.ROUTER_STATUS_ACTIVE
+                self.plugin._update_resource_status(
+                    context, "router", router_id, new_status)
+                new_router['status'] = new_status
+            except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
+                with excutils.save_and_reraise_exception():
+                    reason = _("_update_ofc_routes() failed due to %s") % exc
+                    LOG.error(reason)
+                    new_status = nconst.ROUTER_STATUS_ERROR
+                    self.plugin._update_resource_status(
+                        context, "router", router_id, new_status)
+        return new_router
+
+    @call_log.log
+    def delete_router(self, context, router_id, router):
+        try:
+            self.ofc.delete_ofc_router(context, router_id, router)
+        except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
+            with excutils.save_and_reraise_exception():
+                LOG.error(_("delete_router() failed due to %s"), exc)
+                self.plugin._update_resource_status(
+                    context, "router", router_id, nconst.ROUTER_STATUS_ERROR)
+
+    @call_log.log
+    def add_interface(self, context, router_id, port):
+        port_id = port['id']
+        # port['fixed_ips'] may be empty if ext_net has no subnet.
+        # Such port is invalid for a router port and we don't create a port
+        # on OFC. The port is removed in l3_db._create_router_gw_port.
+        if not port['fixed_ips']:
+            msg = _('RouterOpenFlowDriver.add_interface(): the requested port '
+                    'has no subnet. add_interface() is skipped. '
+                    'router_id=%(id)s, port=%(port)s)')
+            LOG.warning(msg, {'id': router_id, 'port': port})
+            return port
+        fixed_ip = port['fixed_ips'][0]
+        subnet = self.plugin._get_subnet(context, fixed_ip['subnet_id'])
+        port_info = {'network_id': port['network_id'],
+                     'ip_address': fixed_ip['ip_address'],
+                     'cidr': subnet['cidr'],
+                     'mac_address': port['mac_address']}
+        try:
+            self.ofc.add_ofc_router_interface(context, router_id,
+                                              port_id, port_info)
+            new_status = nconst.ROUTER_STATUS_ACTIVE
+            self.plugin._update_resource_status(
+                context, "port", port_id, new_status)
+            return port
+        except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
+            with excutils.save_and_reraise_exception():
+                reason = _("add_router_interface() failed due to %s") % exc
+                LOG.error(reason)
+                new_status = nconst.ROUTER_STATUS_ERROR
+                self.plugin._update_resource_status(
+                    context, "port", port_id, new_status)
+
+    @call_log.log
+    def delete_interface(self, context, router_id, port):
+        port_id = port['id']
+        try:
+            self.ofc.delete_ofc_router_interface(context, router_id, port_id)
+            new_status = nconst.ROUTER_STATUS_ACTIVE
+            self.plugin._update_resource_status(context, "port", port_id,
+                                                new_status)
+            port['status'] = new_status
+            return port
+        except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
+            with excutils.save_and_reraise_exception():
+                reason = _("delete_router_interface() failed due to %s") % exc
+                LOG.error(reason)
+                new_status = nconst.ROUTER_STATUS_ERROR
+                self.plugin._update_resource_status(context, "port", port_id,
+                                                    new_status)
index 88ee53554bde25c3e7edb6afbb476eed5997bfc9..378c80614f0c62904dea81072243e5eee463a1a1 100644 (file)
 #    under the License.
 # @author: Ryota MIBU
 
+import netaddr
+
+from neutron.common import log as call_log
+from neutron.openstack.common import log as logging
+from neutron.openstack.common import uuidutils
+from neutron.plugins.nec.common import exceptions as nexc
 from neutron.plugins.nec import ofc_driver_base
 
 
+LOG = logging.getLogger(__name__)
+
+MAX_NUM_OPENFLOW_ROUTER = 2
+
+
 class StubOFCDriver(ofc_driver_base.OFCDriverBase):
+    """Stub OFC driver for testing.
+
+    This driver can be used not only for unit tests but also for real testing
+    as a logging driver. It stores the created resources on OFC and returns
+    them in get methods().
+
+    If autocheck is enabled, it checks whether the specified resource exists
+    in OFC and raises an exception if it is different from expected status.
+    """
 
     def __init__(self, conf):
-        pass
+        self.autocheck = False
+        self.reset_all()
+
+    def reset_all(self):
+        self.ofc_tenant_dict = {}
+        self.ofc_network_dict = {}
+        self.ofc_port_dict = {}
+        self.ofc_filter_dict = {}
+        self.ofc_router_dict = {}
+        self.ofc_router_inf_dict = {}
+        self.ofc_router_route_dict = {}
+
+    def enable_autocheck(self):
+        self.autocheck = True
 
+    def disable_autocheck(self):
+        self.autocheck = False
+
+    @call_log.log
     def create_tenant(self, description, tenant_id=None):
-        return "ofc-" + tenant_id[:-4]
+        ofc_id = "ofc-" + tenant_id[:-4]
+        if self.autocheck:
+            if ofc_id in self.ofc_tenant_dict:
+                raise Exception(_('(create_tenant) OFC tenant %s '
+                                  'already exists') % ofc_id)
+        self.ofc_tenant_dict[ofc_id] = {'tenant_id': tenant_id,
+                                        'description': description}
+        return ofc_id
 
+    @call_log.log
     def delete_tenant(self, ofc_tenant_id):
-        pass
+        if ofc_tenant_id in self.ofc_tenant_dict:
+            del self.ofc_tenant_dict[ofc_tenant_id]
+        else:
+            if self.autocheck:
+                raise Exception(_('(delete_tenant) OFC tenant %s not found')
+                                % ofc_tenant_id)
+        LOG.debug(_('delete_tenant: SUCCEED'))
 
+    @call_log.log
     def create_network(self, ofc_tenant_id, description, network_id=None):
-        return "ofc-" + network_id[:-4]
+        ofc_id = "ofc-" + network_id[:-4]
+        if self.autocheck:
+            if ofc_tenant_id not in self.ofc_tenant_dict:
+                raise Exception(_('(create_network) OFC tenant %s not found')
+                                % ofc_tenant_id)
+            if ofc_id in self.ofc_network_dict:
+                raise Exception(_('(create_network) OFC network %s '
+                                  'already exists') % ofc_id)
+        self.ofc_network_dict[ofc_id] = {'tenant_id': ofc_tenant_id,
+                                         'network_id': network_id,
+                                         'description': description}
+        return ofc_id
 
+    @call_log.log
     def update_network(self, ofc_network_id, description):
-        pass
+        if self.autocheck:
+            if ofc_network_id not in self.ofc_network_dict:
+                raise Exception(_('(update_network) OFC network %s not found')
+                                % ofc_network_id)
+        data = {'description': description}
+        self.ofc_network_dict[ofc_network_id].update(data)
+        LOG.debug(_('update_network: SUCCEED'))
 
+    @call_log.log
     def delete_network(self, ofc_network_id):
-        pass
+        if ofc_network_id in self.ofc_network_dict:
+            del self.ofc_network_dict[ofc_network_id]
+        else:
+            if self.autocheck:
+                raise Exception(_('(delete_network) OFC network %s not found')
+                                % ofc_network_id)
+        LOG.debug(_('delete_network: SUCCEED'))
 
+    @call_log.log
     def create_port(self, ofc_network_id, info, port_id=None):
-        return "ofc-" + port_id[:-4]
+        ofc_id = "ofc-" + port_id[:-4]
+        if self.autocheck:
+            if ofc_network_id not in self.ofc_network_dict:
+                raise Exception(_('(create_port) OFC network %s not found')
+                                % ofc_network_id)
+            if ofc_id in self.ofc_port_dict:
+                raise Exception(_('(create_port) OFC port %s already exists')
+                                % ofc_id)
+        self.ofc_port_dict[ofc_id] = {'network_id': ofc_network_id,
+                                      'port_id': port_id}
+        return ofc_id
 
+    @call_log.log
     def delete_port(self, ofc_port_id):
-        pass
+        if ofc_port_id in self.ofc_port_dict:
+            del self.ofc_port_dict[ofc_port_id]
+        else:
+            if self.autocheck:
+                raise Exception(_('(delete_port) OFC port %s not found')
+                                % ofc_port_id)
+        LOG.debug(_('delete_port: SUCCEED'))
 
     @classmethod
     def filter_supported(cls):
@@ -66,3 +161,131 @@ class StubOFCDriver(ofc_driver_base.OFCDriverBase):
 
     def convert_ofc_filter_id(self, context, ofc_filter_id):
         return ofc_filter_id
+
+    router_supported = True
+    router_nat_supported = True
+
+    @call_log.log
+    def create_router(self, ofc_tenant_id, router_id, description):
+        ofc_id = "ofc-" + router_id[:-4]
+        if self.autocheck:
+            if ofc_tenant_id not in self.ofc_tenant_dict:
+                raise Exception(_('(create_router) OFC tenant %s not found')
+                                % ofc_tenant_id)
+            if ofc_id in self.ofc_router_dict:
+                raise Exception(_('(create_router) OFC router %s '
+                                  'already exists') % ofc_id)
+        if len(self.ofc_router_dict) >= MAX_NUM_OPENFLOW_ROUTER:
+            params = {'reason': _("Operation on OFC is failed"),
+                      'status': 409}
+            raise nexc.OFCException(**params)
+        self.ofc_router_dict[ofc_id] = {'tenant_id': ofc_tenant_id,
+                                        'router_id': router_id,
+                                        'description': description}
+        return ofc_id
+
+    @call_log.log
+    def delete_router(self, ofc_router_id):
+        if ofc_router_id in self.ofc_router_dict:
+            del self.ofc_router_dict[ofc_router_id]
+        else:
+            if self.autocheck:
+                raise Exception(_('(delete_router) OFC router %s not found')
+                                % ofc_router_id)
+        LOG.debug(_('delete_router: SUCCEED'))
+
+    @call_log.log
+    def add_router_interface(self, ofc_router_id, ofc_net_id,
+                             ip_address=None, mac_address=None):
+        if_id = "ofc-" + uuidutils.generate_uuid()[:-4]
+        # IP address should have a format of a.b.c.d/N
+        if ip_address != str(netaddr.IPNetwork(ip_address)):
+            raise Exception(_('(add_router_interface) '
+                            'ip_address %s is not a valid format (a.b.c.d/N).')
+                            % ip_address)
+        if self.autocheck:
+            if ofc_router_id not in self.ofc_router_dict:
+                raise Exception(_('(add_router_interface) '
+                                  'OFC router %s not found') % ofc_router_id)
+            if ofc_net_id not in self.ofc_network_dict:
+                raise Exception(_('(add_router_interface) '
+                                  'OFC network %s not found') % ofc_net_id)
+            # Check duplicate destination
+        self.ofc_router_inf_dict[if_id] = {'router_id': ofc_router_id,
+                                           'network_id': ofc_net_id,
+                                           'ip_address': ip_address,
+                                           'mac_address': mac_address}
+        LOG.debug(_('add_router_interface: SUCCEED (if_id=%s)'), if_id)
+        return if_id
+
+    @call_log.log
+    def update_router_interface(self, ofc_router_inf_id,
+                                ip_address=None, mac_address=None):
+        if ofc_router_inf_id not in self.ofc_router_inf_dict:
+            if self.autocheck:
+                raise Exception(_('(delete_router_interface) '
+                                  'OFC router interface %s not found')
+                                % ofc_router_inf_id)
+            self.ofc_router_inf_dict[ofc_router_inf_id] = {}
+        inf = self.ofc_router_inf_dict[ofc_router_inf_id]
+        if ip_address:
+            inf.update({'ip_address': ip_address})
+        if mac_address:
+            inf.update({'mac_address': mac_address})
+        LOG.debug(_('update_router_route: SUCCEED'))
+
+    @call_log.log
+    def delete_router_interface(self, ofc_router_inf_id):
+        if ofc_router_inf_id in self.ofc_router_inf_dict:
+            del self.ofc_router_inf_dict[ofc_router_inf_id]
+        else:
+            if self.autocheck:
+                raise Exception(_('(delete_router_interface) '
+                                  'OFC router interface %s not found')
+                                % ofc_router_inf_id)
+        LOG.debug(_('delete_router_interface: SUCCEED'))
+
+    @call_log.log
+    def add_router_route(self, ofc_router_id, destination, nexthop):
+        route_id = "ofc-" + uuidutils.generate_uuid()[:-4]
+        # IP address format check
+        netaddr.IPNetwork(destination)
+        netaddr.IPAddress(nexthop)
+        if self.autocheck:
+            if ofc_router_id not in self.ofc_router_dict:
+                raise Exception(_('(add_router_route) OFC router %s not found')
+                                % ofc_router_id)
+            # Check duplicate destination
+            if destination in [route['destination'] for route in
+                               self.ofc_router_route_dict.values()]:
+                raise Exception(_('(add_router_route) '
+                                'route to "%s" already exists') % destination)
+        self.ofc_router_route_dict[route_id] = {'router_id': ofc_router_id,
+                                                'destination': destination,
+                                                'nexthop': nexthop}
+        LOG.debug(_('add_router_route: SUCCEED (route_id=%s)'), route_id)
+        return route_id
+
+    @call_log.log
+    def delete_router_route(self, ofc_router_route_id):
+        if ofc_router_route_id in self.ofc_router_route_dict:
+            del self.ofc_router_route_dict[ofc_router_route_id]
+        else:
+            if self.autocheck:
+                raise Exception(_('(delete_router_route) OFC router route %s '
+                                  'not found') % ofc_router_route_id)
+        LOG.debug(_('delete_router_route: SUCCEED'))
+
+    @call_log.log
+    def list_router_routes(self, ofc_router_id):
+        if self.autocheck:
+            if ofc_router_id not in self.ofc_router_dict:
+                raise Exception(_('(delete_router) OFC router %s not found')
+                                % ofc_router_id)
+        routes = [{'id': k,
+                   'destination': v['destination'],
+                   'nexthop': v['nexthop']}
+                  for k, v in self.ofc_router_route_dict.items()
+                  if v['router_id'] == ofc_router_id]
+        LOG.debug(_('list_router_routes: routes=%s'), routes)
+        return routes
index 68dd70e18af89687f8fd09e578f1f2eb6e0553b3..ddd6ab13ee7589ccb2c03c58defdf70b3f103f3c 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import mox
+import contextlib
 
-from neutron.plugins.nec.common import ofc_client
+from neutron.common import constants
+from neutron.db import l3_rpc_base
 from neutron.tests.unit.nec import test_nec_plugin
 from neutron.tests.unit.openvswitch import test_agent_scheduler
 
+L3_HOSTA = test_agent_scheduler.L3_HOSTA
+L3_HOSTB = test_agent_scheduler.L3_HOSTB
+
 
 class NecAgentSchedulerTestCase(
-    test_agent_scheduler.OvsAgentSchedulerTestCase):
+    test_agent_scheduler.OvsAgentSchedulerTestCase,
+    test_nec_plugin.NecPluginV2TestCaseBase):
+
     plugin_str = test_nec_plugin.PLUGIN_NAME
 
     def setUp(self):
+        self.setup_nec_plugin_base()
         super(NecAgentSchedulerTestCase, self).setUp()
-        self.mox = mox.Mox()
-        self.mox.StubOutWithMock(ofc_client.OFCClient, 'do_request')
-        self.addCleanup(self.mox.UnsetStubs)
 
 
 class NecDhcpAgentNotifierTestCase(
-    test_agent_scheduler.OvsDhcpAgentNotifierTestCase):
+    test_agent_scheduler.OvsDhcpAgentNotifierTestCase,
+    test_nec_plugin.NecPluginV2TestCaseBase):
+
     plugin_str = test_nec_plugin.PLUGIN_NAME
 
     def setUp(self):
+        # OvsDhcpAgentNotifierTestCase uses stop() for each mock.
+        self.setup_nec_plugin_base(use_stop_each=True)
         super(NecDhcpAgentNotifierTestCase, self).setUp()
-        self.mox = mox.Mox()
-        self.mox.StubOutWithMock(ofc_client.OFCClient, 'do_request')
-        self.addCleanup(self.mox.UnsetStubs)
 
 
 class NecL3AgentNotifierTestCase(
-    test_agent_scheduler.OvsL3AgentNotifierTestCase):
+    test_agent_scheduler.OvsL3AgentNotifierTestCase,
+    test_nec_plugin.NecPluginV2TestCaseBase):
+
     plugin_str = test_nec_plugin.PLUGIN_NAME
 
     def setUp(self):
+        # OvsDhcpAgentNotifierTestCase uses stop() for each mock.
+        self.setup_nec_plugin_base(use_stop_each=True)
         super(NecL3AgentNotifierTestCase, self).setUp()
-        self.mox = mox.Mox()
-        self.mox.StubOutWithMock(ofc_client.OFCClient, 'do_request')
-        self.addCleanup(self.mox.UnsetStubs)
+
+
+class NecL3AgentSchedulerWithOpenFlowRouter(
+    test_agent_scheduler.OvsAgentSchedulerTestCaseBase,
+    test_nec_plugin.NecPluginV2TestCaseBase):
+
+    plugin_str = test_nec_plugin.PLUGIN_NAME
+
+    def setUp(self):
+        self.setup_nec_plugin_base()
+        super(NecL3AgentSchedulerWithOpenFlowRouter, self).setUp()
+
+    def test_router_auto_schedule_with_l3agent_and_openflow(self):
+        with contextlib.nested(
+            self.router(),
+            self.router(arg_list=('provider',),
+                        provider='openflow'
+                        )) as (r1, r2):
+            l3_rpc = l3_rpc_base.L3RpcCallbackMixin()
+            self._register_agent_states()
+            ret_a = l3_rpc.sync_routers(self.adminContext, host=L3_HOSTA)
+            ret_b = l3_rpc.sync_routers(self.adminContext, host=L3_HOSTB)
+            l3_agents = self._list_l3_agents_hosting_router(
+                r1['router']['id'])
+            self.assertEqual(1, len(ret_a))
+            self.assertFalse(len(ret_b))
+            self.assertIn(r1['router']['id'], [r['id'] for r in ret_a])
+            self.assertNotIn(r2['router']['id'], [r['id'] for r in ret_a])
+        self.assertEqual(1, len(l3_agents['agents']))
+        self.assertEqual(L3_HOSTA, l3_agents['agents'][0]['host'])
+
+    def test_router_auto_schedule_only_with_openflow_router(self):
+        with contextlib.nested(
+            self.router(arg_list=('provider',), provider='openflow'),
+            self.router(arg_list=('provider',), provider='openflow')
+        ) as (r1, r2):
+            l3_rpc = l3_rpc_base.L3RpcCallbackMixin()
+            self._register_agent_states()
+            ret_a = l3_rpc.sync_routers(self.adminContext, host=L3_HOSTA)
+            l3_agents_1 = self._list_l3_agents_hosting_router(
+                r1['router']['id'])
+            l3_agents_2 = self._list_l3_agents_hosting_router(
+                r2['router']['id'])
+            self.assertFalse(len(ret_a))
+            self.assertNotIn(r1['router']['id'], [r['id'] for r in ret_a])
+            self.assertNotIn(r2['router']['id'], [r['id'] for r in ret_a])
+        self.assertFalse(len(l3_agents_1['agents']))
+        self.assertFalse(len(l3_agents_2['agents']))
+
+    def test_add_router_to_l3_agent_for_openflow_router(self):
+        with self.router(arg_list=('provider',), provider='openflow') as r1:
+            self._register_agent_states()
+            hosta_id = self._get_agent_id(constants.AGENT_TYPE_L3,
+                                          L3_HOSTA)
+            self._add_router_to_l3_agent(hosta_id,
+                                         r1['router']['id'],
+                                         expected_code=409)
index a4e81bd7ad598a2ec0d56c747c9b9c3c1dbbbf20..2f913caf5458daa964198f578ec2ac54695521d2 100644 (file)
@@ -33,6 +33,8 @@ from neutron.tests.unit import test_db_plugin as test_plugin
 
 
 PLUGIN_NAME = 'neutron.plugins.nec.nec_plugin.NECPluginV2'
+OFC_MANAGER = 'neutron.plugins.nec.nec_plugin.ofc_manager.OFCManager'
+NOTIFIER = 'neutron.plugins.nec.nec_plugin.NECPluginV2AgentNotifierApi'
 NEC_PLUGIN_INI = """
 [DEFAULT]
 api_extensions_path = neutron/plugins/nec/extensions
@@ -42,9 +44,7 @@ enable_packet_filter = False
 """
 
 
-class NecPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
-
-    _plugin_name = PLUGIN_NAME
+class NecPluginV2TestCaseBase(object):
     _nec_ini = NEC_PLUGIN_INI
 
     def _set_nec_ini(self):
@@ -64,6 +64,34 @@ class NecPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
         os.remove(self.nec_ini_file)
         self.nec_ini_file = None
 
+    def patch_remote_calls(self, use_stop=False):
+        self.plugin_notifier_p = mock.patch(NOTIFIER)
+        self.ofc_manager_p = mock.patch(OFC_MANAGER)
+        self.plugin_notifier_p.start()
+        self.ofc_manager_p.start()
+        # When using mock.patch.stopall, we need to ensure
+        # stop is not used anywhere in a single test.
+        # In Neutron several tests use stop for each patched object,
+        # so we need to take care of both cases.
+        if use_stop:
+            self.addCleanup(self.plugin_notifier_p.stop)
+            self.addCleanup(self.ofc_manager_p.stop)
+
+    def setup_nec_plugin_base(self, use_stop_all=True,
+                              use_stop_each=False):
+        # If use_stop_each is set, use_stop_all cannot be set.
+        if use_stop_all and not use_stop_each:
+            self.addCleanup(mock.patch.stopall)
+        self._set_nec_ini()
+        self.addCleanup(self._clean_nec_ini)
+        self.patch_remote_calls(use_stop_each)
+
+
+class NecPluginV2TestCase(NecPluginV2TestCaseBase,
+                          test_plugin.NeutronDbPluginV2TestCase):
+
+    _plugin_name = PLUGIN_NAME
+
     def rpcapi_update_ports(self, agent_id='nec-q-agent.fake',
                             datapath_id="0xabc", added=[], removed=[]):
         kwargs = {'topic': topics.AGENT,
@@ -348,6 +376,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
             mock.call.create_ofc_network(ctx, self._tenant_id, net['id'],
                                          net['name']),
             mock.call.delete_ofc_network(ctx, net['id'], mock.ANY),
+            mock.call.exists_ofc_tenant(ctx, self._tenant_id),
             mock.call.delete_ofc_tenant(ctx, self._tenant_id)
         ]
         self.ofc.assert_has_calls(expected)
@@ -365,6 +394,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
             mock.call.create_ofc_network(ctx, self._tenant_id, net['id'],
                                          net['name']),
             mock.call.delete_ofc_network(ctx, net['id'], mock.ANY),
+            mock.call.exists_ofc_tenant(ctx, self._tenant_id),
             mock.call.delete_ofc_tenant(ctx, self._tenant_id)
         ]
         self.ofc.assert_has_calls(expected)
@@ -389,6 +419,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
                                          nets[1]['name']),
             mock.call.delete_ofc_network(ctx, nets[1]['id'], mock.ANY),
             mock.call.delete_ofc_network(ctx, nets[0]['id'], mock.ANY),
+            mock.call.exists_ofc_tenant(ctx, self._tenant_id),
             mock.call.delete_ofc_tenant(ctx, self._tenant_id)
         ]
         self.ofc.assert_has_calls(expected)
@@ -451,6 +482,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
             mock.call.create_ofc_network(ctx, self._tenant_id, net['id'],
                                          net['name']),
             mock.call.delete_ofc_network(ctx, net['id'], mock.ANY),
+            mock.call.exists_ofc_tenant(ctx, self._tenant_id),
             mock.call.delete_ofc_tenant(ctx, self._tenant_id)
         ]
         self.ofc.assert_has_calls(expected)
@@ -478,6 +510,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
 
             mock.call.exists_ofc_port(ctx, p1['id']),
             mock.call.delete_ofc_network(ctx, net['id'], mock.ANY),
+            mock.call.exists_ofc_tenant(ctx, self._tenant_id),
             mock.call.delete_ofc_tenant(ctx, self._tenant_id)
         ]
         self.ofc.assert_has_calls(expected)
@@ -520,6 +553,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
             mock.call.exists_ofc_port(ctx, p1['id']),
             mock.call.delete_ofc_port(ctx, p1['id'], mock.ANY),
             mock.call.delete_ofc_network(ctx, net['id'], mock.ANY),
+            mock.call.exists_ofc_tenant(ctx, self._tenant_id),
             mock.call.delete_ofc_tenant(ctx, self._tenant_id)
         ]
         self.ofc.assert_has_calls(expected)
@@ -550,6 +584,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
             mock.call.exists_ofc_port(ctx, p['id']),
             mock.call.delete_ofc_port(ctx, p['id'], mock.ANY),
             mock.call.delete_ofc_network(ctx, net['id'], mock.ANY),
+            mock.call.exists_ofc_tenant(ctx, self._tenant_id),
             mock.call.delete_ofc_tenant(ctx, self._tenant_id)
         ]
         self.ofc.assert_has_calls(expected)
@@ -686,6 +721,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
 
             mock.call.exists_ofc_port(ctx, p1['id']),
             mock.call.delete_ofc_network(ctx, net['id'], mock.ANY),
+            mock.call.exists_ofc_tenant(ctx, self._tenant_id),
             mock.call.delete_ofc_tenant(ctx, self._tenant_id)
         ]
         self.ofc.assert_has_calls(expected)
diff --git a/neutron/tests/unit/nec/test_ofc_client.py b/neutron/tests/unit/nec/test_ofc_client.py
new file mode 100644 (file)
index 0000000..1567ae9
--- /dev/null
@@ -0,0 +1,117 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 NEC Corporation.  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: Akihiro Motoki
+
+import json
+import socket
+
+import mock
+
+from neutron.plugins.nec.common import exceptions as nexc
+from neutron.plugins.nec.common import ofc_client
+from neutron.tests import base
+
+
+class OFCClientTest(base.BaseTestCase):
+
+    def _test_do_request(self, status, resbody, data, exctype=None,
+                         exc_checks=None):
+        res = mock.Mock()
+        res.status = status
+        res.read.return_value = resbody
+
+        conn = mock.Mock()
+        conn.getresponse.return_value = res
+
+        with mock.patch.object(ofc_client.OFCClient, 'get_connection',
+                               return_value=conn):
+            client = ofc_client.OFCClient()
+
+            if exctype:
+                e = self.assertRaises(exctype, client.do_request,
+                                      'GET', '/somewhere', body={})
+                self.assertEqual(data, str(e))
+                if exc_checks:
+                    for k, v in exc_checks.items():
+                        self.assertEqual(v, getattr(e, k))
+            else:
+                response = client.do_request('GET', '/somewhere', body={})
+                self.assertEqual(response, data)
+
+            headers = {"Content-Type": "application/json"}
+            expected = [
+                mock.call.request('GET', '/somewhere', '{}', headers),
+                mock.call.getresponse(),
+            ]
+            conn.assert_has_calls(expected)
+
+    def test_do_request_200_json_value(self):
+        self._test_do_request(200, json.dumps([1, 2, 3]), [1, 2, 3])
+
+    def test_do_request_200_string(self):
+        self._test_do_request(200, 'abcdef', 'abcdef')
+
+    def test_do_request_200_no_body(self):
+        self._test_do_request(200, None, None)
+
+    def test_do_request_other_success_codes(self):
+        for status in [201, 202, 204]:
+            self._test_do_request(status, None, None)
+
+    def test_do_request_error_no_body(self):
+        errmsg = _("An OFC exception has occurred: Operation on OFC failed")
+        exc_checks = {'status': 400, 'err_code': None, 'err_msg': None}
+        self._test_do_request(400, None, errmsg, nexc.OFCException, exc_checks)
+
+    def test_do_request_error_string_body(self):
+        resbody = 'This is an error.'
+        errmsg = _("An OFC exception has occurred: Operation on OFC failed")
+        exc_checks = {'status': 400, 'err_code': None,
+                      'err_msg': 'This is an error.'}
+        self._test_do_request(400, resbody, errmsg, nexc.OFCException,
+                              exc_checks)
+
+    def test_do_request_error_json_body(self):
+        resbody = json.dumps({'err_code': 40022,
+                              'err_msg': 'This is an error.'})
+        errmsg = _("An OFC exception has occurred: Operation on OFC failed")
+        exc_checks = {'status': 400, 'err_code': 40022,
+                      'err_msg': 'This is an error.'}
+        self._test_do_request(400, resbody, errmsg, nexc.OFCException,
+                              exc_checks)
+
+    def test_do_request_socket_error(self):
+        conn = mock.Mock()
+        conn.request.side_effect = socket.error
+
+        data = _("An OFC exception has occurred: Failed to connect OFC : ")
+
+        with mock.patch.object(ofc_client.OFCClient, 'get_connection',
+                               return_value=conn):
+            client = ofc_client.OFCClient()
+
+            e = self.assertRaises(nexc.OFCException, client.do_request,
+                                  'GET', '/somewhere', body={})
+            self.assertEqual(data, str(e))
+            for k in ['status', 'err_code', 'err_msg']:
+                self.assertIsNone(getattr(e, k))
+
+            headers = {"Content-Type": "application/json"}
+            expected = [
+                mock.call.request('GET', '/somewhere', '{}', headers),
+            ]
+            conn.assert_has_calls(expected)
index 569f5db5e227fb7dcc4bdaa6183d8a1fa290d1d9..309a4ac3051e9a28c24b1cc8616b52e275e500ac 100644 (file)
@@ -49,6 +49,8 @@ class OFCManagerTestBase(base.BaseTestCase):
         ndb.initialize()
         self.addCleanup(ndb.clear_db)
         self.ofc = ofc_manager.OFCManager()
+        # NOTE: enable_autocheck() is a feature of StubOFCDriver
+        self.ofc.driver.enable_autocheck()
         self.ctx = context.get_admin_context()
         self.addCleanup(mock.patch.stopall)
 
@@ -160,6 +162,8 @@ class OFCManagerTest(OFCManagerTestBase):
         self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_port', p))
         get_portinfo.assert_called_once_with(mock.ANY, p)
 
+
+class OFCManagerFilterTest(OFCManagerTestBase):
     def testj_create_ofc_packet_filter(self):
         """test create ofc_filter."""
         t, n, p, f, none = self.get_random_params()
@@ -198,8 +202,92 @@ class OFCManagerTest(OFCManagerTestBase):
                                           'ofc_packet_filter', f))
 
 
+class OFCManagerRouterTest(OFCManagerTestBase):
+    def get_random_params(self):
+        tenant = uuidutils.generate_uuid()
+        router = uuidutils.generate_uuid()
+        network = uuidutils.generate_uuid()
+        return (tenant, router, network)
+
+    def test_create_ofc_router(self):
+        """test create ofc_router"""
+        t, r, _n = self.get_random_params()
+        self.ofc.create_ofc_tenant(self.ctx, t)
+        self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_router', r))
+        self.ofc.create_ofc_router(self.ctx, t, r, 'test router')
+        self.assertTrue(ndb.get_ofc_item(self.ctx.session, 'ofc_router', r))
+        router = ndb.get_ofc_item(self.ctx.session, 'ofc_router', r)
+        self.assertEqual(router.ofc_id, "ofc-" + r[:-4])
+
+    def test_exists_ofc_router(self):
+        """test exists_ofc_router"""
+        t, r, _n = self.get_random_params()
+        self.ofc.create_ofc_tenant(self.ctx, t)
+        self.assertFalse(self.ofc.exists_ofc_router(self.ctx, r))
+        self.ofc.create_ofc_router(self.ctx, t, r)
+        self.assertTrue(self.ofc.exists_ofc_router(self.ctx, r))
+
+    def test_delete_ofc_router(self):
+        """test delete ofc_router"""
+        t, r, _n = self.get_random_params()
+        self.ofc.create_ofc_tenant(self.ctx, t)
+        self.ofc.create_ofc_router(self.ctx, t, r)
+        self.assertTrue(ndb.get_ofc_item(self.ctx.session, 'ofc_router', r))
+        self.ofc.delete_ofc_router(self.ctx, r, {'tenant_id': t})
+        self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_network', r))
+
+    def test_router_interface(self):
+        t, r, n = self.get_random_params()
+        self.ofc.create_ofc_tenant(self.ctx, t)
+        self.ofc.create_ofc_router(self.ctx, t, r)
+        self.ofc.create_ofc_network(self.ctx, t, n)
+        self.assertTrue(ndb.get_ofc_item(self.ctx.session, 'ofc_router', r))
+        self.assertTrue(ndb.get_ofc_item(self.ctx.session, 'ofc_network', n))
+
+        p = {'id': uuidutils.generate_uuid(),
+             'network_id': n, 'ip_address': '10.1.1.1', 'cidr': '10.1.0.0/20',
+             'mac_address': '11:22:33:44:55:66'}
+        self.ofc.add_ofc_router_interface(self.ctx, r, p['id'], p)
+        self.assertTrue(ndb.get_ofc_item(self.ctx.session,
+                                         'ofc_port', p['id']))
+        self.ofc.delete_ofc_router_interface(self.ctx, r, p['id'])
+        self.assertFalse(ndb.get_ofc_item(self.ctx.session,
+                                          'ofc_port', p['id']))
+        self.ofc.delete_ofc_router(self.ctx, r, {'tenant_id': t})
+        self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_network', r))
+
+    def test_router_route(self):
+        t, r, _n = self.get_random_params()
+        self.ofc.create_ofc_tenant(self.ctx, t)
+        self.ofc.create_ofc_router(self.ctx, t, r)
+        self.assertTrue(ndb.get_ofc_item(self.ctx.session, 'ofc_router', r))
+
+        routes = [{'destination': '2.2.2.0/24', 'nexthop': '1.1.1.10'}]
+        self.ofc.update_ofc_router_route(self.ctx, r, routes)
+        self.assertEqual(len(self.ofc.driver.ofc_router_route_dict), 1)
+
+        routes = [{'destination': '3.3.3.0/24', 'nexthop': '1.1.1.11'},
+                  {'destination': '4.4.4.0/24', 'nexthop': '1.1.1.11'}]
+        self.ofc.update_ofc_router_route(self.ctx, r, routes)
+        self.assertEqual(len(self.ofc.driver.ofc_router_route_dict), 2)
+
+        routes = [{'destination': '2.2.2.0/24', 'nexthop': '1.1.1.10'}]
+        self.ofc.update_ofc_router_route(self.ctx, r, routes)
+        self.assertEqual(len(self.ofc.driver.ofc_router_route_dict), 1)
+
+        routes = []
+        self.ofc.update_ofc_router_route(self.ctx, r, routes)
+        self.assertEqual(len(self.ofc.driver.ofc_router_route_dict), 0)
+
+
 class OFCManagerTestWithOldMapping(OFCManagerTestBase):
 
+    def setUp(self):
+        super(OFCManagerTestWithOldMapping, self).setUp()
+        # NOTE(amotoki): In OldMapping tests, DB entries are directly modified
+        # to create a case where the old mapping tables are used intentionally.
+        self.ofc.driver.disable_autocheck()
+
     def test_exists_ofc_tenant(self):
         t, n, p, f, none = self.get_random_params()
         ofc_t, ofc_n, ofc_p, ofc_f, ofc_none = self.get_random_params()
index 229002f59ef6435c95abac0c1f90232962ef809a..7f5883247fad005ccc01afd2e36cf3e088043d83 100644 (file)
@@ -19,6 +19,7 @@ import random
 import string
 
 import mox
+import netaddr
 
 from neutron import context
 from neutron.openstack.common import uuidutils
@@ -202,6 +203,167 @@ class PFCV4DriverTest(PFCDriverTestBase):
     driver = 'pfc_v4'
 
 
+class PFCV5DriverTest(PFCDriverTestBase):
+    driver = 'pfc_v5'
+
+    def test_create_router(self):
+        t = uuidutils.generate_uuid()
+        r = uuidutils.generate_uuid()
+        description = 'dummy_router_desc'
+
+        tenant_path = "/tenants/%s" % _ofc(t)
+        post_path = "%s/routers" % tenant_path
+        router = {'id': _ofc(r)}
+        ofc.OFCClient.do_request("POST", post_path,
+                                 body=None).AndReturn(router)
+        self.mox.ReplayAll()
+
+        ret = self.driver.create_router(tenant_path, description, r)
+        self.mox.VerifyAll()
+        router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
+        self.assertEqual(ret, router_path)
+
+    def test_delete_router(self):
+        t = uuidutils.generate_uuid()
+        r = uuidutils.generate_uuid()
+
+        router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
+        ofc.OFCClient.do_request("DELETE", router_path)
+        self.mox.ReplayAll()
+
+        self.driver.delete_router(router_path)
+        self.mox.VerifyAll()
+
+    def test_add_router_interface(self):
+        t = uuidutils.generate_uuid()
+        r = uuidutils.generate_uuid()
+        n = uuidutils.generate_uuid()
+        p = uuidutils.generate_uuid()
+
+        router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
+        infs_path = router_path + "/interfaces"
+        net_path = "/tenants/%s/networks/%s" % (_ofc(t), _ofc(n))
+        ip_address = '10.1.1.1/24'
+        mac_address = '11:22:33:44:55:66'
+        body = {'net_id': _ofc(n),
+                'ip_address': ip_address,
+                'mac_address': mac_address}
+        inf = {'id': _ofc(p)}
+        ofc.OFCClient.do_request("POST", infs_path,
+                                 body=body).AndReturn(inf)
+        self.mox.ReplayAll()
+
+        ret = self.driver.add_router_interface(router_path, net_path,
+                                               ip_address, mac_address)
+        self.mox.VerifyAll()
+        inf_path = "%s/interfaces/%s" % (router_path, _ofc(p))
+        self.assertEqual(ret, inf_path)
+
+    def test_update_router_interface(self):
+        t = uuidutils.generate_uuid()
+        r = uuidutils.generate_uuid()
+        p = uuidutils.generate_uuid()
+
+        router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
+        inf_path = "%s/interfaces/%s" % (router_path, _ofc(p))
+        ip_address = '10.1.1.1/24'
+        mac_address = '11:22:33:44:55:66'
+
+        body = {'ip_address': ip_address,
+                'mac_address': mac_address}
+        ofc.OFCClient.do_request("PUT", inf_path, body=body)
+
+        body = {'ip_address': ip_address}
+        ofc.OFCClient.do_request("PUT", inf_path, body=body)
+
+        body = {'mac_address': mac_address}
+        ofc.OFCClient.do_request("PUT", inf_path, body=body)
+
+        self.mox.ReplayAll()
+
+        self.driver.update_router_interface(inf_path, ip_address, mac_address)
+        self.driver.update_router_interface(inf_path, ip_address=ip_address)
+        self.driver.update_router_interface(inf_path, mac_address=mac_address)
+        self.mox.VerifyAll()
+
+    def test_delete_router_interface(self):
+        t = uuidutils.generate_uuid()
+        r = uuidutils.generate_uuid()
+        p = uuidutils.generate_uuid()
+
+        router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
+        inf_path = "%s/interfaces/%s" % (router_path, _ofc(p))
+        ofc.OFCClient.do_request("DELETE", inf_path)
+        self.mox.ReplayAll()
+
+        self.driver.delete_router_interface(inf_path)
+        self.mox.VerifyAll()
+
+    def _get_route_id(self, dest, nexthop):
+        dest = netaddr.IPNetwork(dest)
+        return '-'.join([str(dest.network), nexthop, str(dest.netmask)])
+
+    def test_add_router_route(self):
+        t = uuidutils.generate_uuid()
+        r = uuidutils.generate_uuid()
+
+        router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
+        routes_path = router_path + "/routes"
+        dest = '10.1.1.0/24'
+        nexthop = '192.168.100.10'
+        body = {'destination': dest, 'nexthop': nexthop}
+        route_id = self._get_route_id(dest, nexthop)
+        ofc.OFCClient.do_request("POST", routes_path,
+                                 body=body).AndReturn({'id': route_id})
+        self.mox.ReplayAll()
+
+        ret = self.driver.add_router_route(router_path, '10.1.1.0/24',
+                                           '192.168.100.10')
+        self.mox.VerifyAll()
+        route_path = routes_path + '/' + route_id
+        self.assertEqual(ret, route_path)
+
+    def test_delete_router_route(self):
+        t = uuidutils.generate_uuid()
+        r = uuidutils.generate_uuid()
+
+        router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
+        routes_path = router_path + "/routes"
+
+        route_id = self._get_route_id('10.1.1.0/24', '192.168.100.10')
+        route_path = routes_path + '/' + route_id
+        ofc.OFCClient.do_request("DELETE", route_path)
+        self.mox.ReplayAll()
+
+        self.driver.delete_router_route(route_path)
+        self.mox.VerifyAll()
+
+    def test_list_router_routes(self):
+        t = uuidutils.generate_uuid()
+        r = uuidutils.generate_uuid()
+
+        router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
+        routes_path = router_path + "/routes"
+
+        routes = [('10.1.1.0/24', '192.168.100.10'),
+                  ('10.2.2.0/20', '192.168.100.20')]
+        data = {'routes': [{'id': self._get_route_id(route[0], route[1]),
+                            'destination': route[0], 'nexthop': route[1]}
+                           for route in routes]}
+        ofc.OFCClient.do_request("GET", routes_path).AndReturn(data)
+        self.mox.ReplayAll()
+
+        ret = self.driver.list_router_routes(router_path)
+        self.mox.VerifyAll()
+
+        expected = [{'id': (routes_path + "/" +
+                            self._get_route_id(route[0], route[1])),
+                     'destination': route[0], 'nexthop': route[1]}
+                    for route in routes]
+        self.assertEqual(len(routes), len(ret))
+        self.assertEqual(data['routes'], expected)
+
+
 class PFCDriverStringTest(base.BaseTestCase):
 
     driver = 'neutron.plugins.nec.drivers.pfc.PFCDriverBase'
diff --git a/neutron/tests/unit/nec/test_router.py b/neutron/tests/unit/nec/test_router.py
new file mode 100644 (file)
index 0000000..4e7505e
--- /dev/null
@@ -0,0 +1,46 @@
+# Copyright (c) 2013 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.
+
+import mock
+
+from neutron import manager
+from neutron.plugins.nec.common import config
+from neutron.tests.unit.nec import test_nec_plugin
+from neutron.tests.unit import test_extension_extraroute as test_ext_route
+
+
+class NecRouterL3AgentTestCase(test_ext_route.ExtraRouteDBTestCase):
+
+    _plugin_name = test_nec_plugin.PLUGIN_NAME
+
+    def setUp(self):
+        self.addCleanup(mock.patch.stopall)
+        mock.patch(test_nec_plugin.OFC_MANAGER).start()
+        super(NecRouterL3AgentTestCase, self).setUp(self._plugin_name)
+
+        plugin = manager.NeutronManager.get_plugin()
+        plugin.network_scheduler = None
+        plugin.router_scheduler = None
+
+    def test_floatingip_with_invalid_create_port(self):
+        self._test_floatingip_with_invalid_create_port(self._plugin_name)
+
+
+class NecRouterOpenFlowTestCase(NecRouterL3AgentTestCase):
+
+    def setUp(self):
+        config.CONF.set_override('default_router_provider',
+                                 'openflow', 'PROVIDER')
+        super(NecRouterOpenFlowTestCase, self).setUp()
index c843a2b10888d0a57f003ea26674ae9cf0c5996a..726a7f2779b2c3f52b26f32930de17a59845ec51 100644 (file)
@@ -189,10 +189,10 @@ class AgentSchedulerTestMixIn(object):
                 return agent_data['id']
 
 
-class OvsAgentSchedulerTestCase(test_l3_plugin.L3NatTestCaseMixin,
-                                test_agent_ext_plugin.AgentDBTestMixIn,
-                                AgentSchedulerTestMixIn,
-                                test_plugin.NeutronDbPluginV2TestCase):
+class OvsAgentSchedulerTestCaseBase(test_l3_plugin.L3NatTestCaseMixin,
+                                    test_agent_ext_plugin.AgentDBTestMixIn,
+                                    AgentSchedulerTestMixIn,
+                                    test_plugin.NeutronDbPluginV2TestCase):
     fmt = 'json'
     plugin_str = ('neutron.plugins.openvswitch.'
                   'ovs_neutron_plugin.OVSNeutronPluginV2')
@@ -202,7 +202,7 @@ class OvsAgentSchedulerTestCase(test_l3_plugin.L3NatTestCaseMixin,
         self.saved_attr_map = {}
         for resource, attrs in attributes.RESOURCE_ATTRIBUTE_MAP.iteritems():
             self.saved_attr_map[resource] = attrs.copy()
-        super(OvsAgentSchedulerTestCase, self).setUp(self.plugin_str)
+        super(OvsAgentSchedulerTestCaseBase, self).setUp(self.plugin_str)
         ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
         self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr)
         self.adminContext = context.get_admin_context()
@@ -219,6 +219,9 @@ class OvsAgentSchedulerTestCase(test_l3_plugin.L3NatTestCaseMixin,
         # Restore the original RESOURCE_ATTRIBUTE_MAP
         attributes.RESOURCE_ATTRIBUTE_MAP = self.saved_attr_map
 
+
+class OvsAgentSchedulerTestCase(OvsAgentSchedulerTestCaseBase):
+
     def test_report_states(self):
         self._register_agent_states()
         agents = self._list_agents()
@@ -957,12 +960,7 @@ class OvsAgentSchedulerTestCase(test_l3_plugin.L3NatTestCaseMixin,
                 admin_context=False)
 
 
-class OvsDhcpAgentNotifierTestCase(test_l3_plugin.L3NatTestCaseMixin,
-                                   test_agent_ext_plugin.AgentDBTestMixIn,
-                                   AgentSchedulerTestMixIn,
-                                   test_plugin.NeutronDbPluginV2TestCase):
-    plugin_str = ('neutron.plugins.openvswitch.'
-                  'ovs_neutron_plugin.OVSNeutronPluginV2')
+class OvsDhcpAgentNotifierTestCase(OvsAgentSchedulerTestCaseBase):
 
     def setUp(self):
         self.dhcp_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
@@ -971,27 +969,8 @@ class OvsDhcpAgentNotifierTestCase(test_l3_plugin.L3NatTestCaseMixin,
             'DhcpAgentNotifyAPI')
         self.dhcp_notifier_cls = self.dhcp_notifier_cls_p.start()
         self.dhcp_notifier_cls.return_value = self.dhcp_notifier
-        # Save the global RESOURCE_ATTRIBUTE_MAP
-        self.saved_attr_map = {}
-        for resource, attrs in attributes.RESOURCE_ATTRIBUTE_MAP.iteritems():
-            self.saved_attr_map[resource] = attrs.copy()
-        super(OvsDhcpAgentNotifierTestCase, self).setUp(self.plugin_str)
-        ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
-        self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr)
-        self.adminContext = context.get_admin_context()
-        # Add the resources to the global attribute map
-        # This is done here as the setup process won't
-        # initialize the main API router which extends
-        # the global attribute map
-        attributes.RESOURCE_ATTRIBUTE_MAP.update(
-            agent.RESOURCE_ATTRIBUTE_MAP)
-        self.agentscheduler_dbMinxin = manager.NeutronManager.get_plugin()
+        super(OvsDhcpAgentNotifierTestCase, self).setUp()
         self.addCleanup(self.dhcp_notifier_cls_p.stop)
-        self.addCleanup(self.restore_attribute_map)
-
-    def restore_attribute_map(self):
-        # Restore the original RESOURCE_ATTRIBUTE_MAP
-        attributes.RESOURCE_ATTRIBUTE_MAP = self.saved_attr_map
 
     def test_network_add_to_dhcp_agent_notification(self):
         with mock.patch.object(self.dhcp_notifier, 'cast') as mock_dhcp:
@@ -1093,12 +1072,7 @@ class OvsDhcpAgentNotifierTestCase(test_l3_plugin.L3NatTestCaseMixin,
             self.assertIn(expected, mock_dhcp.call_args_list)
 
 
-class OvsL3AgentNotifierTestCase(test_l3_plugin.L3NatTestCaseMixin,
-                                 test_agent_ext_plugin.AgentDBTestMixIn,
-                                 AgentSchedulerTestMixIn,
-                                 test_plugin.NeutronDbPluginV2TestCase):
-    plugin_str = ('neutron.plugins.openvswitch.'
-                  'ovs_neutron_plugin.OVSNeutronPluginV2')
+class OvsL3AgentNotifierTestCase(OvsAgentSchedulerTestCaseBase):
 
     def setUp(self):
         self.dhcp_notifier_cls_p = mock.patch(
@@ -1107,27 +1081,8 @@ class OvsL3AgentNotifierTestCase(test_l3_plugin.L3NatTestCaseMixin,
         self.dhcp_notifier = mock.Mock(name='dhcp_notifier')
         self.dhcp_notifier_cls = self.dhcp_notifier_cls_p.start()
         self.dhcp_notifier_cls.return_value = self.dhcp_notifier
-        # Save the global RESOURCE_ATTRIBUTE_MAP
-        self.saved_attr_map = {}
-        for resource, attrs in attributes.RESOURCE_ATTRIBUTE_MAP.iteritems():
-            self.saved_attr_map[resource] = attrs.copy()
-        super(OvsL3AgentNotifierTestCase, self).setUp(self.plugin_str)
-        ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
-        self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr)
-        self.adminContext = context.get_admin_context()
-        # Add the resources to the global attribute map
-        # This is done here as the setup process won't
-        # initialize the main API router which extends
-        # the global attribute map
-        attributes.RESOURCE_ATTRIBUTE_MAP.update(
-            agent.RESOURCE_ATTRIBUTE_MAP)
-        self.agentscheduler_dbMinxin = manager.NeutronManager.get_plugin()
+        super(OvsL3AgentNotifierTestCase, self).setUp()
         self.addCleanup(self.dhcp_notifier_cls_p.stop)
-        self.addCleanup(self.restore_attribute_map)
-
-    def restore_attribute_map(self):
-        # Restore the original RESOURCE_ATTRIBUTE_MAP
-        attributes.RESOURCE_ATTRIBUTE_MAP = self.saved_attr_map
 
     def test_router_add_to_l3_agent_notification(self):
         plugin = manager.NeutronManager.get_plugin()
index 4ea4c83cd774a696c73271461ae8fcbc5c992118..50ab18c97e7802bfcc93f185f684883854d6b1b0 100644 (file)
@@ -59,10 +59,11 @@ class TestExtraRoutePlugin(test_l3.TestL3NatPlugin,
 
 class ExtraRouteDBTestCase(test_l3.L3NatDBTestCase):
 
-    def setUp(self):
-        test_config['plugin_name_v2'] = (
-            'neutron.tests.unit.'
-            'test_extension_extraroute.TestExtraRoutePlugin')
+    def setUp(self, plugin=None):
+        if not plugin:
+            plugin = ('neutron.tests.unit.test_extension_extraroute.'
+                      'TestExtraRoutePlugin')
+        test_config['plugin_name_v2'] = plugin
         # for these tests we need to enable overlapping ips
         cfg.CONF.set_default('allow_overlapping_ips', True)
         cfg.CONF.set_default('max_routes', 3)
index c2412c8794cbcde41dacebbae33e33a01c3599e1..ce68bd2f41e412f5250c338c994d31c962d35f0e 100644 (file)
@@ -28,7 +28,6 @@ from webob import exc
 import webtest
 
 from neutron.api import extensions
-from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
 from neutron.api.v2 import attributes
 from neutron.common import config
 from neutron.common import constants as l3_constants
@@ -321,13 +320,15 @@ class L3NatTestCaseMixin(object):
         return router_req.get_response(self.ext_api)
 
     def _make_router(self, fmt, tenant_id, name=None, admin_state_up=None,
-                     external_gateway_info=None, set_context=False):
-        arg_list = (external_gateway_info and
-                    ('external_gateway_info', ) or None)
+                     external_gateway_info=None, set_context=False,
+                     arg_list=None, **kwargs):
+        if external_gateway_info:
+            arg_list = ('external_gateway_info', ) + (arg_list or ())
         res = self._create_router(fmt, tenant_id, name,
                                   admin_state_up, set_context,
                                   arg_list=arg_list,
-                                  external_gateway_info=external_gateway_info)
+                                  external_gateway_info=external_gateway_info,
+                                  **kwargs)
         return self.deserialize(fmt, res)
 
     def _add_external_gateway_to_router(self, router_id, network_id,
@@ -367,10 +368,11 @@ class L3NatTestCaseMixin(object):
     @contextlib.contextmanager
     def router(self, name='router1', admin_state_up=True,
                fmt=None, tenant_id=_uuid(),
-               external_gateway_info=None, set_context=False):
+               external_gateway_info=None, set_context=False,
+               **kwargs):
         router = self._make_router(fmt or self.fmt, tenant_id, name,
                                    admin_state_up, external_gateway_info,
-                                   set_context)
+                                   set_context, **kwargs)
         try:
             yield router
         finally:
@@ -1673,18 +1675,19 @@ class L3AgentDbTestCase(L3NatTestCaseBase):
     def _test_notify_op_agent(self, target_func, *args):
         l3_rpc_agent_api_str = (
             'neutron.api.rpc.agentnotifiers.l3_rpc_agent_api.L3AgentNotifyAPI')
-        oldNotify = l3_rpc_agent_api.L3AgentNotify
+        plugin = NeutronManager.get_plugin()
+        oldNotify = plugin.l3_rpc_notifier
         try:
             with mock.patch(l3_rpc_agent_api_str) as notifyApi:
-                l3_rpc_agent_api.L3AgentNotify = notifyApi
+                plugin.l3_rpc_notifier = notifyApi
                 kargs = [item for item in args]
                 kargs.append(notifyApi)
                 target_func(*kargs)
         except Exception:
-            l3_rpc_agent_api.L3AgentNotify = oldNotify
+            plugin.l3_rpc_notifier = oldNotify
             raise
         else:
-            l3_rpc_agent_api.L3AgentNotify = oldNotify
+            plugin.l3_rpc_notifier = oldNotify
 
     def _test_router_gateway_op_agent(self, notifyApi):
         with self.router() as r: