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:
parent
47262c079d
commit
4a79f7ca53
@ -5,5 +5,6 @@ resource_registry:
|
|||||||
# Choose your implementation of AWS::CloudWatch::Alarm
|
# Choose your implementation of AWS::CloudWatch::Alarm
|
||||||
"AWS::CloudWatch::Alarm": "file:///etc/heat/templates/AWS_CloudWatch_Alarm.yaml"
|
"AWS::CloudWatch::Alarm": "file:///etc/heat/templates/AWS_CloudWatch_Alarm.yaml"
|
||||||
#"AWS::CloudWatch::Alarm": "OS::Heat::CWLiteAlarm"
|
#"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"
|
"AWS::RDS::DBInstance": "file:///etc/heat/templates/AWS_RDS_DBInstance.yaml"
|
||||||
|
"OS::Ceilometer::Alarm": "OS::Aodh::Alarm"
|
||||||
|
@ -60,7 +60,7 @@ Mappings:
|
|||||||
|
|
||||||
Resources:
|
Resources:
|
||||||
__alarm__:
|
__alarm__:
|
||||||
Type: OS::Ceilometer::Alarm
|
Type: OS::Aodh::Alarm
|
||||||
Properties:
|
Properties:
|
||||||
description:
|
description:
|
||||||
Ref: AlarmDescription
|
Ref: AlarmDescription
|
||||||
|
211
heat/engine/resources/alarm_base.py
Normal file
211
heat/engine/resources/alarm_base.py
Normal 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)
|
@ -17,172 +17,13 @@ from heat.common import exception
|
|||||||
from heat.common.i18n import _
|
from heat.common.i18n import _
|
||||||
from heat.engine import constraints
|
from heat.engine import constraints
|
||||||
from heat.engine import properties
|
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 support
|
||||||
from heat.engine import watchrule
|
from heat.engine import watchrule
|
||||||
|
|
||||||
|
|
||||||
COMMON_PROPERTIES = (
|
class AodhAlarm(alarm_base.BaseAlarm):
|
||||||
ALARM_ACTIONS, OK_ACTIONS, REPEAT_ACTIONS,
|
"""A resource that implements alarming service of Aodh.
|
||||||
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.
|
|
||||||
|
|
||||||
A resource that allows for the setting alarms based on threshold evaluation
|
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
|
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 get_alarm_props(self, props):
|
||||||
|
|
||||||
def cfn_to_ceilometer(self, stack, properties):
|
|
||||||
"""Apply all relevant compatibility xforms."""
|
"""Apply all relevant compatibility xforms."""
|
||||||
|
|
||||||
kwargs = actions_to_urls(stack, properties)
|
kwargs = self.actions_to_urls(props)
|
||||||
kwargs['type'] = 'threshold'
|
kwargs['type'] = self.alarm_type
|
||||||
if kwargs.get(self.METER_NAME) in NOVA_METERS:
|
if kwargs.get(self.METER_NAME) in alarm_base.NOVA_METERS:
|
||||||
prefix = 'user_metadata.'
|
prefix = 'user_metadata.'
|
||||||
else:
|
else:
|
||||||
prefix = 'metering.'
|
prefix = 'metering.'
|
||||||
@ -312,8 +150,8 @@ class CeilometerAlarm(resource.Resource):
|
|||||||
if field in kwargs:
|
if field in kwargs:
|
||||||
rule[field] = kwargs[field]
|
rule[field] = kwargs[field]
|
||||||
del kwargs[field]
|
del kwargs[field]
|
||||||
mmd = properties.get(self.MATCHING_METADATA) or {}
|
mmd = props.get(self.MATCHING_METADATA) or {}
|
||||||
query = properties.get(self.QUERY) or []
|
query = props.get(self.QUERY) or []
|
||||||
|
|
||||||
# make sure the matching_metadata appears in the query like this:
|
# make sure the matching_metadata appears in the query like this:
|
||||||
# {field: metadata.$prefix.x, ...}
|
# {field: metadata.$prefix.x, ...}
|
||||||
@ -339,11 +177,10 @@ class CeilometerAlarm(resource.Resource):
|
|||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def handle_create(self):
|
def handle_create(self):
|
||||||
props = self.cfn_to_ceilometer(self.stack,
|
props = self.get_alarm_props(self.properties)
|
||||||
self.properties)
|
|
||||||
props['name'] = self.physical_resource_name()
|
props['name'] = self.physical_resource_name()
|
||||||
alarm = self.client().alarms.create(**props)
|
alarm = self.client().alarm.create(props)
|
||||||
self.resource_id_set(alarm.alarm_id)
|
self.resource_id_set(alarm['alarm_id'])
|
||||||
|
|
||||||
# the watchrule below is for backwards compatibility.
|
# the watchrule below is for backwards compatibility.
|
||||||
# 1) so we don't create watch tasks unnecessarily
|
# 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):
|
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||||
if prop_diff:
|
if prop_diff:
|
||||||
kwargs = {'alarm_id': self.resource_id}
|
kwargs = {}
|
||||||
kwargs.update(self.properties)
|
kwargs.update(self.properties)
|
||||||
kwargs.update(prop_diff)
|
kwargs.update(prop_diff)
|
||||||
alarms_client = self.client().alarms
|
self.client().alarm.update(self.resource_id,
|
||||||
alarms_client.update(**self.cfn_to_ceilometer(self.stack, kwargs))
|
self.get_alarm_props(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)
|
|
||||||
|
|
||||||
def handle_delete(self):
|
def handle_delete(self):
|
||||||
try:
|
try:
|
||||||
@ -382,45 +209,37 @@ class CeilometerAlarm(resource.Resource):
|
|||||||
except exception.EntityNotFound:
|
except exception.EntityNotFound:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return super(CeilometerAlarm, self).handle_delete()
|
return super(AodhAlarm, self).handle_delete()
|
||||||
|
|
||||||
def handle_check(self):
|
def handle_check(self):
|
||||||
watch_name = self.physical_resource_name()
|
watch_name = self.physical_resource_name()
|
||||||
watchrule.WatchRule.load(self.context, watch_name=watch_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'
|
default_client_name = 'ceilometer'
|
||||||
|
|
||||||
entity = 'alarms'
|
entity = 'alarms'
|
||||||
|
|
||||||
def handle_create(self):
|
def handle_create(self):
|
||||||
properties = actions_to_urls(self.stack,
|
props = self.actions_to_urls(self.properties)
|
||||||
self.properties)
|
props['name'] = self.physical_resource_name()
|
||||||
properties['name'] = self.physical_resource_name()
|
props['type'] = self.alarm_type
|
||||||
properties['type'] = self.ceilometer_alarm_type
|
|
||||||
alarm = self.client().alarms.create(
|
alarm = self.client().alarms.create(
|
||||||
**self._reformat_properties(properties))
|
**self._reformat_properties(props))
|
||||||
self.resource_id_set(alarm.alarm_id)
|
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):
|
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||||
if prop_diff:
|
if prop_diff:
|
||||||
kwargs = {'alarm_id': self.resource_id}
|
kwargs = {'alarm_id': self.resource_id}
|
||||||
kwargs.update(prop_diff)
|
kwargs.update(prop_diff)
|
||||||
alarms_client = self.client().alarms
|
alarms_client = self.client().alarms
|
||||||
alarms_client.update(**self._reformat_properties(
|
alarms_client.update(**self._reformat_properties(
|
||||||
actions_to_urls(self.stack, kwargs)))
|
self.actions_to_urls(kwargs)))
|
||||||
|
|
||||||
def handle_suspend(self):
|
def handle_suspend(self):
|
||||||
self.client().alarms.update(
|
self.client().alarms.update(
|
||||||
@ -463,13 +282,13 @@ class CombinationAlarm(BaseCeilometerAlarm):
|
|||||||
constraints=[constraints.AllowedValues(['and', 'or'])],
|
constraints=[constraints.AllowedValues(['and', 'or'])],
|
||||||
update_allowed=True)
|
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():
|
def resource_mapping():
|
||||||
return {
|
return {
|
||||||
'OS::Ceilometer::Alarm': CeilometerAlarm,
|
'OS::Aodh::Alarm': AodhAlarm,
|
||||||
'OS::Ceilometer::CombinationAlarm': CombinationAlarm,
|
'OS::Ceilometer::CombinationAlarm': CombinationAlarm,
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
from heat.common.i18n import _
|
from heat.common.i18n import _
|
||||||
from heat.engine import constraints
|
from heat.engine import constraints
|
||||||
from heat.engine import properties
|
from heat.engine import properties
|
||||||
|
from heat.engine.resources import alarm_base
|
||||||
from heat.engine.resources.openstack.ceilometer import alarm
|
from heat.engine.resources.openstack.ceilometer import alarm
|
||||||
from heat.engine import support
|
from heat.engine import support
|
||||||
|
|
||||||
@ -103,9 +104,9 @@ class CeilometerGnocchiResourcesAlarm(alarm.BaseCeilometerAlarm):
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
properties_schema.update(common_gnocchi_properties_schema)
|
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(
|
class CeilometerGnocchiAggregationByMetricsAlarm(
|
||||||
@ -130,9 +131,9 @@ class CeilometerGnocchiAggregationByMetricsAlarm(
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
properties_schema.update(common_gnocchi_properties_schema)
|
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(
|
class CeilometerGnocchiAggregationByResourcesAlarm(
|
||||||
@ -175,9 +176,9 @@ class CeilometerGnocchiAggregationByResourcesAlarm(
|
|||||||
}
|
}
|
||||||
|
|
||||||
properties_schema.update(common_gnocchi_properties_schema)
|
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():
|
def resource_mapping():
|
||||||
|
@ -143,7 +143,7 @@ class CloudWatchAlarm(resource.Resource):
|
|||||||
support_status = support.SupportStatus(
|
support_status = support.SupportStatus(
|
||||||
status=support.HIDDEN,
|
status=support.HIDDEN,
|
||||||
message=_('OS::Heat::CWLiteAlarm is deprecated, '
|
message=_('OS::Heat::CWLiteAlarm is deprecated, '
|
||||||
'use OS::Ceilometer::Alarm instead.'),
|
'use OS::Aodh::Alarm instead.'),
|
||||||
version='5.0.0',
|
version='5.0.0',
|
||||||
previous_status=support.SupportStatus(
|
previous_status=support.SupportStatus(
|
||||||
status=support.DEPRECATED,
|
status=support.DEPRECATED,
|
||||||
|
@ -20,8 +20,8 @@ import six
|
|||||||
|
|
||||||
from heat.common import exception
|
from heat.common import exception
|
||||||
from heat.common import template_format
|
from heat.common import template_format
|
||||||
|
from heat.engine.clients.os import aodh
|
||||||
from heat.engine.clients.os import ceilometer
|
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.resources.openstack.ceilometer import alarm
|
||||||
from heat.engine import rsrc_defn
|
from heat.engine import rsrc_defn
|
||||||
from heat.engine import scheduler
|
from heat.engine import scheduler
|
||||||
@ -39,7 +39,7 @@ alarm_template = '''
|
|||||||
"Parameters" : {},
|
"Parameters" : {},
|
||||||
"Resources" : {
|
"Resources" : {
|
||||||
"MEMAlarmHigh": {
|
"MEMAlarmHigh": {
|
||||||
"Type": "OS::Ceilometer::Alarm",
|
"Type": "OS::Aodh::Alarm",
|
||||||
"Properties": {
|
"Properties": {
|
||||||
"description": "Scale-up if MEM > 50% for 1 minute",
|
"description": "Scale-up if MEM > 50% for 1 minute",
|
||||||
"meter_name": "MemoryUtilization",
|
"meter_name": "MemoryUtilization",
|
||||||
@ -66,7 +66,7 @@ alarm_template_with_time_constraints = '''
|
|||||||
"Parameters" : {},
|
"Parameters" : {},
|
||||||
"Resources" : {
|
"Resources" : {
|
||||||
"MEMAlarmHigh": {
|
"MEMAlarmHigh": {
|
||||||
"Type": "OS::Ceilometer::Alarm",
|
"Type": "OS::Aodh::Alarm",
|
||||||
"Properties": {
|
"Properties": {
|
||||||
"description": "Scale-up if MEM > 50% for 1 minute",
|
"description": "Scale-up if MEM > 50% for 1 minute",
|
||||||
"meter_name": "MemoryUtilization",
|
"meter_name": "MemoryUtilization",
|
||||||
@ -100,7 +100,7 @@ not_string_alarm_template = '''
|
|||||||
"Parameters" : {},
|
"Parameters" : {},
|
||||||
"Resources" : {
|
"Resources" : {
|
||||||
"MEMAlarmHigh": {
|
"MEMAlarmHigh": {
|
||||||
"Type": "OS::Ceilometer::Alarm",
|
"Type": "OS::Aodh::Alarm",
|
||||||
"Properties": {
|
"Properties": {
|
||||||
"description": "Scale-up if MEM > 50% for 1 minute",
|
"description": "Scale-up if MEM > 50% for 1 minute",
|
||||||
"meter_name": "MemoryUtilization",
|
"meter_name": "MemoryUtilization",
|
||||||
@ -146,9 +146,13 @@ class FakeCeilometerAlarm(object):
|
|||||||
self.to_dict = lambda: {'attr': 'val'}
|
self.to_dict = lambda: {'attr': 'val'}
|
||||||
|
|
||||||
|
|
||||||
class CeilometerAlarmTest(common.HeatTestCase):
|
FakeAodhAlarm = {'other_attrs': 'val',
|
||||||
|
'alarm_id': 'foo'}
|
||||||
|
|
||||||
|
|
||||||
|
class AodhAlarmTest(common.HeatTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(CeilometerAlarmTest, self).setUp()
|
super(AodhAlarmTest, self).setUp()
|
||||||
self.fa = mock.Mock()
|
self.fa = mock.Mock()
|
||||||
|
|
||||||
def create_stack(self, template=None, time_constraints=None):
|
def create_stack(self, template=None, time_constraints=None):
|
||||||
@ -162,46 +166,14 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
disable_rollback=True)
|
disable_rollback=True)
|
||||||
stack.store()
|
stack.store()
|
||||||
|
|
||||||
self.m.StubOutWithMock(ceilometer.CeilometerClientPlugin, '_create')
|
self.patchobject(aodh.AodhClientPlugin,
|
||||||
ceilometer.CeilometerClientPlugin._create().AndReturn(self.fa)
|
'_create').return_value = self.fa
|
||||||
|
|
||||||
al = copy.deepcopy(temp['Resources']['MEMAlarmHigh']['Properties'])
|
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['time_constraints'] = time_constraints if time_constraints else []
|
||||||
al['severity'] = 'low'
|
|
||||||
rule = dict(
|
self.patchobject(self.fa.alarm, 'create').return_value = FakeAodhAlarm
|
||||||
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())
|
|
||||||
return stack
|
return stack
|
||||||
|
|
||||||
def test_mem_alarm_high_update_no_replace(self):
|
def test_mem_alarm_high_update_no_replace(self):
|
||||||
@ -214,37 +186,15 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
properties['matching_metadata'] = {'a': 'v'}
|
properties['matching_metadata'] = {'a': 'v'}
|
||||||
properties['query'] = [dict(field='b', op='eq', value='w')]
|
properties['query'] = [dict(field='b', op='eq', value='w')]
|
||||||
|
|
||||||
self.stack = self.create_stack(template=json.dumps(t))
|
test_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)
|
|
||||||
|
|
||||||
self.m.ReplayAll()
|
update_mock = self.patchobject(self.fa.alarm, 'update')
|
||||||
self.stack.create()
|
|
||||||
rsrc = self.stack['MEMAlarmHigh']
|
|
||||||
|
|
||||||
properties = copy.copy(rsrc.properties.data)
|
test_stack.create()
|
||||||
properties.update({
|
rsrc = test_stack['MEMAlarmHigh']
|
||||||
|
|
||||||
|
update_props = copy.deepcopy(rsrc.properties.data)
|
||||||
|
update_props.update({
|
||||||
'comparison_operator': 'lt',
|
'comparison_operator': 'lt',
|
||||||
'description': 'fruity',
|
'description': 'fruity',
|
||||||
'evaluation_periods': '2',
|
'evaluation_periods': '2',
|
||||||
@ -259,13 +209,15 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
'matching_metadata': {'x': 'y'},
|
'matching_metadata': {'x': 'y'},
|
||||||
'query': [dict(field='c', op='ne', value='z')]
|
'query': [dict(field='c', op='ne', value='z')]
|
||||||
})
|
})
|
||||||
|
|
||||||
snippet = rsrc_defn.ResourceDefinition(rsrc.name,
|
snippet = rsrc_defn.ResourceDefinition(rsrc.name,
|
||||||
rsrc.type(),
|
rsrc.type(),
|
||||||
properties)
|
update_props)
|
||||||
|
|
||||||
scheduler.TaskRunner(rsrc.update, snippet)()
|
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):
|
def test_mem_alarm_high_update_replace(self):
|
||||||
"""Tests resource replacing when changing non-updatable properties."""
|
"""Tests resource replacing when changing non-updatable properties."""
|
||||||
@ -275,11 +227,10 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
properties['alarm_actions'] = ['signal_handler']
|
properties['alarm_actions'] = ['signal_handler']
|
||||||
properties['matching_metadata'] = {'a': 'v'}
|
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()
|
test_stack.create()
|
||||||
self.stack.create()
|
rsrc = test_stack['MEMAlarmHigh']
|
||||||
rsrc = self.stack['MEMAlarmHigh']
|
|
||||||
|
|
||||||
properties = copy.copy(rsrc.properties.data)
|
properties = copy.copy(rsrc.properties.data)
|
||||||
properties['meter_name'] = 'temp'
|
properties['meter_name'] = 'temp'
|
||||||
@ -290,40 +241,34 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
updater = scheduler.TaskRunner(rsrc.update, snippet)
|
updater = scheduler.TaskRunner(rsrc.update, snippet)
|
||||||
self.assertRaises(exception.UpdateReplace, updater)
|
self.assertRaises(exception.UpdateReplace, updater)
|
||||||
|
|
||||||
self.m.VerifyAll()
|
|
||||||
|
|
||||||
def test_mem_alarm_suspend_resume(self):
|
def test_mem_alarm_suspend_resume(self):
|
||||||
"""Tests suspending and resuming of the alarm.
|
"""Tests suspending and resuming of the alarm.
|
||||||
|
|
||||||
Make sure that the Alarm resource gets disabled on suspend
|
Make sure that the Alarm resource gets disabled on suspend
|
||||||
and re-enabled on resume.
|
and re-enabled on resume.
|
||||||
"""
|
"""
|
||||||
self.stack = self.create_stack()
|
test_stack = self.create_stack()
|
||||||
|
|
||||||
self.m.StubOutWithMock(self.fa.alarms, 'update')
|
update_mock = self.patchobject(self.fa.alarm, 'update')
|
||||||
al_suspend = {'alarm_id': mox.IgnoreArg(),
|
al_suspend = {'enabled': False}
|
||||||
'enabled': False}
|
al_resume = {'enabled': True}
|
||||||
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()
|
|
||||||
|
|
||||||
self.stack.create()
|
test_stack.create()
|
||||||
rsrc = self.stack['MEMAlarmHigh']
|
rsrc = test_stack['MEMAlarmHigh']
|
||||||
scheduler.TaskRunner(rsrc.suspend)()
|
scheduler.TaskRunner(rsrc.suspend)()
|
||||||
self.assertEqual((rsrc.SUSPEND, rsrc.COMPLETE), rsrc.state)
|
self.assertEqual((rsrc.SUSPEND, rsrc.COMPLETE), rsrc.state)
|
||||||
scheduler.TaskRunner(rsrc.resume)()
|
scheduler.TaskRunner(rsrc.resume)()
|
||||||
self.assertEqual((rsrc.RESUME, rsrc.COMPLETE), rsrc.state)
|
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):
|
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()
|
test_stack.create()
|
||||||
self.stack.create()
|
rsrc = test_stack['MEMAlarmHigh']
|
||||||
rsrc = self.stack['MEMAlarmHigh']
|
|
||||||
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
||||||
self.assertIsNone(rsrc.validate())
|
self.assertIsNone(rsrc.validate())
|
||||||
|
|
||||||
@ -331,20 +276,18 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
self.assertIsInstance(rsrc.properties['period'], int)
|
self.assertIsInstance(rsrc.properties['period'], int)
|
||||||
self.assertIsInstance(rsrc.properties['threshold'], int)
|
self.assertIsInstance(rsrc.properties['threshold'], int)
|
||||||
|
|
||||||
self.m.VerifyAll()
|
|
||||||
|
|
||||||
def test_alarm_metadata_prefix(self):
|
def test_alarm_metadata_prefix(self):
|
||||||
t = template_format.parse(alarm_template)
|
t = template_format.parse(alarm_template)
|
||||||
properties = t['Resources']['MEMAlarmHigh']['Properties']
|
properties = t['Resources']['MEMAlarmHigh']['Properties']
|
||||||
# Test for bug/1383521, where meter_name is in NOVA_METERS
|
# 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':
|
properties['matching_metadata'] = {'metadata.user_metadata.groupname':
|
||||||
'foo'}
|
'foo'}
|
||||||
|
|
||||||
self.stack = self.create_stack(template=json.dumps(t))
|
test_stack = self.create_stack(template=json.dumps(t))
|
||||||
|
|
||||||
rsrc = self.stack['MEMAlarmHigh']
|
rsrc = test_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'))
|
self.assertIsNone(rsrc.properties.data.get('matching_metadata'))
|
||||||
query = rsrc.properties.data['threshold_rule']['query']
|
query = rsrc.properties.data['threshold_rule']['query']
|
||||||
expected_query = [{'field': u'metadata.user_metadata.groupname',
|
expected_query = [{'field': u'metadata.user_metadata.groupname',
|
||||||
@ -355,13 +298,13 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
t = template_format.parse(alarm_template)
|
t = template_format.parse(alarm_template)
|
||||||
properties = t['Resources']['MEMAlarmHigh']['Properties']
|
properties = t['Resources']['MEMAlarmHigh']['Properties']
|
||||||
# Test that meter_name is not in NOVA_METERS
|
# 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':
|
properties['matching_metadata'] = {'metadata.user_metadata.groupname':
|
||||||
'foo'}
|
'foo'}
|
||||||
self.stack = self.create_stack(template=json.dumps(t))
|
self.stack = self.create_stack(template=json.dumps(t))
|
||||||
|
|
||||||
rsrc = self.stack['MEMAlarmHigh']
|
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'))
|
self.assertIsNone(rsrc.properties.data.get('matching_metadata'))
|
||||||
query = rsrc.properties.data['threshold_rule']['query']
|
query = rsrc.properties.data['threshold_rule']['query']
|
||||||
expected_query = [{'field': u'metadata.metering.groupname',
|
expected_query = [{'field': u'metadata.metering.groupname',
|
||||||
@ -377,19 +320,16 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
'pro': '{"Mem": {"Ala": {"Hig"}}}',
|
'pro': '{"Mem": {"Ala": {"Hig"}}}',
|
||||||
'tro': [1, 2, 3, 4]}
|
'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()
|
test_stack.create()
|
||||||
self.stack.create()
|
rsrc = test_stack['MEMAlarmHigh']
|
||||||
rsrc = self.stack['MEMAlarmHigh']
|
|
||||||
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
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'))
|
self.assertIsNone(rsrc.properties.data.get('matching_metadata'))
|
||||||
for key in rsrc.properties.data['threshold_rule']['query']:
|
for key in rsrc.properties.data['threshold_rule']['query']:
|
||||||
self.assertIsInstance(key['value'], six.text_type)
|
self.assertIsInstance(key['value'], six.text_type)
|
||||||
|
|
||||||
self.m.VerifyAll()
|
|
||||||
|
|
||||||
def test_no_matching_metadata(self):
|
def test_no_matching_metadata(self):
|
||||||
"""Make sure that we can pass in an empty matching_metadata."""
|
"""Make sure that we can pass in an empty matching_metadata."""
|
||||||
|
|
||||||
@ -398,16 +338,13 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
properties['alarm_actions'] = ['signal_handler']
|
properties['alarm_actions'] = ['signal_handler']
|
||||||
del properties['matching_metadata']
|
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()
|
test_stack.create()
|
||||||
self.stack.create()
|
rsrc = test_stack['MEMAlarmHigh']
|
||||||
rsrc = self.stack['MEMAlarmHigh']
|
|
||||||
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
||||||
self.assertIsNone(rsrc.validate())
|
self.assertIsNone(rsrc.validate())
|
||||||
|
|
||||||
self.m.VerifyAll()
|
|
||||||
|
|
||||||
def test_mem_alarm_high_not_correct_string_parameters(self):
|
def test_mem_alarm_high_not_correct_string_parameters(self):
|
||||||
orig_snippet = template_format.parse(not_string_alarm_template)
|
orig_snippet = template_format.parse(not_string_alarm_template)
|
||||||
for p in ('period', 'evaluation_periods'):
|
for p in ('period', 'evaluation_periods'):
|
||||||
@ -416,7 +353,7 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
stack = utils.parse_stack(snippet)
|
stack = utils.parse_stack(snippet)
|
||||||
|
|
||||||
resource_defns = stack.t.resource_definitions(stack)
|
resource_defns = stack.t.resource_definitions(stack)
|
||||||
rsrc = alarm.CeilometerAlarm(
|
rsrc = alarm.AodhAlarm(
|
||||||
'MEMAlarmHigh', resource_defns['MEMAlarmHigh'], stack)
|
'MEMAlarmHigh', resource_defns['MEMAlarmHigh'], stack)
|
||||||
error = self.assertRaises(exception.StackValidationFailed,
|
error = self.assertRaises(exception.StackValidationFailed,
|
||||||
rsrc.validate)
|
rsrc.validate)
|
||||||
@ -432,7 +369,7 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
stack = utils.parse_stack(snippet)
|
stack = utils.parse_stack(snippet)
|
||||||
|
|
||||||
resource_defns = stack.t.resource_definitions(stack)
|
resource_defns = stack.t.resource_definitions(stack)
|
||||||
rsrc = alarm.CeilometerAlarm(
|
rsrc = alarm.AodhAlarm(
|
||||||
'MEMAlarmHigh', resource_defns['MEMAlarmHigh'], stack)
|
'MEMAlarmHigh', resource_defns['MEMAlarmHigh'], stack)
|
||||||
# python 3.4.3 returns another error message
|
# python 3.4.3 returns another error message
|
||||||
# so try to handle this by regexp
|
# so try to handle this by regexp
|
||||||
@ -448,7 +385,7 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
stack = utils.parse_stack(snippet)
|
stack = utils.parse_stack(snippet)
|
||||||
|
|
||||||
resource_defns = stack.t.resource_definitions(stack)
|
resource_defns = stack.t.resource_definitions(stack)
|
||||||
rsrc = alarm.CeilometerAlarm(
|
rsrc = alarm.AodhAlarm(
|
||||||
'MEMAlarmHigh', resource_defns['MEMAlarmHigh'], stack)
|
'MEMAlarmHigh', resource_defns['MEMAlarmHigh'], stack)
|
||||||
error = self.assertRaises(exception.StackValidationFailed,
|
error = self.assertRaises(exception.StackValidationFailed,
|
||||||
rsrc.validate)
|
rsrc.validate)
|
||||||
@ -464,35 +401,35 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
stack = utils.parse_stack(snippet)
|
stack = utils.parse_stack(snippet)
|
||||||
|
|
||||||
resource_defns = stack.t.resource_definitions(stack)
|
resource_defns = stack.t.resource_definitions(stack)
|
||||||
rsrc = alarm.CeilometerAlarm(
|
rsrc = alarm.AodhAlarm(
|
||||||
'MEMAlarmHigh', resource_defns['MEMAlarmHigh'], stack)
|
'MEMAlarmHigh', resource_defns['MEMAlarmHigh'], stack)
|
||||||
self.assertIsNone(rsrc.validate())
|
self.assertIsNone(rsrc.validate())
|
||||||
|
|
||||||
def test_delete_watchrule_destroy(self):
|
def test_delete_watchrule_destroy(self):
|
||||||
t = template_format.parse(alarm_template)
|
t = template_format.parse(alarm_template)
|
||||||
|
|
||||||
self.stack = self.create_stack(template=json.dumps(t))
|
test_stack = self.create_stack(template=json.dumps(t))
|
||||||
rsrc = self.stack['MEMAlarmHigh']
|
rsrc = test_stack['MEMAlarmHigh']
|
||||||
|
|
||||||
wr = mock.MagicMock()
|
wr = mock.MagicMock()
|
||||||
self.patchobject(watchrule.WatchRule, 'load', return_value=wr)
|
self.patchobject(watchrule.WatchRule, 'load', return_value=wr)
|
||||||
wr.destroy.return_value = None
|
wr.destroy.return_value = None
|
||||||
|
|
||||||
self.patchobject(ceilometer.CeilometerClientPlugin, 'client',
|
self.patchobject(aodh.AodhClientPlugin, 'client',
|
||||||
return_value=self.fa)
|
return_value=self.fa)
|
||||||
self.patchobject(self.fa.alarms, 'delete')
|
self.patchobject(self.fa.alarm, 'delete')
|
||||||
rsrc.resource_id = '12345'
|
rsrc.resource_id = '12345'
|
||||||
|
|
||||||
self.assertEqual('12345', rsrc.handle_delete())
|
self.assertEqual('12345', rsrc.handle_delete())
|
||||||
self.assertEqual(1, wr.destroy.call_count)
|
self.assertEqual(1, wr.destroy.call_count)
|
||||||
# check that super method has been called and execute deleting
|
# 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):
|
def test_delete_no_watchrule(self):
|
||||||
t = template_format.parse(alarm_template)
|
t = template_format.parse(alarm_template)
|
||||||
|
|
||||||
self.stack = self.create_stack(template=json.dumps(t))
|
test_stack = self.create_stack(template=json.dumps(t))
|
||||||
rsrc = self.stack['MEMAlarmHigh']
|
rsrc = test_stack['MEMAlarmHigh']
|
||||||
|
|
||||||
wr = mock.MagicMock()
|
wr = mock.MagicMock()
|
||||||
self.patchobject(watchrule.WatchRule, 'load',
|
self.patchobject(watchrule.WatchRule, 'load',
|
||||||
@ -500,15 +437,15 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
entity='Watch Rule', name='test')])
|
entity='Watch Rule', name='test')])
|
||||||
wr.destroy.return_value = None
|
wr.destroy.return_value = None
|
||||||
|
|
||||||
self.patchobject(ceilometer.CeilometerClientPlugin, 'client',
|
self.patchobject(aodh.AodhClientPlugin, 'client',
|
||||||
return_value=self.fa)
|
return_value=self.fa)
|
||||||
self.patchobject(self.fa.alarms, 'delete')
|
self.patchobject(self.fa.alarm, 'delete')
|
||||||
rsrc.resource_id = '12345'
|
rsrc.resource_id = '12345'
|
||||||
|
|
||||||
self.assertEqual('12345', rsrc.handle_delete())
|
self.assertEqual('12345', rsrc.handle_delete())
|
||||||
self.assertEqual(0, wr.destroy.call_count)
|
self.assertEqual(0, wr.destroy.call_count)
|
||||||
# check that super method has been called and execute deleting
|
# 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):
|
def _prepare_check_resource(self):
|
||||||
snippet = template_format.parse(not_string_alarm_template)
|
snippet = template_format.parse(not_string_alarm_template)
|
||||||
@ -516,7 +453,7 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
res = self.stack['MEMAlarmHigh']
|
res = self.stack['MEMAlarmHigh']
|
||||||
res.client = mock.Mock()
|
res.client = mock.Mock()
|
||||||
mock_alarm = mock.Mock(enabled=True, state='ok')
|
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
|
return res
|
||||||
|
|
||||||
@mock.patch.object(alarm.watchrule.WatchRule, 'load')
|
@mock.patch.object(alarm.watchrule.WatchRule, 'load')
|
||||||
@ -539,7 +476,7 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
@mock.patch.object(alarm.watchrule.WatchRule, 'load')
|
@mock.patch.object(alarm.watchrule.WatchRule, 'load')
|
||||||
def test_check_alarm_failure(self, mock_load):
|
def test_check_alarm_failure(self, mock_load):
|
||||||
res = self._prepare_check_resource()
|
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,
|
self.assertRaises(exception.ResourceFailure,
|
||||||
scheduler.TaskRunner(res.check))
|
scheduler.TaskRunner(res.check))
|
||||||
@ -548,11 +485,10 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
|
|
||||||
def test_show_resource(self):
|
def test_show_resource(self):
|
||||||
res = self._prepare_check_resource()
|
res = self._prepare_check_resource()
|
||||||
res.client().alarms.create.return_value = mock.MagicMock(
|
res.client().alarm.create.return_value = FakeAodhAlarm
|
||||||
alarm_id='2')
|
res.client().alarm.get.return_value = FakeAodhAlarm
|
||||||
res.client().alarms.get.return_value = FakeCeilometerAlarm()
|
|
||||||
scheduler.TaskRunner(res.create)()
|
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):
|
def test_alarm_with_wrong_start_time(self):
|
||||||
t = template_format.parse(alarm_template_with_time_constraints)
|
t = template_format.parse(alarm_template_with_time_constraints)
|
||||||
@ -562,11 +498,13 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
"duration": 10800,
|
"duration": 10800,
|
||||||
"description": "a description"
|
"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)
|
time_constraints=time_constraints)
|
||||||
self.m.ReplayAll()
|
test_stack.create()
|
||||||
self.stack.create()
|
self.assertEqual((test_stack.CREATE, test_stack.COMPLETE),
|
||||||
rsrc = self.stack['MEMAlarmHigh']
|
test_stack.state)
|
||||||
|
|
||||||
|
rsrc = test_stack['MEMAlarmHigh']
|
||||||
|
|
||||||
properties = copy.copy(rsrc.properties.data)
|
properties = copy.copy(rsrc.properties.data)
|
||||||
start_time = '* * * * * 100'
|
start_time = '* * * * * 100'
|
||||||
@ -605,8 +543,6 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
"[%s] is not acceptable, out of range" % (start_time, start_time),
|
"[%s] is not acceptable, out of range" % (start_time, start_time),
|
||||||
error.message)
|
error.message)
|
||||||
|
|
||||||
self.m.VerifyAll()
|
|
||||||
|
|
||||||
def test_alarm_with_wrong_timezone(self):
|
def test_alarm_with_wrong_timezone(self):
|
||||||
t = template_format.parse(alarm_template_with_time_constraints)
|
t = template_format.parse(alarm_template_with_time_constraints)
|
||||||
time_constraints = [{"name": "tc1",
|
time_constraints = [{"name": "tc1",
|
||||||
@ -615,11 +551,13 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
"duration": 10800,
|
"duration": 10800,
|
||||||
"description": "a description"
|
"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)
|
time_constraints=time_constraints)
|
||||||
self.m.ReplayAll()
|
test_stack.create()
|
||||||
self.stack.create()
|
self.assertEqual((test_stack.CREATE, test_stack.COMPLETE),
|
||||||
rsrc = self.stack['MEMAlarmHigh']
|
test_stack.state)
|
||||||
|
|
||||||
|
rsrc = test_stack['MEMAlarmHigh']
|
||||||
|
|
||||||
properties = copy.copy(rsrc.properties.data)
|
properties = copy.copy(rsrc.properties.data)
|
||||||
timezone = 'wrongtimezone'
|
timezone = 'wrongtimezone'
|
||||||
@ -658,8 +596,6 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
% (timezone, timezone),
|
% (timezone, timezone),
|
||||||
error.message)
|
error.message)
|
||||||
|
|
||||||
self.m.VerifyAll()
|
|
||||||
|
|
||||||
|
|
||||||
class CombinationAlarmTest(common.HeatTestCase):
|
class CombinationAlarmTest(common.HeatTestCase):
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ IntegrationTestGroup = [
|
|||||||
cfg.ListOpt('skip_scenario_test_list',
|
cfg.ListOpt('skip_scenario_test_list',
|
||||||
help="List of scenario test class or class.method "
|
help="List of scenario test class or class.method "
|
||||||
"names to skip ex. NeutronLoadBalancerTest, "
|
"names to skip ex. NeutronLoadBalancerTest, "
|
||||||
"CeilometerAlarmTest.test_alarm"),
|
"AodhAlarmTest.test_alarm"),
|
||||||
cfg.ListOpt('skip_test_stack_action_list',
|
cfg.ListOpt('skip_test_stack_action_list',
|
||||||
help="List of stack actions in tests to skip "
|
help="List of stack actions in tests to skip "
|
||||||
"ex. ABANDON, ADOPT, SUSPEND, RESUME"),
|
"ex. ABANDON, ADOPT, SUSPEND, RESUME"),
|
||||||
|
@ -104,7 +104,7 @@
|
|||||||
#skip_functional_test_list = <None>
|
#skip_functional_test_list = <None>
|
||||||
|
|
||||||
# List of scenario test class or class.method names to skip ex.
|
# 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>
|
#skip_scenario_test_list = <None>
|
||||||
|
|
||||||
# List of stack actions in tests to skip ex. ABANDON, ADOPT, SUSPEND, RESUME
|
# List of stack actions in tests to skip ex. ABANDON, ADOPT, SUSPEND, RESUME
|
||||||
|
@ -15,7 +15,7 @@ resources:
|
|||||||
cooldown: 0
|
cooldown: 0
|
||||||
scaling_adjustment: 1
|
scaling_adjustment: 1
|
||||||
alarm:
|
alarm:
|
||||||
type: OS::Ceilometer::Alarm
|
type: OS::Aodh::Alarm
|
||||||
properties:
|
properties:
|
||||||
description: Scale-up if the average CPU > 50% for 1 minute
|
description: Scale-up if the average CPU > 50% for 1 minute
|
||||||
meter_name: test_meter
|
meter_name: test_meter
|
@ -18,12 +18,12 @@ from heat_integrationtests.scenario import scenario_base
|
|||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class CeilometerAlarmTest(scenario_base.ScenarioTestsBase):
|
class AodhAlarmTest(scenario_base.ScenarioTestsBase):
|
||||||
"""Class is responsible for testing of ceilometer usage."""
|
"""Class is responsible for testing of aodh usage."""
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(CeilometerAlarmTest, self).setUp()
|
super(AodhAlarmTest, self).setUp()
|
||||||
self.template = self._load_template(__file__,
|
self.template = self._load_template(__file__,
|
||||||
'test_ceilometer_alarm.yaml',
|
'test_aodh_alarm.yaml',
|
||||||
'templates')
|
'templates')
|
||||||
|
|
||||||
def check_instance_count(self, stack_identifier, expected):
|
def check_instance_count(self, stack_identifier, expected):
|
Loading…
x
Reference in New Issue
Block a user