diff --git a/setup.cfg b/setup.cfg index 2b7e6383..c3d0345c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -92,3 +92,4 @@ openstack.tackerclient.v1 = vnflcm_delete = tackerclient.osc.v1.vnflcm.vnflcm:DeleteVnfLcm vnflcm_heal = tackerclient.osc.v1.vnflcm.vnflcm:HealVnfLcm vnflcm_update = tackerclient.osc.v1.vnflcm.vnflcm:UpdateVnfLcm + vnflcm_scale = tackerclient.osc.v1.vnflcm.vnflcm:ScaleVnfLcm diff --git a/tackerclient/osc/v1/vnflcm/samples/scale_vnf_instance_param_sample.json b/tackerclient/osc/v1/vnflcm/samples/scale_vnf_instance_param_sample.json new file mode 100644 index 00000000..5afb282f --- /dev/null +++ b/tackerclient/osc/v1/vnflcm/samples/scale_vnf_instance_param_sample.json @@ -0,0 +1,3 @@ +{ + "additionalParams": {"key1":"value1", "key2":"value2"} +} \ No newline at end of file diff --git a/tackerclient/osc/v1/vnflcm/vnflcm.py b/tackerclient/osc/v1/vnflcm/vnflcm.py index c398b3aa..0005a89a 100644 --- a/tackerclient/osc/v1/vnflcm/vnflcm.py +++ b/tackerclient/osc/v1/vnflcm/vnflcm.py @@ -465,3 +465,71 @@ class UpdateVnfLcm(command.Command): if not result: print((_('Update vnf:%(id)s ') % {'id': parsed_args.vnf_instance})) + + +class ScaleVnfLcm(command.Command): + _description = _("Scale a VNF Instance") + + def get_parser(self, prog_name): + parser = super(ScaleVnfLcm, self).get_parser(prog_name) + parser.add_argument( + _VNF_INSTANCE, + metavar="", + help=_('VNF instance ID to scale')) + parser.add_argument( + '--I', + metavar="", + help=_("Specify scale request parameters in a json file.")) + parser.add_argument( + '--type', + metavar="", + choices=['SCALE_OUT', 'SCALE_IN'], + help=_("Indicates the type of the scale operation requested")) + parser.add_argument( + '--aspect-id', + metavar="", + help=_("Identifier of the scaling aspect.")) + parser.add_argument( + '--number-of-steps', + metavar="", + type=int, + help=_("Number of scaling steps to be executed as part of" + "this Scale VNF operation.")) + parser.add_argument( + '--additional-param-file', + metavar="", + help=_("Additional parameters passed by the NFVO as input" + "to the scaling process.")) + return parser + + def args2body(self, file_path=None): + """To store request body, call jsonfile2body. + + Args: + file_path ([string], optional): file path of param file(json). + Defaults to None. + + Returns: + body[dict]: [description] + """ + body = {} + + if file_path: + return jsonfile2body(file_path) + + return body + + def take_action(self, parsed_args): + """Execute scale_vnf_instance and output result comment. + + Args: + parsed_args ([Namespace]): [description] + """ + client = self.app.client_manager.tackerclient + if parsed_args.additional_param_file: + result = client.scale_vnf_instance( + parsed_args.vnf_instance, + self.args2body(file_path=parsed_args.additional_param_file)) + if not result: + print((_('Scale request for VNF Instance %(id)s has been' + ' accepted.') % {'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 eb0c9bd2..5206dcab 100644 --- a/tackerclient/tests/unit/osc/v1/test_vnflcm.py +++ b/tackerclient/tests/unit/osc/v1/test_vnflcm.py @@ -602,53 +602,109 @@ class TestUpdateVnfLcm(TestVnfLcm): parsed_args = self.check_parser(self.update_vnf_lcm, arglist, verifylist) + self.assertRaises(exceptions.InvalidInput, + self.update_vnf_lcm.take_action, parsed_args) + + +@ddt.ddt +class TestScaleVnfLcm(TestVnfLcm): + def setUp(self): + super(TestScaleVnfLcm, self).setUp() + self.scale_vnf_lcm = vnflcm.ScaleVnfLcm( + self.app, self.app_args, cmd_name='vnflcm scale') + + @ddt.data('SCALE_IN', 'SCALE_OUT') + def test_take_action(self, scale_type): + vnf_instance = vnflcm_fakes.vnf_instance_response() + sample_param_file = ("./tackerclient/osc/v1/vnflcm/samples/" + "scale_vnf_instance_param_sample.json") + + arglist = [vnf_instance['id'], + '--aspect-id', uuidsentinel.aspect_id, + '--number-of-steps', '1', + '--type', scale_type, + '--additional-param-file', sample_param_file] + verifylist = [('vnf_instance', vnf_instance['id']), + ('aspect_id', uuidsentinel.aspect_id), + ('number_of_steps', 1), + ('type', scale_type), + ('additional_param_file', sample_param_file)] + + # command param + parsed_args = self.check_parser(self.scale_vnf_lcm, arglist, + verifylist) + url = os.path.join( + self.url, + 'vnflcm/v1/vnf_instances', + vnf_instance['id'], + 'scale') + + self.requests_mock.register_uri( + 'POST', url, headers=self.header, json={}) + + sys.stdout = buffer = StringIO() + self.scale_vnf_lcm.take_action(parsed_args) + + actual_message = buffer.getvalue().strip() + + expected_message = ("Scale request for VNF Instance %s has been " + "accepted.") % vnf_instance['id'] + + self.assertEqual(expected_message, actual_message) + + @ddt.data('SCALE_IN', 'SCALE_OUT') + def test_take_action_param_file_not_exists(self, scale_type): + vnf_instance = vnflcm_fakes.vnf_instance_response() + sample_param_file = "./not_exists.json" + arglist = [vnf_instance['id'], + '--aspect-id', uuidsentinel.aspect_id, + '--number-of-steps', '2', + '--type', scale_type, + '--additional-param-file', sample_param_file] + verifylist = [('vnf_instance', vnf_instance['id']), + ('aspect_id', uuidsentinel.aspect_id), + ('number_of_steps', 2), + ('type', scale_type), + ('additional_param_file', sample_param_file)] + + # command param + parsed_args = self.check_parser(self.scale_vnf_lcm, arglist, + verifylist) + ex = self.assertRaises(exceptions.InvalidInput, - self.update_vnf_lcm.take_action, parsed_args) + self.scale_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): + @ddt.data('SCALE_IN', 'SCALE_OUT') + def test_take_action_vnf_instance_not_found(self, scale_type): 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] + arglist = [vnf_instance['id'], + '--aspect-id', uuidsentinel.aspect_id, + '--number-of-steps', '3', + '--type', scale_type, + '--additional-param-file', sample_param_file] verifylist = [('vnf_instance', vnf_instance['id']), - ('I', sample_param_file)] + ('aspect_id', uuidsentinel.aspect_id), + ('number_of_steps', 3), + ('type', scale_type), + ('additional_param_file', sample_param_file)] # command param - parsed_args = self.check_parser( - self.update_vnf_lcm, arglist, verifylist) + parsed_args = self.check_parser(self.scale_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={}) + 'POST', url, headers=self.header, status_code=404, json={}) self.assertRaises(exceptions.TackerClientException, - self.update_vnf_lcm.take_action, + self.scale_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 51b51ff2..d2dd5ba7 100644 --- a/tackerclient/v1_0/client.py +++ b/tackerclient/v1_0/client.py @@ -900,6 +900,11 @@ class VnfLCMClient(ClientBase): def update_vnf_instance(self, vnf_id, body): return self.patch(self.vnf_instance_path % vnf_id, body=body) + @APIParamsCall + def scale_vnf_instance(self, vnf_id, body): + return self.post((self.vnf_instance_path + "/scale") % vnf_id, + body=body) + class Client(object): """Unified interface to interact with multiple applications of tacker service. @@ -1170,6 +1175,9 @@ class Client(object): def terminate_vnf_instance(self, vnf_id, body): return self.vnf_lcm_client.terminate_vnf_instance(vnf_id, body) + def scale_vnf_instance(self, vnf_id, body): + return self.vnf_lcm_client.scale_vnf_instance(vnf_id, body) + def delete_vnf_instance(self, vnf_id): return self.vnf_lcm_client.delete_vnf_instance(vnf_id)