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:
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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__':
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user