diff --git a/setup.cfg b/setup.cfg index 1adecf38..314a73f4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 diff --git a/tackerclient/osc/common/__init__.py b/tackerclient/osc/common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tackerclient/osc/common/vnflcm/__init__.py b/tackerclient/osc/common/vnflcm/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tackerclient/osc/common/vnflcm/vnflcm_versions.py b/tackerclient/osc/common/vnflcm/vnflcm_versions.py new file mode 100644 index 00000000..ef4d99d2 --- /dev/null +++ b/tackerclient/osc/common/vnflcm/vnflcm_versions.py @@ -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="", + 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())) diff --git a/tackerclient/osc/plugin.py b/tackerclient/osc/plugin.py index 3077a989..8d183e26 100644 --- a/tackerclient/osc/plugin.py +++ b/tackerclient/osc/plugin.py @@ -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) diff --git a/tackerclient/tests/unit/osc/base.py b/tackerclient/tests/unit/osc/base.py index 4e7d3d33..30968cef 100644 --- a/tackerclient/tests/unit/osc/base.py +++ b/tackerclient/tests/unit/osc/base.py @@ -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): diff --git a/tackerclient/tests/unit/osc/common/__init__.py b/tackerclient/tests/unit/osc/common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tackerclient/tests/unit/osc/common/test_vnflcm_versions.py b/tackerclient/tests/unit/osc/common/test_vnflcm_versions.py new file mode 100644 index 00000000..3f36ec52 --- /dev/null +++ b/tackerclient/tests/unit/osc/common/test_vnflcm_versions.py @@ -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())) diff --git a/tackerclient/tests/unit/osc/v1/fixture_data/client.py b/tackerclient/tests/unit/osc/v1/fixture_data/client.py index d179d81e..4593a640 100644 --- a/tackerclient/tests/unit/osc/v1/fixture_data/client.py +++ b/tackerclient/tests/unit/osc/v1/fixture_data/client.py @@ -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) diff --git a/tackerclient/tests/unit/osc/v1/test_vnflcm.py b/tackerclient/tests/unit/osc/v1/test_vnflcm.py index 9c6dd480..3163df3c 100644 --- a/tackerclient/tests/unit/osc/v1/test_vnflcm.py +++ b/tackerclient/tests/unit/osc/v1/test_vnflcm.py @@ -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. diff --git a/tackerclient/tests/unit/osc/v2/__init__.py b/tackerclient/tests/unit/osc/v2/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tackerclient/tests/unit/osc/v2/test_vnflcm.py b/tackerclient/tests/unit/osc/v2/test_vnflcm.py new file mode 100644 index 00000000..5b2d0575 --- /dev/null +++ b/tackerclient/tests/unit/osc/v2/test_vnflcm.py @@ -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. diff --git a/tackerclient/v1_0/client.py b/tackerclient/v1_0/client.py index b70a4199..90e2e3e6 100644 --- a/tackerclient/v1_0/client.py +++ b/tackerclient/v1_0/client.py @@ -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)