Add support cnf auto scale via threshold interface

Add the Performance Management Threshold CLI to support AutoScale.
The Performance Management Threshold API is based on ETSI NFV-SOL
002 v3.3.1 and ETSI NFV-SOL 003 v3.3.1, which is Version "2.0.0"
API of Tacker.

Implements: blueprint support-auto-lcm
Change-Id: Idd313d6abe47dfa41fc86ddc614d00f99f3fc3b2
This commit is contained in:
Yi Feng 2023-02-07 10:15:51 +09:00 committed by YiFeng
parent 82bf387f77
commit 8b45c8082b
15 changed files with 907 additions and 132 deletions

View File

@ -110,3 +110,8 @@ of individual command can be referred by **openstack help <command-name>**.
openstack vnfpm job update Update PM job.
openstack vnfpm job delete Delete PM job.
openstack vnfpm report show Show PM report.
openstack vnfpm threshold create Create PM threshold.
openstack vnfpm threshold list List PM threshold.
openstack vnfpm threshold show Show PM threshold.
openstack vnfpm threshold update Update PM threshold.
openstack vnfpm threshold delete Delete PM threshold.

View File

@ -137,6 +137,11 @@ openstack.tackerclient.v2 =
vnfpm_job_update = tackerclient.osc.v2.vnfpm.vnfpm_job:UpdateVnfPmJob
vnfpm_job_delete = tackerclient.osc.v2.vnfpm.vnfpm_job:DeleteVnfPmJob
vnfpm_report_show = tackerclient.osc.v2.vnfpm.vnfpm_report:ShowVnfPmReport
vnfpm_threshold_create = tackerclient.osc.v2.vnfpm.vnfpm_threshold:CreateVnfPmThreshold
vnfpm_threshold_list = tackerclient.osc.v2.vnfpm.vnfpm_threshold:ListVnfPmThreshold
vnfpm_threshold_show = tackerclient.osc.v2.vnfpm.vnfpm_threshold:ShowVnfPmThreshold
vnfpm_threshold_update = tackerclient.osc.v2.vnfpm.vnfpm_threshold:UpdateVnfPmThreshold
vnfpm_threshold_delete = tackerclient.osc.v2.vnfpm.vnfpm_threshold:DeleteVnfPmThreshold
vnffm_alarm_list = tackerclient.osc.v2.vnffm.vnffm_alarm:ListVnfFmAlarm
vnffm_alarm_show = tackerclient.osc.v2.vnffm.vnffm_alarm:ShowVnfFmAlarm
vnffm_alarm_update = tackerclient.osc.v2.vnffm.vnffm_alarm:UpdateVnfFmAlarm

View File

@ -20,7 +20,9 @@ Stuffs specific to tackerclient OSC plugin should not be added
to this module. They should go to tackerclient.osc.v1.utils.
"""
import json
import operator
import os
from cliff import columns as cliff_columns
from keystoneclient import exceptions as identity_exc
@ -29,6 +31,7 @@ from keystoneclient.v3 import projects
from osc_lib import utils
from oslo_serialization import jsonutils
from tackerclient.common import exceptions
from tackerclient.i18n import _
@ -212,3 +215,29 @@ class FormatComplexDataColumn(cliff_columns.FormattableColumn):
def human_readable(self):
return format_dict_with_indention(self._value)
def jsonfile2body(file_path):
if file_path is None:
msg = _("File %s does not exist")
reason = msg % file_path
raise exceptions.InvalidInput(reason=reason)
if os.access(file_path, os.R_OK) is False:
msg = _("User does not have read privileges to it")
raise exceptions.InvalidInput(reason=msg)
try:
with open(file_path) as f:
body = json.load(f)
except (IOError, ValueError) as ex:
msg = _("Failed to load parameter file. Error: %s")
reason = msg % ex
raise exceptions.InvalidInput(reason=reason)
if not body:
reason = _('The parameter file is empty')
raise exceptions.EmptyInput(reason=reason)
return body

View File

@ -13,9 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
import logging
import os
import time
from osc_lib.command import command
@ -113,7 +111,7 @@ class CreateVnfLcm(command.ShowOne):
body = {}
if file_path:
return jsonfile2body(file_path)
return tacker_osc_utils.jsonfile2body(file_path)
body['vnfdId'] = parsed_args.vnfd_id
@ -184,29 +182,6 @@ class ListVnfLcm(command.Lister):
) for s in vnf_instances))
def jsonfile2body(file_path):
if file_path is not None and os.access(file_path, os.R_OK) is False:
msg = _("File %s does not exist or user does not have read "
"privileges to it")
reason = msg % file_path
raise exceptions.InvalidInput(reason=reason)
try:
with open(file_path) as f:
body = json.load(f)
except (IOError, ValueError) as ex:
msg = _("Failed to load parameter file. Error: %s")
reason = msg % ex
raise exceptions.InvalidInput(reason=reason)
if not body:
reason = _('The parameter file is empty')
raise exceptions.InvalidInput(reason=reason)
return body
class InstantiateVnfLcm(command.Command):
_description = _("Instantiate a VNF Instance")
@ -226,7 +201,7 @@ class InstantiateVnfLcm(command.Command):
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
result = client.instantiate_vnf_instance(
parsed_args.vnf_instance, jsonfile2body(
parsed_args.vnf_instance, tacker_osc_utils.jsonfile2body(
parsed_args.instantiation_request_file))
if not result:
print((_('Instantiate request for VNF Instance %(id)s has been'
@ -271,7 +246,8 @@ class HealVnfLcm(command.Command):
if parsed_args.vnfc_instance:
body['vnfcInstanceId'] = parsed_args.vnfc_instance
if parsed_args.additional_param_file:
body.update(jsonfile2body(parsed_args.additional_param_file))
body.update(tacker_osc_utils.jsonfile2body(
parsed_args.additional_param_file))
return body
@ -461,7 +437,7 @@ class UpdateVnfLcm(command.Command):
body = {}
if file_path:
return jsonfile2body(file_path)
return tacker_osc_utils.jsonfile2body(file_path)
return body
@ -535,7 +511,8 @@ class ScaleVnfLcm(command.Command):
body['numberOfSteps'] = parsed_args.number_of_steps
if parsed_args.additional_param_file:
body.update(jsonfile2body(parsed_args.additional_param_file))
body.update(tacker_osc_utils.jsonfile2body(
parsed_args.additional_param_file))
return body
@ -574,7 +551,7 @@ class ChangeExtConnVnfLcm(command.Command):
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
result = client.change_ext_conn_vnf_instance(
parsed_args.vnf_instance, jsonfile2body(
parsed_args.vnf_instance, tacker_osc_utils.jsonfile2body(
parsed_args.request_file))
if not result:
print((_('Change External VNF Connectivity for VNF Instance %s '
@ -601,7 +578,7 @@ class ChangeVnfPkgVnfLcm(command.Command):
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
result = client.change_vnfpkg_vnf_instance(
parsed_args.vnf_instance, jsonfile2body(
parsed_args.vnf_instance, tacker_osc_utils.jsonfile2body(
parsed_args.request_file))
if not result:
print((_('Change Current VNF Package for VNF Instance %s '

View File

@ -13,9 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
import logging
import os
from osc_lib.command import command
from osc_lib import utils
@ -49,29 +47,6 @@ def _get_columns(lccn_subsc_obj):
column_map)
def jsonfile2body(file_path):
if file_path is not None and os.access(file_path, os.R_OK) is False:
msg = _("File %s does not exist or user does not have read "
"privileges to it")
reason = msg % file_path
raise exceptions.InvalidInput(reason=reason)
try:
with open(file_path) as f:
body = json.load(f)
except (IOError, ValueError) as ex:
msg = _("Failed to load parameter file. Error: %s")
reason = msg % ex
raise exceptions.InvalidInput(reason=reason)
if not body:
reason = _('The parameter file is empty')
raise exceptions.InvalidInput(reason=reason)
return body
class CreateLccnSubscription(command.ShowOne):
_description = _("Create a new Lccn Subscription")
@ -86,7 +61,7 @@ class CreateLccnSubscription(command.ShowOne):
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
subsc = client.create_lccn_subscription(
jsonfile2body(parsed_args.create_request_file))
tacker_osc_utils.jsonfile2body(parsed_args.create_request_file))
display_columns, columns = _get_columns(subsc)
data = utils.get_item_properties(sdk_utils.DictModel(subsc),
columns, formatters=_FORMATTERS,

View File

@ -13,9 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
import logging
import os
from osc_lib.command import command
from osc_lib import utils
@ -56,32 +54,6 @@ def _get_columns(vnffm_sub_obj):
vnffm_sub_obj, column_map)
def jsonfile2body(file_path):
if file_path is None:
msg = _("File %s does not exist")
reason = msg % file_path
raise exceptions.InvalidInput(reason=reason)
if os.access(file_path, os.R_OK) is False:
msg = _("User does not have read privileges to it")
raise exceptions.InvalidInput(reason=msg)
try:
with open(file_path) as f:
body = json.load(f)
except (IOError, ValueError) as ex:
msg = _("Failed to load parameter file. Error: %s")
reason = msg % ex
raise exceptions.InvalidInput(reason=reason)
if not body:
reason = _('The parameter file is empty')
raise exceptions.EmptyInput(reason=reason)
return body
class CreateVnfFmSub(command.ShowOne):
_description = _("Create a new VNF FM subscription")
@ -97,7 +69,7 @@ class CreateVnfFmSub(command.ShowOne):
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
vnf_fm_sub = client.create_vnf_fm_sub(
jsonfile2body(parsed_args.request_file))
tacker_osc_utils.jsonfile2body(parsed_args.request_file))
display_columns, columns = _get_columns(vnf_fm_sub)
data = utils.get_item_properties(
sdk_utils.DictModel(vnf_fm_sub), columns,

View File

@ -0,0 +1,40 @@
{
"objectType": "Vnfc",
"objectInstanceId": "object-instance-id-1",
"subObjectInstanceIds": [
"sub-object-instance-id-2"
],
"criteria": {
"performanceMetric": "VCpuUsageMeanVnf.object-instance-id-1",
"thresholdType": "SIMPLE",
"simpleThresholdDetails": {
"thresholdValue": 400.5,
"hysteresis": 10.3
}
},
"callbackUri": "/nfvo/notify/threshold",
"authentication": {
"authType": [
"BASIC",
"OAUTH2_CLIENT_CREDENTIALS",
"OAUTH2_CLIENT_CERT"
],
"paramsBasic": {
"userName": "nfvo",
"password": "nfvopwd"
},
"paramsOauth2ClientCredentials": {
"clientId": "auth_user_name",
"clientPassword": "auth_password",
"tokenEndpoint": "token_endpoint"
},
"paramsOauth2ClientCert": {
"clientId": "test",
"certificateRef": {
"type": "x5t#256",
"value": "03c6e188d1fe5d3da8c9bc9a8dc531a2b3e"
},
"tokenEndpoint": "http://127.0.0.1/token"
}
}
}

View File

@ -0,0 +1,27 @@
{
"callbackUri": "/nfvo/notify/threshold",
"authentication": {
"authType": [
"BASIC",
"OAUTH2_CLIENT_CREDENTIALS",
"OAUTH2_CLIENT_CERT"
],
"paramsBasic": {
"userName": "nfvo",
"password": "nfvopwd"
},
"paramsOauth2ClientCredentials": {
"clientId": "auth_user_name",
"clientPassword": "auth_password",
"tokenEndpoint": "token_endpoint"
},
"paramsOauth2ClientCert": {
"clientId": "test",
"certificateRef": {
"type": "x5t#256",
"value": "03c6e188d1fe5d3da8c9bc9a8dc531a2b3e"
},
"tokenEndpoint": "http://127.0.0.1/token"
}
}
}

View File

@ -13,9 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
import logging
import os
from functools import reduce
from osc_lib.command import command
@ -78,32 +76,6 @@ def _get_columns(vnfpm_job_obj, action=None):
vnfpm_job_obj, column_map)
def jsonfile2body(file_path):
if file_path is None:
msg = _("File %s does not exist")
reason = msg % file_path
raise exceptions.InvalidInput(reason=reason)
if os.access(file_path, os.R_OK) is False:
msg = _("User does not have read privileges to it")
raise exceptions.InvalidInput(reason=msg)
try:
with open(file_path) as f:
body = json.load(f)
except (IOError, ValueError) as ex:
msg = _("Failed to load parameter file. Error: %s")
reason = msg % ex
raise exceptions.InvalidInput(reason=reason)
if not body:
reason = _('The parameter file is empty')
raise exceptions.EmptyInput(reason=reason)
return body
class CreateVnfPmJob(command.ShowOne):
_description = _("Create a new VNF PM job")
@ -119,7 +91,7 @@ class CreateVnfPmJob(command.ShowOne):
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
vnf_pm_job = client.create_vnf_pm_job(
jsonfile2body(parsed_args.request_file))
tacker_osc_utils.jsonfile2body(parsed_args.request_file))
display_columns, columns = _get_columns(vnf_pm_job)
data = utils.get_item_properties(
sdk_utils.DictModel(vnf_pm_job), columns,
@ -284,7 +256,7 @@ class UpdateVnfPmJob(command.ShowOne):
client = self.app.client_manager.tackerclient
updated_values = client.update_vnf_pm_job(
parsed_args.vnf_pm_job_id,
jsonfile2body(parsed_args.request_file))
tacker_osc_utils.jsonfile2body(parsed_args.request_file))
display_columns, columns = _get_columns(updated_values, 'update')
data = utils.get_item_properties(
sdk_utils.DictModel(updated_values),

View File

@ -0,0 +1,216 @@
# Copyright (C) 2023 Fujitsu
# All Rights Reserved.
#
# 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.
import logging
from osc_lib.command import command
from osc_lib import utils
from tackerclient.common import exceptions
from tackerclient.i18n import _
from tackerclient.osc import sdk_utils
from tackerclient.osc import utils as tacker_osc_utils
LOG = logging.getLogger(__name__)
_ATTR_MAP = (
('id', 'ID', tacker_osc_utils.LIST_BOTH),
('objectType', 'Object Type', tacker_osc_utils.LIST_BOTH),
('_links', 'Links', tacker_osc_utils.LIST_BOTH)
)
_FORMATTERS = {
'subObjectInstanceIds': tacker_osc_utils.FormatComplexDataColumn,
'criteria': tacker_osc_utils.FormatComplexDataColumn,
'_links': tacker_osc_utils.FormatComplexDataColumn
}
_MIXED_CASE_FIELDS = (
'objectType', 'objectInstanceId', 'subObjectInstanceIds', 'callbackUri'
)
_MIXED_CASE_FIELDS_UPDATE = (
'callbackUri'
)
_VNF_PM_THRESHOLD_ID = 'vnf_pm_threshold_id'
def _get_columns(vnf_pm_threshold, action=None):
if action == 'update':
column_map = {
'callbackUri': 'Callback Uri'
}
else:
column_map = {
'id': 'ID',
'objectType': 'Object Type',
'objectInstanceId': 'Object Instance Id',
'subObjectInstanceIds': 'Sub Object Instance Ids',
'criteria': 'Criteria',
'callbackUri': 'Callback Uri',
'_links': 'Links'
}
return sdk_utils.get_osc_show_columns_for_sdk_resource(
vnf_pm_threshold, column_map)
class CreateVnfPmThreshold(command.ShowOne):
_description = _("Create a new VNF PM threshold")
def get_parser(self, prog_name):
LOG.debug('get_parser(%s)', prog_name)
parser = super(CreateVnfPmThreshold, self).get_parser(prog_name)
parser.add_argument(
'request_file',
metavar="<param-file>",
help=_('Specify create VNF PM threshold request '
'parameters in a json file.'))
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
vnf_pm_threshold = client.create_vnf_pm_threshold(
tacker_osc_utils.jsonfile2body(parsed_args.request_file))
display_columns, columns = _get_columns(vnf_pm_threshold)
data = utils.get_item_properties(
sdk_utils.DictModel(vnf_pm_threshold), columns,
formatters=_FORMATTERS, mixed_case_fields=_MIXED_CASE_FIELDS)
return (display_columns, data)
class ListVnfPmThreshold(command.Lister):
_description = _("List VNF PM thresholds")
def get_parser(self, prog_name):
LOG.debug('get_parser(%s)', prog_name)
parser = super(ListVnfPmThreshold, self).get_parser(prog_name)
parser.add_argument(
"--filter",
metavar="<filter>",
help=_("Attribute-based-filtering parameters"),
)
return parser
def take_action(self, parsed_args):
_params = {}
if parsed_args.filter:
_params['filter'] = parsed_args.filter
client = self.app.client_manager.tackerclient
data = client.list_vnf_pm_thresholds(**_params)
headers, columns = tacker_osc_utils.get_column_definitions(
_ATTR_MAP, long_listing=True)
return (headers,
(utils.get_dict_properties(
s, columns, formatters=_FORMATTERS,
mixed_case_fields=_MIXED_CASE_FIELDS,
) for s in data['vnf_pm_thresholds']))
class ShowVnfPmThreshold(command.ShowOne):
_description = _("Display VNF PM threshold details")
def get_parser(self, prog_name):
LOG.debug('get_parser(%s)', prog_name)
parser = super(ShowVnfPmThreshold, self).get_parser(prog_name)
parser.add_argument(
_VNF_PM_THRESHOLD_ID,
metavar="<vnf-pm-threshold-id>",
help=_("VNF PM threshold ID to display"))
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
obj = client.show_vnf_pm_threshold(parsed_args.vnf_pm_threshold_id)
display_columns, columns = _get_columns(obj)
data = utils.get_item_properties(
sdk_utils.DictModel(obj), columns,
mixed_case_fields=_MIXED_CASE_FIELDS,
formatters=_FORMATTERS)
return (display_columns, data)
class UpdateVnfPmThreshold(command.ShowOne):
_description = _("Update information about an individual VNF PM threshold")
def get_parser(self, prog_name):
LOG.debug('get_parser(%s)', prog_name)
parser = super(UpdateVnfPmThreshold, self).get_parser(prog_name)
parser.add_argument(
_VNF_PM_THRESHOLD_ID,
metavar="<vnf-pm-threshold-id>",
help=_("VNF PM threshold ID to update.")
)
parser.add_argument(
'request_file',
metavar="<param-file>",
help=_('Specify update PM threshold request '
'parameters in a json file.'))
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.tackerclient
updated_values = client.update_vnf_pm_threshold(
parsed_args.vnf_pm_threshold_id,
tacker_osc_utils.jsonfile2body(parsed_args.request_file))
display_columns, columns = _get_columns(updated_values, 'update')
data = utils.get_item_properties(
sdk_utils.DictModel(updated_values), columns,
mixed_case_fields=_MIXED_CASE_FIELDS_UPDATE)
return (display_columns, data)
class DeleteVnfPmThreshold(command.Command):
_description = _("Delete VNF PM threshold")
def get_parser(self, prog_name):
LOG.debug('get_parser(%s)', prog_name)
parser = super(DeleteVnfPmThreshold, self).get_parser(prog_name)
parser.add_argument(
_VNF_PM_THRESHOLD_ID,
metavar="<vnf-pm-threshold-id>",
nargs="+",
help=_("VNF PM threshold ID(s) to delete"))
return parser
def take_action(self, parsed_args):
error_count = 0
client = self.app.client_manager.tackerclient
vnf_pm_threshold_ids = parsed_args.vnf_pm_threshold_id
for threshold_id in vnf_pm_threshold_ids:
try:
client.delete_vnf_pm_threshold(threshold_id)
except Exception as e:
error_count += 1
LOG.error(_("Failed to delete VNF PM threshold with "
"ID '%(threshold_id)s': %(e)s"),
{'threshold_id': threshold_id, 'e': e})
total = len(vnf_pm_threshold_ids)
if error_count > 0:
msg = (_("Failed to delete %(error_count)s of %(total)s "
"VNF PM thresholds.") %
{'error_count': error_count, 'total': total})
raise exceptions.CommandError(message=msg)
if total > 1:
print(_('All specified VNF PM thresholds are deleted '
'successfully'))
return
print(_("VNF PM threshold '%s' deleted "
"successfully") % vnf_pm_threshold_ids[0])

View File

@ -297,9 +297,9 @@ class TestInstantiateVnfLcm(TestVnfLcm):
self.instantiate_vnf_lcm.take_action,
parsed_args)
expected_msg = ("Invalid input: File %s does not exist "
"or user does not have read privileges to it")
self.assertEqual(expected_msg % sample_param_file, str(ex))
expected_msg = ("Invalid input: "
"User does not have read privileges to it")
self.assertEqual(expected_msg, str(ex))
@mock.patch("os.open")
@mock.patch("os.access")
@ -403,9 +403,9 @@ class TestHealVnfLcm(TestVnfLcm):
ex = self.assertRaises(exceptions.InvalidInput,
self.heal_vnf_lcm.take_action, parsed_args)
expected_msg = ("Invalid input: File %s does not exist "
"or user does not have read privileges to it")
self.assertEqual(expected_msg % sample_param_file, str(ex))
expected_msg = ("Invalid input: "
"User does not have read privileges to it")
self.assertEqual(expected_msg, str(ex))
@ddt.ddt
@ -774,9 +774,9 @@ class TestScaleVnfLcm(TestVnfLcm):
ex = self.assertRaises(exceptions.InvalidInput,
self.scale_vnf_lcm.take_action, parsed_args)
expected_msg = ("Invalid input: File %s does not exist "
"or user does not have read privileges to it")
self.assertEqual(expected_msg % sample_param_file, str(ex))
expected_msg = ("Invalid input: "
"User does not have read privileges to it")
self.assertEqual(expected_msg, str(ex))
@ddt.data('SCALE_IN', 'SCALE_OUT')
def test_take_action_vnf_instance_not_found(self, scale_type):
@ -888,9 +888,9 @@ class TestChangeExtConnVnfLcm(TestVnfLcm):
self.change_ext_conn_vnf_lcm.take_action,
parsed_args)
expected_msg = ("Invalid input: File %s does not exist "
"or user does not have read privileges to it")
self.assertEqual(expected_msg % sample_param_file, str(ex))
expected_msg = ("Invalid input: "
"User does not have read privileges to it")
self.assertEqual(expected_msg, str(ex))
@mock.patch("os.open")
@mock.patch("os.access")

View File

@ -121,9 +121,9 @@ class TestChangeVnfPkgVnfLcm(test_vnflcm.TestVnfLcm):
self.change_vnfpkg_vnf_lcm.take_action,
parsed_args)
expected_msg = ("Invalid input: File %s does not exist "
"or user does not have read privileges to it")
self.assertEqual(expected_msg % sample_param_file, str(ex))
expected_msg = ("Invalid input: "
"User does not have read privileges to it")
self.assertEqual(expected_msg, str(ex))
@mock.patch("os.open")
@mock.patch("os.access")

View File

@ -0,0 +1,398 @@
# Copyright (C) 2023 Fujitsu
# All Rights Reserved.
#
# 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.
import ddt
import os
import sys
from io import StringIO
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.v2.vnfpm import vnfpm_threshold
from tackerclient.tests.unit.osc import base
from tackerclient.tests.unit.osc.v1.fixture_data import client
from tackerclient.tests.unit.osc.v2 import vnfpm_threshold_fakes
class TestVnfPmThreshold(base.FixturedTestCase):
client_fixture_class = client.ClientFixture
def setUp(self):
super(TestVnfPmThreshold, self).setUp()
self.url = client.TACKER_URL
self.header = {'content-type': 'application/json'}
self.app = mock.Mock()
self.app_args = mock.Mock()
self.client_manager = self.cs
self.app.client_manager.tackerclient = self.client_manager
def _get_columns_vnfpm_threshold(action=None):
if action == 'update':
columns = ['Callback Uri']
else:
columns = ['ID', 'Object Type', 'Object Instance Id',
'Sub Object Instance Ids', 'Criteria', 'Callback Uri',
'Links']
if action == 'list':
columns = [
'ID', 'Object Type', 'Links'
]
return columns
@ddt.ddt
class TestCreateVnfPmThreshold(TestVnfPmThreshold):
def setUp(self):
super(TestCreateVnfPmThreshold, self).setUp()
self.create_vnf_pm_threshold = vnfpm_threshold.CreateVnfPmThreshold(
self.app, self.app_args, cmd_name='vnfpm threshold create')
def test_create_no_args(self):
self.assertRaises(base.ParserException, self.check_parser,
self.create_vnf_pm_threshold, [], [])
@ddt.unpack
def test_take_action(self):
param_file = ("./tackerclient/osc/v2/vnfpm/samples/"
"create_vnf_pm_threshold_param_sample.json")
arg_list = [param_file]
verify_list = [('request_file', param_file)]
parsed_args = self.check_parser(self.create_vnf_pm_threshold, arg_list,
verify_list)
response_json = vnfpm_threshold_fakes.vnf_pm_threshold_response()
self.requests_mock.register_uri(
'POST', os.path.join(self.url, 'vnfpm/v2/thresholds'),
json=response_json, headers=self.header)
actual_columns, data = (
self.create_vnf_pm_threshold.take_action(parsed_args))
self.assertCountEqual(_get_columns_vnfpm_threshold(),
actual_columns)
_, attributes = vnfpm_threshold._get_columns(response_json)
expected_data = vnfpm_threshold_fakes.get_vnfpm_threshold_data(
response_json, columns=attributes)
self.assertListItemsEqual(expected_data, data)
@ddt.ddt
class TestListVnfPmThreshold(TestVnfPmThreshold):
def setUp(self):
super(TestListVnfPmThreshold, self).setUp()
self.list_vnf_pm_thresholds = vnfpm_threshold.ListVnfPmThreshold(
self.app, self.app_args, cmd_name='vnfpm threshold list')
def test_take_action(self):
vnf_pm_threshold_objs = vnfpm_threshold_fakes.create_vnf_pm_thresholds(
count=3)
parsed_args = self.check_parser(self.list_vnf_pm_thresholds, [], [])
self.requests_mock.register_uri(
'GET',
os.path.join(self.url, 'vnfpm/v2/thresholds'),
json=vnf_pm_threshold_objs, headers=self.header)
actual_columns, data = self.list_vnf_pm_thresholds.take_action(
parsed_args)
_, columns = tacker_osc_utils.get_column_definitions(
vnfpm_threshold._ATTR_MAP, long_listing=True)
expected_data = []
for vnf_pm_threshold_obj_idx in vnf_pm_threshold_objs:
expected_data.append(
vnfpm_threshold_fakes.get_vnfpm_threshold_data(
vnf_pm_threshold_obj_idx, columns=columns))
self.assertCountEqual(_get_columns_vnfpm_threshold(action='list'),
actual_columns)
self.assertCountEqual(expected_data, list(data))
def test_take_action_with_filter(self):
vnf_pm_threshold_objs = vnfpm_threshold_fakes.create_vnf_pm_thresholds(
count=3)
parsed_args = self.check_parser(
self.list_vnf_pm_thresholds,
["--filter", '(eq,perceivedSeverity,WARNING)'],
[('filter', '(eq,perceivedSeverity,WARNING)')])
self.requests_mock.register_uri(
'GET', os.path.join(
self.url,
'vnfpm/v2/thresholds?filter=(eq,perceivedSeverity,WARNING)'),
json=vnf_pm_threshold_objs, headers=self.header)
actual_columns, data = self.list_vnf_pm_thresholds.take_action(
parsed_args)
_, columns = tacker_osc_utils.get_column_definitions(
vnfpm_threshold._ATTR_MAP, long_listing=True)
expected_data = []
for vnf_pm_threshold_obj_idx in vnf_pm_threshold_objs:
expected_data.append(
vnfpm_threshold_fakes.get_vnfpm_threshold_data(
vnf_pm_threshold_obj_idx, columns=columns))
self.assertCountEqual(_get_columns_vnfpm_threshold(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_vnf_pm_thresholds,
["--filter", '(perceivedSeverity)'],
[('filter', '(perceivedSeverity)')])
url = os.path.join(
self.url, 'vnfpm/v2/thresholds?filter=(perceivedSeverity)')
self.requests_mock.register_uri(
'GET', url, headers=self.header, status_code=400, json={})
self.assertRaises(exceptions.TackerClientException,
self.list_vnf_pm_thresholds.take_action,
parsed_args)
def test_take_action_internal_server_error(self):
parsed_args = self.check_parser(
self.list_vnf_pm_thresholds,
["--filter", '(eq,perceivedSeverity,WARNING)'],
[('filter', '(eq,perceivedSeverity,WARNING)')])
url = os.path.join(
self.url,
'vnfpm/v2/thresholds?filter=(eq,perceivedSeverity,WARNING)')
self.requests_mock.register_uri(
'GET', url, headers=self.header, status_code=500, json={})
self.assertRaises(exceptions.TackerClientException,
self.list_vnf_pm_thresholds.take_action,
parsed_args)
class TestShowVnfPmThreshold(TestVnfPmThreshold):
def setUp(self):
super(TestShowVnfPmThreshold, self).setUp()
self.show_vnf_pm_thresholds = vnfpm_threshold.ShowVnfPmThreshold(
self.app, self.app_args, cmd_name='vnfpm threshold show')
def test_take_action(self):
vnfpm_threshold_obj = vnfpm_threshold_fakes.vnf_pm_threshold_response()
arg_list = [vnfpm_threshold_obj['id']]
verify_list = [('vnf_pm_threshold_id', vnfpm_threshold_obj['id'])]
# command param
parsed_args = self.check_parser(
self.show_vnf_pm_thresholds, arg_list, verify_list)
url = os.path.join(
self.url, 'vnfpm/v2/thresholds', vnfpm_threshold_obj['id'])
self.requests_mock.register_uri(
'GET', url, headers=self.header, json=vnfpm_threshold_obj)
columns, data = (self.show_vnf_pm_thresholds.take_action(parsed_args))
self.assertCountEqual(_get_columns_vnfpm_threshold('show'), columns)
_, attributes = vnfpm_threshold._get_columns(vnfpm_threshold_obj)
self.assertListItemsEqual(
vnfpm_threshold_fakes.get_vnfpm_threshold_data(
vnfpm_threshold_obj, columns=attributes), data)
def test_take_action_vnf_pm_threshold_id_not_found(self):
arg_list = [uuidsentinel.vnf_pm_threshold_id]
verify_list = [('vnf_pm_threshold_id',
uuidsentinel.vnf_pm_threshold_id)]
# command param
parsed_args = self.check_parser(
self.show_vnf_pm_thresholds, arg_list, verify_list)
url = os.path.join(
self.url, 'vnfpm/v2/thresholds',
uuidsentinel.vnf_pm_threshold_id)
self.requests_mock.register_uri(
'GET', url, headers=self.header, status_code=404, json={})
self.assertRaises(exceptions.TackerClientException,
self.show_vnf_pm_thresholds.take_action,
parsed_args)
def test_take_action_internal_server_error(self):
arg_list = [uuidsentinel.vnf_pm_threshold_id]
verify_list = [('vnf_pm_threshold_id',
uuidsentinel.vnf_pm_threshold_id)]
# command param
parsed_args = self.check_parser(
self.show_vnf_pm_thresholds, arg_list, verify_list)
url = os.path.join(
self.url, 'vnfpm/v2/thresholds',
uuidsentinel.vnf_pm_threshold_id)
self.requests_mock.register_uri(
'GET', url, headers=self.header, status_code=500, json={})
self.assertRaises(exceptions.TackerClientException,
self.show_vnf_pm_thresholds.take_action,
parsed_args)
@ddt.ddt
class TestUpdateVnfPmThreshold(TestVnfPmThreshold):
def setUp(self):
super(TestUpdateVnfPmThreshold, self).setUp()
self.update_vnf_pm_threshold = vnfpm_threshold.UpdateVnfPmThreshold(
self.app, self.app_args, cmd_name='vnfpm threshold update')
def test_take_action(self):
param_file = ("./tackerclient/osc/v2/vnfpm/samples/"
"update_vnf_pm_threshold_param_sample.json")
arg_list = [uuidsentinel.vnf_pm_threshold_id, param_file]
verify_list = [
('vnf_pm_threshold_id', uuidsentinel.vnf_pm_threshold_id),
('request_file', param_file)
]
vnfpm_threshold_obj = vnfpm_threshold_fakes.vnf_pm_threshold_response(
None, 'update')
# command param
parsed_args = self.check_parser(
self.update_vnf_pm_threshold, arg_list, verify_list)
url = os.path.join(
self.url, 'vnfpm/v2/thresholds',
uuidsentinel.vnf_pm_threshold_id)
self.requests_mock.register_uri(
'PATCH', url, headers=self.header, json=vnfpm_threshold_obj)
actual_columns, data = (
self.update_vnf_pm_threshold.take_action(parsed_args))
expected_columns = _get_columns_vnfpm_threshold(action='update')
self.assertCountEqual(expected_columns, actual_columns)
_, columns = vnfpm_threshold._get_columns(
vnfpm_threshold_obj, action='update')
expected_data = vnfpm_threshold_fakes.get_vnfpm_threshold_data(
vnfpm_threshold_obj, columns=columns)
self.assertEqual(expected_data, data)
def test_take_action_vnf_pm_threshold_id_not_found(self):
param_file = ("./tackerclient/osc/v2/vnfpm/samples/"
"update_vnf_pm_threshold_param_sample.json")
arg_list = [uuidsentinel.vnf_pm_threshold_id, param_file]
verify_list = [
('vnf_pm_threshold_id', uuidsentinel.vnf_pm_threshold_id),
('request_file', param_file)
]
# command param
parsed_args = self.check_parser(
self.update_vnf_pm_threshold, arg_list, verify_list)
url = os.path.join(
self.url, 'vnfpm/v2/thresholds',
uuidsentinel.vnf_pm_threshold_id)
self.requests_mock.register_uri(
'PATCH', url, headers=self.header, status_code=404, json={})
self.assertRaises(exceptions.TackerClientException,
self.update_vnf_pm_threshold.take_action,
parsed_args)
class TestDeleteVnfPmThreshold(TestVnfPmThreshold):
def setUp(self):
super(TestDeleteVnfPmThreshold, self).setUp()
self.delete_vnf_pm_threshold = vnfpm_threshold.DeleteVnfPmThreshold(
self.app, self.app_args, cmd_name='vnfpm threshold delete')
# Vnf Pm threshold to delete
self.vnf_pm_thresholds = (
vnfpm_threshold_fakes.create_vnf_pm_thresholds(count=3))
def _mock_request_url_for_delete(self, index):
url = os.path.join(self.url, 'vnfpm/v2/thresholds',
self.vnf_pm_thresholds[index]['id'])
self.requests_mock.register_uri('DELETE', url,
headers=self.header, json={})
def test_delete_one_vnf_pm_threshold(self):
arg_list = [self.vnf_pm_thresholds[0]['id']]
verify_list = [('vnf_pm_threshold_id',
[self.vnf_pm_thresholds[0]['id']])]
parsed_args = self.check_parser(self.delete_vnf_pm_threshold, arg_list,
verify_list)
self._mock_request_url_for_delete(0)
sys.stdout = buffer = StringIO()
result = self.delete_vnf_pm_threshold.take_action(parsed_args)
self.assertIsNone(result)
self.assertEqual(
(f"VNF PM threshold '{self.vnf_pm_thresholds[0]['id']}' "
f"deleted successfully"), buffer.getvalue().strip())
def test_delete_multiple_vnf_pm_threshold(self):
arg_list = []
for obj in self.vnf_pm_thresholds:
arg_list.append(obj['id'])
verify_list = [('vnf_pm_threshold_id', arg_list)]
parsed_args = self.check_parser(self.delete_vnf_pm_threshold, arg_list,
verify_list)
for i in range(0, len(self.vnf_pm_thresholds)):
self._mock_request_url_for_delete(i)
sys.stdout = buffer = StringIO()
result = self.delete_vnf_pm_threshold.take_action(parsed_args)
self.assertIsNone(result)
self.assertEqual('All specified VNF PM thresholds are deleted '
'successfully', buffer.getvalue().strip())
def test_delete_multiple_vnf_pm_threshold_exception(self):
arg_list = [
self.vnf_pm_thresholds[0]['id'],
'xxxx-yyyy-zzzz',
self.vnf_pm_thresholds[1]['id'],
]
verify_list = [('vnf_pm_threshold_id', arg_list)]
parsed_args = self.check_parser(self.delete_vnf_pm_threshold,
arg_list, verify_list)
self._mock_request_url_for_delete(0)
url = os.path.join(self.url, 'vnfpm/v2/thresholds',
'xxxx-yyyy-zzzz')
self.requests_mock.register_uri(
'GET', url, exc=exceptions.ConnectionFailed)
self._mock_request_url_for_delete(1)
exception = self.assertRaises(exceptions.CommandError,
self.delete_vnf_pm_threshold.take_action,
parsed_args)
self.assertEqual(
f'Failed to delete 1 of {len(self.vnf_pm_thresholds)} '
'VNF PM thresholds.', exception.message)

View File

@ -0,0 +1,111 @@
# Copyright (C) 2023 Fujitsu
# All Rights Reserved.
#
# 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 import uuidutils
from tackerclient.osc import utils as tacker_osc_utils
def create_vnf_pm_thresholds(count=2):
"""Create multiple fake vnf pm thresholds.
:param int count:
The number of vnf_pm_thresholds to fake
:return:
A list of fake vnf pm thresholds dictionary
"""
vnf_pm_thresholds = []
for _ in range(0, count):
unique_id = uuidutils.generate_uuid()
vnf_pm_thresholds.append(vnf_pm_threshold_response(
attrs={'id': unique_id}))
return vnf_pm_thresholds
def vnf_pm_threshold_response(attrs=None, action=None):
"""Create a fake vnf pm threshold.
:param Dictionary attrs:
A dictionary with all attributes
:param String action:
The operation performed on threshold
:return:
A pm threshold dict
"""
if action == 'update':
fake_vnf_pm_threshold = {
"callbackUri": "/nfvo/notify/threshold",
}
return fake_vnf_pm_threshold
attrs = attrs or {}
# Set default attributes.
fake_vnf_pm_threshold = {
"id": "2bb72d78-b1d9-48fe-8c64-332654ffeb5d",
"objectType": "Vnfc",
"objectInstanceId": "object-instance-id-1",
"subObjectInstanceIds": [
"sub-object-instance-id-2"
],
"criteria": {
"performanceMetric": "VCpuUsageMeanVnf.object-instance-id-1",
"thresholdType": "SIMPLE",
"simpleThresholdDetails": {
"thresholdValue": 500.5,
"hysteresis": 10.5
}
},
"callbackUri": "/nfvo/notify/threshold",
"_links": {
"self": {
"href": "/vnfpm/v2/thresholds/"
"78a39661-60a8-4824-b989-88c1b0c3534a"
},
"object": {
"href": "/vnflcm/v1/vnf_instances/"
"0e5f3086-4e79-47ed-a694-54c29155fa26"
}
}
}
# Overwrite default attributes.
fake_vnf_pm_threshold.update(attrs)
return fake_vnf_pm_threshold
def get_vnfpm_threshold_data(vnf_pm_threshold, columns=None):
"""Get the vnfpm threshold.
:param Dictionary vnf_pm_threshold:
A dictionary with vnf_pm_threshold
:param List columns:
A list of column names
:return:
A tuple object sorted based on the name of the columns.
"""
complex_attributes = ['subObjectInstanceIds',
'criteria', '_links', 'authentication']
for attribute in complex_attributes:
if vnf_pm_threshold.get(attribute):
vnf_pm_threshold.update(
{attribute: tacker_osc_utils.FormatComplexDataColumn(
vnf_pm_threshold[attribute])})
# return the list of data as per column order
if columns is None:
columns = sorted(vnf_pm_threshold.keys())
return tuple([vnf_pm_threshold[key] for key in columns])

View File

@ -1104,6 +1104,8 @@ class VnfPMClient(ClientBase):
vnf_pm_jobs_path = '/vnfpm/v2/pm_jobs'
vnf_pm_job_path = '/vnfpm/v2/pm_jobs/%s'
vnf_pm_reports_path = '/vnfpm/v2/pm_jobs/%(job_id)s/reports/%(report_id)s'
vnf_pm_thresholds_path = '/vnfpm/v2/thresholds'
vnf_pm_threshold_path = '/vnfpm/v2/thresholds/%s'
def build_action(self, action):
return action
@ -1143,6 +1145,35 @@ class VnfPMClient(ClientBase):
'job_id': vnf_pm_job_id, 'report_id': vnf_pm_report_id
}, headers=self.headers)
@APIParamsCall
def create_vnf_pm_threshold(self, body):
return self.post(
self.vnf_pm_thresholds_path, body=body, headers=self.headers)
@APIParamsCall
def list_vnf_pm_thresholds(self, retrieve_all=True, **_params):
return self.list(
"vnf_pm_thresholds", self.vnf_pm_thresholds_path, retrieve_all,
headers=self.headers, **_params)
@APIParamsCall
def show_vnf_pm_threshold(self, vnf_pm_threshold_id):
return self.get(
self.vnf_pm_threshold_path % vnf_pm_threshold_id,
headers=self.headers)
@APIParamsCall
def update_vnf_pm_threshold(self, vnf_pm_threshold_id, body):
return self.patch(
self.vnf_pm_threshold_path % vnf_pm_threshold_id, body=body,
headers=self.headers)
@APIParamsCall
def delete_vnf_pm_threshold(self, vnf_pm_threshold_id):
return self.delete(
self.vnf_pm_threshold_path % vnf_pm_threshold_id,
headers=self.headers)
class Client(object):
"""Unified interface to interact with multiple applications of tacker service.
@ -1527,3 +1558,20 @@ class Client(object):
def show_vnf_pm_report(self, vnf_pm_job_id, vnf_pm_report_id):
return self.vnf_pm_client.show_vnf_pm_report(
vnf_pm_job_id, vnf_pm_report_id)
def create_vnf_pm_threshold(self, body):
return self.vnf_pm_client.create_vnf_pm_threshold(body)
def list_vnf_pm_thresholds(self, retrieve_all=True, **_params):
return self.vnf_pm_client.list_vnf_pm_thresholds(
retrieve_all=retrieve_all, **_params)
def show_vnf_pm_threshold(self, vnf_pm_threshold_id):
return self.vnf_pm_client.show_vnf_pm_threshold(vnf_pm_threshold_id)
def update_vnf_pm_threshold(self, vnf_pm_threshold_id, body):
return self.vnf_pm_client.update_vnf_pm_threshold(
vnf_pm_threshold_id, body)
def delete_vnf_pm_threshold(self, vnf_pm_threshold_id):
return self.vnf_pm_client.delete_vnf_pm_threshold(vnf_pm_threshold_id)