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

The underlying feature is available in NVP 3.2, which introduces a
new type of router. Therefore, create_lrouter needs to be made
version 'aware'.

This led to a number of fixes in the nvplib, especially around how
version is retrieved and how version-dependent methods are called.

Implements blueprint nvp-extra-route-extension

Change-Id: Ie4e2d93f70e1948a62563c8523aea61bb2194c84
This commit is contained in:
armando-migliaccio 2013-06-18 18:24:03 -07:00
parent 2890f4c956
commit 3c881595d7
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 db_base_plugin_v2
from neutron.db import dhcp_rpc_base
from neutron.db import extraroute_db
from neutron.db import l3_db
from neutron.db import models_v2
from neutron.db import portsecurity_db
from neutron.db import quota_db # noqa
from neutron.db import securitygroups_db
from neutron.extensions import extraroute
from neutron.extensions import l3
from neutron.extensions import portsecurity as psec
from neutron.extensions import providernet as pnet
@ -124,7 +126,7 @@ class NVPRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin):
class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
l3_db.L3_NAT_db_mixin,
extraroute_db.ExtraRoute_db_mixin,
portsecurity_db.PortSecurityDbMixin,
securitygroups_db.SecurityGroupDbMixin,
mac_db.MacLearningDbMixin,
@ -139,7 +141,8 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
functionality using NVP.
"""
supported_extension_aliases = ["mac-learning",
supported_extension_aliases = ["extraroute",
"mac-learning",
"network-gateway",
"nvp-qos",
"port-security",
@ -1458,7 +1461,7 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
self._update_router_gw_info(context, router_db['id'], gw_info)
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
r = router['router']
nexthop = None
@ -1479,22 +1482,45 @@ class NvpPluginV2(db_base_plugin_v2.NeutronDbPluginV2,
ext_subnet = ext_net.subnets[0]
nexthop = ext_subnet.gateway_ip
try:
nvplib.update_lrouter(self.cluster, id,
router['router'].get('name'), nexthop)
for route in r.get('routes', []):
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
# unfortunately nvplib raises a neutron notfound exception when an
# object is not found in the underlying backend
except q_exc.NotFound:
# Put the router in ERROR status
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
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:
raise nvp_exc.NvpPluginException(
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):
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 ()):
try:
if header_name == 'server':
return header_value.split('/')[1]
return NVPVersion(header_value.split('/')[1])
except IndexError:
LOG.warning(_("Unable to fetch NVP version from response "
"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):
'''API helper class.
@ -153,10 +165,13 @@ class NVPApiHelper(client_eventlet.NvpApiClientEventlet):
def get_nvp_version(self):
if not self._nvp_version:
# generate a simple request (/ws.v1/log)
# this will cause nvp_version to be fetched
# don't bother about response
self.request('GET', '/ws.v1/log')
# Determine the NVP version by querying the control
# cluster nodes. Currently, the version will be the
# one of the server that responds.
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
def fourZeroFour(self):

View File

@ -190,8 +190,9 @@ class NvpApiRequest(object):
# the conn to be released with is_conn_error == True
# which puts the conn on the back of the client's priority
# queue.
if response.status >= 500:
LOG.warn(_("[%(rid)d] Request '%(method) %(url)s' "
if (response.status == httplib.INTERNAL_SERVER_ERROR and
response.status > httplib.NOT_IMPLEMENTED):
LOG.warn(_("[%(rid)d] Request '%(method)s %(url)s' "
"received: %(status)s"),
{'rid': self._rid(), 'method': self._method,
'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")
class NvpInvalidVersion(NvpPluginException):
message = _("Unable to fulfill request with version %(version)s.")
class NvpInvalidConnection(NvpPluginException):
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
from neutron.common import constants
from neutron.common import exceptions as exception
from neutron.openstack.common import excutils
from neutron.openstack.common import log
from neutron.plugins.nicira.common import (
exceptions as nvp_exc)
@ -48,11 +49,12 @@ URI_PREFIX = "/ws.v1"
LSWITCH_RESOURCE = "lswitch"
LSWITCHPORT_RESOURCE = "lport/%s" % LSWITCH_RESOURCE
LROUTER_RESOURCE = "lrouter"
# Current neutron version
LROUTERPORT_RESOURCE = "lport/%s" % LROUTER_RESOURCE
LROUTERRIB_RESOURCE = "rib/%s" % LROUTER_RESOURCE
LROUTERNAT_RESOURCE = "nat/lrouter"
LQUEUE_RESOURCE = "lqueue"
GWSERVICE_RESOURCE = "gateway-service"
# Current neutron version
NEUTRON_VERSION = "2013.1"
# Other constants for NVP resource
MAX_DISPLAY_NAME_LEN = 40
@ -74,16 +76,29 @@ taken_context_ids = []
_lqueue_cache = {}
def version_dependent(func):
func_name = func.__name__
def version_dependent(wrapped_func):
func_name = wrapped_func.__name__
def dispatch_version_dependent_function(cluster, *args, **kwargs):
nvp_ver = cluster.api_client.get_nvp_version()
if nvp_ver:
ver_major = int(nvp_ver.split('.')[0])
real_func = NVPLIB_FUNC_DICT[func_name][ver_major]
# Call the wrapper function, in case we need to
# run validation checks regarding versions. It
# should return the NVP version
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
arg_spec = inspect.getargspec(real_func)
arg_spec = inspect.getargspec(func)
if not arg_spec.keywords and not arg_spec.varargs:
# drop args unknown to function from func_args
arg_set = set(func_kwargs.keys())
@ -91,7 +106,7 @@ def version_dependent(func):
del func_kwargs[arg]
# NOTE(salvatore-orlando): shall we fail here if a required
# 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
@ -284,7 +299,22 @@ def create_l2_gw_service(cluster, tenant_id, display_name, devices):
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.
: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
with the NVP controller
"""
display_name = _check_and_truncate_name(display_name)
tags = [{"tag": tenant_id, "scope": "os_tid"},
{"tag": NEUTRON_VERSION, "scope": "quantum"}]
lrouter_obj = {
"display_name": display_name,
"tags": tags,
"routing_config": {
"default_route_next_hop": {
"gateway_ip_address": nexthop,
"type": "RouterNextHop"
},
"type": "SingleDefaultRouteImplicitRoutingConfig"
implicit_routing_config = {
"default_route_next_hop": {
"gateway_ip_address": nexthop,
"type": "RouterNextHop"
},
"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),
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):
do_request(HTTP_DELETE, _build_uri_path(LROUTER_RESOURCE,
resource_id=lrouter_id),
@ -381,8 +422,8 @@ def update_l2_gw_service(cluster, gateway_id, display_name):
json.dumps(gwservice_obj), cluster=cluster)
def update_lrouter(cluster, lrouter_id, display_name, nexthop):
lrouter_obj = get_lrouter(cluster, lrouter_id)
def update_implicit_routing_lrouter(cluster, r_id, display_name, nexthop):
lrouter_obj = get_lrouter(cluster, r_id)
if not display_name and not nexthop:
# Nothing to update
return lrouter_obj
@ -395,11 +436,150 @@ def update_lrouter(cluster, lrouter_id, display_name, nexthop):
if nh_element:
nh_element["gateway_ip_address"] = nexthop
return do_request(HTTP_PUT, _build_uri_path(LROUTER_RESOURCE,
resource_id=lrouter_id),
resource_id=r_id),
json.dumps(lrouter_obj),
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):
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)
# TODO(salvatore-orlando): Also handle changes in minor versions
NVPLIB_FUNC_DICT = {
'create_lrouter_dnat_rule': {2: create_lrouter_dnat_rule_v2,
3: create_lrouter_dnat_rule_v3},
'create_lrouter_snat_rule': {2: create_lrouter_snat_rule_v2,
3: create_lrouter_snat_rule_v3},
'create_lrouter_nosnat_rule': {2: create_lrouter_nosnat_rule_v2,
3: create_lrouter_nosnat_rule_v3}
'create_lrouter': {
2: {'default': create_implicit_routing_lrouter, },
3: {'default': create_implicit_routing_lrouter,
2: create_explicit_routing_lrouter, }, },
'update_lrouter': {
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.openstack.common import log as logging
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 import test_db_plugin
@ -84,7 +85,7 @@ class MacLearningDBTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
return self.fc.fake_request(*args, **kwargs)
# 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
cfg.CONF.set_override('metadata_mode', None, 'NVP')
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 import NeutronPlugin
from neutron.plugins.nicira import NvpApiClient
from neutron.plugins.nicira.NvpApiClient import NVPVersion
from neutron.plugins.nicira import nvplib
from neutron.tests.unit.nicira import fake_nvpapiclient
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)
# 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
super(NiciraPluginV2TestCase, self).setUp(self._plugin_name)
cfg.CONF.set_override('metadata_mode', None, 'NVP')

View File

@ -44,6 +44,9 @@ class NvplibTestCase(base.BaseTestCase):
% NICIRA_PKG_PATH, autospec=True)
instance = self.mock_nvpapi.start()
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):
return self.fc.fake_request(*args, **kwargs)
@ -69,35 +72,32 @@ class NvplibTestCase(base.BaseTestCase):
class TestNvplibNatRules(NvplibTestCase):
def _test_create_lrouter_dnat_rule(self, func):
tenant_id = 'pippo'
lrouter = nvplib.create_lrouter(self.fake_cluster,
tenant_id,
'fake_router',
'192.168.0.1')
nat_rule = func(self.fake_cluster, lrouter['uuid'], '10.0.0.99',
match_criteria={'destination_ip_addresses':
'192.168.0.5'})
uri = nvplib._build_uri_path(nvplib.LROUTERNAT_RESOURCE,
nat_rule['uuid'],
lrouter['uuid'])
return nvplib.do_request("GET", uri, cluster=self.fake_cluster)
def _test_create_lrouter_dnat_rule(self, version):
with mock.patch.object(self.fake_cluster.api_client,
'get_nvp_version',
new=lambda: NvpApiClient.NVPVersion(version)):
tenant_id = 'pippo'
lrouter = nvplib.create_lrouter(self.fake_cluster,
tenant_id,
'fake_router',
'192.168.0.1')
nat_rule = nvplib.create_lrouter_dnat_rule(
self.fake_cluster, lrouter['uuid'], '10.0.0.99',
match_criteria={'destination_ip_addresses':
'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):
resp_obj = self._test_create_lrouter_dnat_rule(
nvplib.create_lrouter_dnat_rule_v2)
self.assertEqual('DestinationNatRule', resp_obj['type'])
self.assertEqual('192.168.0.5',
resp_obj['match']['destination_ip_addresses'])
self._test_create_lrouter_dnat_rule('2.9')
def test_create_lrouter_dnat_rule_v3(self):
resp_obj = self._test_create_lrouter_dnat_rule(
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'])
def test_create_lrouter_dnat_rule_v31(self):
self._test_create_lrouter_dnat_rule('3.1')
class NvplibNegativeTests(base.BaseTestCase):
@ -110,6 +110,10 @@ class NvplibNegativeTests(base.BaseTestCase):
% NICIRA_PKG_PATH, autospec=True)
instance = self.mock_nvpapi.start()
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):
raise nvplib.NvpApiClient.NvpApiException
@ -365,6 +369,187 @@ class TestNvplibLogicalSwitches(NvplibTestCase):
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):
def _verify_lrouter(self, res_lrouter,
@ -733,7 +918,7 @@ class TestNvplibLogicalRouters(NvplibTestCase):
'10.0.0.1')
with mock.patch.object(self.fake_cluster.api_client,
'get_nvp_version',
new=lambda: version):
new=lambda: NvpApiClient.NVPVersion(version)):
nvplib.create_lrouter_snat_rule(
self.fake_cluster, lrouter['uuid'],
'10.0.0.2', '10.0.0.2', order=200,
@ -754,7 +939,7 @@ class TestNvplibLogicalRouters(NvplibTestCase):
'10.0.0.1')
with mock.patch.object(self.fake_cluster.api_client,
'get_nvp_version',
return_value=version):
return_value=NvpApiClient.NVPVersion(version)):
nvplib.create_lrouter_dnat_rule(
self.fake_cluster, lrouter['uuid'], '192.168.0.2', order=200,
dest_port=dest_port,
@ -797,7 +982,7 @@ class TestNvplibLogicalRouters(NvplibTestCase):
'10.0.0.1')
with mock.patch.object(self.fake_cluster.api_client,
'get_nvp_version',
new=lambda: version):
new=lambda: NvpApiClient.NVPVersion(version)):
nvplib.create_lrouter_nosnat_rule(
self.fake_cluster, lrouter['uuid'],
order=100,
@ -820,7 +1005,7 @@ class TestNvplibLogicalRouters(NvplibTestCase):
# v2 or v3 makes no difference for this test
with mock.patch.object(self.fake_cluster.api_client,
'get_nvp_version',
new=lambda: '2.0'):
new=lambda: NvpApiClient.NVPVersion('2.0')):
nvplib.create_lrouter_snat_rule(
self.fake_cluster, lrouter['uuid'],
'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):
version = nvplib.get_cluster_version('whatever')
self.assertIsNone(version)
def _nicira_method(method_name, module_name='nvplib'):
return '%s.%s.%s' % ('neutron.plugins.nicira', module_name, method_name)