From 4a79f7ca539f92a38e554db718d20a51f302565f Mon Sep 17 00:00:00 2001 From: huangtianhua Date: Wed, 8 Jun 2016 15:50:39 +0800 Subject: [PATCH] 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 --- etc/heat/environment.d/default.yaml | 3 +- etc/heat/templates/AWS_CloudWatch_Alarm.yaml | 2 +- heat/engine/resources/alarm_base.py | 211 +++++++++++++++ .../resources/openstack/ceilometer/alarm.py | 241 +++--------------- .../openstack/ceilometer/gnocchi/alarm.py | 13 +- .../resources/openstack/heat/cloud_watch.py | 2 +- .../ceilometer/test_ceilometer_alarm.py | 234 ++++++----------- heat_integrationtests/common/config.py | 2 +- .../heat_integrationtests.conf.sample | 2 +- ...ometer_alarm.yaml => test_aodh_alarm.yaml} | 2 +- ...ceilometer_alarm.py => test_aodh_alarm.py} | 8 +- 11 files changed, 344 insertions(+), 376 deletions(-) create mode 100644 heat/engine/resources/alarm_base.py rename heat_integrationtests/scenario/templates/{test_ceilometer_alarm.yaml => test_aodh_alarm.yaml} (96%) rename heat_integrationtests/scenario/{test_ceilometer_alarm.py => test_aodh_alarm.py} (89%) diff --git a/etc/heat/environment.d/default.yaml b/etc/heat/environment.d/default.yaml index 143ee48c54..fb680c43ca 100644 --- a/etc/heat/environment.d/default.yaml +++ b/etc/heat/environment.d/default.yaml @@ -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" diff --git a/etc/heat/templates/AWS_CloudWatch_Alarm.yaml b/etc/heat/templates/AWS_CloudWatch_Alarm.yaml index 2e5f366251..f3ee6be6af 100644 --- a/etc/heat/templates/AWS_CloudWatch_Alarm.yaml +++ b/etc/heat/templates/AWS_CloudWatch_Alarm.yaml @@ -60,7 +60,7 @@ Mappings: Resources: __alarm__: - Type: OS::Ceilometer::Alarm + Type: OS::Aodh::Alarm Properties: description: Ref: AlarmDescription diff --git a/heat/engine/resources/alarm_base.py b/heat/engine/resources/alarm_base.py new file mode 100644 index 0000000000..b5ab041109 --- /dev/null +++ b/heat/engine/resources/alarm_base.py @@ -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) diff --git a/heat/engine/resources/openstack/ceilometer/alarm.py b/heat/engine/resources/openstack/ceilometer/alarm.py index f75b3fdbb5..d277a6bab8 100644 --- a/heat/engine/resources/openstack/ceilometer/alarm.py +++ b/heat/engine/resources/openstack/ceilometer/alarm.py @@ -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, } diff --git a/heat/engine/resources/openstack/ceilometer/gnocchi/alarm.py b/heat/engine/resources/openstack/ceilometer/gnocchi/alarm.py index adfcbf0447..16557cb8a3 100644 --- a/heat/engine/resources/openstack/ceilometer/gnocchi/alarm.py +++ b/heat/engine/resources/openstack/ceilometer/gnocchi/alarm.py @@ -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(): diff --git a/heat/engine/resources/openstack/heat/cloud_watch.py b/heat/engine/resources/openstack/heat/cloud_watch.py index a37e0d26b2..8a227a742a 100644 --- a/heat/engine/resources/openstack/heat/cloud_watch.py +++ b/heat/engine/resources/openstack/heat/cloud_watch.py @@ -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, diff --git a/heat/tests/openstack/ceilometer/test_ceilometer_alarm.py b/heat/tests/openstack/ceilometer/test_ceilometer_alarm.py index e8dcb6332e..8ba1ccd5ec 100644 --- a/heat/tests/openstack/ceilometer/test_ceilometer_alarm.py +++ b/heat/tests/openstack/ceilometer/test_ceilometer_alarm.py @@ -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): diff --git a/heat_integrationtests/common/config.py b/heat_integrationtests/common/config.py index e0351d5c2f..b3615d29cf 100644 --- a/heat_integrationtests/common/config.py +++ b/heat_integrationtests/common/config.py @@ -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"), diff --git a/heat_integrationtests/heat_integrationtests.conf.sample b/heat_integrationtests/heat_integrationtests.conf.sample index 59ff0db7df..0e0668584e 100644 --- a/heat_integrationtests/heat_integrationtests.conf.sample +++ b/heat_integrationtests/heat_integrationtests.conf.sample @@ -104,7 +104,7 @@ #skip_functional_test_list = # 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 = # List of stack actions in tests to skip ex. ABANDON, ADOPT, SUSPEND, RESUME diff --git a/heat_integrationtests/scenario/templates/test_ceilometer_alarm.yaml b/heat_integrationtests/scenario/templates/test_aodh_alarm.yaml similarity index 96% rename from heat_integrationtests/scenario/templates/test_ceilometer_alarm.yaml rename to heat_integrationtests/scenario/templates/test_aodh_alarm.yaml index 01bc790b74..9218f56337 100644 --- a/heat_integrationtests/scenario/templates/test_ceilometer_alarm.yaml +++ b/heat_integrationtests/scenario/templates/test_aodh_alarm.yaml @@ -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 diff --git a/heat_integrationtests/scenario/test_ceilometer_alarm.py b/heat_integrationtests/scenario/test_aodh_alarm.py similarity index 89% rename from heat_integrationtests/scenario/test_ceilometer_alarm.py rename to heat_integrationtests/scenario/test_aodh_alarm.py index aa29861a8e..90288a27ab 100644 --- a/heat_integrationtests/scenario/test_ceilometer_alarm.py +++ b/heat_integrationtests/scenario/test_aodh_alarm.py @@ -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):