Support CNF auto scale via PM Threshold interface

Add the Performance Management Threshold API and 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, on which v2
VNF LCM API of Tacker is based. Add the Prometheus Plugin that
has an interface between Tacker and Prometheus as a sample of
External Monitoring Tool.

Implements: blueprint support-auto-lcm
Change-Id: If41e89c5601592aea18a81df54d0db44a8588e1c
This commit is contained in:
Yi Feng 2023-02-06 22:12:34 +09:00
parent c65931ceab
commit f75827612f
54 changed files with 4173 additions and 1018 deletions

View File

@ -12,6 +12,7 @@ use = egg:Paste#urlmap
/alert/auto_healing: prometheus_auto_healing
/alert: prometheus_fm
/pm_event: prometheus_pm
/pm_threshold: prometheus_threshold
/server_notification: server_notification
[composite:tackerapi_v1_0]
@ -103,5 +104,8 @@ paste.app_factory = tacker.sol_refactored.api.prometheus_plugin_router:FmAlertRo
[app:prometheus_pm]
paste.app_factory = tacker.sol_refactored.api.prometheus_plugin_router:PmEventRouter.factory
[app:prometheus_threshold]
paste.app_factory = tacker.sol_refactored.api.prometheus_plugin_router:PmThresholdRouter.factory
[app:server_notification]
paste.app_factory = tacker.sol_refactored.api.server_notification_router:ServerNotificationRouter.factory

View File

@ -0,0 +1,11 @@
---
features:
- |
Add the Performance Management Threshold interfaces and CLI to support
AutoScale.
The Performance Management Threshold interfaces are based on ETSI NFV-SOL
003 v3.3.1, on which v2 VNF LCM API of Tacker is based.
Add the Prometheus Plugin that has an interface between Tacker and the
External Monitoring Tool.
Prometheus Plugin supports data model conversion from Prometheus format
data to SOL based PM Threshold schema, and vice versa.

View File

@ -0,0 +1,46 @@
# Copyright 2023 OpenStack Foundation
#
# 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.
#
"""add_pm_threshold_table
Revision ID: 2531c976c0f1
Revises: de6bfa5bea46
Create Date: 2023-02-01 07:10:06.910825
"""
# flake8: noqa: E402
# revision identifiers, used by Alembic.
revision = '2531c976c0f1'
down_revision = 'de6bfa5bea46'
from alembic import op
import sqlalchemy as sa
def upgrade(active_plugins=None, options=None):
op.create_table('ThresholdV2',
sa.Column('id', sa.String(length=255), nullable=False),
sa.Column('objectType', sa.String(length=32), nullable=False),
sa.Column('objectInstanceId', sa.String(length=255), nullable=False),
sa.Column('subObjectInstanceIds', sa.JSON(), nullable=True),
sa.Column('criteria', sa.JSON(), nullable=False),
sa.Column('callbackUri', sa.String(length=255), nullable=False),
sa.Column('authentication', sa.JSON(), nullable=True),
sa.Column('metadata', sa.JSON(), nullable=True),
sa.PrimaryKeyConstraint('id'),
mysql_engine='InnoDB'
)

View File

@ -1 +1 @@
de6bfa5bea46
2531c976c0f1

View File

@ -1,133 +1,205 @@
# Copyright (C) 2022 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_policy import policy
POLICY_NAME = 'os_nfv_orchestration_api_v2:vnf_performance_management:{}'
RULE_ANY = '@'
V2_PATH = '/vnfpm/v2'
PM_JOB_PATH = V2_PATH + '/pm_jobs'
PM_JOB_ID_PATH = PM_JOB_PATH + '/{pmJobId}'
REPORT_GET = '/vnfpm/v2/pm_jobs/{id}/reports/{report_id}'
POLICY_NAME_PROM_PLUGIN = 'tacker_PROM_PLUGIN_api:PROM_PLUGIN:{}'
PROM_PLUGIN_PM_PATH = '/pm_event'
PROM_PLUGIN_AUTO_HEALING_PATH = '/alert/auto_healing'
PROM_PLUGIN_AUTO_SCALING_PATH = '/alert/auto_scaling'
rules = [
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('create'),
check_str=RULE_ANY,
description="Create a PM job.",
operations=[
{
'method': 'POST',
'path': PM_JOB_PATH
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('index'),
check_str=RULE_ANY,
description="Query PM jobs.",
operations=[
{
'method': 'GET',
'path': PM_JOB_PATH
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('update'),
check_str=RULE_ANY,
description="Update a PM job.",
operations=[
{
'method': 'PATCH',
'path': PM_JOB_ID_PATH
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('show'),
check_str=RULE_ANY,
description="Get an individual PM job.",
operations=[
{
'method': 'GET',
'path': PM_JOB_ID_PATH
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('delete'),
check_str=RULE_ANY,
description="Delete a PM job.",
operations=[
{
'method': 'DELETE',
'path': PM_JOB_ID_PATH
}
]
),
# Add new Rest API GET /vnfpm/v2/pm_jobs/{id}/reports/{report_id} to
# get the specified PM report.
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('report_get'),
check_str=RULE_ANY,
description="Get an individual performance report.",
operations=[
{
'method': 'GET',
'path': REPORT_GET
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME_PROM_PLUGIN.format('pm_event'),
check_str=RULE_ANY,
description="Receive the PM event sent from External Monitoring Tool",
operations=[
{'method': 'POST',
'path': PROM_PLUGIN_PM_PATH}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME_PROM_PLUGIN.format('auto_healing'),
check_str=RULE_ANY,
description="auto_healing",
operations=[
{'method': 'POST',
'path': PROM_PLUGIN_AUTO_HEALING_PATH}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME_PROM_PLUGIN.format('auto_scaling'),
check_str=RULE_ANY,
description="auto_scaling",
operations=[
{'method': 'POST',
'path': PROM_PLUGIN_AUTO_SCALING_PATH}
]
)
]
def list_rules():
return rules
# Copyright (C) 2022 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_policy import policy
POLICY_NAME = 'os_nfv_orchestration_api_v2:vnf_performance_management:{}'
RULE_ANY = '@'
V2_PATH = '/vnfpm/v2'
PM_JOB_PATH = V2_PATH + '/pm_jobs'
PM_THRESHOLD_PATH = V2_PATH + '/thresholds'
PM_JOB_ID_PATH = PM_JOB_PATH + '/{pmJobId}'
PM_THRESHOLD_ID_PATH = PM_THRESHOLD_PATH + '/{thresholdId}'
REPORT_GET = '/vnfpm/v2/pm_jobs/{id}/reports/{report_id}'
POLICY_NAME_PROM_PLUGIN = 'tacker_PROM_PLUGIN_api:PROM_PLUGIN:{}'
PROM_PLUGIN_PM_PATH = '/pm_event'
PROM_PLUGIN_PM_THRESHOLD_PATH = '/pm_threshold'
PROM_PLUGIN_AUTO_HEALING_PATH = '/alert/auto_healing'
PROM_PLUGIN_AUTO_SCALING_PATH = '/alert/auto_scaling'
rules = [
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('create'),
check_str=RULE_ANY,
description="Create a PM job.",
operations=[
{
'method': 'POST',
'path': PM_JOB_PATH
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('index'),
check_str=RULE_ANY,
description="Query PM jobs.",
operations=[
{
'method': 'GET',
'path': PM_JOB_PATH
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('update'),
check_str=RULE_ANY,
description="Update a PM job.",
operations=[
{
'method': 'PATCH',
'path': PM_JOB_ID_PATH
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('show'),
check_str=RULE_ANY,
description="Get an individual PM job.",
operations=[
{
'method': 'GET',
'path': PM_JOB_ID_PATH
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('delete'),
check_str=RULE_ANY,
description="Delete a PM job.",
operations=[
{
'method': 'DELETE',
'path': PM_JOB_ID_PATH
}
]
),
# Add new Rest API GET /vnfpm/v2/pm_jobs/{id}/reports/{report_id} to
# get the specified PM report.
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('report_get'),
check_str=RULE_ANY,
description="Get an individual performance report.",
operations=[
{
'method': 'GET',
'path': REPORT_GET
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME_PROM_PLUGIN.format('pm_event'),
check_str=RULE_ANY,
description="Receive the PM event sent from External Monitoring Tool",
operations=[
{'method': 'POST',
'path': PROM_PLUGIN_PM_PATH}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME_PROM_PLUGIN.format('auto_healing'),
check_str=RULE_ANY,
description="auto_healing",
operations=[
{'method': 'POST',
'path': PROM_PLUGIN_AUTO_HEALING_PATH}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME_PROM_PLUGIN.format('auto_scaling'),
check_str=RULE_ANY,
description="auto_scaling",
operations=[
{'method': 'POST',
'path': PROM_PLUGIN_AUTO_SCALING_PATH}
]
)
]
threshold_rules = [
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('create_threshold'),
check_str=RULE_ANY,
description="Create a PM threshold.",
operations=[
{
'method': 'POST',
'path': PM_THRESHOLD_PATH
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('index_threshold'),
check_str=RULE_ANY,
description="Query PM thresholds.",
operations=[
{
'method': 'GET',
'path': PM_THRESHOLD_PATH
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('show_threshold'),
check_str=RULE_ANY,
description="Get an individual PM threshold.",
operations=[
{
'method': 'GET',
'path': PM_THRESHOLD_ID_PATH
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('update_threshold'),
check_str=RULE_ANY,
description="Update a PM threshold callback.",
operations=[
{
'method': 'PATCH',
'path': PM_THRESHOLD_ID_PATH
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME.format('delete_threshold'),
check_str=RULE_ANY,
description="Delete a PM threshold.",
operations=[
{
'method': 'DELETE',
'path': PM_THRESHOLD_ID_PATH
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_NAME_PROM_PLUGIN.format('pm_threshold'),
check_str=RULE_ANY,
description="Receive the PM threshold sent from "
"External Monitoring Tool.",
operations=[
{
'method': 'POST',
'path': PROM_PLUGIN_PM_THRESHOLD_PATH
}
]
),
]
def list_rules():
return rules + threshold_rules

View File

@ -26,6 +26,13 @@ class PmEventRouter(prom_wsgi.PrometheusPluginAPIRouter):
route_list = [("", {"POST": "pm_event"})]
class PmThresholdRouter(prom_wsgi.PrometheusPluginAPIRouter):
controller = prom_wsgi.PrometheusPluginResource(
prometheus_plugin_controller.PmThresholdController(),
policy_name=vnfpm_policy_v2.POLICY_NAME_PROM_PLUGIN)
route_list = [("", {"POST": "pm_threshold"})]
class FmAlertRouter(prom_wsgi.PrometheusPluginAPIRouter):
controller = prom_wsgi.PrometheusPluginResource(
prometheus_plugin_controller.FmAlertController(),

View File

@ -84,4 +84,9 @@ class VnfPmAPIRouterV2(sol_wsgi.SolAPIRouter):
("/pm_jobs/{id}", {
"PATCH": "update", "GET": "show", "DELETE": "delete"}),
("/pm_jobs/{id}/reports/{report_id}", {"GET": "report_get"}),
("/thresholds", {"POST": "create_threshold",
"GET": "index_threshold"}),
("/thresholds/{thresholdId}", {"PATCH": "update_threshold",
"GET": "show_threshold",
"DELETE": "delete_threshold"}),
]

View File

@ -34,9 +34,12 @@ Alert = {
},
'function_type': {
'type': 'string',
'enum': ['vnffm', 'vnfpm', 'auto_scale', 'auto_heal']
'enum': ['vnffm', 'vnfpm', 'vnfpm_threshold',
'auto_scale', 'auto_heal']
},
'job_id': {'type': 'string'},
'threshold_id': {'type': 'string'},
'metric': {'type': 'string'},
'object_instance_id': {'type': 'string'},
'vnf_instance_id': {'type': 'string'},
'vnfc_info_id': {'type': 'string'},

View File

@ -84,3 +84,71 @@ PmJobModificationsRequest_V210 = {
'required': [],
'additionalProperties': True,
}
# SOL003 6.5.3.4
ThresholdCriteria_V210 = {
'type': 'object',
'properties': {
'performanceMetric': {'type': 'string'},
'thresholdType': {
'type': 'string',
'enum': ['SIMPLE']
},
'simpleThresholdDetails': {
'type': 'object',
'properties': {
'thresholdValue': {'type': 'number'},
'hysteresis': {'type': 'number', 'minimum': 0.0},
},
'required': ['thresholdValue', 'hysteresis'],
'additionalProperties': True
},
},
'required': ['performanceMetric', 'thresholdType'],
'additionalProperties': True,
}
# SOL003 6.5.2.8
CreateThresholdRequest_V210 = {
'type': 'object',
'properties': {
'objectType': {
'type': 'string',
'enum': [
# TODO(YiFeng): Currently, this API only supports CNF, and
# supports the following types. When VNF is supported,
# the types can be extended.
'Vnf',
'Vnfc',
'VnfIntCp',
'VnfExtCp']
},
'objectInstanceId': {
'type': 'string',
},
'subObjectInstanceIds': {
'type': 'array',
'items': common_types.IdentifierInVnf
},
'criteria': ThresholdCriteria_V210,
'callbackUri': {'type': 'string'},
'authentication': common_types.SubscriptionAuthentication,
},
'required': ['objectType', 'objectInstanceId', 'criteria', 'callbackUri'],
'additionalProperties': True,
}
# SOL003 6.5.2.11
ThresholdModifications_V210 = {
'type': 'object',
'properties': {
'callbackUri': {'type': 'string'},
'authentication': common_types.SubscriptionAuthentication
},
'anyOf': [
{'required': ['callbackUri']},
{'required': ['authentication']}
],
'required': [],
'additionalProperties': True,
}

View File

@ -13,7 +13,31 @@
# License for the specific language governing permissions and limitations
# under the License.
import threading
from oslo_log import log as logging
from tacker.sol_refactored.api import api_version
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import http_client
from tacker.sol_refactored.common import subscription_utils
from tacker.sol_refactored.common import vnfd_utils
from tacker.sol_refactored import objects
LOG = logging.getLogger(__name__)
CONF = config.CONF
TEST_NOTIFICATION_TIMEOUT = 20 # seconds
NOTIFY_TYPE_PM = 'PM'
NOTIFY_TYPE_FM = 'FM'
AUTH_TYPE_OAUTH2_CLIENT_CERT = 'OAUTH2_CLIENT_CERT'
AUTH_TYPE_OAUTH2_CLIENT_CREDENTIALS = 'OAUTH2_CLIENT_CREDENTIALS'
AUTH_TYPE_BASIC = 'BASIC'
def get_vnfd(vnfd_id, csar_dir):
@ -273,3 +297,107 @@ def apply_ext_managed_vls_from_inst(hot_dict, inst):
for mgd_vl in mgd_vls
]
_apply_ext_managed_vls(hot_dict, mgd_vls)
def get_notification_auth_handle(obj_data):
auth_req = obj_data.get('authentication', None)
if auth_req:
auth = objects.SubscriptionAuthentication(
authType=auth_req['authType']
)
if AUTH_TYPE_OAUTH2_CLIENT_CERT in auth.authType:
param = obj_data.authentication.paramsOauth2ClientCert
ca_cert = CONF.v2_vnfm.notification_mtls_ca_cert_file
client_cert = CONF.v2_vnfm.notification_mtls_client_cert_file
return http_client.OAuth2MtlsAuthHandle(
None, param.tokenEndpoint,
param.clientId, ca_cert, client_cert)
elif AUTH_TYPE_OAUTH2_CLIENT_CREDENTIALS in auth.authType:
param = obj_data.authentication.paramsOauth2ClientCredentials
verify = CONF.v2_vnfm.notification_verify_cert
if verify and CONF.v2_vnfm.notification_ca_cert_file:
verify = CONF.v2_vnfm.notification_ca_cert_file
return http_client.OAuth2AuthHandle(
None, param.tokenEndpoint, param.clientId,
param.clientPassword,
verify=verify)
elif AUTH_TYPE_BASIC in auth.authType:
param = obj_data.authentication.paramsBasic
verify = CONF.v2_vnfm.notification_verify_cert
if verify and CONF.v2_vnfm.notification_ca_cert_file:
verify = CONF.v2_vnfm.notification_ca_cert_file
return http_client.BasicAuthHandle(
param.userName, param.password,
verify=verify)
else:
raise sol_ex.AuthTypeNotFound(auth.authType)
else:
verify = CONF.v2_vnfm.notification_verify_cert
if verify and CONF.v2_vnfm.notification_ca_cert_file:
verify = CONF.v2_vnfm.notification_ca_cert_file
return http_client.NoAuthHandle(verify=verify)
def async_call(func):
def inner(*args, **kwargs):
th = threading.Thread(target=func, args=args,
kwargs=kwargs, daemon=True)
th.start()
return inner
@async_call
def send_notification(obj_data, notif_data, notify_type=None):
version = api_version.CURRENT_VERSION
auth_handle = subscription_utils.get_notification_auth_handle(obj_data)
connect_retries = (CONF.v2_vnfm.notify_connect_retries
if CONF.v2_vnfm.notify_connect_retries else None)
client = http_client.HttpClient(auth_handle,
version=version,
connect_retries=connect_retries)
if notify_type == NOTIFY_TYPE_PM:
version = api_version.CURRENT_PM_VERSION
auth_handle = get_notification_auth_handle(obj_data)
client = http_client.HttpClient(auth_handle,
version=version)
if notify_type == NOTIFY_TYPE_FM:
version = api_version.CURRENT_FM_VERSION
auth_handle = get_notification_auth_handle(obj_data)
client = http_client.HttpClient(auth_handle,
version=version)
url = obj_data.callbackUri
try:
resp, _ = client.do_request(
url, "POST", expected_status=[204], body=notif_data)
except sol_ex.SolException as ex:
# it may occur if test_notification was not executed.
LOG.exception(f"send_notification failed: {ex}")
if resp.status_code != 204:
LOG.error(f"send_notification failed: {resp.__dict__}")
def test_notification(obj_data, notify_type=None):
version = api_version.CURRENT_VERSION
auth_handle = subscription_utils.get_notification_auth_handle(obj_data)
if notify_type == NOTIFY_TYPE_PM:
version = api_version.CURRENT_PM_VERSION
auth_handle = get_notification_auth_handle(obj_data)
if notify_type == NOTIFY_TYPE_FM:
version = api_version.CURRENT_FM_VERSION
auth_handle = get_notification_auth_handle(obj_data)
client = http_client.HttpClient(auth_handle,
version=version,
timeout=TEST_NOTIFICATION_TIMEOUT)
url = obj_data.callbackUri
try:
resp, _ = client.do_request(url, "GET", expected_status=[204])
except sol_ex.SolException as e:
# any notify_type of error is considered. avoid 500 error.
raise sol_ex.TestNotificationFailed() from e
if resp.status_code != 204:
raise sol_ex.TestNotificationFailed()

View File

@ -68,6 +68,10 @@ VNFM_OPTS = [
default=0, # 0 means no paging
help=_('Paged response size of the query result '
'for VNF Fault Management alarm.')),
cfg.IntOpt('vnfpm_pmthreshold_page_size',
default=0, # 0 means no paging
help=_('Paged response size of the query result for '
'VNF PM threshold.')),
cfg.IntOpt('vnfpm_pmjob_page_size',
default=0, # 0 means no paging
help=_('Paged response size of the query result for '
@ -206,13 +210,25 @@ PROMETHEUS_PLUGIN_OPTS = [
help=_('Enable prometheus plugin autoscaling')),
cfg.StrOpt('performance_management_package',
default='tacker.sol_refactored.common.prometheus_plugin',
help=_('Package name for performance management. '
help=_('Package name for performance management PMJob. '
'This configuration is changed in case of replacing '
'the original function with a vendor specific '
'function.')),
cfg.StrOpt('performance_management_threshold_package',
default='tacker.sol_refactored.common.prometheus_plugin',
help=_('Package name for performance management threshold. '
'This configuration is changed in case of replacing '
'the original function with a vendor specific '
'function.')),
cfg.StrOpt('performance_management_class',
default='PrometheusPluginPm',
help=_('Class name for performance management. '
help=_('Class name for performance management PMJob. '
'This configuration is changed in case of replacing '
'the original function with a vendor specific '
'function.')),
cfg.StrOpt('performance_management_threshold_class',
default='PrometheusPluginThreshold',
help=_('Class name for performance management threshold. '
'This configuration is changed in case of replacing '
'the original function with a vendor specific '
'function.')),
@ -259,6 +275,20 @@ PROMETHEUS_PLUGIN_OPTS = [
cfg.BoolOpt('test_rule_with_promtool',
default=False,
help=_('Enable rule file validation using promtool.')),
cfg.IntOpt('reporting_period_threshold',
default=90,
help=_('The time of reportingPeriod for the PM Threshold. '
'If there is a PromQL '
'that requires `reporting_period`, '
'it is read from the configuration file. '
'The unit shall be seconds.')),
cfg.IntOpt('collection_period_threshold',
default=30,
help=_('The time of collectionPeriod for the PM threshold. '
'If there is a PromQL '
'that requires `collection_period`, '
'it is read from the configuration file. '
'The unit shall be seconds.')),
]
CONF.register_opts(PROMETHEUS_PLUGIN_OPTS, 'prometheus_plugin')

View File

@ -408,6 +408,10 @@ class PMJobNotExist(SolHttpError404):
message = _("The specified PM job does not exist.")
class PMThresholdNotExist(SolHttpError404):
message = _("The specified PM Threshold %(threshold_id)s does not exist.")
class PMReportNotExist(SolHttpError404):
message = _("The specified Performance Report does not exist.")
@ -416,6 +420,10 @@ class PMJobInvalidRequest(SolHttpError400):
message = _("Invalid request")
class PMThresholdInvalidRequest(SolHttpError400):
message = _("Invalid request")
class ResourcesOtherOperationInProgress(SolHttpError409):
message = _("Other LCM operation of resources %(inst_id)s "
"is in progress.")

View File

@ -14,14 +14,10 @@
# under the License.
import threading
from oslo_log import log as logging
from tacker.sol_refactored.api import api_version
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import http_client
from tacker.sol_refactored.common import subscription_utils as subsc_utils
from tacker.sol_refactored import objects
@ -30,8 +26,6 @@ LOG = logging.getLogger(__name__)
CONF = config.CONF
TEST_NOTIFICATION_TIMEOUT = 20 # seconds
def get_subsc(context, subsc_id):
subsc = objects.FmSubscriptionV1.get_by_id(context, subsc_id)
@ -48,84 +42,6 @@ def subsc_href(subsc_id, endpoint):
return f"{endpoint}/vnffm/v1/subscriptions/{subsc_id}"
def _get_notification_auth_handle(subsc):
if not subsc.obj_attr_is_set('authentication'):
verify = CONF.v2_vnfm.notification_verify_cert
if verify and CONF.v2_vnfm.notification_ca_cert_file:
verify = CONF.v2_vnfm.notification_ca_cert_file
return http_client.NoAuthHandle(verify=verify)
if subsc.authentication.obj_attr_is_set('paramsBasic'):
param = subsc.authentication.paramsBasic
verify = CONF.v2_vnfm.notification_verify_cert
if verify and CONF.v2_vnfm.notification_ca_cert_file:
verify = CONF.v2_vnfm.notification_ca_cert_file
return http_client.BasicAuthHandle(
param.userName, param.password, verify=verify)
if subsc.authentication.obj_attr_is_set(
'paramsOauth2ClientCredentials'):
param = subsc.authentication.paramsOauth2ClientCredentials
verify = CONF.v2_vnfm.notification_verify_cert
if verify and CONF.v2_vnfm.notification_ca_cert_file:
verify = CONF.v2_vnfm.notification_ca_cert_file
return http_client.OAuth2AuthHandle(None,
param.tokenEndpoint, param.clientId, param.clientPassword,
verify=verify)
if subsc.authentication.obj_attr_is_set('paramsOauth2ClientCert'):
param = subsc.authentication.paramsOauth2ClientCert
ca_cert = CONF.v2_vnfm.notification_mtls_ca_cert_file
client_cert = CONF.v2_vnfm.notification_mtls_client_cert_file
return http_client.OAuth2MtlsAuthHandle(None,
param.tokenEndpoint, param.clientId, ca_cert, client_cert)
# not reach here
def async_call(func):
def inner(*args, **kwargs):
th = threading.Thread(target=func, args=args,
kwargs=kwargs, daemon=True)
th.start()
return inner
@async_call
def send_notification(subsc, notif_data):
auth_handle = _get_notification_auth_handle(subsc)
client = http_client.HttpClient(auth_handle,
version=api_version.CURRENT_FM_VERSION)
url = subsc.callbackUri
try:
resp, _ = client.do_request(
url, "POST", expected_status=[204], body=notif_data)
except sol_ex.SolException:
# it may occur if test_notification was not executed.
LOG.exception("send_notification failed")
if resp.status_code != 204:
LOG.error(f"send_notification failed: {resp.status_code}")
def test_notification(subsc):
auth_handle = _get_notification_auth_handle(subsc)
client = http_client.HttpClient(auth_handle,
version=api_version.CURRENT_FM_VERSION,
timeout=TEST_NOTIFICATION_TIMEOUT)
url = subsc.callbackUri
try:
resp, _ = client.do_request(url, "GET", expected_status=[204])
except sol_ex.SolException as e:
# any sort of error is considered. avoid 500 error.
raise sol_ex.TestNotificationFailed() from e
if resp.status_code != 204:
raise sol_ex.TestNotificationFailed()
def get_matched_subscs(context, inst, notif_type, alarm):
subscs = []
for subsc in get_subsc_all(context):

View File

@ -41,9 +41,15 @@ class MonitoringPlugin():
def create_job(self, **kwargs):
pass
def create_threshold(self, **kwargs):
pass
def delete_job(self, **kwargs):
pass
def delete_threshold(self, **kwargs):
pass
def alert(self, **kwargs):
pass

View File

@ -13,21 +13,17 @@
# License for the specific language governing permissions and limitations
# under the License.
import threading
from oslo_log import log as logging
from oslo_utils import uuidutils
from tacker.sol_refactored.api import api_version
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import http_client
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
from tacker.sol_refactored import objects
LOG = logging.getLogger(__name__)
CONF = config.CONF
TEST_NOTIFICATION_TIMEOUT = 20 # seconds
def update_report(context, job_id, report, timestamp):
@ -92,54 +88,6 @@ def make_pm_job_links(pm_job, endpoint):
return links
def _get_notification_auth_handle(pm_job):
if not pm_job.obj_attr_is_set('authentication'):
verify = CONF.v2_vnfm.notification_verify_cert
if verify and CONF.v2_vnfm.notification_ca_cert_file:
verify = CONF.v2_vnfm.notification_ca_cert_file
return http_client.NoAuthHandle(verify=verify)
if pm_job.authentication.obj_attr_is_set('paramsBasic'):
param = pm_job.authentication.paramsBasic
verify = CONF.v2_vnfm.notification_verify_cert
if verify and CONF.v2_vnfm.notification_ca_cert_file:
verify = CONF.v2_vnfm.notification_ca_cert_file
return http_client.BasicAuthHandle(param.userName, param.password,
verify=verify)
if pm_job.authentication.obj_attr_is_set(
'paramsOauth2ClientCredentials'):
param = pm_job.authentication.paramsOauth2ClientCredentials
verify = CONF.v2_vnfm.notification_verify_cert
if verify and CONF.v2_vnfm.notification_ca_cert_file:
verify = CONF.v2_vnfm.notification_ca_cert_file
return http_client.OAuth2AuthHandle(
None, param.tokenEndpoint, param.clientId, param.clientPassword,
verify=verify)
if pm_job.authentication.obj_attr_is_set('paramsOauth2ClientCert'):
param = pm_job.authentication.paramsOauth2ClientCert
ca_cert = CONF.v2_vnfm.notification_mtls_ca_cert_file
client_cert = CONF.v2_vnfm.notification_mtls_client_cert_file
return http_client.OAuth2MtlsAuthHandle(None,
param.tokenEndpoint, param.clientId, ca_cert, client_cert)
return None
def test_notification(pm_job):
auth_handle = _get_notification_auth_handle(pm_job)
client = http_client.HttpClient(auth_handle,
version=api_version.CURRENT_PM_VERSION,
timeout=TEST_NOTIFICATION_TIMEOUT)
url = pm_job.callbackUri
try:
resp, _ = client.do_request(url, "GET", expected_status=[204])
except sol_ex.SolException as e:
# any sort of error is considered. avoid 500 error.
raise sol_ex.TestNotificationFailed() from e
if resp.status_code != 204:
raise sol_ex.TestNotificationFailed()
def make_pm_notif_data(instance_id, sub_instance_ids, report_id,
pm_job, timestamp, endpoint):
notif_data = objects.PerformanceInformationAvailableNotificationV2(
@ -163,30 +111,3 @@ def make_pm_notif_data(instance_id, sub_instance_ids, report_id,
if sub_instance_ids:
notif_data.subObjectInstanceIds = sub_instance_ids
return notif_data
def async_call(func):
def inner(*args, **kwargs):
th = threading.Thread(target=func, args=args,
kwargs=kwargs, daemon=True)
th.start()
return inner
@async_call
def send_notification(pm_job, notif_data):
auth_handle = _get_notification_auth_handle(pm_job)
client = http_client.HttpClient(auth_handle,
version=api_version.CURRENT_PM_VERSION)
url = pm_job.callbackUri
try:
resp, _ = client.do_request(
url, "POST", expected_status=[204], body=notif_data)
except sol_ex.SolException:
# it may occur if test_notification was not executed.
LOG.exception("send_notification failed")
if resp.status_code != 204:
LOG.error(f'send_notification failed: {resp.status_code}')

View File

@ -0,0 +1,120 @@
# 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_log import log as logging
from oslo_utils import uuidutils
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import coordinate
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
from tacker.sol_refactored import objects
LOG = logging.getLogger(__name__)
CONF = config.CONF
def get_pm_threshold_all(context, marker=None):
return objects.ThresholdV2.get_all(context, marker)
def get_pm_threshold(context, pm_threshold_id):
return objects.ThresholdV2.get_by_id(context, pm_threshold_id)
def get_pm_threshold_state(pm_threshold, sub_object_instance_id):
if (not pm_threshold.obj_attr_is_set('metadata') or
not pm_threshold.metadata.get('thresholdState')):
return None
pm_threshold_states = pm_threshold.metadata['thresholdState']
for pm_threshold_state in pm_threshold_states:
if (pm_threshold_state.get('subObjectInstanceId') ==
sub_object_instance_id):
return pm_threshold_state
def pm_threshold_href(threshold_id, endpoint):
return f"{endpoint}/vnfpm/v2/thresholds/{threshold_id}"
def make_pm_threshold_links(threshold, endpoint):
links = objects.ThresholdV2_Links()
links.self = objects.Link(href=pm_threshold_href(threshold.id, endpoint))
links.object = objects.Link(
href=inst_utils.inst_href(threshold.objectInstanceId, endpoint))
return links
@coordinate.lock_resources('{threshold_id}')
def update_threshold_state_data(context, threshold_id,
threshold_state_data):
pm_threshold = get_pm_threshold(context, threshold_id)
if not pm_threshold:
raise sol_ex.PMThresholdNotExist(threshold_id=threshold_id)
if pm_threshold.obj_attr_is_set('metadata'):
if 'thresholdState' not in pm_threshold.metadata.keys():
pm_threshold.metadata.update({
'thresholdState': [threshold_state_data]})
else:
is_exist = False
for threshold_state in pm_threshold.metadata['thresholdState']:
if (threshold_state.get('subObjectInstanceId') ==
threshold_state_data['subObjectInstanceId'] and
threshold_state.get('metrics') ==
threshold_state_data['metrics']):
threshold_state.update(threshold_state_data)
is_exist = True
break
if not is_exist:
pm_threshold.metadata['thresholdState'].append(
threshold_state_data)
else:
pm_threshold.metadata = {
'thresholdState': [threshold_state_data]}
with context.session.begin(subtransactions=True):
pm_threshold.update(context)
def make_threshold_notif_data(timestamp, threshold_state,
endpoint, pm_threshold):
notif_data = objects.ThresholdCrossedNotificationV2(
id=uuidutils.generate_uuid(),
notificationType="ThresholdCrossedNotification",
timeStamp=timestamp,
thresholdId=pm_threshold.id,
crossingDirection=threshold_state['crossingDirection'],
objectType=pm_threshold.objectType,
objectInstanceId=pm_threshold.objectInstanceId,
subObjectInstanceId=threshold_state['subObjectInstanceId'],
performanceMetric=threshold_state['metrics'],
performanceValue=threshold_state['performanceValue'],
_links=objects.ThresholdCrossedNotificationV2_Links(
objectInstance=objects.NotificationLink(
href=inst_utils.inst_href(
pm_threshold.objectInstanceId, endpoint)
),
threshold=objects.NotificationLink(
href=pm_threshold_href(
pm_threshold.id, endpoint)
)
)
)
if threshold_state['subObjectInstanceId']:
notif_data.subObjectInstanceId = threshold_state['subObjectInstanceId']
return notif_data

View File

@ -34,6 +34,7 @@ from tacker.sol_refactored.common import fm_alarm_utils
from tacker.sol_refactored.common import http_client
from tacker.sol_refactored.common import monitoring_plugin_base as mon_base
from tacker.sol_refactored.common import pm_job_utils
from tacker.sol_refactored.common import pm_threshold_utils
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
from tacker.sol_refactored.conductor import conductor_rpc_v2 as rpc
from tacker.sol_refactored import objects
@ -62,6 +63,64 @@ class PrometheusPluginPmBase(PrometheusPlugin):
auth_handle = http_client.NoAuthHandle()
self.client = http_client.HttpClient(auth_handle)
def set_callback(self, notification_callback):
self.notification_callback = notification_callback
def alert(self, **kwargs):
try:
self._alert(kwargs['request'], body=kwargs['body'])
except Exception as e:
# All exceptions are ignored here and 204 response will always
# be returned because when Tacker responds errors to alertmanager,
# alertmanager may repeat the same reports.
LOG.error("%s: %s", e.__class__.__name__, e.args[0])
def decompose_metrics(self, pm_job_or_threshold):
if pm_job_or_threshold.objectType in {'Vnf', 'Vnfc'}:
return self.decompose_metrics_vnfc(pm_job_or_threshold)
if pm_job_or_threshold.objectType in {'VnfIntCp', 'VnfExtCp'}:
return self.decompose_metrics_vnfintextcp(pm_job_or_threshold)
raise sol_ex.PrometheusPluginError(
f"Invalid objectType: {pm_job_or_threshold.objectType}.")
def _make_rules(self, pm_job_or_threshold, metric, inst_map):
sub_objs = []
if (pm_job_or_threshold.obj_attr_is_set('subObjectInstanceIds')
and pm_job_or_threshold.subObjectInstanceIds):
sub_objs = pm_job_or_threshold.subObjectInstanceIds
# Cardinality of objectInstanceIds and subObjectInstanceIds
# is N:0 or 1:N.
if len(sub_objs) > 0:
return self._make_rules_for_each_sub_obj(
pm_job_or_threshold, inst_map, metric)
return self._make_rules_for_each_obj(
pm_job_or_threshold, inst_map, metric)
def make_rules(self, context, pm_job_or_threshold):
target_list, reload_list = self.get_access_info(pm_job_or_threshold)
metrics = self.decompose_metrics(pm_job_or_threshold)
inst_map = self.get_vnf_instances(context, pm_job_or_threshold)
rules = sum(
[self._make_rules(pm_job_or_threshold, metric, inst_map)
for metric in metrics], [])
if len(rules) == 0:
raise sol_ex.PrometheusPluginError(
"Converting from a PM job/threshold to alert rules is failed."
f" PM job/threshold id: {pm_job_or_threshold.id}")
rule_group = {
'groups': [
{
'name': f'tacker_{pm_job_or_threshold.id}',
'rules': rules
}
]
}
self.upload_rules(
context, target_list, reload_list,
rule_group, pm_job_or_threshold.id)
return rule_group
def convert_measurement_unit(self, metric, value):
if re.match(r'^V(Cpu|Memory|Disk)Usage(Mean|Peak)Vnf\..+', metric):
value = float(value)
@ -69,7 +128,7 @@ class PrometheusPluginPmBase(PrometheusPlugin):
metric):
value = int(value)
else:
raise sol_ex.PrometheusPluginError(
raise sol_ex.PrometheusPluginSkipped(
"Failed to convert annotations.value to measurement unit.")
return value
@ -116,7 +175,7 @@ class PrometheusPluginPmBase(PrometheusPlugin):
labels = {
'alertname': '',
'receiver_type': 'tacker',
'function_type': 'vnfpm-threshold',
'function_type': 'vnfpm_threshold',
'threshold_id': id,
'object_instance_id': object_instance_id,
'sub_object_instance_id': sub_object_instance_id,
@ -588,16 +647,6 @@ class PrometheusPluginPm(PrometheusPluginPmBase, mon_base.MonitoringPlugin):
collection_period=collection_period))
return rules
def _make_rules(self, pm_job, metric, inst_map):
sub_objs = pm_job.subObjectInstanceIds\
if (pm_job.obj_attr_is_set('subObjectInstanceIds') and
pm_job.subObjectInstanceIds) else []
# Cardinality of objectInstanceIds and subObjectInstanceIds
# is N:0 or 1:N.
if len(sub_objs) > 0:
return self._make_rules_for_each_sub_obj(pm_job, inst_map, metric)
return self._make_rules_for_each_obj(pm_job, inst_map, metric)
def decompose_metrics_vnfintextcp(self, pm_job):
group_name = 'VnfInternalCp'\
if pm_job.objectType == 'VnfIntCp' else 'VnfExternalCp'
@ -623,14 +672,6 @@ class PrometheusPluginPm(PrometheusPluginPmBase, mon_base.MonitoringPlugin):
)
return metrics
def decompose_metrics(self, pm_job):
if pm_job.objectType in {'Vnf', 'Vnfc'}:
return self.decompose_metrics_vnfc(pm_job)
if pm_job.objectType in {'VnfIntCp', 'VnfExtCp'}:
return self.decompose_metrics_vnfintextcp(pm_job)
raise sol_ex.PrometheusPluginError(
f"Invalid objectType: {pm_job.objectType}.")
def get_vnf_instances(self, context, pm_job):
object_instance_ids = list(set(pm_job.objectInstanceIds))
return dict(zip(
@ -639,27 +680,267 @@ class PrometheusPluginPm(PrometheusPluginPmBase, mon_base.MonitoringPlugin):
lambda inst: inst_utils.get_inst(context, inst),
object_instance_ids))))
def make_rules(self, context, pm_job):
target_list, reload_list = self.get_access_info(pm_job)
metrics = self.decompose_metrics(pm_job)
inst_map = self.get_vnf_instances(context, pm_job)
rules = sum([self._make_rules(pm_job, metric, inst_map)
for metric in metrics], [])
if len(rules) == 0:
class PrometheusPluginThreshold(PrometheusPluginPmBase,
mon_base.MonitoringPlugin):
_instance = None
@staticmethod
def instance():
if PrometheusPluginThreshold._instance is None:
if not CONF.prometheus_plugin.performance_management:
stub = mon_base.MonitoringPluginStub.instance()
PrometheusPluginThreshold._instance = stub
else:
PrometheusPluginThreshold()
return PrometheusPluginThreshold._instance
def __init__(self):
if PrometheusPluginThreshold._instance:
raise SystemError(
"Not constructor but instance() should be used.")
super(PrometheusPluginThreshold, self).__init__()
self.notification_callback = self.default_callback
PrometheusPluginThreshold._instance = self
def create_threshold(self, **kwargs):
return self.make_rules(kwargs['context'], kwargs['pm_threshold'])
def delete_threshold(self, **kwargs):
self.delete_rules(kwargs['context'], kwargs['pm_threshold'])
def default_callback(self, context, threshold_states):
self.rpc.store_threshold_state_info(context, threshold_states)
def valid_alert(self, pm_threshold, metric, object_instance_id,
sub_object_instance_id):
instance_id = (
pm_threshold.objectInstanceId
if (pm_threshold.obj_attr_is_set('objectInstanceId') and
pm_threshold.objectInstanceId) else None)
if object_instance_id != instance_id:
LOG.error(
f"labels.object_instance_id {object_instance_id} "
"doesn't match pm_threshold.")
raise sol_ex.PrometheusPluginSkipped()
sub_object_instance_ids = (
pm_threshold.subObjectInstanceIds
if (pm_threshold.obj_attr_is_set('subObjectInstanceIds') and
pm_threshold.subObjectInstanceIds) else [])
if (sub_object_instance_id and
(not sub_object_instance_ids or
sub_object_instance_id not in sub_object_instance_ids)):
LOG.error(
f"labels.sub_object_instance_id {sub_object_instance_id} "
"doesn't match pm_threshold.")
raise sol_ex.PrometheusPluginSkipped()
if metric != pm_threshold.criteria.performanceMetric:
LOG.error(
f"labels.metric {metric} doesn't match pm_threshold.")
raise sol_ex.PrometheusPluginSkipped()
def set_threshold_last_value(
self, threshold_value, pm_threshold_state):
threshold_last_value = threshold_value
if pm_threshold_state and pm_threshold_state.get('performanceValue'):
threshold_last_value = pm_threshold_state['performanceValue']
return float(threshold_last_value)
def set_crossing_direction(self, threshold_new_value, threshold_last_value,
threshold_value, threshold_hysteresis):
# NOTE: "IN" is simply used to mark not to send
# ThresholdCrossedNotification.
crossing_direction = "IN"
if (threshold_new_value > (threshold_value + threshold_hysteresis) >=
threshold_last_value):
crossing_direction = "UP"
if (threshold_new_value < (threshold_value - threshold_hysteresis) <=
threshold_last_value):
crossing_direction = "DOWN"
return crossing_direction
@validator.schema(prometheus_plugin_schemas.AlertMessage)
def _alert(self, request, body):
result = []
context = request.context
alerts = (alert for alert in body['alerts'] if
alert['status'] == 'firing' and
alert['labels']['receiver_type'] == 'tacker' and
alert['labels']['function_type'] == 'vnfpm_threshold')
for alert in alerts:
try:
pm_threshold_id = alert['labels']['threshold_id']
object_instance_id = alert['labels']['object_instance_id']
metric = alert['labels']['metric']
sub_object_instance_id = alert['labels'].get(
'sub_object_instance_id')
value = alert['annotations']['value']
pm_threshold = pm_threshold_utils.get_pm_threshold(
context, pm_threshold_id)
if not pm_threshold:
raise sol_ex.PMThresholdNotExist(
threshold_id=pm_threshold_id)
threshold_type = pm_threshold.criteria.thresholdType
simple_threshold_details = (
pm_threshold.criteria.simpleThresholdDetails)
self.valid_alert(pm_threshold, metric, object_instance_id,
sub_object_instance_id)
if threshold_type == "SIMPLE" and simple_threshold_details:
threshold_value = simple_threshold_details.thresholdValue
threshold_hysteresis = simple_threshold_details.hysteresis
pm_threshold_state = (
pm_threshold_utils.get_pm_threshold_state(
pm_threshold, sub_object_instance_id))
threshold_new_value = self.convert_measurement_unit(
metric, value)
threshold_last_value = self.set_threshold_last_value(
threshold_value, pm_threshold_state)
crossing_direction = self.set_crossing_direction(
threshold_new_value, threshold_last_value,
threshold_value, threshold_hysteresis
)
result.append({
'thresholdId': pm_threshold.id,
'subObjectInstanceId': sub_object_instance_id,
'performanceValue': threshold_new_value,
'metrics': metric,
'crossingDirection': crossing_direction
})
else:
LOG.error("Lack thresholdValue and hysteresis")
except (sol_ex.PrometheusPluginSkipped,
sol_ex.PMThresholdNotExist):
pass
# Call ConductorV2
if result and self.notification_callback:
self.notification_callback(
context, result)
for res in result:
res.pop('thresholdId')
return result
def decompose_metrics_vnfc(self, pm_threshold):
# pm_threshold.criteria.performanceMetric : String 1
# pm_threshold.objectInstanceId : String 1
metrics = []
metric = (pm_threshold.criteria.performanceMetric
if pm_threshold.criteria.obj_attr_is_set('performanceMetric')
else None)
_metric = (re.match(
r'^V(Cpu|Memory|Disk)Usage(Mean|Peak)Vnf\..+', metric) and re.sub(
r'^V(Cpu|Memory|Disk)Usage(Mean|Peak)Vnf\.', '', metric))
if _metric == pm_threshold.objectInstanceId:
metrics.append(metric)
return metrics
else:
raise sol_ex.PrometheusPluginError(
f"Converting from a PM job to alert rules is failed."
f" PM job id: {pm_job.id}")
rule_group = {
'groups': [
{
'name': f'tacker_{pm_job.id}',
'rules': rules
}
]
"Invalid performanceMetric.")
def decompose_metrics_vnfintextcp(self, pm_threshold):
# pm_threshold.criteria.performanceMetric : String 1
metrics = []
metric = (pm_threshold.criteria.performanceMetric
if pm_threshold.criteria.obj_attr_is_set('performanceMetric')
else None)
_metric = re.match(r'^(Byte|Packet)(Incoming|Outgoing)' +
pm_threshold.objectType, metric)
if _metric:
metrics.append(metric)
return metrics
else:
raise sol_ex.PrometheusPluginError(
"Invalid performanceMetric.")
def _make_rules_for_each_obj(self, pm_threshold, inst_map, metric):
target = re.sub(r'\..+$', '', metric)
obj = pm_threshold.objectInstanceId
rules = []
# resource ids are like:
# ['test-test1-756757f8f-xcwmt',
# 'test-test2-756757f8f-kmghr', ...]
# convert them to a regex string such as:
# '(test-test1-[0-9a-f]{1,10}-[0-9a-z]{5}$|
# test-test2-[0-9a-f]{1,10}-[0-9a-z]{5}$|...)'
pods_regexp = self.get_pod_regexp(inst_map[obj])
namespace = self.get_namespace(inst_map[obj])
reporting_period = CONF.prometheus_plugin.reporting_period_threshold
collection_period = CONF.prometheus_plugin.collection_period_threshold
expr = self.make_prom_ql(
target, pods_regexp, collection_period=collection_period,
reporting_period=reporting_period, pm_type="Threshold",
namespace=namespace)
rules.append(self.make_rule(
'Threshold', pm_threshold.id, obj, None, metric, expr,
collection_period=collection_period))
return rules
def _make_rules_for_each_sub_obj(self, pm_threshold, inst_map, metric):
reporting_period = CONF.prometheus_plugin.reporting_period_threshold
collection_period = CONF.prometheus_plugin.collection_period_threshold
target = re.sub(r'\..+$', '', metric)
obj = pm_threshold.objectInstanceId
sub_objs = (pm_threshold.subObjectInstanceIds
if (pm_threshold.obj_attr_is_set('subObjectInstanceIds')
and pm_threshold.subObjectInstanceIds) else [])
rules = []
if pm_threshold.objectType in {'Vnf', 'Vnfc'}:
inst = inst_map[obj]
for sub_obj in sub_objs:
compute_resource = self.get_compute_resource_by_sub_obj(
inst, sub_obj)
if not compute_resource:
continue
resource_id = compute_resource.resourceId
namespace = self.get_namespace(inst)
expr = self.make_prom_ql(
target, resource_id,
collection_period=collection_period,
reporting_period=reporting_period,
pm_type="Threshold",
namespace=namespace
)
rules.append(self.make_rule(
'Threshold', pm_threshold.id, obj, sub_obj, metric, expr,
collection_period=collection_period))
else:
pods_regexp = self.get_pod_regexp(inst_map[obj])
if pods_regexp is None:
return []
for sub_obj in sub_objs:
namespace = self.get_namespace(inst_map[obj])
expr = self.make_prom_ql(
target, pods_regexp,
collection_period=collection_period,
reporting_period=reporting_period,
sub_object_instance_id=sub_obj,
pm_type="Threshold",
namespace=namespace
)
rules.append(self.make_rule(
'Threshold', pm_threshold.id, obj, sub_obj, metric, expr,
collection_period=collection_period))
return rules
def get_vnf_instances(self, context, pm_threshold):
return {
pm_threshold.objectInstanceId: inst_utils.get_inst(
context, pm_threshold.objectInstanceId)
}
self.upload_rules(
context, target_list, reload_list, rule_group, pm_job.id)
return rule_group
class PrometheusPluginFm(PrometheusPlugin, mon_base.MonitoringPlugin):

View File

@ -14,13 +14,11 @@
# under the License.
import threading
from oslo_log import log as logging
from oslo_utils import timeutils
from oslo_utils import uuidutils
from tacker.sol_refactored.api import api_version
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import http_client
@ -32,8 +30,6 @@ LOG = logging.getLogger(__name__)
CONF = config.CONF
TEST_NOTIFICATION_TIMEOUT = 20 # seconds
def get_subsc(context, subsc_id):
subsc = objects.LccnSubscriptionV2.get_by_id(context, subsc_id)
@ -50,87 +46,16 @@ def subsc_href(subsc_id, endpoint):
return "{}/vnflcm/v2/subscriptions/{}".format(endpoint, subsc_id)
def _get_notification_auth_handle(subsc):
def get_notification_auth_handle(subsc):
auth_req = subsc.get('authentication', None)
if auth_req:
auth = objects.SubscriptionAuthentication(
authType=auth_req['authType']
)
if 'OAUTH2_CLIENT_CERT' in auth.authType:
param = subsc.authentication.paramsOauth2ClientCert
verify_cert = CONF.v2_vnfm.notification_mtls_ca_cert_file
client_cert = CONF.v2_vnfm.notification_mtls_client_cert_file
return http_client.OAuth2MtlsAuthHandle(None,
param.tokenEndpoint, param.clientId, verify_cert, client_cert)
elif 'OAUTH2_CLIENT_CREDENTIALS' in auth.authType:
param = subsc.authentication.paramsOauth2ClientCredentials
verify = CONF.v2_vnfm.notification_verify_cert
if verify and CONF.v2_vnfm.notification_ca_cert_file:
verify = CONF.v2_vnfm.notification_ca_cert_file
return http_client.OAuth2AuthHandle(None,
param.tokenEndpoint, param.clientId, param.clientPassword,
verify=verify)
elif 'BASIC' in auth.authType:
param = subsc.authentication.paramsBasic
verify = CONF.v2_vnfm.notification_verify_cert
if verify and CONF.v2_vnfm.notification_ca_cert_file:
verify = CONF.v2_vnfm.notification_ca_cert_file
return http_client.BasicAuthHandle(param.userName, param.password,
verify=verify)
else:
raise sol_ex.AuthTypeNotFound(auth.authType)
return common_script_utils.get_notification_auth_handle(subsc)
else:
return http_client.NoAuthHandle()
# not reach here
def async_call(func):
def inner(*args, **kwargs):
th = threading.Thread(target=func, args=args,
kwargs=kwargs, daemon=True)
th.start()
return inner
@async_call
def send_notification(subsc, notif_data):
auth_handle = _get_notification_auth_handle(subsc)
connect_retries = (CONF.v2_vnfm.notify_connect_retries
if CONF.v2_vnfm.notify_connect_retries else None)
client = http_client.HttpClient(auth_handle,
version=api_version.CURRENT_VERSION,
connect_retries=connect_retries)
url = subsc.callbackUri
try:
resp, body = client.do_request(
url, "POST", expected_status=[204], body=notif_data)
except sol_ex.SolException:
# it may occur if test_notification was not executed.
LOG.exception("send_notification failed")
if resp.status_code != 204:
LOG.error("send_notification failed: %d" % resp.status_code)
def test_notification(subsc):
auth_handle = _get_notification_auth_handle(subsc)
client = http_client.HttpClient(auth_handle,
version=api_version.CURRENT_VERSION,
timeout=TEST_NOTIFICATION_TIMEOUT)
url = subsc.callbackUri
try:
resp, _ = client.do_request(url, "GET", expected_status=[204])
except sol_ex.SolException as e:
# any sort of error is considered. avoid 500 error.
raise sol_ex.TestNotificationFailed() from e
if resp.status_code != 204:
raise sol_ex.TestNotificationFailed()
def match_version(version, inst):
# - vnfSoftwareVersion 1
# - vnfdVersions 0..N

View File

@ -95,6 +95,10 @@ class PrometheusPluginConductor(object):
def store_job_info(self, context, report):
self.cast(context, 'store_job_info', report=report)
def store_threshold_state_info(self, context, threshold_states):
self.cast(context, 'store_threshold_state_info',
threshold_states=threshold_states)
def trigger_scale(self, context, id, scale_req):
self.cast(context, 'trigger_scale', id=id, scale_req=scale_req)

View File

@ -393,6 +393,10 @@ class ConductorV2(object):
# call pm_driver
self.vnfpm_driver.store_job_info(context, report)
def store_threshold_state_info(self, context, threshold_states):
# call pm_driver
self.vnfpm_driver.store_threshold_info(context, threshold_states)
@log.log
def trigger_scale(self, context, id, scale_req):
self.prom_driver.trigger_scale(context, id, scale_req)

View File

@ -13,8 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import pm_job_utils
from tacker.sol_refactored.common import pm_threshold_utils
from tacker.sol_refactored.nfvo import nfvo_client
from tacker.sol_refactored import objects
@ -44,6 +49,31 @@ class VnfPmDriverV2():
self.nfvo_client.send_pm_job_notification(
report, pm_job, timestamp, self.endpoint)
def store_threshold_info(self, context, threshold_states):
for threshold_state in threshold_states:
update_threshold_state_data = {
'subObjectInstanceId': threshold_state[
'subObjectInstanceId'],
'performanceValue': threshold_state['performanceValue'],
'metrics': threshold_state['metrics'],
'crossingDirection': threshold_state['crossingDirection']
}
pm_threshold_utils.update_threshold_state_data(
context, threshold_state['thresholdId'],
update_threshold_state_data)
datetime_now = datetime.datetime.now(datetime.timezone.utc)
threshold = pm_threshold_utils.get_pm_threshold(
context, threshold_state['thresholdId'])
if not threshold:
raise sol_ex.PMThresholdNotExist(
threshold_id=threshold_state['thresholdId'])
if threshold_state['crossingDirection'] in {"UP", "DOWN"}:
notif_data = pm_threshold_utils.make_threshold_notif_data(
datetime_now, threshold_state,
self.endpoint, threshold)
common_script_utils.send_notification(
threshold, notif_data, common_script_utils.NOTIFY_TYPE_PM)
def _store_report(self, context, report):
report = objects.PerformanceReportV2.from_dict(report)
report.create(context)

View File

@ -34,6 +34,19 @@ class PmEventController(prom_wsgi.PrometheusPluginAPIController):
return prom_wsgi.PrometheusPluginResponse(204, None)
class PmThresholdController(prom_wsgi.PrometheusPluginAPIController):
def pm_threshold(self, request, body):
if not CONF.prometheus_plugin.performance_management:
raise sol_ex.PrometheusPluginNotEnabled(
name='Performance management')
cls = mon_base.get_class(
CONF.prometheus_plugin.performance_management_threshold_package,
CONF.prometheus_plugin.performance_management_threshold_class)
mon_base.MonitoringPlugin.get_instance(cls).alert(
request=request, body=body)
return prom_wsgi.PrometheusPluginResponse(204, None)
class FmAlertController(prom_wsgi.PrometheusPluginAPIController):
def alert(self, request, body):
if not CONF.prometheus_plugin.fault_management:

View File

@ -20,6 +20,7 @@ from tacker.sol_refactored.api import api_version
from tacker.sol_refactored.api.schemas import vnffm_v1 as schema
from tacker.sol_refactored.api import validator
from tacker.sol_refactored.api import wsgi as sol_wsgi
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import coordinate
from tacker.sol_refactored.common import exceptions as sol_ex
@ -122,7 +123,8 @@ class VnfFmControllerV1(sol_wsgi.SolAPIController):
subsc.authentication = subsc_utils.check_http_client_auth(auth_req)
if CONF.v2_nfvo.test_callback_uri:
fm_subsc_utils.test_notification(subsc)
common_script_utils.test_notification(
subsc, common_script_utils.NOTIFY_TYPE_FM)
subsc.create(context)

View File

@ -21,6 +21,7 @@ from tacker.sol_refactored.api import api_version
from tacker.sol_refactored.api.schemas import vnflcm_v2 as schema
from tacker.sol_refactored.api import validator
from tacker.sol_refactored.api import wsgi as sol_wsgi
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import coordinate
from tacker.sol_refactored.common import exceptions as sol_ex
@ -399,7 +400,7 @@ class VnfLcmControllerV2(sol_wsgi.SolAPIController):
subsc.authentication = subsc_utils.check_http_client_auth(auth_req)
if CONF.v2_nfvo.test_callback_uri:
subsc_utils.test_notification(subsc)
common_script_utils.test_notification(subsc)
subsc.create(context)

View File

@ -23,11 +23,13 @@ from tacker.sol_refactored.api import api_version
from tacker.sol_refactored.api.schemas import vnfpm_v2 as schema
from tacker.sol_refactored.api import validator
from tacker.sol_refactored.api import wsgi as sol_wsgi
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import coordinate
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import monitoring_plugin_base as plugin
from tacker.sol_refactored.common import pm_job_utils
from tacker.sol_refactored.common import pm_threshold_utils
from tacker.sol_refactored.common import subscription_utils as subsc_utils
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
from tacker.sol_refactored.controller import vnfpm_view
@ -45,7 +47,7 @@ OBJ_TYPE_TO_GROUP_TYPE = {
'VnfExtCp': 'VnfExternalCp'
}
OBJ_TYPE_TO_METRIC_LISt = {
OBJ_TYPE_TO_METRIC_LIST = {
'Vnf': {'VCpuUsageMeanVnf', 'VCpuUsagePeakVnf',
'VMemoryUsageMeanVnf', 'VMemoryUsagePeakVnf',
'VDiskUsageMeanVnf', 'VDiskUsagePeakVnf'},
@ -71,20 +73,40 @@ def _check_performance_metric_or_group(
# Check if the type in performance metric matches the standard type.
if performance_metric:
metric_types = {metric.split('.')[0] for metric in performance_metric}
if not metric_types.issubset(OBJ_TYPE_TO_METRIC_LISt[obj_type]):
if not metric_types.issubset(OBJ_TYPE_TO_METRIC_LIST[obj_type]):
raise sol_ex.PMJobInvalidRequest
def _check_metric_and_obj_type(performance_metric, obj_type):
metric_type = {performance_metric}
if obj_type == 'Vnf' or obj_type == 'Vnfc':
if metric_type.issubset(OBJ_TYPE_TO_METRIC_LIST[obj_type]):
raise sol_ex.PMThresholdInvalidRequest
if obj_type == 'VnfIntCp' or obj_type == 'VnfExtCp':
if len(performance_metric.split('.')) == 2:
raise sol_ex.PMThresholdInvalidRequest
metric_type = {performance_metric.split('.')[0]}
if not metric_type.issubset(OBJ_TYPE_TO_METRIC_LIST[obj_type]):
raise sol_ex.PMThresholdInvalidRequest
class VnfPmControllerV2(sol_wsgi.SolAPIController):
def __init__(self):
self.nfvo_client = nfvo_client.NfvoClient()
self.endpoint = CONF.v2_vnfm.endpoint
self._pm_job_view = vnfpm_view.PmJobViewBuilder(self.endpoint)
self._pm_threshold_view = (
vnfpm_view.PmThresholdViewBuilder(self.endpoint))
cls = plugin.get_class(
CONF.prometheus_plugin.performance_management_package,
CONF.prometheus_plugin.performance_management_class)
self.plugin = plugin.MonitoringPlugin.get_instance(cls)
threshold_cls = plugin.get_class(
CONF.prometheus_plugin.performance_management_threshold_package,
CONF.prometheus_plugin.performance_management_threshold_class)
self.threshold_plugin = (
plugin.MonitoringPlugin.get_instance(threshold_cls))
@validator.schema(schema.CreatePmJobRequest_V210, '2.1.0')
def create(self, request, body):
@ -160,7 +182,8 @@ class VnfPmControllerV2(sol_wsgi.SolAPIController):
pm_job.metadata = metadata
if CONF.v2_nfvo.test_callback_uri:
pm_job_utils.test_notification(pm_job)
common_script_utils.test_notification(
pm_job, common_script_utils.NOTIFY_TYPE_PM)
try:
self.plugin.create_job(context=context, pm_job=pm_job)
@ -216,7 +239,8 @@ class VnfPmControllerV2(sol_wsgi.SolAPIController):
body.get("authentication"))
if CONF.v2_nfvo.test_callback_uri:
pm_job_utils.test_notification(pm_job)
common_script_utils.test_notification(
pm_job, common_script_utils.NOTIFY_TYPE_PM)
with context.session.begin(subtransactions=True):
pm_job.update(context)
@ -253,7 +277,7 @@ class VnfPmControllerV2(sol_wsgi.SolAPIController):
version=api_version.CURRENT_PM_VERSION)
def allowed_content_types(self, action):
if action == 'update':
if action in {'update', 'update_threshold'}:
# Content-Type of Modify request shall be
# 'application/mergepatch+json' according to SOL spec.
# But 'application/json' and 'text/plain' is OK for backward
@ -262,5 +286,157 @@ class VnfPmControllerV2(sol_wsgi.SolAPIController):
'text/plain']
return ['application/json', 'text/plain']
def allowed_accept(self, action):
return ['application/json', 'application/mergepatch+json',
'text/plain']
def supported_api_versions(self, action):
return api_version.v2_pm_versions
@validator.schema(schema.CreateThresholdRequest_V210, '2.1.0')
def create_threshold(self, request, body):
context = request.context
object_type = body['objectType']
performance_metric = body["criteria"]['performanceMetric']
# Check if the type in performance metric matches the standard type.
_check_metric_and_obj_type(performance_metric, object_type)
# According to nfv-sol003 6.5.3.4,
# currently criteria.thresholdType supports only "SIMPLE".
if body["criteria"]["thresholdType"] != "SIMPLE":
raise sol_ex.PMThresholdInvalidRequest
# check criteria.thresholdType and criteria.ThresholdDetails
if not body["criteria"].get("simpleThresholdDetails"):
raise sol_ex.PMThresholdInvalidRequest
threshold_value = (
body["criteria"]["simpleThresholdDetails"]["thresholdValue"])
threshold_hysteresis = (
body["criteria"]["simpleThresholdDetails"]["hysteresis"])
# check vnf instance status
inst_id = body.get("objectInstanceId")
inst = inst_utils.get_inst(context, inst_id)
if inst.instantiationState == 'NOT_INSTANTIATED':
raise sol_ex.VnfInstanceIsNotInstantiated(inst_id=inst_id)
threshold_criteria = objects.ThresholdCriteriaV2(
performanceMetric=performance_metric,
thresholdType=body["criteria"]['thresholdType']
)
threshold_details = objects.SimpleThresholdDetails(
thresholdValue=threshold_value,
hysteresis=threshold_hysteresis)
threshold_criteria.simpleThresholdDetails = threshold_details
threshold_id = uuidutils.generate_uuid()
threshold = objects.ThresholdV2(
id=threshold_id,
objectType=object_type,
objectInstanceId=inst_id,
criteria=threshold_criteria,
callbackUri=body["callbackUri"],
)
if body.get("subObjectInstanceIds"):
threshold.subObjectInstanceIds = body["subObjectInstanceIds"]
metadata = body.get('metadata')
if not metadata:
raise sol_ex.PMThresholdInvalidRequest
threshold.metadata = metadata
auth_req = body.get('authentication')
if auth_req:
threshold.authentication = subsc_utils.check_http_client_auth(
auth_req)
if CONF.v2_nfvo.test_callback_uri:
common_script_utils.test_notification(
threshold, common_script_utils.NOTIFY_TYPE_PM)
try:
self.threshold_plugin.create_threshold(
context=context,
pm_threshold=threshold)
except sol_ex.PrometheusPluginError as e:
LOG.error("Failed to create PM Threshold: %s", e.args[0])
raise sol_ex.PrometheusSettingFailed from e
threshold.create(context)
location = pm_threshold_utils.pm_threshold_href(threshold.id,
self.endpoint)
resp_body = self._pm_threshold_view.detail(threshold)
return sol_wsgi.SolResponse(201, resp_body,
version=api_version.CURRENT_PM_VERSION,
location=location)
def index_threshold(self, request):
filter_param = request.GET.get('filter')
filters = (self._pm_threshold_view.parse_filter(filter_param)
if filter_param else None)
page_size = CONF.v2_vnfm.vnfpm_pmthreshold_page_size
pager = self._pm_threshold_view.parse_pager(request, page_size)
pm_job = pm_threshold_utils.get_pm_threshold_all(request.context,
marker=pager.marker)
resp_body = self._pm_threshold_view.detail_list(pm_job, filters,
None, pager)
return sol_wsgi.SolResponse(200, resp_body,
version=api_version.CURRENT_PM_VERSION,
link=pager.get_link())
def show_threshold(self, request, thresholdId):
pm_threshold = pm_threshold_utils.get_pm_threshold(
request.context, thresholdId)
if not pm_threshold:
raise sol_ex.PMThresholdNotExist(threshold_id=thresholdId)
pm_threshold_resp = self._pm_threshold_view.detail(pm_threshold)
return sol_wsgi.SolResponse(200, pm_threshold_resp,
version=api_version.CURRENT_PM_VERSION)
@validator.schema(schema.ThresholdModifications_V210, '2.1.0')
@coordinate.lock_resources('{thresholdId}')
def update_threshold(self, request, thresholdId, body):
context = request.context
pm_threshold = pm_threshold_utils.get_pm_threshold(
context, thresholdId)
if not pm_threshold:
raise sol_ex.PMThresholdNotExist(threshold_id=thresholdId)
if body.get("callbackUri"):
pm_threshold.callbackUri = body.get("callbackUri")
if CONF.v2_nfvo.test_callback_uri:
common_script_utils.test_notification(
pm_threshold, common_script_utils.NOTIFY_TYPE_PM)
if body.get("authentication"):
pm_threshold.authentication = subsc_utils.check_http_client_auth(
body.get("authentication"))
with context.session.begin(subtransactions=True):
pm_threshold.update(context)
pm_threshold_modifications = objects.ThresholdModificationsV2(
callbackUri=pm_threshold.callbackUri)
resp = pm_threshold_modifications.to_dict()
return sol_wsgi.SolResponse(200, resp,
version=api_version.CURRENT_PM_VERSION)
@coordinate.lock_resources('{thresholdId}')
def delete_threshold(self, request, thresholdId):
context = request.context
pm_threshold = pm_threshold_utils.get_pm_threshold(
context, thresholdId)
if not pm_threshold:
raise sol_ex.PMThresholdNotExist(thresholdId=thresholdId)
self.threshold_plugin.delete_threshold(
context=context, pm_threshold=pm_threshold)
pm_threshold.delete(context)
return sol_wsgi.SolResponse(204, None,
version=api_version.CURRENT_PM_VERSION)

View File

@ -17,6 +17,7 @@ from oslo_log import log as logging
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import pm_job_utils
from tacker.sol_refactored.common import pm_threshold_utils
from tacker.sol_refactored.controller import vnflcm_view as base_view
@ -52,3 +53,23 @@ class PmJobViewBuilder(base_view.BaseViewBuilder):
if resp.get('jobId'):
resp.pop('jobId')
return resp
class PmThresholdViewBuilder(base_view.BaseViewBuilder):
_EXCLUDE_DEFAULT = []
def __init__(self, endpoint):
self.endpoint = endpoint
def detail(self, threshold, selector=None):
# NOTE: _links is not saved in DB. create when it is necessary.
if not threshold.obj_attr_is_set('_links'):
threshold._links = pm_threshold_utils.make_pm_threshold_links(
threshold, self.endpoint)
resp = threshold.to_dict()
resp.pop('authentication', None)
resp.pop('metadata', None)
if selector is not None:
resp = selector.filter(threshold, resp)
return resp

View File

@ -227,6 +227,28 @@ class PmJobV2(model_base.BASE):
metadata__ = sa.Column("metadata", sa.JSON(), nullable=True)
class ThresholdV2(model_base.BASE):
"""Type: Threshold
NFV-SOL 003
- v3.3.1 6.5.2.9 (API version: 2.1.0)
"""
__tablename__ = 'ThresholdV2'
id = sa.Column(sa.String(255), nullable=False, primary_key=True)
objectType = sa.Column(sa.String(32), nullable=False)
objectInstanceId = sa.Column(sa.String(255), nullable=False)
subObjectInstanceIds = sa.Column(sa.JSON(), nullable=True)
criteria = sa.Column(sa.JSON(), nullable=False)
callbackUri = sa.Column(sa.String(255), nullable=False)
# NOTE: 'authentication' attribute is not included in the
# original 'Threshold' data type definition.
authentication = sa.Column(sa.JSON(), nullable=True)
# NOTE: 'metadata' attribute is not included in the
# original 'Threshold' data type definition.
metadata__ = sa.Column("metadata", sa.JSON(), nullable=True)
class PerformanceReportV2(model_base.BASE):
"""Type: Report

View File

@ -21,6 +21,7 @@ import zipfile
from oslo_log import log as logging
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import fm_alarm_utils as alarm_utils
from tacker.sol_refactored.common import fm_subscription_utils as fm_utils
@ -185,7 +186,7 @@ class NfvoClient(object):
for subsc in subscs:
notif_data = subsc_utils.make_create_inst_notif_data(
subsc, inst, endpoint)
subsc_utils.send_notification(subsc, notif_data)
common_script_utils.send_notification(subsc, notif_data)
if self.is_local:
self.nfvo.recv_inst_create_notification(context, inst)
@ -195,7 +196,7 @@ class NfvoClient(object):
for subsc in subscs:
notif_data = subsc_utils.make_delete_inst_notif_data(
subsc, inst, endpoint)
subsc_utils.send_notification(subsc, notif_data)
common_script_utils.send_notification(subsc, notif_data)
if self.is_local:
self.nfvo.recv_inst_delete_notification(context, inst)
@ -208,7 +209,7 @@ class NfvoClient(object):
for subsc in subscs:
notif_data = lcmocc_utils.make_lcmocc_notif_data(
subsc, lcmocc, endpoint)
subsc_utils.send_notification(subsc, notif_data)
common_script_utils.send_notification(subsc, notif_data)
if self.is_local:
self.nfvo.recv_lcmocc_notification(context, lcmocc, inst)
@ -218,7 +219,8 @@ class NfvoClient(object):
for subsc in subscs:
notif_data = alarm_utils.make_alarm_notif_data(
subsc, alarm, endpoint)
fm_utils.send_notification(subsc, notif_data)
common_script_utils.send_notification(
subsc, notif_data, common_script_utils.NOTIFY_TYPE_FM)
def send_pm_job_notification(self, report, pm_job, timestamp, endpoint):
report_object_instance_id = {entry.objectInstanceId
@ -232,4 +234,5 @@ class NfvoClient(object):
notif_data = pm_job_utils.make_pm_notif_data(
instance_id, sub_instance_ids, report.id,
pm_job, timestamp, endpoint)
pm_job_utils.send_notification(pm_job, notif_data)
common_script_utils.send_notification(
pm_job, notif_data, common_script_utils.NOTIFY_TYPE_PM)

View File

@ -71,6 +71,7 @@ def register_all():
__import__(objects_root + '.v2.change_ext_vnf_connectivity_request')
__import__(objects_root + '.v2.change_vnf_flavour_request')
__import__(objects_root + '.v2.cp_protocol_info')
__import__(objects_root + '.v2.create_threshold_request')
__import__(objects_root + '.v2.create_vnf_pkg_info_request')
__import__(objects_root + '.v2.create_vnf_request')
__import__(objects_root + '.v2.create_vnf_snapshot_info_request')
@ -105,6 +106,10 @@ def register_all():
__import__(objects_root + '.v2.scale_vnf_request')
__import__(objects_root + '.v2.scale_vnf_to_level_request')
__import__(objects_root + '.v2.terminate_vnf_request')
__import__(objects_root + '.v2.threshold')
__import__(objects_root + '.v2.threshold_criteria')
__import__(objects_root + '.v2.threshold_crossed_notification')
__import__(objects_root + '.v2.threshold_modifications')
__import__(objects_root + '.v2.upload_vnf_package_from_uri_request')
__import__(objects_root + '.v2.virtual_storage_resource_info')
__import__(objects_root + '.v2.vnfc_info')

View File

@ -38,6 +38,8 @@ MACAddressField = ovoo_fields.MACAddressField
NonNegativeIntegerField = ovoo_fields.NonNegativeIntegerField
ObjectField = ovoo_fields.ObjectField
StringField = ovoo_fields.StringField
FloatField = ovoo_fields.FloatField
NonNegativeFloatField = ovoo_fields.NonNegativeFloatField
class BaseTackerEnum(Enum):

View File

@ -0,0 +1,36 @@
# 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 tacker.sol_refactored.objects import base
from tacker.sol_refactored.objects import fields
# NFV-SOL 003
# - v3.3.1 6.5.2.8 (API version: 2.1.0)
@base.TackerObjectRegistry.register
class CreateThresholdRequestV2(base.TackerObject,
base.TackerObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'objectType': fields.StringField(nullable=False),
'objectInstanceId': fields.StringField(nullable=False),
'subObjectInstanceIds': fields.ListOfStringsField(nullable=True),
'criteria': fields.ObjectField('ThresholdCriteriaV2', nullable=False),
'callbackUri': fields.UriField(nullable=False),
'authentication': fields.ObjectField(
'SubscriptionAuthentication', nullable=True),
}

View File

@ -0,0 +1,57 @@
# 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 tacker.sol_refactored.objects import base
from tacker.sol_refactored.objects import fields
# NFV-SOL 003
# - v3.3.1 6.5.2.9 (API version: 2.1.0)
@base.TackerObjectRegistry.register
class ThresholdV2(base.TackerPersistentObject, base.TackerObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'id': fields.StringField(nullable=False),
'objectType': fields.StringField(nullable=False),
'objectInstanceId': fields.StringField(nullable=False),
'subObjectInstanceIds': fields.ListOfStringsField(nullable=True),
'criteria': fields.ObjectField('ThresholdCriteriaV2', nullable=False),
'callbackUri': fields.UriField(nullable=False),
'_links': fields.ObjectField(
'ThresholdV2_Links', nullable=False),
# NOTE: 'authentication' attribute is not included in the
# original 'Threshold' data type definition.
# It is necessary to keep this to be used at sending
# notifications. Note that it is dropped at GET subscription.
'authentication': fields.ObjectField(
'SubscriptionAuthentication', nullable=True),
# NOTE: 'metadata' attribute is not included in the
# original 'Threshold' data type definition.
# It is necessary to keep this to be used at setting prometheus config.
'metadata': fields.KeyValuePairsField(nullable=True),
}
@base.TackerObjectRegistry.register
class ThresholdV2_Links(base.TackerObject, base.TackerObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'self': fields.ObjectField('Link', nullable=False),
'object': fields.ObjectField('Link', nullable=True),
}

View File

@ -0,0 +1,43 @@
# 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 tacker.sol_refactored.objects import base
from tacker.sol_refactored.objects import fields
# NFV-SOL 003
# - v3.3.1 6.5.3.4 (API version: 2.1.0)
@base.TackerObjectRegistry.register
class ThresholdCriteriaV2(base.TackerObject, base.TackerObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'performanceMetric': fields.StringField(nullable=False),
'thresholdType': fields.StringField(nullable=False),
'simpleThresholdDetails': fields.ObjectField(
'SimpleThresholdDetails', nullable=True),
}
@base.TackerObjectRegistry.register
class SimpleThresholdDetails(base.TackerObject, base.TackerObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'thresholdValue': fields.FloatField(nullable=False),
'hysteresis': fields.NonNegativeFloatField(nullable=False),
}

View File

@ -0,0 +1,62 @@
# 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 tacker.sol_refactored.objects import base
from tacker.sol_refactored.objects import fields
# NFV-SOL 003
# - v3.3.1 6.5.2.4 (API version: 2.1.0)
@base.TackerObjectRegistry.register
class ThresholdCrossedNotificationV2(
base.TackerObject,
base.TackerObjectDictCompat
):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'id': fields.StringField(nullable=False),
'notificationType': fields.StringField(nullable=False),
'timeStamp': fields.DateTimeField(nullable=False),
'thresholdId': fields.StringField(nullable=False),
'crossingDirection': fields.StringField(nullable=False),
'objectType': fields.StringField(nullable=False),
'objectInstanceId': fields.StringField(nullable=False),
'subObjectInstanceId': fields.StringField(nullable=True),
'performanceMetric': fields.StringField(nullable=False),
'performanceValue': fields.StringField(nullable=False),
# NOTE: ThresholdCrossedNotificationV2 does not support 'context' now
'context': fields.KeyValuePairsField(nullable=True),
'_links': fields.ObjectField(
'ThresholdCrossedNotificationV2_Links',
nullable=False),
}
@base.TackerObjectRegistry.register
class ThresholdCrossedNotificationV2_Links(
base.TackerObject,
base.TackerObjectDictCompat
):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'objectInstance': fields.ObjectField(
'NotificationLink', nullable=True),
'threshold': fields.ObjectField(
'NotificationLink', nullable=False),
}

View File

@ -0,0 +1,31 @@
# 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 tacker.sol_refactored.objects import base
from tacker.sol_refactored.objects import fields
# NFV-SOL 003
# - v3.3.1 6.5.2.12 (API version: 2.1.0)
@base.TackerObjectRegistry.register
class ThresholdModificationsV2(base.TackerObject, base.TackerObjectDictCompat):
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'callbackUri': fields.StringField(nullable=True),
'authentication': fields.ObjectField(
'SubscriptionAuthentication', nullable=True),
}

View File

@ -206,6 +206,16 @@ class BaseVnfLcmKubernetesV2Test(base.BaseTestCase):
self.assertEqual(notify_type, notify_mock_responses[0].request_body[
'notificationType'])
def _check_no_notification(self, callback_url):
notify_mock_responses = FAKE_SERVER_MANAGER.get_history(
callback_url)
self.assertEqual(0, len(notify_mock_responses))
def _get_crossing_direction(self, callback_url):
notify_mock_responses = FAKE_SERVER_MANAGER.get_history(
callback_url)
return notify_mock_responses[0].request_body['crossingDirection']
@classmethod
def delete_vnf_package(cls, pkg_id):
path = f"/vnfpkgm/v1/vnf_packages/{pkg_id}"
@ -376,6 +386,39 @@ class BaseVnfLcmKubernetesV2Test(base.BaseTestCase):
return self.tacker_client.do_request(
path, "DELETE", version="2.1.0")
def create_pm_threshold(self, req_body):
path = "/vnfpm/v2/thresholds"
return self.tacker_client.do_request(
path, "POST", body=req_body, version="2.1.0")
def update_pm_threshold(self, pm_threshold_id, req_body):
path = f"/vnfpm/v2/thresholds/{pm_threshold_id}"
return self.tacker_client.do_request(
path, "PATCH", body=req_body, version="2.1.0",
content_type="application/mergepatch+json")
def pm_threshold(self, req_body):
path = "/pm_threshold"
return self.tacker_client.do_request(
path, "POST", body=req_body, version="2.1.0")
def list_pm_threshold(self, filter_expr=None):
path = "/vnfpm/v2/thresholds"
if filter_expr:
path = "{}?{}".format(path, urllib.parse.urlencode(filter_expr))
return self.tacker_client.do_request(
path, "GET", version="2.1.0")
def show_pm_threshold(self, pm_threshold_id):
path = f"/vnfpm/v2/thresholds/{pm_threshold_id}"
return self.tacker_client.do_request(
path, "GET", version="2.1.0")
def delete_pm_threshold(self, pm_threshold_id):
path = f"/vnfpm/v2/thresholds/{pm_threshold_id}"
return self.tacker_client.do_request(
path, "DELETE", version="2.1.0")
def prometheus_auto_scaling_alert(self, req_body):
path = "/alert/auto_scaling"
return self.tacker_client.do_request(

View File

@ -153,6 +153,15 @@ def max_sample_scale_in():
}
def scale_out():
return {
"type": "SCALE_OUT",
"aspectId": "vdu2_aspect",
"numberOfSteps": 1,
"additionalParams": {"dummy-key": "dummy-value"}
}
def max_sample_heal(vnfc_ids):
return {
"vnfcInstanceId": vnfc_ids
@ -756,6 +765,113 @@ def update_pm_job(callback_uri):
}
def pm_threshold_min(
callback_uri, inst_id, host_ip,
objectType="Vnf",
sub_object_instance_id=None,
p_metric=None,
thresholdValue=55,
hysteresis=30):
metric = f"VCpuUsageMeanVnf.{inst_id}"
if p_metric:
metric = f"{p_metric}"
return {
"objectType": objectType,
"objectInstanceId": inst_id,
"subObjectInstanceIds": ([sub_object_instance_id]
if sub_object_instance_id else []),
"criteria": {
"performanceMetric": metric,
"thresholdType": "SIMPLE",
"simpleThresholdDetails": {
"thresholdValue": thresholdValue,
"hysteresis": hysteresis
}
},
"callbackUri": callback_uri,
"metadata": {
"monitoring": {
"monitorName": "prometheus",
"driverType": "external",
"targetsInfo": [
{
"prometheusHost": host_ip,
"prometheusHostPort": 50022,
"authInfo": {
"ssh_username": "root",
"ssh_password": "root"
},
"alertRuleConfigPath":
"/tmp",
"prometheusReloadApiEndpoint":
"http://localhost:9990/-/reload"
}
]
}
}
}
def pm_threshold_max(
callback_uri, inst_id, host_ip,
objectType="Vnf",
sub_object_instance_id=None,
p_metric=None,
thresholdValue=55,
hysteresis=30):
metric = f"VCpuUsageMeanVnf.{inst_id}"
if p_metric:
metric = f"{p_metric}"
return {
"objectType": objectType,
"objectInstanceId": inst_id,
"subObjectInstanceIds": ([sub_object_instance_id]
if sub_object_instance_id else []),
"criteria": {
"performanceMetric": metric,
"thresholdType": "SIMPLE",
"simpleThresholdDetails": {
"thresholdValue": thresholdValue,
"hysteresis": hysteresis
}
},
"callbackUri": callback_uri,
"authentication": {
"authType": ["BASIC"],
"paramsBasic": {
"userName": "test",
"password": "test"
}
},
"metadata": {
"monitoring": {
"monitorName": "prometheus",
"driverType": "external",
"targetsInfo": [
{
"prometheusHost": host_ip,
"prometheusHostPort": 50022,
"authInfo": {
"ssh_username": "root",
"ssh_password": "root"
},
"alertRuleConfigPath":
"/tmp",
"prometheusReloadApiEndpoint":
"http://localhost:9990/-/reload"
}
]
}
}
}
def update_pm_threshold(callback_uri):
return {
"callbackUri": callback_uri
}
def pm_event(job_id, inst_id):
return {
"receiver": "receiver",
@ -795,6 +911,51 @@ def pm_event(job_id, inst_id):
}
def pm_threshold(threshold_id, inst_id,
sub_inst_id=None, value=99, p_metric=None):
metric = f"VCpuUsageMeanVnf.{inst_id}"
if p_metric:
metric = f"{p_metric}"
# This data simulates the complete request body sent by alertmanager.
return {
"receiver": "receiver",
"status": "firing",
"alerts": [
{
"status": "firing",
"labels": {
"receiver_type": "tacker",
"function_type": "vnfpm_threshold",
"threshold_id": threshold_id,
"metric": metric,
"object_instance_id": inst_id,
"sub_object_instance_id": sub_inst_id
},
"annotations": {
"value": value,
},
"startsAt": "2022-12-15T23:47:36.453Z",
"endsAt": "0001-01-01T00:00:00Z",
"generatorURL": "http://controller147:9090/graph?g0.expr=up%7B"
"job%3D%22node%22%7D+%3D%3D+0&g0.tab=1",
"fingerprint": "5ef77f1f8a3ecb8d"
}
],
"groupLabels": {},
"commonLabels": {
"alertname": "NodeInstanceDown",
"job": "node"
},
"commonAnnotations": {
"description": "sample"
},
"externalURL": "http://controller147:9093",
"version": "4",
"groupKey": "{}:{}",
"truncatedAlerts": 0
}
def prometheus_auto_scaling_alert(inst_id):
return {
"receiver": "receiver",

View File

@ -46,6 +46,7 @@ max_sample_instantiate = paramgen.max_sample_instantiate(
max_sample_terminate = paramgen.max_sample_terminate()
max_sample_scale_out = paramgen.max_sample_scale_out()
max_sample_scale_in = paramgen.max_sample_scale_in()
scale_out = paramgen.scale_out()
max_sample_heal = paramgen.max_sample_heal(["replace real vnfc ids"])
# if you instantiate with only one resource
@ -78,6 +79,9 @@ with open("max_sample_scale_out", "w", encoding='utf-8') as f:
with open("max_sample_scale_in", "w", encoding='utf-8') as f:
f.write(json.dumps(max_sample_scale_in, indent=2))
with open("scale_out", "w", encoding='utf-8') as f:
f.write(json.dumps(scale_out, indent=2))
with open("max_sample_heal", "w", encoding='utf-8') as f:
f.write(json.dumps(max_sample_heal, indent=2))

View File

@ -0,0 +1,782 @@
# 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 time
from tacker.objects import fields
from tacker.tests.functional.common.fake_server import FakeServerManager
from tacker.tests.functional.sol_kubernetes_v2 import base_v2
from tacker.tests.functional.sol_kubernetes_v2 import paramgen
FAKE_SERVER_MANAGER = FakeServerManager()
@ddt.ddt
class VnfPmThresholdTest(base_v2.BaseVnfLcmKubernetesV2Test):
@classmethod
def setUpClass(cls):
super(VnfPmThresholdTest, cls).setUpClass()
cur_dir = os.path.dirname(__file__)
test_instantiate_cnf_resources_path = os.path.join(
cur_dir, "samples/test_instantiate_cnf_resources")
cls.vnf_pkg_1, cls.vnfd_id_1 = cls.create_vnf_package(
test_instantiate_cnf_resources_path)
@classmethod
def tearDownClass(cls):
super(VnfPmThresholdTest, cls).tearDownClass()
cls.delete_vnf_package(cls.vnf_pkg_1)
def setUp(self):
super(VnfPmThresholdTest, self).setUp()
base_v2.FAKE_SERVER_MANAGER.set_callback(
'PUT', "/-/reload",
status_code=202,
response_headers={"Content-Type": "text/plain"})
def test_pm_threshold_autoscaling_min(self):
"""Test PM Threshold operations with omitting except for required attributes
* About attributes:
Omit except for required attributes.
Only the following cardinality attributes are set.
- 1
- 1..N (1)
* About LCM operations:
This test includes the following operations.
- 1. Create a new VNF instance resource
- 2. Instantiate a VNF instance
- 3. PMThreshold-Create 1
- 4. PM-Threshold 1
- 5. LCM-Scale
- 6. PMThreshold-Create 2
- 7. PM-Threshold 2
- 8. PMThreshold-Create 3
- 9. PM-Threshold 3
- 10. PMThreshold-Create 4
- 11. PM-Threshold 4
- 12. Terminate a VNF instance
- 13. Delete a VNF instance
* About PMThreshold-Create 1-4/PM-Threshold 1-4:
PMThreshold-Create 1:
"objectType": "vnf"
no "subObjectInstanceIds"
"performanceMetric": "VCpuUsageMeanVnf.{inst_id}"
PMThreshold-Create 2:
"objectType": "Vnfc"
"subObjectInstanceIds": {rsc}
"performanceMetric": "VCpuUsageMeanVnf.{inst_id}"
PMThreshold-Create 3:
"objectType": "VnfIntCp"
"subObjectInstanceIds": "eth0"
"performanceMetric": "ByteIncomingVnfIntCp"
PMThreshold-Create 4:
"objectType": "VnfExtCp",
"subObjectInstanceIds": "eth0"
"performanceMetric": "ByteIncomingVnfExtCp"
PMThreshold-Create 1-4 uses different types of "objectType".
PM-Threshold 1:
"metric": "VCpuUsageMeanVnf.{inst_id}"
no "sub_object_instance_id"
PM-Threshold 2:
"metric": "VCpuUsageMeanVnf.{inst_id}"
"sub_object_instance_id": {rsc}
PM-Threshold 3:
"metric": "ByteIncomingVnfIntCp"
"sub_object_instance_id": "eth0",
PM-Threshold 4:
"metric": "ByteIncomingVnfExtCp"
"sub_object_instance_id": "eth0"
"""
# 1. LCM-Create: Create a new VNF instance resource
create_req = paramgen.pm_instantiate_cnf_resources_create(
self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
self.assertEqual(201, resp.status_code)
inst_id = body['id']
# 2. LCM-Instantiate: Instantiate a VNF instance
vim_id = self.get_k8s_vim_id()
instantiate_req = paramgen.min_sample_instantiate(vim_id)
instantiate_req['additionalParams'][
'lcm-kubernetes-def-files'] = ['Files/kubernetes/deployment.yaml']
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
rsc = body['instantiatedVnfInfo']['vnfcInfo'][0]['id']
# 3. PMThreshold-Create 1
pm_expected_attrs = [
'id',
'objectType',
'objectInstanceId',
'criteria',
'callbackUri',
'_links'
]
callback_url = os.path.join(base_v2.MOCK_NOTIFY_CALLBACK_URL,
self._testMethodName)
callback_uri = ('http://localhost:'
f'{base_v2.FAKE_SERVER_MANAGER.SERVER_PORT}'
f'{callback_url}')
sub_req = paramgen.pm_threshold_min(
callback_uri, inst_id, self.fake_prometheus_ip
)
resp, body = self.create_pm_threshold(sub_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, pm_expected_attrs)
# Test notification
self.assert_notification_get(callback_url)
pm_threshold_id_1 = body.get('id')
# 4. PM-Threshold 1
sub_req = paramgen.pm_threshold(pm_threshold_id_1, inst_id)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# The creation of "pm_threshold" will be asynchronous
# and wait for the creation to end
time.sleep(5)
self.assertEqual('UP', self._get_crossing_direction(callback_url))
self._check_notification(
callback_url, 'ThresholdCrossedNotification')
# 5. LCM-Scale
# Scale out a VNF instance
scale_out_req = paramgen.scale_out()
resp, body = self.scale_vnf_instance(inst_id, scale_out_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# 6. PMThreshold-Create 2
pm_expected_attrs_sub = [
'id',
'objectType',
'objectInstanceId',
'subObjectInstanceIds',
'criteria',
'callbackUri',
'_links'
]
sub_req = paramgen.pm_threshold_min(
callback_uri, inst_id, self.fake_prometheus_ip,
objectType="Vnfc",
sub_object_instance_id=rsc
)
resp, body = self.create_pm_threshold(sub_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, pm_expected_attrs_sub)
self.assert_notification_get(callback_url)
pm_threshold_id_2 = body.get('id')
# 7. PM-Threshold 2
sub_req = paramgen.pm_threshold(
pm_threshold_id_2, inst_id,
sub_inst_id=rsc,
)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# The creation of "pm_threshold" will be asynchronous
# and wait for the creation to end
time.sleep(5)
self.assertEqual('UP', self._get_crossing_direction(callback_url))
self._check_notification(
callback_url, 'ThresholdCrossedNotification')
# 8. PMThreshold-Create 3
sub_req = paramgen.pm_threshold_min(
callback_uri, inst_id, self.fake_prometheus_ip,
objectType="VnfIntCp",
sub_object_instance_id="eth0",
p_metric="ByteIncomingVnfIntCp"
)
resp, body = self.create_pm_threshold(sub_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, pm_expected_attrs_sub)
self.assert_notification_get(callback_url)
pm_threshold_id_3 = body.get('id')
# 9. PM-Threshold 3
sub_req = paramgen.pm_threshold(
pm_threshold_id_3, inst_id,
sub_inst_id="eth0",
p_metric="ByteIncomingVnfIntCp"
)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# The creation of "pm_threshold" will be asynchronous
# and wait for the creation to end
time.sleep(5)
self.assertEqual('UP', self._get_crossing_direction(callback_url))
self._check_notification(
callback_url, 'ThresholdCrossedNotification')
# 10. PMThreshold-Create 4
sub_req = paramgen.pm_threshold_min(
callback_uri, inst_id, self.fake_prometheus_ip,
objectType="VnfExtCp",
sub_object_instance_id="eth0",
p_metric="ByteIncomingVnfExtCp"
)
resp, body = self.create_pm_threshold(sub_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, pm_expected_attrs_sub)
self.assert_notification_get(callback_url)
pm_threshold_id_4 = body.get('id')
# 11. PM-Threshold 4
sub_req = paramgen.pm_threshold(
pm_threshold_id_4, inst_id,
sub_inst_id="eth0",
p_metric="ByteIncomingVnfExtCp"
)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# The creation of "pm_threshold" will be asynchronous
# and wait for the creation to end
time.sleep(5)
self.assertEqual('UP', self._get_crossing_direction(callback_url))
self._check_notification(
callback_url, 'ThresholdCrossedNotification')
resp, body = self.delete_pm_threshold(pm_threshold_id_1)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
resp, body = self.delete_pm_threshold(pm_threshold_id_2)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
resp, body = self.delete_pm_threshold(pm_threshold_id_3)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
resp, body = self.delete_pm_threshold(pm_threshold_id_4)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
# 12. LCM-Terminate: Terminate VNF
terminate_req = paramgen.terminate_vnf_min()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(10)
# check instantiationState of VNF
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.assertEqual(
fields.VnfInstanceState.NOT_INSTANTIATED,
body['instantiationState'])
# 13. LCM-Delete: Delete a VNF instance
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
# check deletion of VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(404, resp.status_code)
def test_pm_threshold_autoscaling_max(self):
"""Test PM Threshold operations with all attributes set
* About attributes:
All of the following cardinality attributes are set.
In addition, 0..N or 1..N attributes are set to 2 or more.
- 0..1 (1)
- 0..N (2 or more)
- 1
- 1..N (2 or more)
* About LCM operations:
This test includes the following operations.
- 1. Create a new VNF instance resource
- 2. Instantiate a VNF instance
- 3. PMThreshold-Create 1
- 4. PM-Threshold 1
- 5. LCM-Scale
- 6. PMThreshold-Create 2
- 7. PM-Threshold 2
- 8. PMThreshold-Create 3
- 9. PM-Threshold 3
- 10. PMThreshold-Create 4
- 11. PM-Threshold 4
- 12. Terminate a VNF instance
- 13. Delete a VNF instance
* About PMThreshold-Create 1-4/PM-Threshold 1-4:
PMThreshold-Create 1:
"objectType": "vnf"
no "subObjectInstanceIds"
"performanceMetric": "VCpuUsageMeanVnf.{inst_id}"
PMThreshold-Create 2:
"objectType": "Vnfc"
"subObjectInstanceIds": {rsc}
"performanceMetric": "VCpuUsageMeanVnf.{inst_id}"
PMThreshold-Create 3:
"objectType": "VnfIntCp"
"subObjectInstanceIds": "eth0"
"performanceMetric": "ByteIncomingVnfIntCp"
PMThreshold-Create 4:
"objectType": "VnfExtCp",
"subObjectInstanceIds": "eth0"
"performanceMetric": "ByteIncomingVnfExtCp"
PMThreshold-Create 1-4 uses different types of "objectType".
PM-Threshold 1:
"metric": "VCpuUsageMeanVnf.{inst_id}"
no "sub_object_instance_id"
PM-Threshold 2:
"metric": "VCpuUsageMeanVnf.{inst_id}"
"sub_object_instance_id": {rsc}
PM-Threshold 3:
"metric": "ByteIncomingVnfIntCp"
"sub_object_instance_id": "eth0",
PM-Threshold 4:
"metric": "ByteIncomingVnfExtCp"
"sub_object_instance_id": "eth0"
"""
# 1. LCM-Create: Create a new VNF instance resource
# NOTE: extensions and vnfConfigurableProperties are omitted
# because they are commented out in etsi_nfv_sol001.
create_req = paramgen.pm_instantiate_cnf_resources_create(
self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
self.assertEqual(201, resp.status_code)
inst_id = body['id']
# 2. LCM-Instantiate: Instantiate a VNF instance
vim_id = self.get_k8s_vim_id()
instantiate_req = paramgen.min_sample_instantiate(vim_id)
instantiate_req['additionalParams'][
'lcm-kubernetes-def-files'] = ['Files/kubernetes/deployment.yaml']
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
rsc = body['instantiatedVnfInfo']['vnfcInfo'][0]['id']
# 3. PMThreshold-Create 1
pm_expected_attrs = [
'id',
'objectType',
'objectInstanceId',
'criteria',
'callbackUri',
'_links'
]
callback_url = os.path.join(base_v2.MOCK_NOTIFY_CALLBACK_URL,
self._testMethodName)
callback_uri = ('http://localhost:'
f'{base_v2.FAKE_SERVER_MANAGER.SERVER_PORT}'
f'{callback_url}')
sub_req = paramgen.pm_threshold_max(
callback_uri, inst_id, self.fake_prometheus_ip)
resp, body = self.create_pm_threshold(sub_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, pm_expected_attrs)
# Test notification
self.assert_notification_get(callback_url)
pm_threshold_id_1 = body.get('id')
# 4. PM-Threshold 1
sub_req = paramgen.pm_threshold(pm_threshold_id_1, inst_id)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# The creation of "pm_threshold" will be asynchronous
# and wait for the creation to end
time.sleep(5)
self.assertEqual('UP', self._get_crossing_direction(callback_url))
self._check_notification(
callback_url, 'ThresholdCrossedNotification')
# 5. LCM-Scale
# Scale out a VNF instance
scale_out_req = paramgen.scale_out()
resp, body = self.scale_vnf_instance(inst_id, scale_out_req)
self.assertEqual(202, resp.status_code)
self.check_resp_headers_in_operation_task(resp)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# 6. PMThreshold-Create 2
pm_expected_attrs_sub = [
'id',
'objectType',
'objectInstanceId',
'subObjectInstanceIds',
'criteria',
'callbackUri',
'_links'
]
sub_req = paramgen.pm_threshold_max(
callback_uri, inst_id, self.fake_prometheus_ip,
objectType="Vnfc",
sub_object_instance_id=rsc
)
resp, body = self.create_pm_threshold(sub_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, pm_expected_attrs_sub)
# Test notification
self.assert_notification_get(callback_url)
pm_threshold_id_2 = body.get('id')
# 7. PM-Threshold 2
sub_req = paramgen.pm_threshold(
pm_threshold_id_2, inst_id,
sub_inst_id=rsc,
)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# The creation of "pm_threshold" will be asynchronous
# and wait for the creation to end
time.sleep(5)
self.assertEqual('UP', self._get_crossing_direction(callback_url))
self._check_notification(
callback_url, 'ThresholdCrossedNotification')
# 8. PMThreshold-Create 3
sub_req = paramgen.pm_threshold_max(
callback_uri, inst_id, self.fake_prometheus_ip,
objectType="VnfIntCp",
sub_object_instance_id="eth0",
p_metric="ByteIncomingVnfIntCp"
)
resp, body = self.create_pm_threshold(sub_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, pm_expected_attrs_sub)
# Test notification
self.assert_notification_get(callback_url)
pm_threshold_id_3 = body.get('id')
# 9. PM-Threshold 3
sub_req = paramgen.pm_threshold(
pm_threshold_id_3, inst_id,
sub_inst_id="eth0",
p_metric="ByteIncomingVnfIntCp"
)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# The creation of "pm_threshold" will be asynchronous
# and wait for the creation to end
time.sleep(5)
self.assertEqual('UP', self._get_crossing_direction(callback_url))
self._check_notification(
callback_url, 'ThresholdCrossedNotification')
# 10. PMThreshold-Create 4
sub_req = paramgen.pm_threshold_max(
callback_uri, inst_id, self.fake_prometheus_ip,
objectType="VnfExtCp",
sub_object_instance_id="eth0",
p_metric="ByteIncomingVnfExtCp"
)
resp, body = self.create_pm_threshold(sub_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, pm_expected_attrs_sub)
# Test notification
self.assert_notification_get(callback_url)
pm_threshold_id_4 = body.get('id')
# 11. PM-Threshold 4
sub_req = paramgen.pm_threshold(
pm_threshold_id_4, inst_id,
sub_inst_id="eth0",
p_metric="ByteIncomingVnfExtCp"
)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# The creation of "pm_threshold" will be asynchronous
# and wait for the creation to end
time.sleep(5)
self.assertEqual('UP', self._get_crossing_direction(callback_url))
self._check_notification(
callback_url, 'ThresholdCrossedNotification')
resp, body = self.delete_pm_threshold(pm_threshold_id_1)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
resp, body = self.delete_pm_threshold(pm_threshold_id_2)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
resp, body = self.delete_pm_threshold(pm_threshold_id_3)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
resp, body = self.delete_pm_threshold(pm_threshold_id_4)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
# 12. LCM-Terminate: Terminate VNF
terminate_req = paramgen.terminate_vnf_min()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(10)
# check instantiationState of VNF
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.assertEqual(
fields.VnfInstanceState.NOT_INSTANTIATED,
body['instantiationState']
)
# 13. LCM-Delete: Delete a VNF instance
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
# check deletion of VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(404, resp.status_code)
def test_pm_threshold_with_all_attibutes(self):
"""Test PM Threshold operations with all attributes set
* About attributes:
All of the following cardinality attributes are set.
In addition, 0..N or 1..N attributes are set to 2 or more.
- 0..1 (1)
- 0..N (2 or more)
- 1
- 1..N (2 or more)
* About LCM operations:
This test includes the following operations.
- 1. Create a new VNF instance resource
- 2. Instantiate a VNF instance
- 3. PMThreshold-Create 5
- 4. PM-Threshold 5-1
- 5. PM-Threshold 5-2
- 6. PM-Threshold 5-3
- 7. PM-Threshold 5-4
- 8. PM-Threshold 5-5
- 9. PM-Threshold 5-6
- 10. Terminate a VNF instance
- 11. Delete a VNF instance
* About PMThreshold-Create 5/PM-Threshold 5:
PMThreshold-Create 5:
"objectType": "vnf"
no "subObjectInstanceIds"
"performanceMetric": "VCpuUsageMeanVnf.{inst_id}"
This is a mediocre threshold.
PM-Threshold 5-1:
"value": 99
PM-Threshold 5-2:
"value": 105
PM-Threshold 5-3:
"value": 80
PM-Threshold 5-4:
"value": 40
PM-Threshold 5-5:
"value": 20
PM-Threshold 5-6:
"value": 10
At PM-Threshold 5-1 PM-Threshold 5-5, the notification will be
triggered.
"""
# 1. LCM-Create: Create a new VNF instance resource
# NOTE: extensions and vnfConfigurableProperties are omitted
# because they are commented out in etsi_nfv_sol001.
create_req = paramgen.pm_instantiate_cnf_resources_create(
self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
self.assertEqual(201, resp.status_code)
inst_id = body['id']
# 2. LCM-Instantiate: Instantiate a VNF instance
vim_id = self.get_k8s_vim_id()
instantiate_req = paramgen.min_sample_instantiate(vim_id)
instantiate_req['additionalParams'][
'lcm-kubernetes-def-files'] = ['Files/kubernetes/deployment.yaml']
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
# 3. PMThreshold-Create 5
pm_expected_attrs = [
'id',
'objectType',
'objectInstanceId',
'criteria',
'callbackUri',
'_links'
]
callback_url = os.path.join(base_v2.MOCK_NOTIFY_CALLBACK_URL,
self._testMethodName)
callback_uri = ('http://localhost:'
f'{base_v2.FAKE_SERVER_MANAGER.SERVER_PORT}'
f'{callback_url}')
sub_req = paramgen.pm_threshold_max(
callback_uri, inst_id, self.fake_prometheus_ip
)
resp, body = self.create_pm_threshold(sub_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, pm_expected_attrs)
# Test notification
self.assert_notification_get(callback_url)
pm_threshold_id_5 = body.get('id')
# 4. PM-Threshold 5-1
sub_req = paramgen.pm_threshold(pm_threshold_id_5, inst_id)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(5)
self.assertEqual('UP', self._get_crossing_direction(callback_url))
self._check_notification(
callback_url, 'ThresholdCrossedNotification')
# 5. PM-Threshold 5-2
sub_req = paramgen.pm_threshold(
pm_threshold_id_5, inst_id,
value=105,
)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(5)
self._check_no_notification(callback_url)
# 6. PM-Threshold 5-3
sub_req = paramgen.pm_threshold(
pm_threshold_id_5, inst_id,
value=80
)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(5)
self._check_no_notification(callback_url)
# 7. PM-Threshold 5-4
sub_req = paramgen.pm_threshold(
pm_threshold_id_5, inst_id,
value=40
)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(5)
self._check_no_notification(callback_url)
# 8. PM-Threshold 5-5
sub_req = paramgen.pm_threshold(
pm_threshold_id_5, inst_id,
value=20
)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(5)
self.assertEqual('DOWN', self._get_crossing_direction(callback_url))
self._check_notification(
callback_url, 'ThresholdCrossedNotification')
# 9. PM-Threshold 5-6
sub_req = paramgen.pm_threshold(
pm_threshold_id_5, inst_id,
value=10
)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(5)
self._check_no_notification(callback_url)
resp, body = self.delete_pm_threshold(pm_threshold_id_5)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
# 10. LCM-Terminate: Terminate VNF
terminate_req = paramgen.terminate_vnf_min()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(10)
# check instantiationState of VNF
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.assertEqual(
fields.VnfInstanceState.NOT_INSTANTIATED,
body['instantiationState']
)
# 11. LCM-Delete: Delete a VNF instance
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
# check deletion of VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(404, resp.status_code)

View File

@ -0,0 +1,336 @@
# 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 time
from tacker.objects import fields
from tacker.tests.functional.sol_kubernetes_v2 import base_v2
from tacker.tests.functional.sol_kubernetes_v2 import paramgen
@ddt.ddt
class VnfPmThresholdTest(base_v2.BaseVnfLcmKubernetesV2Test):
@classmethod
def setUpClass(cls):
super(VnfPmThresholdTest, cls).setUpClass()
cur_dir = os.path.dirname(__file__)
test_instantiate_cnf_resources_path = os.path.join(
cur_dir, "samples/test_instantiate_cnf_resources")
cls.vnf_pkg_1, cls.vnfd_id_1 = cls.create_vnf_package(
test_instantiate_cnf_resources_path)
@classmethod
def tearDownClass(cls):
super(VnfPmThresholdTest, cls).tearDownClass()
cls.delete_vnf_package(cls.vnf_pkg_1)
def setUp(self):
super(VnfPmThresholdTest, self).setUp()
base_v2.FAKE_SERVER_MANAGER.set_callback(
'PUT', "/-/reload",
status_code=202,
response_headers={"Content-Type": "text/plain"})
def test_pm_threshold_interface_min(self):
"""Test PM Threshold operations with all attributes set
* About attributes:
Omit except for required attributes.
Only the following cardinality attributes are set.
- 1
- 1..N (1)
* About LCM operations:
This test includes the following operations.
- 1. Create a new VNF instance resource
- 2. Instantiate a VNF instance
- 3. PMThreshold-Create
- 4. PMThreshold-Update
- 5. PM-Threshold
- 6. PMThreshold-List
- 7. PMThreshold-Show
- 8. PMThreshold-Delete
- 9. Terminate a VNF instance
- 10. Delete a VNF instance
"""
# 1. LCM-Create: Create a new VNF instance resource
# NOTE: extensions and vnfConfigurableProperties are omitted
# because they are commented out in etsi_nfv_sol001.
create_req = paramgen.pm_instantiate_cnf_resources_create(
self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
self.assertEqual(201, resp.status_code)
inst_id = body['id']
# 2. LCM-Instantiate: Instantiate a VNF instance
vim_id = self.get_k8s_vim_id()
instantiate_req = paramgen.min_sample_instantiate(vim_id)
instantiate_req['additionalParams'][
'lcm-kubernetes-def-files'] = ['Files/kubernetes/deployment.yaml']
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# 3. PMThreshold-Create
pm_expected_attrs = [
'id',
'objectType',
'objectInstanceId',
'criteria',
'callbackUri',
'_links'
]
callback_url = os.path.join(base_v2.MOCK_NOTIFY_CALLBACK_URL,
self._testMethodName)
callback_uri = ('http://localhost:'
f'{base_v2.FAKE_SERVER_MANAGER.SERVER_PORT}'
f'{callback_url}')
sub_req = paramgen.pm_threshold_min(
callback_uri, inst_id, self.fake_prometheus_ip
)
resp, body = self.create_pm_threshold(sub_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, pm_expected_attrs)
# Test notification
self.assert_notification_get(callback_url)
pm_threshold_id = body.get('id')
# 4. PMThreshold-Update
callback_url = os.path.join(
base_v2.MOCK_NOTIFY_CALLBACK_URL,
self._testMethodName
)
callback_url = f'{callback_url}_1'
callback_uri = ('http://localhost:'
f'{base_v2.FAKE_SERVER_MANAGER.SERVER_PORT}'
f'{callback_url}')
# Because the update of the threshold is executed, the 'callback_url'
# is updated, so the url of the fake server needs to be modified.
base_v2.FAKE_SERVER_MANAGER.set_callback(
'GET', callback_url, status_code=204)
base_v2.FAKE_SERVER_MANAGER.set_callback(
'POST', callback_url, status_code=204)
update_req = paramgen.update_pm_threshold(callback_uri)
resp, body = self.update_pm_threshold(pm_threshold_id, update_req)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
self.check_resp_body(body, ['callbackUri'])
# Test notification
self.assert_notification_get(callback_url)
# 5. PM-Threshold
sub_req = paramgen.pm_threshold(pm_threshold_id, inst_id)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# The creation of "pm_threshold" will be asynchronous
# and wait for the creation to end
time.sleep(5)
self._check_notification(
callback_url, 'ThresholdCrossedNotification')
# 6. PMThreshold-List
resp, body = self.list_pm_threshold()
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
for sbsc in body:
self.check_resp_body(sbsc, pm_expected_attrs)
# 7. PMThreshold-Show
resp, body = self.show_pm_threshold(pm_threshold_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
self.check_resp_body(body, pm_expected_attrs)
# 8. PMThreshold-Delete
resp, body = self.delete_pm_threshold(pm_threshold_id)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
# 9. LCM-Terminate: Terminate VNF
terminate_req = paramgen.terminate_vnf_min()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(10)
# check instantiationState of VNF
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.assertEqual(
fields.VnfInstanceState.NOT_INSTANTIATED,
body['instantiationState'])
# 10. LCM-Delete: Delete a VNF instance
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
# check deletion of VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(404, resp.status_code)
def test_pm_threshold_interface_max(self):
"""Test PM Threshold operations with all attributes set
* About attributes:
All of the following cardinality attributes are set.
In addition, 0..N or 1..N attributes are set to 2 or more.
- 0..1 (1)
- 0..N (2 or more)
- 1
- 1..N (2 or more)
* About LCM operations:
This test includes the following operations.
- 1. Create a new VNF instance resource
- 2. Instantiate a VNF instance
- 3. PMThreshold-Create
- 4. PMThreshold-Update
- 5. PM-Threshold
- 6. PMThreshold-List
- 7. PMThreshold-Show
- 8. PMThreshold-Delete
- 9. Terminate a VNF instance
- 10. Delete a VNF instance
"""
# 1. LCM-Create: Create a new VNF instance resource
# NOTE: extensions and vnfConfigurableProperties are omitted
# because they are commented out in etsi_nfv_sol001.
create_req = paramgen.instantiate_cnf_resources_create(self.vnfd_id_1)
resp, body = self.create_vnf_instance(create_req)
self.assertEqual(201, resp.status_code)
inst_id = body['id']
# 2. LCM-Instantiate: Instantiate a VNF instance
vim_id = self.get_k8s_vim_id()
instantiate_req = paramgen.min_sample_instantiate(vim_id)
instantiate_req['additionalParams'][
'lcm-kubernetes-def-files'] = ['Files/kubernetes/deployment.yaml']
resp, body = self.instantiate_vnf_instance(inst_id, instantiate_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# 3. PMThreshold-Create
pm_expected_attrs = [
'id',
'objectType',
'objectInstanceId',
'criteria',
'callbackUri',
'_links'
]
callback_url = os.path.join(base_v2.MOCK_NOTIFY_CALLBACK_URL,
self._testMethodName)
callback_uri = ('http://localhost:'
f'{base_v2.FAKE_SERVER_MANAGER.SERVER_PORT}'
f'{callback_url}')
sub_req = paramgen.pm_threshold_max(
callback_uri, inst_id, self.fake_prometheus_ip)
resp, body = self.create_pm_threshold(sub_req)
self.assertEqual(201, resp.status_code)
self.check_resp_headers_in_create(resp)
self.check_resp_body(body, pm_expected_attrs)
# Test notification
self.assert_notification_get(callback_url)
pm_threshold_id = body.get('id')
# 4. PMThreshold-Update
callback_url = os.path.join(base_v2.MOCK_NOTIFY_CALLBACK_URL,
self._testMethodName)
callback_url = f'{callback_url}_1'
callback_uri = ('http://localhost:'
f'{base_v2.FAKE_SERVER_MANAGER.SERVER_PORT}'
f'{callback_url}')
# Because the update of the threshold is executed, the 'callback_url'
# is updated, so the url of the fake server needs to be modified.
base_v2.FAKE_SERVER_MANAGER.set_callback(
'GET', callback_url, status_code=204)
base_v2.FAKE_SERVER_MANAGER.set_callback(
'POST', callback_url, status_code=204)
update_req = paramgen.update_pm_threshold(callback_uri)
resp, body = self.update_pm_threshold(pm_threshold_id, update_req)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
self.check_resp_body(body, ['callbackUri'])
# Test notification
self.assert_notification_get(callback_url)
# 5. PM-Threshold
sub_req = paramgen.pm_threshold(pm_threshold_id, inst_id)
resp, body = self.pm_threshold(sub_req)
self.assertEqual(204, resp.status_code)
# The creation of "pm_threshold" will be asynchronous
# and wait for the creation to end
time.sleep(5)
self._check_notification(
callback_url, 'ThresholdCrossedNotification')
# 6. PMThreshold-List
filter_expr = {'filter': '(eq,objectType,VirtualCompute)'}
resp, body = self.list_pm_threshold(filter_expr)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
for sbsc in body:
self.check_resp_body(sbsc, pm_expected_attrs)
# 7. PMThreshold-Show
resp, body = self.show_pm_threshold(pm_threshold_id)
self.assertEqual(200, resp.status_code)
self.check_resp_headers_in_get(resp)
self.check_resp_body(body, pm_expected_attrs)
# 8. PMThreshold-Delete
resp, body = self.delete_pm_threshold(pm_threshold_id)
self.assertEqual(204, resp.status_code)
self.check_resp_headers_in_delete(resp)
# 9. LCM-Terminate: Terminate VNF
terminate_req = paramgen.terminate_vnf_min()
resp, body = self.terminate_vnf_instance(inst_id, terminate_req)
self.assertEqual(202, resp.status_code)
lcmocc_id = os.path.basename(resp.headers['Location'])
self.wait_lcmocc_complete(lcmocc_id)
# wait a bit because there is a bit time lag between lcmocc DB
# update and terminate completion.
time.sleep(10)
# check instantiationState of VNF
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(200, resp.status_code)
self.assertEqual(fields.VnfInstanceState.NOT_INSTANTIATED,
body['instantiationState'])
# 10. LCM-Delete: Delete a VNF instance
resp, body = self.delete_vnf_instance(inst_id)
self.assertEqual(204, resp.status_code)
# check deletion of VNF instance
resp, body = self.show_vnf_instance(inst_id)
self.assertEqual(404, resp.status_code)

View File

@ -13,11 +13,22 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import requests
from unittest import mock
from oslo_config import cfg
from oslo_utils import uuidutils
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import fm_alarm_utils
from tacker.sol_refactored.common import http_client
from tacker.sol_refactored.common import pm_job_utils
from tacker.sol_refactored import objects
from tacker.tests import base
from tacker.tests.unit.sol_refactored.samples import fakes_for_fm
SAMPLE_VNFD_ID = "b1bb0ce7-ebca-4fa7-95ed-4840d7000000"
SAMPLE_FLAVOUR_ID = "simple"
@ -27,6 +38,7 @@ class TestCommontScriptUtils(base.BaseTestCase):
def setUp(self):
super(TestCommontScriptUtils, self).setUp()
objects.register_all()
cur_dir = os.path.dirname(__file__)
sample_dir = os.path.join(cur_dir, "..", "samples")
@ -566,3 +578,276 @@ class TestCommontScriptUtils(base.BaseTestCase):
# removed
self.assertNotIn(vl, top_hot['resources'])
self.assertNotIn(vl_subnet, top_hot['resources'])
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_send_notification(self, mock_resp):
subsc_no_auth = objects.LccnSubscriptionV2(
id='sub-1', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback')
notif_data_no_auth = objects.VnfLcmOperationOccurrenceNotificationV2(
id=uuidutils.generate_uuid()
)
resp_no_auth = requests.Response()
resp_no_auth.status_code = 204
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
common_script_utils.send_notification(
subsc_no_auth, notif_data_no_auth)
subsc_basic_auth = objects.LccnSubscriptionV2(
id='sub-2', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback',
authentication=objects.SubscriptionAuthentication(
authType=['BASIC'],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test', password='test')))
# execute basic_auth
common_script_utils.send_notification(
subsc_basic_auth, notif_data_no_auth)
subsc_oauth2 = objects.LccnSubscriptionV2(
id='sub-3', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback',
authentication=objects.SubscriptionAuthentication(
authType=['OAUTH2_CLIENT_CREDENTIALS'],
paramsOauth2ClientCredentials=(
objects.SubscriptionAuthentication_ParamsOauth2(
clientId='test', clientPassword='test',
tokenEndpoint='http://127.0.0.1/token'))))
# execute oauth2
common_script_utils.send_notification(subsc_oauth2, notif_data_no_auth)
subsc_oauth2_mtls = objects.LccnSubscriptionV2(
id='sub-4', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback',
authentication=objects.SubscriptionAuthentication(
authType=["OAUTH2_CLIENT_CERT"],
paramsOauth2ClientCert=(
objects.SubscriptionAuthentication_ParamsOauth2ClientCert(
clientId='test',
certificateRef=objects.
ParamsOauth2ClientCert_CertificateRef(
type='x5t#256',
value='03c6e188d1fe5d3da8c9bc9a8dc531a2'
'b3ecf812b03aede9bec7ba1b410b6b64'
),
tokenEndpoint='http://127.0.0.1/token'
)
)
)
)
# execute oauth2 mtls
common_script_utils.send_notification(
subsc_oauth2_mtls, notif_data_no_auth)
cfg.CONF.set_override("notification_verify_cert", "True",
group="v2_vnfm")
subsc_no_auth = objects.LccnSubscriptionV2(
id='sub-5', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback')
notif_data_no_auth = objects.VnfLcmOperationOccurrenceNotificationV2(
id=uuidutils.generate_uuid()
)
resp_no_auth = requests.Response()
resp_no_auth.status_code = 204
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
common_script_utils.send_notification(
subsc_no_auth, notif_data_no_auth)
subsc_basic_auth = objects.LccnSubscriptionV2(
id='sub-6', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback',
authentication=objects.SubscriptionAuthentication(
authType=['BASIC'],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test', password='test')
)
)
# execute basic_auth
common_script_utils.send_notification(
subsc_basic_auth, notif_data_no_auth)
subsc_oauth2 = objects.LccnSubscriptionV2(
id='sub-7', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback',
authentication=objects.SubscriptionAuthentication(
authType=['OAUTH2_CLIENT_CREDENTIALS'],
paramsOauth2ClientCredentials=(
objects.SubscriptionAuthentication_ParamsOauth2(
clientId='test', clientPassword='test',
tokenEndpoint='http://127.0.0.1/token')
)
)
)
# execute oauth2
common_script_utils.send_notification(subsc_oauth2, notif_data_no_auth)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_send_notification_error_code(self, mock_resp):
subsc_no_auth = objects.LccnSubscriptionV2(
id='sub-1', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback')
notif_data_no_auth = objects.VnfLcmOperationOccurrenceNotificationV2(
id=uuidutils.generate_uuid()
)
resp_no_auth = requests.Response()
resp_no_auth.status_code = 200
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
common_script_utils.send_notification(
subsc_no_auth, notif_data_no_auth)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_send_notification_error(self, mock_resp):
subsc_no_auth = objects.LccnSubscriptionV2(
id='sub-1', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback')
notif_data_no_auth = objects.VnfLcmOperationOccurrenceNotificationV2(
id=uuidutils.generate_uuid()
)
resp_no_auth = Exception()
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
common_script_utils.send_notification(
subsc_no_auth, notif_data_no_auth)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_test_notification(self, mock_resp):
subsc_no_auth = objects.LccnSubscriptionV2(
id='sub-1', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback')
resp_no_auth = requests.Response()
resp_no_auth.status_code = 204
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
common_script_utils.test_notification(subsc_no_auth)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_test_notification_error_code(self, mock_resp):
subsc_no_auth = objects.LccnSubscriptionV2(
id='sub-1', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback')
resp_no_auth = requests.Response()
resp_no_auth.status_code = 200
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
self.assertRaises(sol_ex.TestNotificationFailed,
common_script_utils.test_notification, subsc_no_auth)
class mock_session():
def request(url, method, raise_exc=False, **kwargs):
resp = requests.Response()
resp.status_code = 400
resp.headers['Content-Type'] = 'application/zip'
return resp
@mock.patch.object(http_client.HttpClient, '_decode_body')
@mock.patch.object(http_client.NoAuthHandle, 'get_session')
def test_test_notification_error(self, mock_session, mock_decode_body):
subsc_no_auth = objects.LccnSubscriptionV2(
id='sub-1', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback')
mock_session.return_value = self.mock_session
mock_decode_body.return_value = None
self.assertRaises(sol_ex.TestNotificationFailed,
common_script_utils.test_notification, subsc_no_auth)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_test_notification_fm_subscription(self, mock_resp):
resp_no_auth = requests.Response()
resp_no_auth.status_code = 204
mock_resp.return_value = (resp_no_auth, None)
subsc_basic_auth = objects.FmSubscriptionV1.from_dict(
fakes_for_fm.fm_subsc_example)
subsc_basic_auth.authentication = objects.SubscriptionAuthentication(
authType=["BASIC"],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test', password='test'))
common_script_utils.test_notification(
subsc_basic_auth, common_script_utils.NOTIFY_TYPE_FM)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_test_notification_pm_job(self, mock_do_request):
resp_no_auth = requests.Response()
resp_no_auth.status_code = 204
mock_do_request.return_value = (resp_no_auth, None)
pm_job = objects.PmJobV2(
id='pm_job_1',
authentication=objects.SubscriptionAuthentication(
authType=["BASIC"],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test',
password='test'
)
),
callbackUri='http://127.0.0.1/callback'
)
common_script_utils.test_notification(
pm_job, common_script_utils.NOTIFY_TYPE_PM)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_send_notification_fm_subscribtion(self, mock_resp):
resp_no_auth = requests.Response()
resp_no_auth.status_code = 204
mock_resp.return_value = (resp_no_auth, None)
subsc_basic_auth = objects.FmSubscriptionV1.from_dict(
fakes_for_fm.fm_subsc_example)
subsc_basic_auth.authentication = objects.SubscriptionAuthentication(
authType=["BASIC"],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test', password='test'))
alarm = objects.AlarmV1.from_dict(
fakes_for_fm.alarm_example)
notif_data = fm_alarm_utils.make_alarm_notif_data(
subsc_basic_auth, alarm, 'http://127.0.0.1:9890')
common_script_utils.send_notification(
subsc_basic_auth, notif_data)
self.assertEqual(1, mock_resp.call_count)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_send_notification_pm_job(self, mock_resp):
pm_job = objects.PmJobV2(
id='pm_job_1',
objectType='VNF',
authentication=objects.SubscriptionAuthentication(
authType=["BASIC"],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test',
password='test'
),
),
callbackUri='http://127.0.0.1/callback'
)
sub_instance_ids = ['1', '2', '3', '4']
notif_data = pm_job_utils.make_pm_notif_data('instance_id',
sub_instance_ids,
'report_id',
pm_job,
'2008-01-03 08:04:34',
'endpoint')
resp_no_auth = requests.Response()
resp_no_auth.status_code = 204
mock_resp.return_value = (resp_no_auth, None)
common_script_utils.send_notification(
pm_job, notif_data, common_script_utils.NOTIFY_TYPE_PM)

View File

@ -12,18 +12,16 @@
# 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 copy
import requests
from unittest import mock
from oslo_config import cfg
from oslo_log import log as logging
from tacker import context
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import fm_alarm_utils as alarm_utils
from tacker.sol_refactored.common import fm_subscription_utils as subsc_utils
from tacker.sol_refactored.common import http_client
from tacker.sol_refactored import objects
from tacker.tests import base
from tacker.tests.unit.sol_refactored.samples import fakes_for_fm
@ -60,156 +58,6 @@ class TestFmSubscriptionUtils(base.BaseTestCase):
result = subsc_utils.get_subsc_all(context)
self.assertEqual(fakes_for_fm.fm_subsc_example['id'], result[0].id)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_send_notification(self, mock_resp):
subsc_no_auth = objects.FmSubscriptionV1.from_dict(
fakes_for_fm.fm_subsc_example)
alarm = objects.AlarmV1.from_dict(fakes_for_fm.alarm_example)
notif_data_no_auth = alarm_utils.make_alarm_notif_data(
subsc_no_auth, alarm, 'http://127.0.0.1:9890')
resp_no_auth = requests.Response()
resp_no_auth.status_code = 204
mock_resp.return_value = (resp_no_auth, None)
# 1. execute no_auth
subsc_utils.send_notification(subsc_no_auth, notif_data_no_auth)
subsc_basic_auth = copy.deepcopy(subsc_no_auth)
subsc_basic_auth.authentication = objects.SubscriptionAuthentication(
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test', password='test'))
# 2. execute basic_auth
subsc_utils.send_notification(subsc_basic_auth, notif_data_no_auth)
subsc_oauth2 = copy.deepcopy(subsc_no_auth)
subsc_oauth2.authentication = objects.SubscriptionAuthentication(
paramsOauth2ClientCredentials=(
objects.SubscriptionAuthentication_ParamsOauth2(
clientId='test', clientPassword='test',
tokenEndpoint='http://127.0.0.1/token')))
# 3. execute oauth2
subsc_utils.send_notification(subsc_oauth2, notif_data_no_auth)
subsc_oauth2_mtls = copy.deepcopy(subsc_no_auth)
subsc_oauth2_mtls.authentication = objects.SubscriptionAuthentication(
paramsOauth2ClientCert=(
objects.SubscriptionAuthentication_ParamsOauth2ClientCert(
clientId='test',
certificateRef=objects.
ParamsOauth2ClientCert_CertificateRef(
type='x5t#256',
value='03c6e188d1fe5d3da8c9bc9a8dc531a2'
'b3ecf812b03aede9bec7ba1b410b6b64'
),
tokenEndpoint='http://127.0.0.1/token'
)
)
)
# 4. execute oauth2 mTLS
subsc_utils.send_notification(subsc_oauth2_mtls, notif_data_no_auth)
cfg.CONF.set_override("notification_verify_cert", "True",
group="v2_vnfm")
subsc_no_auth = objects.FmSubscriptionV1.from_dict(
fakes_for_fm.fm_subsc_example)
# 5. execute no_auth
subsc_utils.send_notification(subsc_no_auth, notif_data_no_auth)
subsc_basic_auth = copy.deepcopy(subsc_no_auth)
subsc_basic_auth.authentication = objects.SubscriptionAuthentication(
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test', password='test'))
# 6. execute basic_auth
subsc_utils.send_notification(subsc_basic_auth, notif_data_no_auth)
subsc_oauth2 = copy.deepcopy(subsc_no_auth)
subsc_oauth2.authentication = objects.SubscriptionAuthentication(
paramsOauth2ClientCredentials=(
objects.SubscriptionAuthentication_ParamsOauth2(
clientId='test', clientPassword='test',
tokenEndpoint='http://127.0.0.1/token')))
# 7. execute oauth2
subsc_utils.send_notification(subsc_oauth2, notif_data_no_auth)
self.assertEqual(7, mock_resp.call_count)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_send_notification_error_code(self, mock_resp):
subsc_no_auth = objects.FmSubscriptionV1.from_dict(
fakes_for_fm.fm_subsc_example)
alarm = objects.AlarmV1.from_dict(fakes_for_fm.alarm_example)
notif_data_no_auth = alarm_utils.make_alarm_notif_data(
subsc_no_auth, alarm, 'http://127.0.0.1:9890')
resp_no_auth = requests.Response()
resp_no_auth.status_code = 200
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
subsc_utils.send_notification(subsc_no_auth, notif_data_no_auth)
self.assertLogs(LOG, 'ERROR')
def test_send_notification_error(self):
subsc_no_auth = objects.FmSubscriptionV1.from_dict(
fakes_for_fm.fm_subsc_example)
alarm = objects.AlarmV1.from_dict(fakes_for_fm.alarm_example)
notif_data_no_auth = alarm_utils.make_alarm_notif_data(
subsc_no_auth, alarm, 'http://127.0.0.1:9890')
# execute no_auth
subsc_utils.send_notification(subsc_no_auth, notif_data_no_auth)
self.assertLogs(LOG, 'EXCEPTION')
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_test_notification(self, mock_resp):
subsc_no_auth = objects.FmSubscriptionV1.from_dict(
fakes_for_fm.fm_subsc_example)
resp_no_auth = requests.Response()
resp_no_auth.status_code = 204
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
subsc_utils.test_notification(subsc_no_auth)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_test_notification_error_code(self, mock_resp):
subsc_no_auth = objects.FmSubscriptionV1.from_dict(
fakes_for_fm.fm_subsc_example)
resp_no_auth = requests.Response()
resp_no_auth.status_code = 200
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
self.assertRaises(sol_ex.TestNotificationFailed,
subsc_utils.test_notification, subsc_no_auth)
class mock_session():
def request(url, method, raise_exc=False, **kwargs):
resp = requests.Response()
resp.status_code = 400
resp.headers['Content-Type'] = 'application/zip'
return resp
@mock.patch.object(http_client.HttpClient, '_decode_body')
@mock.patch.object(http_client.NoAuthHandle, 'get_session')
def test_test_notification_error(self, mock_session, mock_decode_body):
subsc_no_auth = objects.FmSubscriptionV1.from_dict(
fakes_for_fm.fm_subsc_example)
mock_session.return_value = self.mock_session
mock_decode_body.return_value = None
self.assertRaises(sol_ex.TestNotificationFailed,
subsc_utils.test_notification, subsc_no_auth)
@mock.patch.object(objects.base.TackerPersistentObject, 'get_all')
def test_get_matched_subscs(self, mock_subscs):
inst = objects.VnfInstanceV2(id='test-instance', vnfProvider='company')

View File

@ -13,15 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from oslo_utils import uuidutils
import requests
from unittest import mock
from tacker import context
from tacker.sol_refactored.api import api_version
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import http_client
from tacker.sol_refactored.common import pm_job_utils
from tacker.sol_refactored import objects
from tacker.tests import base
@ -120,236 +117,3 @@ class TestPmJobUtils(base.BaseTestCase):
result = pm_job_utils.make_pm_job_links(pm_job, 'endpoint')
href = result.self.href
self.assertEqual('endpoint/vnfpm/v2/pm_jobs/pm_job_1', href)
def test_get_notification_auth_handle(self):
pm_job = objects.PmJobV2(id='pm_job_1')
result = pm_job_utils._get_notification_auth_handle(pm_job)
res = type(result).__name__
name = type(http_client.NoAuthHandle()).__name__
self.assertEqual(name, res)
pm_job_1_auth = objects.SubscriptionAuthentication(
authType=["BASIC"],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test',
password='test',
)
)
pm_job_1 = objects.PmJobV2(
id='pm_job_1',
authentication=pm_job_1_auth)
result = pm_job_utils._get_notification_auth_handle(pm_job_1)
self.assertIsInstance(result, http_client.BasicAuthHandle)
pm_job_2 = objects.PmJobV2(
id='pm_job_2',
authentication=objects.SubscriptionAuthentication(
authType=["OAUTH2_CLIENT_CREDENTIALS"],
paramsOauth2ClientCredentials=(
objects.SubscriptionAuthentication_ParamsOauth2(
clientId='test',
clientPassword='test',
tokenEndpoint='http://127.0.0.1/token'
))
)
)
result = pm_job_utils._get_notification_auth_handle(pm_job_2)
self.assertIsInstance(result, http_client.OAuth2AuthHandle)
pm_job_3 = objects.PmJobV2(
id='pm_job_3',
authentication=objects.SubscriptionAuthentication(
authType=["OAUTH2_CLIENT_CERT"],
paramsOauth2ClientCert=(
objects.SubscriptionAuthentication_ParamsOauth2ClientCert(
clientId='test',
certificateRef=objects.
ParamsOauth2ClientCert_CertificateRef(
type='x5t#256',
value='03c6e188d1fe5d3da8c9bc9a8dc531a2'
'b3ecf812b03aede9bec7ba1b410b6b64'
),
tokenEndpoint='http://127.0.0.1/token'
))
)
)
result = pm_job_utils._get_notification_auth_handle(pm_job_3)
self.assertIsInstance(result, http_client.OAuth2MtlsAuthHandle)
cfg.CONF.set_override("notification_verify_cert", "True",
group="v2_vnfm")
pm_job_4 = objects.PmJobV2(
id='pm_job_4',
authentication=pm_job_1_auth)
result = pm_job_utils._get_notification_auth_handle(pm_job_4)
res = type(result).__name__
name = type(http_client.BasicAuthHandle('test', 'test')).__name__
self.assertEqual(name, res)
pm_job_5 = objects.PmJobV2(
id='pm_job_5',
authentication=objects.SubscriptionAuthentication(
authType=["OAUTH2_CLIENT_CREDENTIALS"],
paramsOauth2ClientCredentials=(
objects.SubscriptionAuthentication_ParamsOauth2(
clientId='test',
clientPassword='test',
tokenEndpoint='http://127.0.0.1/token'
))
)
)
result = pm_job_utils._get_notification_auth_handle(pm_job_5)
res = type(result).__name__
name = type(http_client.OAuth2AuthHandle(
None, 'http://127.0.0.1/token', 'test', 'test')).__name__
self.assertEqual(name, res)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_test_notification(self, mock_do_request):
resp_no_auth = requests.Response()
resp_no_auth.status_code = 204
mock_do_request.return_value = (resp_no_auth, None)
pm_job = objects.PmJobV2(
id='pm_job_1',
authentication=objects.SubscriptionAuthentication(
authType=["BASIC"],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test',
password='test'
)
),
callbackUri='http://127.0.0.1/callback'
)
pm_job_utils.test_notification(pm_job)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_test_notification_error_code(self, mock_do_request):
# execute not 204
resp_no_auth = requests.Response()
resp_no_auth.status_code = 500
mock_do_request.return_value = (resp_no_auth, None)
pm_job = objects.PmJobV2(
id='pm_job_1',
authentication=objects.SubscriptionAuthentication(
authType=["BASIC"],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test',
password='test'
)
),
callbackUri='http://127.0.0.1/callback'
)
self.assertRaises(sol_ex.TestNotificationFailed,
pm_job_utils.test_notification, pm_job=pm_job)
class mock_session():
def request(url, method, raise_exc=False, **kwargs):
resp = requests.Response()
resp.status_code = 400
resp.headers['Content-Type'] = 'application/zip'
return resp
@mock.patch.object(http_client.HttpClient, '_decode_body')
@mock.patch.object(http_client.BasicAuthHandle, 'get_session')
def test_test_notification_error(self, mock_session, mock_decode_body):
# execute not 204
mock_session.return_value = self.mock_session
mock_decode_body.return_value = None
pm_job = objects.PmJobV2(
id='pm_job_1',
authentication=objects.SubscriptionAuthentication(
authType=["BASIC"],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test',
password='test'
),
),
callbackUri='http://127.0.0.1/callback'
)
self.assertRaises(sol_ex.TestNotificationFailed,
pm_job_utils.test_notification, pm_job=pm_job)
def test_make_pm_notif_data(self):
sub_instance_ids = ['1', '2', '3', '4']
pm_job = objects.PmJobV2(id='pm_job_1',
objectType='VNF'
)
result = pm_job_utils.make_pm_notif_data('instance_id',
sub_instance_ids,
'report_id',
pm_job,
'2008-01-03 08:04:34',
'endpoint')
self.assertEqual('instance_id', result.objectInstanceId)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_send_notification(self, mock_resp):
pm_job = objects.PmJobV2(id='pm_job_1',
objectType='VNF',
callbackUri='http://127.0.0.1/callback'
)
sub_instance_ids = ['1', '2', '3', '4']
notif_data = pm_job_utils.make_pm_notif_data('instance_id',
sub_instance_ids,
'report_id',
pm_job,
'2008-01-03 08:04:34',
'endpoint')
resp_no_auth = requests.Response()
resp_no_auth.status_code = 204
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
pm_job_utils.send_notification(pm_job, notif_data)
pm_job = objects.PmJobV2(
id='pm_job_1',
objectType='VNF',
authentication=objects.SubscriptionAuthentication(
authType=["BASIC"],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test',
password='test'
),
),
callbackUri='http://127.0.0.1/callback'
)
sub_instance_ids = ['1', '2', '3', '4']
notif_data = pm_job_utils.make_pm_notif_data('instance_id',
sub_instance_ids,
'report_id',
pm_job,
'2008-01-03 08:04:34',
'endpoint')
resp_no_auth = requests.Response()
resp_no_auth.status_code = 204
mock_resp.return_value = (resp_no_auth, None)
# execute basic_auth
pm_job_utils.send_notification(pm_job, notif_data)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_send_notification_error(self, mock_resp):
pm_job = objects.PmJobV2(
id='pm_job_1',
objectType='VNF',
authentication=objects.SubscriptionAuthentication(
authType=["BASIC"],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test',
password='test'
),
),
callbackUri='http://127.0.0.1/callback'
)
sub_instance_ids = ['1', '2', '3', '4']
notif_data = pm_job_utils.make_pm_notif_data('instance_id',
sub_instance_ids,
'report_id',
pm_job,
'2008-01-03 08:04:34',
'endpoint')
resp_no_auth = requests.Response()
resp_no_auth.status_code = Exception()
mock_resp.return_value = (resp_no_auth, None)
# execute basic_auth
pm_job_utils.send_notification(pm_job, notif_data)

View File

@ -0,0 +1,122 @@
# 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 unittest import mock
from tacker import context
from tacker.sol_refactored.api import api_version
from tacker.sol_refactored.common import pm_threshold_utils
from tacker.sol_refactored import objects
from tacker.tests import base
class TestPmThresholdUtils(base.BaseTestCase):
def setUp(self):
super(TestPmThresholdUtils, self).setUp()
objects.register_all()
self.context = context.get_admin_context()
self.context.api_version = api_version.APIVersion('2.1.0')
@mock.patch.object(objects.base.TackerPersistentObject, 'get_all')
def test_get_pm_threshold_all(self, mock_pm):
mock_pm.return_value = [objects.ThresholdV2(id='pm_threshold_1')]
result = pm_threshold_utils.get_pm_threshold_all(self.context)
self.assertEqual('pm_threshold_1', result[0].id)
@mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id')
def test_get_pm_threshold(self, mock_pm):
mock_pm.return_value = objects.ThresholdV2(id='pm_threshold_1')
result = pm_threshold_utils.get_pm_threshold(
self.context, 'pm_threshold_1')
self.assertEqual('pm_threshold_1', result.id)
def test_get_pm_threshold_state(self):
pm_threshold = objects.ThresholdV2(id='pm_threshold_1')
result = pm_threshold_utils.get_pm_threshold_state(
pm_threshold, 'subObjectInstanceId')
self.assertIsNone(result)
def test_get_pm_threshold_state_with_empty_metadata(self):
pm_threshold = objects.ThresholdV2(
id='pm_threshold_1',
metadata={})
result = pm_threshold_utils.get_pm_threshold_state(
pm_threshold, 'subObjectInstanceId')
self.assertIsNone(result)
def test_get_pm_threshold_state_with_metadata(self):
pm_threshold = objects.ThresholdV2(
id='pm_threshold_1',
metadata={
'thresholdState': [{
'subObjectInstanceId': 'subObjectInstanceId',
'performanceValue': 200,
'metrics': 'metrics',
'crossingDirection': 'crossingDirection'
}]
}
)
result = pm_threshold_utils.get_pm_threshold_state(
pm_threshold, 'subObjectInstanceId')
self.assertEqual(200, result['performanceValue'])
def test_pm_threshold_href(self):
result = pm_threshold_utils.pm_threshold_href(
'pm_threshold_1', 'endpoint')
self.assertEqual('endpoint/vnfpm/v2/thresholds/pm_threshold_1', result)
def test_pm_threshold_links(self):
pm_threshold = objects.ThresholdV2(
id='pm_threshold_1',
objectInstanceId="id_1")
result = pm_threshold_utils.make_pm_threshold_links(
pm_threshold, 'endpoint')
href = result.self.href
self.assertEqual('endpoint/vnfpm/v2/thresholds/pm_threshold_1', href)
@mock.patch.object(objects.base.TackerPersistentObject, 'update')
@mock.patch.object(objects.base.TackerPersistentObject, 'create')
@mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id')
def test_update_threshold_state_data(
self, mock_pms, mock_create, mock_update):
mock_pms.return_value = objects.ThresholdV2(
id='pm_threshold_1')
mock_create.return_value = None
mock_update.return_value = None
pm_threshold_state_1 = {
'thresholdId': 'pm_threshold_1',
'subObjectInstanceId': 'sub_id_1',
'performanceValue': '200.5',
'metrics': 'VCpuUsageMeanVnf.VNF',
'crossingDirection': 'UP'
}
update_threshold_state_data = {
'subObjectInstanceId': pm_threshold_state_1[
'subObjectInstanceId'],
'performanceValue': pm_threshold_state_1['performanceValue'],
'metrics': pm_threshold_state_1['metrics'],
'crossingDirection': pm_threshold_state_1['crossingDirection']
}
pm_threshold_utils.update_threshold_state_data(
self.context,
pm_threshold_state_1['thresholdId'],
update_threshold_state_data)

View File

@ -26,6 +26,7 @@ from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import http_client
from tacker.sol_refactored.common import monitoring_plugin_base as mon_base
from tacker.sol_refactored.common import pm_job_utils
from tacker.sol_refactored.common import pm_threshold_utils
from tacker.sol_refactored.common import prometheus_plugin
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
from tacker.sol_refactored import objects
@ -67,8 +68,6 @@ _body_pm_alert1 = {
},
'startsAt': '2022-06-21T23:47:36.453Z',
'endsAt': '0001-01-01T00:00:00Z',
'generatorURL': 'http://controller147:9090/graph?g0.expr='
'up%7Bjob%3D%22node%22%7D+%3D%3D+0&g0.tab=1',
'fingerprint': '5ef77f1f8a3ecb8d'
}
@ -90,6 +89,44 @@ _body_pm_alert5['labels']['metric'] = 'ByteIncomingVnfIntCp'
_body_pm_alert6 = copy.deepcopy(_body_pm_alert1)
_body_pm_alert6['labels']['metric'] = 'InvalidMetric'
_body_pm_threshold_alert1 = {
'status': 'firing',
'labels': {
'receiver_type': 'tacker',
'function_type': 'vnfpm_threshold',
'threshold_id': '64e46b0e-887a-4691-8d2b-aa3d7b157e2c',
'metric': 'VCpuUsageMeanVnf.'
'25b9b9d0-2461-4109-866e-a7767375415b',
'object_instance_id': '25b9b9d0-2461-4109-866e-a7767375415b'
},
'annotations': {
'value': '510',
},
'startsAt': '2022-06-21T23:47:36.453Z',
'endsAt': '0001-01-01T00:00:00Z',
'fingerprint': '5ef77f1f8a3ecb8d'
}
# function_type mismatch
_body_pm_threshold_alert2 = copy.deepcopy(_body_pm_threshold_alert1)
_body_pm_threshold_alert2['labels']['function_type'] = 'vnffm'
# object_instance_id mismatch
_body_pm_threshold_alert3 = copy.deepcopy(_body_pm_threshold_alert1)
_body_pm_threshold_alert3['labels']['object_instance_id'] = (
'obj_instance_mismatch')
# sub_object_instance_id mismatch
_body_pm_threshold_alert4 = copy.deepcopy(_body_pm_threshold_alert1)
_body_pm_threshold_alert4['labels']['sub_object_instance_id'] = (
'sub_object_mismatch')
_body_pm_threshold_alert5 = copy.deepcopy(_body_pm_threshold_alert1)
_body_pm_threshold_alert5['labels']['metric'] = 'ByteIncomingVnfIntCp'
_body_pm_threshold_alert6 = copy.deepcopy(_body_pm_threshold_alert1)
_body_pm_threshold_alert6['labels']['metric'] = 'InvalidMetric'
_body_pm1 = copy.deepcopy(_body_base)
_body_pm1.update({
'alerts': [
@ -101,6 +138,20 @@ _body_pm2.update({
'alerts': [_body_pm_alert5, _body_pm_alert6]
})
_body_pm_threshold1 = copy.deepcopy(_body_base)
_body_pm_threshold1.update({
'alerts': [
_body_pm_threshold_alert1,
_body_pm_threshold_alert2,
_body_pm_threshold_alert3,
_body_pm_threshold_alert4]
})
_body_pm_threshold2 = copy.deepcopy(_body_base)
_body_pm_threshold2.update({
'alerts': [_body_pm_threshold_alert5, _body_pm_threshold_alert6]
})
_pm_job = {
'id': 'job_id',
'objectType': 'Vnf',
@ -108,7 +159,7 @@ _pm_job = {
'subObjectInstanceIds': [],
'criteria': {
'performanceMetric': [
'VcpuUsageMeanVnf.25b9b9d0-2461-4109-866e-a7767375415b'
'VCpuUsageMeanVnf.25b9b9d0-2461-4109-866e-a7767375415b'
],
'performanceMetricGroup': [
'VirtualisedComputeResource',
@ -154,6 +205,52 @@ _pm_job2['criteria']['performanceMetric'] = ['ByteIncomingVnfIntCp']
_pm_job2['criteria']['performanceMetricGroup'] = [
'VnfInternalCp', 'VnfExternalCp']
_pm_threshold = {
'id': 'threshold_id',
'objectType': 'Vnf',
'objectInstanceId': '25b9b9d0-2461-4109-866e-a7767375415b',
'subObjectInstanceIds': ['vnfc_info1'],
'criteria': {
'performanceMetric':
'VCpuUsageMeanVnf.25b9b9d0-2461-4109-866e-a7767375415b',
'thresholdType': 'SIMPLE',
'simpleThresholdDetails': {
'thresholdValue': 500.5,
'hysteresis': 10.5
}
},
'callbackUri': '',
'metadata': {
'monitoring': {
'monitorName': 'prometheus',
'driverType': 'external',
'targetsInfo': [
{
'prometheusHost':
'prometheusHost',
'prometheusHostPort': '22',
'authInfo': {
'ssh_username': 'ssh_username',
'ssh_password': 'ssh_password'
},
'alertRuleConfigPath':
'alertRuleConfigPath',
'prometheusReloadApiEndpoint':
'prometheusReloadApiEndpoint'
},
{
# invalid access info
'prometheusHost':
'prometheusHost',
}
]
}
}
}
_pm_threshold2 = copy.deepcopy(_pm_threshold)
_pm_threshold2['objectType'] = 'VnfIntCp'
_pm_threshold2['criteria']['performanceMetric'] = 'ByteIncomingVnfIntCp'
_pm_report = {
'id': 'report_id',
'jobId': 'pm_job_id',
@ -221,6 +318,14 @@ _pm_report = {
}]
}
_pm_threshold_state = {
'thresholdId': 'threshold_id',
'subObjectInstanceId': 'sub_id_1',
'metrics': 'VCpuUsageMeanVnf.25b9b9d0-2461-4109-866e-a7767375415b',
'performanceValue': '200.5',
'crossingDirection': 'UP'
}
_pm_report2 = {
'id': 'report_id',
'jobId': 'pm_job_id',
@ -382,10 +487,10 @@ class TestPrometheusPluginPm(base.TestCase):
prometheus_plugin.PrometheusPluginPm)
unload_uuidsentinel()
with freezegun.freeze_time(datetime_test):
self.assertRaises(
sol_ex.PrometheusPluginError,
pp._alert, self.request, body=_body_pm2
)
result = pp._alert(self.request, body=_body_pm2)
self.assertTrue(len(result) == 1)
self.assertEqual(result[0]["performanceMetric"],
'ByteIncomingVnfIntCp')
@mock.patch.object(pm_job_utils, 'get_pm_report')
@mock.patch.object(pm_job_utils, 'get_pm_job')
@ -814,6 +919,479 @@ class TestPrometheusPluginPm(base.TestCase):
)
class TestPrometheusPluginThreshold(base.TestCase):
def setUp(self):
super(TestPrometheusPluginThreshold, self).setUp()
objects.register_all()
self.context = context.get_admin_context()
self.request = mock.Mock()
self.request.context = self.context
prometheus_plugin.PrometheusPluginThreshold._instance = None
def tearDown(self):
super(TestPrometheusPluginThreshold, self).tearDown()
# delete singleton object
prometheus_plugin.PrometheusPluginThreshold._instance = None
def test_constructor_error(self):
self.config_fixture.config(
group='prometheus_plugin', auto_scaling=False)
mon_base.MonitoringPlugin.get_instance(
prometheus_plugin.PrometheusPluginThreshold)
self.assertRaises(
SystemError,
prometheus_plugin.PrometheusPluginThreshold)
def test_constructor_stub(self):
self.config_fixture.config(
group='prometheus_plugin', auto_scaling=False)
pp = mon_base.MonitoringPlugin.get_instance(
prometheus_plugin.PrometheusPluginThreshold)
self.assertIsInstance(pp._instance, mon_base.MonitoringPluginStub)
pp = mon_base.MonitoringPlugin.get_instance(
prometheus_plugin.PrometheusPluginThreshold)
self.assertIsInstance(pp._instance, mon_base.MonitoringPluginStub)
@mock.patch.object(pm_threshold_utils, 'get_pm_threshold_state')
@mock.patch.object(pm_threshold_utils, 'get_pm_threshold')
def test_pm_threshold(self, mock_pm_threshold, mock_pm_threshold_state):
self.config_fixture.config(
group='prometheus_plugin', performance_management=True)
mock_pm_threshold.return_value = (
objects.ThresholdV2.from_dict(_pm_threshold))
mock_pm_threshold_state.return_value = _pm_threshold_state
pp = mon_base.MonitoringPlugin.get_instance(
prometheus_plugin.PrometheusPluginThreshold)
unload_uuidsentinel()
with freezegun.freeze_time(datetime_test):
result = pp._alert(self.request, body=_body_pm_threshold1)
self.assertTrue(len(result) > 0)
self.assertEqual(result[0]['performanceValue'], 510)
@mock.patch.object(pm_threshold_utils, 'get_pm_threshold_state')
@mock.patch.object(pm_threshold_utils, 'get_pm_threshold')
def test_pm_threshold_metrics(
self, mock_pm_threshold, mock_pm_threshold_state):
self.config_fixture.config(
group='prometheus_plugin', performance_management=True)
mock_pm_threshold.return_value = (
objects.ThresholdV2.from_dict(_pm_threshold))
mock_pm_threshold_state.return_value = _pm_threshold_state
pp = mon_base.MonitoringPlugin.get_instance(
prometheus_plugin.PrometheusPluginThreshold)
unload_uuidsentinel()
with freezegun.freeze_time(datetime_test):
result = pp._alert(self.request, body=_body_pm_threshold2)
self.assertTrue(len(result) == 0)
@mock.patch.object(pm_threshold_utils, 'get_pm_threshold_state')
@mock.patch.object(pm_threshold_utils, 'get_pm_threshold')
def test_pm_threshold_with_threshold_state(
self, mock_pm_threshold, mock_pm_threshold_state):
self.config_fixture.config(
group='prometheus_plugin', performance_management=True)
mock_pm_threshold.return_value = (
objects.ThresholdV2.from_dict(_pm_threshold))
mock_pm_threshold_state.return_value = None
pp = mon_base.MonitoringPlugin.get_instance(
prometheus_plugin.PrometheusPluginThreshold)
unload_uuidsentinel()
with freezegun.freeze_time(datetime_test):
result = pp._alert(self.request, body=_body_pm_threshold1)
self.assertTrue(len(result) > 0)
self.assertEqual(result[0]['performanceValue'], 510)
@mock.patch.object(pm_threshold_utils, 'get_pm_threshold_state')
@mock.patch.object(pm_threshold_utils, 'get_pm_threshold')
def test_pm_threshold_set_callback(
self, mock_pm_threshold, mock_pm_threshold_state):
self.config_fixture.config(
group='prometheus_plugin', performance_management=True)
mock_pm_threshold.return_value = (
objects.ThresholdV2.from_dict(_pm_threshold))
mock_pm_threshold_state.return_value = _pm_threshold_state
pp = mon_base.MonitoringPlugin.get_instance(
prometheus_plugin.PrometheusPluginThreshold)
pp.set_callback(None)
unload_uuidsentinel()
with freezegun.freeze_time(datetime_test):
result = pp._alert(self.request, body=_body_pm_threshold1)
self.assertTrue(len(result) > 0)
self.assertEqual(result[0]['performanceValue'], 510)
def test_pm_threshold_error_access_info(self):
self.config_fixture.config(
group='prometheus_plugin', performance_management=True)
pp = mon_base.MonitoringPlugin.get_instance(
prometheus_plugin.PrometheusPluginThreshold)
threshold = copy.deepcopy(_pm_threshold)
del threshold['metadata']
threshold = objects.ThresholdV2.from_dict(threshold)
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.delete_threshold, context=self.context, pm_threshold=threshold
)
threshold2 = copy.deepcopy(_pm_threshold)
threshold2['metadata'] = dict({'monitoring': {}})
threshold2 = objects.ThresholdV2.from_dict(threshold2)
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.delete_threshold, context=self.context, pm_threshold=threshold2
)
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(paramiko.SFTPClient, 'from_transport')
@mock.patch.object(paramiko, 'Transport')
def test_delete_pm_threshold(
self, mock_paramiko, mock_sftp, mock_do_request):
mock_paramiko.return_value = _ParamikoTest()
mock_sftp.return_value = _ParamikoTest()
resp = webob.Response()
resp.status_code = 202
mock_do_request.return_value = resp, {}
self.config_fixture.config(
group='prometheus_plugin', performance_management=True)
pp = mon_base.MonitoringPlugin.get_instance(
prometheus_plugin.PrometheusPluginThreshold)
# normal
threshold = objects.ThresholdV2.from_dict(_pm_threshold)
pp.delete_threshold(context=self.context, pm_threshold=threshold)
# error
resp.status_code = 503
pp.delete_threshold(context=self.context, pm_threshold=threshold)
# paramiko error
resp.status_code = 202
mock_paramiko.return_value = _ParamikoTest(
exp=sol_ex.PrometheusPluginError())
pp.delete_threshold(context=self.context, pm_threshold=threshold)
@mock.patch.object(paramiko, 'SSHClient')
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(paramiko.SFTPClient, 'from_transport')
@mock.patch.object(paramiko, 'Transport')
@mock.patch.object(inst_utils, 'get_inst')
def test_create_pm_threshold(
self, mock_inst, mock_paramiko, mock_sftp,
mock_do_request, mock_sshclient):
mock_paramiko.return_value = _ParamikoTest()
mock_sftp.return_value = _ParamikoTest()
mock_sshclient.return_value = _ParamikoTest()
resp = webob.Response()
resp.status_code = 202
mock_do_request.return_value = resp, {}
mock_inst.return_value = objects.VnfInstanceV2.from_dict(_inst1)
self.config_fixture.config(
group='prometheus_plugin', performance_management=True)
self.config_fixture.config(
group='prometheus_plugin', test_rule_with_promtool=True)
pp = mon_base.MonitoringPlugin.get_instance(
prometheus_plugin.PrometheusPluginThreshold)
# VirtualisedComputeResource
pm_threshold = objects.ThresholdV2.from_dict(_pm_threshold)
rule = pp.create_threshold(
context=self.context, pm_threshold=pm_threshold)
self.assertTrue(len(rule['groups'][0]['rules']) > 0)
# VnfInternalCp
pm_threshold2 = objects.ThresholdV2.from_dict(_pm_threshold2)
rule = pp.create_threshold(
context=self.context, pm_threshold=pm_threshold2)
self.assertTrue(len(rule['groups'][0]['rules']) > 0)
self.assertTrue('interface=' in str(rule))
# namespace
threshold = objects.ThresholdV2.from_dict(_pm_threshold)
rule = pp.create_threshold(
context=self.context, pm_threshold=threshold)
self.assertTrue('namespace="default"' in str(rule))
self.assertFalse('namespace="test"' in str(rule))
mock_inst.return_value = objects.VnfInstanceV2.from_dict(_inst2)
rule = pp.create_threshold(
context=self.context, pm_threshold=threshold)
self.assertFalse('namespace="default"' in str(rule))
self.assertTrue('namespace="test"' in str(rule))
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(paramiko.SFTPClient, 'from_transport')
@mock.patch.object(paramiko, 'Transport')
@mock.patch.object(inst_utils, 'get_inst')
def test_create_pm_threshold_subobj(
self, mock_inst, mock_paramiko, mock_sftp, mock_do_request):
mock_paramiko.return_value = _ParamikoTest()
mock_sftp.return_value = _ParamikoTest()
resp = webob.Response()
resp.status_code = 202
mock_do_request.return_value = resp, {}
inst = objects.VnfInstanceV2.from_dict(_inst1)
mock_inst.return_value = inst
self.config_fixture.config(
group='prometheus_plugin', performance_management=True)
pp = mon_base.MonitoringPlugin.get_instance(
prometheus_plugin.PrometheusPluginThreshold)
# VirtualisedComputeResource
threshold = copy.deepcopy(_pm_threshold)
threshold['subObjectInstanceIds'] = ['vnfc_info1']
threshold = objects.ThresholdV2.from_dict(threshold)
rule = pp.create_threshold(
context=self.context, pm_threshold=threshold)
self.assertTrue(len(rule['groups'][0]['rules']) > 0)
self.assertEqual(
rule['groups'][0]['rules'][0]['labels']['sub_object_instance_id'],
threshold['subObjectInstanceIds'][0])
# VnfInternalCp
threshold = copy.deepcopy(_pm_threshold2)
threshold['subObjectInstanceIds'] = ['test_if0']
threshold = objects.ThresholdV2.from_dict(threshold)
rule = pp.create_threshold(
context=self.context, pm_threshold=threshold)
self.assertTrue(len(rule['groups'][0]['rules']) > 0)
self.assertTrue('interface="test_if0"' in str(rule))
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(paramiko.SFTPClient, 'from_transport')
@mock.patch.object(paramiko, 'Transport')
@mock.patch.object(inst_utils, 'get_inst')
def test_create_pm_threshold_error(
self, mock_inst, mock_paramiko, mock_sftp, mock_do_request):
mock_paramiko.return_value = _ParamikoTest()
mock_sftp.return_value = _ParamikoTest()
resp = webob.Response()
resp.status_code = 202
mock_do_request.return_value = resp, {}
mock_inst.return_value = objects.VnfInstanceV2.from_dict(_inst1)
self.config_fixture.config(
group='prometheus_plugin', performance_management=True)
pp = mon_base.MonitoringPlugin.get_instance(
prometheus_plugin.PrometheusPluginThreshold)
# invalid object type
threshold = copy.deepcopy(_pm_threshold)
threshold['objectType'] = 'invalid_type'
threshold = objects.ThresholdV2.from_dict(threshold)
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.create_threshold, context=self.context, pm_threshold=threshold
)
# invalid performanceMetric.
threshold = copy.deepcopy(_pm_threshold)
threshold['criteria']['performanceMetric'] = (
"invalid performanceMetric")
threshold = objects.ThresholdV2.from_dict(threshold)
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.create_threshold, context=self.context, pm_threshold=threshold
)
# no instantiatedVnfInfo
mock_inst.return_value = objects.VnfInstanceV2.from_dict(_inst_base)
threshold = objects.ThresholdV2.from_dict(_pm_threshold)
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.create_threshold, context=self.context, pm_threshold=threshold
)
# no instantiatedVnfInfo with subObjectInstanceIds
threshold = copy.deepcopy(_pm_threshold2)
threshold['subObjectInstanceIds'] = ['test_if0']
threshold = objects.ThresholdV2.from_dict(threshold)
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.create_threshold, context=self.context, pm_threshold=threshold
)
# no valid computeResource
ins = copy.deepcopy(_inst1)
_ = ins['instantiatedVnfInfo']['vnfcResourceInfo'][0]
_['computeResource'] = {}
mock_inst.return_value = objects.VnfInstanceV2.from_dict(ins)
threshold = objects.ThresholdV2.from_dict(_pm_threshold)
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.create_threshold, context=self.context, pm_threshold=threshold
)
# no vnfcInfo
ins = copy.deepcopy(_inst1)
del ins['instantiatedVnfInfo']['vnfcInfo']
mock_inst.return_value = objects.VnfInstanceV2.from_dict(ins)
threshold = copy.deepcopy(_pm_threshold)
threshold['subObjectInstanceIds'] = ['vnfc_info1']
threshold = objects.ThresholdV2.from_dict(threshold)
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.create_threshold, context=self.context, pm_threshold=threshold
)
# vnfcInfo mismatch
ins = copy.deepcopy(_inst1)
ins['instantiatedVnfInfo']['vnfcInfo'][0]['vnfcResourceInfoId'] = 'ng'
mock_inst.return_value = objects.VnfInstanceV2.from_dict(ins)
threshold = copy.deepcopy(_pm_threshold)
threshold['subObjectInstanceIds'] = ['vnfc_info1']
threshold = objects.ThresholdV2.from_dict(threshold)
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.create_threshold, context=self.context, pm_threshold=threshold
)
# vnfcInfo mismatch
ins = copy.deepcopy(_inst1)
del ins['instantiatedVnfInfo']['vnfcInfo'][0]['vnfcResourceInfoId']
mock_inst.return_value = objects.VnfInstanceV2.from_dict(ins)
threshold = copy.deepcopy(_pm_threshold)
threshold['subObjectInstanceIds'] = ['vnfc_info1']
threshold = objects.ThresholdV2.from_dict(threshold)
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.create_threshold, context=self.context, pm_threshold=threshold
)
# resourcename mismatch: VirtualisedComputeResource
ins = copy.deepcopy(_inst1)
_ = ins['instantiatedVnfInfo']['vnfcResourceInfo']
del _[0]['computeResource']['resourceId']
mock_inst.return_value = objects.VnfInstanceV2.from_dict(ins)
threshold = copy.deepcopy(_pm_threshold)
threshold['subObjectInstanceIds'] = ['vnfc_info1']
threshold = objects.ThresholdV2.from_dict(threshold)
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.create_threshold, context=self.context, pm_threshold=threshold
)
ins = copy.deepcopy(_inst1)
_ = ins['instantiatedVnfInfo']['vnfcResourceInfo'][0]
_['computeResource']['vimLevelResourceType'] = 'ng'
mock_inst.return_value = objects.VnfInstanceV2.from_dict(ins)
threshold = copy.deepcopy(_pm_threshold2)
threshold['subObjectInstanceIds'] = ['test_if0']
threshold = objects.ThresholdV2.from_dict(threshold)
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.create_threshold, context=self.context, pm_threshold=threshold
)
@mock.patch.object(paramiko, 'SSHClient')
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(paramiko.SFTPClient, 'from_transport')
@mock.patch.object(paramiko, 'Transport')
@mock.patch.object(inst_utils, 'get_inst')
def test_create_pm_threshold_error2(
self, mock_inst, mock_paramiko,
mock_sftp, mock_do_request, mock_sshclient):
mock_paramiko.return_value = _ParamikoTest()
mock_sftp.return_value = _ParamikoTest()
exp = ValueError("test_create_pm_threshold_error2")
mock_sshclient.return_value = _ParamikoTest(
exp=exp, recv_exit_status_value=1)
resp = webob.Response()
resp.status_code = 202
mock_do_request.return_value = resp, {}
mock_inst.return_value = objects.VnfInstanceV2.from_dict(_inst1)
self.config_fixture.config(
group='prometheus_plugin', performance_management=True)
self.config_fixture.config(
group='prometheus_plugin', test_rule_with_promtool=True)
pp = mon_base.MonitoringPlugin.get_instance(
prometheus_plugin.PrometheusPluginThreshold)
# upload error
threshold = objects.ThresholdV2.from_dict(_pm_threshold)
self.assertRaises(
ValueError,
pp.create_threshold, context=self.context, pm_threshold=threshold)
exp = sol_ex.PrometheusPluginError("test_create_pm_threshold_error2")
mock_paramiko.return_value = _ParamikoTest(exp=exp)
mock_sshclient.return_value = _ParamikoTest(
exp=exp, recv_exit_status_value=1)
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.create_threshold, context=self.context, pm_threshold=threshold)
@mock.patch.object(utils, 'find_config_file')
@mock.patch.object(paramiko, 'SSHClient')
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(paramiko.SFTPClient, 'from_transport')
@mock.patch.object(paramiko, 'Transport')
@mock.patch.object(inst_utils, 'get_inst')
def test_promql(
self, mock_inst, mock_paramiko, mock_sftp, mock_do_request,
mock_sshclient, mock_utils):
mock_paramiko.return_value = _ParamikoTest()
mock_sftp.return_value = _ParamikoTest()
mock_sshclient.return_value = _ParamikoTest(recv_exit_status_value=1)
resp = webob.Response()
resp.status_code = 202
mock_do_request.return_value = resp, {}
mock_inst.return_value = objects.VnfInstanceV2.from_dict(_inst1)
self.config_fixture.config(
group='prometheus_plugin', performance_management=True)
self.config_fixture.config(
group='prometheus_plugin', test_rule_with_promtool=True)
pp = mon_base.MonitoringPlugin.get_instance(
prometheus_plugin.PrometheusPluginThreshold)
# no config file
mock_utils.return_value = None
threshold = objects.ThresholdV2.from_dict(_pm_threshold)
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.create_threshold, context=self.context, pm_threshold=threshold
)
# Type check
mock_utils.return_value = None
pp.make_rule("Threshold", "id", "id", "id", "metric", "exp")
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.make_rule, "TypeError", "id", "id", "id", "metric", "exp"
)
@mock.patch.object(paramiko, 'SSHClient')
@mock.patch.object(http_client.HttpClient, 'do_request')
@mock.patch.object(paramiko.SFTPClient, 'from_transport')
@mock.patch.object(paramiko, 'Transport')
@mock.patch.object(inst_utils, 'get_inst')
def test_promql2(
self, mock_inst, mock_paramiko, mock_sftp, mock_do_request,
mock_sshclient):
mock_paramiko.return_value = _ParamikoTest()
mock_sftp.return_value = _ParamikoTest()
mock_sshclient.return_value = _ParamikoTest(recv_exit_status_value=1)
resp = webob.Response()
resp.status_code = 202
mock_do_request.return_value = resp, {}
mock_inst.return_value = objects.VnfInstanceV2.from_dict(_inst1)
self.config_fixture.config(
group='prometheus_plugin', performance_management=True)
self.config_fixture.config(
group='prometheus_plugin', test_rule_with_promtool=True)
pp = mon_base.MonitoringPlugin.get_instance(
prometheus_plugin.PrometheusPluginThreshold)
# no config file
threshold = objects.ThresholdV2.from_dict(_pm_threshold)
self.assertRaises(
sol_ex.PrometheusPluginError,
pp.create_threshold, context=self.context, pm_threshold=threshold
)
class TestPrometheusPluginFm(base.TestCase):
def setUp(self):
super(TestPrometheusPluginFm, self).setUp()

View File

@ -12,16 +12,13 @@
# 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 requests
from unittest import mock
from oslo_config import cfg
from oslo_utils import uuidutils
from unittest import mock
from tacker import context
from tacker.sol_refactored.api import api_version
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import http_client
from tacker.sol_refactored.common import subscription_utils as subsc_utils
from tacker.sol_refactored import objects
from tacker.tests import base
@ -56,179 +53,6 @@ class TestSubscriptionUtils(base.BaseTestCase):
result = subsc_utils.get_subsc_all(context)
self.assertEqual('subsc-1', result[0].id)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_send_notification(self, mock_resp):
subsc_no_auth = objects.LccnSubscriptionV2(
id='sub-1', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback')
notif_data_no_auth = objects.VnfLcmOperationOccurrenceNotificationV2(
id=uuidutils.generate_uuid()
)
resp_no_auth = requests.Response()
resp_no_auth.status_code = 204
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
subsc_utils.send_notification(subsc_no_auth, notif_data_no_auth)
subsc_basic_auth = objects.LccnSubscriptionV2(
id='sub-2', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback',
authentication=objects.SubscriptionAuthentication(
authType=['BASIC'],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test', password='test')))
# execute basic_auth
subsc_utils.send_notification(subsc_basic_auth, notif_data_no_auth)
subsc_oauth2 = objects.LccnSubscriptionV2(
id='sub-3', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback',
authentication=objects.SubscriptionAuthentication(
authType=['OAUTH2_CLIENT_CREDENTIALS'],
paramsOauth2ClientCredentials=(
objects.SubscriptionAuthentication_ParamsOauth2(
clientId='test', clientPassword='test',
tokenEndpoint='http://127.0.0.1/token'))))
# execute oauth2
subsc_utils.send_notification(subsc_oauth2, notif_data_no_auth)
subsc_oauth2_mtls = objects.LccnSubscriptionV2(
id='sub-4', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback',
authentication=objects.SubscriptionAuthentication(
authType=["OAUTH2_CLIENT_CERT"],
paramsOauth2ClientCert=(
objects.SubscriptionAuthentication_ParamsOauth2ClientCert(
clientId='test',
certificateRef=objects.
ParamsOauth2ClientCert_CertificateRef(
type='x5t#256',
value='03c6e188d1fe5d3da8c9bc9a8dc531a2'
'b3ecf812b03aede9bec7ba1b410b6b64'
),
tokenEndpoint='http://127.0.0.1/token'))))
# execute oauth2 mtls
subsc_utils.send_notification(subsc_oauth2_mtls, notif_data_no_auth)
cfg.CONF.set_override("notification_verify_cert", "True",
group="v2_vnfm")
subsc_no_auth = objects.LccnSubscriptionV2(
id='sub-5', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback')
notif_data_no_auth = objects.VnfLcmOperationOccurrenceNotificationV2(
id=uuidutils.generate_uuid()
)
resp_no_auth = requests.Response()
resp_no_auth.status_code = 204
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
subsc_utils.send_notification(subsc_no_auth, notif_data_no_auth)
subsc_basic_auth = objects.LccnSubscriptionV2(
id='sub-6', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback',
authentication=objects.SubscriptionAuthentication(
authType=['BASIC'],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test', password='test')))
# execute basic_auth
subsc_utils.send_notification(subsc_basic_auth, notif_data_no_auth)
subsc_oauth2 = objects.LccnSubscriptionV2(
id='sub-7', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback',
authentication=objects.SubscriptionAuthentication(
authType=['OAUTH2_CLIENT_CREDENTIALS'],
paramsOauth2ClientCredentials=(
objects.SubscriptionAuthentication_ParamsOauth2(
clientId='test', clientPassword='test',
tokenEndpoint='http://127.0.0.1/token'))))
# execute oauth2
subsc_utils.send_notification(subsc_oauth2, notif_data_no_auth)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_send_notification_error_code(self, mock_resp):
subsc_no_auth = objects.LccnSubscriptionV2(
id='sub-1', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback')
notif_data_no_auth = objects.VnfLcmOperationOccurrenceNotificationV2(
id=uuidutils.generate_uuid()
)
resp_no_auth = requests.Response()
resp_no_auth.status_code = 200
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
subsc_utils.send_notification(subsc_no_auth, notif_data_no_auth)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_send_notification_error(self, mock_resp):
subsc_no_auth = objects.LccnSubscriptionV2(
id='sub-1', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback')
notif_data_no_auth = objects.VnfLcmOperationOccurrenceNotificationV2(
id=uuidutils.generate_uuid()
)
resp_no_auth = Exception()
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
subsc_utils.send_notification(subsc_no_auth, notif_data_no_auth)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_test_notification(self, mock_resp):
subsc_no_auth = objects.LccnSubscriptionV2(
id='sub-1', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback')
resp_no_auth = requests.Response()
resp_no_auth.status_code = 204
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
subsc_utils.test_notification(subsc_no_auth)
@mock.patch.object(http_client.HttpClient, 'do_request')
def test_test_notification_error_code(self, mock_resp):
subsc_no_auth = objects.LccnSubscriptionV2(
id='sub-1', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback')
resp_no_auth = requests.Response()
resp_no_auth.status_code = 200
mock_resp.return_value = (resp_no_auth, None)
# execute no_auth
self.assertRaises(sol_ex.TestNotificationFailed,
subsc_utils.test_notification, subsc_no_auth)
class mock_session():
def request(url, method, raise_exc=False, **kwargs):
resp = requests.Response()
resp.status_code = 400
resp.headers['Content-Type'] = 'application/zip'
return resp
@mock.patch.object(http_client.HttpClient, '_decode_body')
@mock.patch.object(http_client.NoAuthHandle, 'get_session')
def test_test_notification_error(self, mock_session, mock_decode_body):
subsc_no_auth = objects.LccnSubscriptionV2(
id='sub-1', verbosity='SHORT',
callbackUri='http://127.0.0.1/callback')
mock_session.return_value = self.mock_session
mock_decode_body.return_value = None
self.assertRaises(sol_ex.TestNotificationFailed,
subsc_utils.test_notification, subsc_no_auth)
def test_match_version(self):
inst = objects.VnfInstanceV2(
id='test-instance', vnfSoftwareVersion='1.1.1', vnfdVersion='1.2')

View File

@ -18,6 +18,7 @@ from unittest import mock
from oslo_utils import uuidutils
from tacker import context
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import fm_subscription_utils
from tacker.sol_refactored.common import vnf_instance_utils as inst_utils
from tacker.sol_refactored.conductor import vnffm_driver_v1
@ -34,7 +35,7 @@ class TestVnffmDriverV1(base.BaseTestCase):
self.driver = vnffm_driver_v1.VnfFmDriverV1()
self.context = context.get_admin_context()
@mock.patch.object(fm_subscription_utils, 'send_notification')
@mock.patch.object(common_script_utils, 'send_notification')
@mock.patch.object(fm_subscription_utils, 'get_alarm_subscs')
@mock.patch.object(objects.base.TackerPersistentObject, 'create')
@mock.patch.object(objects.base.TackerPersistentObject, 'update')

View File

@ -18,7 +18,9 @@ from unittest import mock
from tacker.tests import base
from tacker import context
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import pm_job_utils
from tacker.sol_refactored.common import pm_threshold_utils
from tacker.sol_refactored.conductor.vnfpm_driver_v2 import VnfPmDriverV2
from tacker.sol_refactored.nfvo.nfvo_client import NfvoClient
from tacker.sol_refactored import objects
@ -70,6 +72,40 @@ class TestVnfPmDriverV2(base.BaseTestCase):
VnfPmDriverV2().store_job_info(context=self.context,
report=report)
@mock.patch.object(common_script_utils, 'send_notification')
@mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id')
@mock.patch.object(pm_threshold_utils,
'update_threshold_state_data')
def test_store_threshold_info(self,
mock_update_threshold_state_data,
mock_pm, mock_send):
threshold_states = [{
'thresholdId': 'pm_threshold_1',
'subObjectInstanceId': "sub_id_1",
'performanceValue': '200.5',
'metrics': 'VCpuUsageMeanVnf.VNF',
'crossingDirection': 'UP'
}]
mock_pm.return_value = objects.ThresholdV2(
id='pm_threshold_1',
objectType='Vnf',
objectInstanceId='id_1',
callbackUri='http://127.0.0.1/callback',
authentication=objects.SubscriptionAuthentication(
authType=["OAUTH2_CLIENT_CREDENTIALS"],
paramsOauth2ClientCredentials=(
objects.SubscriptionAuthentication_ParamsOauth2(
clientId='test',
clientPassword='test',
tokenEndpoint='http://127.0.0.1/token'
))
)
)
mock_send.return_value = None
mock_update_threshold_state_data.return_value = threshold_states[0]
VnfPmDriverV2().store_threshold_info(context=self.context,
threshold_states=threshold_states)
@mock.patch.object(objects.base.TackerPersistentObject, 'create')
def test_store_report(self, mock_create):
mock_create.return_value = None

View File

@ -287,9 +287,9 @@ datetime_test = datetime.datetime.fromisoformat(
'2022-06-22T01:23:45.678Z'.replace('Z', '+00:00'))
class TestPrometheusPluginPm(base.TestCase):
class TestPrometheusPluginPmEvent(base.TestCase):
def setUp(self):
super(TestPrometheusPluginPm, self).setUp()
super(TestPrometheusPluginPmEvent, self).setUp()
objects.register_all()
self.context = context.get_admin_context()
self.request = mock.Mock()
@ -298,7 +298,7 @@ class TestPrometheusPluginPm(base.TestCase):
plugin.PrometheusPluginPm._instance = None
def tearDown(self):
super(TestPrometheusPluginPm, self).tearDown()
super(TestPrometheusPluginPmEvent, self).tearDown()
# delete singleton object
plugin.PrometheusPluginPm._instance = None
@ -316,6 +316,35 @@ class TestPrometheusPluginPm(base.TestCase):
self.assertEqual(204, result.status)
class TestPrometheusPluginPmThreshold(base.TestCase):
def setUp(self):
super(TestPrometheusPluginPmThreshold, self).setUp()
objects.register_all()
self.context = context.get_admin_context()
self.request = mock.Mock()
self.request.context = self.context
self.controller = prometheus_plugin_controller.PmThresholdController()
plugin.PrometheusPluginThreshold._instance = None
def tearDown(self):
super(TestPrometheusPluginPmThreshold, self).tearDown()
# delete singleton object
plugin.PrometheusPluginThreshold._instance = None
def test_pm_threshold_config_false(self):
self.config_fixture.config(
group='prometheus_plugin', performance_management=False)
self.assertRaises(
sol_ex.PrometheusPluginNotEnabled,
self.controller.pm_threshold, self.request, {})
def test_pm_exception(self):
self.config_fixture.config(
group='prometheus_plugin', performance_management=True)
result = self.controller.pm_threshold(self.request, {})
self.assertEqual(204, result.status)
class TestPrometheusPluginFm(base.TestCase):
def setUp(self):
super(TestPrometheusPluginFm, self).setUp()

View File

@ -19,6 +19,7 @@ from unittest import mock
from tacker import context
from tacker.sol_refactored.api import api_version
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import fm_alarm_utils as alarm_utils
from tacker.sol_refactored.common import fm_subscription_utils as subsc_utils
@ -109,7 +110,7 @@ class TestVnffmV1(base.BaseTestCase):
request=self.request, id=SAMPLE_ALARM_ID, body=body)
@mock.patch.object(objects.base.TackerPersistentObject, 'create')
@mock.patch.object(subsc_utils, 'test_notification')
@mock.patch.object(common_script_utils, 'test_notification')
def test_subscription_create(self, mock_test, mock_create):
body_1 = {
"callbackUri": "http://127.0.0.1:6789/notification",

View File

@ -20,6 +20,7 @@ from oslo_utils import uuidutils
from tacker import context
from tacker.sol_refactored.api import api_version
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import lcm_op_occ_utils as lcmocc_utils
@ -575,7 +576,7 @@ class TestVnflcmV2(db_base.SqlTestCase):
self.assertEqual("paramsOauth2ClientCert must be specified.",
ex.detail)
@mock.patch.object(subsc_utils, 'test_notification')
@mock.patch.object(common_script_utils, 'test_notification')
def test_subscription_create_201(self, mock_test):
body_1 = {
"callbackUri": "http://127.0.0.1:6789/notification",

View File

@ -18,9 +18,12 @@ from unittest import mock
from tacker import context
from tacker.sol_refactored.api import api_version
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import exceptions as sol_ex
from tacker.sol_refactored.common import pm_job_utils
from tacker.sol_refactored.common.prometheus_plugin import (
PrometheusPluginThreshold)
from tacker.sol_refactored.controller.vnflcm_view import BaseViewBuilder
from tacker.sol_refactored.controller.vnflcm_view import Pager
from tacker.sol_refactored.controller import vnfpm_v2
@ -244,7 +247,7 @@ class TestVnfpmV2(base.BaseTestCase):
self.controller.create, request=self.request, body=body)
@mock.patch.object(objects.base.TackerPersistentObject, 'create')
@mock.patch.object(pm_job_utils, 'test_notification')
@mock.patch.object(common_script_utils, 'test_notification')
@mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id')
def test_create_201(self, mock_inst, mock_notifi, mock_create):
mock_inst.return_value = objects.VnfInstanceV2(
@ -332,7 +335,7 @@ class TestVnfpmV2(base.BaseTestCase):
@mock.patch.object(objects.base.TackerPersistentObject, 'update')
@mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id')
@mock.patch.object(pm_job_utils, 'test_notification')
@mock.patch.object(common_script_utils, 'test_notification')
def test_update(self, mock_notifi, mock_pm, mock_update):
mock_notifi.return_value = None
mock_pm.return_value = objects.PmJobV2(id='pm_job_1')
@ -386,3 +389,235 @@ class TestVnfpmV2(base.BaseTestCase):
def test_supported_api_version(self):
result = self.controller.supported_api_versions('create')
self.assertEqual(['2.1.0'], result)
# 'performanceMetric' doesn't match objectType
def test_create_pm_threshold_error_1(self):
_ThresholdCriteria_V2 = {
'performanceMetric': 'ByteIncomingVnfIntCp.VNF',
'thresholdType': 'SIMPLE',
'simpleThresholdDetails': {
'thresholdValue': 500.5,
'hysteresis': 10.5
}
}
_SubscriptionAuthentication = {
'authType': ['BASIC'],
'paramsBasic': {
'userName': 'test_name',
'password': 'test_pwd'
}
}
body = {
"objectType": "Vnf",
"objectInstanceId": "id_1",
"subObjectInstanceIds": ["sub_id_1", "sub_id_2"],
"criteria": _ThresholdCriteria_V2,
"callbackUri": 'callbackuri',
"authentication": _SubscriptionAuthentication,
'metadata': {"metadata": "example"}
}
self.assertRaises(sol_ex.PMThresholdInvalidRequest,
self.controller.create_threshold,
request=self.request, body=body)
# 'simpleThresholdDetails' is not assigned
def test_create_pm_threshold_error_2(self):
_ThresholdCriteria_V2 = {
'performanceMetric': 'VCpuUsageMeanVnf.VNF',
'thresholdType': 'SIMPLE',
}
_SubscriptionAuthentication = {
'authType': ['BASIC'],
'paramsBasic': {
'userName': 'test_name',
'password': 'test_pwd'
}
}
body = {
"objectType": "Vnf",
"objectInstanceId": "id_1",
"subObjectInstanceIds": ["sub_id_1", "sub_id_2"],
"criteria": _ThresholdCriteria_V2,
"callbackUri": 'callbackuri',
"authentication": _SubscriptionAuthentication,
'metadata': {"metadata": "example"}
}
self.assertRaises(sol_ex.PMThresholdInvalidRequest,
self.controller.create_threshold,
request=self.request, body=body)
# vnf instance status: "NOT_INSTANTIATED"
@mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id')
def test_create_pm_threshold_error_5(self, mock_inst):
mock_inst.return_value = objects.VnfInstanceV2(
id='dummy-vnfInstanceId-1', vnfdId='dummy-vnfdId-1',
vnfProvider='dummy-vnfProvider-1',
instantiationState='NOT_INSTANTIATED',
vnfProductName='dummy-vnfProductName-1-1',
vnfSoftwareVersion='1.0', vnfdVersion='1.0',
vnfInstanceName='dummy-vnfInstanceName-1')
_ThresholdCriteria_V2 = {
'performanceMetric': 'VCpuUsageMeanVnf.VNF',
'thresholdType': 'SIMPLE',
'simpleThresholdDetails': {
'thresholdValue': 100.5,
'hysteresis': 10.5
}
}
_SubscriptionAuthentication = {
'authType': ['BASIC'],
'paramsBasic': {
'userName': 'test_name',
'password': 'test_pwd'
}
}
body = {
"objectType": "Vnf",
"objectInstanceId": "id_1",
"subObjectInstanceIds": ["sub_id_1", "sub_id_2"],
"criteria": _ThresholdCriteria_V2,
"callbackUri": 'callbackuri',
"authentication": _SubscriptionAuthentication,
'metadata': {"metadata": "example"}
}
self.assertRaises(sol_ex.VnfInstanceIsNotInstantiated,
self.controller.create_threshold,
request=self.request, body=body)
@mock.patch.object(objects.base.TackerPersistentObject, 'create')
@mock.patch.object(PrometheusPluginThreshold, 'create_threshold')
@mock.patch.object(common_script_utils, 'test_notification')
@mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id')
def test_create_pm_threshold(
self, mock_inst, mock_notifi, mock_create_threshold, mock_create):
mock_inst.return_value = objects.VnfInstanceV2(
id='dummy-vnfInstanceId-1', vnfdId='dummy-vnfdId-1',
vnfProvider='dummy-vnfProvider-1',
instantiationState='INSTANTIATED',
vnfProductName='dummy-vnfProductName-1-1',
vnfSoftwareVersion='1.0', vnfdVersion='1.0',
vnfInstanceName='dummy-vnfInstanceName-1')
mock_notifi.return_value = None
mock_create_threshold.return_value = None
mock_create.return_value = None
_ThresholdCriteria_V2 = {
'performanceMetric': 'VCpuUsageMeanVnf.VNF',
'thresholdType': 'SIMPLE',
'simpleThresholdDetails': {
'thresholdValue': 100.5,
'hysteresis': 10.5
}
}
_SubscriptionAuthentication = {
'authType': ['BASIC'],
'paramsBasic': {
'userName': 'test_name',
'password': 'test_pwd'
}
}
body = {
"objectType": "Vnf",
"objectInstanceId": "id_1",
"subObjectInstanceIds": ["sub_id_1", "sub_id_2"],
"criteria": _ThresholdCriteria_V2,
"callbackUri": 'callbackuri',
"authentication": _SubscriptionAuthentication,
'metadata': {"metadata": "example"}
}
result = self.controller.create_threshold(
request=self.request, body=body)
self.assertEqual(201, result.status)
@mock.patch.object(Pager, 'get_link')
@mock.patch.object(BaseViewBuilder, 'detail_list')
@mock.patch.object(objects.base.TackerPersistentObject, 'get_all')
@mock.patch.object(vnfpm_view.PmThresholdViewBuilder, 'parse_pager')
@mock.patch.object(vnfpm_view.PmThresholdViewBuilder, 'parse_filter')
@mock.patch.object(vnfpm_view.PmThresholdViewBuilder, 'parse_selector')
def test_pm_threshold_index(self, mock_parse_selector, mock_parse_filter,
mock_parse_pager, mock_pm, mock_detail_list,
mock_get_link):
mock_parse_selector.return_value = 'selector'
mock_parse_filter.return_value = 'filter'
request = requests.Request()
request.GET = {
'filter': 'threshold', 'nextpage_opaque_marker': 'marker'}
request.url = 'url'
page_size = CONF.v2_vnfm.vnfpm_pmthreshold_page_size
pager = Pager(request.GET.get('nextpage_opaque_marker'),
request.url,
page_size)
mock_parse_pager.return_value = pager
mock_pm.return_value = [objects.ThresholdV2(id='pm_threshold_1')]
mock_detail_list.return_value = 1
mock_get_link.return_value = 'url'
result = self.controller.index_threshold(self.request)
self.assertEqual(200, result.status)
@mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id')
def test_pm_threshold_show(self, mock_pm):
mock_pm.return_value = objects.ThresholdV2(
id='pm_threshold_1',
objectInstanceId="id_1",
authentication=objects.SubscriptionAuthentication(
authType=["BASIC"],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test',
password='test'
),
)
)
result = self.controller.show_threshold(self.request, 'pm_threshold_1')
self.assertEqual(200, result.status)
@mock.patch.object(objects.base.TackerPersistentObject, 'update')
@mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id')
@mock.patch.object(common_script_utils, 'test_notification')
def test_pm_threshold_update(self, mock_notifi, mock_pm, mock_update):
mock_notifi.return_value = None
mock_pm.return_value = objects.ThresholdV2(
id='pm_threshold_1',
objectInstanceId="id_1",
callbackUri='http://127.0.0.1/callback',
authentication=objects.SubscriptionAuthentication(
authType=["BASIC"],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test',
password='test'
),
)
)
mock_update.return_value = None
_SubscriptionAuthentication = {
'authType': ['BASIC'],
'paramsBasic': {
'userName': 'test_name',
'password': 'test_pwd'
}
}
body = {
'callbackUri': 'callbackuri_update',
'authentication': _SubscriptionAuthentication
}
result = self.controller.update_threshold(
request=self.request, thresholdId='id',
body=body)
self.assertEqual(200, result.status)
@mock.patch.object(PrometheusPluginThreshold, 'create_threshold')
@mock.patch.object(objects.base.TackerPersistentObject, 'get_by_id')
def test_pm_threshold_delete(
self, mock_pm, mock_create_threshold):
mock_pm.return_value = objects.ThresholdV2(id='pm_threshold_1')
mock_create_threshold.return_value = None
result = self.controller.delete_threshold(
self.request, 'pm_threshold_1')
self.assertEqual(204, result.status)

View File

@ -20,6 +20,7 @@ from unittest import mock
from tacker.sol_refactored.common import config
from tacker.sol_refactored.controller.vnflcm_view import BaseViewBuilder
from tacker.sol_refactored.controller.vnfpm_view import PmJobViewBuilder
from tacker.sol_refactored.controller.vnfpm_view import PmThresholdViewBuilder
from tacker.sol_refactored import objects
@ -71,3 +72,44 @@ class TestPmJobViewBuilder(base.BaseTestCase):
result = PmJobViewBuilder(self.endpoint).detail_list(
'pm_jobs', 'filters', 'selector', 'pager')
self.assertEqual(1, result)
class TestPmThresholdViewBuilder(base.BaseTestCase):
def setUp(self):
super(TestPmThresholdViewBuilder, self).setUp()
objects.register_all()
self.context = context.get_admin_context()
self.request = mock.Mock()
self.request.context = self.context
self.endpoint = CONF.v2_vnfm.endpoint
@mock.patch.object(BaseViewBuilder, 'parse_filter')
def test_parse_filter(self, mock_parse_filter):
mock_parse_filter.return_value = 1
result = PmThresholdViewBuilder(
self.endpoint).parse_filter('filter_param')
self.assertEqual(1, result)
@mock.patch.object(BaseViewBuilder, 'parse_pager')
def test_parse_pager(self, mock_parse_pager):
mock_parse_pager.return_value = 1
page_size = CONF.v2_vnfm.vnfpm_pmthreshold_page_size
result = PmThresholdViewBuilder(self.endpoint).parse_pager(
self.request, page_size)
self.assertEqual(1, result)
def test_detail(self):
pm_threshold = objects.ThresholdV2(
id='pm_threshold_1',
objectInstanceId='id_1',
authentication=objects.SubscriptionAuthentication(
authType=["BASIC"],
paramsBasic=objects.SubscriptionAuthentication_ParamsBasic(
userName='test',
password='test'
),
)
)
result = PmThresholdViewBuilder(self.endpoint).detail(pm_threshold)
self.assertEqual('pm_threshold_1', result.get('id'))

View File

@ -23,10 +23,10 @@ from oslo_utils import uuidutils
from tacker import context
from tacker.sol_refactored.api import api_version
from tacker.sol_refactored.common import common_script_utils
from tacker.sol_refactored.common import config
from tacker.sol_refactored.common import http_client
from tacker.sol_refactored.common import pm_job_utils
from tacker.sol_refactored.common import subscription_utils as subsc_utils
from tacker.sol_refactored.common import vnfd_utils
from tacker.sol_refactored.nfvo import local_nfvo
from tacker.sol_refactored.nfvo import nfvo_client
@ -465,7 +465,7 @@ class TestNfvoClient(base.BaseTestCase):
'vimAssets']['softwareImages'][0]['vimSoftwareImageId'])
@mock.patch.object(objects.base.TackerPersistentObject, 'get_all')
@mock.patch.object(subsc_utils, 'send_notification')
@mock.patch.object(common_script_utils, 'send_notification')
@mock.patch.object(local_nfvo.LocalNfvo, 'recv_inst_create_notification')
def test_send_inst_create_notification(
self, mock_recv, mock_send, mock_subscs):
@ -479,7 +479,7 @@ class TestNfvoClient(base.BaseTestCase):
self.assertEqual(1, mock_send.call_count)
@mock.patch.object(objects.base.TackerPersistentObject, 'get_all')
@mock.patch.object(subsc_utils, 'send_notification')
@mock.patch.object(common_script_utils, 'send_notification')
@mock.patch.object(local_nfvo.LocalNfvo, 'recv_inst_delete_notification')
def test_send_inst_delete_notification(
self, mock_recv, mock_send, mock_subscs):
@ -493,7 +493,7 @@ class TestNfvoClient(base.BaseTestCase):
self.assertEqual(1, mock_send.call_count)
@mock.patch.object(objects.base.TackerPersistentObject, 'get_all')
@mock.patch.object(subsc_utils, 'send_notification')
@mock.patch.object(common_script_utils, 'send_notification')
@mock.patch.object(local_nfvo.LocalNfvo, 'recv_lcmocc_notification')
def test_send_lcmocc_notification(self, mock_recv, mock_send, mock_subscs):
inst = objects.VnfInstanceV2(id='test-instance')
@ -505,7 +505,7 @@ class TestNfvoClient(base.BaseTestCase):
self.assertEqual(1, mock_recv.call_count)
self.assertEqual(1, mock_send.call_count)
@mock.patch.object(pm_job_utils, 'send_notification')
@mock.patch.object(common_script_utils, 'send_notification')
@mock.patch.object(pm_job_utils, 'make_pm_notif_data')
def test_send_pm_job_notification(self, mock_notif, mock_send):
mock_notif.return_value = 'mock_notif'