diff --git a/devstack/lib/tacker b/devstack/lib/tacker index b6b44fad2..2fe776db6 100644 --- a/devstack/lib/tacker +++ b/devstack/lib/tacker @@ -244,6 +244,13 @@ function configure_tacker { iniset $TACKER_CONF DEFAULT auth_strategy $TACKER_AUTH_STRATEGY _tacker_setup_keystone $TACKER_CONF keystone_authtoken + # Experimental settings for monitor alarm auth settings, + # Will be changed according to new implementation. + iniset $TACKER_CONF alarm_auth uername tacker + iniset $TACKER_CONF alarm_auth password "$SERVICE_PASSWORD" + iniset $TACKER_CONF alarm_auth project_name "$SERVICE_PROJECT_NAME" + iniset $TACKER_CONF alarm_auth url http://$SERVICE_HOST:35357/v3 + # Configuration for tacker requests to nova. iniset $TACKER_CONF DEFAULT nova_url $TACKER_NOVA_URL iniset $TACKER_CONF DEFAULT nova_admin_user_name nova diff --git a/doc/source/devref/alarm_monitoring_usage_guide.rst b/doc/source/devref/alarm_monitoring_usage_guide.rst new file mode 100644 index 000000000..205595a26 --- /dev/null +++ b/doc/source/devref/alarm_monitoring_usage_guide.rst @@ -0,0 +1,177 @@ +.. + 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. + +.. _ref-alarm_frm: + +========================== +Alarm monitoring framework +========================== + +This document describes how to use alarm-based monitoring driver in Tacker. + +Sample TOSCA with monitoring policy +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following example shows monitoring policy using TOSCA template. +The target (VDU1) of the monitoring policy in this example need to be +described firstly like other TOSCA templates in Tacker. + +.. code-block:: yaml + + policies: + - vdu1_cpu_usage_monitoring_policy: + type: tosca.policies.tacker.Alarming + triggers: + resize_compute: + event_type: + type: tosca.events.resource.utilization + implementation: ceilometer + metrics: cpu_util + condition: + threshold: 50 + constraint: utilization greater_than 50% + period: 65 + evaluations: 1 + method: avg + comparison_operator: gt + action: + resize_compute: + action_name: respawn + +Alarm framework already supported the some default backend actions like +**repsawn, log, and log_and_kill**. + +Tacker users could change the desired action as described in the above example. +Until now, the backend actions could be pointed to the specific policy which +is also described in TOSCA template like scaling policy. The integration between +alarming monitoring and auto-scaling was also supported by Alarm monitor in Tacker: + +.. code-block:: yaml + + policies: + - SP1: + type: tosca.policy.tacker.Scaling + properties: + increment: 1 + cooldown: 120 + min_instances: 1 + max_instances: 3 + default_instances: 2 + targets: [VDU1] + + - vdu1_cpu_usage_monitoring_policy: + type: tosca.policies.tacker.Alarming + triggers: + resize_compute: + event_type: + type: tosca.events.resource.utilization + implementation: ceilometer + metrics: cpu_util + condition: + threshold: 50 + constraint: utilization greater_than 50% + period: 600 + evaluations: 1 + method: avg + comparison_operator: gt + action: + resize_compute: + action_name: SP1 + +How to setup environment +~~~~~~~~~~~~~~~~~~~~~~~~ + +If OpenStack Devstack is used to test alarm monitoring in Tacker, OpenStack Ceilometer +and Aodh plugins will need to be enabled in local.conf: + +.. code-block::ini + +**enable_plugin ceilometer https://git.openstack.org/openstack/ceilometer** + +**enable_plugin aodh https://git.openstack.org/openstack/aodh** + +Further, once OpenStack Monasca is leveraged in Tacker, it will need to be enabled +plugin in local.conf as well. + +How to monitor VNFs via alarm triggers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +How to setup alarm configuration +================================ + +Firstly, vnfd and vnf need to be created successfully using pre-defined TOSCA template +for alarm monitoring. Then, in order to know whether alarm configuration defined in Tacker +is successfully passed to Ceilometer, Tacker users could use CLI: + +.. code-block::ini + +$ ceilometer alarm-list + ++--------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------+-------------------+----------+---------+------------+------------------------------------+------------------+ +| Alarm ID | Name | State | Severity | Enabled | Continuous | Alarm condition | Time constraints | ++--------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------+-------------------+----------+---------+------------+------------------------------------+------------------+ +| 35a80852-e24f-46ed-bd34-e2f831d00172 | tacker.vnfm.infra_drivers.heat.heat_DeviceHeat-6f3e523d-9e12-4973-a2e8-ea04b9601253-vdu1_cpu_usage_monitoring_policy-qer2ipsi2mk4 | insufficient data | low | True | True | avg(cpu_util) > 50 during 1 x 65s | None | ++--------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------+-------------------+----------+---------+------------+------------------------------------+------------------+ + +$ ceilometer alarm-show 35a80852-e24f-46ed-bd34-e2f831d00172 + ++---------------------------+--------------------------------------------------------------------------+ +| Property | Value | ++---------------------------+--------------------------------------------------------------------------+ +| alarm_actions | ["http://ubuntu:9890/v1.0/vnfs/6f3e523d-9e12-4973-a2e8-ea04b9601253/vdu1 | +| | _cpu_usage_monitoring_policy/respawn/g0jtsxu9"] | +| alarm_id | 35a80852-e24f-46ed-bd34-e2f831d00172 | +| comparison_operator | gt | +| description | utilization greater_than 50% | +| enabled | True | +| evaluation_periods | 1 | +| exclude_outliers | False | +| insufficient_data_actions | None | +| meter_name | cpu_util | +| name | tacker.vnfm.infra_drivers.heat.heat_DeviceHeat-6f3e523d- | +| | 9e12-4973-a2e8-ea04b9601253-vdu1_cpu_usage_monitoring_policy- | +| | qer2ipsi2mk4 | +| ok_actions | None | +| period | 65 | +| project_id | 8361286345c4482cb777da6657c38238 | +| query | | +| repeat_actions | True | +| severity | low | +| state | insufficient data | +| statistic | avg | +| threshold | 50 | +| type | threshold | +| user_id | b5f7fefac7874e45ae93443e95447fb9 | ++---------------------------+--------------------------------------------------------------------------+ + + +How to trigger alarms: +====================== +As shown in the above Ceilometer command, alarm state is shown as "insufficient data". Alarm is +triggered by Ceilometer once alarm state changes to "alarm". +To make VNF instance reach to the pre-defined threshold, some simple scripts could be used. + +Note: Because Ceilometer pipeline set the default interval to 600s (10 mins), +in order to reduce this interval, users could edit "interval" value +in **/etc/ceilometer/pipeline.yaml** file and then restart Ceilometer service. + +Another way could be used to check if backend action is handled well in Tacker: + +.. code-block::ini + +curl -H "Content-Type: application/json" -X POST -d '{"alarm_id": "35a80852-e24f-46ed-bd34-e2f831d00172", "current": "alarm"}' http://ubuntu:9890/v1.0/vnfs/6f3e523d-9e12-4973-a2e8-ea04b9601253/vdu1_cpu_usage_monitoring_policy/respawn/g0jtsxu9 + +Then, users can check Horizon to know if vnf is respawned. Please note that the url used +in the above command could be captured from "**ceilometer alarm-show** command as shown before. +"key" attribute in body request need to be captured from the url. The reason is that key will be authenticated +so that the url is requested only one time. diff --git a/doc/source/index.rst b/doc/source/index.rst index 000cabc26..53923eb72 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -55,6 +55,7 @@ Feature Documentation devref/multisite_vim_usage_guide.rst devref/mistral_workflows_usage_guide.rst devref/scale_usage_guide.rst + devref/alarm_monitoring_usage_guide.rst API Documentation ================= diff --git a/etc/config-generator.conf b/etc/config-generator.conf index 1913d2d12..2eda73d42 100644 --- a/etc/config-generator.conf +++ b/etc/config-generator.conf @@ -14,3 +14,5 @@ namespace = tacker.vnfm.infra_drivers.openstack.openstack namespace = tacker.vnfm.mgmt_drivers.openwrt.openwrt namespace = tacker.vnfm.monitor_drivers.http_ping.http_ping namespace = tacker.vnfm.monitor_drivers.ping.ping +namespace = tacker.vnfm.monitor_drivers.ceilometer.ceilometer +namespace = tacker.alarm_receiver diff --git a/etc/tacker/api-paste.ini b/etc/tacker/api-paste.ini index 1e44ef2e3..02f0dab31 100644 --- a/etc/tacker/api-paste.ini +++ b/etc/tacker/api-paste.ini @@ -6,7 +6,7 @@ use = egg:Paste#urlmap [composite:tackerapi_v1_0] use = call:tacker.auth:pipeline_factory noauth = request_id catch_errors extensions tackerapiapp_v1_0 -keystone = request_id catch_errors authtoken keystonecontext extensions tackerapiapp_v1_0 +keystone = request_id catch_errors alarm_receiver authtoken keystonecontext extensions tackerapiapp_v1_0 [filter:request_id] paste.filter_factory = oslo_middleware:RequestId.factory @@ -14,6 +14,9 @@ paste.filter_factory = oslo_middleware:RequestId.factory [filter:catch_errors] paste.filter_factory = oslo_middleware:CatchErrors.factory +[filter:alarm_receiver] +paste.filter_factory = tacker.alarm_receiver:AlarmReceiver.factory + [filter:keystonecontext] paste.filter_factory = tacker.auth:TackerKeystoneContext.factory diff --git a/samples/tosca-templates/vnfd/tosca-vnfd-alarm.yaml b/samples/tosca-templates/vnfd/tosca-vnfd-alarm.yaml new file mode 100644 index 000000000..320bd9c85 --- /dev/null +++ b/samples/tosca-templates/vnfd/tosca-vnfd-alarm.yaml @@ -0,0 +1,67 @@ +tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0 +description: Demo example + +metadata: + template_name: sample-tosca-vnfd + +topology_template: + node_templates: + VDU1: + type: tosca.nodes.nfv.VDU.Tacker + capabilities: + nfv_compute: + properties: + disk_size: 1 GB + mem_size: 512 MB + num_cpus: 2 + properties: + image: cirros-0.3.4-x86_64-uec + mgmt_driver: noop + availability_zone: nova + + CP1: + type: tosca.nodes.nfv.CP.Tacker + properties: + management: true + anti_spoofing_protection: false + requirements: + - virtualLink: + node: VL1 + - virtualBinding: + node: VDU1 + + VL1: + type: tosca.nodes.nfv.VL + properties: + network_name: net_mgmt + vendor: Tacker + + policies: + - SP1: + type: tosca.policy.tacker.Scaling + properties: + increment: 1 + cooldown: 120 + min_instances: 1 + max_instances: 3 + default_instances: 2 + targets: [VDU1] + + - vdu1_cpu_usage_monitoring_policy: + type: tosca.policies.tacker.Alarming + triggers: + resize_compute: + event_type: + type: tosca.events.resource.utilization + implementation: ceilometer + metrics: cpu_util + condition: + threshold: 50 + constraint: utilization greater_than 50% + period: 600 + evaluations: 1 + method: avg + comparison_operator: gt + action: + resize_compute: + action_name: SP1 diff --git a/setup.cfg b/setup.cfg index 3def35adf..11bd954a7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -58,6 +58,8 @@ tacker.tacker.mgmt.drivers = tacker.tacker.monitor.drivers = ping = tacker.vnfm.monitor_drivers.ping.ping:VNFMonitorPing http_ping = tacker.vnfm.monitor_drivers.http_ping.http_ping:VNFMonitorHTTPPing +tacker.tacker.alarm_monitor.drivers = + ceilometer = tacker.vnfm.monitor_drivers.ceilometer.ceilometer:VNFMonitorCeilometer oslo.config.opts = tacker.common.config = tacker.common.config:config_opts tacker.wsgi = tacker.wsgi:config_opts @@ -72,6 +74,8 @@ oslo.config.opts = tacker.vnfm.mgmt_drivers.openwrt.openwrt = tacker.vnfm.mgmt_drivers.openwrt.openwrt:config_opts tacker.vnfm.monitor_drivers.http_ping.http_ping = tacker.vnfm.monitor_drivers.http_ping.http_ping:config_opts tacker.vnfm.monitor_drivers.ping.ping = tacker.vnfm.monitor_drivers.ping.ping:config_opts + tacker.vnfm.monitor_drivers.ceilometer.ceilometer = tacker.vnfm.monitor_drivers.ceilometer.ceilometer:config_opts + tacker.alarm_receiver = tacker.alarm_receiver:config_opts diff --git a/tacker/alarm_receiver.py b/tacker/alarm_receiver.py new file mode 100644 index 000000000..e6c9de6bf --- /dev/null +++ b/tacker/alarm_receiver.py @@ -0,0 +1,92 @@ +# Copyright 2012 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. +from oslo_config import cfg +from oslo_log import log as logging +from oslo_serialization import jsonutils +from six.moves.urllib import parse as urlparse +from tacker.vnfm.monitor_drivers.token import Token +from tacker import wsgi +# check alarm url with db --> move to plugin + +LOG = logging.getLogger(__name__) + +OPTS = [ + cfg.StrOpt('username', default='tacker', + help=_('User name for alarm monitoring')), + cfg.StrOpt('password', default='nomoresecret', + help=_('password for alarm monitoring')), + cfg.StrOpt('project_name', default='service', + help=_('project name for alarm monitoring')), + cfg.StrOpt('url', default='http://localhost:35357/v3', + help=_('url for alarm monitoring')), +] + +cfg.CONF.register_opts(OPTS, 'alarm_auth') + + +def config_opts(): + return [('alarm_auth', OPTS)] + + +class AlarmReceiver(wsgi.Middleware): + def process_request(self, req): + LOG.debug(_('Process request: %s'), req) + if req.method != 'POST': + return + url = req.url + if not self.handle_url(url): + return + prefix, info, params = self.handle_url(req.url) + token = Token(username=cfg.CONF.alarm_auth.username, + password=cfg.CONF.alarm_auth.password, + project_name=cfg.CONF.alarm_auth.project_name, + auth_url=cfg.CONF.alarm_auth.url, + user_domain_name='default', + project_domain_name='default') + + token_identity = token.create_token() + req.headers['X_AUTH_TOKEN'] = token_identity + # Change the body request + if req.body: + body_dict = dict() + body_dict['trigger'] = {} + body_dict['trigger'].setdefault('params', {}) + # Update params in the body request + body_info = jsonutils.loads(req.body) + body_dict['trigger']['params']['data'] = body_info + body_dict['trigger']['params']['credential'] = info[6] + # Update policy and action + body_dict['trigger']['policy_name'] = info[4] + body_dict['trigger']['action_name'] = info[5] + req.body = jsonutils.dumps(body_dict) + LOG.debug('Body alarm: %s', req.body) + # Need to change url because of mandatory + req.environ['PATH_INFO'] = prefix + 'triggers' + req.environ['QUERY_STRING'] = '' + LOG.debug('alarm url in receiver: %s', req.url) + + def handle_url(self, url): + # alarm_url = 'http://host:port/v1.0/vnfs/vnf-uuid/mon-policy-name/action-name/8ef785' # noqa + parts = urlparse.urlparse(url) + p = parts.path.split('/') + if len(p) != 7: + return None + + if any((p[0] != '', p[2] != 'vnfs')): + return None + qs = urlparse.parse_qs(parts.query) + params = dict((k, v[0]) for k, v in qs.items()) + prefix_url = '/%(collec)s/%(vnf_uuid)s/' % {'collec': p[2], + 'vnf_uuid': p[3]} + return prefix_url, p, params diff --git a/tacker/common/exceptions.py b/tacker/common/exceptions.py index bc9fa38f8..1130ac2c2 100644 --- a/tacker/common/exceptions.py +++ b/tacker/common/exceptions.py @@ -245,6 +245,10 @@ class MgmtDriverException(TackerException): message = _("VNF configuration failed") +class AlarmUrlInvalid(BadRequest): + message = _("Invalid alarm url for VNF %(vnf_id)s") + + class VnfPolicyNotFound(NotFound): message = _("Policy %(policy)s does not exist for VNF %(vnf_id)s") diff --git a/tacker/extensions/vnfm.py b/tacker/extensions/vnfm.py index 9dfe7a8e2..5e84e26f3 100644 --- a/tacker/extensions/vnfm.py +++ b/tacker/extensions/vnfm.py @@ -369,7 +369,44 @@ SUB_RESOURCE_ATTRIBUTE_MAP = { 'is_visible': False }, } - } + }, + } + }, + 'triggers': { + 'parent': { + 'collection_name': 'vnfs', + 'member_name': 'vnf' + }, + 'members': { + 'trigger': { + 'parameters': { + 'policy_name': { + 'allow_post': True, + 'allow_put': False, + 'is_visible': True, + 'validate': {'type:string': None} + }, + 'action_name': { + 'allow_post': True, + 'allow_put': False, + 'is_visible': True, + 'validate': {'type:string': None} + }, + 'params': { + 'allow_post': True, + 'allow_put': False, + 'is_visible': True, + 'validate': {'type:dict_or_none': None} + }, + 'tenant_id': { + 'allow_post': True, + 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': False, + 'is_visible': False + } + } + }, } }, 'resources': { @@ -449,11 +486,11 @@ class Vnfm(extensions.ExtensionDescriptor): allow_bulk=True, parent=parent) - resource = extensions.ResourceExtension( - collection_name, - controller, parent, - attr_map=params) - resources.append(resource) + resource = extensions.ResourceExtension( + collection_name, + controller, parent, + attr_map=params) + resources.append(resource) return resources @classmethod @@ -525,3 +562,8 @@ class VNFMPluginBase(service_base.NFVPluginBase): def create_vnf_scale( self, context, vnf_id, scale): pass + + @abc.abstractmethod + def create_vnf_trigger( + self, context, vnf_id, trigger): + pass diff --git a/tacker/plugins/common/constants.py b/tacker/plugins/common/constants.py index 3075b9aec..f2ad46f21 100644 --- a/tacker/plugins/common/constants.py +++ b/tacker/plugins/common/constants.py @@ -52,6 +52,8 @@ POLICY_SCALING = 'tosca.policy.tacker.Scaling' POLICY_SCALING_ACTIONS = (ACTION_SCALE_OUT, ACTION_SCALE_IN) = ('out', 'in') POLICY_ACTIONS = {POLICY_SCALING: POLICY_SCALING_ACTIONS} +POLICY_ALARMING = 'tosca.policies.tacker.Alarming' +DEFAULT_ALARM_ACTIONS = ['respawn', 'log', 'log_and_kill', 'notify'] RES_TYPE_VNFD = "vnfd" RES_TYPE_VNF = "vnf" diff --git a/tacker/tests/unit/vm/infra_drivers/heat/test_heat.py b/tacker/tests/unit/vm/infra_drivers/heat/test_heat.py index 4563d2475..61af1a6eb 100644 --- a/tacker/tests/unit/vm/infra_drivers/heat/test_heat.py +++ b/tacker/tests/unit/vm/infra_drivers/heat/test_heat.py @@ -236,7 +236,8 @@ class TestDeviceHeat(base.TestCase): tosca_tpl_name, hot_tpl_name, param_values='', - is_monitor=True): + is_monitor=True, + is_alarm=False): tosca_tpl = _get_template(tosca_tpl_name) exp_tmpl = self._get_expected_vnfd(tosca_tpl) tosca_hw_dict = yaml.safe_load(_get_template(hot_tpl_name)) @@ -264,10 +265,11 @@ class TestDeviceHeat(base.TestCase): '"respawn"}, "parameters": {"count": 3, ' '"interval": 10}, "monitoring_params": ' '{"count": 3, "interval": 10}}}}}'}) - + if is_alarm: + dvc['attributes'].update({'alarm_url': ''}) return dvc - def _get_dummy_tosca_vnf(self, template, input_params=''): + def _get_dummy_tosca_vnf(self, template, input_params='', is_alarm=False): tosca_template = _get_template(template) vnf = utils.get_dummy_device_obj() @@ -278,21 +280,24 @@ class TestDeviceHeat(base.TestCase): vnf['vnfd'] = dtemplate['vnfd'] vnf['attributes'] = {} vnf['attributes']['param_values'] = input_params + if is_alarm: + vnf['attributes']['alarm_url'] = '' return vnf - def _test_assert_equal_for_tosca_templates(self, - tosca_tpl_name, + def _test_assert_equal_for_tosca_templates(self, tosca_tpl_name, hot_tpl_name, input_params='', files=None, - is_monitor=True): - vnf = self._get_dummy_tosca_vnf(tosca_tpl_name, input_params) + is_monitor=True, + is_alarm=False): + vnf = self._get_dummy_tosca_vnf(tosca_tpl_name, input_params, is_alarm) expected_result = '4a4c2d44-8a52-4895-9a75-9d1c76c3e738' expected_fields = self._get_expected_fields_tosca(hot_tpl_name) expected_vnf = self._get_expected_tosca_vnf(tosca_tpl_name, hot_tpl_name, input_params, - is_monitor) + is_monitor, + is_alarm) result = self.heat_driver.create(plugin=None, context=self.context, vnf=vnf, auth_attr=utils.get_vim_auth_obj()) @@ -439,3 +444,11 @@ class TestDeviceHeat(base.TestCase): 'test_tosca_security_groups.yaml', 'hot_tosca_security_groups.yaml' ) + + def test_create_tosca_with_alarm_monitoring(self): + self._test_assert_equal_for_tosca_templates( + 'tosca_alarm.yaml', + 'hot_tosca_alarm.yaml', + is_monitor=False, + is_alarm=True + ) diff --git a/tacker/tests/unit/vm/infra_drivers/openstack/data/hot_tosca_alarm.yaml b/tacker/tests/unit/vm/infra_drivers/openstack/data/hot_tosca_alarm.yaml new file mode 100644 index 000000000..255cd0f6b --- /dev/null +++ b/tacker/tests/unit/vm/infra_drivers/openstack/data/hot_tosca_alarm.yaml @@ -0,0 +1,41 @@ +heat_template_version: 2013-05-23 +description: 'Demo example + +' + +outputs: + mgmt_ip-VDU1: + value: + get_attr: [CP1, fixed_ips, 0, ip_address] +parameters: {} +resources: + VDU1: + properties: + availability_zone: nova + config_drive: false + flavor: {get_resource: VDU1_flavor} + image: cirros-0.3.4-x86_64-uec + networks: + - port: {get_resource: CP1} + user_data_format: SOFTWARE_CONFIG + type: OS::Nova::Server + CP1: + properties: {network: private, port_security_enabled: false} + type: OS::Neutron::Port + VDU1_flavor: + type: OS::Nova::Flavor + properties: + disk: 1 + ram: 512 + vcpus: 2 + vdu1_cpu_usage_monitoring_policy: + type: OS::Aodh::Alarm + properties: + description: utilization greater_than 50% + meter_name: cpu_util + threshold: 50 + period: 60 + statistic: average + evaluation_periods: 1 + comparison_operator: gt + diff --git a/tacker/tests/unit/vm/infra_drivers/openstack/data/tosca_alarm.yaml b/tacker/tests/unit/vm/infra_drivers/openstack/data/tosca_alarm.yaml new file mode 100644 index 000000000..9c59132e1 --- /dev/null +++ b/tacker/tests/unit/vm/infra_drivers/openstack/data/tosca_alarm.yaml @@ -0,0 +1,56 @@ +tosca_definitions_version: tosca_simple_profile_for_nfv_1_0_0 +description: Demo example + +metadata: + template_name: sample-tosca-vnfd + +topology_template: + node_templates: + VDU1: + type: tosca.nodes.nfv.VDU.Tacker + capabilities: + nfv_compute: + properties: + disk_size: 1 GB + mem_size: 512 MB + num_cpus: 2 + properties: + image: cirros-0.3.4-x86_64-uec + mgmt_driver: noop + availability_zone: nova + + CP1: + type: tosca.nodes.nfv.CP.Tacker + properties: + management: true + anti_spoofing_protection: false + requirements: + - virtualLink: + node: VL1 + - virtualBinding: + node: VDU1 + + VL1: + type: tosca.nodes.nfv.VL + properties: + network_name: private + vendor: Tacker + + policies: + - vdu1_cpu_usage_monitoring_policy: + type: tosca.policies.tacker.Alarming + triggers: + resize_compute: + event_type: + type: tosca.events.resource.utilization + implementation: Ceilometer + metrics: cpu_util + condition: + threshold: 50 + constraint: utilization greater_than 50% + period: 60 + evaluations: 1 + method: average + comparison_operator: gt + action: + resize_compute: '' \ No newline at end of file diff --git a/tacker/tests/unit/vm/test_alarm_receiver.py b/tacker/tests/unit/vm/test_alarm_receiver.py new file mode 100644 index 000000000..3bfa831b6 --- /dev/null +++ b/tacker/tests/unit/vm/test_alarm_receiver.py @@ -0,0 +1,60 @@ +# Copyright 2015 Brocade Communications System, Inc. +# 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 mock +from webob import Request + +from tacker.alarm_receiver import AlarmReceiver +from tacker.tests.unit import base + + +class TestAlarmReceiver(base.TestCase): + def setUp(self): + '''url: + + http://tacker:9890/v1.0/vnfs/vnf-uuid/mon-policy-name/ + action-name/8ef785 + ''' + super(TestAlarmReceiver, self).setUp() + self.alarmrc = AlarmReceiver(None) + self.alarm_url = { + '00_base': 'http://tacker:9890/v1.0', + '01_url_base': '/vnfs/vnf-uuid/', + '02_vnf_id': 'vnf-uuid', + '03_monitoring_policy_name': 'mon-policy-name', + '04_action_name': 'action-name', + '05_key': 'KEY' + } + self.vnf_id = 'vnf-uuid' + self.ordered_url = self._generate_alarm_url() + + def _generate_alarm_url(self): + return 'http://tacker:9890/v1.0/vnfs/vnf-uuid/mon-policy-name/'\ + 'action-name/8ef785' + + def test_handle_url(self): + prefix_url, p, params = self.alarmrc.handle_url(self.ordered_url) + self.assertEqual(self.alarm_url['01_url_base'], prefix_url) + self.assertEqual(self.alarm_url['02_vnf_id'], p[3]) + self.assertEqual(self.alarm_url['03_monitoring_policy_name'], p[4]) + self.assertEqual(self.alarm_url['04_action_name'], p[5]) + + @mock.patch('tacker.vnfm.monitor_drivers.token.Token.create_token') + def test_process_request(self, mock_token): + req = Request.blank(self.ordered_url) + req.method = 'POST' + self.alarmrc.process_request(req) + self.assertIsNotNone(req.body) + self.assertIn('triggers', req.environ['PATH_INFO']) diff --git a/tacker/tests/unit/vm/test_tosca_templates_under_samples.py b/tacker/tests/unit/vm/test_tosca_templates_under_samples.py index 9bb4bbaa1..b3ae4c08b 100644 --- a/tacker/tests/unit/vm/test_tosca_templates_under_samples.py +++ b/tacker/tests/unit/vm/test_tosca_templates_under_samples.py @@ -22,13 +22,6 @@ from tacker.vnfm.tosca import utils from translator.hot import tosca_translator -# TODO(kanagaraj-manickam) Update it for including other samples also -def get_list_of_samples(): - base_path = (os.path.dirname(os.path.abspath(__file__)) + - '/../../../../samples/tosca-templates/vnfd/') - return [base_path + 'tosca-vnfd-scale.yaml'] - - class TestSamples(testtools.TestCase): """Sample tosca validation. @@ -37,40 +30,62 @@ class TestSamples(testtools.TestCase): possible to translate into HOT template. """ - def test_samples(self): - for f in get_list_of_samples(): - with open(f, 'r') as _f: - yaml_dict = None - try: - yaml_dict = yamlparser.simple_ordered_parse(_f.read()) - except: # noqa - pass + def _get_list_of_sample(self, tosca_files): + if tosca_files: + base_path = (os.path.dirname(os.path.abspath(__file__)) + + '/../../../../samples/tosca-templates/vnfd/') + if isinstance(tosca_files, list): + list_of_samples = [] + for tosca_file in tosca_files: + sample = base_path + tosca_file + list_of_samples.append(sample) + return list_of_samples - self.assertIsNotNone( - yaml_dict, - "Yaml parser failed to parse %s" % f) + def _test_samples(self, files): + if files: + for f in self._get_list_of_sample(files): + with open(f, 'r') as _f: + yaml_dict = None + try: + yaml_dict = yamlparser.simple_ordered_parse(_f.read()) + except: # noqa + pass + self.assertIsNotNone( + yaml_dict, + "Yaml parser failed to parse %s" % f) - utils.updateimports(yaml_dict) + utils.updateimports(yaml_dict) - tosca = None - try: - tosca = tosca_template.ToscaTemplate( - a_file=False, - yaml_dict_tpl=yaml_dict) - except: # noqa - pass + tosca = None + try: + tosca = tosca_template.ToscaTemplate( + a_file=False, + yaml_dict_tpl=yaml_dict) + except: # noqa + pass - self.assertIsNotNone( - tosca, - "Tosca parser failed to parse %s" % f) + self.assertIsNotNone( + tosca, + "Tosca parser failed to parse %s" % f) - hot = None - try: - hot = tosca_translator.TOSCATranslator(tosca, - {}).translate() - except: # noqa - pass + hot = None + try: + hot = tosca_translator.TOSCATranslator(tosca, + {}).translate() + except: # noqa + pass - self.assertIsNotNone( - hot, - "Heat-translator failed to translate %s" % f) + self.assertIsNotNone( + hot, + "Heat-translator failed to translate %s" % f) + + def test_scale_sample(self, tosca_file=['tosca-vnfd-scale.yaml']): + self._test_samples(tosca_file) + + def test_alarm_sample(self, tosca_file=['tosca-vnfd-alarm.yaml']): + self._test_samples(tosca_file) + + def test_list_samples(self, + files=['tosca-vnfd-scale.yaml', + 'tosca-vnfd-alarm.yaml']): + self._test_samples(files) diff --git a/tacker/vnfm/infra_drivers/openstack/openstack.py b/tacker/vnfm/infra_drivers/openstack/openstack.py index 49e79c090..c255b3df8 100644 --- a/tacker/vnfm/infra_drivers/openstack/openstack.py +++ b/tacker/vnfm/infra_drivers/openstack/openstack.py @@ -323,7 +323,16 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, # scale_resource_type is custome type mapped the HOT template # generated for all VDUs in the tosca template properties['resource']['type'] = scale_resource_type - + # support monitoring + if 'policies' in vnfd_dict: + for policies in vnfd_dict['policies']: + policy_name, policy_dt = list(policies.items())[0] + if policy_dt['type'] ==\ + 'tosca.polices.tacker.Alarming': + metadata_dict = dict() + metadata_dict['metering.vnf_id'] = vnf['id'] + properties['resource']['metadata'] = metadata_dict + break # TODO(kanagraj-manickam) add custom type params here, to # support parameterized template group_hot['properties'] = properties @@ -394,6 +403,66 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, scaling_group_names, template_dict) + def generate_hot_alarm_resource(topology_tpl_dict, heat_tpl): + alarm_resource = dict() + heat_dict = yamlparser.simple_ordered_parse(heat_tpl) + is_enabled_alarm = False + + def _convert_to_heat_monitoring_prop(mon_policy): + name, mon_policy_dict = list(mon_policy.items())[0] + tpl_trigger_name = \ + mon_policy_dict['triggers']['resize_compute'] + tpl_condition = tpl_trigger_name['condition'] + properties = {} + properties['meter_name'] = tpl_trigger_name['metrics'] + properties['comparison_operator'] = \ + tpl_condition['comparison_operator'] + properties['period'] = tpl_condition['period'] + properties['evaluation_periods'] = tpl_condition['evaluations'] + properties['statistic'] = tpl_condition['method'] + properties['description'] = tpl_condition['constraint'] + properties['threshold'] = tpl_condition['threshold'] + # alarm url process here + alarm_url = str(vnf['attributes'].get('alarm_url')) + if alarm_url: + LOG.debug('Alarm url in heat %s', alarm_url) + properties['alarm_actions'] = [alarm_url] + return properties + + def _convert_to_heat_monitoring_resource(mon_policy): + mon_policy_hot = {'type': 'OS::Aodh::Alarm'} + mon_policy_hot['properties'] = \ + _convert_to_heat_monitoring_prop(mon_policy) + + if 'policies' in topology_tpl_dict: + for policies in topology_tpl_dict['policies']: + policy_name, policy_dt = list(policies.items())[0] + if policy_dt['type'] == \ + 'tosca.policy.tacker.Scaling': + metadata_dict = dict() + metadata_dict['metadata.user_metadata.vnf_id'] =\ + vnf['id'] + mon_policy_hot['properties']['matching_metadata'] =\ + metadata_dict + break + return mon_policy_hot + + if 'policies' in topology_tpl_dict: + for policy_dict in topology_tpl_dict['policies']: + name, policy_tpl_dict = list(policy_dict.items())[0] + if policy_tpl_dict['type'] == \ + 'tosca.policies.tacker.Alarming': + is_enabled_alarm = True + alarm_resource[name] =\ + _convert_to_heat_monitoring_resource(policy_dict) + heat_dict['resources'].update(alarm_resource) + break + + heat_tpl_yaml = yaml.dump(heat_dict) + return (is_enabled_alarm, + alarm_resource, + heat_tpl_yaml) + def generate_hot_from_legacy(vnfd_dict): assert 'template' not in fields assert 'template_url' not in fields @@ -490,16 +559,22 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, monitoring_dict) = generate_hot_from_legacy(vnfd_dict) fields['template'] = heat_template_yaml - - # Handle scaling here if is_tosca_format: - (is_scaling_needed, - scaling_group_names, + (is_scaling_needed, scaling_group_names, main_dict) = generate_hot_scaling( vnfd_dict['topology_template'], 'scaling.yaml') + (is_enabled_alarm, alarm_resource, + heat_tpl_yaml) = generate_hot_alarm_resource( + vnfd_dict['topology_template'], + heat_template_yaml) + if is_enabled_alarm and not is_scaling_needed: + heat_template_yaml = heat_tpl_yaml + fields['template'] = heat_template_yaml if is_scaling_needed: + if is_enabled_alarm: + main_dict['resources'].update(alarm_resource) main_yaml = yaml.dump(main_dict) fields['template'] = main_yaml fields['files'] = {'scaling.yaml': heat_template_yaml} @@ -513,7 +588,8 @@ class OpenStack(abstract_driver.DeviceAbstractDriver, 'scaling_group_names'] = jsonutils.dumps( scaling_group_names ) - else: + + elif not is_scaling_needed: if not vnf['attributes'].get('heat_template'): vnf['attributes'][ 'heat_template'] = fields['template'] diff --git a/tacker/vnfm/monitor.py b/tacker/vnfm/monitor.py index 7673b224d..889633d81 100644 --- a/tacker/vnfm/monitor.py +++ b/tacker/vnfm/monitor.py @@ -44,7 +44,9 @@ CONF.register_opts(OPTS, group='monitor') def config_opts(): - return [('monitor', OPTS), ('tacker', VNFMonitor.OPTS)] + return [('monitor', OPTS), + ('tacker', VNFMonitor.OPTS), + ('tacker', VNFAlarmMonitor.OPTS), ] def _log_monitor_events(context, vnf_dict, evt_details): @@ -196,6 +198,72 @@ class VNFMonitor(object): vnf=vnf_dict, kwargs=kwargs) +class VNFAlarmMonitor(object): + """VNF Alarm monitor""" + OPTS = [ + cfg.ListOpt( + 'alarm_monitor_driver', default=['ceilometer'], + help=_('Alarm monitoring driver to communicate with ' + 'Hosting VNF/logical service ' + 'instance tacker plugin will use')), + ] + cfg.CONF.register_opts(OPTS, 'tacker') + + # get alarm here + def __init__(self): + self._alarm_monitor_manager = driver_manager.DriverManager( + 'tacker.tacker.alarm_monitor.drivers', + cfg.CONF.tacker.alarm_monitor_driver) + + def update_vnf_with_alarm(self, vnf, policy_name, policy_dict): + params = dict() + params['vnf_id'] = vnf['id'] + params['mon_policy_name'] = policy_name + _log_monitor_events(t_context.get_admin_context(), + vnf, + "update vnf with alarm") + driver = policy_dict['triggers']['resize_compute'][ + 'event_type']['implementation'] + policy_action = policy_dict['triggers']['resize_compute'].get('action') + if not policy_action: + return + alarm_action_name = policy_action['resize_compute'].get('action_name') + if not alarm_action_name: + return + params['mon_policy_action'] = alarm_action_name + alarm_url = self.call_alarm_url(driver, vnf, params) + _log_monitor_events(t_context.get_admin_context(), + vnf, + "Alarm url invoked") + return alarm_url + # vnf['attribute']['alarm_url'] = alarm_url ---> create + # by plugin or vm_db + + def process_alarm_for_vnf(self, policy): + '''call in plugin''' + vnf = policy['vnf'] + params = policy['params'] + mon_prop = policy['properties'] + alarm_dict = dict() + alarm_dict['alarm_id'] = params['data'].get('alarm_id') + alarm_dict['status'] = params['data'].get('current') + driver = mon_prop['resize_compute']['event_type']['implementation'] + return self.process_alarm(driver, vnf, alarm_dict) + + def _invoke(self, driver, **kwargs): + method = inspect.stack()[1][3] + return self._alarm_monitor_manager.invoke( + driver, method, **kwargs) + + def call_alarm_url(self, driver, vnf_dict, kwargs): + return self._invoke(driver, + vnf=vnf_dict, kwargs=kwargs) + + def process_alarm(self, driver, vnf_dict, kwargs): + return self._invoke(driver, + vnf=vnf_dict, kwargs=kwargs) + + @six.add_metaclass(abc.ABCMeta) class ActionPolicy(object): @classmethod @@ -269,29 +337,65 @@ class ActionRespawnHeat(ActionPolicy): vnf_id = vnf_dict['id'] LOG.info(_('vnf %s dead and to be respawned'), vnf_id) if plugin._mark_vnf_dead(vnf_dict['id']): - plugin._vnf_monitor.mark_dead(vnf_dict['id']) - attributes = vnf_dict['attributes'] - failure_count = int(attributes.get('failure_count', '0')) + 1 - failure_count_str = str(failure_count) - attributes['failure_count'] = failure_count_str - attributes['dead_instance_id_' + failure_count_str] = vnf_dict[ - 'instance_id'] - placement_attr = vnf_dict.get('placement_attr', {}) - region_name = placement_attr.get('region_name') - # kill heat stack - heatclient = openstack.HeatClient(auth_attr=auth_attr, - region_name=region_name) - heatclient.delete(vnf_dict['instance_id']) + if vnf_dict['attributes'].get('monitoring_policy'): + plugin._vnf_monitor.mark_dead(vnf_dict['id']) + attributes = vnf_dict['attributes'] + failure_count = int(attributes.get('failure_count', '0')) + 1 + failure_count_str = str(failure_count) + attributes['failure_count'] = failure_count_str + attributes['dead_instance_id_' + failure_count_str] = vnf_dict[ + 'instance_id'] + placement_attr = vnf_dict.get('placement_attr', {}) + region_name = placement_attr.get('region_name') + # kill heat stack + heatclient = openstack.HeatClient(auth_attr=auth_attr, + region_name=region_name) + heatclient.delete(vnf_dict['instance_id']) - # TODO(anyone) set the current request ctxt instead of admin ctxt - context = t_context.get_admin_context() - _log_monitor_events(context, vnf_dict, - "ActionRespawnHeat invoked") - update_vnf_dict = plugin.create_vnf_sync(context, - vnf_dict) - LOG.info(_('respawned new vnf %s'), update_vnf_dict['id']) - plugin.config_vnf(context, update_vnf_dict) - plugin.add_vnf_to_monitor(update_vnf_dict, auth_attr) + # TODO(anyone) set the current request ctxt + context = t_context.get_admin_context() + _log_monitor_events(context, vnf_dict, + "ActionRespawnHeat invoked") + + update_vnf_dict = plugin.create_vnf_sync(context, + vnf_dict) + LOG.info(_('respawned new vnf %s'), update_vnf_dict['id']) + plugin.config_vnf(context, update_vnf_dict) + plugin.add_vnf_to_monitor(update_vnf_dict, auth_attr) + + if vnf_dict['attributes'].get('alarm_url'): + attributes = vnf_dict['attributes'] + failure_count = int(attributes.get('failure_count', '0')) + 1 + failure_count_str = str(failure_count) + attributes['failure_count'] = failure_count_str + attributes['dead_instance_id_' + failure_count_str] = vnf_dict[ + 'instance_id'] + placement_attr = vnf_dict.get('placement_attr', {}) + region_name = placement_attr.get('region_name') + # kill heat stack + heatclient = openstack.HeatClient(auth_attr=auth_attr, + region_name=region_name) + heatclient.delete(vnf_dict['instance_id']) + vnf_dict['attributes'].pop('alarm_url') + + # TODO(anyone) set the current request ctxt + context = t_context.get_admin_context() + _log_monitor_events(context, vnf_dict, + "ActionRespawnHeat invoked") + update_vnf_dict = plugin.create_vnf_sync(context, + vnf_dict) + plugin.config_vnf(context, update_vnf_dict) + + +@ActionPolicy.register('scaling') +class ActionAutoscalingHeat(ActionPolicy): + @classmethod + def execute_action(cls, plugin, vnf_dict, scale): + vnf_id = vnf_dict['id'] + plugin.create_vnf_scale(t_context.get_admin_context(), vnf_id, scale) + _log_monitor_events(t_context.get_admin_context(), + vnf_dict, + "ActionAutoscalingHeat invoked") @ActionPolicy.register('log') @@ -314,6 +418,7 @@ class ActionLogAndKill(ActionPolicy): "ActionLogAndKill invoked") vnf_id = vnf_dict['id'] if plugin._mark_vnf_dead(vnf_dict['id']): - plugin._vnf_monitor.mark_dead(vnf_dict['id']) + if vnf_dict['attributes'].get('monitoring_policy'): + plugin._vnf_monitor.mark_dead(vnf_dict['id']) plugin.delete_vnf(t_context.get_admin_context(), vnf_id) LOG.error(_('vnf %s dead'), vnf_id) diff --git a/tacker/vnfm/monitor_drivers/ceilometer/__init__.py b/tacker/vnfm/monitor_drivers/ceilometer/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tacker/vnfm/monitor_drivers/ceilometer/ceilometer.py b/tacker/vnfm/monitor_drivers/ceilometer/ceilometer.py new file mode 100644 index 000000000..661d63267 --- /dev/null +++ b/tacker/vnfm/monitor_drivers/ceilometer/ceilometer.py @@ -0,0 +1,92 @@ +# +# 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_config import cfg +from oslo_log import log as logging +import random +import string +from tacker.common import utils +from tacker.vnfm.monitor_drivers import abstract_driver + + +LOG = logging.getLogger(__name__) + +OPTS = [ + cfg.StrOpt('host', default=utils.get_hostname(), + help=_('Address which drivers use to trigger')), + cfg.PortOpt('port', default=9890, + help=_('port number which drivers use to trigger')) +] +cfg.CONF.register_opts(OPTS, group='ceilometer') + + +def config_opts(): + return [('ceilometer', OPTS)] + +ALARM_INFO = ( + ALARM_ACTIONS, OK_ACTIONS, REPEAT_ACTIONS, ALARM, + INSUFFICIENT_DATA_ACTIONS, DESCRIPTION, ENABLED, TIME_CONSTRAINTS, + SEVERITY, +) = ('alarm_actions', 'ok_actions', 'repeat_actions', 'alarm', + 'insufficient_data_actions', 'description', 'enabled', 'time_constraints', + 'severity', + ) + + +class VNFMonitorCeilometer( + abstract_driver.VNFMonitorAbstractDriver): + def get_type(self): + return 'ceilometer' + + def get_name(self): + return 'ceilometer' + + def get_description(self): + return 'Tacker VNFMonitor Ceilometer Driver' + + def _create_alarm_url(self, vnf_id, mon_policy_name, mon_policy_action): + # alarm_url = 'http://host:port/v1.0/vnfs/vnf-uuid/monitoring-policy + # -name/action-name?key=8785' + host = cfg.CONF.ceilometer.host + port = cfg.CONF.ceilometer.port + LOG.info(_("Tacker in heat listening on %(host)s:%(port)s"), + {'host': host, + 'port': port}) + origin = "http://%(host)s:%(port)s/v1.0/vnfs" % { + 'host': host, 'port': port} + access_key = ''.join( + random.SystemRandom().choice( + string.ascii_lowercase + string.digits) + for _ in range(8)) + alarm_url = "".join([origin, '/', vnf_id, '/', mon_policy_name, '/', + mon_policy_action, '/', access_key]) + return alarm_url + + def call_alarm_url(self, vnf, kwargs): + '''must be used after call heat-create in plugin''' + return self._create_alarm_url(**kwargs) + + def _process_alarm(self, alarm_id, status): + if alarm_id and status == ALARM: + return True + + def process_alarm(self, vnf, kwargs): + '''Check alarm state. if available, will be processed''' + return self._process_alarm(**kwargs) + + def monitor_url(self, plugin, context, vnf): + pass + + def monitor_call(self, vnf, kwargs): + pass diff --git a/tacker/vnfm/monitor_drivers/token.py b/tacker/vnfm/monitor_drivers/token.py new file mode 100644 index 000000000..8fafdaec7 --- /dev/null +++ b/tacker/vnfm/monitor_drivers/token.py @@ -0,0 +1,37 @@ +# Copyright 2012 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. +from keystoneauth1.identity import v3 +from keystoneauth1 import session + + +class Token(object): + def __init__(self, username, password, project_name, + auth_url, user_domain_name, project_domain_name): + self.username = username + self.password = password + self.auth_url = auth_url + self.project_name = project_name + self.user_domain_name = user_domain_name + self.project_domain_name = project_domain_name + + def create_token(self): + auth = v3.Password(auth_url=self.auth_url, + username=self.username, + password=self.password, + project_name=self.project_name, + user_domain_name=self.user_domain_name, + project_domain_name=self.project_domain_name) + sess = session.Session(auth=auth) + token_id = sess.auth.get_token(sess) + return token_id diff --git a/tacker/vnfm/plugin.py b/tacker/vnfm/plugin.py index 19a5809fa..6c3f894c8 100644 --- a/tacker/vnfm/plugin.py +++ b/tacker/vnfm/plugin.py @@ -129,6 +129,7 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin): 'tacker.tacker.vnfm.drivers', cfg.CONF.tacker.infra_driver) self._vnf_monitor = monitor.VNFMonitor(self.boot_wait) + self._vnf_alarm_monitor = monitor.VNFAlarmMonitor() def spawn_n(self, function, *args, **kwargs): self._pool.spawn_n(function, *args, **kwargs) @@ -246,6 +247,19 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin): LOG.debug('hosting_vnf: %s', hosting_vnf) self._vnf_monitor.add_hosting_vnf(hosting_vnf) + def add_alarm_url_to_vnf(self, vnf_dict): + vnfd_yaml = vnf_dict['vnfd']['attributes'].get('vnfd', '') + vnfd_dict = yaml.load(vnfd_yaml) + if vnfd_dict and vnfd_dict.get('tosca_definitions_version'): + polices = vnfd_dict['topology_template'].get('policies', []) + for policy_dict in polices: + name, policy = policy_dict.items()[0] + if policy['type'] in constants.POLICY_ALARMING: + alarm_url = self._vnf_alarm_monitor.update_vnf_with_alarm( + vnf_dict, name, policy) + vnf_dict['attributes']['alarm_url'] = alarm_url + break + def config_vnf(self, context, vnf_dict): config = vnf_dict['attributes'].get('config') if not config: @@ -327,6 +341,7 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin): vnf_id = vnf_dict['id'] LOG.debug(_('vnf_dict %s'), vnf_dict) self.mgmt_create_pre(context, vnf_dict) + self.add_alarm_url_to_vnf(vnf_dict) try: instance_id = self._vnf_manager.invoke( driver_name, 'create', plugin=self, @@ -640,8 +655,8 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin): def _make_policy_dict(self, vnf, name, policy): p = {} - p['type'] = policy['type'] - p['properties'] = policy['properties'] + p['type'] = policy.get('type') + p['properties'] = policy.get('properties') or policy.get('triggers') p['vnf'] = vnf p['name'] = name p['id'] = p['name'] @@ -694,6 +709,81 @@ class VNFMPlugin(vnfm_db.VNFMPluginDb, VNFMMgmtMixin): return scale['scale'] + def _validate_alarming_policy(self, context, policy): + vnf_id = policy['vnf']['id'] + # validate policy type + type = policy['type'] + if type not in constants.POLICY_ALARMING: + raise exceptions.VnfPolicyTypeInvalid( + type=type, + valid_types=constants.POLICY_ALARMING, + policy=policy['id'] + ) + # validate alarm status + if not self._vnf_alarm_monitor.process_alarm_for_vnf(policy): + raise exceptions.AlarmUrlInvalid(vnf_id=vnf_id) + + # validate policy action + action = policy['action_name'] + policy_ = self.get_vnf_policy(context, action, vnf_id) + if not policy_ and action not in constants.DEFAULT_ALARM_ACTIONS: + raise exceptions.VnfPolicyNotFound( + vnf_id=action, + policy=policy['id'] + ) + LOG.debug(_("Policy %s is validated successfully") % policy) + return policy_ + # validate url + + def _handle_vnf_monitoring(self, context, policy): + vnf_dict = policy['vnf'] + if policy['action_name'] in constants.DEFAULT_ALARM_ACTIONS: + action = policy['action_name'] + LOG.debug(_('vnf for monitoring: %s'), vnf_dict) + vim_auth = self.get_vim(context, vnf_dict) + action_cls = monitor.ActionPolicy.get_policy(action, + vnf_dict) + if action_cls: + if action == 'notify': + action_cls.execute_action(self, policy, vim_auth) + else: + action_cls.execute_action(self, vnf_dict, vim_auth) + + if policy['bckend_policy']: + bckend_policy = policy['bckend_policy'] + bckend_policy_type = bckend_policy['type'] + cp = policy['properties']['resize_compute']['condition'].\ + get('comparison_operator') + if bckend_policy_type == constants.POLICY_SCALING: + action = 'scaling' + scale = {} + scale.setdefault('scale', {}) + scale['scale']['type'] = 'out' if cp == 'gt' else 'in' + scale['scale']['policy'] = bckend_policy['name'] + action_cls = monitor.ActionPolicy.get_policy(action, + vnf_dict) + if action_cls: + action_cls.execute_action(self, vnf_dict, scale) + + def create_vnf_trigger( + self, context, vnf_id, trigger): + # Verified API: pending + # Need to use: _make_policy_dict, get_vnf_policies, get_vnf_policy + # action: scaling, refer to template to find specific scaling policy + # we can extend in future to support other policies + # Monitoring policy should be describe in heat_template_yaml. + # Create first + policy_ = self.get_vnf_policy(context, + trigger['trigger']['policy_name'], + vnf_id) + policy_.update({'action_name': trigger['trigger']['action_name']}) + policy_.update({'params': trigger['trigger']['params']}) + bk_policy = self._validate_alarming_policy(context, policy_) + policy_.update({'bckend_policy': bk_policy}) + self._handle_vnf_monitoring(context, policy_) + + return trigger['trigger'] + def get_vnf_resources(self, context, vnf_id, fields=None, filters=None): vnf_info = self.get_vnf(context, vnf_id) infra_driver, vim_auth = self._get_infra_driver(context, vnf_info) diff --git a/tacker/vnfm/tosca/lib/tacker_defs.yaml b/tacker/vnfm/tosca/lib/tacker_defs.yaml index 0c58e73da..3267d79dd 100644 --- a/tacker/vnfm/tosca/lib/tacker_defs.yaml +++ b/tacker/vnfm/tosca/lib/tacker_defs.yaml @@ -125,6 +125,29 @@ policy_types: properties: name: http-ping + tosca.policies.tacker.Alarming: + derived_from: tosca.policies.Monitoring + triggers: + resize_compute: + event_type: + type: map + entry_schema: + type: string + required: true + metrics: + type: string + required: true + condition: + type: map + entry_schema: + type: string + required: false + action: + type: map + entry_schema: + type: string + required: true + tosca.policies.tacker.Scaling: derived_from: tosca.policies.Scaling description: Defines policy for scaling the given targets.