add time_constraints property in OS::Ceilometer::Alarm
Alarm already support time constraints property, but "OS::Ceilometer::Alarm" resource in heat is not support yet, so we couldn't create an alarm constrained by time-of-day or day-of-week by launching stack in heat. Change-Id: I4d05a6bf98ca7d616c36398fdfe91dff20559f3a Partial-Bug: #1447942
This commit is contained in:
parent
9044625ce4
commit
e24aef1a91
@ -24,12 +24,17 @@ from heat.engine import watchrule
|
|||||||
|
|
||||||
COMMON_PROPERTIES = (
|
COMMON_PROPERTIES = (
|
||||||
ALARM_ACTIONS, OK_ACTIONS, REPEAT_ACTIONS, INSUFFICIENT_DATA_ACTIONS,
|
ALARM_ACTIONS, OK_ACTIONS, REPEAT_ACTIONS, INSUFFICIENT_DATA_ACTIONS,
|
||||||
DESCRIPTION, ENABLED,
|
DESCRIPTION, ENABLED, TIME_CONSTRAINTS
|
||||||
) = (
|
) = (
|
||||||
'alarm_actions', 'ok_actions', 'repeat_actions',
|
'alarm_actions', 'ok_actions', 'repeat_actions',
|
||||||
'insufficient_data_actions', 'description', 'enabled',
|
'insufficient_data_actions', 'description', 'enabled', 'time_constraints'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
_TIME_CONSTRAINT_KEYS = (
|
||||||
|
NAME, START, DURATION, TIMEZONE, TIME_CONSTRAINT_DESCRIPTION,
|
||||||
|
) = (
|
||||||
|
'name', 'start', 'duration', 'timezone', 'description'
|
||||||
|
)
|
||||||
|
|
||||||
common_properties_schema = {
|
common_properties_schema = {
|
||||||
DESCRIPTION: properties.Schema(
|
DESCRIPTION: properties.Schema(
|
||||||
@ -68,6 +73,58 @@ common_properties_schema = {
|
|||||||
"each time the threshold is reached."),
|
"each time the threshold is reached."),
|
||||||
default='true',
|
default='true',
|
||||||
update_allowed=True
|
update_allowed=True
|
||||||
|
),
|
||||||
|
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=[],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,41 @@ alarm_template = '''
|
|||||||
"threshold": "50",
|
"threshold": "50",
|
||||||
"alarm_actions": [],
|
"alarm_actions": [],
|
||||||
"matching_metadata": {},
|
"matching_metadata": {},
|
||||||
"comparison_operator": "gt"
|
"comparison_operator": "gt",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"signal_handler" : {
|
||||||
|
"Type" : "SignalResourceType"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
alarm_template_with_time_constraints = '''
|
||||||
|
{
|
||||||
|
"AWSTemplateFormatVersion" : "2010-09-09",
|
||||||
|
"Description" : "Alarm Test",
|
||||||
|
"Parameters" : {},
|
||||||
|
"Resources" : {
|
||||||
|
"MEMAlarmHigh": {
|
||||||
|
"Type": "OS::Ceilometer::Alarm",
|
||||||
|
"Properties": {
|
||||||
|
"description": "Scale-up if MEM > 50% for 1 minute",
|
||||||
|
"meter_name": "MemoryUtilization",
|
||||||
|
"statistic": "avg",
|
||||||
|
"period": "60",
|
||||||
|
"evaluation_periods": "1",
|
||||||
|
"threshold": "50",
|
||||||
|
"alarm_actions": [],
|
||||||
|
"matching_metadata": {},
|
||||||
|
"comparison_operator": "gt",
|
||||||
|
"time_constraints":
|
||||||
|
[{"name": "tc1",
|
||||||
|
"start": "0 23 * * *",
|
||||||
|
"timezone": "Asia/Taipei",
|
||||||
|
"duration": 10800,
|
||||||
|
"description": "a description"
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"signal_handler" : {
|
"signal_handler" : {
|
||||||
@ -77,7 +111,7 @@ not_string_alarm_template = '''
|
|||||||
"threshold": 50,
|
"threshold": 50,
|
||||||
"alarm_actions": [],
|
"alarm_actions": [],
|
||||||
"matching_metadata": {},
|
"matching_metadata": {},
|
||||||
"comparison_operator": "gt"
|
"comparison_operator": "gt",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"signal_handler" : {
|
"signal_handler" : {
|
||||||
@ -118,7 +152,7 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
super(CeilometerAlarmTest, self).setUp()
|
super(CeilometerAlarmTest, self).setUp()
|
||||||
self.fa = mock.Mock()
|
self.fa = mock.Mock()
|
||||||
|
|
||||||
def create_stack(self, template=None):
|
def create_stack(self, template=None, time_constraints=[]):
|
||||||
if template is None:
|
if template is None:
|
||||||
template = alarm_template
|
template = alarm_template
|
||||||
temp = template_format.parse(template)
|
temp = template_format.parse(template)
|
||||||
@ -140,6 +174,7 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
al['ok_actions'] = None
|
al['ok_actions'] = None
|
||||||
al['repeat_actions'] = True
|
al['repeat_actions'] = True
|
||||||
al['enabled'] = True
|
al['enabled'] = True
|
||||||
|
al['time_constraints'] = time_constraints
|
||||||
rule = dict(
|
rule = dict(
|
||||||
period=60,
|
period=60,
|
||||||
evaluation_periods=1,
|
evaluation_periods=1,
|
||||||
@ -190,6 +225,7 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
al2 = dict((k, mox.IgnoreArg())
|
al2 = dict((k, mox.IgnoreArg())
|
||||||
for k, s in schema.items()
|
for k, s in schema.items()
|
||||||
if s.update_allowed and k not in exns)
|
if s.update_allowed and k not in exns)
|
||||||
|
al2['time_constraints'] = mox.IgnoreArg()
|
||||||
al2['alarm_id'] = mox.IgnoreArg()
|
al2['alarm_id'] = mox.IgnoreArg()
|
||||||
al2['type'] = 'threshold'
|
al2['type'] = 'threshold'
|
||||||
al2['threshold_rule'] = dict(
|
al2['threshold_rule'] = dict(
|
||||||
@ -476,6 +512,112 @@ class CeilometerAlarmTest(common.HeatTestCase):
|
|||||||
scheduler.TaskRunner(res.create)()
|
scheduler.TaskRunner(res.create)()
|
||||||
self.assertEqual({'attr': 'val'}, res.FnGetAtt('show'))
|
self.assertEqual({'attr': 'val'}, res.FnGetAtt('show'))
|
||||||
|
|
||||||
|
def test_alarm_with_wrong_start_time(self):
|
||||||
|
t = template_format.parse(alarm_template_with_time_constraints)
|
||||||
|
time_constraints = [{"name": "tc1",
|
||||||
|
"start": "0 23 * * *",
|
||||||
|
"timezone": "Asia/Taipei",
|
||||||
|
"duration": 10800,
|
||||||
|
"description": "a description"
|
||||||
|
}]
|
||||||
|
self.stack = self.create_stack(template=json.dumps(t),
|
||||||
|
time_constraints=time_constraints)
|
||||||
|
self.m.ReplayAll()
|
||||||
|
self.stack.create()
|
||||||
|
rsrc = self.stack['MEMAlarmHigh']
|
||||||
|
|
||||||
|
properties = copy.copy(rsrc.properties.data)
|
||||||
|
start_time = '* * * * * 100'
|
||||||
|
properties.update({
|
||||||
|
'comparison_operator': 'lt',
|
||||||
|
'description': 'fruity',
|
||||||
|
'evaluation_periods': '2',
|
||||||
|
'period': '90',
|
||||||
|
'enabled': True,
|
||||||
|
'repeat_actions': True,
|
||||||
|
'statistic': 'max',
|
||||||
|
'threshold': '39',
|
||||||
|
'insufficient_data_actions': [],
|
||||||
|
'alarm_actions': [],
|
||||||
|
'ok_actions': ['signal_handler'],
|
||||||
|
'matching_metadata': {'x': 'y'},
|
||||||
|
'query': [dict(field='c', op='ne', value='z')],
|
||||||
|
'time_constraints': [{"name": "tc1",
|
||||||
|
"start": start_time,
|
||||||
|
"timezone": "Asia/Taipei",
|
||||||
|
"duration": 10800,
|
||||||
|
"description": "a description"
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
snippet = rsrc_defn.ResourceDefinition(rsrc.name,
|
||||||
|
rsrc.type(),
|
||||||
|
properties)
|
||||||
|
error = self.assertRaises(
|
||||||
|
exception.ResourceFailure,
|
||||||
|
scheduler.TaskRunner(rsrc.update, snippet)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
"StackValidationFailed: resources.MEMAlarmHigh: Property error: "
|
||||||
|
"MEMAlarmHigh.Properties.time_constraints[0].start: Error "
|
||||||
|
"validating value '%s': Invalid CRON expression: "
|
||||||
|
"[%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",
|
||||||
|
"start": "0 23 * * *",
|
||||||
|
"timezone": "Asia/Taipei",
|
||||||
|
"duration": 10800,
|
||||||
|
"description": "a description"
|
||||||
|
}]
|
||||||
|
self.stack = self.create_stack(template=json.dumps(t),
|
||||||
|
time_constraints=time_constraints)
|
||||||
|
self.m.ReplayAll()
|
||||||
|
self.stack.create()
|
||||||
|
rsrc = self.stack['MEMAlarmHigh']
|
||||||
|
|
||||||
|
properties = copy.copy(rsrc.properties.data)
|
||||||
|
timezone = 'wrongtimezone'
|
||||||
|
properties.update({
|
||||||
|
'comparison_operator': 'lt',
|
||||||
|
'description': 'fruity',
|
||||||
|
'evaluation_periods': '2',
|
||||||
|
'period': '90',
|
||||||
|
'enabled': True,
|
||||||
|
'repeat_actions': True,
|
||||||
|
'statistic': 'max',
|
||||||
|
'threshold': '39',
|
||||||
|
'insufficient_data_actions': [],
|
||||||
|
'alarm_actions': [],
|
||||||
|
'ok_actions': ['signal_handler'],
|
||||||
|
'matching_metadata': {'x': 'y'},
|
||||||
|
'query': [dict(field='c', op='ne', value='z')],
|
||||||
|
'time_constraints': [{"name": "tc1",
|
||||||
|
"start": "0 23 * * *",
|
||||||
|
"timezone": timezone,
|
||||||
|
"duration": 10800,
|
||||||
|
"description": "a description"
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
snippet = rsrc_defn.ResourceDefinition(rsrc.name,
|
||||||
|
rsrc.type(),
|
||||||
|
properties)
|
||||||
|
error = self.assertRaises(
|
||||||
|
exception.ResourceFailure,
|
||||||
|
scheduler.TaskRunner(rsrc.update, snippet)
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
"StackValidationFailed: resources.MEMAlarmHigh: Property error: "
|
||||||
|
"MEMAlarmHigh.Properties.time_constraints[0].timezone: Error "
|
||||||
|
"validating value '%s': Invalid timezone: '%s'"
|
||||||
|
% (timezone, timezone),
|
||||||
|
error.message)
|
||||||
|
|
||||||
|
self.m.VerifyAll()
|
||||||
|
|
||||||
|
|
||||||
class CombinationAlarmTest(common.HeatTestCase):
|
class CombinationAlarmTest(common.HeatTestCase):
|
||||||
|
|
||||||
@ -497,7 +639,8 @@ class CombinationAlarmTest(common.HeatTestCase):
|
|||||||
name=mox.IgnoreArg(), type='combination',
|
name=mox.IgnoreArg(), type='combination',
|
||||||
repeat_actions=True,
|
repeat_actions=True,
|
||||||
combination_rule={'alarm_ids': [u'alarm1', u'alarm2'],
|
combination_rule={'alarm_ids': [u'alarm1', u'alarm2'],
|
||||||
'operator': u'and'}
|
'operator': u'and'},
|
||||||
|
time_constraints=[]
|
||||||
).AndReturn(FakeCeilometerAlarm())
|
).AndReturn(FakeCeilometerAlarm())
|
||||||
snippet = template_format.parse(combination_alarm_template)
|
snippet = template_format.parse(combination_alarm_template)
|
||||||
self.stack = utils.parse_stack(snippet)
|
self.stack = utils.parse_stack(snippet)
|
||||||
|
@ -117,7 +117,8 @@ class GnocchiResourcesAlarmTest(common.HeatTestCase):
|
|||||||
"resource_type": "instance",
|
"resource_type": "instance",
|
||||||
"resource_id": "5a517ceb-b068-4aca-9eb9-3e4eb9b90d9a",
|
"resource_id": "5a517ceb-b068-4aca-9eb9-3e4eb9b90d9a",
|
||||||
"comparison_operator": "gt",
|
"comparison_operator": "gt",
|
||||||
}
|
},
|
||||||
|
time_constraints=[]
|
||||||
).AndReturn(FakeCeilometerAlarm())
|
).AndReturn(FakeCeilometerAlarm())
|
||||||
snippet = template_format.parse(gnocchi_resources_alarm_template)
|
snippet = template_format.parse(gnocchi_resources_alarm_template)
|
||||||
self.stack = utils.parse_stack(snippet)
|
self.stack = utils.parse_stack(snippet)
|
||||||
@ -259,7 +260,8 @@ class GnocchiAggregationByMetricsAlarmTest(GnocchiResourcesAlarmTest):
|
|||||||
"comparison_operator": "gt",
|
"comparison_operator": "gt",
|
||||||
"metrics": ["911fce07-e0d7-4210-8c8c-4a9d811fcabc",
|
"metrics": ["911fce07-e0d7-4210-8c8c-4a9d811fcabc",
|
||||||
"2543d435-fe93-4443-9351-fb0156930f94"],
|
"2543d435-fe93-4443-9351-fb0156930f94"],
|
||||||
}
|
},
|
||||||
|
time_constraints=[]
|
||||||
).AndReturn(FakeCeilometerAlarm())
|
).AndReturn(FakeCeilometerAlarm())
|
||||||
snippet = template_format.parse(
|
snippet = template_format.parse(
|
||||||
gnocchi_aggregation_by_metrics_alarm_template)
|
gnocchi_aggregation_by_metrics_alarm_template)
|
||||||
@ -334,7 +336,8 @@ class GnocchiAggregationByResourcesAlarmTest(GnocchiResourcesAlarmTest):
|
|||||||
"metric": "cpu_util",
|
"metric": "cpu_util",
|
||||||
"resource_type": "instance",
|
"resource_type": "instance",
|
||||||
"query": '{"=": {"server_group": "my_autoscaling_group"}}',
|
"query": '{"=": {"server_group": "my_autoscaling_group"}}',
|
||||||
}
|
},
|
||||||
|
time_constraints=[]
|
||||||
).AndReturn(FakeCeilometerAlarm())
|
).AndReturn(FakeCeilometerAlarm())
|
||||||
snippet = template_format.parse(
|
snippet = template_format.parse(
|
||||||
gnocchi_aggregation_by_resources_alarm_template)
|
gnocchi_aggregation_by_resources_alarm_template)
|
||||||
|
Loading…
Reference in New Issue
Block a user