Implement Alarm monitor

Add definition of alarm monitor in VNFD TOSCA template definition,
support it in VNFM plugin. It can integrate with scaling feature.

An WSGI filter is used to receive ceilometer action URL call and
translate the call into inner action, such as scalling.

The configuration group alarm_auth in tacker.conf is used as the
credentials for ceilometer action URL call. they must have 'admin'
role to query all related stuff for the action.

Implements blueprint: #alarm-based-monitoring-driver

Co-Authored-By: dharmendra kushwaha <dharmendra.kushwaha@nectechnologies.in>
Co-Authored-By: gong yong sheng <gong.yongsheng@99cloud.net>
Change-Id: I7f1dbae361a5dfb97a86e8532bfd09813ce535e2
This commit is contained in:
doantungbk 2016-09-04 21:31:53 -07:00 committed by dharmendra
parent 0988954e15
commit 2d8baa76bd
23 changed files with 1094 additions and 85 deletions

View File

@ -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

View File

@ -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.

View File

@ -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
=================

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

92
tacker/alarm_receiver.py Normal file
View File

@ -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

View File

@ -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")

View File

@ -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': {
@ -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

View File

@ -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"

View File

@ -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
)

View File

@ -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

View File

@ -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: ''

View File

@ -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'])

View File

@ -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,15 +30,26 @@ class TestSamples(testtools.TestCase):
possible to translate into HOT template.
"""
def test_samples(self):
for f in get_list_of_samples():
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
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)
@ -74,3 +78,14 @@ class TestSamples(testtools.TestCase):
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)

View File

@ -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']

View File

@ -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,6 +337,7 @@ 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']):
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
@ -283,16 +352,51 @@ class ActionRespawnHeat(ActionPolicy):
region_name=region_name)
heatclient.delete(vnf_dict['instance_id'])
# TODO(anyone) set the current request ctxt instead of admin ctxt
# 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')
class ActionLogOnly(ActionPolicy):
@ -314,6 +418,7 @@ class ActionLogAndKill(ActionPolicy):
"ActionLogAndKill invoked")
vnf_id = vnf_dict['id']
if plugin._mark_vnf_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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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.