]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Allow nicira plugin to handle multiple NVP API versions
authorSalvatore Orlando <sorlando@nicira.com>
Tue, 29 Jan 2013 00:41:53 +0000 (16:41 -0800)
committerSalvatore Orlando <salv.orlando@gmail.com>
Sat, 16 Feb 2013 00:38:26 +0000 (01:38 +0100)
Bug #1126352

This patch introduces a simple framework for enabling
nvlib to call the appropriate routine according to the
current version. To this aim, we leverage the 'server' header
which is returned by every NVP API calls (except login/logout).
The patch also accounts for the changes introduced in NVP 3.0

Change-Id: Ief3a6f2b5b99ea33548353ce3bee3fa23e42752f

quantum/plugins/nicira/nicira_nvp_plugin/NvpApiClient.py
quantum/plugins/nicira/nicira_nvp_plugin/QuantumPlugin.py
quantum/plugins/nicira/nicira_nvp_plugin/nvplib.py
quantum/tests/unit/nicira/test_nicira_plugin.py
quantum/tests/unit/nicira/test_nvplib.py [new file with mode: 0644]

index ae511e580fa009e750cb2847718738832d20f66a..d9f491e1a30774192033e7cb00eea0be01b31a16 100644 (file)
@@ -26,6 +26,17 @@ LOG = logging.getLogger("NVPApiHelper")
 LOG.setLevel(logging.INFO)
 
 
+def _find_nvp_version_in_headers(headers):
+    # be safe if headers is None - do not cause a failure
+    for (header_name, header_value) in (headers or ()):
+        try:
+            if header_name == 'server':
+                return header_value.split('/')[1]
+        except IndexError:
+            LOG.warning(_("Unable to fetch NVP version from response "
+                          "headers:%s"), headers)
+
+
 class NVPApiHelper(client_eventlet.NvpApiClientEventlet):
     '''
     Helper class to do basic login, cookie management, and provide base
@@ -62,7 +73,10 @@ class NVPApiHelper(client_eventlet.NvpApiClientEventlet):
         self._http_timeout = http_timeout
         self._retries = retries
         self._redirects = redirects
+        self._nvp_version = None
 
+    # NOTE(salvatore-orlando): This method is not used anymore. Login is now
+    # performed automatically inside the request eventlet if necessary.
     def login(self, user=None, password=None):
         '''Login to NVP controller.
 
@@ -130,8 +144,19 @@ class NVPApiHelper(client_eventlet.NvpApiClientEventlet):
                        'status': response.status, 'body': response.body})
             return None
 
+        if not self._nvp_version:
+            self._nvp_version = _find_nvp_version_in_headers(response.headers)
+
         return response.body
 
+    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')
+        return self._nvp_version
+
     def fourZeroFour(self):
         raise ResourceNotFound()
 
index dbbfe5c75afbc3a2de554f22ba1445047627e6ef..0f54173548bb9681855613be213ad5a205f53eb9 100644 (file)
@@ -61,6 +61,8 @@ from quantum.plugins.nicira.nicira_nvp_plugin.nvp_plugin_version import (
     PLUGIN_VERSION)
 
 LOG = logging.getLogger("QuantumPlugin")
+NVP_FLOATINGIP_NAT_RULES_ORDER = 200
+NVP_EXTGW_NAT_RULES_ORDER = 255
 
 
 # Provider network extension - allowed network types for the NVP Plugin
@@ -470,7 +472,8 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
                 cluster, router_id,
                 ip_addresses[0].split('/')[0],
                 ip_addresses[0].split('/')[0],
-                source_ip_addresses=cidr)
+                order=NVP_EXTGW_NAT_RULES_ORDER,
+                match_criteria={'source_ip_addresses': cidr})
 
         LOG.debug(_("_nvp_create_ext_gw_port completed on external network "
                     "%(ext_net_id)s, attached to router:%(router_id)s. "
@@ -1500,7 +1503,8 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
                 subnet = self._get_subnet(context, subnet_id)
                 nvplib.create_lrouter_snat_rule(
                     cluster, router_id, snat_ip, snat_ip,
-                    source_ip_addresses=subnet['cidr'])
+                    order=NVP_EXTGW_NAT_RULES_ORDER,
+                    match_criteria={'source_ip_addresses': subnet['cidr']})
 
         LOG.debug(_("Add_router_interface completed for subnet:%(subnet_id)s "
                     "and router:%(router_id)s"),
@@ -1678,13 +1682,16 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
                 try:
                     # Create new NAT rules
                     nvplib.create_lrouter_dnat_rule(
-                        cluster, router_id, internal_ip, internal_ip,
-                        destination_ip_addresses=floating_ip)
+                        cluster, router_id, internal_ip,
+                        order=NVP_FLOATINGIP_NAT_RULES_ORDER,
+                        match_criteria={'destination_ip_addresses':
+                                        floating_ip})
                     # setup snat rule such that src ip of a IP packet when
                     #  using floating is the floating ip itself.
                     nvplib.create_lrouter_snat_rule(
                         cluster, router_id, floating_ip, floating_ip,
-                        source_ip_addresses=internal_ip)
+                        order=NVP_FLOATINGIP_NAT_RULES_ORDER,
+                        match_criteria={'source_ip_addresses': internal_ip})
                     # Add Floating IP address to router_port
                     nvplib.update_lrouter_port_ips(cluster,
                                                    router_id,
index eac3aaf4fdf5f2394207830a68f0bf83a1c8df2a..388ba6882020ce5608212957b9dc78dd62080731 100644 (file)
@@ -22,6 +22,7 @@
 
 from copy import copy
 import hashlib
+import inspect
 import json
 import logging
 
@@ -82,6 +83,28 @@ _net_type_cache = {}  # cache of {net_id: network_type}
 _lqueue_cache = {}
 
 
+def version_dependent(func):
+    func_name = 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]
+        func_kwargs = kwargs
+        arg_spec = inspect.getargspec(real_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())
+            for arg in arg_set - set(arg_spec.args):
+                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 dispatch_version_dependent_function
+
+
 def _build_uri_path(resource,
                     resource_id=None,
                     parent_resource_id=None,
@@ -990,34 +1013,71 @@ def _create_lrouter_nat_rule(cluster, router_id, nat_rule_obj):
     return rule
 
 
-def create_lrouter_snat_rule(cluster, router_id,
-                             min_src_ip, max_src_ip, **kwargs):
+def _build_snat_rule_obj(min_src_ip, max_src_ip, nat_match_obj):
+    return {"to_source_ip_address_min": min_src_ip,
+            "to_source_ip_address_max": max_src_ip,
+            "type": "SourceNatRule",
+            "match": nat_match_obj}
+
+
+def create_lrouter_snat_rule_v2(cluster, router_id,
+                                min_src_ip, max_src_ip, match_criteria=None):
 
-    nat_match_obj = _create_nat_match_obj(**kwargs)
+    nat_match_obj = _create_nat_match_obj(**match_criteria)
+    nat_rule_obj = _build_snat_rule_obj(min_src_ip, max_src_ip, nat_match_obj)
+    return _create_lrouter_nat_rule(cluster, router_id, nat_rule_obj)
+
+
+def create_lrouter_dnat_rule_v2(cluster, router_id, dst_ip,
+                                to_dst_port=None, match_criteria=None):
+
+    nat_match_obj = _create_nat_match_obj(**match_criteria)
     nat_rule_obj = {
-        "to_source_ip_address_min": min_src_ip,
-        "to_source_ip_address_max": max_src_ip,
-        "type": "SourceNatRule",
+        "to_destination_ip_address_min": dst_ip,
+        "to_destination_ip_address_max": dst_ip,
+        "type": "DestinationNatRule",
         "match": nat_match_obj
     }
+    if to_dst_port:
+        nat_rule_obj['to_destination_port'] = to_dst_port
+    return _create_lrouter_nat_rule(cluster, router_id, nat_rule_obj)
+
+
+def create_lrouter_snat_rule_v3(cluster, router_id, min_src_ip, max_src_ip,
+                                order=None, match_criteria=None):
+    nat_match_obj = _create_nat_match_obj(**match_criteria)
+    nat_rule_obj = _build_snat_rule_obj(min_src_ip, max_src_ip, nat_match_obj)
+    if order:
+        nat_rule_obj['order'] = order
     return _create_lrouter_nat_rule(cluster, router_id, nat_rule_obj)
 
 
-def create_lrouter_dnat_rule(cluster, router_id, to_min_dst_ip,
-                             to_max_dst_ip, to_dst_port=None, **kwargs):
+def create_lrouter_dnat_rule_v3(cluster, router_id, dst_ip, to_dst_port=None,
+                                order=None, match_criteria=None):
 
-    nat_match_obj = _create_nat_match_obj(**kwargs)
+    nat_match_obj = _create_nat_match_obj(**match_criteria)
     nat_rule_obj = {
-        "to_destination_ip_address_min": to_min_dst_ip,
-        "to_destination_ip_address_max": to_max_dst_ip,
+        "to_destination_ip_address": dst_ip,
         "type": "DestinationNatRule",
         "match": nat_match_obj
     }
     if to_dst_port:
         nat_rule_obj['to_destination_port'] = to_dst_port
+    if order:
+        nat_rule_obj['order'] = order
     return _create_lrouter_nat_rule(cluster, router_id, nat_rule_obj)
 
 
+@version_dependent
+def create_lrouter_dnat_rule(cluster, *args, **kwargs):
+    pass
+
+
+@version_dependent
+def create_lrouter_snat_rule(cluster, *args, **kwargs):
+    pass
+
+
 def delete_nat_rules_by_match(cluster, router_id, rule_type,
                               max_num_expected,
                               min_num_expected=0,
@@ -1113,3 +1173,12 @@ def update_lrouter_port_ips(cluster, lrouter_id, lport_id,
                 "router logical port:%s") % str(e)
         LOG.exception(msg)
         raise nvp_exc.NvpPluginException(err_desc=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}
+}
index ae47fb9a4b7348320063e0465d51f3e36cdd55ce..7202dc336c1dd67bfc353307d3f8a0e73697af5f 100644 (file)
@@ -70,11 +70,12 @@ class NiciraPluginV2TestCase(test_plugin.QuantumDbPluginV2TestCase):
         self.mock_nvpapi = mock.patch('%s.NvpApiClient.NVPApiHelper'
                                       % NICIRA_PKG_PATH, autospec=True)
         instance = self.mock_nvpapi.start()
-        instance.return_value.login.return_value = "the_cookie"
 
         def _fake_request(*args, **kwargs):
             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.request.side_effect = _fake_request
         super(NiciraPluginV2TestCase, self).setUp(self._plugin_name)
 
diff --git a/quantum/tests/unit/nicira/test_nvplib.py b/quantum/tests/unit/nicira/test_nvplib.py
new file mode 100644 (file)
index 0000000..24ac76d
--- /dev/null
@@ -0,0 +1,95 @@
+# Copyright (c) 2013 OpenStack, LLC.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# @author: Salvatore Orlando, VMware
+
+import json
+import os
+
+import mock
+import unittest2 as unittest
+
+from quantum.openstack.common import log as logging
+from quantum.plugins.nicira.nicira_nvp_plugin import NvpApiClient
+from quantum.plugins.nicira.nicira_nvp_plugin import nvp_cluster
+from quantum.plugins.nicira.nicira_nvp_plugin import nvplib
+import quantum.plugins.nicira.nicira_nvp_plugin as nvp_plugin
+from quantum.tests.unit.nicira import fake_nvpapiclient
+from quantum.tests.unit import test_api_v2
+
+LOG = logging.getLogger(__name__)
+NICIRA_PKG_PATH = nvp_plugin.__name__
+_uuid = test_api_v2._uuid
+
+
+class TestNvplibNatRules(unittest.TestCase):
+
+    def setUp(self):
+        # mock nvp api client
+        etc_path = os.path.join(os.path.dirname(__file__), 'etc')
+        self.fc = fake_nvpapiclient.FakeClient(etc_path)
+        self.mock_nvpapi = mock.patch('%s.NvpApiClient.NVPApiHelper'
+                                      % NICIRA_PKG_PATH, autospec=True)
+        instance = self.mock_nvpapi.start()
+
+        def _fake_request(*args, **kwargs):
+            return self.fc.fake_request(*args, **kwargs)
+
+        instance.return_value.request.side_effect = _fake_request
+        self.fake_cluster = nvp_cluster.NVPCluster('fake-cluster')
+        self.fake_cluster.add_controller('1.1.1.1', '999', 'foo', 'bar',
+                                         9, 9, 9, 9, _uuid())
+        self.fake_cluster.api_client = NvpApiClient.NVPApiHelper(
+            ('1.1.1.1', '999', True),
+            self.fake_cluster.user, self.fake_cluster.password,
+            self.fake_cluster.request_timeout, self.fake_cluster.http_timeout,
+            self.fake_cluster.retries, self.fake_cluster.redirects)
+
+        super(TestNvplibNatRules, self).setUp()
+
+    def tearDown(self):
+        self.fc.reset_all()
+        self.mock_nvpapi.stop()
+
+    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 json.loads(nvplib.do_single_request("GET", uri,
+                                                   cluster=self.fake_cluster))
+
+    def test_create_lrouter_dnat_rule_v2(self):
+        resp_obj = self._test_create_lrouter_dnat_rule(
+            nvplib.create_lrouter_dnat_rule_v2)
+        self.assertEquals('DestinationNatRule', resp_obj['type'])
+        self.assertEquals('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.assertEquals('DestinationNatRule', resp_obj['type'])
+        self.assertEquals('192.168.0.5',
+                          resp_obj['match']['destination_ip_addresses'])