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 <shardy@redhat.com>
This commit is contained in:
Steven Hardy
2012-08-30 13:56:47 +01:00
parent bea7e3faad
commit 4772e69b38
5 changed files with 147 additions and 25 deletions

View 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

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

View 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

View 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__':

View 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")