From 60c268f04d15ae3ec32e9e0095973d04cf65bba5 Mon Sep 17 00:00:00 2001 From: Koichi Edagawa Date: Fri, 21 Aug 2020 17:31:53 +0900 Subject: [PATCH] Support of UpdateVNF command in openstackclient Supported UpdateVNF command in OpenStack Tacker Client. Change-Id: I20435547e6072c4ca14a5d2cccd7ab77bfea08f8 --- setup.cfg | 1 + .../update_vnf_instance_param_sample.json | 5 + tackerclient/osc/v1/vnflcm/vnflcm.py | 64 +++++++++++- tackerclient/tests/unit/osc/v1/test_vnflcm.py | 98 +++++++++++++++++++ tackerclient/v1_0/client.py | 7 ++ 5 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 tackerclient/osc/v1/vnflcm/samples/update_vnf_instance_param_sample.json diff --git a/setup.cfg b/setup.cfg index ac9fd09f..2b7e6383 100644 --- a/setup.cfg +++ b/setup.cfg @@ -91,3 +91,4 @@ openstack.tackerclient.v1 = vnflcm_terminate = tackerclient.osc.v1.vnflcm.vnflcm:TerminateVnfLcm vnflcm_delete = tackerclient.osc.v1.vnflcm.vnflcm:DeleteVnfLcm vnflcm_heal = tackerclient.osc.v1.vnflcm.vnflcm:HealVnfLcm + vnflcm_update = tackerclient.osc.v1.vnflcm.vnflcm:UpdateVnfLcm diff --git a/tackerclient/osc/v1/vnflcm/samples/update_vnf_instance_param_sample.json b/tackerclient/osc/v1/vnflcm/samples/update_vnf_instance_param_sample.json new file mode 100644 index 00000000..62c5b32b --- /dev/null +++ b/tackerclient/osc/v1/vnflcm/samples/update_vnf_instance_param_sample.json @@ -0,0 +1,5 @@ +{ + "vnfInstanceName": "sample", + "vnfInstanceDescription" : "sample_description", + "vnfdId" : "sample_id" + } \ No newline at end of file diff --git a/tackerclient/osc/v1/vnflcm/vnflcm.py b/tackerclient/osc/v1/vnflcm/vnflcm.py index c4afc3c4..c398b3aa 100644 --- a/tackerclient/osc/v1/vnflcm/vnflcm.py +++ b/tackerclient/osc/v1/vnflcm/vnflcm.py @@ -111,7 +111,7 @@ class CreateVnfLcm(command.ShowOne): body = {} if file_path: - return instantiate_vnf_args2body(file_path) + return jsonfile2body(file_path) body['vnfdId'] = parsed_args.vnfd_id @@ -182,7 +182,7 @@ class ListVnfLcm(command.Lister): ) for s in vnf_instances)) -def instantiate_vnf_args2body(file_path): +def jsonfile2body(file_path): if file_path is not None and os.access(file_path, os.R_OK) is False: msg = _("File %s does not exist or user does not have read " @@ -224,7 +224,7 @@ class InstantiateVnfLcm(command.Command): def take_action(self, parsed_args): client = self.app.client_manager.tackerclient result = client.instantiate_vnf_instance( - parsed_args.vnf_instance, instantiate_vnf_args2body( + parsed_args.vnf_instance, jsonfile2body( parsed_args.instantiation_request_file)) if not result: print((_('Instantiate request for VNF Instance %(id)s has been' @@ -407,3 +407,61 @@ class DeleteVnfLcm(command.Command): else: print(_("Vnf instance '%s' deleted " "successfully") % vnf_instances[0]) + + +class UpdateVnfLcm(command.Command): + _description = _("Update VNF Instance") + + def get_parser(self, prog_name): + """Add arguments to parser. + + Args: + prog_name ([string]): program name + + Returns: + parser([ArgumentParser]): [description] + """ + parser = super(UpdateVnfLcm, self).get_parser(prog_name) + parser.add_argument( + _VNF_INSTANCE, + metavar="", + help=_('VNF instance ID to update.')) + parser.add_argument( + '--I', + metavar="", + help=_("Specify update request parameters in a json file.")) + + return parser + + def args2body(self, file_path=None): + """Call jsonfile2body to store request body to body(dict) + + Args: + file_path ([string], optional): file path of param file(json). + Defaults to None. + + Returns: + body ([dict]): Request body is stored + """ + body = {} + + if file_path: + return jsonfile2body(file_path) + + return body + + def take_action(self, parsed_args): + """Execute update_vnf_instance and output result comment + + Args: + parsed_args ([Namespace]): [description] + """ + client = self.app.client_manager.tackerclient + if parsed_args.I: + # Update VNF instance. + result = client.update_vnf_instance( + parsed_args.vnf_instance, + self.args2body(file_path=parsed_args.I)) + if not result: + print((_('Update vnf:%(id)s ') % + {'id': parsed_args.vnf_instance})) diff --git a/tackerclient/tests/unit/osc/v1/test_vnflcm.py b/tackerclient/tests/unit/osc/v1/test_vnflcm.py index e2836b76..eb0c9bd2 100644 --- a/tackerclient/tests/unit/osc/v1/test_vnflcm.py +++ b/tackerclient/tests/unit/osc/v1/test_vnflcm.py @@ -554,3 +554,101 @@ class TestDeleteVnfLcm(TestVnfLcm): self.assertEqual('Failed to delete 1 of 3 vnf instances.', exception.message) + + +class TestUpdateVnfLcm(TestVnfLcm): + def setUp(self): + super(TestUpdateVnfLcm, self).setUp() + self.update_vnf_lcm = vnflcm.UpdateVnfLcm( + self.app, self.app_args, cmd_name='vnflcm modify') + + def test_take_action(self): + vnf_instance = vnflcm_fakes.vnf_instance_response() + sample_param_file = ("./tackerclient/osc/v1/vnflcm/samples/" + "update_vnf_instance_param_sample.json") + + arglist = [vnf_instance['id'], '--I', sample_param_file] + verifylist = [('vnf_instance', vnf_instance['id']), + ('I', sample_param_file)] + + # command param + parsed_args = self.check_parser( + self.update_vnf_lcm, arglist, verifylist) + url = os.path.join( + self.url, + 'vnflcm/v1/vnf_instances', + vnf_instance['id']) + + self.requests_mock.register_uri( + 'PATCH', url, headers=self.header, json={}) + + sys.stdout = buffer = StringIO() + self.update_vnf_lcm.take_action(parsed_args) + + actual_message = buffer.getvalue().strip() + + expected_message = ('Update vnf:' + vnf_instance['id']) + + self.assertEqual(expected_message, actual_message) + + 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'], '--I', sample_param_file] + verifylist = [('vnf_instance', vnf_instance['id']), + ('I', sample_param_file)] + + # command param + parsed_args = self.check_parser(self.update_vnf_lcm, arglist, + verifylist) + + ex = self.assertRaises(exceptions.InvalidInput, + self.update_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)) + + def test_take_action_vnf_instance_not_found(self): + vnf_instance = vnflcm_fakes.vnf_instance_response() + sample_param_file = ("./tackerclient/osc/v1/vnflcm/samples/" + "update_vnf_instance_param_sample.json") + arglist = [vnf_instance['id'], '--I', sample_param_file] + verifylist = [('vnf_instance', vnf_instance['id']), + ('I', sample_param_file)] + + # command param + parsed_args = self.check_parser( + self.update_vnf_lcm, arglist, verifylist) + + url = os.path.join( + self.url, + 'vnflcm/v1/vnf_instances', + vnf_instance['id']) + self.requests_mock.register_uri( + 'PATCH', url, headers=self.header, status_code=404, json={}) + + self.assertRaises(exceptions.TackerClientException, + self.update_vnf_lcm.take_action, + parsed_args) + + @mock.patch("os.open") + @mock.patch("os.access") + def test_take_action_invalid_format_param_file(self, mock_open, + mock_access): + vnf_instance = vnflcm_fakes.vnf_instance_response() + sample_param_file = "./invalid_param_file.json" + arglist = [vnf_instance['id'], '--I', sample_param_file] + verifylist = [('vnf_instance', vnf_instance['id']), + ('I', sample_param_file)] + + mock_open.return_value = "invalid_json_data" + # command param + parsed_args = self.check_parser(self.update_vnf_lcm, arglist, + verifylist) + + ex = self.assertRaises(exceptions.InvalidInput, + self.update_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 fe136ace..51b51ff2 100644 --- a/tackerclient/v1_0/client.py +++ b/tackerclient/v1_0/client.py @@ -896,6 +896,10 @@ class VnfLCMClient(ClientBase): def delete_vnf_instance(self, vnf_id): return self.delete(self.vnf_instance_path % vnf_id) + @APIParamsCall + def update_vnf_instance(self, vnf_id, body): + return self.patch(self.vnf_instance_path % vnf_id, body=body) + class Client(object): """Unified interface to interact with multiple applications of tacker service. @@ -1169,6 +1173,9 @@ class Client(object): def delete_vnf_instance(self, vnf_id): return self.vnf_lcm_client.delete_vnf_instance(vnf_id) + def update_vnf_instance(self, vnf_id, body): + return self.vnf_lcm_client.update_vnf_instance(vnf_id, body) + def update_vnf_package(self, vnf_package, body): return self.vnf_package_client.update_vnf_package(vnf_package, body)