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)
|
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):
|
class NVPApiHelper(client_eventlet.NvpApiClientEventlet):
|
||||||
'''
|
'''
|
||||||
Helper class to do basic login, cookie management, and provide base
|
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._http_timeout = http_timeout
|
||||||
self._retries = retries
|
self._retries = retries
|
||||||
self._redirects = redirects
|
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):
|
def login(self, user=None, password=None):
|
||||||
'''Login to NVP controller.
|
'''Login to NVP controller.
|
||||||
|
|
||||||
@ -130,8 +144,19 @@ class NVPApiHelper(client_eventlet.NvpApiClientEventlet):
|
|||||||
'status': response.status, 'body': response.body})
|
'status': response.status, 'body': response.body})
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if not self._nvp_version:
|
||||||
|
self._nvp_version = _find_nvp_version_in_headers(response.headers)
|
||||||
|
|
||||||
return response.body
|
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):
|
def fourZeroFour(self):
|
||||||
raise ResourceNotFound()
|
raise ResourceNotFound()
|
||||||
|
|
||||||
|
@ -61,6 +61,8 @@ from quantum.plugins.nicira.nicira_nvp_plugin.nvp_plugin_version import (
|
|||||||
PLUGIN_VERSION)
|
PLUGIN_VERSION)
|
||||||
|
|
||||||
LOG = logging.getLogger("QuantumPlugin")
|
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
|
# Provider network extension - allowed network types for the NVP Plugin
|
||||||
@ -470,7 +472,8 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
|
|||||||
cluster, router_id,
|
cluster, router_id,
|
||||||
ip_addresses[0].split('/')[0],
|
ip_addresses[0].split('/')[0],
|
||||||
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 "
|
LOG.debug(_("_nvp_create_ext_gw_port completed on external network "
|
||||||
"%(ext_net_id)s, attached to router:%(router_id)s. "
|
"%(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)
|
subnet = self._get_subnet(context, subnet_id)
|
||||||
nvplib.create_lrouter_snat_rule(
|
nvplib.create_lrouter_snat_rule(
|
||||||
cluster, router_id, snat_ip, snat_ip,
|
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 "
|
LOG.debug(_("Add_router_interface completed for subnet:%(subnet_id)s "
|
||||||
"and router:%(router_id)s"),
|
"and router:%(router_id)s"),
|
||||||
@ -1679,13 +1683,16 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
|
|||||||
try:
|
try:
|
||||||
# Create new NAT rules
|
# Create new NAT rules
|
||||||
nvplib.create_lrouter_dnat_rule(
|
nvplib.create_lrouter_dnat_rule(
|
||||||
cluster, router_id, internal_ip, internal_ip,
|
cluster, router_id, internal_ip,
|
||||||
destination_ip_addresses=floating_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
|
# setup snat rule such that src ip of a IP packet when
|
||||||
# using floating is the floating ip itself.
|
# using floating is the floating ip itself.
|
||||||
nvplib.create_lrouter_snat_rule(
|
nvplib.create_lrouter_snat_rule(
|
||||||
cluster, router_id, floating_ip, floating_ip,
|
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
|
# Add Floating IP address to router_port
|
||||||
nvplib.update_lrouter_port_ips(cluster,
|
nvplib.update_lrouter_port_ips(cluster,
|
||||||
router_id,
|
router_id,
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
|
|
||||||
from copy import copy
|
from copy import copy
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import inspect
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -84,6 +85,28 @@ _net_type_cache = {} # cache of {net_id: network_type}
|
|||||||
_lqueue_cache = {}
|
_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,
|
def _build_uri_path(resource,
|
||||||
resource_id=None,
|
resource_id=None,
|
||||||
parent_resource_id=None,
|
parent_resource_id=None,
|
||||||
@ -992,26 +1015,28 @@ def _create_lrouter_nat_rule(cluster, router_id, nat_rule_obj):
|
|||||||
return rule
|
return rule
|
||||||
|
|
||||||
|
|
||||||
def create_lrouter_snat_rule(cluster, router_id,
|
def _build_snat_rule_obj(min_src_ip, max_src_ip, nat_match_obj):
|
||||||
min_src_ip, max_src_ip, **kwargs):
|
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 = {
|
def create_lrouter_snat_rule_v2(cluster, router_id,
|
||||||
"to_source_ip_address_min": min_src_ip,
|
min_src_ip, max_src_ip, match_criteria=None):
|
||||||
"to_source_ip_address_max": max_src_ip,
|
|
||||||
"type": "SourceNatRule",
|
nat_match_obj = _create_nat_match_obj(**match_criteria)
|
||||||
"match": nat_match_obj
|
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)
|
return _create_lrouter_nat_rule(cluster, router_id, nat_rule_obj)
|
||||||
|
|
||||||
|
|
||||||
def create_lrouter_dnat_rule(cluster, router_id, to_min_dst_ip,
|
def create_lrouter_dnat_rule_v2(cluster, router_id, dst_ip,
|
||||||
to_max_dst_ip, to_dst_port=None, **kwargs):
|
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 = {
|
nat_rule_obj = {
|
||||||
"to_destination_ip_address_min": to_min_dst_ip,
|
"to_destination_ip_address_min": dst_ip,
|
||||||
"to_destination_ip_address_max": to_max_dst_ip,
|
"to_destination_ip_address_max": dst_ip,
|
||||||
"type": "DestinationNatRule",
|
"type": "DestinationNatRule",
|
||||||
"match": nat_match_obj
|
"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)
|
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,
|
def delete_nat_rules_by_match(cluster, router_id, rule_type,
|
||||||
max_num_expected,
|
max_num_expected,
|
||||||
min_num_expected=0,
|
min_num_expected=0,
|
||||||
@ -1115,3 +1175,12 @@ def update_lrouter_port_ips(cluster, lrouter_id, lport_id,
|
|||||||
"router logical port:%s") % str(e)
|
"router logical port:%s") % str(e)
|
||||||
LOG.exception(msg)
|
LOG.exception(msg)
|
||||||
raise nvp_exc.NvpPluginException(err_desc=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'
|
self.mock_nvpapi = mock.patch('%s.NvpApiClient.NVPApiHelper'
|
||||||
% NICIRA_PKG_PATH, autospec=True)
|
% NICIRA_PKG_PATH, autospec=True)
|
||||||
instance = self.mock_nvpapi.start()
|
instance = self.mock_nvpapi.start()
|
||||||
instance.return_value.login.return_value = "the_cookie"
|
|
||||||
|
|
||||||
def _fake_request(*args, **kwargs):
|
def _fake_request(*args, **kwargs):
|
||||||
return self.fc.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
|
instance.return_value.request.side_effect = _fake_request
|
||||||
super(NiciraPluginV2TestCase, self).setUp(self._plugin_name)
|
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