Merge "Allow nicira plugin to handle multiple NVP API versions"
This commit is contained in:
commit
f3b64bc985
@ -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()
|
||||
|
||||
|
@ -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. "
|
||||
@ -1501,7 +1504,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"),
|
||||
@ -1679,13 +1683,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,
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
from copy import copy
|
||||
import hashlib
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
|
||||
@ -84,6 +85,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,
|
||||
@ -992,26 +1015,28 @@ 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}
|
||||
|
||||
nat_match_obj = _create_nat_match_obj(**kwargs)
|
||||
nat_rule_obj = {
|
||||
"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(**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(cluster, router_id, to_min_dst_ip,
|
||||
to_max_dst_ip, to_dst_port=None, **kwargs):
|
||||
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(**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_min": dst_ip,
|
||||
"to_destination_ip_address_max": dst_ip,
|
||||
"type": "DestinationNatRule",
|
||||
"match": nat_match_obj
|
||||
}
|
||||
@ -1020,6 +1045,41 @@ def create_lrouter_dnat_rule(cluster, router_id, to_min_dst_ip,
|
||||
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_v3(cluster, router_id, dst_ip, to_dst_port=None,
|
||||
order=None, match_criteria=None):
|
||||
|
||||
nat_match_obj = _create_nat_match_obj(**match_criteria)
|
||||
nat_rule_obj = {
|
||||
"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,
|
||||
@ -1115,3 +1175,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}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
95
quantum/tests/unit/nicira/test_nvplib.py
Normal file
95
quantum/tests/unit/nicira/test_nvplib.py
Normal file
@ -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'])
|
Loading…
Reference in New Issue
Block a user