Merge "Allow nicira plugin to handle multiple NVP API versions"

This commit is contained in:
Jenkins 2013-02-17 02:32:07 +00:00 committed by Gerrit Code Review
commit f3b64bc985
5 changed files with 217 additions and 20 deletions

View 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()

View 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. "
@ -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,

View File

@ -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}
}

View 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)

View 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'])