Merge "OpenFlow distributed router support in NEC plugin"
This commit is contained in:
commit
42cfd92556
etc/neutron/plugins/nec
neutron
db
plugins/nec
tests/unit
@ -41,3 +41,9 @@ firewall_driver = neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewal
|
|||||||
|
|
||||||
# Certificate file
|
# Certificate file
|
||||||
# cert_file =
|
# cert_file =
|
||||||
|
|
||||||
|
[provider]
|
||||||
|
# Default router provider to use.
|
||||||
|
# default_router_provider = l3-agent
|
||||||
|
# List of enabled router providers.
|
||||||
|
# router_providers = l3-agent,openflow
|
||||||
|
@ -91,6 +91,8 @@ class FloatingIP(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
|
|||||||
class L3_NAT_db_mixin(l3.RouterPluginBase):
|
class L3_NAT_db_mixin(l3.RouterPluginBase):
|
||||||
"""Mixin class to add L3/NAT router methods to db_plugin_base_v2."""
|
"""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):
|
def _network_model_hook(self, context, original_model, query):
|
||||||
query = query.outerjoin(ExternalNetwork,
|
query = query.outerjoin(ExternalNetwork,
|
||||||
(original_model.id ==
|
(original_model.id ==
|
||||||
@ -186,7 +188,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
|||||||
# Ensure we actually have something to update
|
# Ensure we actually have something to update
|
||||||
if r.keys():
|
if r.keys():
|
||||||
router_db.update(r)
|
router_db.update(r)
|
||||||
l3_rpc_agent_api.L3AgentNotify.routers_updated(
|
self.l3_rpc_notifier.routers_updated(
|
||||||
context, [router_db['id']])
|
context, [router_db['id']])
|
||||||
return self._make_router_dict(router_db)
|
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'])
|
self._delete_port(context.elevated(), ports[0]['id'])
|
||||||
|
|
||||||
context.session.delete(router)
|
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):
|
def get_router(self, context, id, fields=None):
|
||||||
router = self._get_router(context, id)
|
router = self._get_router(context, id)
|
||||||
@ -385,7 +387,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
|||||||
'device_owner': DEVICE_OWNER_ROUTER_INTF,
|
'device_owner': DEVICE_OWNER_ROUTER_INTF,
|
||||||
'name': ''}})
|
'name': ''}})
|
||||||
|
|
||||||
l3_rpc_agent_api.L3AgentNotify.routers_updated(
|
self.l3_rpc_notifier.routers_updated(
|
||||||
context, [router_id], 'add_router_interface')
|
context, [router_id], 'add_router_interface')
|
||||||
info = {'id': router_id,
|
info = {'id': router_id,
|
||||||
'tenant_id': subnet['tenant_id'],
|
'tenant_id': subnet['tenant_id'],
|
||||||
@ -457,7 +459,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
|||||||
if not found:
|
if not found:
|
||||||
raise l3.RouterInterfaceNotFoundForSubnet(router_id=router_id,
|
raise l3.RouterInterfaceNotFoundForSubnet(router_id=router_id,
|
||||||
subnet_id=subnet_id)
|
subnet_id=subnet_id)
|
||||||
l3_rpc_agent_api.L3AgentNotify.routers_updated(
|
self.l3_rpc_notifier.routers_updated(
|
||||||
context, [router_id], 'remove_router_interface')
|
context, [router_id], 'remove_router_interface')
|
||||||
info = {'id': router_id,
|
info = {'id': router_id,
|
||||||
'tenant_id': subnet['tenant_id'],
|
'tenant_id': subnet['tenant_id'],
|
||||||
@ -670,7 +672,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
|||||||
|
|
||||||
router_id = floatingip_db['router_id']
|
router_id = floatingip_db['router_id']
|
||||||
if router_id:
|
if router_id:
|
||||||
l3_rpc_agent_api.L3AgentNotify.routers_updated(
|
self.l3_rpc_notifier.routers_updated(
|
||||||
context, [router_id],
|
context, [router_id],
|
||||||
'create_floatingip')
|
'create_floatingip')
|
||||||
return self._make_floatingip_dict(floatingip_db)
|
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:
|
if router_id and router_id != before_router_id:
|
||||||
router_ids.append(router_id)
|
router_ids.append(router_id)
|
||||||
if router_ids:
|
if router_ids:
|
||||||
l3_rpc_agent_api.L3AgentNotify.routers_updated(context, router_ids,
|
self.l3_rpc_notifier.routers_updated(
|
||||||
'update_floatingip')
|
context, router_ids, 'update_floatingip')
|
||||||
return self._make_floatingip_dict(floatingip_db)
|
return self._make_floatingip_dict(floatingip_db)
|
||||||
|
|
||||||
def delete_floatingip(self, context, id):
|
def delete_floatingip(self, context, id):
|
||||||
@ -706,7 +708,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
|||||||
floatingip['floating_port_id'],
|
floatingip['floating_port_id'],
|
||||||
l3_port_check=False)
|
l3_port_check=False)
|
||||||
if router_id:
|
if router_id:
|
||||||
l3_rpc_agent_api.L3AgentNotify.routers_updated(
|
self.l3_rpc_notifier.routers_updated(
|
||||||
context, [router_id],
|
context, [router_id],
|
||||||
'delete_floatingip')
|
'delete_floatingip')
|
||||||
|
|
||||||
@ -777,7 +779,7 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
|||||||
raise Exception(_('Multiple floating IPs found for port %s')
|
raise Exception(_('Multiple floating IPs found for port %s')
|
||||||
% port_id)
|
% port_id)
|
||||||
if router_id:
|
if router_id:
|
||||||
l3_rpc_agent_api.L3AgentNotify.routers_updated(
|
self.l3_rpc_notifier.routers_updated(
|
||||||
context, [router_id])
|
context, [router_id])
|
||||||
|
|
||||||
def _network_is_external(self, context, net_id):
|
def _network_is_external(self, context, net_id):
|
||||||
|
@ -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')
|
@ -19,6 +19,7 @@ from oslo.config import cfg
|
|||||||
|
|
||||||
from neutron.agent.common import config
|
from neutron.agent.common import config
|
||||||
from neutron.openstack.common import rpc # noqa
|
from neutron.openstack.common import rpc # noqa
|
||||||
|
from neutron.plugins.nec.common import constants as nconst
|
||||||
|
|
||||||
|
|
||||||
ovs_opts = [
|
ovs_opts = [
|
||||||
@ -49,10 +50,20 @@ ofc_opts = [
|
|||||||
help=_("Certificate file")),
|
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(ovs_opts, "OVS")
|
||||||
cfg.CONF.register_opts(agent_opts, "AGENT")
|
cfg.CONF.register_opts(agent_opts, "AGENT")
|
||||||
cfg.CONF.register_opts(ofc_opts, "OFC")
|
cfg.CONF.register_opts(ofc_opts, "OFC")
|
||||||
|
cfg.CONF.register_opts(provider_opts, "PROVIDER")
|
||||||
config.register_agent_state_opts_helper(cfg.CONF)
|
config.register_agent_state_opts_helper(cfg.CONF)
|
||||||
config.register_root_helper(cfg.CONF)
|
config.register_root_helper(cfg.CONF)
|
||||||
|
|
||||||
@ -61,3 +72,4 @@ CONF = cfg.CONF
|
|||||||
OVS = cfg.CONF.OVS
|
OVS = cfg.CONF.OVS
|
||||||
AGENT = cfg.CONF.AGENT
|
AGENT = cfg.CONF.AGENT
|
||||||
OFC = cfg.CONF.OFC
|
OFC = cfg.CONF.OFC
|
||||||
|
PROVIDER = cfg.CONF.PROVIDER
|
||||||
|
24
neutron/plugins/nec/common/constants.py
Normal file
24
neutron/plugins/nec/common/constants.py
Normal 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'
|
@ -21,6 +21,12 @@ from neutron.common import exceptions as qexc
|
|||||||
class OFCException(qexc.NeutronException):
|
class OFCException(qexc.NeutronException):
|
||||||
message = _("An OFC exception has occurred: %(reason)s")
|
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):
|
class NECDBException(qexc.NeutronException):
|
||||||
message = _("An exception occurred in NECPluginV2 DB: %(reason)s")
|
message = _("An exception occurred in NECPluginV2 DB: %(reason)s")
|
||||||
@ -44,3 +50,22 @@ class ProfilePortInfoInvalidDataPathId(qexc.InvalidInput):
|
|||||||
class ProfilePortInfoInvalidPortNo(qexc.InvalidInput):
|
class ProfilePortInfoInvalidPortNo(qexc.InvalidInput):
|
||||||
message = _('Invalid input for operation: '
|
message = _('Invalid input for operation: '
|
||||||
'portinfo:port_no should be [0:65535]')
|
'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.")
|
||||||
|
@ -46,12 +46,26 @@ class OFCClient(object):
|
|||||||
self.cert_file = cert_file
|
self.cert_file = cert_file
|
||||||
self.connection = None
|
self.connection = None
|
||||||
|
|
||||||
def get_connection_type(self):
|
def get_connection(self):
|
||||||
"""Returns the proper connection type."""
|
"""Returns the proper connection."""
|
||||||
if self.use_ssl:
|
if self.use_ssl:
|
||||||
return httplib.HTTPSConnection
|
connection_type = httplib.HTTPSConnection
|
||||||
else:
|
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):
|
def do_request(self, method, action, body=None):
|
||||||
LOG.debug(_("Client request: %(host)s:%(port)s "
|
LOG.debug(_("Client request: %(host)s:%(port)s "
|
||||||
@ -61,32 +75,40 @@ class OFCClient(object):
|
|||||||
if type(body) is dict:
|
if type(body) is dict:
|
||||||
body = json.dumps(body)
|
body = json.dumps(body)
|
||||||
try:
|
try:
|
||||||
connection_type = self.get_connection_type()
|
conn = self.get_connection()
|
||||||
headers = {"Content-Type": "application/json"}
|
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)
|
conn.request(method, action, body, headers)
|
||||||
res = conn.getresponse()
|
res = conn.getresponse()
|
||||||
data = res.read()
|
data = res.read()
|
||||||
LOG.debug(_("OFC returns [%(status)s:%(data)s]"),
|
LOG.debug(_("OFC returns [%(status)s:%(data)s]"),
|
||||||
{'status': res.status,
|
{'status': res.status,
|
||||||
'data': data})
|
'data': data})
|
||||||
|
|
||||||
|
# Try to decode JSON data if possible.
|
||||||
|
try:
|
||||||
|
data = json.loads(data)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
if res.status in (httplib.OK,
|
if res.status in (httplib.OK,
|
||||||
httplib.CREATED,
|
httplib.CREATED,
|
||||||
httplib.ACCEPTED,
|
httplib.ACCEPTED,
|
||||||
httplib.NO_CONTENT):
|
httplib.NO_CONTENT):
|
||||||
if data and len(data) > 1:
|
return data
|
||||||
return json.loads(data)
|
|
||||||
else:
|
else:
|
||||||
reason = _("An operation on OFC is failed.")
|
LOG.warning(_("Operation on OFC failed: "
|
||||||
raise nexc.OFCException(reason=reason)
|
"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:
|
except (socket.error, IOError) as e:
|
||||||
reason = _("Failed to connect OFC : %s") % str(e)
|
reason = _("Failed to connect OFC : %s") % e
|
||||||
LOG.error(reason)
|
LOG.error(reason)
|
||||||
raise nexc.OFCException(reason=reason)
|
raise nexc.OFCException(reason=reason)
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ OFP_VLAN_NONE = 0xffff
|
|||||||
resource_map = {'ofc_tenant': nmodels.OFCTenantMapping,
|
resource_map = {'ofc_tenant': nmodels.OFCTenantMapping,
|
||||||
'ofc_network': nmodels.OFCNetworkMapping,
|
'ofc_network': nmodels.OFCNetworkMapping,
|
||||||
'ofc_port': nmodels.OFCPortMapping,
|
'ofc_port': nmodels.OFCPortMapping,
|
||||||
|
'ofc_router': nmodels.OFCRouterMapping,
|
||||||
'ofc_packet_filter': nmodels.OFCFilterMapping}
|
'ofc_packet_filter': nmodels.OFCFilterMapping}
|
||||||
|
|
||||||
old_resource_map = {'ofc_tenant': nmodels.OFCTenant,
|
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):
|
def _get_resource_model(resource, old_style):
|
||||||
if 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:
|
else:
|
||||||
return resource_map[resource]
|
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):
|
def get_ofc_item(session, resource, neutron_id, old_style=False):
|
||||||
try:
|
|
||||||
model = _get_resource_model(resource, old_style)
|
model = _get_resource_model(resource, old_style)
|
||||||
|
if not model:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
return session.query(model).filter_by(quantum_id=neutron_id).one()
|
return session.query(model).filter_by(quantum_id=neutron_id).one()
|
||||||
except sa.orm.exc.NoResultFound:
|
except sa.orm.exc.NoResultFound:
|
||||||
return None
|
return None
|
||||||
|
@ -47,6 +47,10 @@ class OFCPortMapping(model_base.BASEV2, NeutronId, OFCId):
|
|||||||
"""Represents a Port on OpenFlow Network/Controller."""
|
"""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):
|
class OFCFilterMapping(model_base.BASEV2, NeutronId, OFCId):
|
||||||
"""Represents a Filter on OpenFlow Network/Controller."""
|
"""Represents a Filter on OpenFlow Network/Controller."""
|
||||||
|
|
||||||
|
92
neutron/plugins/nec/db/router.py
Normal file
92
neutron/plugins/nec/db/router.py
Normal 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
|
@ -28,7 +28,9 @@ DRIVER_LIST = {
|
|||||||
'trema_mac': DRIVER_PATH % "trema.TremaMACBaseDriver",
|
'trema_mac': DRIVER_PATH % "trema.TremaMACBaseDriver",
|
||||||
'pfc': DRIVER_PATH % "pfc.PFCV4Driver",
|
'pfc': DRIVER_PATH % "pfc.PFCV4Driver",
|
||||||
'pfc_v3': DRIVER_PATH % "pfc.PFCV3Driver",
|
'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):
|
def get_driver(driver_name):
|
||||||
|
@ -33,6 +33,8 @@ class PFCDriverBase(ofc_driver_base.OFCDriverBase):
|
|||||||
The class implements the API for PFC V4.0 or later.
|
The class implements the API for PFC V4.0 or later.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
router_supported = False
|
||||||
|
|
||||||
def __init__(self, conf_ofc):
|
def __init__(self, conf_ofc):
|
||||||
self.client = ofc_client.OFCClient(host=conf_ofc.host,
|
self.client = ofc_client.OFCClient(host=conf_ofc.host,
|
||||||
port=conf_ofc.port,
|
port=conf_ofc.port,
|
||||||
@ -73,6 +75,10 @@ class PFCDriverBase(ofc_driver_base.OFCDriverBase):
|
|||||||
"""
|
"""
|
||||||
return self._generate_pfc_str(desc)[:127]
|
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):
|
def create_tenant(self, description, tenant_id=None):
|
||||||
ofc_tenant_id = self._generate_pfc_id(tenant_id)
|
ofc_tenant_id = self._generate_pfc_id(tenant_id)
|
||||||
body = {'id': ofc_tenant_id}
|
body = {'id': ofc_tenant_id}
|
||||||
@ -136,6 +142,66 @@ class PFCDriverBase(ofc_driver_base.OFCDriverBase):
|
|||||||
return '%(network)s/ports/%(port)s' % params
|
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):
|
class PFCV3Driver(PFCDriverBase):
|
||||||
|
|
||||||
def create_tenant(self, description, tenant_id):
|
def create_tenant(self, description, tenant_id):
|
||||||
@ -148,3 +214,7 @@ class PFCV3Driver(PFCDriverBase):
|
|||||||
|
|
||||||
class PFCV4Driver(PFCDriverBase):
|
class PFCV4Driver(PFCDriverBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PFCV5Driver(PFCRouterDriverMixin, PFCDriverBase):
|
||||||
|
pass
|
||||||
|
@ -27,6 +27,8 @@ class TremaDriverBase(ofc_driver_base.OFCDriverBase):
|
|||||||
networks_path = "/networks"
|
networks_path = "/networks"
|
||||||
network_path = "/networks/%s"
|
network_path = "/networks/%s"
|
||||||
|
|
||||||
|
router_supported = False
|
||||||
|
|
||||||
def __init__(self, conf_ofc):
|
def __init__(self, conf_ofc):
|
||||||
# Trema sliceable REST API does not support HTTPS
|
# Trema sliceable REST API does not support HTTPS
|
||||||
self.client = ofc_client.OFCClient(host=conf_ofc.host,
|
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
|
return self.network_path % ofc_network_id
|
||||||
|
|
||||||
|
|
||||||
class TremaFilterDriver(object):
|
class TremaFilterDriverMixin(object):
|
||||||
"""Trema (Sliceable Switch) PacketFilter Driver Mixin."""
|
"""Trema (Sliceable Switch) PacketFilter Driver Mixin."""
|
||||||
filters_path = "/filters"
|
filters_path = "/filters"
|
||||||
filter_path = "/filters/%s"
|
filter_path = "/filters/%s"
|
||||||
@ -173,7 +175,7 @@ class TremaFilterDriver(object):
|
|||||||
return self.filter_path % ofc_filter_id
|
return self.filter_path % ofc_filter_id
|
||||||
|
|
||||||
|
|
||||||
class TremaPortBaseDriver(TremaDriverBase, TremaFilterDriver):
|
class TremaPortBaseDriver(TremaDriverBase, TremaFilterDriverMixin):
|
||||||
"""Trema (Sliceable Switch) Driver for port base binding.
|
"""Trema (Sliceable Switch) Driver for port base binding.
|
||||||
|
|
||||||
TremaPortBaseDriver uses port base binding.
|
TremaPortBaseDriver uses port base binding.
|
||||||
@ -211,7 +213,7 @@ class TremaPortBaseDriver(TremaDriverBase, TremaFilterDriver):
|
|||||||
'port': ofc_port_id}
|
'port': ofc_port_id}
|
||||||
|
|
||||||
|
|
||||||
class TremaPortMACBaseDriver(TremaDriverBase, TremaFilterDriver):
|
class TremaPortMACBaseDriver(TremaDriverBase, TremaFilterDriverMixin):
|
||||||
"""Trema (Sliceable Switch) Driver for port-mac base binding.
|
"""Trema (Sliceable Switch) Driver for port-mac base binding.
|
||||||
|
|
||||||
TremaPortBaseDriver uses port-mac base binding.
|
TremaPortBaseDriver uses port-mac base binding.
|
||||||
|
61
neutron/plugins/nec/extensions/router_provider.py
Normal file
61
neutron/plugins/nec/extensions/router_provider.py
Normal 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 {}
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
from neutron.agent import securitygroups_rpc as sg_rpc
|
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 dhcp_rpc_agent_api
|
||||||
from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
|
|
||||||
from neutron.api.v2 import attributes as attrs
|
from neutron.api.v2 import attributes as attrs
|
||||||
from neutron.common import constants as const
|
from neutron.common import constants as const
|
||||||
from neutron.common import exceptions as q_exc
|
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 agentschedulers_db
|
||||||
from neutron.db import db_base_plugin_v2
|
from neutron.db import db_base_plugin_v2
|
||||||
from neutron.db import dhcp_rpc_base
|
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 l3_rpc_base
|
||||||
from neutron.db import portbindings_base
|
from neutron.db import portbindings_base
|
||||||
from neutron.db import portbindings_db
|
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 config
|
||||||
from neutron.plugins.nec.common import exceptions as nexc
|
from neutron.plugins.nec.common import exceptions as nexc
|
||||||
from neutron.plugins.nec.db import api as ndb
|
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 ofc_manager
|
||||||
from neutron.plugins.nec import packet_filter
|
from neutron.plugins.nec import packet_filter
|
||||||
|
|
||||||
@ -51,11 +50,10 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
|
class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
|
||||||
extraroute_db.ExtraRoute_db_mixin,
|
nec_router.RouterMixin,
|
||||||
l3_gwmode_db.L3_NAT_db_mixin,
|
|
||||||
sg_db_rpc.SecurityGroupServerRpcMixin,
|
sg_db_rpc.SecurityGroupServerRpcMixin,
|
||||||
agentschedulers_db.L3AgentSchedulerDbMixin,
|
|
||||||
agentschedulers_db.DhcpAgentSchedulerDbMixin,
|
agentschedulers_db.DhcpAgentSchedulerDbMixin,
|
||||||
|
nec_router.L3AgentSchedulerDbMixin,
|
||||||
packet_filter.PacketFilterMixin,
|
packet_filter.PacketFilterMixin,
|
||||||
portbindings_db.PortBindingMixin):
|
portbindings_db.PortBindingMixin):
|
||||||
"""NECPluginV2 controls an OpenFlow Controller.
|
"""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
|
The port binding extension enables an external application relay
|
||||||
information to and from the plugin.
|
information to and from the plugin.
|
||||||
"""
|
"""
|
||||||
_supported_extension_aliases = ["router", "ext-gw-mode", "quotas",
|
_supported_extension_aliases = ["agent",
|
||||||
"binding", "security-group",
|
"binding",
|
||||||
"extraroute", "agent",
|
|
||||||
"l3_agent_scheduler",
|
|
||||||
"dhcp_agent_scheduler",
|
"dhcp_agent_scheduler",
|
||||||
"packet-filter"]
|
"ext-gw-mode",
|
||||||
|
"extraroute",
|
||||||
|
"l3_agent_scheduler",
|
||||||
|
"packet-filter",
|
||||||
|
"quotas",
|
||||||
|
"router",
|
||||||
|
"router_provider",
|
||||||
|
"security-group",
|
||||||
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_extension_aliases(self):
|
def supported_extension_aliases(self):
|
||||||
@ -99,6 +103,7 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
|
|||||||
'neutron/plugins/nec/extensions')
|
'neutron/plugins/nec/extensions')
|
||||||
|
|
||||||
self.setup_rpc()
|
self.setup_rpc()
|
||||||
|
self.l3_rpc_notifier = nec_router.L3AgentNotifyAPI()
|
||||||
|
|
||||||
self.network_scheduler = importutils.import_object(
|
self.network_scheduler = importutils.import_object(
|
||||||
config.CONF.network_scheduler_driver
|
config.CONF.network_scheduler_driver
|
||||||
@ -107,6 +112,20 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
|
|||||||
config.CONF.router_scheduler_driver
|
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):
|
def setup_rpc(self):
|
||||||
self.topic = topics.PLUGIN
|
self.topic = topics.PLUGIN
|
||||||
self.conn = rpc.create_connection(new=True)
|
self.conn = rpc.create_connection(new=True)
|
||||||
@ -115,13 +134,14 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
|
|||||||
dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
|
dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
|
||||||
)
|
)
|
||||||
self.agent_notifiers[const.AGENT_TYPE_L3] = (
|
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.
|
# NOTE: callback_sg is referred to from the sg unit test.
|
||||||
self.callback_sg = SecurityGroupServerRpcCallback()
|
self.callback_sg = SecurityGroupServerRpcCallback()
|
||||||
callbacks = [NECPluginV2RPCCallbacks(self),
|
callbacks = [NECPluginV2RPCCallbacks(self),
|
||||||
DhcpRpcCallback(), L3RpcCallback(),
|
DhcpRpcCallback(),
|
||||||
|
L3RpcCallback(),
|
||||||
self.callback_sg,
|
self.callback_sg,
|
||||||
agents_db.AgentExtRpcCallback()]
|
agents_db.AgentExtRpcCallback()]
|
||||||
self.dispatcher = q_rpc.PluginRpcDispatcher(callbacks)
|
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):
|
def _update_resource_status(self, context, resource, id, status):
|
||||||
"""Update status of specified resource."""
|
"""Update status of specified resource."""
|
||||||
request = {}
|
request = {'status': status}
|
||||||
request[resource] = dict(status=status)
|
obj_getter = getattr(self, '_get_%s' % resource)
|
||||||
obj_updater = getattr(super(NECPluginV2, self), "update_%s" % resource)
|
with context.session.begin(subtransactions=True):
|
||||||
obj_updater(context, id, request)
|
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):
|
def activate_port_if_ready(self, context, port, network=None):
|
||||||
"""Activate port by creating port on OFC if ready.
|
"""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'])
|
self.delete_packet_filter(context, pf['id'])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 'net' parameter is required to lookup old OFC mapping
|
|
||||||
self.ofc.delete_ofc_network(context, id, net)
|
self.ofc.delete_ofc_network(context, id, net)
|
||||||
except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
|
except (nexc.OFCException, nexc.OFCConsistencyBroken) as exc:
|
||||||
reason = _("delete_network() failed due to %s") % 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)
|
super(NECPluginV2, self).delete_network(context, id)
|
||||||
|
|
||||||
# delete unnessary ofc_tenant
|
self._cleanup_ofc_tenant(context, tenant_id)
|
||||||
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)
|
|
||||||
|
|
||||||
def _get_base_binding_dict(self):
|
def _get_base_binding_dict(self):
|
||||||
binding = {
|
binding = {
|
||||||
@ -449,6 +485,14 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
|
|||||||
context, port_data, port)
|
context, port_data, port)
|
||||||
return portinfo_changed
|
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):
|
def create_port(self, context, port):
|
||||||
"""Create a new port entry on DB, then try to activate it."""
|
"""Create a new port entry on DB, then try to activate it."""
|
||||||
LOG.debug(_("NECPluginV2.create_port() called, port=%s ."), port)
|
LOG.debug(_("NECPluginV2.create_port() called, port=%s ."), port)
|
||||||
@ -465,7 +509,8 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
|
|||||||
context, port, sgids)
|
context, port, sgids)
|
||||||
self.notify_security_groups_member_updated(context, port)
|
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,
|
def _update_ofc_port_if_required(self, context, old_port, new_port,
|
||||||
portinfo_changed):
|
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()
|
# Thus we need to call self.get_port() instead of super().get_port()
|
||||||
port = self.get_port(context, id)
|
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:
|
if port['status'] == const.PORT_STATUS_ERROR:
|
||||||
reason = _("Failed to delete port=%s from OFC.") % id
|
reason = _("Failed to delete port=%s from OFC.") % id
|
||||||
raise nexc.OFCException(reason=reason)
|
raise nexc.OFCException(reason=reason)
|
||||||
|
356
neutron/plugins/nec/nec_router.py
Normal file
356
neutron/plugins/nec/nec_router.py
Normal 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]
|
@ -16,12 +16,19 @@
|
|||||||
# @author: Ryota MIBU
|
# @author: Ryota MIBU
|
||||||
# @author: Akihiro MOTOKI
|
# @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 config
|
||||||
from neutron.plugins.nec.common import exceptions as nexc
|
from neutron.plugins.nec.common import exceptions as nexc
|
||||||
from neutron.plugins.nec.db import api as ndb
|
from neutron.plugins.nec.db import api as ndb
|
||||||
from neutron.plugins.nec import drivers
|
from neutron.plugins.nec import drivers
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class OFCManager(object):
|
class OFCManager(object):
|
||||||
"""This class manages an OpenFlow Controller and map resources.
|
"""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):
|
def _del_ofc_item(self, context, resource, neutron_id):
|
||||||
ndb.del_ofc_item_lookup_both(context.session, 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):
|
def create_ofc_tenant(self, context, tenant_id):
|
||||||
desc = "ID=%s at OpenStack." % tenant_id
|
desc = "ID=%s at OpenStack." % tenant_id
|
||||||
ofc_tenant_id = self.driver.create_tenant(desc, 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.driver.delete_filter(ofc_pf_id)
|
||||||
self._del_ofc_item(context, "ofc_packet_filter", filter_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'])
|
||||||
|
221
neutron/plugins/nec/router_drivers.py
Normal file
221
neutron/plugins/nec/router_drivers.py
Normal 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)
|
@ -15,34 +15,129 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
# @author: Ryota MIBU
|
# @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
|
from neutron.plugins.nec import ofc_driver_base
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
MAX_NUM_OPENFLOW_ROUTER = 2
|
||||||
|
|
||||||
|
|
||||||
class StubOFCDriver(ofc_driver_base.OFCDriverBase):
|
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):
|
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):
|
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):
|
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):
|
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):
|
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):
|
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):
|
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):
|
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
|
@classmethod
|
||||||
def filter_supported(cls):
|
def filter_supported(cls):
|
||||||
@ -66,3 +161,131 @@ class StubOFCDriver(ofc_driver_base.OFCDriverBase):
|
|||||||
|
|
||||||
def convert_ofc_filter_id(self, context, ofc_filter_id):
|
def convert_ofc_filter_id(self, context, ofc_filter_id):
|
||||||
return 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
|
||||||
|
@ -15,41 +15,104 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# 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.nec import test_nec_plugin
|
||||||
from neutron.tests.unit.openvswitch import test_agent_scheduler
|
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(
|
class NecAgentSchedulerTestCase(
|
||||||
test_agent_scheduler.OvsAgentSchedulerTestCase):
|
test_agent_scheduler.OvsAgentSchedulerTestCase,
|
||||||
|
test_nec_plugin.NecPluginV2TestCaseBase):
|
||||||
|
|
||||||
plugin_str = test_nec_plugin.PLUGIN_NAME
|
plugin_str = test_nec_plugin.PLUGIN_NAME
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
self.setup_nec_plugin_base()
|
||||||
super(NecAgentSchedulerTestCase, self).setUp()
|
super(NecAgentSchedulerTestCase, self).setUp()
|
||||||
self.mox = mox.Mox()
|
|
||||||
self.mox.StubOutWithMock(ofc_client.OFCClient, 'do_request')
|
|
||||||
self.addCleanup(self.mox.UnsetStubs)
|
|
||||||
|
|
||||||
|
|
||||||
class NecDhcpAgentNotifierTestCase(
|
class NecDhcpAgentNotifierTestCase(
|
||||||
test_agent_scheduler.OvsDhcpAgentNotifierTestCase):
|
test_agent_scheduler.OvsDhcpAgentNotifierTestCase,
|
||||||
|
test_nec_plugin.NecPluginV2TestCaseBase):
|
||||||
|
|
||||||
plugin_str = test_nec_plugin.PLUGIN_NAME
|
plugin_str = test_nec_plugin.PLUGIN_NAME
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
# OvsDhcpAgentNotifierTestCase uses stop() for each mock.
|
||||||
|
self.setup_nec_plugin_base(use_stop_each=True)
|
||||||
super(NecDhcpAgentNotifierTestCase, self).setUp()
|
super(NecDhcpAgentNotifierTestCase, self).setUp()
|
||||||
self.mox = mox.Mox()
|
|
||||||
self.mox.StubOutWithMock(ofc_client.OFCClient, 'do_request')
|
|
||||||
self.addCleanup(self.mox.UnsetStubs)
|
|
||||||
|
|
||||||
|
|
||||||
class NecL3AgentNotifierTestCase(
|
class NecL3AgentNotifierTestCase(
|
||||||
test_agent_scheduler.OvsL3AgentNotifierTestCase):
|
test_agent_scheduler.OvsL3AgentNotifierTestCase,
|
||||||
|
test_nec_plugin.NecPluginV2TestCaseBase):
|
||||||
|
|
||||||
plugin_str = test_nec_plugin.PLUGIN_NAME
|
plugin_str = test_nec_plugin.PLUGIN_NAME
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
# OvsDhcpAgentNotifierTestCase uses stop() for each mock.
|
||||||
|
self.setup_nec_plugin_base(use_stop_each=True)
|
||||||
super(NecL3AgentNotifierTestCase, self).setUp()
|
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)
|
||||||
|
@ -33,6 +33,8 @@ from neutron.tests.unit import test_db_plugin as test_plugin
|
|||||||
|
|
||||||
|
|
||||||
PLUGIN_NAME = 'neutron.plugins.nec.nec_plugin.NECPluginV2'
|
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 = """
|
NEC_PLUGIN_INI = """
|
||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
api_extensions_path = neutron/plugins/nec/extensions
|
api_extensions_path = neutron/plugins/nec/extensions
|
||||||
@ -42,9 +44,7 @@ enable_packet_filter = False
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class NecPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
|
class NecPluginV2TestCaseBase(object):
|
||||||
|
|
||||||
_plugin_name = PLUGIN_NAME
|
|
||||||
_nec_ini = NEC_PLUGIN_INI
|
_nec_ini = NEC_PLUGIN_INI
|
||||||
|
|
||||||
def _set_nec_ini(self):
|
def _set_nec_ini(self):
|
||||||
@ -64,6 +64,34 @@ class NecPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
|
|||||||
os.remove(self.nec_ini_file)
|
os.remove(self.nec_ini_file)
|
||||||
self.nec_ini_file = None
|
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',
|
def rpcapi_update_ports(self, agent_id='nec-q-agent.fake',
|
||||||
datapath_id="0xabc", added=[], removed=[]):
|
datapath_id="0xabc", added=[], removed=[]):
|
||||||
kwargs = {'topic': topics.AGENT,
|
kwargs = {'topic': topics.AGENT,
|
||||||
@ -348,6 +376,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
|
|||||||
mock.call.create_ofc_network(ctx, self._tenant_id, net['id'],
|
mock.call.create_ofc_network(ctx, self._tenant_id, net['id'],
|
||||||
net['name']),
|
net['name']),
|
||||||
mock.call.delete_ofc_network(ctx, net['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)
|
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
|
||||||
]
|
]
|
||||||
self.ofc.assert_has_calls(expected)
|
self.ofc.assert_has_calls(expected)
|
||||||
@ -365,6 +394,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
|
|||||||
mock.call.create_ofc_network(ctx, self._tenant_id, net['id'],
|
mock.call.create_ofc_network(ctx, self._tenant_id, net['id'],
|
||||||
net['name']),
|
net['name']),
|
||||||
mock.call.delete_ofc_network(ctx, net['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)
|
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
|
||||||
]
|
]
|
||||||
self.ofc.assert_has_calls(expected)
|
self.ofc.assert_has_calls(expected)
|
||||||
@ -389,6 +419,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
|
|||||||
nets[1]['name']),
|
nets[1]['name']),
|
||||||
mock.call.delete_ofc_network(ctx, nets[1]['id'], mock.ANY),
|
mock.call.delete_ofc_network(ctx, nets[1]['id'], mock.ANY),
|
||||||
mock.call.delete_ofc_network(ctx, nets[0]['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)
|
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
|
||||||
]
|
]
|
||||||
self.ofc.assert_has_calls(expected)
|
self.ofc.assert_has_calls(expected)
|
||||||
@ -451,6 +482,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
|
|||||||
mock.call.create_ofc_network(ctx, self._tenant_id, net['id'],
|
mock.call.create_ofc_network(ctx, self._tenant_id, net['id'],
|
||||||
net['name']),
|
net['name']),
|
||||||
mock.call.delete_ofc_network(ctx, net['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)
|
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
|
||||||
]
|
]
|
||||||
self.ofc.assert_has_calls(expected)
|
self.ofc.assert_has_calls(expected)
|
||||||
@ -478,6 +510,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
|
|||||||
|
|
||||||
mock.call.exists_ofc_port(ctx, p1['id']),
|
mock.call.exists_ofc_port(ctx, p1['id']),
|
||||||
mock.call.delete_ofc_network(ctx, net['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)
|
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
|
||||||
]
|
]
|
||||||
self.ofc.assert_has_calls(expected)
|
self.ofc.assert_has_calls(expected)
|
||||||
@ -520,6 +553,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
|
|||||||
mock.call.exists_ofc_port(ctx, p1['id']),
|
mock.call.exists_ofc_port(ctx, p1['id']),
|
||||||
mock.call.delete_ofc_port(ctx, p1['id'], mock.ANY),
|
mock.call.delete_ofc_port(ctx, p1['id'], mock.ANY),
|
||||||
mock.call.delete_ofc_network(ctx, net['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)
|
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
|
||||||
]
|
]
|
||||||
self.ofc.assert_has_calls(expected)
|
self.ofc.assert_has_calls(expected)
|
||||||
@ -550,6 +584,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
|
|||||||
mock.call.exists_ofc_port(ctx, p['id']),
|
mock.call.exists_ofc_port(ctx, p['id']),
|
||||||
mock.call.delete_ofc_port(ctx, p['id'], mock.ANY),
|
mock.call.delete_ofc_port(ctx, p['id'], mock.ANY),
|
||||||
mock.call.delete_ofc_network(ctx, net['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)
|
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
|
||||||
]
|
]
|
||||||
self.ofc.assert_has_calls(expected)
|
self.ofc.assert_has_calls(expected)
|
||||||
@ -686,6 +721,7 @@ class TestNecPluginOfcManager(NecPluginV2TestCase):
|
|||||||
|
|
||||||
mock.call.exists_ofc_port(ctx, p1['id']),
|
mock.call.exists_ofc_port(ctx, p1['id']),
|
||||||
mock.call.delete_ofc_network(ctx, net['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)
|
mock.call.delete_ofc_tenant(ctx, self._tenant_id)
|
||||||
]
|
]
|
||||||
self.ofc.assert_has_calls(expected)
|
self.ofc.assert_has_calls(expected)
|
||||||
|
117
neutron/tests/unit/nec/test_ofc_client.py
Normal file
117
neutron/tests/unit/nec/test_ofc_client.py
Normal 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)
|
@ -49,6 +49,8 @@ class OFCManagerTestBase(base.BaseTestCase):
|
|||||||
ndb.initialize()
|
ndb.initialize()
|
||||||
self.addCleanup(ndb.clear_db)
|
self.addCleanup(ndb.clear_db)
|
||||||
self.ofc = ofc_manager.OFCManager()
|
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.ctx = context.get_admin_context()
|
||||||
self.addCleanup(mock.patch.stopall)
|
self.addCleanup(mock.patch.stopall)
|
||||||
|
|
||||||
@ -160,6 +162,8 @@ class OFCManagerTest(OFCManagerTestBase):
|
|||||||
self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_port', p))
|
self.assertFalse(ndb.get_ofc_item(self.ctx.session, 'ofc_port', p))
|
||||||
get_portinfo.assert_called_once_with(mock.ANY, p)
|
get_portinfo.assert_called_once_with(mock.ANY, p)
|
||||||
|
|
||||||
|
|
||||||
|
class OFCManagerFilterTest(OFCManagerTestBase):
|
||||||
def testj_create_ofc_packet_filter(self):
|
def testj_create_ofc_packet_filter(self):
|
||||||
"""test create ofc_filter."""
|
"""test create ofc_filter."""
|
||||||
t, n, p, f, none = self.get_random_params()
|
t, n, p, f, none = self.get_random_params()
|
||||||
@ -198,8 +202,92 @@ class OFCManagerTest(OFCManagerTestBase):
|
|||||||
'ofc_packet_filter', f))
|
'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):
|
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):
|
def test_exists_ofc_tenant(self):
|
||||||
t, n, p, f, none = self.get_random_params()
|
t, n, p, f, none = self.get_random_params()
|
||||||
ofc_t, ofc_n, ofc_p, ofc_f, ofc_none = self.get_random_params()
|
ofc_t, ofc_n, ofc_p, ofc_f, ofc_none = self.get_random_params()
|
||||||
|
@ -19,6 +19,7 @@ import random
|
|||||||
import string
|
import string
|
||||||
|
|
||||||
import mox
|
import mox
|
||||||
|
import netaddr
|
||||||
|
|
||||||
from neutron import context
|
from neutron import context
|
||||||
from neutron.openstack.common import uuidutils
|
from neutron.openstack.common import uuidutils
|
||||||
@ -202,6 +203,167 @@ class PFCV4DriverTest(PFCDriverTestBase):
|
|||||||
driver = 'pfc_v4'
|
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):
|
class PFCDriverStringTest(base.BaseTestCase):
|
||||||
|
|
||||||
driver = 'neutron.plugins.nec.drivers.pfc.PFCDriverBase'
|
driver = 'neutron.plugins.nec.drivers.pfc.PFCDriverBase'
|
||||||
|
46
neutron/tests/unit/nec/test_router.py
Normal file
46
neutron/tests/unit/nec/test_router.py
Normal 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()
|
@ -189,7 +189,7 @@ class AgentSchedulerTestMixIn(object):
|
|||||||
return agent_data['id']
|
return agent_data['id']
|
||||||
|
|
||||||
|
|
||||||
class OvsAgentSchedulerTestCase(test_l3_plugin.L3NatTestCaseMixin,
|
class OvsAgentSchedulerTestCaseBase(test_l3_plugin.L3NatTestCaseMixin,
|
||||||
test_agent_ext_plugin.AgentDBTestMixIn,
|
test_agent_ext_plugin.AgentDBTestMixIn,
|
||||||
AgentSchedulerTestMixIn,
|
AgentSchedulerTestMixIn,
|
||||||
test_plugin.NeutronDbPluginV2TestCase):
|
test_plugin.NeutronDbPluginV2TestCase):
|
||||||
@ -202,7 +202,7 @@ class OvsAgentSchedulerTestCase(test_l3_plugin.L3NatTestCaseMixin,
|
|||||||
self.saved_attr_map = {}
|
self.saved_attr_map = {}
|
||||||
for resource, attrs in attributes.RESOURCE_ATTRIBUTE_MAP.iteritems():
|
for resource, attrs in attributes.RESOURCE_ATTRIBUTE_MAP.iteritems():
|
||||||
self.saved_attr_map[resource] = attrs.copy()
|
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()
|
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
|
||||||
self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr)
|
self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr)
|
||||||
self.adminContext = context.get_admin_context()
|
self.adminContext = context.get_admin_context()
|
||||||
@ -219,6 +219,9 @@ class OvsAgentSchedulerTestCase(test_l3_plugin.L3NatTestCaseMixin,
|
|||||||
# Restore the original RESOURCE_ATTRIBUTE_MAP
|
# Restore the original RESOURCE_ATTRIBUTE_MAP
|
||||||
attributes.RESOURCE_ATTRIBUTE_MAP = self.saved_attr_map
|
attributes.RESOURCE_ATTRIBUTE_MAP = self.saved_attr_map
|
||||||
|
|
||||||
|
|
||||||
|
class OvsAgentSchedulerTestCase(OvsAgentSchedulerTestCaseBase):
|
||||||
|
|
||||||
def test_report_states(self):
|
def test_report_states(self):
|
||||||
self._register_agent_states()
|
self._register_agent_states()
|
||||||
agents = self._list_agents()
|
agents = self._list_agents()
|
||||||
@ -957,12 +960,7 @@ class OvsAgentSchedulerTestCase(test_l3_plugin.L3NatTestCaseMixin,
|
|||||||
admin_context=False)
|
admin_context=False)
|
||||||
|
|
||||||
|
|
||||||
class OvsDhcpAgentNotifierTestCase(test_l3_plugin.L3NatTestCaseMixin,
|
class OvsDhcpAgentNotifierTestCase(OvsAgentSchedulerTestCaseBase):
|
||||||
test_agent_ext_plugin.AgentDBTestMixIn,
|
|
||||||
AgentSchedulerTestMixIn,
|
|
||||||
test_plugin.NeutronDbPluginV2TestCase):
|
|
||||||
plugin_str = ('neutron.plugins.openvswitch.'
|
|
||||||
'ovs_neutron_plugin.OVSNeutronPluginV2')
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.dhcp_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
|
self.dhcp_notifier = dhcp_rpc_agent_api.DhcpAgentNotifyAPI()
|
||||||
@ -971,27 +969,8 @@ class OvsDhcpAgentNotifierTestCase(test_l3_plugin.L3NatTestCaseMixin,
|
|||||||
'DhcpAgentNotifyAPI')
|
'DhcpAgentNotifyAPI')
|
||||||
self.dhcp_notifier_cls = self.dhcp_notifier_cls_p.start()
|
self.dhcp_notifier_cls = self.dhcp_notifier_cls_p.start()
|
||||||
self.dhcp_notifier_cls.return_value = self.dhcp_notifier
|
self.dhcp_notifier_cls.return_value = self.dhcp_notifier
|
||||||
# Save the global RESOURCE_ATTRIBUTE_MAP
|
super(OvsDhcpAgentNotifierTestCase, self).setUp()
|
||||||
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()
|
|
||||||
self.addCleanup(self.dhcp_notifier_cls_p.stop)
|
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):
|
def test_network_add_to_dhcp_agent_notification(self):
|
||||||
with mock.patch.object(self.dhcp_notifier, 'cast') as mock_dhcp:
|
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)
|
self.assertIn(expected, mock_dhcp.call_args_list)
|
||||||
|
|
||||||
|
|
||||||
class OvsL3AgentNotifierTestCase(test_l3_plugin.L3NatTestCaseMixin,
|
class OvsL3AgentNotifierTestCase(OvsAgentSchedulerTestCaseBase):
|
||||||
test_agent_ext_plugin.AgentDBTestMixIn,
|
|
||||||
AgentSchedulerTestMixIn,
|
|
||||||
test_plugin.NeutronDbPluginV2TestCase):
|
|
||||||
plugin_str = ('neutron.plugins.openvswitch.'
|
|
||||||
'ovs_neutron_plugin.OVSNeutronPluginV2')
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.dhcp_notifier_cls_p = mock.patch(
|
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 = mock.Mock(name='dhcp_notifier')
|
||||||
self.dhcp_notifier_cls = self.dhcp_notifier_cls_p.start()
|
self.dhcp_notifier_cls = self.dhcp_notifier_cls_p.start()
|
||||||
self.dhcp_notifier_cls.return_value = self.dhcp_notifier
|
self.dhcp_notifier_cls.return_value = self.dhcp_notifier
|
||||||
# Save the global RESOURCE_ATTRIBUTE_MAP
|
super(OvsL3AgentNotifierTestCase, self).setUp()
|
||||||
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()
|
|
||||||
self.addCleanup(self.dhcp_notifier_cls_p.stop)
|
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):
|
def test_router_add_to_l3_agent_notification(self):
|
||||||
plugin = manager.NeutronManager.get_plugin()
|
plugin = manager.NeutronManager.get_plugin()
|
||||||
|
@ -59,10 +59,11 @@ class TestExtraRoutePlugin(test_l3.TestL3NatPlugin,
|
|||||||
|
|
||||||
class ExtraRouteDBTestCase(test_l3.L3NatDBTestCase):
|
class ExtraRouteDBTestCase(test_l3.L3NatDBTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self, plugin=None):
|
||||||
test_config['plugin_name_v2'] = (
|
if not plugin:
|
||||||
'neutron.tests.unit.'
|
plugin = ('neutron.tests.unit.test_extension_extraroute.'
|
||||||
'test_extension_extraroute.TestExtraRoutePlugin')
|
'TestExtraRoutePlugin')
|
||||||
|
test_config['plugin_name_v2'] = plugin
|
||||||
# for these tests we need to enable overlapping ips
|
# for these tests we need to enable overlapping ips
|
||||||
cfg.CONF.set_default('allow_overlapping_ips', True)
|
cfg.CONF.set_default('allow_overlapping_ips', True)
|
||||||
cfg.CONF.set_default('max_routes', 3)
|
cfg.CONF.set_default('max_routes', 3)
|
||||||
|
@ -28,7 +28,6 @@ from webob import exc
|
|||||||
import webtest
|
import webtest
|
||||||
|
|
||||||
from neutron.api import extensions
|
from neutron.api import extensions
|
||||||
from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api
|
|
||||||
from neutron.api.v2 import attributes
|
from neutron.api.v2 import attributes
|
||||||
from neutron.common import config
|
from neutron.common import config
|
||||||
from neutron.common import constants as l3_constants
|
from neutron.common import constants as l3_constants
|
||||||
@ -321,13 +320,15 @@ class L3NatTestCaseMixin(object):
|
|||||||
return router_req.get_response(self.ext_api)
|
return router_req.get_response(self.ext_api)
|
||||||
|
|
||||||
def _make_router(self, fmt, tenant_id, name=None, admin_state_up=None,
|
def _make_router(self, fmt, tenant_id, name=None, admin_state_up=None,
|
||||||
external_gateway_info=None, set_context=False):
|
external_gateway_info=None, set_context=False,
|
||||||
arg_list = (external_gateway_info and
|
arg_list=None, **kwargs):
|
||||||
('external_gateway_info', ) or None)
|
if external_gateway_info:
|
||||||
|
arg_list = ('external_gateway_info', ) + (arg_list or ())
|
||||||
res = self._create_router(fmt, tenant_id, name,
|
res = self._create_router(fmt, tenant_id, name,
|
||||||
admin_state_up, set_context,
|
admin_state_up, set_context,
|
||||||
arg_list=arg_list,
|
arg_list=arg_list,
|
||||||
external_gateway_info=external_gateway_info)
|
external_gateway_info=external_gateway_info,
|
||||||
|
**kwargs)
|
||||||
return self.deserialize(fmt, res)
|
return self.deserialize(fmt, res)
|
||||||
|
|
||||||
def _add_external_gateway_to_router(self, router_id, network_id,
|
def _add_external_gateway_to_router(self, router_id, network_id,
|
||||||
@ -367,10 +368,11 @@ class L3NatTestCaseMixin(object):
|
|||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def router(self, name='router1', admin_state_up=True,
|
def router(self, name='router1', admin_state_up=True,
|
||||||
fmt=None, tenant_id=_uuid(),
|
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,
|
router = self._make_router(fmt or self.fmt, tenant_id, name,
|
||||||
admin_state_up, external_gateway_info,
|
admin_state_up, external_gateway_info,
|
||||||
set_context)
|
set_context, **kwargs)
|
||||||
try:
|
try:
|
||||||
yield router
|
yield router
|
||||||
finally:
|
finally:
|
||||||
@ -1673,18 +1675,19 @@ class L3AgentDbTestCase(L3NatTestCaseBase):
|
|||||||
def _test_notify_op_agent(self, target_func, *args):
|
def _test_notify_op_agent(self, target_func, *args):
|
||||||
l3_rpc_agent_api_str = (
|
l3_rpc_agent_api_str = (
|
||||||
'neutron.api.rpc.agentnotifiers.l3_rpc_agent_api.L3AgentNotifyAPI')
|
'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:
|
try:
|
||||||
with mock.patch(l3_rpc_agent_api_str) as notifyApi:
|
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 = [item for item in args]
|
||||||
kargs.append(notifyApi)
|
kargs.append(notifyApi)
|
||||||
target_func(*kargs)
|
target_func(*kargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
l3_rpc_agent_api.L3AgentNotify = oldNotify
|
plugin.l3_rpc_notifier = oldNotify
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
l3_rpc_agent_api.L3AgentNotify = oldNotify
|
plugin.l3_rpc_notifier = oldNotify
|
||||||
|
|
||||||
def _test_router_gateway_op_agent(self, notifyApi):
|
def _test_router_gateway_op_agent(self, notifyApi):
|
||||||
with self.router() as r:
|
with self.router() as r:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user