OpenFlow distributed router support in NEC plugin

Implements blueprint nec-distribute-router

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

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

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

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

Change-Id: I538201742950a61b92fb05c49a9256bc96ae9014
This commit is contained in:
Akihiro MOTOKI 2013-08-23 15:22:04 +09:00
parent 508fa4ba6f
commit 5932096f21
28 changed files with 1939 additions and 158 deletions

View File

@ -41,3 +41,9 @@ firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewal
# Certificate file
# cert_file =
[provider]
# Default router provider to use.
# default_router_provider = l3-agent
# List of enabled router providers.
# router_providers = l3-agent,openflow

View File

@ -91,6 +91,8 @@ class FloatingIP(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
class L3_NAT_db_mixin(l3.RouterPluginBase):
"""Mixin class to add L3/NAT router methods to db_plugin_base_v2."""
l3_rpc_notifier = l3_rpc_agent_api.L3AgentNotify
def _network_model_hook(self, context, original_model, query):
query = query.outerjoin(ExternalNetwork,
(original_model.id ==
@ -186,7 +188,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
# Ensure we actually have something to update
if r.keys():
router_db.update(r)
l3_rpc_agent_api.L3AgentNotify.routers_updated(
self.l3_rpc_notifier.routers_updated(
context, [router_db['id']])
return self._make_router_dict(router_db)
@ -278,7 +280,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
self._delete_port(context.elevated(), ports[0]['id'])
context.session.delete(router)
l3_rpc_agent_api.L3AgentNotify.router_deleted(context, id)
self.l3_rpc_notifier.router_deleted(context, id)
def get_router(self, context, id, fields=None):
router = self._get_router(context, id)
@ -385,7 +387,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
'device_owner': DEVICE_OWNER_ROUTER_INTF,
'name': ''}})
l3_rpc_agent_api.L3AgentNotify.routers_updated(
self.l3_rpc_notifier.routers_updated(
context, [router_id], 'add_router_interface')
info = {'id': router_id,
'tenant_id': subnet['tenant_id'],
@ -457,7 +459,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
if not found:
raise l3.RouterInterfaceNotFoundForSubnet(router_id=router_id,
subnet_id=subnet_id)
l3_rpc_agent_api.L3AgentNotify.routers_updated(
self.l3_rpc_notifier.routers_updated(
context, [router_id], 'remove_router_interface')
info = {'id': router_id,
'tenant_id': subnet['tenant_id'],
@ -670,7 +672,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
router_id = floatingip_db['router_id']
if router_id:
l3_rpc_agent_api.L3AgentNotify.routers_updated(
self.l3_rpc_notifier.routers_updated(
context, [router_id],
'create_floatingip')
return self._make_floatingip_dict(floatingip_db)
@ -693,8 +695,8 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
if router_id and router_id != before_router_id:
router_ids.append(router_id)
if router_ids:
l3_rpc_agent_api.L3AgentNotify.routers_updated(context, router_ids,
'update_floatingip')
self.l3_rpc_notifier.routers_updated(
context, router_ids, 'update_floatingip')
return self._make_floatingip_dict(floatingip_db)
def delete_floatingip(self, context, id):
@ -706,7 +708,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
floatingip['floating_port_id'],
l3_port_check=False)
if router_id:
l3_rpc_agent_api.L3AgentNotify.routers_updated(
self.l3_rpc_notifier.routers_updated(
context, [router_id],
'delete_floatingip')
@ -777,7 +779,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
raise Exception(_('Multiple floating IPs found for port %s')
% port_id)
if router_id:
l3_rpc_agent_api.L3AgentNotify.routers_updated(
self.l3_rpc_notifier.routers_updated(
context, [router_id])
def _network_is_external(self, context, net_id):

View File

@ -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')

View File

@ -19,6 +19,7 @@ from oslo.config import cfg
from neutron.agent.common import config
from neutron.openstack.common import rpc # noqa
from neutron.plugins.nec.common import constants as nconst
ovs_opts = [
@ -49,10 +50,20 @@ ofc_opts = [
help=_("Certificate file")),
]
provider_opts = [
cfg.StrOpt('default_router_provider',
default=nconst.DEFAULT_ROUTER_PROVIDER,
help=_('Default router provider to use.')),
cfg.ListOpt('router_providers',
default=nconst.DEFAULT_ROUTER_PROVIDERS,
help=_('List of enabled router providers.'))
]
cfg.CONF.register_opts(ovs_opts, "OVS")
cfg.CONF.register_opts(agent_opts, "AGENT")
cfg.CONF.register_opts(ofc_opts, "OFC")
cfg.CONF.register_opts(provider_opts, "PROVIDER")
config.register_agent_state_opts_helper(cfg.CONF)
config.register_root_helper(cfg.CONF)
@ -61,3 +72,4 @@ CONF = cfg.CONF
OVS = cfg.CONF.OVS
AGENT = cfg.CONF.AGENT
OFC = cfg.CONF.OFC
PROVIDER = cfg.CONF.PROVIDER

View File

@ -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'

View File

@ -21,6 +21,12 @@ from neutron.common import exceptions as qexc
class OFCException(qexc.NeutronException):
message = _("An OFC exception has occurred: %(reason)s")
def __init__(self, **kwargs):
super(OFCException, self).__init__(**kwargs)
self.status = kwargs.get('status')
self.err_msg = kwargs.get('err_msg')
self.err_code = kwargs.get('err_code')
class NECDBException(qexc.NeutronException):
message = _("An exception occurred in NECPluginV2 DB: %(reason)s")
@ -44,3 +50,22 @@ class ProfilePortInfoInvalidDataPathId(qexc.InvalidInput):
class ProfilePortInfoInvalidPortNo(qexc.InvalidInput):
message = _('Invalid input for operation: '
'portinfo:port_no should be [0:65535]')
class RouterExternalGatewayNotSupported(qexc.BadRequest):
message = _("Router (provider=%(provider)s) does not support "
"an external network")
class ProviderNotFound(qexc.NotFound):
message = _("Provider %(provider)s could not be found")
class RouterOverLimit(qexc.Conflict):
message = _("Cannot create more routers with provider=%(provider)s")
class RouterProviderMismatch(qexc.Conflict):
message = _("Provider of Router %(router_id)s is %(provider)s. "
"This operation is supported only for router provider "
"%(expected_provider)s.")

View File

@ -46,12 +46,26 @@ class OFCClient(object):
self.cert_file = cert_file
self.connection = None
def get_connection_type(self):
"""Returns the proper connection type."""
def get_connection(self):
"""Returns the proper connection."""
if self.use_ssl:
return httplib.HTTPSConnection
connection_type = httplib.HTTPSConnection
else:
return httplib.HTTPConnection
connection_type = httplib.HTTPConnection
# Open connection and send request, handling SSL certs
certs = {'key_file': self.key_file, 'cert_file': self.cert_file}
certs = dict((x, certs[x]) for x in certs if certs[x] is not None)
if self.use_ssl and len(certs):
conn = connection_type(self.host, self.port, **certs)
else:
conn = connection_type(self.host, self.port)
return conn
def _format_error_message(self, status, detail):
detail = ' ' + detail if detail else ''
return (_("Operation on OFC failed: %(status)s%(msg)s") %
{'status': status, 'msg': detail})
def do_request(self, method, action, body=None):
LOG.debug(_("Client request: %(host)s:%(port)s "
@ -61,32 +75,40 @@ class OFCClient(object):
if type(body) is dict:
body = json.dumps(body)
try:
connection_type = self.get_connection_type()
conn = self.get_connection()
headers = {"Content-Type": "application/json"}
# Open connection and send request, handling SSL certs
certs = {'key_file': self.key_file, 'cert_file': self.cert_file}
certs = dict((x, certs[x]) for x in certs if certs[x] is not None)
if self.use_ssl and len(certs):
conn = connection_type(self.host, self.port, **certs)
else:
conn = connection_type(self.host, self.port)
conn.request(method, action, body, headers)
res = conn.getresponse()
data = res.read()
LOG.debug(_("OFC returns [%(status)s:%(data)s]"),
{'status': res.status,
'data': data})
# Try to decode JSON data if possible.
try:
data = json.loads(data)
except (ValueError, TypeError):
pass
if res.status in (httplib.OK,
httplib.CREATED,
httplib.ACCEPTED,
httplib.NO_CONTENT):
if data and len(data) > 1:
return json.loads(data)
return data
else:
reason = _("An operation on OFC is failed.")
raise nexc.OFCException(reason=reason)
LOG.warning(_("Operation on OFC failed: "
"status=%(status), detail=%(detail)"),
{'status': res.status, 'detail': data})
params = {'reason': _("Operation on OFC failed"),
'status': res.status}
if isinstance(data, dict):
params['err_code'] = data.get('err_code')
params['err_msg'] = data.get('err_msg')
else:
params['err_msg'] = data
raise nexc.OFCException(**params)
except (socket.error, IOError) as e:
reason = _("Failed to connect OFC : %s") % str(e)
reason = _("Failed to connect OFC : %s") % e
LOG.error(reason)
raise nexc.OFCException(reason=reason)

View File

@ -36,6 +36,7 @@ OFP_VLAN_NONE = 0xffff
resource_map = {'ofc_tenant': nmodels.OFCTenantMapping,
'ofc_network': nmodels.OFCNetworkMapping,
'ofc_port': nmodels.OFCPortMapping,
'ofc_router': nmodels.OFCRouterMapping,
'ofc_packet_filter': nmodels.OFCFilterMapping}
old_resource_map = {'ofc_tenant': nmodels.OFCTenant,
@ -48,7 +49,9 @@ old_resource_map = {'ofc_tenant': nmodels.OFCTenant,
def _get_resource_model(resource, old_style):
if old_style:
return old_resource_map[resource]
# NOTE: Some new resources are not defined in old_resource_map.
# In such case None is returned.
return old_resource_map.get(resource)
else:
return resource_map[resource]
@ -62,8 +65,10 @@ def clear_db(base=model_base.BASEV2):
def get_ofc_item(session, resource, neutron_id, old_style=False):
try:
model = _get_resource_model(resource, old_style)
if not model:
return None
try:
return session.query(model).filter_by(quantum_id=neutron_id).one()
except sa.orm.exc.NoResultFound:
return None

View File

@ -47,6 +47,10 @@ class OFCPortMapping(model_base.BASEV2, NeutronId, OFCId):
"""Represents a Port on OpenFlow Network/Controller."""
class OFCRouterMapping(model_base.BASEV2, NeutronId, OFCId):
"""Represents a router on OpenFlow Network/Controller."""
class OFCFilterMapping(model_base.BASEV2, NeutronId, OFCId):
"""Represents a Filter on OpenFlow Network/Controller."""

View File

@ -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

View File

@ -28,7 +28,9 @@ DRIVER_LIST = {
'trema_mac': DRIVER_PATH % "trema.TremaMACBaseDriver",
'pfc': DRIVER_PATH % "pfc.PFCV4Driver",
'pfc_v3': DRIVER_PATH % "pfc.PFCV3Driver",
'pfc_v4': DRIVER_PATH % "pfc.PFCV4Driver"}
'pfc_v4': DRIVER_PATH % "pfc.PFCV4Driver",
'pfc_v5': DRIVER_PATH % "pfc.PFCV5Driver",
}
def get_driver(driver_name):

View File

@ -33,6 +33,8 @@ class PFCDriverBase(ofc_driver_base.OFCDriverBase):
The class implements the API for PFC V4.0 or later.
"""
router_supported = False
def __init__(self, conf_ofc):
self.client = ofc_client.OFCClient(host=conf_ofc.host,
port=conf_ofc.port,
@ -73,6 +75,10 @@ class PFCDriverBase(ofc_driver_base.OFCDriverBase):
"""
return self._generate_pfc_str(desc)[:127]
def _extract_ofc_network_id(self, ofc_network_id):
# ofc_network_id : /tenants/<tenant-id>/networks/<network-id>
return ofc_network_id.split('/')[4]
def create_tenant(self, description, tenant_id=None):
ofc_tenant_id = self._generate_pfc_id(tenant_id)
body = {'id': ofc_tenant_id}
@ -136,6 +142,66 @@ class PFCDriverBase(ofc_driver_base.OFCDriverBase):
return '%(network)s/ports/%(port)s' % params
class PFCRouterDriverMixin(object):
router_supported = True
router_nat_supported = False
def create_router(self, ofc_tenant_id, router_id, description):
path = '%s/routers' % ofc_tenant_id
res = self.client.post(path, body=None)
ofc_router_id = res['id']
return path + '/' + ofc_router_id
def delete_router(self, ofc_router_id):
return self.client.delete(ofc_router_id)
def add_router_interface(self, ofc_router_id, ofc_net_id,
ip_address=None, mac_address=None):
# ip_address : <ip_address>/<netmask> (e.g., 10.0.0.0/24)
path = '%s/interfaces' % ofc_router_id
body = {'net_id': self._extract_ofc_network_id(ofc_net_id)}
if ip_address:
body['ip_address'] = ip_address
if mac_address:
body['mac_address'] = mac_address
res = self.client.post(path, body=body)
return path + '/' + res['id']
def update_router_interface(self, ofc_router_inf_id,
ip_address=None, mac_address=None):
# ip_address : <ip_address>/<netmask> (e.g., 10.0.0.0/24)
if not ip_address and not mac_address:
return
body = {}
if ip_address:
body['ip_address'] = ip_address
if mac_address:
body['mac_address'] = mac_address
return self.client.put(ofc_router_inf_id, body=body)
def delete_router_interface(self, ofc_router_inf_id):
return self.client.delete(ofc_router_inf_id)
def list_router_routes(self, ofc_router_id):
path = '%s/routes' % ofc_router_id
ret = self.client.get(path)
# Prepend ofc_router_id to route_id
for r in ret['routes']:
r['id'] = ofc_router_id + '/routes/' + r['id']
return ret['routes']
def add_router_route(self, ofc_router_id, destination, nexthop):
path = '%s/routes' % ofc_router_id
body = {'destination': destination,
'nexthop': nexthop}
ret = self.client.post(path, body=body)
return path + '/' + ret['id']
def delete_router_route(self, ofc_router_route_id):
return self.client.delete(ofc_router_route_id)
class PFCV3Driver(PFCDriverBase):
def create_tenant(self, description, tenant_id):
@ -148,3 +214,7 @@ class PFCV3Driver(PFCDriverBase):
class PFCV4Driver(PFCDriverBase):
pass
class PFCV5Driver(PFCRouterDriverMixin, PFCDriverBase):
pass

View File

@ -27,6 +27,8 @@ class TremaDriverBase(ofc_driver_base.OFCDriverBase):
networks_path = "/networks"
network_path = "/networks/%s"
router_supported = False
def __init__(self, conf_ofc):
# Trema sliceable REST API does not support HTTPS
self.client = ofc_client.OFCClient(host=conf_ofc.host,
@ -74,7 +76,7 @@ class TremaDriverBase(ofc_driver_base.OFCDriverBase):
return self.network_path % ofc_network_id
class TremaFilterDriver(object):
class TremaFilterDriverMixin(object):
"""Trema (Sliceable Switch) PacketFilter Driver Mixin."""
filters_path = "/filters"
filter_path = "/filters/%s"
@ -173,7 +175,7 @@ class TremaFilterDriver(object):
return self.filter_path % ofc_filter_id
class TremaPortBaseDriver(TremaDriverBase, TremaFilterDriver):
class TremaPortBaseDriver(TremaDriverBase, TremaFilterDriverMixin):
"""Trema (Sliceable Switch) Driver for port base binding.
TremaPortBaseDriver uses port base binding.
@ -211,7 +213,7 @@ class TremaPortBaseDriver(TremaDriverBase, TremaFilterDriver):
'port': ofc_port_id}
class TremaPortMACBaseDriver(TremaDriverBase, TremaFilterDriver):
class TremaPortMACBaseDriver(TremaDriverBase, TremaFilterDriverMixin):
"""Trema (Sliceable Switch) Driver for port-mac base binding.
TremaPortBaseDriver uses port-mac base binding.

View File

@ -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 {}

View File

@ -18,7 +18,6 @@
from neutron.agent import securitygroups_rpc as sg_rpc
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
from neutron.api.v2 import attributes as attrs
from neutron.common import constants as const
from neutron.common import exceptions as q_exc
@ -28,8 +27,6 @@ from neutron.db import agents_db
from neutron.db import agentschedulers_db
from neutron.db import db_base_plugin_v2
from neutron.db import dhcp_rpc_base
from neutron.db import extraroute_db
from neutron.db import l3_gwmode_db
from neutron.db import l3_rpc_base
from neutron.db import portbindings_base
from neutron.db import portbindings_db
@ -44,6 +41,8 @@ from neutron.openstack.common import uuidutils
from neutron.plugins.nec.common import config
from neutron.plugins.nec.common import exceptions as nexc
from neutron.plugins.nec.db import api as ndb
from neutron.plugins.nec.db import router as rdb
from neutron.plugins.nec import nec_router
from neutron.plugins.nec import ofc_manager
from neutron.plugins.nec import packet_filter
@ -51,11 +50,10 @@ LOG = logging.getLogger(__name__)
class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
extraroute_db.ExtraRoute_db_mixin,
l3_gwmode_db.L3_NAT_db_mixin,
nec_router.RouterMixin,
sg_db_rpc.SecurityGroupServerRpcMixin,
agentschedulers_db.L3AgentSchedulerDbMixin,
agentschedulers_db.DhcpAgentSchedulerDbMixin,
nec_router.L3AgentSchedulerDbMixin,
packet_filter.PacketFilterMixin,
portbindings_db.PortBindingMixin):
"""NECPluginV2 controls an OpenFlow Controller.
@ -70,12 +68,18 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
The port binding extension enables an external application relay
information to and from the plugin.
"""
_supported_extension_aliases = ["router", "ext-gw-mode", "quotas",
"binding", "security-group",
"extraroute", "agent",
"l3_agent_scheduler",
_supported_extension_aliases = ["agent",
"binding",
"dhcp_agent_scheduler",
"packet-filter"]
"ext-gw-mode",
"extraroute",
"l3_agent_scheduler",
"packet-filter",
"quotas",
"router",
"router_provider",
"security-group",
]
@property
def supported_extension_aliases(self):
@ -99,6 +103,7 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
'neutron/plugins/nec/extensions')
self.setup_rpc()
self.l3_rpc_notifier = nec_router.L3AgentNotifyAPI()
self.network_scheduler = importutils.import_object(
config.CONF.network_scheduler_driver
@ -107,6 +112,20 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
config.CONF.router_scheduler_driver
)
nec_router.load_driver(self, self.ofc)
self.port_handlers = {
'create': {
const.DEVICE_OWNER_ROUTER_GW: self.create_router_port,
const.DEVICE_OWNER_ROUTER_INTF: self.create_router_port,
'default': self.activate_port_if_ready,
},
'delete': {
const.DEVICE_OWNER_ROUTER_GW: self.delete_router_port,
const.DEVICE_OWNER_ROUTER_INTF: self.delete_router_port,
'default': self.deactivate_port,
}
}
def setup_rpc(self):
self.topic = topics.PLUGIN
self.conn = rpc.create_connection(new=True)
@ -115,13 +134,14 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
)
self.agent_notifiers[const.AGENT_TYPE_L3] = (
l3_rpc_agent_api.L3AgentNotify
nec_router.L3AgentNotifyAPI()
)
# NOTE: callback_sg is referred to from the sg unit test.
self.callback_sg = SecurityGroupServerRpcCallback()
callbacks = [NECPluginV2RPCCallbacks(self),
DhcpRpcCallback(), L3RpcCallback(),
DhcpRpcCallback(),
L3RpcCallback(),
self.callback_sg,
agents_db.AgentExtRpcCallback()]
self.dispatcher = q_rpc.PluginRpcDispatcher(callbacks)
@ -131,10 +151,35 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
def _update_resource_status(self, context, resource, id, status):
"""Update status of specified resource."""
request = {}
request[resource] = dict(status=status)
obj_updater = getattr(super(NECPluginV2, self), "update_%s" % resource)
obj_updater(context, id, request)
request = {'status': status}
obj_getter = getattr(self, '_get_%s' % resource)
with context.session.begin(subtransactions=True):
obj_db = obj_getter(context, id)
obj_db.update(request)
def _check_ofc_tenant_in_use(self, context, tenant_id):
"""Check if the specified tenant is used."""
# All networks are created on OFC
filters = {'tenant_id': [tenant_id]}
if self.get_networks_count(context, filters=filters):
return True
if rdb.get_router_count_by_provider(context.session,
nec_router.PROVIDER_OPENFLOW,
tenant_id):
return True
return False
def _cleanup_ofc_tenant(self, context, tenant_id):
if not self._check_ofc_tenant_in_use(context, tenant_id):
try:
if self.ofc.exists_ofc_tenant(context, tenant_id):
self.ofc.delete_ofc_tenant(context, tenant_id)
else:
LOG.debug(_('_cleanup_ofc_tenant: No OFC tenant for %s'),
tenant_id)
except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
reason = _("delete_ofc_tenant() failed due to %s") % exc
LOG.warn(reason)
def activate_port_if_ready(self, context, port, network=None):
"""Activate port by creating port on OFC if ready.
@ -315,7 +360,6 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
self.delete_packet_filter(context, pf['id'])
try:
# 'net' parameter is required to lookup old OFC mapping
self.ofc.delete_ofc_network(context, id, net)
except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
reason = _("delete_network() failed due to %s") % exc
@ -326,15 +370,7 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
super(NECPluginV2, self).delete_network(context, id)
# delete unnessary ofc_tenant
filters = dict(tenant_id=[tenant_id])
nets = super(NECPluginV2, self).get_networks(context, filters=filters)
if not nets:
try:
self.ofc.delete_ofc_tenant(context, tenant_id)
except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
reason = _("delete_ofc_tenant() failed due to %s") % exc
LOG.warn(reason)
self._cleanup_ofc_tenant(context, tenant_id)
def _get_base_binding_dict(self):
binding = {
@ -449,6 +485,14 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
context, port_data, port)
return portinfo_changed
def _get_port_handler(self, operation, device_owner):
handlers = self.port_handlers[operation]
handler = handlers.get(device_owner)
if handler:
return handler
else:
return handlers['default']
def create_port(self, context, port):
"""Create a new port entry on DB, then try to activate it."""
LOG.debug(_("NECPluginV2.create_port() called, port=%s ."), port)
@ -465,7 +509,8 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
context, port, sgids)
self.notify_security_groups_member_updated(context, port)
return self.activate_port_if_ready(context, port)
handler = self._get_port_handler('create', port['device_owner'])
return handler(context, port)
def _update_ofc_port_if_required(self, context, old_port, new_port,
portinfo_changed):
@ -539,7 +584,9 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
# Thus we need to call self.get_port() instead of super().get_port()
port = self.get_port(context, id)
port = self.deactivate_port(context, port)
handler = self._get_port_handler('delete', port['device_owner'])
port = handler(context, port)
# port = self.deactivate_port(context, port)
if port['status'] == const.PORT_STATUS_ERROR:
reason = _("Failed to delete port=%s from OFC.") % id
raise nexc.OFCException(reason=reason)

View File

@ -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]

View File

@ -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'])

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -33,6 +33,8 @@ from neutron.tests.unit import test_db_plugin as test_plugin
PLUGIN_NAME = 'neutron.plugins.nec.nec_plugin.NECPluginV2'
OFC_MANAGER = 'neutron.plugins.nec.nec_plugin.ofc_manager.OFCManager'
NOTIFIER = 'neutron.plugins.nec.nec_plugin.NECPluginV2AgentNotifierApi'
NEC_PLUGIN_INI = """
[DEFAULT]
api_extensions_path = neutron/plugins/nec/extensions
@ -42,9 +44,7 @@ enable_packet_filter = False
"""
class NecPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
_plugin_name = PLUGIN_NAME
class NecPluginV2TestCaseBase(object):
_nec_ini = NEC_PLUGIN_INI
def _set_nec_ini(self):
@ -64,6 +64,34 @@ class NecPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
os.remove(self.nec_ini_file)
self.nec_ini_file = None
def patch_remote_calls(self, use_stop=False):
self.plugin_notifier_p = mock.patch(NOTIFIER)
self.ofc_manager_p = mock.patch(OFC_MANAGER)
self.plugin_notifier_p.start()
self.ofc_manager_p.start()
# When using mock.patch.stopall, we need to ensure
# stop is not used anywhere in a single test.
# In Neutron several tests use stop for each patched object,
# so we need to take care of both cases.
if use_stop:
self.addCleanup(self.plugin_notifier_p.stop)
self.addCleanup(self.ofc_manager_p.stop)
def setup_nec_plugin_base(self, use_stop_all=True,
use_stop_each=False):
# If use_stop_each is set, use_stop_all cannot be set.
if use_stop_all and not use_stop_each:
self.addCleanup(mock.patch.stopall)
self._set_nec_ini()
self.addCleanup(self._clean_nec_ini)
self.patch_remote_calls(use_stop_each)
class NecPluginV2TestCase(NecPluginV2TestCaseBase,
test_plugin.NeutronDbPluginV2TestCase):
_plugin_name = PLUGIN_NAME
def rpcapi_update_ports(self, agent_id='nec-q-agent.fake',
datapath_id="0xabc", added=[], removed=[]):
kwargs = {'topic': topics.AGENT,
@ -348,6 +376,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
mock.call.create_ofc_network(ctx, self._tenant_id, net['id'],
net['name']),
mock.call.delete_ofc_network(ctx, net['id'], mock.ANY),
mock.call.exists_ofc_tenant(ctx, self._tenant_id),
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
]
self.ofc.assert_has_calls(expected)
@ -365,6 +394,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
mock.call.create_ofc_network(ctx, self._tenant_id, net['id'],
net['name']),
mock.call.delete_ofc_network(ctx, net['id'], mock.ANY),
mock.call.exists_ofc_tenant(ctx, self._tenant_id),
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
]
self.ofc.assert_has_calls(expected)
@ -389,6 +419,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
nets[1]['name']),
mock.call.delete_ofc_network(ctx, nets[1]['id'], mock.ANY),
mock.call.delete_ofc_network(ctx, nets[0]['id'], mock.ANY),
mock.call.exists_ofc_tenant(ctx, self._tenant_id),
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
]
self.ofc.assert_has_calls(expected)
@ -451,6 +482,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
mock.call.create_ofc_network(ctx, self._tenant_id, net['id'],
net['name']),
mock.call.delete_ofc_network(ctx, net['id'], mock.ANY),
mock.call.exists_ofc_tenant(ctx, self._tenant_id),
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
]
self.ofc.assert_has_calls(expected)
@ -478,6 +510,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
mock.call.exists_ofc_port(ctx, p1['id']),
mock.call.delete_ofc_network(ctx, net['id'], mock.ANY),
mock.call.exists_ofc_tenant(ctx, self._tenant_id),
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
]
self.ofc.assert_has_calls(expected)
@ -520,6 +553,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
mock.call.exists_ofc_port(ctx, p1['id']),
mock.call.delete_ofc_port(ctx, p1['id'], mock.ANY),
mock.call.delete_ofc_network(ctx, net['id'], mock.ANY),
mock.call.exists_ofc_tenant(ctx, self._tenant_id),
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
]
self.ofc.assert_has_calls(expected)
@ -550,6 +584,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
mock.call.exists_ofc_port(ctx, p['id']),
mock.call.delete_ofc_port(ctx, p['id'], mock.ANY),
mock.call.delete_ofc_network(ctx, net['id'], mock.ANY),
mock.call.exists_ofc_tenant(ctx, self._tenant_id),
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
]
self.ofc.assert_has_calls(expected)
@ -686,6 +721,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
mock.call.exists_ofc_port(ctx, p1['id']),
mock.call.delete_ofc_network(ctx, net['id'], mock.ANY),
mock.call.exists_ofc_tenant(ctx, self._tenant_id),
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
]
self.ofc.assert_has_calls(expected)

View File

@ -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)

View File

@ -49,6 +49,8 @@ class OFCManagerTestBase(base.BaseTestCase):
ndb.initialize()
self.addCleanup(ndb.clear_db)
self.ofc = ofc_manager.OFCManager()
# NOTE: enable_autocheck() is a feature of StubOFCDriver
self.ofc.driver.enable_autocheck()
self.ctx = context.get_admin_context()
self.addCleanup(mock.patch.stopall)
@ -160,6 +162,8 @@ class OFCManagerTest(OFCManagerTestBase):
self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_port', p))
get_portinfo.assert_called_once_with(mock.ANY, p)
class OFCManagerFilterTest(OFCManagerTestBase):
def testj_create_ofc_packet_filter(self):
"""test create ofc_filter."""
t, n, p, f, none = self.get_random_params()
@ -198,8 +202,92 @@ class OFCManagerTest(OFCManagerTestBase):
'ofc_packet_filter', f))
class OFCManagerRouterTest(OFCManagerTestBase):
def get_random_params(self):
tenant = uuidutils.generate_uuid()
router = uuidutils.generate_uuid()
network = uuidutils.generate_uuid()
return (tenant, router, network)
def test_create_ofc_router(self):
"""test create ofc_router"""
t, r, _n = self.get_random_params()
self.ofc.create_ofc_tenant(self.ctx, t)
self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_router', r))
self.ofc.create_ofc_router(self.ctx, t, r, 'test router')
self.assertTrue(ndb.get_ofc_item(self.ctx.session, 'ofc_router', r))
router = ndb.get_ofc_item(self.ctx.session, 'ofc_router', r)
self.assertEqual(router.ofc_id, "ofc-" + r[:-4])
def test_exists_ofc_router(self):
"""test exists_ofc_router"""
t, r, _n = self.get_random_params()
self.ofc.create_ofc_tenant(self.ctx, t)
self.assertFalse(self.ofc.exists_ofc_router(self.ctx, r))
self.ofc.create_ofc_router(self.ctx, t, r)
self.assertTrue(self.ofc.exists_ofc_router(self.ctx, r))
def test_delete_ofc_router(self):
"""test delete ofc_router"""
t, r, _n = self.get_random_params()
self.ofc.create_ofc_tenant(self.ctx, t)
self.ofc.create_ofc_router(self.ctx, t, r)
self.assertTrue(ndb.get_ofc_item(self.ctx.session, 'ofc_router', r))
self.ofc.delete_ofc_router(self.ctx, r, {'tenant_id': t})
self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_network', r))
def test_router_interface(self):
t, r, n = self.get_random_params()
self.ofc.create_ofc_tenant(self.ctx, t)
self.ofc.create_ofc_router(self.ctx, t, r)
self.ofc.create_ofc_network(self.ctx, t, n)
self.assertTrue(ndb.get_ofc_item(self.ctx.session, 'ofc_router', r))
self.assertTrue(ndb.get_ofc_item(self.ctx.session, 'ofc_network', n))
p = {'id': uuidutils.generate_uuid(),
'network_id': n, 'ip_address': '10.1.1.1', 'cidr': '10.1.0.0/20',
'mac_address': '11:22:33:44:55:66'}
self.ofc.add_ofc_router_interface(self.ctx, r, p['id'], p)
self.assertTrue(ndb.get_ofc_item(self.ctx.session,
'ofc_port', p['id']))
self.ofc.delete_ofc_router_interface(self.ctx, r, p['id'])
self.assertFalse(ndb.get_ofc_item(self.ctx.session,
'ofc_port', p['id']))
self.ofc.delete_ofc_router(self.ctx, r, {'tenant_id': t})
self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_network', r))
def test_router_route(self):
t, r, _n = self.get_random_params()
self.ofc.create_ofc_tenant(self.ctx, t)
self.ofc.create_ofc_router(self.ctx, t, r)
self.assertTrue(ndb.get_ofc_item(self.ctx.session, 'ofc_router', r))
routes = [{'destination': '2.2.2.0/24', 'nexthop': '1.1.1.10'}]
self.ofc.update_ofc_router_route(self.ctx, r, routes)
self.assertEqual(len(self.ofc.driver.ofc_router_route_dict), 1)
routes = [{'destination': '3.3.3.0/24', 'nexthop': '1.1.1.11'},
{'destination': '4.4.4.0/24', 'nexthop': '1.1.1.11'}]
self.ofc.update_ofc_router_route(self.ctx, r, routes)
self.assertEqual(len(self.ofc.driver.ofc_router_route_dict), 2)
routes = [{'destination': '2.2.2.0/24', 'nexthop': '1.1.1.10'}]
self.ofc.update_ofc_router_route(self.ctx, r, routes)
self.assertEqual(len(self.ofc.driver.ofc_router_route_dict), 1)
routes = []
self.ofc.update_ofc_router_route(self.ctx, r, routes)
self.assertEqual(len(self.ofc.driver.ofc_router_route_dict), 0)
class OFCManagerTestWithOldMapping(OFCManagerTestBase):
def setUp(self):
super(OFCManagerTestWithOldMapping, self).setUp()
# NOTE(amotoki): In OldMapping tests, DB entries are directly modified
# to create a case where the old mapping tables are used intentionally.
self.ofc.driver.disable_autocheck()
def test_exists_ofc_tenant(self):
t, n, p, f, none = self.get_random_params()
ofc_t, ofc_n, ofc_p, ofc_f, ofc_none = self.get_random_params()

View File

@ -19,6 +19,7 @@ import random
import string
import mox
import netaddr
from neutron import context
from neutron.openstack.common import uuidutils
@ -202,6 +203,167 @@ class PFCV4DriverTest(PFCDriverTestBase):
driver = 'pfc_v4'
class PFCV5DriverTest(PFCDriverTestBase):
driver = 'pfc_v5'
def test_create_router(self):
t = uuidutils.generate_uuid()
r = uuidutils.generate_uuid()
description = 'dummy_router_desc'
tenant_path = "/tenants/%s" % _ofc(t)
post_path = "%s/routers" % tenant_path
router = {'id': _ofc(r)}
ofc.OFCClient.do_request("POST", post_path,
body=None).AndReturn(router)
self.mox.ReplayAll()
ret = self.driver.create_router(tenant_path, description, r)
self.mox.VerifyAll()
router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
self.assertEqual(ret, router_path)
def test_delete_router(self):
t = uuidutils.generate_uuid()
r = uuidutils.generate_uuid()
router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
ofc.OFCClient.do_request("DELETE", router_path)
self.mox.ReplayAll()
self.driver.delete_router(router_path)
self.mox.VerifyAll()
def test_add_router_interface(self):
t = uuidutils.generate_uuid()
r = uuidutils.generate_uuid()
n = uuidutils.generate_uuid()
p = uuidutils.generate_uuid()
router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
infs_path = router_path + "/interfaces"
net_path = "/tenants/%s/networks/%s" % (_ofc(t), _ofc(n))
ip_address = '10.1.1.1/24'
mac_address = '11:22:33:44:55:66'
body = {'net_id': _ofc(n),
'ip_address': ip_address,
'mac_address': mac_address}
inf = {'id': _ofc(p)}
ofc.OFCClient.do_request("POST", infs_path,
body=body).AndReturn(inf)
self.mox.ReplayAll()
ret = self.driver.add_router_interface(router_path, net_path,
ip_address, mac_address)
self.mox.VerifyAll()
inf_path = "%s/interfaces/%s" % (router_path, _ofc(p))
self.assertEqual(ret, inf_path)
def test_update_router_interface(self):
t = uuidutils.generate_uuid()
r = uuidutils.generate_uuid()
p = uuidutils.generate_uuid()
router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
inf_path = "%s/interfaces/%s" % (router_path, _ofc(p))
ip_address = '10.1.1.1/24'
mac_address = '11:22:33:44:55:66'
body = {'ip_address': ip_address,
'mac_address': mac_address}
ofc.OFCClient.do_request("PUT", inf_path, body=body)
body = {'ip_address': ip_address}
ofc.OFCClient.do_request("PUT", inf_path, body=body)
body = {'mac_address': mac_address}
ofc.OFCClient.do_request("PUT", inf_path, body=body)
self.mox.ReplayAll()
self.driver.update_router_interface(inf_path, ip_address, mac_address)
self.driver.update_router_interface(inf_path, ip_address=ip_address)
self.driver.update_router_interface(inf_path, mac_address=mac_address)
self.mox.VerifyAll()
def test_delete_router_interface(self):
t = uuidutils.generate_uuid()
r = uuidutils.generate_uuid()
p = uuidutils.generate_uuid()
router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
inf_path = "%s/interfaces/%s" % (router_path, _ofc(p))
ofc.OFCClient.do_request("DELETE", inf_path)
self.mox.ReplayAll()
self.driver.delete_router_interface(inf_path)
self.mox.VerifyAll()
def _get_route_id(self, dest, nexthop):
dest = netaddr.IPNetwork(dest)
return '-'.join([str(dest.network), nexthop, str(dest.netmask)])
def test_add_router_route(self):
t = uuidutils.generate_uuid()
r = uuidutils.generate_uuid()
router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
routes_path = router_path + "/routes"
dest = '10.1.1.0/24'
nexthop = '192.168.100.10'
body = {'destination': dest, 'nexthop': nexthop}
route_id = self._get_route_id(dest, nexthop)
ofc.OFCClient.do_request("POST", routes_path,
body=body).AndReturn({'id': route_id})
self.mox.ReplayAll()
ret = self.driver.add_router_route(router_path, '10.1.1.0/24',
'192.168.100.10')
self.mox.VerifyAll()
route_path = routes_path + '/' + route_id
self.assertEqual(ret, route_path)
def test_delete_router_route(self):
t = uuidutils.generate_uuid()
r = uuidutils.generate_uuid()
router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
routes_path = router_path + "/routes"
route_id = self._get_route_id('10.1.1.0/24', '192.168.100.10')
route_path = routes_path + '/' + route_id
ofc.OFCClient.do_request("DELETE", route_path)
self.mox.ReplayAll()
self.driver.delete_router_route(route_path)
self.mox.VerifyAll()
def test_list_router_routes(self):
t = uuidutils.generate_uuid()
r = uuidutils.generate_uuid()
router_path = "/tenants/%s/routers/%s" % (_ofc(t), _ofc(r))
routes_path = router_path + "/routes"
routes = [('10.1.1.0/24', '192.168.100.10'),
('10.2.2.0/20', '192.168.100.20')]
data = {'routes': [{'id': self._get_route_id(route[0], route[1]),
'destination': route[0], 'nexthop': route[1]}
for route in routes]}
ofc.OFCClient.do_request("GET", routes_path).AndReturn(data)
self.mox.ReplayAll()
ret = self.driver.list_router_routes(router_path)
self.mox.VerifyAll()
expected = [{'id': (routes_path + "/" +
self._get_route_id(route[0], route[1])),
'destination': route[0], 'nexthop': route[1]}
for route in routes]
self.assertEqual(len(routes), len(ret))
self.assertEqual(data['routes'], expected)
class PFCDriverStringTest(base.BaseTestCase):
driver = 'neutron.plugins.nec.drivers.pfc.PFCDriverBase'

View File

@ -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()

View File

@ -189,7 +189,7 @@ class AgentSchedulerTestMixIn(object):
return agent_data['id']
class OvsAgentSchedulerTestCase(test_l3_plugin.L3NatTestCaseMixin,
class OvsAgentSchedulerTestCaseBase(test_l3_plugin.L3NatTestCaseMixin,
test_agent_ext_plugin.AgentDBTestMixIn,
AgentSchedulerTestMixIn,
test_plugin.NeutronDbPluginV2TestCase):
@ -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()

View File

@ -59,10 +59,11 @@ class TestExtraRoutePlugin(test_l3.TestL3NatPlugin,
class ExtraRouteDBTestCase(test_l3.L3NatDBTestCase):
def setUp(self):
test_config['plugin_name_v2'] = (
'neutron.tests.unit.'
'test_extension_extraroute.TestExtraRoutePlugin')
def setUp(self, plugin=None):
if not plugin:
plugin = ('neutron.tests.unit.test_extension_extraroute.'
'TestExtraRoutePlugin')
test_config['plugin_name_v2'] = plugin
# for these tests we need to enable overlapping ips
cfg.CONF.set_default('allow_overlapping_ips', True)
cfg.CONF.set_default('max_routes', 3)

View File

@ -28,7 +28,6 @@ from webob import exc
import webtest
from neutron.api import extensions
from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
from neutron.api.v2 import attributes
from neutron.common import config
from neutron.common import constants as l3_constants
@ -321,13 +320,15 @@ class L3NatTestCaseMixin(object):
return router_req.get_response(self.ext_api)
def _make_router(self, fmt, tenant_id, name=None, admin_state_up=None,
external_gateway_info=None, set_context=False):
arg_list = (external_gateway_info and
('external_gateway_info', ) or None)
external_gateway_info=None, set_context=False,
arg_list=None, **kwargs):
if external_gateway_info:
arg_list = ('external_gateway_info', ) + (arg_list or ())
res = self._create_router(fmt, tenant_id, name,
admin_state_up, set_context,
arg_list=arg_list,
external_gateway_info=external_gateway_info)
external_gateway_info=external_gateway_info,
**kwargs)
return self.deserialize(fmt, res)
def _add_external_gateway_to_router(self, router_id, network_id,
@ -367,10 +368,11 @@ class L3NatTestCaseMixin(object):
@contextlib.contextmanager
def router(self, name='router1', admin_state_up=True,
fmt=None, tenant_id=_uuid(),
external_gateway_info=None, set_context=False):
external_gateway_info=None, set_context=False,
**kwargs):
router = self._make_router(fmt or self.fmt, tenant_id, name,
admin_state_up, external_gateway_info,
set_context)
set_context, **kwargs)
try:
yield router
finally:
@ -1673,18 +1675,19 @@ class L3AgentDbTestCase(L3NatTestCaseBase):
def _test_notify_op_agent(self, target_func, *args):
l3_rpc_agent_api_str = (
'neutron.api.rpc.agentnotifiers.l3_rpc_agent_api.L3AgentNotifyAPI')
oldNotify = l3_rpc_agent_api.L3AgentNotify
plugin = NeutronManager.get_plugin()
oldNotify = plugin.l3_rpc_notifier
try:
with mock.patch(l3_rpc_agent_api_str) as notifyApi:
l3_rpc_agent_api.L3AgentNotify = notifyApi
plugin.l3_rpc_notifier = notifyApi
kargs = [item for item in args]
kargs.append(notifyApi)
target_func(*kargs)
except Exception:
l3_rpc_agent_api.L3AgentNotify = oldNotify
plugin.l3_rpc_notifier = oldNotify
raise
else:
l3_rpc_agent_api.L3AgentNotify = oldNotify
plugin.l3_rpc_notifier = oldNotify
def _test_router_gateway_op_agent(self, notifyApi):
with self.router() as r: