diff --git a/etc/neutron/plugins/nec/nec.ini b/etc/neutron/plugins/nec/nec.ini index cbcc0ec5c4..cc9d812bfd 100644 --- a/etc/neutron/plugins/nec/nec.ini +++ b/etc/neutron/plugins/nec/nec.ini @@ -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 diff --git a/neutron/db/l3_db.py b/neutron/db/l3_db.py index 7059c00ac4..87cf9dc9cf 100644 --- a/neutron/db/l3_db.py +++ b/neutron/db/l3_db.py @@ -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 index 0000000000..ffead11472 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/66a59a7f516_nec_openflow_router.py @@ -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') diff --git a/neutron/plugins/nec/common/config.py b/neutron/plugins/nec/common/config.py index 669887f591..bcd4cc8d28 100644 --- a/neutron/plugins/nec/common/config.py +++ b/neutron/plugins/nec/common/config.py @@ -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 index 0000000000..b1bc7e5b39 --- /dev/null +++ b/neutron/plugins/nec/common/constants.py @@ -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' diff --git a/neutron/plugins/nec/common/exceptions.py b/neutron/plugins/nec/common/exceptions.py index 74e000b5c5..d6b2b3b500 100644 --- a/neutron/plugins/nec/common/exceptions.py +++ b/neutron/plugins/nec/common/exceptions.py @@ -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.") diff --git a/neutron/plugins/nec/common/ofc_client.py b/neutron/plugins/nec/common/ofc_client.py index 44ba4dfd92..6df8b4f030 100644 --- a/neutron/plugins/nec/common/ofc_client.py +++ b/neutron/plugins/nec/common/ofc_client.py @@ -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) diff --git a/neutron/plugins/nec/db/api.py b/neutron/plugins/nec/db/api.py index 333065122d..e606861ff2 100644 --- a/neutron/plugins/nec/db/api.py +++ b/neutron/plugins/nec/db/api.py @@ -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 diff --git a/neutron/plugins/nec/db/models.py b/neutron/plugins/nec/db/models.py index 7d9cf10691..bf2dfea6d4 100644 --- a/neutron/plugins/nec/db/models.py +++ b/neutron/plugins/nec/db/models.py @@ -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 index 0000000000..9659cd7fd9 --- /dev/null +++ b/neutron/plugins/nec/db/router.py @@ -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 diff --git a/neutron/plugins/nec/drivers/__init__.py b/neutron/plugins/nec/drivers/__init__.py index 178b34d343..2bb0516855 100644 --- a/neutron/plugins/nec/drivers/__init__.py +++ b/neutron/plugins/nec/drivers/__init__.py @@ -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): diff --git a/neutron/plugins/nec/drivers/pfc.py b/neutron/plugins/nec/drivers/pfc.py index e60bb89073..e8d23677f5 100644 --- a/neutron/plugins/nec/drivers/pfc.py +++ b/neutron/plugins/nec/drivers/pfc.py @@ -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//networks/ + 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 : / (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 : / (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 diff --git a/neutron/plugins/nec/drivers/trema.py b/neutron/plugins/nec/drivers/trema.py index e4081e1d2a..5a2d29524b 100644 --- a/neutron/plugins/nec/drivers/trema.py +++ b/neutron/plugins/nec/drivers/trema.py @@ -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 index 0000000000..d893a4c188 --- /dev/null +++ b/neutron/plugins/nec/extensions/router_provider.py @@ -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 {} diff --git a/neutron/plugins/nec/nec_plugin.py b/neutron/plugins/nec/nec_plugin.py index df3ee1ef62..26088148d5 100644 --- a/neutron/plugins/nec/nec_plugin.py +++ b/neutron/plugins/nec/nec_plugin.py @@ -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 index 0000000000..be1f0e37c8 --- /dev/null +++ b/neutron/plugins/nec/nec_router.py @@ -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] diff --git a/neutron/plugins/nec/ofc_manager.py b/neutron/plugins/nec/ofc_manager.py index fd80a00f79..ff12e72568 100644 --- a/neutron/plugins/nec/ofc_manager.py +++ b/neutron/plugins/nec/ofc_manager.py @@ -16,12 +16,19 @@ # @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 index 0000000000..b16c4d8a0f --- /dev/null +++ b/neutron/plugins/nec/router_drivers.py @@ -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) diff --git a/neutron/tests/unit/nec/stub_ofc_driver.py b/neutron/tests/unit/nec/stub_ofc_driver.py index 88ee53554b..378c80614f 100644 --- a/neutron/tests/unit/nec/stub_ofc_driver.py +++ b/neutron/tests/unit/nec/stub_ofc_driver.py @@ -15,34 +15,129 @@ # 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 diff --git a/neutron/tests/unit/nec/test_agent_scheduler.py b/neutron/tests/unit/nec/test_agent_scheduler.py index 68dd70e18a..ddd6ab13ee 100644 --- a/neutron/tests/unit/nec/test_agent_scheduler.py +++ b/neutron/tests/unit/nec/test_agent_scheduler.py @@ -15,41 +15,104 @@ # 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) diff --git a/neutron/tests/unit/nec/test_nec_plugin.py b/neutron/tests/unit/nec/test_nec_plugin.py index a4e81bd7ad..2f913caf54 100644 --- a/neutron/tests/unit/nec/test_nec_plugin.py +++ b/neutron/tests/unit/nec/test_nec_plugin.py @@ -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 index 0000000000..1567ae9e0e --- /dev/null +++ b/neutron/tests/unit/nec/test_ofc_client.py @@ -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) diff --git a/neutron/tests/unit/nec/test_ofc_manager.py b/neutron/tests/unit/nec/test_ofc_manager.py index 569f5db5e2..309a4ac305 100644 --- a/neutron/tests/unit/nec/test_ofc_manager.py +++ b/neutron/tests/unit/nec/test_ofc_manager.py @@ -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() diff --git a/neutron/tests/unit/nec/test_pfc_driver.py b/neutron/tests/unit/nec/test_pfc_driver.py index 229002f59e..7f5883247f 100644 --- a/neutron/tests/unit/nec/test_pfc_driver.py +++ b/neutron/tests/unit/nec/test_pfc_driver.py @@ -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 index 0000000000..4e7505ef0b --- /dev/null +++ b/neutron/tests/unit/nec/test_router.py @@ -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() diff --git a/neutron/tests/unit/openvswitch/test_agent_scheduler.py b/neutron/tests/unit/openvswitch/test_agent_scheduler.py index c843a2b108..726a7f2779 100644 --- a/neutron/tests/unit/openvswitch/test_agent_scheduler.py +++ b/neutron/tests/unit/openvswitch/test_agent_scheduler.py @@ -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() diff --git a/neutron/tests/unit/test_extension_extraroute.py b/neutron/tests/unit/test_extension_extraroute.py index 4ea4c83cd7..50ab18c97e 100644 --- a/neutron/tests/unit/test_extension_extraroute.py +++ b/neutron/tests/unit/test_extension_extraroute.py @@ -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) diff --git a/neutron/tests/unit/test_l3_plugin.py b/neutron/tests/unit/test_l3_plugin.py index c2412c8794..ce68bd2f41 100644 --- a/neutron/tests/unit/test_l3_plugin.py +++ b/neutron/tests/unit/test_l3_plugin.py @@ -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: