Support of Fail VNF command in openstackclient
Add ``openstack vnflcm op fail`` to python-tackerclient. This command can execute Fail operation. The API can change operationStatus from "FAILED_TEMP" to "FAILED". Implements: blueprint support-error-handling Spec: https://specs.openstack.org/openstack/tacker-specs/specs/wallaby/support-error-handling-based-on-ETSI-NFV.html Change-Id: I83395e159e28c7e831dfe0ecd90435b3fb18c196
This commit is contained in:
		
				
					committed by
					
						
						Aldinson Esto
					
				
			
			
				
	
			
			
			
						parent
						
							b7968d815e
						
					
				
				
					commit
					58167535c7
				
			@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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="<vnf-lcm-op-occ-id>",
 | 
			
		||||
            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="<vnf-lcm-op-occ-id>",
 | 
			
		||||
            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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										86
									
								
								tackerclient/tests/unit/osc/v1/vnflcm_op_occs_fakes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								tackerclient/tests/unit/osc/v1/vnflcm_op_occs_fakes.py
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user