Migrate to aodh for OS::Ceilometer::Alarm

This changes:
1. use aodhclient to manage OS::Ceilometer::Alarm
resource, including create, update, delete, check, suspend,
resume and show.
2. rename OS::Ceilometer::Alarm to OS::Aodh::Alarm
3. considering to compatible with old templates with resource
OS::Ceilometer::Alarm, set resource_registry to map Ceilometer alarm
to Aodh alarm

Blueprint migrate-to-use-aodh-for-alarms

Change-Id: I6e2d14f15a345b927b53adc237cf2bf4010842f0
This commit is contained in:
huangtianhua 2016-06-08 15:50:39 +08:00
parent 47262c079d
commit 4a79f7ca53
11 changed files with 344 additions and 376 deletions

View File

@ -5,5 +5,6 @@ resource_registry:
# Choose your implementation of AWS::CloudWatch::Alarm
"AWS::CloudWatch::Alarm": "file:///etc/heat/templates/AWS_CloudWatch_Alarm.yaml"
#"AWS::CloudWatch::Alarm": "OS::Heat::CWLiteAlarm"
"OS::Metering::Alarm": "OS::Ceilometer::Alarm"
"OS::Metering::Alarm": "OS::Aodh::Alarm"
"AWS::RDS::DBInstance": "file:///etc/heat/templates/AWS_RDS_DBInstance.yaml"
"OS::Ceilometer::Alarm": "OS::Aodh::Alarm"

View File

@ -60,7 +60,7 @@ Mappings:
Resources:
__alarm__:
Type: OS::Ceilometer::Alarm
Type: OS::Aodh::Alarm
Properties:
description:
Ref: AlarmDescription

View File

@ -0,0 +1,211 @@
#
# 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 heat.common.i18n import _
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
from heat.engine import support
COMMON_PROPERTIES = (
ALARM_ACTIONS, OK_ACTIONS, REPEAT_ACTIONS,
INSUFFICIENT_DATA_ACTIONS, DESCRIPTION, ENABLED, TIME_CONSTRAINTS,
SEVERITY,
) = (
'alarm_actions', 'ok_actions', 'repeat_actions',
'insufficient_data_actions', 'description', 'enabled', 'time_constraints',
'severity',
)
_TIME_CONSTRAINT_KEYS = (
NAME, START, DURATION, TIMEZONE, TIME_CONSTRAINT_DESCRIPTION,
) = (
'name', 'start', 'duration', 'timezone', 'description',
)
common_properties_schema = {
DESCRIPTION: properties.Schema(
properties.Schema.STRING,
_('Description for the alarm.'),
update_allowed=True
),
ENABLED: properties.Schema(
properties.Schema.BOOLEAN,
_('True if alarm evaluation/actioning is enabled.'),
default='true',
update_allowed=True
),
ALARM_ACTIONS: properties.Schema(
properties.Schema.LIST,
_('A list of URLs (webhooks) to invoke when state transitions to '
'alarm.'),
update_allowed=True
),
OK_ACTIONS: properties.Schema(
properties.Schema.LIST,
_('A list of URLs (webhooks) to invoke when state transitions to '
'ok.'),
update_allowed=True
),
INSUFFICIENT_DATA_ACTIONS: properties.Schema(
properties.Schema.LIST,
_('A list of URLs (webhooks) to invoke when state transitions to '
'insufficient-data.'),
update_allowed=True
),
REPEAT_ACTIONS: properties.Schema(
properties.Schema.BOOLEAN,
_("False to trigger actions when the threshold is reached AND "
"the alarm's state has changed. By default, actions are called "
"each time the threshold is reached."),
default='true',
update_allowed=True
),
SEVERITY: properties.Schema(
properties.Schema.STRING,
_('Severity of the alarm.'),
default='low',
constraints=[
constraints.AllowedValues(['low', 'moderate', 'critical'])
],
update_allowed=True,
support_status=support.SupportStatus(version='5.0.0'),
),
TIME_CONSTRAINTS: properties.Schema(
properties.Schema.LIST,
_('Describe time constraints for the alarm. '
'Only evaluate the alarm if the time at evaluation '
'is within this time constraint. Start point(s) of '
'the constraint are specified with a cron expression, '
'whereas its duration is given in seconds.'
),
schema=properties.Schema(
properties.Schema.MAP,
schema={
NAME: properties.Schema(
properties.Schema.STRING,
_("Name for the time constraint."),
required=True
),
START: properties.Schema(
properties.Schema.STRING,
_("Start time for the time constraint. "
"A CRON expression property."),
constraints=[
constraints.CustomConstraint(
'cron_expression')
],
required=True
),
TIME_CONSTRAINT_DESCRIPTION: properties.Schema(
properties.Schema.STRING,
_("Description for the time constraint."),
),
DURATION: properties.Schema(
properties.Schema.INTEGER,
_("Duration for the time constraint."),
constraints=[
constraints.Range(min=0)
],
required=True
),
TIMEZONE: properties.Schema(
properties.Schema.STRING,
_("Timezone for the time constraint "
"(eg. 'Taiwan/Taipei', 'Europe/Amsterdam')."),
constraints=[
constraints.CustomConstraint('timezone')
],
)
}
),
support_status=support.SupportStatus(version='5.0.0'),
default=[],
)
}
NOVA_METERS = ['instance', 'memory', 'memory.usage',
'cpu', 'cpu_util', 'vcpus',
'disk.read.requests', 'disk.read.requests.rate',
'disk.write.requests', 'disk.write.requests.rate',
'disk.read.bytes', 'disk.read.bytes.rate',
'disk.write.bytes', 'disk.write.bytes.rate',
'disk.device.read.requests', 'disk.device.read.requests.rate',
'disk.device.write.requests', 'disk.device.write.requests.rate',
'disk.device.read.bytes', 'disk.device.read.bytes.rate',
'disk.device.write.bytes', 'disk.device.write.bytes.rate',
'disk.root.size', 'disk.ephemeral.size',
'network.incoming.bytes', 'network.incoming.bytes.rate',
'network.outgoing.bytes', 'network.outgoing.bytes.rate',
'network.incoming.packets', 'network.incoming.packets.rate',
'network.outgoing.packets', 'network.outgoing.packets.rate']
class BaseAlarm(resource.Resource):
"""Base Alarm Manager."""
default_client_name = 'aodh'
entity = 'alarm'
alarm_type = 'threshold'
def actions_to_urls(self, props):
kwargs = {}
for k, v in iter(props.items()):
if k in [ALARM_ACTIONS, OK_ACTIONS,
INSUFFICIENT_DATA_ACTIONS] and v is not None:
kwargs[k] = []
for act in v:
# if the action is a resource name
# we ask the destination resource for an alarm url.
# the template writer should really do this in the
# template if possible with:
# {Fn::GetAtt: ['MyAction', 'AlarmUrl']}
if act in self.stack:
url = self.stack[act].FnGetAtt('AlarmUrl')
kwargs[k].append(url)
else:
if act:
kwargs[k].append(act)
else:
kwargs[k] = v
return kwargs
def _reformat_properties(self, props):
rule = {}
for name in self.PROPERTIES:
value = props.pop(name, None)
if value:
rule[name] = value
if rule:
props['%s_rule' % self.alarm_type] = rule
return props
def handle_suspend(self):
if self.resource_id is not None:
alarm_update = {'enabled': False}
self.client().alarm.update(self.resource_id,
alarm_update)
def handle_resume(self):
if self.resource_id is not None:
alarm_update = {'enabled': True}
self.client().alarm.update(self.resource_id,
alarm_update)
def handle_check(self):
self.client().alarm.get(self.resource_id)

View File

@ -17,172 +17,13 @@ from heat.common import exception
from heat.common.i18n import _
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
from heat.engine.resources import alarm_base
from heat.engine import support
from heat.engine import watchrule
COMMON_PROPERTIES = (
ALARM_ACTIONS, OK_ACTIONS, REPEAT_ACTIONS,
INSUFFICIENT_DATA_ACTIONS, DESCRIPTION, ENABLED, TIME_CONSTRAINTS,
SEVERITY,
) = (
'alarm_actions', 'ok_actions', 'repeat_actions',
'insufficient_data_actions', 'description', 'enabled', 'time_constraints',
'severity',
)
_TIME_CONSTRAINT_KEYS = (
NAME, START, DURATION, TIMEZONE, TIME_CONSTRAINT_DESCRIPTION,
) = (
'name', 'start', 'duration', 'timezone', 'description',
)
common_properties_schema = {
DESCRIPTION: properties.Schema(
properties.Schema.STRING,
_('Description for the alarm.'),
update_allowed=True
),
ENABLED: properties.Schema(
properties.Schema.BOOLEAN,
_('True if alarm evaluation/actioning is enabled.'),
default='true',
update_allowed=True
),
ALARM_ACTIONS: properties.Schema(
properties.Schema.LIST,
_('A list of URLs (webhooks) to invoke when state transitions to '
'alarm.'),
update_allowed=True
),
OK_ACTIONS: properties.Schema(
properties.Schema.LIST,
_('A list of URLs (webhooks) to invoke when state transitions to '
'ok.'),
update_allowed=True
),
INSUFFICIENT_DATA_ACTIONS: properties.Schema(
properties.Schema.LIST,
_('A list of URLs (webhooks) to invoke when state transitions to '
'insufficient-data.'),
update_allowed=True
),
REPEAT_ACTIONS: properties.Schema(
properties.Schema.BOOLEAN,
_("False to trigger actions when the threshold is reached AND "
"the alarm's state has changed. By default, actions are called "
"each time the threshold is reached."),
default='true',
update_allowed=True
),
SEVERITY: properties.Schema(
properties.Schema.STRING,
_('Severity of the alarm.'),
default='low',
constraints=[
constraints.AllowedValues(['low', 'moderate', 'critical'])
],
update_allowed=True,
support_status=support.SupportStatus(version='5.0.0'),
),
TIME_CONSTRAINTS: properties.Schema(
properties.Schema.LIST,
_('Describe time constraints for the alarm. '
'Only evaluate the alarm if the time at evaluation '
'is within this time constraint. Start point(s) of '
'the constraint are specified with a cron expression, '
'whereas its duration is given in seconds.'
),
schema=properties.Schema(
properties.Schema.MAP,
schema={
NAME: properties.Schema(
properties.Schema.STRING,
_("Name for the time constraint."),
required=True
),
START: properties.Schema(
properties.Schema.STRING,
_("Start time for the time constraint. "
"A CRON expression property."),
constraints=[
constraints.CustomConstraint(
'cron_expression')
],
required=True
),
TIME_CONSTRAINT_DESCRIPTION: properties.Schema(
properties.Schema.STRING,
_("Description for the time constraint."),
),
DURATION: properties.Schema(
properties.Schema.INTEGER,
_("Duration for the time constraint."),
constraints=[
constraints.Range(min=0)
],
required=True
),
TIMEZONE: properties.Schema(
properties.Schema.STRING,
_("Timezone for the time constraint "
"(eg. 'Taiwan/Taipei', 'Europe/Amsterdam')."),
constraints=[
constraints.CustomConstraint('timezone')
],
)
}
),
support_status=support.SupportStatus(version='5.0.0'),
default=[],
)
}
NOVA_METERS = ['instance', 'memory', 'memory.usage',
'cpu', 'cpu_util', 'vcpus',
'disk.read.requests', 'disk.read.requests.rate',
'disk.write.requests', 'disk.write.requests.rate',
'disk.read.bytes', 'disk.read.bytes.rate',
'disk.write.bytes', 'disk.write.bytes.rate',
'disk.device.read.requests', 'disk.device.read.requests.rate',
'disk.device.write.requests', 'disk.device.write.requests.rate',
'disk.device.read.bytes', 'disk.device.read.bytes.rate',
'disk.device.write.bytes', 'disk.device.write.bytes.rate',
'disk.root.size', 'disk.ephemeral.size',
'network.incoming.bytes', 'network.incoming.bytes.rate',
'network.outgoing.bytes', 'network.outgoing.bytes.rate',
'network.incoming.packets', 'network.incoming.packets.rate',
'network.outgoing.packets', 'network.outgoing.packets.rate']
def actions_to_urls(stack, properties):
kwargs = {}
for k, v in iter(properties.items()):
if k in [ALARM_ACTIONS, OK_ACTIONS,
INSUFFICIENT_DATA_ACTIONS] and v is not None:
kwargs[k] = []
for act in v:
# if the action is a resource name
# we ask the destination resource for an alarm url.
# the template writer should really do this in the
# template if possible with:
# {Fn::GetAtt: ['MyAction', 'AlarmUrl']}
if act in stack:
url = stack[act].FnGetAtt('AlarmUrl')
kwargs[k].append(url)
else:
if act:
kwargs[k].append(act)
else:
kwargs[k] = v
return kwargs
class CeilometerAlarm(resource.Resource):
"""A resource that implements alarming service of Ceilometer.
class AodhAlarm(alarm_base.BaseAlarm):
"""A resource that implements alarming service of Aodh.
A resource that allows for the setting alarms based on threshold evaluation
for a collection of samples. Also, you can define actions to take if state
@ -290,18 +131,15 @@ class CeilometerAlarm(resource.Resource):
)
)
}
properties_schema.update(common_properties_schema)
default_client_name = 'ceilometer'
properties_schema.update(alarm_base.common_properties_schema)
entity = 'alarms'
def cfn_to_ceilometer(self, stack, properties):
def get_alarm_props(self, props):
"""Apply all relevant compatibility xforms."""
kwargs = actions_to_urls(stack, properties)
kwargs['type'] = 'threshold'
if kwargs.get(self.METER_NAME) in NOVA_METERS:
kwargs = self.actions_to_urls(props)
kwargs['type'] = self.alarm_type
if kwargs.get(self.METER_NAME) in alarm_base.NOVA_METERS:
prefix = 'user_metadata.'
else:
prefix = 'metering.'
@ -312,8 +150,8 @@ class CeilometerAlarm(resource.Resource):
if field in kwargs:
rule[field] = kwargs[field]
del kwargs[field]
mmd = properties.get(self.MATCHING_METADATA) or {}
query = properties.get(self.QUERY) or []
mmd = props.get(self.MATCHING_METADATA) or {}
query = props.get(self.QUERY) or []
# make sure the matching_metadata appears in the query like this:
# {field: metadata.$prefix.x, ...}
@ -339,11 +177,10 @@ class CeilometerAlarm(resource.Resource):
return kwargs
def handle_create(self):
props = self.cfn_to_ceilometer(self.stack,
self.properties)
props = self.get_alarm_props(self.properties)
props['name'] = self.physical_resource_name()
alarm = self.client().alarms.create(**props)
self.resource_id_set(alarm.alarm_id)
alarm = self.client().alarm.create(props)
self.resource_id_set(alarm['alarm_id'])
# the watchrule below is for backwards compatibility.
# 1) so we don't create watch tasks unnecessarily
@ -358,21 +195,11 @@ class CeilometerAlarm(resource.Resource):
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
if prop_diff:
kwargs = {'alarm_id': self.resource_id}
kwargs = {}
kwargs.update(self.properties)
kwargs.update(prop_diff)
alarms_client = self.client().alarms
alarms_client.update(**self.cfn_to_ceilometer(self.stack, kwargs))
def handle_suspend(self):
if self.resource_id is not None:
self.client().alarms.update(alarm_id=self.resource_id,
enabled=False)
def handle_resume(self):
if self.resource_id is not None:
self.client().alarms.update(alarm_id=self.resource_id,
enabled=True)
self.client().alarm.update(self.resource_id,
self.get_alarm_props(kwargs))
def handle_delete(self):
try:
@ -382,45 +209,37 @@ class CeilometerAlarm(resource.Resource):
except exception.EntityNotFound:
pass
return super(CeilometerAlarm, self).handle_delete()
return super(AodhAlarm, self).handle_delete()
def handle_check(self):
watch_name = self.physical_resource_name()
watchrule.WatchRule.load(self.context, watch_name=watch_name)
self.client().alarms.get(self.resource_id)
self.client().alarm.get(self.resource_id)
def _show_resource(self):
return self.client().alarm.get(self.resource_id)
class BaseCeilometerAlarm(resource.Resource):
class BaseCeilometerAlarm(alarm_base.BaseAlarm):
default_client_name = 'ceilometer'
entity = 'alarms'
def handle_create(self):
properties = actions_to_urls(self.stack,
self.properties)
properties['name'] = self.physical_resource_name()
properties['type'] = self.ceilometer_alarm_type
props = self.actions_to_urls(self.properties)
props['name'] = self.physical_resource_name()
props['type'] = self.alarm_type
alarm = self.client().alarms.create(
**self._reformat_properties(properties))
**self._reformat_properties(props))
self.resource_id_set(alarm.alarm_id)
def _reformat_properties(self, properties):
rule = {}
for name in self.PROPERTIES:
value = properties.pop(name, None)
if value:
rule[name] = value
if rule:
properties['%s_rule' % self.ceilometer_alarm_type] = rule
return properties
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
if prop_diff:
kwargs = {'alarm_id': self.resource_id}
kwargs.update(prop_diff)
alarms_client = self.client().alarms
alarms_client.update(**self._reformat_properties(
actions_to_urls(self.stack, kwargs)))
self.actions_to_urls(kwargs)))
def handle_suspend(self):
self.client().alarms.update(
@ -463,13 +282,13 @@ class CombinationAlarm(BaseCeilometerAlarm):
constraints=[constraints.AllowedValues(['and', 'or'])],
update_allowed=True)
}
properties_schema.update(common_properties_schema)
properties_schema.update(alarm_base.common_properties_schema)
ceilometer_alarm_type = 'combination'
alarm_type = 'combination'
def resource_mapping():
return {
'OS::Ceilometer::Alarm': CeilometerAlarm,
'OS::Aodh::Alarm': AodhAlarm,
'OS::Ceilometer::CombinationAlarm': CombinationAlarm,
}

View File

@ -15,6 +15,7 @@
from heat.common.i18n import _
from heat.engine import constraints
from heat.engine import properties
from heat.engine.resources import alarm_base
from heat.engine.resources.openstack.ceilometer import alarm
from heat.engine import support
@ -103,9 +104,9 @@ class CeilometerGnocchiResourcesAlarm(alarm.BaseCeilometerAlarm):
),
}
properties_schema.update(common_gnocchi_properties_schema)
properties_schema.update(alarm.common_properties_schema)
properties_schema.update(alarm_base.common_properties_schema)
ceilometer_alarm_type = 'gnocchi_resources_threshold'
alarm_type = 'gnocchi_resources_threshold'
class CeilometerGnocchiAggregationByMetricsAlarm(
@ -130,9 +131,9 @@ class CeilometerGnocchiAggregationByMetricsAlarm(
),
}
properties_schema.update(common_gnocchi_properties_schema)
properties_schema.update(alarm.common_properties_schema)
properties_schema.update(alarm_base.common_properties_schema)
ceilometer_alarm_type = 'gnocchi_aggregation_by_metrics_threshold'
alarm_type = 'gnocchi_aggregation_by_metrics_threshold'
class CeilometerGnocchiAggregationByResourcesAlarm(
@ -175,9 +176,9 @@ class CeilometerGnocchiAggregationByResourcesAlarm(
}
properties_schema.update(common_gnocchi_properties_schema)
properties_schema.update(alarm.common_properties_schema)
properties_schema.update(alarm_base.common_properties_schema)
ceilometer_alarm_type = 'gnocchi_aggregation_by_resources_threshold'
alarm_type = 'gnocchi_aggregation_by_resources_threshold'
def resource_mapping():

View File

@ -143,7 +143,7 @@ class CloudWatchAlarm(resource.Resource):
support_status = support.SupportStatus(
status=support.HIDDEN,
message=_('OS::Heat::CWLiteAlarm is deprecated, '
'use OS::Ceilometer::Alarm instead.'),
'use OS::Aodh::Alarm instead.'),
version='5.0.0',
previous_status=support.SupportStatus(
status=support.DEPRECATED,

View File

@ -20,8 +20,8 @@ import six
from heat.common import exception
from heat.common import template_format
from heat.engine.clients.os import aodh
from heat.engine.clients.os import ceilometer
from heat.engine import properties as props
from heat.engine.resources.openstack.ceilometer import alarm
from heat.engine import rsrc_defn
from heat.engine import scheduler
@ -39,7 +39,7 @@ alarm_template = '''
"Parameters" : {},
"Resources" : {
"MEMAlarmHigh": {
"Type": "OS::Ceilometer::Alarm",
"Type": "OS::Aodh::Alarm",
"Properties": {
"description": "Scale-up if MEM > 50% for 1 minute",
"meter_name": "MemoryUtilization",
@ -66,7 +66,7 @@ alarm_template_with_time_constraints = '''
"Parameters" : {},
"Resources" : {
"MEMAlarmHigh": {
"Type": "OS::Ceilometer::Alarm",
"Type": "OS::Aodh::Alarm",
"Properties": {
"description": "Scale-up if MEM > 50% for 1 minute",
"meter_name": "MemoryUtilization",
@ -100,7 +100,7 @@ not_string_alarm_template = '''
"Parameters" : {},
"Resources" : {
"MEMAlarmHigh": {
"Type": "OS::Ceilometer::Alarm",
"Type": "OS::Aodh::Alarm",
"Properties": {
"description": "Scale-up if MEM > 50% for 1 minute",
"meter_name": "MemoryUtilization",
@ -146,9 +146,13 @@ class FakeCeilometerAlarm(object):
self.to_dict = lambda: {'attr': 'val'}
class CeilometerAlarmTest(common.HeatTestCase):
FakeAodhAlarm = {'other_attrs': 'val',
'alarm_id': 'foo'}
class AodhAlarmTest(common.HeatTestCase):
def setUp(self):
super(CeilometerAlarmTest, self).setUp()
super(AodhAlarmTest, self).setUp()
self.fa = mock.Mock()
def create_stack(self, template=None, time_constraints=None):
@ -162,46 +166,14 @@ class CeilometerAlarmTest(common.HeatTestCase):
disable_rollback=True)
stack.store()
self.m.StubOutWithMock(ceilometer.CeilometerClientPlugin, '_create')
ceilometer.CeilometerClientPlugin._create().AndReturn(self.fa)
self.patchobject(aodh.AodhClientPlugin,
'_create').return_value = self.fa
al = copy.deepcopy(temp['Resources']['MEMAlarmHigh']['Properties'])
al['description'] = mox.IgnoreArg()
al['name'] = mox.IgnoreArg()
al['alarm_actions'] = mox.IgnoreArg()
al['insufficient_data_actions'] = None
al['ok_actions'] = None
al['repeat_actions'] = True
al['enabled'] = True
al['time_constraints'] = time_constraints if time_constraints else []
al['severity'] = 'low'
rule = dict(
period=60,
evaluation_periods=1,
threshold=50)
for field in ['period', 'evaluation_periods', 'threshold']:
del al[field]
for field in ['statistic', 'comparison_operator', 'meter_name']:
rule[field] = al[field]
del al[field]
if 'query' in al and al['query']:
query = al['query']
else:
query = []
if 'query' in al:
del al['query']
if 'matching_metadata' in al and al['matching_metadata']:
for k, v in al['matching_metadata'].items():
key = 'metadata.metering.' + k
query.append(dict(field=key, op='eq', value=six.text_type(v)))
if 'matching_metadata' in al:
del al['matching_metadata']
if query:
rule['query'] = mox.SameElementsAs(query)
al['threshold_rule'] = rule
al['type'] = 'threshold'
self.m.StubOutWithMock(self.fa.alarms, 'create')
self.fa.alarms.create(**al).AndReturn(FakeCeilometerAlarm())
self.patchobject(self.fa.alarm, 'create').return_value = FakeAodhAlarm
return stack
def test_mem_alarm_high_update_no_replace(self):
@ -214,37 +186,15 @@ class CeilometerAlarmTest(common.HeatTestCase):
properties['matching_metadata'] = {'a': 'v'}
properties['query'] = [dict(field='b', op='eq', value='w')]
self.stack = self.create_stack(template=json.dumps(t))
self.m.StubOutWithMock(self.fa.alarms, 'update')
schema = props.schemata(alarm.CeilometerAlarm.properties_schema)
exns = ['period', 'evaluation_periods', 'threshold',
'statistic', 'comparison_operator', 'meter_name',
'matching_metadata', 'query']
al2 = dict((k, mox.IgnoreArg())
for k, s in schema.items()
if s.update_allowed and k not in exns)
al2['time_constraints'] = mox.IgnoreArg()
al2['alarm_id'] = mox.IgnoreArg()
al2['type'] = 'threshold'
al2['threshold_rule'] = dict(
meter_name=properties['meter_name'],
period=90,
evaluation_periods=2,
threshold=39,
statistic='max',
comparison_operator='lt',
query=[
dict(field='c', op='ne', value='z'),
dict(field='metadata.metering.x', op='eq', value='y')
])
self.fa.alarms.update(**al2).AndReturn(None)
test_stack = self.create_stack(template=json.dumps(t))
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['MEMAlarmHigh']
update_mock = self.patchobject(self.fa.alarm, 'update')
properties = copy.copy(rsrc.properties.data)
properties.update({
test_stack.create()
rsrc = test_stack['MEMAlarmHigh']
update_props = copy.deepcopy(rsrc.properties.data)
update_props.update({
'comparison_operator': 'lt',
'description': 'fruity',
'evaluation_periods': '2',
@ -259,13 +209,15 @@ class CeilometerAlarmTest(common.HeatTestCase):
'matching_metadata': {'x': 'y'},
'query': [dict(field='c', op='ne', value='z')]
})
snippet = rsrc_defn.ResourceDefinition(rsrc.name,
rsrc.type(),
properties)
update_props)
scheduler.TaskRunner(rsrc.update, snippet)()
self.m.VerifyAll()
self.assertEqual((rsrc.UPDATE, rsrc.COMPLETE), rsrc.state)
self.assertEqual(1, update_mock.call_count)
def test_mem_alarm_high_update_replace(self):
"""Tests resource replacing when changing non-updatable properties."""
@ -275,11 +227,10 @@ class CeilometerAlarmTest(common.HeatTestCase):
properties['alarm_actions'] = ['signal_handler']
properties['matching_metadata'] = {'a': 'v'}
self.stack = self.create_stack(template=json.dumps(t))
test_stack = self.create_stack(template=json.dumps(t))
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['MEMAlarmHigh']
test_stack.create()
rsrc = test_stack['MEMAlarmHigh']
properties = copy.copy(rsrc.properties.data)
properties['meter_name'] = 'temp'
@ -290,40 +241,34 @@ class CeilometerAlarmTest(common.HeatTestCase):
updater = scheduler.TaskRunner(rsrc.update, snippet)
self.assertRaises(exception.UpdateReplace, updater)
self.m.VerifyAll()
def test_mem_alarm_suspend_resume(self):
"""Tests suspending and resuming of the alarm.
Make sure that the Alarm resource gets disabled on suspend
and re-enabled on resume.
"""
self.stack = self.create_stack()
test_stack = self.create_stack()
self.m.StubOutWithMock(self.fa.alarms, 'update')
al_suspend = {'alarm_id': mox.IgnoreArg(),
'enabled': False}
self.fa.alarms.update(**al_suspend).AndReturn(None)
al_resume = {'alarm_id': mox.IgnoreArg(),
'enabled': True}
self.fa.alarms.update(**al_resume).AndReturn(None)
self.m.ReplayAll()
update_mock = self.patchobject(self.fa.alarm, 'update')
al_suspend = {'enabled': False}
al_resume = {'enabled': True}
self.stack.create()
rsrc = self.stack['MEMAlarmHigh']
test_stack.create()
rsrc = test_stack['MEMAlarmHigh']
scheduler.TaskRunner(rsrc.suspend)()
self.assertEqual((rsrc.SUSPEND, rsrc.COMPLETE), rsrc.state)
scheduler.TaskRunner(rsrc.resume)()
self.assertEqual((rsrc.RESUME, rsrc.COMPLETE), rsrc.state)
self.m.VerifyAll()
update_mock.assert_has_calls((
mock.call('foo', al_suspend),
mock.call('foo', al_resume)))
def test_mem_alarm_high_correct_int_parameters(self):
self.stack = self.create_stack(not_string_alarm_template)
test_stack = self.create_stack(not_string_alarm_template)
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['MEMAlarmHigh']
test_stack.create()
rsrc = test_stack['MEMAlarmHigh']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.assertIsNone(rsrc.validate())
@ -331,20 +276,18 @@ class CeilometerAlarmTest(common.HeatTestCase):
self.assertIsInstance(rsrc.properties['period'], int)
self.assertIsInstance(rsrc.properties['threshold'], int)
self.m.VerifyAll()
def test_alarm_metadata_prefix(self):
t = template_format.parse(alarm_template)
properties = t['Resources']['MEMAlarmHigh']['Properties']
# Test for bug/1383521, where meter_name is in NOVA_METERS
properties[alarm.CeilometerAlarm.METER_NAME] = 'memory.usage'
properties[alarm.AodhAlarm.METER_NAME] = 'memory.usage'
properties['matching_metadata'] = {'metadata.user_metadata.groupname':
'foo'}
self.stack = self.create_stack(template=json.dumps(t))
test_stack = self.create_stack(template=json.dumps(t))
rsrc = self.stack['MEMAlarmHigh']
rsrc.properties.data = rsrc.cfn_to_ceilometer(self.stack, properties)
rsrc = test_stack['MEMAlarmHigh']
rsrc.properties.data = rsrc.get_alarm_props(properties)
self.assertIsNone(rsrc.properties.data.get('matching_metadata'))
query = rsrc.properties.data['threshold_rule']['query']
expected_query = [{'field': u'metadata.user_metadata.groupname',
@ -355,13 +298,13 @@ class CeilometerAlarmTest(common.HeatTestCase):
t = template_format.parse(alarm_template)
properties = t['Resources']['MEMAlarmHigh']['Properties']
# Test that meter_name is not in NOVA_METERS
properties[alarm.CeilometerAlarm.METER_NAME] = 'memory_util'
properties[alarm.AodhAlarm.METER_NAME] = 'memory_util'
properties['matching_metadata'] = {'metadata.user_metadata.groupname':
'foo'}
self.stack = self.create_stack(template=json.dumps(t))
rsrc = self.stack['MEMAlarmHigh']
rsrc.properties.data = rsrc.cfn_to_ceilometer(self.stack, properties)
rsrc.properties.data = rsrc.get_alarm_props(properties)
self.assertIsNone(rsrc.properties.data.get('matching_metadata'))
query = rsrc.properties.data['threshold_rule']['query']
expected_query = [{'field': u'metadata.metering.groupname',
@ -377,19 +320,16 @@ class CeilometerAlarmTest(common.HeatTestCase):
'pro': '{"Mem": {"Ala": {"Hig"}}}',
'tro': [1, 2, 3, 4]}
self.stack = self.create_stack(template=json.dumps(t))
test_stack = self.create_stack(template=json.dumps(t))
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['MEMAlarmHigh']
test_stack.create()
rsrc = test_stack['MEMAlarmHigh']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
rsrc.properties.data = rsrc.cfn_to_ceilometer(self.stack, properties)
rsrc.properties.data = rsrc.get_alarm_props(properties)
self.assertIsNone(rsrc.properties.data.get('matching_metadata'))
for key in rsrc.properties.data['threshold_rule']['query']:
self.assertIsInstance(key['value'], six.text_type)
self.m.VerifyAll()
def test_no_matching_metadata(self):
"""Make sure that we can pass in an empty matching_metadata."""
@ -398,16 +338,13 @@ class CeilometerAlarmTest(common.HeatTestCase):
properties['alarm_actions'] = ['signal_handler']
del properties['matching_metadata']
self.stack = self.create_stack(template=json.dumps(t))
test_stack = self.create_stack(template=json.dumps(t))
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['MEMAlarmHigh']
test_stack.create()
rsrc = test_stack['MEMAlarmHigh']
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
self.assertIsNone(rsrc.validate())
self.m.VerifyAll()
def test_mem_alarm_high_not_correct_string_parameters(self):
orig_snippet = template_format.parse(not_string_alarm_template)
for p in ('period', 'evaluation_periods'):
@ -416,7 +353,7 @@ class CeilometerAlarmTest(common.HeatTestCase):
stack = utils.parse_stack(snippet)
resource_defns = stack.t.resource_definitions(stack)
rsrc = alarm.CeilometerAlarm(
rsrc = alarm.AodhAlarm(
'MEMAlarmHigh', resource_defns['MEMAlarmHigh'], stack)
error = self.assertRaises(exception.StackValidationFailed,
rsrc.validate)
@ -432,7 +369,7 @@ class CeilometerAlarmTest(common.HeatTestCase):
stack = utils.parse_stack(snippet)
resource_defns = stack.t.resource_definitions(stack)
rsrc = alarm.CeilometerAlarm(
rsrc = alarm.AodhAlarm(
'MEMAlarmHigh', resource_defns['MEMAlarmHigh'], stack)
# python 3.4.3 returns another error message
# so try to handle this by regexp
@ -448,7 +385,7 @@ class CeilometerAlarmTest(common.HeatTestCase):
stack = utils.parse_stack(snippet)
resource_defns = stack.t.resource_definitions(stack)
rsrc = alarm.CeilometerAlarm(
rsrc = alarm.AodhAlarm(
'MEMAlarmHigh', resource_defns['MEMAlarmHigh'], stack)
error = self.assertRaises(exception.StackValidationFailed,
rsrc.validate)
@ -464,35 +401,35 @@ class CeilometerAlarmTest(common.HeatTestCase):
stack = utils.parse_stack(snippet)
resource_defns = stack.t.resource_definitions(stack)
rsrc = alarm.CeilometerAlarm(
rsrc = alarm.AodhAlarm(
'MEMAlarmHigh', resource_defns['MEMAlarmHigh'], stack)
self.assertIsNone(rsrc.validate())
def test_delete_watchrule_destroy(self):
t = template_format.parse(alarm_template)
self.stack = self.create_stack(template=json.dumps(t))
rsrc = self.stack['MEMAlarmHigh']
test_stack = self.create_stack(template=json.dumps(t))
rsrc = test_stack['MEMAlarmHigh']
wr = mock.MagicMock()
self.patchobject(watchrule.WatchRule, 'load', return_value=wr)
wr.destroy.return_value = None
self.patchobject(ceilometer.CeilometerClientPlugin, 'client',
self.patchobject(aodh.AodhClientPlugin, 'client',
return_value=self.fa)
self.patchobject(self.fa.alarms, 'delete')
self.patchobject(self.fa.alarm, 'delete')
rsrc.resource_id = '12345'
self.assertEqual('12345', rsrc.handle_delete())
self.assertEqual(1, wr.destroy.call_count)
# check that super method has been called and execute deleting
self.assertEqual(1, self.fa.alarms.delete.call_count)
self.assertEqual(1, self.fa.alarm.delete.call_count)
def test_delete_no_watchrule(self):
t = template_format.parse(alarm_template)
self.stack = self.create_stack(template=json.dumps(t))
rsrc = self.stack['MEMAlarmHigh']
test_stack = self.create_stack(template=json.dumps(t))
rsrc = test_stack['MEMAlarmHigh']
wr = mock.MagicMock()
self.patchobject(watchrule.WatchRule, 'load',
@ -500,15 +437,15 @@ class CeilometerAlarmTest(common.HeatTestCase):
entity='Watch Rule', name='test')])
wr.destroy.return_value = None
self.patchobject(ceilometer.CeilometerClientPlugin, 'client',
self.patchobject(aodh.AodhClientPlugin, 'client',
return_value=self.fa)
self.patchobject(self.fa.alarms, 'delete')
self.patchobject(self.fa.alarm, 'delete')
rsrc.resource_id = '12345'
self.assertEqual('12345', rsrc.handle_delete())
self.assertEqual(0, wr.destroy.call_count)
# check that super method has been called and execute deleting
self.assertEqual(1, self.fa.alarms.delete.call_count)
self.assertEqual(1, self.fa.alarm.delete.call_count)
def _prepare_check_resource(self):
snippet = template_format.parse(not_string_alarm_template)
@ -516,7 +453,7 @@ class CeilometerAlarmTest(common.HeatTestCase):
res = self.stack['MEMAlarmHigh']
res.client = mock.Mock()
mock_alarm = mock.Mock(enabled=True, state='ok')
res.client().alarms.get.return_value = mock_alarm
res.client().alarm.get.return_value = mock_alarm
return res
@mock.patch.object(alarm.watchrule.WatchRule, 'load')
@ -539,7 +476,7 @@ class CeilometerAlarmTest(common.HeatTestCase):
@mock.patch.object(alarm.watchrule.WatchRule, 'load')
def test_check_alarm_failure(self, mock_load):
res = self._prepare_check_resource()
res.client().alarms.get.side_effect = Exception('Boom')
res.client().alarm.get.side_effect = Exception('Boom')
self.assertRaises(exception.ResourceFailure,
scheduler.TaskRunner(res.check))
@ -548,11 +485,10 @@ class CeilometerAlarmTest(common.HeatTestCase):
def test_show_resource(self):
res = self._prepare_check_resource()
res.client().alarms.create.return_value = mock.MagicMock(
alarm_id='2')
res.client().alarms.get.return_value = FakeCeilometerAlarm()
res.client().alarm.create.return_value = FakeAodhAlarm
res.client().alarm.get.return_value = FakeAodhAlarm
scheduler.TaskRunner(res.create)()
self.assertEqual({'attr': 'val'}, res.FnGetAtt('show'))
self.assertEqual(FakeAodhAlarm, res.FnGetAtt('show'))
def test_alarm_with_wrong_start_time(self):
t = template_format.parse(alarm_template_with_time_constraints)
@ -562,11 +498,13 @@ class CeilometerAlarmTest(common.HeatTestCase):
"duration": 10800,
"description": "a description"
}]
self.stack = self.create_stack(template=json.dumps(t),
test_stack = self.create_stack(template=json.dumps(t),
time_constraints=time_constraints)
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['MEMAlarmHigh']
test_stack.create()
self.assertEqual((test_stack.CREATE, test_stack.COMPLETE),
test_stack.state)
rsrc = test_stack['MEMAlarmHigh']
properties = copy.copy(rsrc.properties.data)
start_time = '* * * * * 100'
@ -605,8 +543,6 @@ class CeilometerAlarmTest(common.HeatTestCase):
"[%s] is not acceptable, out of range" % (start_time, start_time),
error.message)
self.m.VerifyAll()
def test_alarm_with_wrong_timezone(self):
t = template_format.parse(alarm_template_with_time_constraints)
time_constraints = [{"name": "tc1",
@ -615,11 +551,13 @@ class CeilometerAlarmTest(common.HeatTestCase):
"duration": 10800,
"description": "a description"
}]
self.stack = self.create_stack(template=json.dumps(t),
test_stack = self.create_stack(template=json.dumps(t),
time_constraints=time_constraints)
self.m.ReplayAll()
self.stack.create()
rsrc = self.stack['MEMAlarmHigh']
test_stack.create()
self.assertEqual((test_stack.CREATE, test_stack.COMPLETE),
test_stack.state)
rsrc = test_stack['MEMAlarmHigh']
properties = copy.copy(rsrc.properties.data)
timezone = 'wrongtimezone'
@ -658,8 +596,6 @@ class CeilometerAlarmTest(common.HeatTestCase):
% (timezone, timezone),
error.message)
self.m.VerifyAll()
class CombinationAlarmTest(common.HeatTestCase):

View File

@ -121,7 +121,7 @@ IntegrationTestGroup = [
cfg.ListOpt('skip_scenario_test_list',
help="List of scenario test class or class.method "
"names to skip ex. NeutronLoadBalancerTest, "
"CeilometerAlarmTest.test_alarm"),
"AodhAlarmTest.test_alarm"),
cfg.ListOpt('skip_test_stack_action_list',
help="List of stack actions in tests to skip "
"ex. ABANDON, ADOPT, SUSPEND, RESUME"),

View File

@ -104,7 +104,7 @@
#skip_functional_test_list = <None>
# List of scenario test class or class.method names to skip ex.
# NeutronLoadBalancerTest, CeilometerAlarmTest.test_alarm (list value)
# NeutronLoadBalancerTest, AodhAlarmTest.test_alarm (list value)
#skip_scenario_test_list = <None>
# List of stack actions in tests to skip ex. ABANDON, ADOPT, SUSPEND, RESUME

View File

@ -15,7 +15,7 @@ resources:
cooldown: 0
scaling_adjustment: 1
alarm:
type: OS::Ceilometer::Alarm
type: OS::Aodh::Alarm
properties:
description: Scale-up if the average CPU > 50% for 1 minute
meter_name: test_meter

View File

@ -18,12 +18,12 @@ from heat_integrationtests.scenario import scenario_base
LOG = logging.getLogger(__name__)
class CeilometerAlarmTest(scenario_base.ScenarioTestsBase):
"""Class is responsible for testing of ceilometer usage."""
class AodhAlarmTest(scenario_base.ScenarioTestsBase):
"""Class is responsible for testing of aodh usage."""
def setUp(self):
super(CeilometerAlarmTest, self).setUp()
super(AodhAlarmTest, self).setUp()
self.template = self._load_template(__file__,
'test_ceilometer_alarm.yaml',
'test_aodh_alarm.yaml',
'templates')
def check_instance_count(self, stack_identifier, expected):