Multi version API support

Add version '2' for support version of --os-tacker-api-version to
'openstack vnflcm' CLI.

If '--os-tacker-api-version 2' is specified, REST API url
'/vnflcm/v2' is used instead of '/vnflcm/v1'.

New CLI 'openstack vnflcm versions' is added too.

Implements: blueprint multi-version-api
Change-Id: I256a4010043d0b84ffe43b055c6a6a67a2d5d661
This commit is contained in:
Itsuro Oda
2021-08-19 04:24:59 +00:00
parent 6325127c03
commit 98789f8f9a
13 changed files with 290 additions and 31 deletions

View File

@@ -100,3 +100,14 @@ openstack.tackerclient.v1 =
vnflcm_op_retry = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:RetryVnfLcmOp
vnflcm_op_list = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:ListVnfLcmOp
vnflcm_op_show = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:ShowVnfLcmOp
vnflcm_versions = tackerclient.osc.common.vnflcm.vnflcm_versions:VnfLcmVersions
openstack.tackerclient.v2 =
vnflcm_create = tackerclient.osc.v1.vnflcm.vnflcm:CreateVnfLcm
vnflcm_show = tackerclient.osc.v1.vnflcm.vnflcm:ShowVnfLcm
vnflcm_list = tackerclient.osc.v1.vnflcm.vnflcm:ListVnfLcm
vnflcm_instantiate = tackerclient.osc.v1.vnflcm.vnflcm:InstantiateVnfLcm
vnflcm_terminate = tackerclient.osc.v1.vnflcm.vnflcm:TerminateVnfLcm
vnflcm_delete = tackerclient.osc.v1.vnflcm.vnflcm:DeleteVnfLcm
vnflcm_op_list = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:ListVnfLcmOp
vnflcm_op_show = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:ShowVnfLcmOp
vnflcm_versions = tackerclient.osc.common.vnflcm.vnflcm_versions:VnfLcmVersions

View File

View File

@@ -0,0 +1,49 @@
# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation
# All Rights Reserved.
#
# 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.
from osc_lib.command import command
from tackerclient.common import exceptions
from tackerclient.i18n import _
SUPPORTED_VERSIONS = [1, 2]
class VnfLcmVersions(command.ShowOne):
_description = _("Show VnfLcm Api versions")
def get_parser(self, prog_name):
parser = super(VnfLcmVersions, self).get_parser(prog_name)
parser.add_argument(
'--major-version',
metavar="<major-version>",
type=int,
help=_('Show only specify major version.'))
return parser
def take_action(self, parsed_args):
v = None
if parsed_args.major_version:
if parsed_args.major_version not in SUPPORTED_VERSIONS:
msg = _("Major version %d is not supported")
reason = msg % parsed_args.major_version
raise exceptions.InvalidInput(reason=reason)
v = "v{}".format(parsed_args.major_version)
client = self.app.client_manager.tackerclient
data = client.show_vnf_lcm_versions(v)
return (tuple(data.keys()), tuple(data.values()))

View File

@@ -26,15 +26,17 @@ API_NAME = 'tackerclient'
API_VERSION_OPTION = 'os_tacker_api_version'
API_VERSIONS = {
'1': 'tackerclient.v1_0.client.Client',
'2': 'tackerclient.v1_0.client.Client',
}
def make_client(instance):
"""Returns a client to the ClientManager."""
api_version = instance._api_version[API_NAME]
tacker_client = utils.get_client_class(
API_NAME,
instance._api_version[API_NAME],
api_version,
API_VERSIONS)
LOG.debug('Instantiating tacker client: %s', tacker_client)
@@ -42,7 +44,8 @@ def make_client(instance):
'region_name': instance._region_name,
'endpoint_type': instance._interface,
'interface': instance._interface,
'session': instance.session
'session': instance.session,
'api_version': api_version
}
client = tacker_client(**kwargs)

View File

@@ -22,6 +22,7 @@ from cliff import columns as cliff_columns
class FixturedTestCase(testtools.TestCase):
client_fixture_class = None
api_version = '1'
def setUp(self):
super(FixturedTestCase, self).setUp()
@@ -29,7 +30,8 @@ class FixturedTestCase(testtools.TestCase):
if self.client_fixture_class:
self.requests_mock = self.useFixture(requests_mock_fixture.
Fixture())
fix = self.client_fixture_class(self.requests_mock)
fix = self.client_fixture_class(self.requests_mock,
api_version=self.api_version)
self.cs = self.useFixture(fix).client
def check_parser(self, cmd, args, verify_args):

View File

@@ -0,0 +1,101 @@
# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation
# All Rights Reserved.
#
# 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.
import os
import ddt
from unittest import mock
from tackerclient.common import exceptions
from tackerclient.osc.common.vnflcm import vnflcm_versions
from tackerclient.tests.unit.osc import base
from tackerclient.tests.unit.osc.v1.fixture_data import client
class TestVnfLcm(base.FixturedTestCase):
client_fixture_class = client.ClientFixture
def setUp(self):
super(TestVnfLcm, self).setUp()
self.url = client.TACKER_URL
self.header = {'content-type': 'application/json'}
self.app = mock.Mock()
self.app_args = mock.Mock()
self.client_manager = self.cs
self.app.client_manager.tackerclient = self.client_manager
@ddt.ddt
class TestVnfLcmVersions(TestVnfLcm):
def setUp(self):
super(TestVnfLcmVersions, self).setUp()
self.vnflcm_versions = vnflcm_versions.VnfLcmVersions(
self.app, self.app_args, cmd_name='vnflcm versions')
def _versions_response(self, major_version=None):
if major_version is None:
return {"uriPrefix": "/vnflcm",
"apiVersions": [{"version": "1.3.0",
"isDeprecated": False},
{"version": "2.0.0",
"isDeprecated": False}]}
elif major_version == "1":
return {"uriPrefix": "/vnflcm/v1",
"apiVersions": [{"version": "1.3.0",
"isDeprecated": False}]}
elif major_version == "2":
return {"uriPrefix": "/vnflcm/v2",
"apiVersions": [{"version": "2.0.0",
"isDeprecated": False}]}
def test_invalid_major_version(self):
parser = self.vnflcm_versions.get_parser('vnflcm versions')
parsed_args = parser.parse_args(["--major-version", "3"])
self.assertRaises(exceptions.InvalidInput,
self.vnflcm_versions.take_action,
parsed_args)
def test_take_action_no_arg(self):
parser = self.vnflcm_versions.get_parser('vnflcm versions')
parsed_args = parser.parse_args([])
response = self._versions_response()
self.requests_mock.register_uri(
'GET', os.path.join(self.url, 'vnflcm/api_versions'),
json=response, headers=self.header)
colmns, data = self.vnflcm_versions.take_action(parsed_args)
self.assertEqual(colmns, tuple(response.keys()))
self.assertEqual(data, tuple(response.values()))
@ddt.data('1', '2')
def test_take_action_with_major_version(self, major_version):
parser = self.vnflcm_versions.get_parser('vnflcm versions')
parsed_args = parser.parse_args(["--major-version",
major_version])
response = self._versions_response(major_version)
self.requests_mock.register_uri(
'GET',
os.path.join(self.url,
'vnflcm/v{}/api_versions'.format(major_version)),
json=response, headers=self.header)
colmns, data = self.vnflcm_versions.take_action(parsed_args)
self.assertEqual(colmns, tuple(response.keys()))
self.assertEqual(data, tuple(response.values()))

View File

@@ -25,7 +25,8 @@ TACKER_URL = 'http://nfv-orchestration'
class ClientFixture(fixtures.Fixture):
def __init__(self, requests_mock, identity_url=IDENTITY_URL):
def __init__(self, requests_mock, identity_url=IDENTITY_URL,
api_version='1'):
super(ClientFixture, self).__init__()
self.identity_url = identity_url
self.client = None
@@ -35,6 +36,7 @@ class ClientFixture(fixtures.Fixture):
self.discovery = fixture.V2Discovery(href=self.identity_url)
s = self.token.add_service('nfv-orchestration')
s.add_endpoint(TACKER_URL)
self.api_version = api_version
def setUp(self):
super(ClientFixture, self).setUp()
@@ -57,4 +59,5 @@ class ClientFixture(fixtures.Fixture):
region_name='RegionOne',
auth_url=self.identity_url,
token=self.token.token_id,
endpoint_url=TACKER_URL)
endpoint_url=TACKER_URL,
api_version=self.api_version)

View File

@@ -848,3 +848,18 @@ class TestChangeExtConnVnfLcm(TestVnfLcm):
expected_msg = "Failed to load parameter file."
self.assertIn(expected_msg, str(ex))
class TestVnfLcmV1(base.FixturedTestCase):
client_fixture_class = client.ClientFixture
api_version = '1'
def setUp(self):
super(TestVnfLcmV1, self).setUp()
def test_client_v2(self):
self.assertEqual(self.cs.vnf_lcm_client.headers,
{'Version': '1.3.0'})
self.assertEqual(self.cs.vnf_lcm_client.vnf_instances_path,
'/vnflcm/v1/vnf_instances')
# check of other paths is omitted.

View File

@@ -0,0 +1,32 @@
# Copyright (C) 2021 Nippon Telegraph and Telephone Corporation
# All Rights Reserved.
#
# 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.
from tackerclient.tests.unit.osc import base
from tackerclient.tests.unit.osc.v1.fixture_data import client
class TestVnfLcmV2(base.FixturedTestCase):
client_fixture_class = client.ClientFixture
api_version = '2'
def setUp(self):
super(TestVnfLcmV2, self).setUp()
def test_client_v2(self):
self.assertEqual(self.cs.vnf_lcm_client.headers,
{'Version': '2.0.0'})
self.assertEqual(self.cs.vnf_lcm_client.vnf_instances_path,
'/vnflcm/v2/vnf_instances')
# check of other paths is omitted.

View File

@@ -229,8 +229,12 @@ class ClientBase(object):
if body or body == {}:
body = self.serialize(body)
if headers is None:
# self.httpclient.do_request is not accept 'headers=None'.
headers = {}
resp, replybody = self.httpclient.do_request(
action, method, body=body,
action, method, body=body, headers=headers,
content_type=self.content_type())
if 'application/zip' == resp.headers.get('Content-Type'):
@@ -355,26 +359,27 @@ class ClientBase(object):
return self.retry_request("PATCH", action, body=body,
headers=headers, params=params)
def list(self, collection, path, retrieve_all=True, **params):
def list(self, collection, path, retrieve_all=True, headers=None,
**params):
if retrieve_all:
res = []
for r in self._pagination(collection, path, **params):
for r in self._pagination(collection, path, headers, **params):
if type(r) is list:
res.extend(r)
else:
res.extend(r[collection])
return {collection: res} if collection else res
else:
return self._pagination(collection, path, **params)
return self._pagination(collection, path, headers, **params)
def _pagination(self, collection, path, **params):
def _pagination(self, collection, path, headers, **params):
if params.get('page_reverse', False):
linkrel = 'previous'
else:
linkrel = 'next'
next = True
while next:
res = self.get(path, params=params)
res = self.get(path, headers=headers, params=params)
yield res
next = False
try:
@@ -874,82 +879,116 @@ class VnfLCMClient(ClientBase):
APIs.
"""
vnf_instances_path = '/vnflcm/v1/vnf_instances'
vnf_instance_path = '/vnflcm/v1/vnf_instances/%s'
vnf_lcm_op_occurrences_path = '/vnflcm/v1/vnf_lcm_op_occs'
vnf_lcm_op_occs_path = '/vnflcm/v1/vnf_lcm_op_occs/%s'
def __init__(self, api_version, **kwargs):
super(VnfLCMClient, self).__init__(**kwargs)
self.headers = {'Version': '1.3.0'}
sol_api_version = 'v1'
if api_version == '2':
self.headers = {'Version': '2.0.0'}
sol_api_version = 'v2'
self.vnf_instances_path = (
'/vnflcm/{}/vnf_instances'.format(sol_api_version))
self.vnf_instance_path = (
'/vnflcm/{}/vnf_instances/%s'.format(sol_api_version))
self.vnf_lcm_op_occurrences_path = (
'/vnflcm/{}/vnf_lcm_op_occs'.format(sol_api_version))
self.vnf_lcm_op_occs_path = (
'/vnflcm/{}/vnf_lcm_op_occs/%s'.format(sol_api_version))
def build_action(self, action):
return action
@APIParamsCall
def create_vnf_instance(self, body):
return self.post(self.vnf_instances_path, body=body)
return self.post(self.vnf_instances_path, body=body,
headers=self.headers)
@APIParamsCall
def show_vnf_instance(self, vnf_id, **_params):
return self.get(self.vnf_instance_path % vnf_id, params=_params)
return self.get(self.vnf_instance_path % vnf_id,
headers=self.headers, params=_params)
@APIParamsCall
def list_vnf_instances(self, retrieve_all=True, **_params):
vnf_instances = self.list(None, self.vnf_instances_path,
retrieve_all, **_params)
retrieve_all, headers=self.headers,
**_params)
return vnf_instances
@APIParamsCall
def instantiate_vnf_instance(self, vnf_id, body):
return self.post((self.vnf_instance_path + "/instantiate") % vnf_id,
body=body)
body=body, headers=self.headers)
@APIParamsCall
def heal_vnf_instance(self, vnf_id, body):
return self.post((self.vnf_instance_path + "/heal") % vnf_id,
body=body)
body=body, headers=self.headers)
@APIParamsCall
def terminate_vnf_instance(self, vnf_id, body):
return self.post((self.vnf_instance_path + "/terminate") % vnf_id,
body=body)
body=body, headers=self.headers)
@APIParamsCall
def delete_vnf_instance(self, vnf_id):
return self.delete(self.vnf_instance_path % vnf_id)
return self.delete(self.vnf_instance_path % vnf_id,
headers=self.headers)
@APIParamsCall
def update_vnf_instance(self, vnf_id, body):
return self.patch(self.vnf_instance_path % vnf_id, body=body)
return self.patch(self.vnf_instance_path % vnf_id, body=body,
headers=self.headers)
@APIParamsCall
def scale_vnf_instance(self, vnf_id, body):
return self.post((self.vnf_instance_path + "/scale") % vnf_id,
body=body)
body=body, headers=self.headers)
@APIParamsCall
def rollback_vnf_instance(self, occ_id):
return self.post((self.vnf_lcm_op_occs_path + "/rollback") % occ_id)
return self.post((self.vnf_lcm_op_occs_path + "/rollback") % occ_id,
headers=self.headers)
@APIParamsCall
def fail_vnf_instance(self, occ_id):
return self.post((self.vnf_lcm_op_occs_path + "/fail") % occ_id)
return self.post((self.vnf_lcm_op_occs_path + "/fail") % occ_id,
headers=self.headers)
@APIParamsCall
def change_ext_conn_vnf_instance(self, vnf_id, body):
return self.post((self.vnf_instance_path + "/change_ext_conn") %
vnf_id, body=body)
vnf_id, body=body, headers=self.headers)
@APIParamsCall
def retry_vnf_instance(self, occ_id):
return self.post((self.vnf_lcm_op_occs_path + "/retry") % occ_id)
return self.post((self.vnf_lcm_op_occs_path + "/retry") % occ_id,
headers=self.headers)
@APIParamsCall
def list_vnf_lcm_op_occs(self, retrieve_all=True, **_params):
vnf_lcm_op_occs = self.list(None, self.vnf_lcm_op_occurrences_path,
retrieve_all, **_params)
retrieve_all, headers=self.headers,
**_params)
return vnf_lcm_op_occs
@APIParamsCall
def show_vnf_lcm_op_occs(self, occ_id):
return self.get(self.vnf_lcm_op_occs_path % occ_id)
return self.get(self.vnf_lcm_op_occs_path % occ_id,
headers=self.headers)
@APIParamsCall
def show_vnf_lcm_versions(self, major_version):
if major_version is None:
path = "/vnflcm/api_versions"
else:
path = "/vnflcm/{}/api_versions".format(major_version)
# NOTE: This may be called with any combination of
# --os-tacker-api-verson:[1, 2] and major_version:[None, 1, 2].
# Specifying "headers={'Version': '2.0.0'}" is most simple to
# make all cases OK.
return self.get(path, headers={'Version': '2.0.0'})
class Client(object):
@@ -972,7 +1011,8 @@ class Client(object):
"""
def __init__(self, **kwargs):
self.vnf_lcm_client = VnfLCMClient(**kwargs)
api_version = kwargs.pop('api_version', '1')
self.vnf_lcm_client = VnfLCMClient(api_version, **kwargs)
self.vnf_package_client = VnfPackageClient(**kwargs)
self.legacy_client = LegacyClient(**kwargs)
@@ -1263,3 +1303,6 @@ class Client(object):
def show_vnf_lcm_op_occs(self, occ_id):
return self.vnf_lcm_client.show_vnf_lcm_op_occs(occ_id)
def show_vnf_lcm_versions(self, major_version):
return self.vnf_lcm_client.show_vnf_lcm_versions(major_version)