Merge "add time_constraints property in OS::Ceilometer::Alarm"

This commit is contained in:
Jenkins 2015-08-04 09:20:00 +00:00 committed by Gerrit Code Review
commit 7cc74c26a1
3 changed files with 212 additions and 9 deletions

View File

@ -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=[],
) )
} }

View File

@ -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)

View File

@ -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)