From c297b4300d1943a545950dd0a606b0026a7d5089 Mon Sep 17 00:00:00 2001 From: Koji Shimizu Date: Wed, 19 Oct 2022 00:07:19 +0000 Subject: [PATCH] Improve plugin flexibility The monitoring plugin such as prometheus plugin or fault notification have been modified to be replaceable according to the configuration. Therefore, operators can use their original monitoring functions without changing tacker source code. Implements: blueprint support-auto-lcm Implements: blueprint support-autoheal-queue Change-Id: Ifcdf12c4be2d307c78b38703fe6db4e5aac236ea --- .../fault_notification_use_case_guide.rst | 28 +++++++++- .../user/prometheus_plugin_use_case_guide.rst | 34 +++++++++++ tacker/sol_refactored/common/config.py | 52 +++++++++++++++-- tacker/sol_refactored/common/exceptions.py | 5 ++ .../common/monitoring_plugin_base.py | 31 ++++------ .../common/prometheus_plugin.py | 4 +- .../prometheus_plugin_controller.py | 12 +++- .../controller/server_notification.py | 4 +- tacker/sol_refactored/controller/vnfpm_v2.py | 4 +- .../sol_v2/test_server_notification.py | 2 - .../common/test_prometheus_plugin.py | 2 +- .../conductor/test_prometheus_plugin.py | 2 +- .../controller/test_prometheus_plugin.py | 2 +- .../controller/test_server_notification.py | 56 ++++++++++++++++++- 14 files changed, 197 insertions(+), 41 deletions(-) diff --git a/doc/source/user/fault_notification_use_case_guide.rst b/doc/source/user/fault_notification_use_case_guide.rst index 26f367bf3..26164bf7d 100644 --- a/doc/source/user/fault_notification_use_case_guide.rst +++ b/doc/source/user/fault_notification_use_case_guide.rst @@ -118,17 +118,17 @@ The ``additionalParams`` must be set when using FaultNotification. - Cardinality - Description * - additionalParams - - 0..1 - KeyValuePairs (inlined) + - 0..1 - Additional input parameters for the instantiation process, specific to the VNF being instantiated. * - >ServerNotifierUri - - 1 - String + - 1 - Base Uri for ServerNotifier. * - >ServerNotifierFaultID - - 1..N - String + - 1..N - List of string that indicates which type of alarms to detect. The value of ``ServerNotifierUri`` and ``ServerNotifierFaultID`` are stored @@ -162,6 +162,28 @@ whether AutoHealing should be performed. In case of performing AutoHealing, VMs are deleted and created via Heat. The client is no need to handle healing. +Using Vendor Specific Plugin +---------------------------- + +ServerNotification plugin can be replaced with a vendor specific function. +To replace a plugin, change the configurations below. +The replaced class must be a subclass of +tacker.sol_refactored.common.monitoring_plugin_base.MonitoringPlugin. + +.. list-table:: + :header-rows: 1 + :widths: 40 40 40 + + * - Configuration + - Default + - Description + * - ``CONF.server_notification.server_notification_package`` + - tacker.sol_refactored.common.server_notification + - Package name for server notification. + * - ``CONF.server_notification.server_notification_class`` + - ServerNotification + - Class name for server notification. + References ========== diff --git a/doc/source/user/prometheus_plugin_use_case_guide.rst b/doc/source/user/prometheus_plugin_use_case_guide.rst index 61ca193db..c37f97875 100644 --- a/doc/source/user/prometheus_plugin_use_case_guide.rst +++ b/doc/source/user/prometheus_plugin_use_case_guide.rst @@ -334,3 +334,37 @@ rule file directly. Below is example of alert rule. auto_scale_type: SCALE_OUT, aspect_id: VDU1_aspect annotations: + +Using Vendor Specific Plugin +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Prometheus Plugin can be replaced with a vendor specific function. +To replace a plugin, change the configurations below. +The replaced class must be a subclass of +tacker.sol_refactored.common.monitoring_plugin_base.MonitoringPlugin. + +.. list-table:: + :header-rows: 1 + :widths: 40 40 40 + + * - Configuration + - Default + - Description + * - ``CONF.prometheus_plugin.performance_management_package`` + - tacker.sol_refactored.common.prometheus_plugin + - Package name for performance management. + * - ``CONF.prometheus_plugin.performance_management_class`` + - PrometheusPluginPm + - Class name for performance management. + * - ``CONF.prometheus_plugin.fault_management_package`` + - tacker.sol_refactored.common.prometheus_plugin + - Package name for fault management. + * - ``CONF.prometheus_plugin.fault_management_class`` + - PrometheusPluginFm + - Class name for fault management. + * - ``CONF.prometheus_plugin.auto_scaling_package`` + - tacker.sol_refactored.common.prometheus_plugin + - Package name for auto scaling. + * - ``CONF.prometheus_plugin.auto_scaling_class`` + - PrometheusPluginAutoScaling + - Class name for auto scaling. diff --git a/tacker/sol_refactored/common/config.py b/tacker/sol_refactored/common/config.py index 4a78a6c2a..cd2be997e 100644 --- a/tacker/sol_refactored/common/config.py +++ b/tacker/sol_refactored/common/config.py @@ -116,18 +116,51 @@ PROMETHEUS_PLUGIN_OPTS = [ cfg.BoolOpt('performance_management', default=False, help=_('Enable prometheus plugin performance management')), - cfg.IntOpt('reporting_period_margin', default=1, help=_('Some margin time for PM jos\'s reportingPeriod')), - cfg.BoolOpt('fault_management', default=False, help=_('Enable prometheus plugin fault management')), - cfg.BoolOpt('auto_scaling', default=False, help=_('Enable prometheus plugin autoscaling')), + cfg.StrOpt('performance_management_package', + default='tacker.sol_refactored.common.prometheus_plugin', + help=_('Package name for performance management. ' + '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. ' + 'This configuration is changed in case of replacing ' + 'the original function with a vendor specific ' + 'function.')), + cfg.StrOpt('fault_management_package', + default='tacker.sol_refactored.common.prometheus_plugin', + help=_('Package name for fault management. ' + 'This configuration is changed in case of replacing ' + 'the original function with a vendor specific ' + 'function.')), + cfg.StrOpt('fault_management_class', + default='PrometheusPluginFm', + help=_('Class name for fault management. ' + 'This configuration is changed in case of replacing ' + 'the original function with a vendor specific ' + 'function.')), + cfg.StrOpt('auto_scaling_package', + default='tacker.sol_refactored.common.prometheus_plugin', + help=_('Package name for auto scaling. ' + 'This configuration is changed in case of replacing ' + 'the original function with a vendor specific ' + 'function.')), + cfg.StrOpt('auto_scaling_class', + default='PrometheusPluginAutoScaling', + help=_('Class name for auto scaling. ' + 'This configuration is changed in case of replacing ' + 'the original function with a vendor specific ' + 'function.')), ] CONF.register_opts(PROMETHEUS_PLUGIN_OPTS, 'prometheus_plugin') @@ -136,7 +169,6 @@ SERVER_NOTIFICATION_OPTS = [ cfg.BoolOpt('server_notification', default=False, help=_('Enable server notification autohealing')), - cfg.StrOpt('uri_path_prefix', default='/server_notification', help=_('Uri path prefix string for server notification. ' @@ -147,6 +179,18 @@ SERVER_NOTIFICATION_OPTS = [ default=20, help=_('Timeout (second) of packing for multiple ' 'server notification.')), + cfg.StrOpt('server_notification_package', + default='tacker.sol_refactored.common.server_notification', + help=_('Package name for server notification. ' + 'This configuration is changed in case of replacing ' + 'the original function with a vendor specific ' + 'function.')), + cfg.StrOpt('server_notification_class', + default='ServerNotification', + help=_('Class name for server notification. ' + 'This configuration is changed in case of replacing ' + 'the original function with a vendor specific ' + 'function.')), ] CONF.register_opts(SERVER_NOTIFICATION_OPTS, 'server_notification') diff --git a/tacker/sol_refactored/common/exceptions.py b/tacker/sol_refactored/common/exceptions.py index c744b5172..b7a2f89f4 100644 --- a/tacker/sol_refactored/common/exceptions.py +++ b/tacker/sol_refactored/common/exceptions.py @@ -416,6 +416,11 @@ class ResourcesOtherOperationInProgress(SolHttpError409): "is in progress.") +# plugin base +class MonitoringPluginClassError(SolHttpError503): + message = _("%(class_name)s is not a subclass of MonitoringPlugin.") + + # prometheus plugin class PrometheusPluginNotEnabled(SolHttpError404): message = _("%(name)s API is not enabled.") diff --git a/tacker/sol_refactored/common/monitoring_plugin_base.py b/tacker/sol_refactored/common/monitoring_plugin_base.py index 228d3cbb4..35b7292c3 100644 --- a/tacker/sol_refactored/common/monitoring_plugin_base.py +++ b/tacker/sol_refactored/common/monitoring_plugin_base.py @@ -14,29 +14,20 @@ # under the License. from importlib import import_module +from oslo_log import log as logging -module_and_class = { - 'stub': - ('tacker.sol_refactored.common.monitoring_plugin_base', - 'MonitoringPluginStub'), - 'pm_event': - ('tacker.sol_refactored.common.prometheus_plugin', - 'PrometheusPluginPm'), - 'alert': - ('tacker.sol_refactored.common.prometheus_plugin', - 'PrometheusPluginFm'), - 'auto_healing': - ('tacker.sol_refactored.common.prometheus_plugin', - 'PrometheusPluginAutoScaling'), - 'server_notification': - ('tacker.sol_refactored.common.server_notification', - 'ServerNotification'), -} +from tacker.sol_refactored.common import exceptions as sol_ex + +LOG = logging.getLogger(__name__) -def get_class(short_name): - module = import_module(module_and_class[short_name][0]) - return getattr(module, module_and_class[short_name][1]) +def get_class(package_name, class_name): + module = import_module(package_name) + _class = getattr(module, class_name) + if not issubclass(_class, MonitoringPlugin): + LOG.error(f"Loading plugin failed: {package_name}.{class_name}.") + raise sol_ex.MonitoringPluginClassError(class_name=class_name) + return _class class MonitoringPlugin(): diff --git a/tacker/sol_refactored/common/prometheus_plugin.py b/tacker/sol_refactored/common/prometheus_plugin.py index e588a8792..317c44cc5 100644 --- a/tacker/sol_refactored/common/prometheus_plugin.py +++ b/tacker/sol_refactored/common/prometheus_plugin.py @@ -398,7 +398,7 @@ class PrometheusPluginPm(PrometheusPlugin, mon_base.MonitoringPlugin): collection_period)) return rules - def get_compute_resouce_by_sub_obj(self, vnf_instance, sub_obj): + def get_compute_resource_by_sub_obj(self, vnf_instance, sub_obj): inst = vnf_instance if (not inst.obj_attr_is_set('instantiatedVnfInfo') or not inst.instantiatedVnfInfo.obj_attr_is_set( @@ -440,7 +440,7 @@ class PrometheusPluginPm(PrometheusPlugin, mon_base.MonitoringPlugin): # resource id is like 'test-test1-756757f8f-xcwmt' # obtain 'test-test1' as deployment # obtain 'test' as container - compute_resource = self.get_compute_resouce_by_sub_obj( + compute_resource = self.get_compute_resource_by_sub_obj( inst, sub_obj) if not compute_resource: continue diff --git a/tacker/sol_refactored/controller/prometheus_plugin_controller.py b/tacker/sol_refactored/controller/prometheus_plugin_controller.py index f2df3c9aa..0da23661c 100644 --- a/tacker/sol_refactored/controller/prometheus_plugin_controller.py +++ b/tacker/sol_refactored/controller/prometheus_plugin_controller.py @@ -26,7 +26,9 @@ class PmEventController(prom_wsgi.PrometheusPluginAPIController): if not CONF.prometheus_plugin.performance_management: raise sol_ex.PrometheusPluginNotEnabled( name='Performance management') - cls = mon_base.get_class('pm_event') + cls = mon_base.get_class( + CONF.prometheus_plugin.performance_management_package, + CONF.prometheus_plugin.performance_management_class) mon_base.MonitoringPlugin.get_instance(cls).alert( request=request, body=body) return prom_wsgi.PrometheusPluginResponse(204, None) @@ -37,7 +39,9 @@ class FmAlertController(prom_wsgi.PrometheusPluginAPIController): if not CONF.prometheus_plugin.fault_management: raise sol_ex.PrometheusPluginNotEnabled( name='Fault management') - cls = mon_base.get_class('alert') + cls = mon_base.get_class( + CONF.prometheus_plugin.fault_management_package, + CONF.prometheus_plugin.fault_management_class) mon_base.MonitoringPlugin.get_instance(cls).alert( request=request, body=body) return prom_wsgi.PrometheusPluginResponse(204, None) @@ -48,7 +52,9 @@ class AutoScalingController(prom_wsgi.PrometheusPluginAPIController): if not CONF.prometheus_plugin.auto_scaling: raise sol_ex.PrometheusPluginNotEnabled( name='Auto scaling') - cls = mon_base.get_class('auto_healing') + cls = mon_base.get_class( + CONF.prometheus_plugin.auto_scaling_package, + CONF.prometheus_plugin.auto_scaling_class) mon_base.MonitoringPlugin.get_instance(cls).alert( request=request, body=body) return prom_wsgi.PrometheusPluginResponse(204, None) diff --git a/tacker/sol_refactored/controller/server_notification.py b/tacker/sol_refactored/controller/server_notification.py index 580300a18..34016cea0 100644 --- a/tacker/sol_refactored/controller/server_notification.py +++ b/tacker/sol_refactored/controller/server_notification.py @@ -25,7 +25,9 @@ class ServerNotificationController(sn_wsgi.ServerNotificationAPIController): def notify(self, request, vnf_instance_id, server_id, body): if not CONF.server_notification.server_notification: raise sol_ex.ServerNotificationNotEnabled() - cls = mon_base.get_class('server_notification') + cls = mon_base.get_class( + CONF.server_notification.server_notification_package, + CONF.server_notification.server_notification_class) mon_base.MonitoringPlugin.get_instance(cls).alert( request=request, vnf_instance_id=vnf_instance_id, server_id=server_id, body=body) diff --git a/tacker/sol_refactored/controller/vnfpm_v2.py b/tacker/sol_refactored/controller/vnfpm_v2.py index eb3cb4294..f40d2f796 100644 --- a/tacker/sol_refactored/controller/vnfpm_v2.py +++ b/tacker/sol_refactored/controller/vnfpm_v2.py @@ -116,7 +116,9 @@ class VnfPmControllerV2(sol_wsgi.SolAPIController): self.nfvo_client = nfvo_client.NfvoClient() self.endpoint = CONF.v2_vnfm.endpoint self._pm_job_view = vnfpm_view.PmJobViewBuilder(self.endpoint) - cls = plugin.get_class('pm_event') + cls = plugin.get_class( + CONF.prometheus_plugin.performance_management_package, + CONF.prometheus_plugin.performance_management_class) self.plugin = plugin.MonitoringPlugin.get_instance(cls) @validator.schema(schema.CreatePmJobRequest_V210, '2.1.0') diff --git a/tacker/tests/functional/sol_v2/test_server_notification.py b/tacker/tests/functional/sol_v2/test_server_notification.py index 7620d5b61..051f30f5a 100644 --- a/tacker/tests/functional/sol_v2/test_server_notification.py +++ b/tacker/tests/functional/sol_v2/test_server_notification.py @@ -147,8 +147,6 @@ class ServerNotificationTest(test_vnflcm_basic_common.CommonVnfLcmTest): # Test notification self.assert_notification_get(callback_url) - # check usageState of VNF Package - self._check_package_usage(is_nfvo, self.svn_pkg) # 1. LCM-Create # ETSI NFV SOL003 v3.3.1 5.5.2.2 VnfInstance diff --git a/tacker/tests/unit/sol_refactored/common/test_prometheus_plugin.py b/tacker/tests/unit/sol_refactored/common/test_prometheus_plugin.py index 996c24cfb..1d11093da 100644 --- a/tacker/tests/unit/sol_refactored/common/test_prometheus_plugin.py +++ b/tacker/tests/unit/sol_refactored/common/test_prometheus_plugin.py @@ -463,7 +463,7 @@ class TestPrometheusPluginPm(base.TestCase): group='prometheus_plugin', performance_management=True) pp = mon_base.MonitoringPlugin.get_instance( prometheus_plugin.PrometheusPluginPm) - # noromal + # normal job = objects.PmJobV2.from_dict(_pm_job) pp.delete_job(context=self.context, pm_job=job) # error diff --git a/tacker/tests/unit/sol_refactored/conductor/test_prometheus_plugin.py b/tacker/tests/unit/sol_refactored/conductor/test_prometheus_plugin.py index 942674a32..7f5428f80 100644 --- a/tacker/tests/unit/sol_refactored/conductor/test_prometheus_plugin.py +++ b/tacker/tests/unit/sol_refactored/conductor/test_prometheus_plugin.py @@ -124,7 +124,7 @@ class TestPrometheusPlugin(db_base.SqlTestCase): self.conductor = conductor_v2.ConductorV2() @mock.patch.object(http_client.HttpClient, 'do_request') - def test_requst_scale(self, mock_do_request): + def test_request_scale(self, mock_do_request): resp = webob.Response() resp.status_code = 202 mock_do_request.return_value = resp, {} diff --git a/tacker/tests/unit/sol_refactored/controller/test_prometheus_plugin.py b/tacker/tests/unit/sol_refactored/controller/test_prometheus_plugin.py index 555000c58..505e1375a 100644 --- a/tacker/tests/unit/sol_refactored/controller/test_prometheus_plugin.py +++ b/tacker/tests/unit/sol_refactored/controller/test_prometheus_plugin.py @@ -145,7 +145,7 @@ _body_scale_alert1 = { 'fingerprint': '5ef77f1f8a3ecb8d' } -# fuction_type mismatch +# function_type mismatch _body_scale_alert2 = copy.deepcopy(_body_scale_alert1) _body_scale_alert2['labels']['function_type'] = 'vnffm' diff --git a/tacker/tests/unit/sol_refactored/controller/test_server_notification.py b/tacker/tests/unit/sol_refactored/controller/test_server_notification.py index 6659c36e3..e00070f83 100644 --- a/tacker/tests/unit/sol_refactored/controller/test_server_notification.py +++ b/tacker/tests/unit/sol_refactored/controller/test_server_notification.py @@ -18,6 +18,7 @@ from unittest import mock from tacker import context from tacker.sol_refactored.common import exceptions as sol_ex +from tacker.sol_refactored.common import monitoring_plugin_base as mon_base from tacker.sol_refactored.common import server_notification as sn_common from tacker.sol_refactored.common import vnf_instance_utils as inst_utils from tacker.sol_refactored.controller import server_notification @@ -77,6 +78,31 @@ _body2 = { 'error_schema': {} } +pkg = 'tacker.tests.unit.sol_refactored.controller.test_server_notification' + + +class VendorSpecificMonitoringPlugin(mon_base.MonitoringPlugin): + _instance = None + + @staticmethod + def instance(): + if not VendorSpecificMonitoringPlugin._instance: + VendorSpecificMonitoringPlugin() + return VendorSpecificMonitoringPlugin._instance + + def __init__(self): + if VendorSpecificMonitoringPlugin._instance: + raise SystemError( + "Not constructor but instance() should be used.") + VendorSpecificMonitoringPlugin._instance = self + + def alert(self, **kwargs): + pass + + +class NotASubClassOfMonitoringPlugin(): + pass + class TestServerNotification(base.TestCase): def setUp(self): @@ -105,8 +131,7 @@ class TestServerNotification(base.TestCase): server_id='test_server_id', body=_body) @mock.patch.object(inst_utils, 'get_inst') - def test_notify(self, - mock_inst): + def test_notify(self, mock_inst): self.config_fixture.config( group='server_notification', server_notification=True) mock_inst.return_value = objects.VnfInstanceV2.from_dict(_inst1) @@ -168,3 +193,30 @@ class TestServerNotification(base.TestCase): self.controller.notify, request=self.request, vnf_instance_id='test_id', server_id='test_server_id', body=_body) + + def test_vendor_specific_plugin(self): + self.config_fixture.config( + group='server_notification', server_notification=True) + self.config_fixture.config( + group='server_notification', server_notification_package=pkg) + self.config_fixture.config( + group='server_notification', + server_notification_class='VendorSpecificMonitoringPlugin') + response = self.controller.notify( + request=self.request, + vnf_instance_id='test_id', + server_id='test_server_id', body=_body) + self.assertEqual(204, response.status) + + def test_vendor_specific_plugin_subclass(self): + self.config_fixture.config( + group='server_notification', server_notification=True) + self.config_fixture.config( + group='server_notification', server_notification_package=pkg) + self.config_fixture.config( + group='server_notification', + server_notification_class='NotASubClassOfMonitoringPlugin') + self.assertRaises( + sol_ex.MonitoringPluginClassError, self.controller.notify, + request=self.request, vnf_instance_id='test_id', + server_id='test_server_id', body=_body)