from heat.db import api as db_api
from heat.engine import parser
from heat.rpc import api as rpc_api
-import eventlet
logger = logging.getLogger(__name__)
-greenpool = eventlet.GreenPool()
class WatchRule(object):
# has enough time progressed to run the rule
self.now = timeutils.utcnow()
if self.now < (self.last_evaluated + self.timeperiod):
- return
- self.run_rule()
+ return []
+ return self.run_rule()
def run_rule(self):
new_state = self.get_alarm_state()
+ actions = []
if new_state != self.state:
- action = self.rule_action(new_state)
+ actions = self.rule_actions(new_state)
self.state = new_state
self.last_evaluated = self.now
self.store()
+ return actions
- def rule_action(self, new_state):
+ def rule_actions(self, new_state):
logger.warn('WATCH: stack:%s, watch_name:%s %s',
self.stack_id, self.name, new_state)
-
- actioned = False
+ actions = []
if not self.ACTION_MAP[new_state] in self.rule:
logger.info('no action for new state %s',
new_state)
- actioned = True
else:
s = db_api.stack_get(self.context, self.stack_id)
if s and s.status in (parser.Stack.CREATE_COMPLETE,
parser.Stack.UPDATE_COMPLETE):
stack = parser.Stack.load(self.context, stack=s)
for a in self.rule[self.ACTION_MAP[new_state]]:
- greenpool.spawn_n(stack[a].alarm)
- actioned = True
+ actions.append(stack[a].alarm)
else:
logger.warning("Could not process watch state %s for stack" %
new_state)
- return actioned
+ return actions
def create_watch_data(self, data):
if not self.rule['MetricName'] in data:
def set_watch_state(self, state):
'''
- Temporarily set the watch state
+ Temporarily set the watch state, returns list of functions to be
+ scheduled in the stack ThreadGroup for the specified state
'''
if state not in self.WATCH_STATES:
raise ValueError('Unknown watch state %s' % state)
+ actions = []
if state != self.state:
- if self.rule_action(state):
+ actions = self.rule_actions(state)
+ if actions:
logger.debug("Overriding state %s for watch %s with %s" %
(self.state, self.name, state))
else:
logger.warning("Unable to override state %s for watch %s" %
(self.state, self.name))
+ return actions
class DummyThreadGroup(object):
+ def __init__(self):
+ self.threads = []
+
def add_timer(self, interval, callback, initial_delay=None,
*args, **kwargs):
pass
def add_thread(self, callback, *args, **kwargs):
+ self.threads.append(callback)
pass
def stop(self):
class DummyAction:
alarm = "dummyfoo"
+ dummy_action = DummyAction()
self.m.StubOutWithMock(parser.Stack, '__getitem__')
parser.Stack.__getitem__(
- 'WebServerRestartPolicy').AndReturn(DummyAction())
+ 'WebServerRestartPolicy').AndReturn(dummy_action)
- self.m.StubOutWithMock(watchrule.greenpool, 'spawn_n')
- watchrule.greenpool.spawn_n("dummyfoo").AndReturn(None)
- self.m.ReplayAll()
+ # Replace the real stack threadgroup with a dummy one, so we can
+ # check the function returned on ALARM is correctly scheduled
+ self.man.stg[self.stack.id] = DummyThreadGroup()
- for state in watchrule.WatchRule.WATCH_STATES:
+ self.m.ReplayAll()
- result = self.man.set_watch_state(self.ctx,
- watch_name="OverrideAlarm",
- state=state)
- self.assertNotEqual(result, None)
- self.assertEqual(result[engine_api.WATCH_STATE_VALUE], state)
+ state = watchrule.WatchRule.NODATA
+ result = self.man.set_watch_state(self.ctx,
+ watch_name="OverrideAlarm",
+ state=state)
+ self.assertEqual(result[engine_api.WATCH_STATE_VALUE], state)
+ self.assertEqual(self.man.stg[self.stack.id].threads, [])
+
+ state = watchrule.WatchRule.NORMAL
+ result = self.man.set_watch_state(self.ctx,
+ watch_name="OverrideAlarm",
+ state=state)
+ self.assertEqual(result[engine_api.WATCH_STATE_VALUE], state)
+ self.assertEqual(self.man.stg[self.stack.id].threads, [])
+
+ state = watchrule.WatchRule.ALARM
+ result = self.man.set_watch_state(self.ctx,
+ watch_name="OverrideAlarm",
+ state=state)
+ self.assertEqual(result[engine_api.WATCH_STATE_VALUE], state)
+ self.assertEqual(self.man.stg[self.stack.id].threads,
+ [DummyAction.alarm])
# Cleanup, delete the dummy rule
db_api.watch_rule_delete(self.ctx, "OverrideAlarm")
from heat.openstack.common import timeutils
from heat.engine import watchrule
+from heat.engine import parser
class WatchData:
'Unit': 'Count'}}
+class DummyAction:
+ alarm = "DummyAction"
+
+
@attr(tag=['unit', 'watchrule'])
@attr(speed='fast')
class WatchRuleTest(unittest.TestCase):
# Create a dummy stack in the DB as WatchRule instances
# must be associated with a stack
ctx = context.get_admin_context()
- tmpl = db_api.raw_template_create(ctx, {'foo': 'bar'})
+ empty_tmpl = {"template": {}}
+ tmpl = db_api.raw_template_create(ctx, empty_tmpl)
dummy_stack = {'id': '6754d843-bed2-40dc-a325-84882bb90a98',
'name': 'dummystack',
'raw_template_id': tmpl.id,
self.m = mox.Mox()
self.ctx = context.get_admin_context()
- self.m.StubOutWithMock(self.ctx, 'username')
self.ctx.username = self.username
+ self.ctx.tenant_id = u'123456'
self.m.ReplayAll()
def tearDown(self):
self.m.UnsetStubs()
+ def _action_set_stubs(self, now):
+ # Setup stubs for the action tests
+ self.m.StubOutWithMock(timeutils, 'utcnow')
+ timeutils.utcnow().MultipleTimes().AndReturn(now)
+
+ dummy_action = DummyAction()
+ self.m.StubOutWithMock(parser.Stack, '__getitem__')
+ parser.Stack.__getitem__(mox.IgnoreArg()
+ ).MultipleTimes().AndReturn(dummy_action)
+
+ self.m.ReplayAll()
+
def test_minimum(self):
rule = {'EvaluationPeriods': '1',
'MetricName': 'test_metric',
stack_id=self.stack_id,
last_evaluated=last)
- watcher.evaluate()
+ actions = watcher.evaluate()
self.assertEqual(watcher.state, 'NORMAL')
+ self.assertEqual(actions, [])
# now - last == Period, so should set ALARM
last = now - datetime.timedelta(seconds=300)
stack_id=self.stack_id,
last_evaluated=last)
- watcher.evaluate()
+ actions = watcher.evaluate()
self.assertEqual(watcher.state, 'ALARM')
self.assertEqual(watcher.last_evaluated, now)
+ # No AlarmActions defined in the rule, so expect []
+ self.assertEqual(actions, [])
+
+ def test_rule_actions_alarm_normal(self):
+ rule = {'EvaluationPeriods': '1',
+ 'MetricName': 'test_metric',
+ 'AlarmActions': ['DummyAction'],
+ 'Period': '300',
+ 'Statistic': 'Maximum',
+ 'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
+ 'Threshold': '30'}
+
+ now = timeutils.utcnow()
+ self._action_set_stubs(now)
+
+ # Set data so rule evaluates to NORMAL state
+ last = now - datetime.timedelta(seconds=300)
+ data = WatchData(25, now - datetime.timedelta(seconds=150))
+ watcher = watchrule.WatchRule(context=self.ctx,
+ watch_name="testwatch",
+ rule=rule,
+ watch_data=[data],
+ stack_id=self.stack_id,
+ last_evaluated=last)
+
+ actions = watcher.evaluate()
+ self.assertEqual(watcher.state, 'NORMAL')
+ self.assertEqual(actions, [])
+
+ def test_rule_actions_alarm_alarm(self):
+ rule = {'EvaluationPeriods': '1',
+ 'MetricName': 'test_metric',
+ 'AlarmActions': ['DummyAction'],
+ 'Period': '300',
+ 'Statistic': 'Maximum',
+ 'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
+ 'Threshold': '30'}
+
+ now = timeutils.utcnow()
+ self._action_set_stubs(now)
+
+ # Set data so rule evaluates to ALARM state
+ last = now - datetime.timedelta(seconds=300)
+ data = WatchData(35, now - datetime.timedelta(seconds=150))
+ watcher = watchrule.WatchRule(context=self.ctx,
+ watch_name="testwatch",
+ rule=rule,
+ watch_data=[data],
+ stack_id=self.stack_id,
+ last_evaluated=last)
+
+ actions = watcher.evaluate()
+ self.assertEqual(watcher.state, 'ALARM')
+ self.assertEqual(actions, ['DummyAction'])
+
+ # re-set last_evaluated so the rule will be evaluated again,
+ # but since we're already in ALARM state, we should not generate
+ # any additional actions
+ last = now - datetime.timedelta(seconds=300)
+ watcher.last_evaluated = last
+ actions = watcher.evaluate()
+ self.assertEqual(watcher.state, 'ALARM')
+ self.assertEqual(actions, [])
+
+ def test_rule_actions_alarm_two_actions(self):
+ rule = {'EvaluationPeriods': '1',
+ 'MetricName': 'test_metric',
+ 'AlarmActions': ['DummyAction', 'AnotherDummyAction'],
+ 'Period': '300',
+ 'Statistic': 'Maximum',
+ 'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
+ 'Threshold': '30'}
+
+ now = timeutils.utcnow()
+ self._action_set_stubs(now)
+
+ # Set data so rule evaluates to ALARM state
+ last = now - datetime.timedelta(seconds=300)
+ data = WatchData(35, now - datetime.timedelta(seconds=150))
+ watcher = watchrule.WatchRule(context=self.ctx,
+ watch_name="testwatch",
+ rule=rule,
+ watch_data=[data],
+ stack_id=self.stack_id,
+ last_evaluated=last)
+
+ actions = watcher.evaluate()
+ self.assertEqual(watcher.state, 'ALARM')
+ self.assertEqual(actions, ['DummyAction', 'DummyAction'])
+
+ def test_rule_actions_ok_alarm(self):
+ rule = {'EvaluationPeriods': '1',
+ 'MetricName': 'test_metric',
+ 'OKActions': ['DummyAction'],
+ 'Period': '300',
+ 'Statistic': 'Maximum',
+ 'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
+ 'Threshold': '30'}
+
+ now = timeutils.utcnow()
+ self._action_set_stubs(now)
+
+ # Set data so rule evaluates to ALARM state
+ last = now - datetime.timedelta(seconds=300)
+ data = WatchData(35, now - datetime.timedelta(seconds=150))
+ watcher = watchrule.WatchRule(context=self.ctx,
+ watch_name="testwatch",
+ rule=rule,
+ watch_data=[data],
+ stack_id=self.stack_id,
+ last_evaluated=last)
+
+ actions = watcher.evaluate()
+ self.assertEqual(watcher.state, 'ALARM')
+ self.assertEqual(actions, [])
+
+ # Move time forward and add data below threshold so we transition from
+ # ALARM -> NORMAL, so evaluate() should output a 'DummyAction'
+ now = now + datetime.timedelta(seconds=300)
+ self.m.UnsetStubs()
+ self._action_set_stubs(now)
+
+ data = WatchData(25, now - datetime.timedelta(seconds=150))
+ watcher.watch_data = [data]
+
+ actions = watcher.evaluate()
+ self.assertEqual(watcher.state, 'NORMAL')
+ self.assertEqual(actions, ['DummyAction'])
+
+ def test_rule_actions_nodata(self):
+ rule = {'EvaluationPeriods': '1',
+ 'MetricName': 'test_metric',
+ 'InsufficientDataActions': ['DummyAction'],
+ 'Period': '300',
+ 'Statistic': 'Maximum',
+ 'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
+ 'Threshold': '30'}
+
+ now = timeutils.utcnow()
+ self._action_set_stubs(now)
+
+ # Set data so rule evaluates to ALARM state
+ last = now - datetime.timedelta(seconds=300)
+ data = WatchData(35, now - datetime.timedelta(seconds=150))
+ watcher = watchrule.WatchRule(context=self.ctx,
+ watch_name="testwatch",
+ rule=rule,
+ watch_data=[data],
+ stack_id=self.stack_id,
+ last_evaluated=last)
+
+ actions = watcher.evaluate()
+ self.assertEqual(watcher.state, 'ALARM')
+ self.assertEqual(actions, [])
+
+ # Move time forward and don't add data so we transition from
+ # ALARM -> NODATA, so evaluate() should output a 'DummyAction'
+ now = now + datetime.timedelta(seconds=300)
+ self.m.UnsetStubs()
+ self._action_set_stubs(now)
+
+ actions = watcher.evaluate()
+ self.assertEqual(watcher.state, 'NODATA')
+ self.assertEqual(actions, ['DummyAction'])