From cfe14110d11a9782e891dc889a93db6a51970d4b Mon Sep 17 00:00:00 2001 From: Wataru Juso Date: Thu, 18 Feb 2021 00:36:24 +0900 Subject: [PATCH] Support CLI of Change External VNF Connectivity Add ``openstack vnflcm op chg-ext-conn`` to python-tackerclient. This command can execute Change external VNF Connectivity operation. This API can change VL setting. Implements: blueprint support-change-external-connectivity Spec: https://specs.openstack.org/openstack/tacker-specs/specs/wallaby/support-change-external-VNF-connectivity-operation.html Change-Id: I3a935296646361032665082a4060bc21ff51c1b1 --- setup.cfg | 1 + ...ge_ext_conn_vnf_instance_param_sample.json | 69 +++++++++++ tackerclient/osc/v1/vnflcm/vnflcm.py | 27 +++++ tackerclient/tests/unit/osc/v1/test_vnflcm.py | 107 ++++++++++++++++++ tackerclient/v1_0/client.py | 8 ++ 5 files changed, 212 insertions(+) create mode 100644 tackerclient/osc/v1/vnflcm/samples/change_ext_conn_vnf_instance_param_sample.json diff --git a/setup.cfg b/setup.cfg index e5969036..5fe367ca 100644 --- a/setup.cfg +++ b/setup.cfg @@ -94,5 +94,6 @@ openstack.tackerclient.v1 = vnflcm_heal = tackerclient.osc.v1.vnflcm.vnflcm:HealVnfLcm vnflcm_update = tackerclient.osc.v1.vnflcm.vnflcm:UpdateVnfLcm vnflcm_scale = tackerclient.osc.v1.vnflcm.vnflcm:ScaleVnfLcm + vnflcm_change-ext-conn = tackerclient.osc.v1.vnflcm.vnflcm:ChangeExtConnVnfLcm vnflcm_op_rollback = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:RollbackVnfLcmOp vnflcm_op_fail = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:FailVnfLcmOp diff --git a/tackerclient/osc/v1/vnflcm/samples/change_ext_conn_vnf_instance_param_sample.json b/tackerclient/osc/v1/vnflcm/samples/change_ext_conn_vnf_instance_param_sample.json new file mode 100644 index 00000000..a0d60e53 --- /dev/null +++ b/tackerclient/osc/v1/vnflcm/samples/change_ext_conn_vnf_instance_param_sample.json @@ -0,0 +1,69 @@ +{ + "extVirtualLinks": [ + { + "id": "ext-vl-uuid-VL1", + "resourceId": "neutron-network-uuid_VL1", + "extCps": [ + { + "cpdId": "CP1", + "cpConfig": [ + { + "cpProtocolData": [ + { + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [ + { + "type": "IPV4", + "numDynamicAddresses": 1, + "subnetId": "subnet-uuid" + } + ] + } + } + ] + } + ] + }, + { + "cpdId": "CP2", + "cpConfig": [ + { + "cpProtocolData": [ + { + "layerProtocol": "IP_OVER_ETHERNET", + "ipOverEthernet": { + "ipAddresses": [ + { + "type": "IPV4", + "fixedAddresses": [ + "10.0.0.1" + ], + "subnetId": "subnet-uuid" + } + ] + } + } + ] + } + ] + } + ] + } + ], + "vimConnectionInfo": [ + { + "id": "vim-uuid", + "vimType": "ETSINFV.OPENSTACK_KEYSTONE.v_2", + "vimConnectionId": "dummy-vimid", + "interfaceInfo": { + "key1":"value1", + "key2":"value2" + }, + "accessInfo": { + "key1":"value1", + "key2":"value2" + } + } + ] +} diff --git a/tackerclient/osc/v1/vnflcm/vnflcm.py b/tackerclient/osc/v1/vnflcm/vnflcm.py index a85bb06c..e39a57a7 100644 --- a/tackerclient/osc/v1/vnflcm/vnflcm.py +++ b/tackerclient/osc/v1/vnflcm/vnflcm.py @@ -537,3 +537,30 @@ class ScaleVnfLcm(command.Command): if not result: print((_('Scale request for VNF Instance %s has been accepted.') % parsed_args.vnf_instance)) + + +class ChangeExtConnVnfLcm(command.Command): + _description = _("Change External VNF Connectivity") + + def get_parser(self, prog_name): + parser = super(ChangeExtConnVnfLcm, self).get_parser(prog_name) + parser.add_argument( + _VNF_INSTANCE, + metavar="", + help=_("VNF instance ID to Change External VNF Connectivity")) + parser.add_argument( + 'request_file', + metavar="", + help=_("Specify change-ext-conn request parameters " + "in a json file.")) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.tackerclient + result = client.change_ext_conn_vnf_instance( + parsed_args.vnf_instance, jsonfile2body( + parsed_args.request_file)) + if not result: + print((_('Change External VNF Connectivity for VNF Instance %s ' + 'has been accepted.') % 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 7e5cc533..6e4826bb 100644 --- a/tackerclient/tests/unit/osc/v1/test_vnflcm.py +++ b/tackerclient/tests/unit/osc/v1/test_vnflcm.py @@ -739,3 +739,110 @@ class TestScaleVnfLcm(TestVnfLcm): self.assertRaises(exceptions.TackerClientException, self.scale_vnf_lcm.take_action, parsed_args) + + +class TestChangeExtConnVnfLcm(TestVnfLcm): + + def setUp(self): + super(TestChangeExtConnVnfLcm, self).setUp() + self.change_ext_conn_vnf_lcm = vnflcm.ChangeExtConnVnfLcm( + self.app, self.app_args, + cmd_name='vnflcm change-ext-conn') + + def test_take_action(self): + vnf_instance = vnflcm_fakes.vnf_instance_response() + sample_param_file = ("./tackerclient/osc/v1/vnflcm/samples/" + "change_ext_conn_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_ext_conn_vnf_lcm, + arglist, + verifylist) + + url = os.path.join(self.url, 'vnflcm/v1/vnf_instances', + vnf_instance['id'], 'change_ext_conn') + 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_ext_conn_vnf_lcm.take_action(parsed_args) + # check no fault response is received + self.assertNotCalled(m) + self.assertEqual( + ('Change External VNF Connectivity 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_ext_conn_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_ext_conn_vnf_lcm, + arglist, + verifylist) + + url = os.path.join(self.url, 'vnflcm/v1/vnf_instances', + vnf_instance['id'], 'change_ext_conn') + self.requests_mock.register_uri( + 'POST', url, headers=self.header, status_code=404, json={}) + + self.assertRaises(exceptions.TackerClientException, + self.change_ext_conn_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_ext_conn_vnf_lcm, + arglist, + verifylist) + + ex = self.assertRaises( + exceptions.InvalidInput, + self.change_ext_conn_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_open, + mock_access): + 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" + # command param + parsed_args = self.check_parser(self.change_ext_conn_vnf_lcm, + arglist, + verifylist) + + ex = self.assertRaises( + exceptions.InvalidInput, + self.change_ext_conn_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 424a926d..e93236cb 100644 --- a/tackerclient/v1_0/client.py +++ b/tackerclient/v1_0/client.py @@ -931,6 +931,11 @@ class VnfLCMClient(ClientBase): def fail_vnf_instance(self, occ_id): return self.post((self.vnf_lcm_op_occs_path + "/fail") % occ_id) + @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) + class Client(object): """Unified interface to interact with multiple applications of tacker service. @@ -1204,6 +1209,9 @@ class Client(object): def scale_vnf_instance(self, vnf_id, body): return self.vnf_lcm_client.scale_vnf_instance(vnf_id, body) + def change_ext_conn_vnf_instance(self, vnf_id, body): + return self.vnf_lcm_client.change_ext_conn_vnf_instance(vnf_id, body) + def delete_vnf_instance(self, vnf_id): return self.vnf_lcm_client.delete_vnf_instance(vnf_id)