]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
heat engine : Add set_watch_state engine RPC action
authorSteven Hardy <shardy@redhat.com>
Thu, 30 Aug 2012 12:56:47 +0000 (13:56 +0100)
committerSteven Hardy <shardy@redhat.com>
Fri, 31 Aug 2012 15:11:28 +0000 (16:11 +0100)
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 <shardy@redhat.com>
heat/engine/manager.py
heat/engine/rpcapi.py
heat/engine/watchrule.py
heat/tests/test_engine_manager.py
heat/tests/test_rpcapi.py

index a4ba4828908f1cf877fb28b4d14e7bed81c6221b..bf16842bf22e8e8dd4afd2daf8752cf7c0ebf1ad 100644 (file)
@@ -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
index 691a31fb16121427dd8b51be1c444b789f37529b..3586b9c55d73dd3e83a0022962c90f40e327d4ed 100644 (file)
@@ -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))
index e92f66326a555de6ea38069dbd17e3536d489a82..33fa1e64850eaaefb23fde58378c6fdf5aa9d187 100644 (file)
@@ -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
index 42e647217b4df1220875b7ddfdc266f770e7034e..172ae56997315f343cdcef564ef4a8af44385593 100644 (file)
@@ -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__':
index 061157227c1c86a7b0ff431f55e0f245f29cf06a..2d02337e2104157049f06712ef38504eec4fad7c 100644 (file)
@@ -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")