diff --git a/setup.cfg b/setup.cfg index 7cd4a015..71a68a1d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -110,6 +110,7 @@ openstack.tackerclient.v2 = 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_change-vnfpkg = tackerclient.osc.v1.vnflcm.vnflcm:ChangeVnfPkgVnfLcm 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 diff --git a/tackerclient/common/exceptions.py b/tackerclient/common/exceptions.py index 94c338ee..9a52280a 100644 --- a/tackerclient/common/exceptions.py +++ b/tackerclient/common/exceptions.py @@ -207,6 +207,10 @@ class InvalidInput(TackerClientException): message = _("Invalid input: %(reason)s") +class UnsupportedCommandVersion(TackerClientException): + message = _("This command is not supported in version %(version)s") + + # Command line exceptions class TackerCLIError(TackerException): diff --git a/tackerclient/osc/v1/vnflcm/vnflcm.py b/tackerclient/osc/v1/vnflcm/vnflcm.py index 796d0d79..f0108417 100644 --- a/tackerclient/osc/v1/vnflcm/vnflcm.py +++ b/tackerclient/osc/v1/vnflcm/vnflcm.py @@ -566,3 +566,30 @@ class ChangeExtConnVnfLcm(command.Command): if not result: print((_('Change External VNF Connectivity for VNF Instance %s ' 'has been accepted.') % parsed_args.vnf_instance)) + + +class ChangeVnfPkgVnfLcm(command.Command): + _description = _("Change Current VNF Package") + + def get_parser(self, prog_name): + parser = super(ChangeVnfPkgVnfLcm, self).get_parser(prog_name) + parser.add_argument( + _VNF_INSTANCE, + metavar="", + help=_("VNF instance ID to Change Current VNF Package")) + parser.add_argument( + 'request_file', + metavar="", + help=_("Specify change-vnfpkg request parameters " + "in a json file.")) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.tackerclient + result = client.change_vnfpkg_vnf_instance( + parsed_args.vnf_instance, jsonfile2body( + parsed_args.request_file)) + if not result: + print((_('Change Current VNF Package for VNF Instance %s ' + 'has been accepted.') % parsed_args.vnf_instance)) diff --git a/tackerclient/osc/v2/vnflcm/samples/change_vnfpkg_vnf_instance_param_sample.json b/tackerclient/osc/v2/vnflcm/samples/change_vnfpkg_vnf_instance_param_sample.json new file mode 100644 index 00000000..7e83cddf --- /dev/null +++ b/tackerclient/osc/v2/vnflcm/samples/change_vnfpkg_vnf_instance_param_sample.json @@ -0,0 +1,36 @@ +{ + "vnfdId": "c6595341-a5bb-8246-53c4-7aeb843d60c5", + "additionalParams": { + "upgrade_type": "RollingUpdate", + "lcm-operation-coordinate-old-vnf": "./Scripts/coordinate_old_vnf.py", + "lcm-operation-coordinate-old-vnf-class": "CoordinateOldVnf", + "lcm-operation-coordinate-new-vnf": "./Scripts/coordinate_new_vnf.py", + "lcm-operation-coordinate-new-vnf-class": "CoordinateNewVnf", + "vdu_params": [{ + "vduId": "VDU1", + "old_vnfc_param": { + "cp_name": "VDU1_CP1", + "username": "ubuntu", + "password": "ubuntu" + }, + "new_vnfc_param": { + "cp_name": "VDU1_CP1", + "username": "ubuntu", + "password": "ubuntu" + } + }, { + "vduId": "VDU2", + "old_vnfc_param": { + "cp_name": "VDU2_CP1", + "username": "ubuntu", + "password": "ubuntu" + }, + "new_vnfc_param": { + "cp_name": "VDU2_CP1", + "username": "ubuntu", + "password": "ubuntu" + } + }] + } +} + diff --git a/tackerclient/tests/unit/osc/v1/test_vnflcm.py b/tackerclient/tests/unit/osc/v1/test_vnflcm.py index 3163df3c..f5d64c62 100644 --- a/tackerclient/tests/unit/osc/v1/test_vnflcm.py +++ b/tackerclient/tests/unit/osc/v1/test_vnflcm.py @@ -850,6 +850,40 @@ class TestChangeExtConnVnfLcm(TestVnfLcm): self.assertIn(expected_msg, str(ex)) +class TestChangeVnfPkgVnfLcm(TestVnfLcm): + + def setUp(self): + super(TestChangeVnfPkgVnfLcm, self).setUp() + self.change_vnfpkg_vnf_lcm = vnflcm.ChangeVnfPkgVnfLcm( + self.app, self.app_args, + cmd_name='vnflcm change-vnfpkg') + + def test_take_action_with_v1_version(self): + vnf_instance = vnflcm_fakes.vnf_instance_response() + sample_param_file = ("./tackerclient/osc/v2/vnflcm/samples/" + "change_vnfpkg_vnf_instance_param_sample.json") + arglist = [vnf_instance['id'], sample_param_file] + verifylist = [('vnf_instance', vnf_instance['id']), + ('request_file', sample_param_file)] + + # command param + parsed_args = self.check_parser(self.change_vnfpkg_vnf_lcm, + arglist, + verifylist) + + url = os.path.join(self.url, 'vnflcm/v1/vnf_instances', + vnf_instance['id'], 'change_vnfpkg') + self.requests_mock.register_uri( + 'POST', url, headers=self.header, status_code=400, json={}) + + ex = self.assertRaises(exceptions.UnsupportedCommandVersion, + self.change_vnfpkg_vnf_lcm.take_action, + parsed_args) + + expected_msg = "This command is not supported in version 1" + self.assertEqual(expected_msg, str(ex)) + + class TestVnfLcmV1(base.FixturedTestCase): client_fixture_class = client.ClientFixture api_version = '1' diff --git a/tackerclient/tests/unit/osc/v2/test_vnflcm.py b/tackerclient/tests/unit/osc/v2/test_vnflcm.py index 5b2d0575..825b9b37 100644 --- a/tackerclient/tests/unit/osc/v2/test_vnflcm.py +++ b/tackerclient/tests/unit/osc/v2/test_vnflcm.py @@ -13,8 +13,18 @@ # License for the specific language governing permissions and limitations # under the License. +from io import StringIO +import os +import sys +from unittest import mock + +from tackerclient.common import exceptions +from tackerclient.osc.v1.vnflcm import vnflcm from tackerclient.tests.unit.osc import base from tackerclient.tests.unit.osc.v1.fixture_data import client +from tackerclient.tests.unit.osc.v1 import test_vnflcm +from tackerclient.tests.unit.osc.v1 import vnflcm_fakes +from tackerclient.v1_0 import client as proxy_client class TestVnfLcmV2(base.FixturedTestCase): @@ -30,3 +40,112 @@ class TestVnfLcmV2(base.FixturedTestCase): self.assertEqual(self.cs.vnf_lcm_client.vnf_instances_path, '/vnflcm/v2/vnf_instances') # check of other paths is omitted. + + +class TestChangeVnfPkgVnfLcm(test_vnflcm.TestVnfLcm): + api_version = '2' + + def setUp(self): + super(TestChangeVnfPkgVnfLcm, self).setUp() + self.change_vnfpkg_vnf_lcm = vnflcm.ChangeVnfPkgVnfLcm( + self.app, self.app_args, + cmd_name='vnflcm change-vnfpkg') + + def test_take_action(self): + vnf_instance = vnflcm_fakes.vnf_instance_response() + sample_param_file = ("./tackerclient/osc/v2/vnflcm/samples/" + "change_vnfpkg_vnf_instance_param_sample.json") + + arglist = [vnf_instance['id'], sample_param_file] + verifylist = [('vnf_instance', vnf_instance['id']), + ('request_file', sample_param_file)] + + # command param + parsed_args = self.check_parser(self.change_vnfpkg_vnf_lcm, + arglist, + verifylist) + + url = os.path.join(self.url, 'vnflcm/v2/vnf_instances', + vnf_instance['id'], 'change_vnfpkg') + self.requests_mock.register_uri( + 'POST', url, headers=self.header, json={}) + + sys.stdout = buffer = StringIO() + with mock.patch.object(proxy_client.ClientBase, + '_handle_fault_response') as m: + self.change_vnfpkg_vnf_lcm.take_action(parsed_args) + # check no fault response is received + self.assertNotCalled(m) + self.assertEqual( + ('Change Current VNF Package for VNF Instance {0} ' + 'has been accepted.'.format(vnf_instance['id'])), + buffer.getvalue().strip()) + + def test_take_action_vnf_instance_not_found(self): + vnf_instance = vnflcm_fakes.vnf_instance_response() + sample_param_file = ("./tackerclient/osc/v1/vnflcm/samples/" + "change_vnfpkg_vnf_instance_param_sample.json") + arglist = [vnf_instance['id'], sample_param_file] + verifylist = [('vnf_instance', vnf_instance['id']), + ('request_file', sample_param_file)] + + # command param + parsed_args = self.check_parser(self.change_vnfpkg_vnf_lcm, + arglist, + verifylist) + + url = os.path.join(self.url, 'vnflcm/v2/vnf_instances', + vnf_instance['id'], 'change_vnfpkg') + self.requests_mock.register_uri( + 'POST', url, headers=self.header, status_code=404, json={}) + + self.assertRaises(exceptions.TackerClientException, + self.change_vnfpkg_vnf_lcm.take_action, + parsed_args) + + def test_take_action_param_file_not_exists(self): + vnf_instance = vnflcm_fakes.vnf_instance_response() + sample_param_file = "./not_exists.json" + arglist = [vnf_instance['id'], sample_param_file] + verifylist = [('vnf_instance', vnf_instance['id']), + ('request_file', sample_param_file)] + + # command param + parsed_args = self.check_parser( + self.change_vnfpkg_vnf_lcm, + arglist, + verifylist) + + ex = self.assertRaises( + exceptions.InvalidInput, + self.change_vnfpkg_vnf_lcm.take_action, + parsed_args) + + expected_msg = ("Invalid input: File %s does not exist " + "or user does not have read privileges to it") + self.assertEqual(expected_msg % sample_param_file, str(ex)) + + @mock.patch("os.open") + @mock.patch("os.access") + def test_take_action_invalid_format_param_file(self, mock_access, + mock_open): + vnf_instance = vnflcm_fakes.vnf_instance_response() + sample_param_file = "./invalid_param_file.json" + arglist = [vnf_instance['id'], sample_param_file] + verifylist = [('vnf_instance', vnf_instance['id']), + ('request_file', sample_param_file)] + + mock_open.return_value = "invalid_json_data" + mock_access.return_value = True + # command param + parsed_args = self.check_parser(self.change_vnfpkg_vnf_lcm, + arglist, + verifylist) + + ex = self.assertRaises( + exceptions.InvalidInput, + self.change_vnfpkg_vnf_lcm.take_action, + parsed_args) + + expected_msg = "Failed to load parameter file." + self.assertIn(expected_msg, str(ex)) diff --git a/tackerclient/v1_0/client.py b/tackerclient/v1_0/client.py index 543107c5..b444d36b 100644 --- a/tackerclient/v1_0/client.py +++ b/tackerclient/v1_0/client.py @@ -966,6 +966,15 @@ class VnfLCMClient(ClientBase): return self.post((self.vnf_instance_path + "/change_ext_conn") % vnf_id, body=body, headers=self.headers) + @APIParamsCall + def change_vnfpkg_vnf_instance(self, vnf_id, body): + # NOTE: it is only supported by V2-API. + if self.vnf_instance_path.split('/')[2] == 'v2': + return self.post((self.vnf_instance_path + "/change_vnfpkg") % + vnf_id, body=body, headers=self.headers) + else: + raise exceptions.UnsupportedCommandVersion(version='1') + @APIParamsCall def retry_vnf_instance(self, occ_id): return self.post((self.vnf_lcm_op_occs_path + "/retry") % occ_id, @@ -1272,6 +1281,9 @@ class Client(object): def change_ext_conn_vnf_instance(self, vnf_id, body): return self.vnf_lcm_client.change_ext_conn_vnf_instance(vnf_id, body) + def change_vnfpkg_vnf_instance(self, vnf_id, body): + return self.vnf_lcm_client.change_vnfpkg_vnf_instance(vnf_id, body) + def delete_vnf_instance(self, vnf_id): return self.vnf_lcm_client.delete_vnf_instance(vnf_id)