Merge "Add support for the extra route extension in the NVP plugin."

This commit is contained in:
Jenkins 2013-07-15 17:54:08 +00:00 committed by Gerrit Code Review
commit e13014ec7e
8 changed files with 510 additions and 80 deletions

View File

@ -42,11 +42,13 @@ from neutron.db import agentschedulers_db
from neutron.db import api as db from neutron.db import api as 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_db from neutron.db import l3_db
from neutron.db import models_v2 from neutron.db import models_v2
from neutron.db import portsecurity_db from neutron.db import portsecurity_db
from neutron.db import quota_db # noqa from neutron.db import quota_db # noqa
from neutron.db import securitygroups_db from neutron.db import securitygroups_db
from neutron.extensions import extraroute
from neutron.extensions import l3 from neutron.extensions import l3
from neutron.extensions import portsecurity as psec from neutron.extensions import portsecurity as psec
from neutron.extensions import providernet as pnet from neutron.extensions import providernet as pnet
@ -124,7 +126,7 @@ class NVPRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2, class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
l3_db.L3_NAT_db_mixin, extraroute_db.ExtraRoute_db_mixin,
portsecurity_db.PortSecurityDbMixin, portsecurity_db.PortSecurityDbMixin,
securitygroups_db.SecurityGroupDbMixin, securitygroups_db.SecurityGroupDbMixin,
mac_db.MacLearningDbMixin, mac_db.MacLearningDbMixin,
@ -139,7 +141,8 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
functionality using NVP. functionality using NVP.
""" """
supported_extension_aliases = ["mac-learning", supported_extension_aliases = ["extraroute",
"mac-learning",
"network-gateway", "network-gateway",
"nvp-qos", "nvp-qos",
"port-security", "port-security",
@ -1458,7 +1461,7 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
self._update_router_gw_info(context, router_db['id'], gw_info) self._update_router_gw_info(context, router_db['id'], gw_info)
return self._make_router_dict(router_db) return self._make_router_dict(router_db)
def update_router(self, context, id, router): def update_router(self, context, router_id, router):
# Either nexthop is updated or should be kept as it was before # Either nexthop is updated or should be kept as it was before
r = router['router'] r = router['router']
nexthop = None nexthop = None
@ -1479,22 +1482,45 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
ext_subnet = ext_net.subnets[0] ext_subnet = ext_net.subnets[0]
nexthop = ext_subnet.gateway_ip nexthop = ext_subnet.gateway_ip
try: try:
nvplib.update_lrouter(self.cluster, id, for route in r.get('routes', []):
router['router'].get('name'), nexthop) if route['destination'] == '0.0.0.0/0':
msg = _("'routes' cannot contain route '0.0.0.0/0', "
"this must be updated through the default "
"gateway attribute")
raise q_exc.BadRequest(resource='router', msg=msg)
previous_routes = nvplib.update_lrouter(
self.cluster, router_id, r.get('name'),
nexthop, routes=r.get('routes'))
# NOTE(salv-orlando): The exception handling below is not correct, but # NOTE(salv-orlando): The exception handling below is not correct, but
# unfortunately nvplib raises a neutron notfound exception when an # unfortunately nvplib raises a neutron notfound exception when an
# object is not found in the underlying backend # object is not found in the underlying backend
except q_exc.NotFound: except q_exc.NotFound:
# Put the router in ERROR status # Put the router in ERROR status
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
router_db = self._get_router(context, id) router_db = self._get_router(context, router_id)
router_db['status'] = constants.NET_STATUS_ERROR router_db['status'] = constants.NET_STATUS_ERROR
raise nvp_exc.NvpPluginException( raise nvp_exc.NvpPluginException(
err_msg=_("Logical router %s not found on NVP Platform") % id) err_msg=_("Logical router %s not found "
"on NVP Platform") % router_id)
except NvpApiClient.NvpApiException: except NvpApiClient.NvpApiException:
raise nvp_exc.NvpPluginException( raise nvp_exc.NvpPluginException(
err_msg=_("Unable to update logical router on NVP Platform")) err_msg=_("Unable to update logical router on NVP Platform"))
return super(NvpPluginV2, self).update_router(context, id, router) except nvp_exc.NvpInvalidVersion:
msg = _("Request cannot contain 'routes' with the NVP "
"platform currently in execution. Please, try "
"without specifying the static routes.")
LOG.exception(msg)
raise q_exc.BadRequest(resource='router', msg=msg)
try:
return super(NvpPluginV2, self).update_router(context,
router_id, router)
except (extraroute.InvalidRoutes,
extraroute.RouterInterfaceInUseByRoute,
extraroute.RoutesExhausted):
with excutils.save_and_reraise_exception():
# revert changes made to NVP
nvplib.update_explicit_routes_lrouter(
self.cluster, router_id, previous_routes)
def delete_router(self, context, id): def delete_router(self, context, id):
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):

View File

@ -31,12 +31,24 @@ def _find_nvp_version_in_headers(headers):
for (header_name, header_value) in (headers or ()): for (header_name, header_value) in (headers or ()):
try: try:
if header_name == 'server': if header_name == 'server':
return header_value.split('/')[1] return NVPVersion(header_value.split('/')[1])
except IndexError: except IndexError:
LOG.warning(_("Unable to fetch NVP version from response " LOG.warning(_("Unable to fetch NVP version from response "
"headers:%s"), headers) "headers:%s"), headers)
class NVPVersion(object):
"""Abstracts NVP version by exposing major and minor."""
def __init__(self, nvp_version):
self.full_version = nvp_version.split('.')
self.major = int(self.full_version[0])
self.minor = int(self.full_version[1])
def __str__(self):
return '.'.join(self.full_version)
class NVPApiHelper(client_eventlet.NvpApiClientEventlet): class NVPApiHelper(client_eventlet.NvpApiClientEventlet):
'''API helper class. '''API helper class.
@ -153,10 +165,13 @@ class NVPApiHelper(client_eventlet.NvpApiClientEventlet):
def get_nvp_version(self): def get_nvp_version(self):
if not self._nvp_version: if not self._nvp_version:
# generate a simple request (/ws.v1/log) # Determine the NVP version by querying the control
# this will cause nvp_version to be fetched # cluster nodes. Currently, the version will be the
# don't bother about response # one of the server that responds.
self.request('GET', '/ws.v1/log') self.request('GET', '/ws.v1/control-cluster/node')
if not self._nvp_version:
LOG.error(_('Unable to determine NVP version. '
'Plugin might not work as expected.'))
return self._nvp_version return self._nvp_version
def fourZeroFour(self): def fourZeroFour(self):

View File

@ -190,8 +190,9 @@ class NvpApiRequest(object):
# the conn to be released with is_conn_error == True # the conn to be released with is_conn_error == True
# which puts the conn on the back of the client's priority # which puts the conn on the back of the client's priority
# queue. # queue.
if response.status >= 500: if (response.status == httplib.INTERNAL_SERVER_ERROR and
LOG.warn(_("[%(rid)d] Request '%(method) %(url)s' " response.status > httplib.NOT_IMPLEMENTED):
LOG.warn(_("[%(rid)d] Request '%(method)s %(url)s' "
"received: %(status)s"), "received: %(status)s"),
{'rid': self._rid(), 'method': self._method, {'rid': self._rid(), 'method': self._method,
'url': self._url, 'status': response.status}) 'url': self._url, 'status': response.status})

View File

@ -24,6 +24,10 @@ class NvpPluginException(q_exc.NeutronException):
message = _("An unexpected error occurred in the NVP Plugin:%(err_msg)s") message = _("An unexpected error occurred in the NVP Plugin:%(err_msg)s")
class NvpInvalidVersion(NvpPluginException):
message = _("Unable to fulfill request with version %(version)s.")
class NvpInvalidConnection(NvpPluginException): class NvpInvalidConnection(NvpPluginException):
message = _("Invalid NVP connection parameters: %(conn_params)s") message = _("Invalid NVP connection parameters: %(conn_params)s")

View File

@ -30,6 +30,7 @@ from oslo.config import cfg
# no neutron-specific logic in it # no neutron-specific logic in it
from neutron.common import constants from neutron.common import constants
from neutron.common import exceptions as exception from neutron.common import exceptions as exception
from neutron.openstack.common import excutils
from neutron.openstack.common import log from neutron.openstack.common import log
from neutron.plugins.nicira.common import ( from neutron.plugins.nicira.common import (
exceptions as nvp_exc) exceptions as nvp_exc)
@ -48,11 +49,12 @@ URI_PREFIX = "/ws.v1"
LSWITCH_RESOURCE = "lswitch" LSWITCH_RESOURCE = "lswitch"
LSWITCHPORT_RESOURCE = "lport/%s" % LSWITCH_RESOURCE LSWITCHPORT_RESOURCE = "lport/%s" % LSWITCH_RESOURCE
LROUTER_RESOURCE = "lrouter" LROUTER_RESOURCE = "lrouter"
# Current neutron version
LROUTERPORT_RESOURCE = "lport/%s" % LROUTER_RESOURCE LROUTERPORT_RESOURCE = "lport/%s" % LROUTER_RESOURCE
LROUTERRIB_RESOURCE = "rib/%s" % LROUTER_RESOURCE
LROUTERNAT_RESOURCE = "nat/lrouter" LROUTERNAT_RESOURCE = "nat/lrouter"
LQUEUE_RESOURCE = "lqueue" LQUEUE_RESOURCE = "lqueue"
GWSERVICE_RESOURCE = "gateway-service" GWSERVICE_RESOURCE = "gateway-service"
# Current neutron version
NEUTRON_VERSION = "2013.1" NEUTRON_VERSION = "2013.1"
# Other constants for NVP resource # Other constants for NVP resource
MAX_DISPLAY_NAME_LEN = 40 MAX_DISPLAY_NAME_LEN = 40
@ -74,16 +76,29 @@ taken_context_ids = []
_lqueue_cache = {} _lqueue_cache = {}
def version_dependent(func): def version_dependent(wrapped_func):
func_name = func.__name__ func_name = wrapped_func.__name__
def dispatch_version_dependent_function(cluster, *args, **kwargs): def dispatch_version_dependent_function(cluster, *args, **kwargs):
nvp_ver = cluster.api_client.get_nvp_version() # Call the wrapper function, in case we need to
if nvp_ver: # run validation checks regarding versions. It
ver_major = int(nvp_ver.split('.')[0]) # should return the NVP version
real_func = NVPLIB_FUNC_DICT[func_name][ver_major] v = (wrapped_func(cluster, *args, **kwargs) or
cluster.api_client.get_nvp_version())
if v:
func = (NVPLIB_FUNC_DICT[func_name][v.major].get(v.minor) or
NVPLIB_FUNC_DICT[func_name][v.major]['default'])
if func is None:
LOG.error(_('NVP version %(ver)s does not support method '
'%(fun)s.') % {'ver': v, 'fun': func_name})
raise NotImplementedError()
else:
raise NvpApiClient.ServiceUnavailable('NVP version is not set. '
'Unable to complete request'
'correctly. Check log for '
'NVP communication errors.')
func_kwargs = kwargs func_kwargs = kwargs
arg_spec = inspect.getargspec(real_func) arg_spec = inspect.getargspec(func)
if not arg_spec.keywords and not arg_spec.varargs: if not arg_spec.keywords and not arg_spec.varargs:
# drop args unknown to function from func_args # drop args unknown to function from func_args
arg_set = set(func_kwargs.keys()) arg_set = set(func_kwargs.keys())
@ -91,7 +106,7 @@ def version_dependent(func):
del func_kwargs[arg] del func_kwargs[arg]
# NOTE(salvatore-orlando): shall we fail here if a required # NOTE(salvatore-orlando): shall we fail here if a required
# argument is not passed, or let the called function raise? # argument is not passed, or let the called function raise?
real_func(cluster, *args, **func_kwargs) return func(cluster, *args, **func_kwargs)
return dispatch_version_dependent_function return dispatch_version_dependent_function
@ -284,7 +299,22 @@ def create_l2_gw_service(cluster, tenant_id, display_name, devices):
json.dumps(gwservice_obj), cluster=cluster) json.dumps(gwservice_obj), cluster=cluster)
def create_lrouter(cluster, tenant_id, display_name, nexthop): def _prepare_lrouter_body(name, tenant_id, router_type, **kwargs):
body = {
"display_name": _check_and_truncate_name(name),
"tags": [{"tag": tenant_id, "scope": "os_tid"},
{"tag": NEUTRON_VERSION, "scope": "quantum"}],
"routing_config": {
"type": router_type
},
"type": "LogicalRouterConfig"
}
if kwargs:
body["routing_config"].update(kwargs)
return body
def create_implicit_routing_lrouter(cluster, tenant_id, display_name, nexthop):
"""Create a NVP logical router on the specified cluster. """Create a NVP logical router on the specified cluster.
:param cluster: The target NVP cluster :param cluster: The target NVP cluster
@ -295,25 +325,36 @@ def create_lrouter(cluster, tenant_id, display_name, nexthop):
:raise NvpApiException: if there is a problem while communicating :raise NvpApiException: if there is a problem while communicating
with the NVP controller with the NVP controller
""" """
display_name = _check_and_truncate_name(display_name) implicit_routing_config = {
tags = [{"tag": tenant_id, "scope": "os_tid"}, "default_route_next_hop": {
{"tag": NEUTRON_VERSION, "scope": "quantum"}] "gateway_ip_address": nexthop,
lrouter_obj = { "type": "RouterNextHop"
"display_name": display_name,
"tags": tags,
"routing_config": {
"default_route_next_hop": {
"gateway_ip_address": nexthop,
"type": "RouterNextHop"
},
"type": "SingleDefaultRouteImplicitRoutingConfig"
}, },
"type": "LogicalRouterConfig"
} }
lrouter_obj = _prepare_lrouter_body(
display_name, tenant_id,
"SingleDefaultRouteImplicitRoutingConfig",
**implicit_routing_config)
return do_request(HTTP_POST, _build_uri_path(LROUTER_RESOURCE), return do_request(HTTP_POST, _build_uri_path(LROUTER_RESOURCE),
json.dumps(lrouter_obj), cluster=cluster) json.dumps(lrouter_obj), cluster=cluster)
def create_explicit_routing_lrouter(cluster, tenant_id,
display_name, nexthop):
lrouter_obj = _prepare_lrouter_body(
display_name, tenant_id, "RoutingTableRoutingConfig")
router = do_request(HTTP_POST, _build_uri_path(LROUTER_RESOURCE),
json.dumps(lrouter_obj), cluster=cluster)
default_gw = {'prefix': '0.0.0.0/0', 'next_hop_ip': nexthop}
create_explicit_route_lrouter(cluster, router['uuid'], default_gw)
return router
@version_dependent
def create_lrouter(cluster, *args, **kwargs):
pass
def delete_lrouter(cluster, lrouter_id): def delete_lrouter(cluster, lrouter_id):
do_request(HTTP_DELETE, _build_uri_path(LROUTER_RESOURCE, do_request(HTTP_DELETE, _build_uri_path(LROUTER_RESOURCE,
resource_id=lrouter_id), resource_id=lrouter_id),
@ -381,8 +422,8 @@ def update_l2_gw_service(cluster, gateway_id, display_name):
json.dumps(gwservice_obj), cluster=cluster) json.dumps(gwservice_obj), cluster=cluster)
def update_lrouter(cluster, lrouter_id, display_name, nexthop): def update_implicit_routing_lrouter(cluster, r_id, display_name, nexthop):
lrouter_obj = get_lrouter(cluster, lrouter_id) lrouter_obj = get_lrouter(cluster, r_id)
if not display_name and not nexthop: if not display_name and not nexthop:
# Nothing to update # Nothing to update
return lrouter_obj return lrouter_obj
@ -395,11 +436,150 @@ def update_lrouter(cluster, lrouter_id, display_name, nexthop):
if nh_element: if nh_element:
nh_element["gateway_ip_address"] = nexthop nh_element["gateway_ip_address"] = nexthop
return do_request(HTTP_PUT, _build_uri_path(LROUTER_RESOURCE, return do_request(HTTP_PUT, _build_uri_path(LROUTER_RESOURCE,
resource_id=lrouter_id), resource_id=r_id),
json.dumps(lrouter_obj), json.dumps(lrouter_obj),
cluster=cluster) cluster=cluster)
def get_explicit_routes_lrouter(cluster, router_id, protocol_type='static'):
static_filter = {'protocol': protocol_type}
existing_routes = do_request(
HTTP_GET,
_build_uri_path(LROUTERRIB_RESOURCE,
filters=static_filter,
fields="*",
parent_resource_id=router_id),
cluster=cluster)['results']
return existing_routes
def delete_explicit_route_lrouter(cluster, router_id, route_id):
do_request(HTTP_DELETE,
_build_uri_path(LROUTERRIB_RESOURCE,
resource_id=route_id,
parent_resource_id=router_id),
cluster=cluster)
def create_explicit_route_lrouter(cluster, router_id, route):
next_hop_ip = route.get("nexthop") or route.get("next_hop_ip")
prefix = route.get("destination") or route.get("prefix")
uuid = do_request(
HTTP_POST,
_build_uri_path(LROUTERRIB_RESOURCE,
parent_resource_id=router_id),
json.dumps({
"action": "accept",
"next_hop_ip": next_hop_ip,
"prefix": prefix,
"protocol": "static"
}),
cluster=cluster)['uuid']
return uuid
def update_explicit_routes_lrouter(cluster, router_id, routes):
# Update in bulk: delete them all, and add the ones specified
# but keep track of what is been modified to allow roll-backs
# in case of failures
nvp_routes = get_explicit_routes_lrouter(cluster, router_id)
try:
deleted_routes = []
added_routes = []
# omit the default route (0.0.0.0/0) from the processing;
# this must be handled through the nexthop for the router
for route in nvp_routes:
prefix = route.get("destination") or route.get("prefix")
if prefix != '0.0.0.0/0':
delete_explicit_route_lrouter(cluster,
router_id,
route['uuid'])
deleted_routes.append(route)
for route in routes:
prefix = route.get("destination") or route.get("prefix")
if prefix != '0.0.0.0/0':
uuid = create_explicit_route_lrouter(cluster,
router_id, route)
added_routes.append(uuid)
except NvpApiClient.NvpApiException:
LOG.exception(_('Cannot update NVP routes %(routes)s for'
'router %(router_id)s') % {'routes': routes,
'router_id': router_id})
# Roll back to keep NVP in consistent state
with excutils.save_and_reraise_exception():
if nvp_routes:
if deleted_routes:
for route in deleted_routes:
create_explicit_route_lrouter(cluster,
router_id, route)
if added_routes:
for route_id in added_routes:
delete_explicit_route_lrouter(cluster,
router_id, route_id)
return nvp_routes
@version_dependent
def get_default_route_explicit_routing_lrouter(cluster, *args, **kwargs):
pass
def get_default_route_explicit_routing_lrouter_v33(cluster, router_id):
static_filter = {"protocol": "static",
"prefix": "0.0.0.0/0"}
default_route = do_request(
HTTP_GET,
_build_uri_path(LROUTERRIB_RESOURCE,
filters=static_filter,
fields="*",
parent_resource_id=router_id),
cluster=cluster)["results"][0]
return default_route
def get_default_route_explicit_routing_lrouter_v32(cluster, router_id):
# Scan all routes because 3.2 does not support query by prefix
all_routes = get_explicit_routes_lrouter(cluster, router_id)
for route in all_routes:
if route['prefix'] == '0.0.0.0/0':
return route
def update_default_gw_explicit_routing_lrouter(cluster, router_id, next_hop):
default_route = get_default_route_explicit_routing_lrouter(cluster,
router_id)
if next_hop != default_route["next_hop_ip"]:
new_default_route = {"action": "accept",
"next_hop_ip": next_hop,
"prefix": "0.0.0.0/0",
"protocol": "static"}
do_request(HTTP_PUT,
_build_uri_path(LROUTERRIB_RESOURCE,
resource_id=default_route['uuid'],
parent_resource_id=router_id),
json.dumps(new_default_route),
cluster=cluster)
def update_explicit_routing_lrouter(cluster, router_id,
display_name, next_hop, routes=None):
update_implicit_routing_lrouter(cluster, router_id, display_name, next_hop)
if next_hop:
update_default_gw_explicit_routing_lrouter(cluster,
router_id, next_hop)
if routes:
return update_explicit_routes_lrouter(cluster, router_id, routes)
@version_dependent
def update_lrouter(cluster, *args, **kwargs):
if kwargs.get('routes', None):
v = cluster.api_client.get_nvp_version()
if (v.major < 3) or (v.major >= 3 and v.minor < 2):
raise nvp_exc.NvpInvalidVersion(version=v)
return v
def delete_network(cluster, net_id, lswitch_id): def delete_network(cluster, net_id, lswitch_id):
delete_networks(cluster, net_id, [lswitch_id]) delete_networks(cluster, net_id, [lswitch_id])
@ -1027,14 +1207,27 @@ def update_lrouter_port_ips(cluster, lrouter_id, lport_id,
raise nvp_exc.NvpPluginException(err_msg=msg) raise nvp_exc.NvpPluginException(err_msg=msg)
# TODO(salvatore-orlando): Also handle changes in minor versions
NVPLIB_FUNC_DICT = { NVPLIB_FUNC_DICT = {
'create_lrouter_dnat_rule': {2: create_lrouter_dnat_rule_v2, 'create_lrouter': {
3: create_lrouter_dnat_rule_v3}, 2: {'default': create_implicit_routing_lrouter, },
'create_lrouter_snat_rule': {2: create_lrouter_snat_rule_v2, 3: {'default': create_implicit_routing_lrouter,
3: create_lrouter_snat_rule_v3}, 2: create_explicit_routing_lrouter, }, },
'create_lrouter_nosnat_rule': {2: create_lrouter_nosnat_rule_v2, 'update_lrouter': {
3: create_lrouter_nosnat_rule_v3} 2: {'default': update_implicit_routing_lrouter, },
3: {'default': update_implicit_routing_lrouter,
2: update_explicit_routing_lrouter, }, },
'create_lrouter_dnat_rule': {
2: {'default': create_lrouter_dnat_rule_v2, },
3: {'default': create_lrouter_dnat_rule_v3, }, },
'create_lrouter_snat_rule': {
2: {'default': create_lrouter_snat_rule_v2, },
3: {'default': create_lrouter_snat_rule_v3, }, },
'create_lrouter_nosnat_rule': {
2: {'default': create_lrouter_nosnat_rule_v2, },
3: {'default': create_lrouter_nosnat_rule_v3, }, },
'get_default_route_explicit_routing_lrouter': {
3: {2: get_default_route_explicit_routing_lrouter_v32,
3: get_default_route_explicit_routing_lrouter_v33, }, },
} }

View File

@ -27,6 +27,7 @@ from neutron import context
from neutron.extensions import agent from neutron.extensions import agent
from neutron.openstack.common import log as logging from neutron.openstack.common import log as logging
import neutron.plugins.nicira as nvp_plugin import neutron.plugins.nicira as nvp_plugin
from neutron.plugins.nicira.NvpApiClient import NVPVersion
from neutron.tests.unit.nicira import fake_nvpapiclient from neutron.tests.unit.nicira import fake_nvpapiclient
from neutron.tests.unit import test_db_plugin from neutron.tests.unit import test_db_plugin
@ -84,7 +85,7 @@ class MacLearningDBTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
return self.fc.fake_request(*args, **kwargs) return self.fc.fake_request(*args, **kwargs)
# Emulate tests against NVP 2.x # Emulate tests against NVP 2.x
instance.return_value.get_nvp_version.return_value = "2.999" instance.return_value.get_nvp_version.return_value = NVPVersion("3.0")
instance.return_value.request.side_effect = _fake_request instance.return_value.request.side_effect = _fake_request
cfg.CONF.set_override('metadata_mode', None, 'NVP') cfg.CONF.set_override('metadata_mode', None, 'NVP')
self.addCleanup(self.fc.reset_all) self.addCleanup(self.fc.reset_all)

View File

@ -34,6 +34,7 @@ from neutron.plugins.nicira.extensions import nvp_networkgw
from neutron.plugins.nicira.extensions import nvp_qos as ext_qos from neutron.plugins.nicira.extensions import nvp_qos as ext_qos
from neutron.plugins.nicira import NeutronPlugin from neutron.plugins.nicira import NeutronPlugin
from neutron.plugins.nicira import NvpApiClient from neutron.plugins.nicira import NvpApiClient
from neutron.plugins.nicira.NvpApiClient import NVPVersion
from neutron.plugins.nicira import nvplib from neutron.plugins.nicira import nvplib
from neutron.tests.unit.nicira import fake_nvpapiclient from neutron.tests.unit.nicira import fake_nvpapiclient
import neutron.tests.unit.nicira.test_networkgw as test_l2_gw import neutron.tests.unit.nicira.test_networkgw as test_l2_gw
@ -86,7 +87,7 @@ class NiciraPluginV2TestCase(test_plugin.NeutronDbPluginV2TestCase):
return self.fc.fake_request(*args, **kwargs) return self.fc.fake_request(*args, **kwargs)
# Emulate tests against NVP 2.x # Emulate tests against NVP 2.x
instance.return_value.get_nvp_version.return_value = "2.999" instance.return_value.get_nvp_version.return_value = NVPVersion("2.9")
instance.return_value.request.side_effect = _fake_request instance.return_value.request.side_effect = _fake_request
super(NiciraPluginV2TestCase, self).setUp(self._plugin_name) super(NiciraPluginV2TestCase, self).setUp(self._plugin_name)
cfg.CONF.set_override('metadata_mode', None, 'NVP') cfg.CONF.set_override('metadata_mode', None, 'NVP')

View File

@ -44,6 +44,9 @@ class NvplibTestCase(base.BaseTestCase):
% NICIRA_PKG_PATH, autospec=True) % NICIRA_PKG_PATH, autospec=True)
instance = self.mock_nvpapi.start() instance = self.mock_nvpapi.start()
instance.return_value.login.return_value = "the_cookie" instance.return_value.login.return_value = "the_cookie"
fake_version = getattr(self, 'fake_version', "2.9")
instance.return_value.get_nvp_version.return_value = (
NvpApiClient.NVPVersion(fake_version))
def _fake_request(*args, **kwargs): def _fake_request(*args, **kwargs):
return self.fc.fake_request(*args, **kwargs) return self.fc.fake_request(*args, **kwargs)
@ -69,35 +72,32 @@ class NvplibTestCase(base.BaseTestCase):
class TestNvplibNatRules(NvplibTestCase): class TestNvplibNatRules(NvplibTestCase):
def _test_create_lrouter_dnat_rule(self, func): def _test_create_lrouter_dnat_rule(self, version):
tenant_id = 'pippo' with mock.patch.object(self.fake_cluster.api_client,
lrouter = nvplib.create_lrouter(self.fake_cluster, 'get_nvp_version',
tenant_id, new=lambda: NvpApiClient.NVPVersion(version)):
'fake_router', tenant_id = 'pippo'
'192.168.0.1') lrouter = nvplib.create_lrouter(self.fake_cluster,
nat_rule = func(self.fake_cluster, lrouter['uuid'], '10.0.0.99', tenant_id,
match_criteria={'destination_ip_addresses': 'fake_router',
'192.168.0.5'}) '192.168.0.1')
uri = nvplib._build_uri_path(nvplib.LROUTERNAT_RESOURCE, nat_rule = nvplib.create_lrouter_dnat_rule(
nat_rule['uuid'], self.fake_cluster, lrouter['uuid'], '10.0.0.99',
lrouter['uuid']) match_criteria={'destination_ip_addresses':
return nvplib.do_request("GET", uri, cluster=self.fake_cluster) '192.168.0.5'})
uri = nvplib._build_uri_path(nvplib.LROUTERNAT_RESOURCE,
nat_rule['uuid'],
lrouter['uuid'])
resp_obj = nvplib.do_request("GET", uri, cluster=self.fake_cluster)
self.assertEqual('DestinationNatRule', resp_obj['type'])
self.assertEqual('192.168.0.5',
resp_obj['match']['destination_ip_addresses'])
def test_create_lrouter_dnat_rule_v2(self): def test_create_lrouter_dnat_rule_v2(self):
resp_obj = self._test_create_lrouter_dnat_rule( self._test_create_lrouter_dnat_rule('2.9')
nvplib.create_lrouter_dnat_rule_v2)
self.assertEqual('DestinationNatRule', resp_obj['type'])
self.assertEqual('192.168.0.5',
resp_obj['match']['destination_ip_addresses'])
def test_create_lrouter_dnat_rule_v3(self): def test_create_lrouter_dnat_rule_v31(self):
resp_obj = self._test_create_lrouter_dnat_rule( self._test_create_lrouter_dnat_rule('3.1')
nvplib.create_lrouter_dnat_rule_v2)
# TODO(salvatore-orlando): Extend FakeNVPApiClient to deal with
# different versions of NVP API
self.assertEqual('DestinationNatRule', resp_obj['type'])
self.assertEqual('192.168.0.5',
resp_obj['match']['destination_ip_addresses'])
class NvplibNegativeTests(base.BaseTestCase): class NvplibNegativeTests(base.BaseTestCase):
@ -110,6 +110,10 @@ class NvplibNegativeTests(base.BaseTestCase):
% NICIRA_PKG_PATH, autospec=True) % NICIRA_PKG_PATH, autospec=True)
instance = self.mock_nvpapi.start() instance = self.mock_nvpapi.start()
instance.return_value.login.return_value = "the_cookie" instance.return_value.login.return_value = "the_cookie"
# Choose 2.9, but the version is irrelevant for the aim of
# these tests as calls are throwing up errors anyway
self.fake_version = NvpApiClient.NVPVersion('2.9')
instance.return_value.get_nvp_version.return_value = self.fake_version
def _faulty_request(*args, **kwargs): def _faulty_request(*args, **kwargs):
raise nvplib.NvpApiClient.NvpApiException raise nvplib.NvpApiClient.NvpApiException
@ -365,6 +369,187 @@ class TestNvplibLogicalSwitches(NvplibTestCase):
self.fake_cluster, 'whatever', ['whatever']) self.fake_cluster, 'whatever', ['whatever'])
class TestNvplibExplicitLRouters(NvplibTestCase):
def setUp(self):
self.fake_version = '3.2'
super(TestNvplibExplicitLRouters, self).setUp()
def _get_lrouter(self, tenant_id, router_name, router_id, relations=None):
schema = '/ws.v1/schema/RoutingTableRoutingConfig'
router = {'display_name': router_name,
'uuid': router_id,
'tags': [{'scope': 'quantum', 'tag': '2013.1'},
{'scope': 'os_tid', 'tag': '%s' % tenant_id}],
'distributed': False,
'routing_config': {'type': 'RoutingTableRoutingConfig',
'_schema': schema},
'_schema': schema,
'nat_synchronization_enabled': True,
'replication_mode': 'service',
'type': 'LogicalRouterConfig',
'_href': '/ws.v1/lrouter/%s' % router_id, }
if relations:
router['_relations'] = relations
return router
def _get_single_route(self, router_id, route_id='fake_route_id_0',
prefix='0.0.0.0/0', next_hop_ip='1.1.1.1'):
return {'protocol': 'static',
'_href': '/ws.v1/lrouter/%s/rib/%s' % (router_id, route_id),
'prefix': prefix,
'_schema': '/ws.v1/schema/RoutingTableEntry',
'next_hop_ip': next_hop_ip,
'action': 'accept',
'uuid': route_id}
def test_prepare_body_with_implicit_routing_config(self):
router_name = 'fake_router_name'
tenant_id = 'fake_tenant_id'
router_type = 'SingleDefaultRouteImplicitRoutingConfig'
route_config = {
'default_route_next_hop': {'gateway_ip_address': 'fake_address',
'type': 'RouterNextHop'}, }
body = nvplib._prepare_lrouter_body(router_name, tenant_id,
router_type, **route_config)
expected = {'display_name': 'fake_router_name',
'routing_config': {
'default_route_next_hop':
{'gateway_ip_address': 'fake_address',
'type': 'RouterNextHop'},
'type': 'SingleDefaultRouteImplicitRoutingConfig'},
'tags': [{'scope': 'os_tid', 'tag': 'fake_tenant_id'},
{'scope': 'quantum', 'tag': '2013.1'}],
'type': 'LogicalRouterConfig'}
self.assertEqual(expected, body)
def test_prepare_body_without_routing_config(self):
router_name = 'fake_router_name'
tenant_id = 'fake_tenant_id'
router_type = 'RoutingTableRoutingConfig'
body = nvplib._prepare_lrouter_body(router_name, tenant_id,
router_type)
expected = {'display_name': 'fake_router_name',
'routing_config': {'type': 'RoutingTableRoutingConfig'},
'tags': [{'scope': 'os_tid', 'tag': 'fake_tenant_id'},
{'scope': 'quantum', 'tag': '2013.1'}],
'type': 'LogicalRouterConfig'}
self.assertEqual(expected, body)
def test_get_lrouter(self):
tenant_id = 'fake_tenant_id'
router_name = 'fake_router_name'
router_id = 'fake_router_id'
relations = {
'LogicalRouterStatus':
{'_href': '/ws.v1/lrouter/%s/status' % router_id,
'lport_admin_up_count': 1,
'_schema': '/ws.v1/schema/LogicalRouterStatus',
'lport_count': 1,
'fabric_status': True,
'type': 'LogicalRouterStatus',
'lport_link_up_count': 0, }, }
with mock.patch(_nicira_method('do_request'),
return_value=self._get_lrouter(tenant_id,
router_name,
router_id,
relations)):
lrouter = nvplib.get_lrouter(self.fake_cluster, router_id)
self.assertTrue(
lrouter['_relations']['LogicalRouterStatus']['fabric_status'])
def test_create_lrouter(self):
tenant_id = 'fake_tenant_id'
router_name = 'fake_router_name'
router_id = 'fake_router_id'
nexthop_ip = '10.0.0.1'
with mock.patch(_nicira_method('do_request'),
return_value=self._get_lrouter(tenant_id,
router_name,
router_id)):
lrouter = nvplib.create_lrouter(self.fake_cluster, tenant_id,
router_name, nexthop_ip)
self.assertEqual(lrouter['routing_config']['type'],
'RoutingTableRoutingConfig')
self.assertNotIn('default_route_next_hop',
lrouter['routing_config'])
def test_update_lrouter_nvp_with_no_routes(self):
router_id = 'fake_router_id'
new_routes = [{"nexthop": "10.0.0.2",
"destination": "169.254.169.0/30"}, ]
nvp_routes = [self._get_single_route(router_id)]
with mock.patch(_nicira_method('get_explicit_routes_lrouter'),
return_value=nvp_routes):
with mock.patch(_nicira_method('create_explicit_route_lrouter'),
return_value='fake_uuid'):
old_routes = nvplib.update_explicit_routes_lrouter(
self.fake_cluster, router_id, new_routes)
self.assertEqual(old_routes, nvp_routes)
def test_update_lrouter_nvp_with_no_routes_raise_nvp_exception(self):
router_id = 'fake_router_id'
new_routes = [{"nexthop": "10.0.0.2",
"destination": "169.254.169.0/30"}, ]
nvp_routes = [self._get_single_route(router_id)]
with mock.patch(_nicira_method('get_explicit_routes_lrouter'),
return_value=nvp_routes):
with mock.patch(_nicira_method('create_explicit_route_lrouter'),
side_effect=NvpApiClient.NvpApiException):
self.assertRaises(NvpApiClient.NvpApiException,
nvplib.update_explicit_routes_lrouter,
self.fake_cluster, router_id, new_routes)
def test_update_lrouter_with_routes(self):
router_id = 'fake_router_id'
new_routes = [{"next_hop_ip": "10.0.0.2",
"prefix": "169.254.169.0/30"}, ]
nvp_routes = [self._get_single_route(router_id),
self._get_single_route(router_id, 'fake_route_id_1',
'0.0.0.1/24', '10.0.0.3'),
self._get_single_route(router_id, 'fake_route_id_2',
'0.0.0.2/24', '10.0.0.4'), ]
with mock.patch(_nicira_method('get_explicit_routes_lrouter'),
return_value=nvp_routes):
with mock.patch(_nicira_method('delete_explicit_route_lrouter'),
return_value=None):
with mock.patch(_nicira_method(
'create_explicit_route_lrouter'),
return_value='fake_uuid'):
old_routes = nvplib.update_explicit_routes_lrouter(
self.fake_cluster, router_id, new_routes)
self.assertEqual(old_routes, nvp_routes)
def test_update_lrouter_with_routes_raises_nvp_expception(self):
router_id = 'fake_router_id'
new_routes = [{"nexthop": "10.0.0.2",
"destination": "169.254.169.0/30"}, ]
nvp_routes = [self._get_single_route(router_id),
self._get_single_route(router_id, 'fake_route_id_1',
'0.0.0.1/24', '10.0.0.3'),
self._get_single_route(router_id, 'fake_route_id_2',
'0.0.0.2/24', '10.0.0.4'), ]
with mock.patch(_nicira_method('get_explicit_routes_lrouter'),
return_value=nvp_routes):
with mock.patch(_nicira_method('delete_explicit_route_lrouter'),
side_effect=NvpApiClient.NvpApiException):
with mock.patch(
_nicira_method('create_explicit_route_lrouter'),
return_value='fake_uuid'):
self.assertRaises(
NvpApiClient.NvpApiException,
nvplib.update_explicit_routes_lrouter,
self.fake_cluster, router_id, new_routes)
class TestNvplibLogicalRouters(NvplibTestCase): class TestNvplibLogicalRouters(NvplibTestCase):
def _verify_lrouter(self, res_lrouter, def _verify_lrouter(self, res_lrouter,
@ -733,7 +918,7 @@ class TestNvplibLogicalRouters(NvplibTestCase):
'10.0.0.1') '10.0.0.1')
with mock.patch.object(self.fake_cluster.api_client, with mock.patch.object(self.fake_cluster.api_client,
'get_nvp_version', 'get_nvp_version',
new=lambda: version): new=lambda: NvpApiClient.NVPVersion(version)):
nvplib.create_lrouter_snat_rule( nvplib.create_lrouter_snat_rule(
self.fake_cluster, lrouter['uuid'], self.fake_cluster, lrouter['uuid'],
'10.0.0.2', '10.0.0.2', order=200, '10.0.0.2', '10.0.0.2', order=200,
@ -754,7 +939,7 @@ class TestNvplibLogicalRouters(NvplibTestCase):
'10.0.0.1') '10.0.0.1')
with mock.patch.object(self.fake_cluster.api_client, with mock.patch.object(self.fake_cluster.api_client,
'get_nvp_version', 'get_nvp_version',
return_value=version): return_value=NvpApiClient.NVPVersion(version)):
nvplib.create_lrouter_dnat_rule( nvplib.create_lrouter_dnat_rule(
self.fake_cluster, lrouter['uuid'], '192.168.0.2', order=200, self.fake_cluster, lrouter['uuid'], '192.168.0.2', order=200,
dest_port=dest_port, dest_port=dest_port,
@ -797,7 +982,7 @@ class TestNvplibLogicalRouters(NvplibTestCase):
'10.0.0.1') '10.0.0.1')
with mock.patch.object(self.fake_cluster.api_client, with mock.patch.object(self.fake_cluster.api_client,
'get_nvp_version', 'get_nvp_version',
new=lambda: version): new=lambda: NvpApiClient.NVPVersion(version)):
nvplib.create_lrouter_nosnat_rule( nvplib.create_lrouter_nosnat_rule(
self.fake_cluster, lrouter['uuid'], self.fake_cluster, lrouter['uuid'],
order=100, order=100,
@ -820,7 +1005,7 @@ class TestNvplibLogicalRouters(NvplibTestCase):
# v2 or v3 makes no difference for this test # v2 or v3 makes no difference for this test
with mock.patch.object(self.fake_cluster.api_client, with mock.patch.object(self.fake_cluster.api_client,
'get_nvp_version', 'get_nvp_version',
new=lambda: '2.0'): new=lambda: NvpApiClient.NVPVersion('2.0')):
nvplib.create_lrouter_snat_rule( nvplib.create_lrouter_snat_rule(
self.fake_cluster, lrouter['uuid'], self.fake_cluster, lrouter['uuid'],
'10.0.0.2', '10.0.0.2', order=220, '10.0.0.2', '10.0.0.2', order=220,
@ -1164,3 +1349,7 @@ class TestNvplibClusterVersion(NvplibTestCase):
with mock.patch.object(nvplib, 'do_request', new=fakedorequest): with mock.patch.object(nvplib, 'do_request', new=fakedorequest):
version = nvplib.get_cluster_version('whatever') version = nvplib.get_cluster_version('whatever')
self.assertIsNone(version) self.assertIsNone(version)
def _nicira_method(method_name, module_name='nvplib'):
return '%s.%s.%s' % ('neutron.plugins.nicira', module_name, method_name)