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_update = tackerclient.osc.v1.vnflcm.vnflcm:UpdateVnfLcm
|
||||||
vnflcm_scale = tackerclient.osc.v1.vnflcm.vnflcm:ScaleVnfLcm
|
vnflcm_scale = tackerclient.osc.v1.vnflcm.vnflcm:ScaleVnfLcm
|
||||||
vnflcm_op_rollback = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:RollbackVnfLcmOp
|
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.
|
# under the License.
|
||||||
|
|
||||||
from osc_lib.command import command
|
from osc_lib.command import command
|
||||||
|
from osc_lib import utils
|
||||||
from tackerclient.i18n import _
|
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):
|
class RollbackVnfLcmOp(command.Command):
|
||||||
@@ -26,7 +56,7 @@ class RollbackVnfLcmOp(command.Command):
|
|||||||
"""
|
"""
|
||||||
parser = super(RollbackVnfLcmOp, self).get_parser(prog_name)
|
parser = super(RollbackVnfLcmOp, self).get_parser(prog_name)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'vnf_lcm_op_occ_id',
|
_VNF_LCM_OP_OCC_ID,
|
||||||
metavar="<vnf-lcm-op-occ-id>",
|
metavar="<vnf-lcm-op-occ-id>",
|
||||||
help=_('VNF lifecycle management operation occurrence ID.'))
|
help=_('VNF lifecycle management operation occurrence ID.'))
|
||||||
|
|
||||||
@@ -43,3 +73,38 @@ class RollbackVnfLcmOp(command.Command):
|
|||||||
if not result:
|
if not result:
|
||||||
print((_('Rollback request for LCM operation %(id)s has been'
|
print((_('Rollback request for LCM operation %(id)s has been'
|
||||||
' accepted') % {'id': parsed_args.vnf_lcm_op_occ_id}))
|
' 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.osc.v1.vnflcm import vnflcm_op_occs
|
||||||
from tackerclient.tests.unit.osc import base
|
from tackerclient.tests.unit.osc import base
|
||||||
from tackerclient.tests.unit.osc.v1.fixture_data import client
|
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):
|
class TestVnfLcm(base.FixturedTestCase):
|
||||||
@@ -92,3 +102,111 @@ class TestRollbackVnfLcmOp(TestVnfLcm):
|
|||||||
self.assertRaises(exceptions.TackerClientException,
|
self.assertRaises(exceptions.TackerClientException,
|
||||||
self.rollback_vnf_lcm.take_action,
|
self.rollback_vnf_lcm.take_action,
|
||||||
parsed_args)
|
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,
|
exceptions.TackerClientException, 500,
|
||||||
expected_msg=expected_msg,
|
expected_msg=expected_msg,
|
||||||
error_content=error_content)
|
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)
|
message=message)
|
||||||
# ETSI error response
|
# ETSI error response
|
||||||
if isinstance(etsi_error_content, dict):
|
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')
|
message = etsi_error_content.get('detail')
|
||||||
raise exceptions.TackerClientException(status_code=status_code,
|
raise exceptions.TackerClientException(status_code=status_code,
|
||||||
message=message)
|
message=message)
|
||||||
@@ -926,6 +927,10 @@ class VnfLCMClient(ClientBase):
|
|||||||
def rollback_vnf_instance(self, occ_id):
|
def rollback_vnf_instance(self, occ_id):
|
||||||
return self.post((self.vnf_lcm_op_occs_path + "/rollback") % 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):
|
class Client(object):
|
||||||
"""Unified interface to interact with multiple applications of tacker service.
|
"""Unified interface to interact with multiple applications of tacker service.
|
||||||
@@ -1208,6 +1213,9 @@ class Client(object):
|
|||||||
def rollback_vnf_instance(self, occ_id):
|
def rollback_vnf_instance(self, occ_id):
|
||||||
return self.vnf_lcm_client.rollback_vnf_instance(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):
|
def update_vnf_package(self, vnf_package, body):
|
||||||
return self.vnf_package_client.update_vnf_package(vnf_package, body)
|
return self.vnf_package_client.update_vnf_package(vnf_package, body)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user