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:
Aldinson Esto
2021-03-01 22:35:28 +09:00
parent 1299e15f35
commit 6b29bb78b1
5 changed files with 254 additions and 13 deletions

View File

@@ -98,3 +98,4 @@ openstack.tackerclient.v1 =
vnflcm_op_rollback = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:RollbackVnfLcmOp
vnflcm_op_fail = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:FailVnfLcmOp
vnflcm_op_retry = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:RetryVnfLcmOp
vnflcm_op_list = tackerclient.osc.v1.vnflcm.vnflcm_op_occs:ListVnfLcmOp

View File

@@ -18,9 +18,11 @@ 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')
_MIXED_CASE_FIELDS = ['operationState', 'stateEnteredTime', 'startTime',
'vnfInstanceId', 'grantId', 'isAutomaticInvocation',
'isCancelPending', 'cancelMode', 'operationParams',
'resourceChanges', 'changedInfo',
'changedExtConnectivity']
_FORMATTERS = {'error': tacker_osc_utils.FormatComplexDataColumn,
'_links': tacker_osc_utils.FormatComplexDataColumn}
@@ -141,3 +143,105 @@ class RetryVnfLcmOp(command.Command):
if not result:
print((_('Retry request for LCM operation %(id)s has been'
' 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)

View File

@@ -18,19 +18,30 @@ from oslo_utils.fixture import uuidsentinel
from unittest import mock
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.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']
def _get_columns_vnflcm_op_occs(action=''):
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):
@@ -114,7 +125,8 @@ class TestFailVnfLcmOp(TestVnfLcm):
def test_take_action(self):
"""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']]
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)
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)
@@ -324,3 +336,95 @@ class TestRetryVnfLcmOp(TestVnfLcm):
verify_list = [('vnf_lcm_op_occ_id', arg_list)]
self.assertRaises(base.ParserException, self.check_parser,
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)

View File

@@ -16,7 +16,7 @@ from oslo_utils import uuidutils
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.
:param Dictionary attrs:
@@ -33,18 +33,36 @@ def vnflcm_op_occ_response(attrs=None):
"stateEnteredTime": "2018-12-22T16:59:45.187Z",
"startTime": "2018-12-22T16:59:45.187Z",
"vnfInstanceId": "376f37f3-d4e9-4d41-8e6a-9b0ec98695cc",
"grantId": "",
"operation": "INSTANTIATE",
"isAutomaticInvocation": "true",
"operationParams": {
"flavourId": "default",
"instantiationLevelId": "n-mme-min"
},
"isCancelPending": "true",
"cancelMode": "",
"error": {
"status": "500",
"detail": "internal server error"
},
"resourceChanges": [],
"changedInfo": [],
"changedExtConnectivity": [],
"_links": {
"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.
dummy_vnf_lcm_op_occ.update(attrs)
@@ -57,7 +75,10 @@ def get_vnflcm_op_occ_data(vnf_lcm_op_occ, columns=None):
:return:
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:
if vnf_lcm_op_occ.get(attribute):
vnf_lcm_op_occ.update(

View File

@@ -876,6 +876,7 @@ class VnfLCMClient(ClientBase):
vnf_instances_path = '/vnflcm/v1/vnf_instances'
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'
def build_action(self, action):
@@ -940,6 +941,12 @@ class VnfLCMClient(ClientBase):
def retry_vnf_instance(self, 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):
"""Unified interface to interact with multiple applications of tacker service.
@@ -1245,3 +1252,7 @@ class Client(object):
def download_vnf_package(self, 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)