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:
Wataru Juso 2021-02-18 00:15:47 +09:00 committed by Aldinson Esto
parent b7968d815e
commit 58167535c7
6 changed files with 304 additions and 2 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)

View 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

View File

@ -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)

View File

@ -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)