]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add support for the extra route extension in the NVP plugin.
authorarmando-migliaccio <amigliaccio@nicira.com>
Wed, 19 Jun 2013 01:24:03 +0000 (18:24 -0700)
committerarmando-migliaccio <amigliaccio@nicira.com>
Fri, 12 Jul 2013 20:21:17 +0000 (13:21 -0700)
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

neutron/plugins/nicira/NeutronPlugin.py
neutron/plugins/nicira/NvpApiClient.py
neutron/plugins/nicira/api_client/request.py
neutron/plugins/nicira/common/exceptions.py
neutron/plugins/nicira/nvplib.py
neutron/tests/unit/nicira/test_maclearning.py
neutron/tests/unit/nicira/test_nicira_plugin.py
neutron/tests/unit/nicira/test_nvplib.py

index c9d38b129164d054ead5e5b6a824bb05c5e38363..557521e0257fe7df2a6e1c3c7a49d20e63033c3e 100644 (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):
index 08cad9e920e08616566689fa051a813a18f2cbb4..d64c37216516594931c32b2fc8b180609fe843d5 100644 (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):
index 01d08bfda8eacb08a0fd396addda23a5bdc9a662..056e55cd8bb4d42f3b5f4208dec4ae6869e39776 100644 (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})
index 62acbf83866c3ace9b208b53e9d0e189c61f19a6..c18102440fa915af0fe3f60bc4ee739a00b6140d 100644 (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")
 
index 6f3780383fea19945888bdfaf6ea20b60062e815..88b2b848c10a33fedc9a8db3a7d6b3306c2b6ce9 100644 (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, }, },
 }
 
 
index db05e45f5fd521f01a5523b504c0b00c7801fcf2..2c46ac5179a96bac9f9c6ded8b8aaf132037c7ff 100644 (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)
index 3be010b028dae17c080dcf70134a73d45645329d..f95da9e153ac8239716586aa1d8be7bd958daa1b 100644 (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')
index 0a98f77c5d6d9e3c0e0352961319f7cbd3d298bb..bc860f9f4f3e29a22667a3eae5367af34adfb9b2 100644 (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'])
-
-    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'])
+        self._test_create_lrouter_dnat_rule('2.9')
+
+    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)