Support CLI for Getting List of VNF LCM Operation Occurrences
Add ``openstack vnflcm op list`` to python-tackerclient. This command can execute getting the list of VNF LCM Operation Occurrences. User can specify filters for more specific results. Note: - Filtering for the following attributes: operationParams, error, resourceChanges and changedInfo is only limited to the parent attribute. Currently, child attributes/nested attributes are not searchable. Implements: blueprint support-fundamental-lcm Spec: https://specs.openstack.org/openstack/tacker-specs/specs/wallaby/support-fundamental-vnf-lcm-based-on-ETSI-NFV.html Change-Id: Ie0b3399946d2a705011269025102d9380102ca92
This commit is contained in:
@@ -98,3 +98,4 @@ openstack.tackerclient.v1 =
|
|||||||
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
|
vnflcm_op_fail = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:FailVnfLcmOp
|
||||||
vnflcm_op_retry = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:RetryVnfLcmOp
|
vnflcm_op_retry = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:RetryVnfLcmOp
|
||||||
|
vnflcm_op_list = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:ListVnfLcmOp
|
||||||
|
@@ -18,9 +18,11 @@ from tackerclient.osc import utils as tacker_osc_utils
|
|||||||
|
|
||||||
_VNF_LCM_OP_OCC_ID = 'vnf_lcm_op_occ_id'
|
_VNF_LCM_OP_OCC_ID = 'vnf_lcm_op_occ_id'
|
||||||
|
|
||||||
_MIXED_CASE_FIELDS = ('operationState', 'stateEnteredTime', 'startTime',
|
_MIXED_CASE_FIELDS = ['operationState', 'stateEnteredTime', 'startTime',
|
||||||
'vnfInstanceId', 'isAutomaticInvocation',
|
'vnfInstanceId', 'grantId', 'isAutomaticInvocation',
|
||||||
'isCancelPending')
|
'isCancelPending', 'cancelMode', 'operationParams',
|
||||||
|
'resourceChanges', 'changedInfo',
|
||||||
|
'changedExtConnectivity']
|
||||||
|
|
||||||
_FORMATTERS = {'error': tacker_osc_utils.FormatComplexDataColumn,
|
_FORMATTERS = {'error': tacker_osc_utils.FormatComplexDataColumn,
|
||||||
'_links': tacker_osc_utils.FormatComplexDataColumn}
|
'_links': tacker_osc_utils.FormatComplexDataColumn}
|
||||||
@@ -141,3 +143,105 @@ class RetryVnfLcmOp(command.Command):
|
|||||||
if not result:
|
if not result:
|
||||||
print((_('Retry request for LCM operation %(id)s has been'
|
print((_('Retry 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 ListVnfLcmOp(command.Lister):
|
||||||
|
_description = _("List LCM Operation Occurrences")
|
||||||
|
|
||||||
|
def get_parser(self, program_name):
|
||||||
|
"""Add arguments to parser.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
program_name ([type]): program name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
parser([ArgumentParser]):
|
||||||
|
"""
|
||||||
|
parser = super(ListVnfLcmOp, self).get_parser(program_name)
|
||||||
|
parser.add_argument(
|
||||||
|
"--filter",
|
||||||
|
metavar="<filter>",
|
||||||
|
help=_("Attribute-based-filtering parameters"),
|
||||||
|
)
|
||||||
|
fields_exclusive_group = parser.add_mutually_exclusive_group(
|
||||||
|
required=False)
|
||||||
|
fields_exclusive_group.add_argument(
|
||||||
|
"--fields",
|
||||||
|
metavar="<fields>",
|
||||||
|
help=_("Complex attributes to be included into the response"),
|
||||||
|
)
|
||||||
|
fields_exclusive_group.add_argument(
|
||||||
|
"--exclude-fields",
|
||||||
|
metavar="<exclude-fields>",
|
||||||
|
help=_("Complex attributes to be excluded from the response"),
|
||||||
|
)
|
||||||
|
return parser
|
||||||
|
|
||||||
|
def get_attributes(self):
|
||||||
|
"""Get attributes.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
attributes([attributes]): a list of table entry definitions.
|
||||||
|
Each entry should be a tuple consisting of
|
||||||
|
(API attribute name, header name, listing mode).
|
||||||
|
"""
|
||||||
|
fields = [
|
||||||
|
{
|
||||||
|
"key": "id",
|
||||||
|
"value": "ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "operationState",
|
||||||
|
"value": "Operation State"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "vnfInstanceId",
|
||||||
|
"value": "VNF Instance ID"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "operation",
|
||||||
|
"value": "Operation"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
attributes = []
|
||||||
|
for field in fields:
|
||||||
|
attributes.extend([(field['key'], field['value'],
|
||||||
|
tacker_osc_utils.LIST_BOTH)])
|
||||||
|
|
||||||
|
return tuple(attributes)
|
||||||
|
|
||||||
|
def take_action(self, parsed_args):
|
||||||
|
"""Execute list_vnflcm_op_occs and output response.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parsed_args ([Namespace]): arguments of CLI.
|
||||||
|
"""
|
||||||
|
params = {}
|
||||||
|
exclude_fields = []
|
||||||
|
extra_fields = []
|
||||||
|
|
||||||
|
if parsed_args.filter:
|
||||||
|
params['filter'] = parsed_args.filter
|
||||||
|
if parsed_args.fields:
|
||||||
|
params['fields'] = parsed_args.fields
|
||||||
|
fields = parsed_args.fields.split(',')
|
||||||
|
for field in fields:
|
||||||
|
extra_fields.append(field.split('/')[0])
|
||||||
|
if parsed_args.exclude_fields:
|
||||||
|
params['exclude-fields'] = parsed_args.exclude_fields
|
||||||
|
fields = parsed_args.exclude_fields.split(',')
|
||||||
|
exclude_fields.extend(fields)
|
||||||
|
|
||||||
|
client = self.app.client_manager.tackerclient
|
||||||
|
vnflcm_op_occs = client.list_vnf_lcm_op_occs(**params)
|
||||||
|
headers, columns = tacker_osc_utils.get_column_definitions(
|
||||||
|
self.get_attributes(),
|
||||||
|
long_listing=True)
|
||||||
|
|
||||||
|
dictionary_properties = (utils.get_dict_properties(
|
||||||
|
s, columns, mixed_case_fields=_MIXED_CASE_FIELDS)
|
||||||
|
for s in vnflcm_op_occs
|
||||||
|
)
|
||||||
|
|
||||||
|
return (headers, dictionary_properties)
|
||||||
|
@@ -18,19 +18,30 @@ from oslo_utils.fixture import uuidsentinel
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from tackerclient.common import exceptions
|
from tackerclient.common import exceptions
|
||||||
|
from tackerclient.osc import utils as tacker_osc_utils
|
||||||
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
|
from tackerclient.tests.unit.osc.v1 import vnflcm_op_occs_fakes
|
||||||
|
|
||||||
|
|
||||||
def _get_columns_vnflcm_op_occs():
|
def _get_columns_vnflcm_op_occs(action=''):
|
||||||
columns = ['ID', 'Operation State', 'State Entered Time',
|
|
||||||
'Start Time', 'VNF Instance ID', 'Operation',
|
|
||||||
'Is Automatic Invocation', 'Is Cancel Pending',
|
|
||||||
'Error', 'Links']
|
|
||||||
|
|
||||||
return columns
|
if action == 'fail':
|
||||||
|
return ['ID', 'Operation State', 'State Entered Time',
|
||||||
|
'Start Time', 'VNF Instance ID', 'Operation',
|
||||||
|
'Is Automatic Invocation', 'Is Cancel Pending',
|
||||||
|
'Error', 'Links']
|
||||||
|
elif action == 'list':
|
||||||
|
return ['ID', 'Operation State', 'VNF Instance ID',
|
||||||
|
'Operation']
|
||||||
|
else:
|
||||||
|
return ['ID', 'Operation State', 'State Entered Time',
|
||||||
|
'Start Time', 'VNF Instance ID', 'Grant ID',
|
||||||
|
'Operation', 'Is Automatic Invocation',
|
||||||
|
'Operation Parameters', 'Is Cancel Pending',
|
||||||
|
'Cancel Mode', 'Error', 'Resource Changes',
|
||||||
|
'Changed Info', 'Changed External Connectivity', 'Links']
|
||||||
|
|
||||||
|
|
||||||
class TestVnfLcm(base.FixturedTestCase):
|
class TestVnfLcm(base.FixturedTestCase):
|
||||||
@@ -114,7 +125,8 @@ class TestFailVnfLcmOp(TestVnfLcm):
|
|||||||
def test_take_action(self):
|
def test_take_action(self):
|
||||||
"""Test of take_action()"""
|
"""Test of take_action()"""
|
||||||
|
|
||||||
vnflcm_op_occ = vnflcm_op_occs_fakes.vnflcm_op_occ_response()
|
vnflcm_op_occ = vnflcm_op_occs_fakes.vnflcm_op_occ_response(
|
||||||
|
action='fail')
|
||||||
|
|
||||||
arg_list = [vnflcm_op_occ['id']]
|
arg_list = [vnflcm_op_occ['id']]
|
||||||
verify_list = [('vnf_lcm_op_occ_id', vnflcm_op_occ['id'])]
|
verify_list = [('vnf_lcm_op_occ_id', vnflcm_op_occ['id'])]
|
||||||
@@ -132,7 +144,7 @@ class TestFailVnfLcmOp(TestVnfLcm):
|
|||||||
'POST', url, headers=self.header, json=vnflcm_op_occ)
|
'POST', url, headers=self.header, json=vnflcm_op_occ)
|
||||||
|
|
||||||
columns, data = (self.fail_vnf_lcm.take_action(parsed_args))
|
columns, data = (self.fail_vnf_lcm.take_action(parsed_args))
|
||||||
expected_columns = _get_columns_vnflcm_op_occs()
|
expected_columns = _get_columns_vnflcm_op_occs(action='fail')
|
||||||
|
|
||||||
self.assertCountEqual(expected_columns, columns)
|
self.assertCountEqual(expected_columns, columns)
|
||||||
|
|
||||||
@@ -324,3 +336,95 @@ class TestRetryVnfLcmOp(TestVnfLcm):
|
|||||||
verify_list = [('vnf_lcm_op_occ_id', arg_list)]
|
verify_list = [('vnf_lcm_op_occ_id', arg_list)]
|
||||||
self.assertRaises(base.ParserException, self.check_parser,
|
self.assertRaises(base.ParserException, self.check_parser,
|
||||||
self.retry_vnf_lcm, arg_list, verify_list)
|
self.retry_vnf_lcm, arg_list, verify_list)
|
||||||
|
|
||||||
|
|
||||||
|
class TestListVnfLcmOp(TestVnfLcm):
|
||||||
|
|
||||||
|
vnflcm_op_occs_obj = vnflcm_op_occs_fakes.create_vnflcm_op_occs(count=3)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestListVnfLcmOp, self).setUp()
|
||||||
|
self.list_vnflcm_op_occ = vnflcm_op_occs.ListVnfLcmOp(
|
||||||
|
self.app, self.app_args, cmd_name='vnflcm op list')
|
||||||
|
|
||||||
|
def test_take_action(self):
|
||||||
|
parsed_args = self.check_parser(self.list_vnflcm_op_occ, [], [])
|
||||||
|
self.requests_mock.register_uri(
|
||||||
|
'GET', os.path.join(self.url,
|
||||||
|
'vnflcm/v1/vnf_lcm_op_occs'),
|
||||||
|
json=self.vnflcm_op_occs_obj, headers=self.header)
|
||||||
|
|
||||||
|
actual_columns, data = self.list_vnflcm_op_occ.take_action(parsed_args)
|
||||||
|
|
||||||
|
headers, columns = tacker_osc_utils.get_column_definitions(
|
||||||
|
self.list_vnflcm_op_occ.get_attributes(), long_listing=True)
|
||||||
|
|
||||||
|
expected_data = []
|
||||||
|
for vnflcm_op_occ_obj in self.vnflcm_op_occs_obj:
|
||||||
|
expected_data.append(vnflcm_op_occs_fakes.get_vnflcm_op_occ_data(
|
||||||
|
vnflcm_op_occ_obj, columns=columns))
|
||||||
|
|
||||||
|
self.assertItemsEqual(_get_columns_vnflcm_op_occs(action='list'),
|
||||||
|
actual_columns)
|
||||||
|
self.assertItemsEqual(expected_data, list(data))
|
||||||
|
|
||||||
|
def test_take_action_with_filter(self):
|
||||||
|
parsed_args = self.check_parser(
|
||||||
|
self.list_vnflcm_op_occ,
|
||||||
|
["--filter", '(eq,operationState,STARTING)'],
|
||||||
|
[('filter', '(eq,operationState,STARTING)')])
|
||||||
|
self.requests_mock.register_uri(
|
||||||
|
'GET', os.path.join(
|
||||||
|
self.url,
|
||||||
|
'vnflcm/v1/vnf_lcm_op_occs?'
|
||||||
|
'filter=(eq,operationState,STARTING)'),
|
||||||
|
json=self.vnflcm_op_occs_obj, headers=self.header)
|
||||||
|
|
||||||
|
actual_columns, data = self.list_vnflcm_op_occ.take_action(parsed_args)
|
||||||
|
|
||||||
|
headers, columns = tacker_osc_utils.get_column_definitions(
|
||||||
|
self.list_vnflcm_op_occ.get_attributes(), long_listing=True)
|
||||||
|
|
||||||
|
expected_data = []
|
||||||
|
for vnflcm_op_occ_obj in self.vnflcm_op_occs_obj:
|
||||||
|
expected_data.append(vnflcm_op_occs_fakes.get_vnflcm_op_occ_data(
|
||||||
|
vnflcm_op_occ_obj, columns=columns))
|
||||||
|
|
||||||
|
self.assertItemsEqual(_get_columns_vnflcm_op_occs(action='list'),
|
||||||
|
actual_columns)
|
||||||
|
self.assertListItemsEqual(expected_data, list(data))
|
||||||
|
|
||||||
|
def test_take_action_with_incorrect_filter(self):
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(
|
||||||
|
self.list_vnflcm_op_occ,
|
||||||
|
["--filter", '(operationState)'],
|
||||||
|
[('filter', '(operationState)')])
|
||||||
|
|
||||||
|
url = os.path.join(
|
||||||
|
self.url,
|
||||||
|
'vnflcm/v1/vnf_lcm_op_occs?filter=(operationState)')
|
||||||
|
self.requests_mock.register_uri(
|
||||||
|
'POST', url, headers=self.header, status_code=400, json={})
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.TackerClientException,
|
||||||
|
self.list_vnflcm_op_occ.take_action,
|
||||||
|
parsed_args)
|
||||||
|
|
||||||
|
def test_take_action_internal_server_error(self):
|
||||||
|
|
||||||
|
parsed_args = self.check_parser(
|
||||||
|
self.list_vnflcm_op_occ,
|
||||||
|
["--filter", '(eq,operationState,STARTING)'],
|
||||||
|
[('filter', '(eq,operationState,STARTING)')])
|
||||||
|
|
||||||
|
url = os.path.join(
|
||||||
|
self.url,
|
||||||
|
'vnflcm/v1/vnf_lcm_op_occs?'
|
||||||
|
'filter=(eq,operationState,STARTING)')
|
||||||
|
self.requests_mock.register_uri(
|
||||||
|
'POST', url, headers=self.header, status_code=500, json={})
|
||||||
|
|
||||||
|
self.assertRaises(exceptions.TackerClientException,
|
||||||
|
self.list_vnflcm_op_occ.take_action,
|
||||||
|
parsed_args)
|
||||||
|
@@ -16,7 +16,7 @@ from oslo_utils import uuidutils
|
|||||||
from tackerclient.osc import utils as tacker_osc_utils
|
from tackerclient.osc import utils as tacker_osc_utils
|
||||||
|
|
||||||
|
|
||||||
def vnflcm_op_occ_response(attrs=None):
|
def vnflcm_op_occ_response(attrs=None, action=''):
|
||||||
"""Create a fake vnflcm op occurrence.
|
"""Create a fake vnflcm op occurrence.
|
||||||
|
|
||||||
:param Dictionary attrs:
|
:param Dictionary attrs:
|
||||||
@@ -33,18 +33,36 @@ def vnflcm_op_occ_response(attrs=None):
|
|||||||
"stateEnteredTime": "2018-12-22T16:59:45.187Z",
|
"stateEnteredTime": "2018-12-22T16:59:45.187Z",
|
||||||
"startTime": "2018-12-22T16:59:45.187Z",
|
"startTime": "2018-12-22T16:59:45.187Z",
|
||||||
"vnfInstanceId": "376f37f3-d4e9-4d41-8e6a-9b0ec98695cc",
|
"vnfInstanceId": "376f37f3-d4e9-4d41-8e6a-9b0ec98695cc",
|
||||||
|
"grantId": "",
|
||||||
"operation": "INSTANTIATE",
|
"operation": "INSTANTIATE",
|
||||||
"isAutomaticInvocation": "true",
|
"isAutomaticInvocation": "true",
|
||||||
|
"operationParams": {
|
||||||
|
"flavourId": "default",
|
||||||
|
"instantiationLevelId": "n-mme-min"
|
||||||
|
},
|
||||||
"isCancelPending": "true",
|
"isCancelPending": "true",
|
||||||
|
"cancelMode": "",
|
||||||
"error": {
|
"error": {
|
||||||
"status": "500",
|
"status": "500",
|
||||||
"detail": "internal server error"
|
"detail": "internal server error"
|
||||||
},
|
},
|
||||||
|
"resourceChanges": [],
|
||||||
|
"changedInfo": [],
|
||||||
|
"changedExtConnectivity": [],
|
||||||
"_links": {
|
"_links": {
|
||||||
"self": ""
|
"self": ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if action == 'fail':
|
||||||
|
fail_not_needed_columns = [
|
||||||
|
'grantId', 'operationParams',
|
||||||
|
'cancelMode', 'resourceChanges', 'changedInfo',
|
||||||
|
'changedExtConnectivity']
|
||||||
|
|
||||||
|
for key in fail_not_needed_columns:
|
||||||
|
del dummy_vnf_lcm_op_occ[key]
|
||||||
|
|
||||||
# Overwrite default attributes.
|
# Overwrite default attributes.
|
||||||
dummy_vnf_lcm_op_occ.update(attrs)
|
dummy_vnf_lcm_op_occ.update(attrs)
|
||||||
|
|
||||||
@@ -57,7 +75,10 @@ def get_vnflcm_op_occ_data(vnf_lcm_op_occ, columns=None):
|
|||||||
:return:
|
:return:
|
||||||
A tuple object sorted based on the name of the columns.
|
A tuple object sorted based on the name of the columns.
|
||||||
"""
|
"""
|
||||||
complex_attributes = ['error', 'links']
|
complex_attributes = [
|
||||||
|
'operationParams', 'error', 'resourceChanges',
|
||||||
|
'changedInfo', 'changedExtConnectivity', 'links']
|
||||||
|
|
||||||
for attribute in complex_attributes:
|
for attribute in complex_attributes:
|
||||||
if vnf_lcm_op_occ.get(attribute):
|
if vnf_lcm_op_occ.get(attribute):
|
||||||
vnf_lcm_op_occ.update(
|
vnf_lcm_op_occ.update(
|
||||||
|
@@ -876,6 +876,7 @@ class VnfLCMClient(ClientBase):
|
|||||||
|
|
||||||
vnf_instances_path = '/vnflcm/v1/vnf_instances'
|
vnf_instances_path = '/vnflcm/v1/vnf_instances'
|
||||||
vnf_instance_path = '/vnflcm/v1/vnf_instances/%s'
|
vnf_instance_path = '/vnflcm/v1/vnf_instances/%s'
|
||||||
|
vnf_lcm_op_occurrences_path = '/vnflcm/v1/vnf_lcm_op_occs'
|
||||||
vnf_lcm_op_occs_path = '/vnflcm/v1/vnf_lcm_op_occs/%s'
|
vnf_lcm_op_occs_path = '/vnflcm/v1/vnf_lcm_op_occs/%s'
|
||||||
|
|
||||||
def build_action(self, action):
|
def build_action(self, action):
|
||||||
@@ -940,6 +941,12 @@ class VnfLCMClient(ClientBase):
|
|||||||
def retry_vnf_instance(self, occ_id):
|
def retry_vnf_instance(self, occ_id):
|
||||||
return self.post((self.vnf_lcm_op_occs_path + "/retry") % occ_id)
|
return self.post((self.vnf_lcm_op_occs_path + "/retry") % occ_id)
|
||||||
|
|
||||||
|
@APIParamsCall
|
||||||
|
def list_vnf_lcm_op_occs(self, retrieve_all=True, **_params):
|
||||||
|
vnf_lcm_op_occs = self.list(None, self.vnf_lcm_op_occurrences_path,
|
||||||
|
retrieve_all, **_params)
|
||||||
|
return vnf_lcm_op_occs
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
@@ -1245,3 +1252,7 @@ class Client(object):
|
|||||||
|
|
||||||
def download_vnf_package(self, vnf_package):
|
def download_vnf_package(self, vnf_package):
|
||||||
return self.vnf_package_client.download_vnf_package(vnf_package)
|
return self.vnf_package_client.download_vnf_package(vnf_package)
|
||||||
|
|
||||||
|
def list_vnf_lcm_op_occs(self, retrieve_all=True, **_params):
|
||||||
|
return self.vnf_lcm_client.list_vnf_lcm_op_occs(
|
||||||
|
retrieve_all=retrieve_all, **_params)
|
||||||
|
Reference in New Issue
Block a user