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:
parent
0988954e15
commit
2d8baa76bd
@ -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
|
||||
|
177
doc/source/devref/alarm_monitoring_usage_guide.rst
Normal file
177
doc/source/devref/alarm_monitoring_usage_guide.rst
Normal 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.
|
@ -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
|
||||
=================
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
67
samples/tosca-templates/vnfd/tosca-vnfd-alarm.yaml
Normal file
67
samples/tosca-templates/vnfd/tosca-vnfd-alarm.yaml
Normal 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
|
@ -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
92
tacker/alarm_receiver.py
Normal 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
|
@ -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")
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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: ''
|
60
tacker/tests/unit/vm/test_alarm_receiver.py
Normal file
60
tacker/tests/unit/vm/test_alarm_receiver.py
Normal 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'])
|
@ -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)
|
||||
|
@ -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']
|
||||
|
@ -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)
|
||||
|
0
tacker/vnfm/monitor_drivers/ceilometer/__init__.py
Normal file
0
tacker/vnfm/monitor_drivers/ceilometer/__init__.py
Normal file
92
tacker/vnfm/monitor_drivers/ceilometer/ceilometer.py
Normal file
92
tacker/vnfm/monitor_drivers/ceilometer/ceilometer.py
Normal 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
|
37
tacker/vnfm/monitor_drivers/token.py
Normal file
37
tacker/vnfm/monitor_drivers/token.py
Normal 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
|
@ -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)
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user