From 4772e69b3895a737521eadb741f774680d7a7b27 Mon Sep 17 00:00:00 2001 From: Steven Hardy Date: Thu, 30 Aug 2012 13:56:47 +0100 Subject: [PATCH] heat engine : Add set_watch_state engine RPC action Add set_watch_state which allows a watch/alarm state to be temporarily overridden, simplified asynchronous version following review/discussion Change-Id: I9f1424007fc16d1cec2f7dc504600455fe5ab3bb Signed-off-by: Steven Hardy --- heat/engine/manager.py | 87 +++++++++++++++++++++++-------- heat/engine/rpcapi.py | 11 ++++ heat/engine/watchrule.py | 9 ++-- heat/tests/test_engine_manager.py | 61 ++++++++++++++++++++++ heat/tests/test_rpcapi.py | 4 ++ 5 files changed, 147 insertions(+), 25 deletions(-) diff --git a/heat/engine/manager.py b/heat/engine/manager.py index a4ba4828..bf16842b 100644 --- a/heat/engine/manager.py +++ b/heat/engine/manager.py @@ -423,36 +423,41 @@ class EngineManager(manager.Manager): self.run_rule(context, wr, now) def run_rule(self, context, wr, now=timeutils.utcnow()): - action_map = {'ALARM': 'AlarmActions', - 'NORMAL': 'OKActions', - 'NODATA': 'InsufficientDataActions'} - watcher = watchrule.WatchRule(wr.rule, wr.watch_data, wr.last_evaluated, now) new_state = watcher.get_alarm_state() if new_state != wr.state: - logger.warn('WATCH: stack:%s, watch_name:%s %s', - wr.stack_name, wr.name, new_state) - - if not action_map[new_state] in wr.rule: - logger.info('no action for new state %s', - new_state) + if self.rule_action(wr, new_state): wr.state = new_state - wr.save() - else: - s = db_api.stack_get_by_name(None, wr.stack_name) - if s and s.status in ('CREATE_COMPLETE', - 'UPDATE_COMPLETE'): - user_creds = db_api.user_creds_get(s.user_creds_id) - ctxt = ctxtlib.RequestContext.from_dict(dict(user_creds)) - stack = parser.Stack.load(ctxt, s.id) - for a in wr.rule[action_map[new_state]]: - greenpool.spawn_n(stack[a].alarm) - wr.state = new_state - wr.save() wr.last_evaluated = now + wr.save() + + def rule_action(self, wr, new_state): + # TODO : push watch-rule processing into engine.watchrule + logger.warn('WATCH: stack:%s, watch_name:%s %s', + wr.stack_name, wr.name, new_state) + + actioned = False + if not watchrule.WatchRule.ACTION_MAP[new_state] in wr.rule: + logger.info('no action for new state %s', + new_state) + actioned = True + else: + s = db_api.stack_get_by_name(None, wr.stack_name) + if s and s.status in (parser.Stack.CREATE_COMPLETE, + parser.Stack.UPDATE_COMPLETE): + user_creds = db_api.user_creds_get(s.user_creds_id) + ctxt = ctxtlib.RequestContext.from_dict(dict(user_creds)) + stack = parser.Stack.load(ctxt, s.id) + for a in wr.rule[watchrule.WatchRule.ACTION_MAP[new_state]]: + greenpool.spawn_n(stack[a].alarm) + actioned = True + else: + logger.warning("Could not process watch state %s for stack" % + new_state) + return actioned def create_watch_data(self, context, watch_name, stats_data): ''' @@ -529,3 +534,41 @@ class EngineManager(manager.Manager): result = [api.format_watch_data(w) for w in wds] return result + + def set_watch_state(self, context, watch_name, state): + ''' + Temporarily set the state of a given watch + arg1 -> RPC context. + arg2 -> Name of the watch + arg3 -> State (must be one defined in WatchRule class + ''' + + if state not in watchrule.WatchRule.WATCH_STATES: + raise AttributeError('Unknown watch state %s' % state) + + if watch_name: + try: + wr = db_api.watch_rule_get(context, watch_name) + except Exception as ex: + logger.warn('show_watch (%s) db error %s' % + (watch_name, str(ex))) + + if not wr: + raise AttributeError('Unknown watch name %s' % watch_name) + + else: + raise AttributeError('Must pass watch_name') + + if state != wr.state: + if self.rule_action(wr, state): + logger.debug("Overriding state %s for watch %s with %s" % + (wr.state, watch_name, state)) + else: + logger.warning("Unable to override state %s for watch %s" % + (wr.state, watch_name)) + + # Return the watch with the state overriden to indicate success + # We do not update the timestamps as we are not modifying the DB + result = api.format_watch(wr) + result[api.WATCH_STATE_VALUE] = state + return result diff --git a/heat/engine/rpcapi.py b/heat/engine/rpcapi.py index 691a31fb..3586b9c5 100644 --- a/heat/engine/rpcapi.py +++ b/heat/engine/rpcapi.py @@ -247,3 +247,14 @@ class EngineAPI(heat.openstack.common.rpc.proxy.RpcProxy): return self.call(ctxt, self.make_msg('show_watch_metric', namespace=namespace, metric_name=metric_name), topic=_engine_topic(self.topic, ctxt, None)) + + def set_watch_state(self, ctxt, watch_name, state): + ''' + Temporarily set the state of a given watch + arg1 -> RPC context. + arg2 -> Name of the watch + arg3 -> State (must be one defined in WatchRule class) + ''' + return self.call(ctxt, self.make_msg('set_watch_state', + watch_name=watch_name, state=state), + topic=_engine_topic(self.topic, ctxt, None)) diff --git a/heat/engine/watchrule.py b/heat/engine/watchrule.py index e92f6632..33fa1e64 100644 --- a/heat/engine/watchrule.py +++ b/heat/engine/watchrule.py @@ -22,9 +22,12 @@ logger = logging.getLogger('heat.engine.watchrule') class WatchRule(object): - ALARM = 'ALARM' - NORMAL = 'NORMAL' - NODATA = 'NODATA' + WATCH_STATES = (ALARM, NORMAL, NODATA + ) = ('ALARM', 'NORMAL', 'NODATA') + + ACTION_MAP = {ALARM: 'AlarmActions', + NORMAL: 'OKActions', + NODATA: 'InsufficientDataActions'} def __init__(self, rule, dataset, last_evaluated, now): self.rule = rule diff --git a/heat/tests/test_engine_manager.py b/heat/tests/test_engine_manager.py index 42e64721..172ae569 100644 --- a/heat/tests/test_engine_manager.py +++ b/heat/tests/test_engine_manager.py @@ -32,6 +32,7 @@ import heat.db as db_api from heat.engine import parser from heat.engine import manager from heat.engine import auth +from heat.engine import watchrule tests_dir = os.path.dirname(os.path.realpath(__file__)) @@ -427,6 +428,66 @@ class stackManagerTest(unittest.TestCase): for key in engine_api.WATCH_DATA_KEYS: self.assertTrue(key in result[0]) + def test_set_watch_state(self): + # Insert dummy watch rule into the DB + values = {'stack_name': u'wordpress_ha', 'state': 'NORMAL', + 'name': u'OverrideAlarm', + 'rule': { + u'EvaluationPeriods': u'1', + u'AlarmActions': [u'WebServerRestartPolicy'], + u'AlarmDescription': u'Restart the WikiDatabase', + u'Namespace': u'system/linux', + u'Period': u'300', + u'ComparisonOperator': u'GreaterThanThreshold', + u'Statistic': u'SampleCount', + u'Threshold': u'2', + u'MetricName': u'ServiceFailure'}} + db_ret = db_api.watch_rule_create(self.ctx, values) + self.assertNotEqual(db_ret, None) + + for state in watchrule.WatchRule.WATCH_STATES: + 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) + + # Cleanup, delete the dummy rule + db_api.watch_rule_delete(self.ctx, "OverrideAlarm") + + def test_set_watch_state_badstate(self): + # Insert dummy watch rule into the DB + values = {'stack_name': u'wordpress_ha', 'state': 'NORMAL', + 'name': u'OverrideAlarm2', + 'rule': { + u'EvaluationPeriods': u'1', + u'AlarmActions': [u'WebServerRestartPolicy'], + u'AlarmDescription': u'Restart the WikiDatabase', + u'Namespace': u'system/linux', + u'Period': u'300', + u'ComparisonOperator': u'GreaterThanThreshold', + u'Statistic': u'SampleCount', + u'Threshold': u'2', + u'MetricName': u'ServiceFailure'}} + db_ret = db_api.watch_rule_create(self.ctx, values) + self.assertNotEqual(db_ret, None) + + for state in ["HGJHGJHG", "1234", "!\*(&%"]: + self.assertRaises(AttributeError, + self.man.set_watch_state, + self.ctx, watch_name="OverrideAlarm2", + state=state) + + # Cleanup, delete the dummy rule + db_api.watch_rule_delete(self.ctx, "OverrideAlarm2") + + def test_set_watch_state_noexist(self): + # watch_name="nonexistent" should raise an AttributeError + state = watchrule.WatchRule.ALARM # State valid + self.assertRaises(AttributeError, + self.man.set_watch_state, + self.ctx, watch_name="nonexistent", state=state) + # allows testing of the test directly if __name__ == '__main__': diff --git a/heat/tests/test_rpcapi.py b/heat/tests/test_rpcapi.py index 06115722..2d02337e 100644 --- a/heat/tests/test_rpcapi.py +++ b/heat/tests/test_rpcapi.py @@ -173,3 +173,7 @@ class EngineRpcAPITestCase(unittest.TestCase): def test_show_watch_metric(self): self._test_engine_api('show_watch_metric', 'call', namespace=None, metric_name=None) + + def test_set_watch_state(self): + self._test_engine_api('set_watch_state', 'call', + watch_name='watch1', state="xyz") -- 2.45.2