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:
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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue