diff --git a/setup.cfg b/setup.cfg index 273a58a5..e5969036 100644 --- a/setup.cfg +++ b/setup.cfg @@ -95,3 +95,4 @@ openstack.tackerclient.v1 = vnflcm_update = tackerclient.osc.v1.vnflcm.vnflcm:UpdateVnfLcm vnflcm_scale = tackerclient.osc.v1.vnflcm.vnflcm:ScaleVnfLcm 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/vnflcm_op_occs.py b/tackerclient/osc/v1/vnflcm/vnflcm_op_occs.py index a75e1c49..2266ec48 100644 --- a/tackerclient/osc/v1/vnflcm/vnflcm_op_occs.py +++ b/tackerclient/osc/v1/vnflcm/vnflcm_op_occs.py @@ -11,7 +11,37 @@ # under the License. from osc_lib.command import command +from osc_lib import utils from tackerclient.i18n import _ +from tackerclient.osc import sdk_utils +from tackerclient.osc import utils as tacker_osc_utils + +_VNF_LCM_OP_OCC_ID = 'vnf_lcm_op_occ_id' + +_MIXED_CASE_FIELDS = ('operationState', 'stateEnteredTime', 'startTime', + 'vnfInstanceId', 'isAutomaticInvocation', + 'isCancelPending') + +_FORMATTERS = {'error': tacker_osc_utils.FormatComplexDataColumn, + '_links': tacker_osc_utils.FormatComplexDataColumn} + + +def _get_columns(vnflcm_op_occ_obj): + column_map = { + 'id': 'ID', + 'operationState': 'Operation State', + 'stateEnteredTime': 'State Entered Time', + 'startTime': 'Start Time', + 'vnfInstanceId': 'VNF Instance ID', + 'operation': 'Operation', + 'isAutomaticInvocation': 'Is Automatic Invocation', + 'isCancelPending': 'Is Cancel Pending', + 'error': 'Error', + '_links': 'Links' + } + + return sdk_utils.get_osc_show_columns_for_sdk_resource(vnflcm_op_occ_obj, + column_map) class RollbackVnfLcmOp(command.Command): @@ -26,7 +56,7 @@ class RollbackVnfLcmOp(command.Command): """ parser = super(RollbackVnfLcmOp, self).get_parser(prog_name) parser.add_argument( - 'vnf_lcm_op_occ_id', + _VNF_LCM_OP_OCC_ID, metavar="", help=_('VNF lifecycle management operation occurrence ID.')) @@ -43,3 +73,38 @@ class RollbackVnfLcmOp(command.Command): if not result: print((_('Rollback request for LCM operation %(id)s has been' ' accepted') % {'id': parsed_args.vnf_lcm_op_occ_id})) + + +class FailVnfLcmOp(command.ShowOne): + _description = _("Fail VNF Instance") + + def get_parser(self, prog_name): + """Add arguments to parser. + + Args: + prog_name ([type]): program name + + Returns: + parser([ArgumentParser]): + """ + parser = super(FailVnfLcmOp, self).get_parser(prog_name) + parser.add_argument( + _VNF_LCM_OP_OCC_ID, + metavar="", + help=_('VNF lifecycle management operation occurrence ID.')) + return parser + + def take_action(self, parsed_args): + """Execute fail_vnf_instance and output response. + + Args: + parsed_args ([Namespace]): arguments of CLI. + """ + client = self.app.client_manager.tackerclient + obj = client.fail_vnf_instance(parsed_args.vnf_lcm_op_occ_id) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties( + sdk_utils.DictModel(obj), + columns, formatters=_FORMATTERS, + mixed_case_fields=_MIXED_CASE_FIELDS) + return (display_columns, data) diff --git a/tackerclient/tests/unit/osc/v1/test_vnflcm_op_occs.py b/tackerclient/tests/unit/osc/v1/test_vnflcm_op_occs.py index bd20819c..ce7a55b3 100644 --- a/tackerclient/tests/unit/osc/v1/test_vnflcm_op_occs.py +++ b/tackerclient/tests/unit/osc/v1/test_vnflcm_op_occs.py @@ -21,6 +21,16 @@ from tackerclient.common import exceptions from tackerclient.osc.v1.vnflcm import vnflcm_op_occs from tackerclient.tests.unit.osc import base from tackerclient.tests.unit.osc.v1.fixture_data import client +from tackerclient.tests.unit.osc.v1 import vnflcm_op_occs_fakes + + +def _get_columns_vnflcm_op_occs(): + columns = ['ID', 'Operation State', 'State Entered Time', + 'Start Time', 'VNF Instance ID', 'Operation', + 'Is Automatic Invocation', 'Is Cancel Pending', + 'Error', 'Links'] + + return columns class TestVnfLcm(base.FixturedTestCase): @@ -92,3 +102,111 @@ class TestRollbackVnfLcmOp(TestVnfLcm): self.assertRaises(exceptions.TackerClientException, self.rollback_vnf_lcm.take_action, parsed_args) + + +class TestFailVnfLcmOp(TestVnfLcm): + + def setUp(self): + super(TestFailVnfLcmOp, self).setUp() + self.fail_vnf_lcm = vnflcm_op_occs.FailVnfLcmOp( + self.app, self.app_args, cmd_name='vnflcm op fail') + + def test_take_action(self): + """Test of take_action()""" + + vnflcm_op_occ = vnflcm_op_occs_fakes.vnflcm_op_occ_response() + + arg_list = [vnflcm_op_occ['id']] + verify_list = [('vnf_lcm_op_occ_id', vnflcm_op_occ['id'])] + + # command param + parsed_args = self.check_parser( + self.fail_vnf_lcm, arg_list, verify_list) + url = os.path.join( + self.url, + 'vnflcm/v1/vnf_lcm_op_occs', + vnflcm_op_occ['id'], + 'fail') + + self.requests_mock.register_uri( + 'POST', url, headers=self.header, json=vnflcm_op_occ) + + columns, data = (self.fail_vnf_lcm.take_action(parsed_args)) + expected_columns = _get_columns_vnflcm_op_occs() + + self.assertCountEqual(expected_columns, columns) + + def test_take_action_vnf_lcm_op_occ_id_not_found(self): + """Test if vnf-lcm-op-occ-id does not find""" + + arg_list = [uuidsentinel.vnf_lcm_op_occ_id] + verify_list = [('vnf_lcm_op_occ_id', uuidsentinel.vnf_lcm_op_occ_id)] + + # command param + parsed_args = self.check_parser( + self.fail_vnf_lcm, arg_list, verify_list) + + url = os.path.join( + self.url, + 'vnflcm/v1/vnf_lcm_op_occs', + uuidsentinel.vnf_lcm_op_occ_id, + 'fail') + self.requests_mock.register_uri( + 'POST', url, headers=self.header, status_code=404, json={}) + + self.assertRaises(exceptions.TackerClientException, + self.fail_vnf_lcm.take_action, + parsed_args) + + def test_take_action_vnf_lcm_op_occ_state_is_conflict(self): + """Test if vnf-lcm-op-occ state is conflict""" + + arg_list = [uuidsentinel.vnf_lcm_op_occ_id] + verify_list = [('vnf_lcm_op_occ_id', uuidsentinel.vnf_lcm_op_occ_id)] + + # command param + parsed_args = self.check_parser( + self.fail_vnf_lcm, arg_list, verify_list) + + url = os.path.join( + self.url, + 'vnflcm/v1/vnf_lcm_op_occs', + uuidsentinel.vnf_lcm_op_occ_id, + 'fail') + self.requests_mock.register_uri( + 'POST', url, headers=self.header, status_code=409, json={}) + + self.assertRaises(exceptions.TackerClientException, + self.fail_vnf_lcm.take_action, + parsed_args) + + def test_take_action_vnf_lcm_op_occ_internal_server_error(self): + """Test if request is internal server error""" + + arg_list = [uuidsentinel.vnf_lcm_op_occ_id] + verify_list = [('vnf_lcm_op_occ_id', uuidsentinel.vnf_lcm_op_occ_id)] + + # command param + parsed_args = self.check_parser( + self.fail_vnf_lcm, arg_list, verify_list) + + url = os.path.join( + self.url, + 'vnflcm/v1/vnf_lcm_op_occs', + uuidsentinel.vnf_lcm_op_occ_id, + 'fail') + self.requests_mock.register_uri( + 'POST', url, headers=self.header, status_code=500, json={}) + + self.assertRaises(exceptions.TackerClientException, + self.fail_vnf_lcm.take_action, + parsed_args) + + def test_take_action_vnf_lcm_op_occ_missing_vnf_lcm_op_occ_id_argument( + self): + """Test if vnflcm_op_occ_id is not provided""" + + arg_list = [] + verify_list = [('vnf_lcm_op_occ_id', arg_list)] + self.assertRaises(base.ParserException, self.check_parser, + self.fail_vnf_lcm, arg_list, verify_list) diff --git a/tackerclient/tests/unit/osc/v1/vnflcm_op_occs_fakes.py b/tackerclient/tests/unit/osc/v1/vnflcm_op_occs_fakes.py new file mode 100644 index 00000000..bbf6202c --- /dev/null +++ b/tackerclient/tests/unit/osc/v1/vnflcm_op_occs_fakes.py @@ -0,0 +1,86 @@ +# 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 oslo_utils.fixture import uuidsentinel +from oslo_utils import uuidutils + +from tackerclient.osc import utils as tacker_osc_utils + + +def vnflcm_op_occ_response(attrs=None): + """Create a fake vnflcm op occurrence. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A vnf lcm op occs dict + """ + attrs = attrs or {} + + # Set default attributes. + dummy_vnf_lcm_op_occ = { + "id": uuidsentinel.vnflcm_op_occ_id, + "operationState": "STARTING", + "stateEnteredTime": "2018-12-22T16:59:45.187Z", + "startTime": "2018-12-22T16:59:45.187Z", + "vnfInstanceId": "376f37f3-d4e9-4d41-8e6a-9b0ec98695cc", + "operation": "INSTANTIATE", + "isAutomaticInvocation": "true", + "isCancelPending": "true", + "error": { + "status": "500", + "detail": "internal server error" + }, + "_links": { + "self": "" + } + } + + # Overwrite default attributes. + dummy_vnf_lcm_op_occ.update(attrs) + + return dummy_vnf_lcm_op_occ + + +def get_vnflcm_op_occ_data(vnf_lcm_op_occ, columns=None): + """Get the vnflcm op occurrence. + + :return: + A tuple object sorted based on the name of the columns. + """ + complex_attributes = ['error', 'links'] + for attribute in complex_attributes: + if vnf_lcm_op_occ.get(attribute): + vnf_lcm_op_occ.update( + {attribute: tacker_osc_utils.FormatComplexDataColumn( + vnf_lcm_op_occ[attribute])}) + + # return the list of data as per column order + if columns: + return tuple([vnf_lcm_op_occ[key] for key in columns]) + + return tuple([vnf_lcm_op_occ[key] for key in sorted( + vnf_lcm_op_occ.keys())]) + + +def create_vnflcm_op_occs(count=2): + """Create multiple fake vnflcm op occs. + + :param count: The number of vnflcm op occs to fake + :return: + A list of fake vnflcm op occs dictionary + """ + vnflcm_op_occs = [] + for i in range(0, count): + unique_id = uuidutils.generate_uuid() + vnflcm_op_occs.append(vnflcm_op_occ_response(attrs={'id': unique_id})) + return vnflcm_op_occs diff --git a/tackerclient/tests/unit/test_cli10.py b/tackerclient/tests/unit/test_cli10.py index d6a010fa..e9622b22 100644 --- a/tackerclient/tests/unit/test_cli10.py +++ b/tackerclient/tests/unit/test_cli10.py @@ -798,3 +798,27 @@ class CLITestV10ExceptionHandler(CLITestV10Base): exceptions.TackerClientException, 500, expected_msg=expected_msg, error_content=error_content) + + def test_exception_handler_v10_tacker_etsi_error(self): + """Test ETSI error response""" + + known_error_map = [ + ({ + "status": "status 1", + "detail": "sample 1" + }, 400), + ({ + "status": "status 2", + "detail": "sample 2" + }, 404), + ({ + "status": "status 3", + "detail": "sample 3" + }, 409) + ] + + for error_content, status_code in known_error_map: + self._test_exception_handler_v10( + exceptions.TackerClientException, status_code, + expected_msg=error_content['detail'], + error_content=error_content) diff --git a/tackerclient/v1_0/client.py b/tackerclient/v1_0/client.py index 0bb9483c..424a926d 100644 --- a/tackerclient/v1_0/client.py +++ b/tackerclient/v1_0/client.py @@ -97,7 +97,8 @@ def exception_handler_v10(status_code, error_content): message=message) # ETSI error response if isinstance(etsi_error_content, dict): - if etsi_error_content.get('detail'): + if etsi_error_content.get('status') and \ + etsi_error_content.get('detail'): message = etsi_error_content.get('detail') raise exceptions.TackerClientException(status_code=status_code, message=message) @@ -926,6 +927,10 @@ class VnfLCMClient(ClientBase): def rollback_vnf_instance(self, occ_id): return self.post((self.vnf_lcm_op_occs_path + "/rollback") % occ_id) + @APIParamsCall + def fail_vnf_instance(self, occ_id): + return self.post((self.vnf_lcm_op_occs_path + "/fail") % occ_id) + class Client(object): """Unified interface to interact with multiple applications of tacker service. @@ -1208,6 +1213,9 @@ class Client(object): def rollback_vnf_instance(self, occ_id): return self.vnf_lcm_client.rollback_vnf_instance(occ_id) + def fail_vnf_instance(self, occ_id): + return self.vnf_lcm_client.fail_vnf_instance(occ_id) + def update_vnf_package(self, vnf_package, body): return self.vnf_package_client.update_vnf_package(vnf_package, body)