Commit 0b71623f authored by Alain Takoudjou's avatar Alain Takoudjou

grid.promise: add support for promise without test or anomaly


Support for promise test-less or anomaly-less allow to disable either the test phase or the anomaly phase.

by default, test and anomaly are enabled, `__is_tested = True` and `__is_anomaly_detected = True`. Test will run when buildout is processing the computer partition, and anomaly when the partition is processed.

Call `self.setTestLess()` in `__init__.py` of the promise will disable Test and set `__is_tested = False`. Call `self.setAnomalyLess()` will rather disable Anomaly and set `__is_anomaly_detected = False`.


    def __init__(self, config):
        GenericPromise.__init__(self, config)
        # Skip test check on this promise
        self.setTestLess()

If the promise is test less, then slapgrid will not run the promise while processing the partition which mean that the promise will not report error, only Anomaly will be checked. Samething, if promise is anomaly less, only test will run when slapgrid is processing the partition.

/reviewed-on !93
parents 83fedcb4 1213a413
...@@ -492,6 +492,12 @@ class PromiseLauncher(object): ...@@ -492,6 +492,12 @@ class PromiseLauncher(object):
message="Error: No output returned by the promise", message="Error: No output returned by the promise",
execution_time=execution_time execution_time=execution_time
) )
elif queue_item.item is None:
# no result collected (sense skipped)
skipped_method = "Anomaly" if self.check_anomaly else "Test"
self.logger.debug("Skipped, %s is disabled in promise %r." % (
skipped_method, promise_name))
return False
if not self.dry_run: if not self.dry_run:
self._savePromiseResult(queue_item) self._savePromiseResult(queue_item)
......
...@@ -58,7 +58,7 @@ class BaseResult(object): ...@@ -58,7 +58,7 @@ class BaseResult(object):
# The promise message should be very short, # The promise message should be very short,
# a huge message size can freeze the process Pipe # a huge message size can freeze the process Pipe
# XXX this is important to prevent process deadlock # XXX this is important to prevent process deadlock
if len(message) > 5000: if message is not None and len(message) > 5000:
message = '...%s' % message[-5000:] message = '...%s' % message[-5000:]
self.__message = message self.__message = message
self.__date = date self.__date = date
...@@ -154,6 +154,9 @@ class GenericPromise(with_metaclass(ABCMeta, object)): ...@@ -154,6 +154,9 @@ class GenericPromise(with_metaclass(ABCMeta, object)):
self.setPeriodicity(self.__config.pop('periodicity', 2)) self.setPeriodicity(self.__config.pop('periodicity', 2))
self.__transaction_id = '%s-%s' % (int(time.time()), random.randint(100, 999)) self.__transaction_id = '%s-%s' % (int(time.time()), random.randint(100, 999))
self.__is_tested = True
# XXX - JP asked to use __is_anomaly_detected = True, but __is_anomaly_less seems more convenient
self.__is_anomaly_detected = True
self._validateConf() self._validateConf()
self._configureLogger() self._configureLogger()
...@@ -227,6 +230,24 @@ class GenericPromise(with_metaclass(ABCMeta, object)): ...@@ -227,6 +230,24 @@ class GenericPromise(with_metaclass(ABCMeta, object)):
def getPeriodicity(self): def getPeriodicity(self):
return self.__periodicity return self.__periodicity
def setTestLess(self):
"""
Ask to skip test method. Only Anomaly will run
"""
self.__is_tested = False
def isTested(self):
return self.__is_tested
def setAnomalyLess(self):
"""
Ask to skip anomaly method. Only Test will run
"""
self.__is_anomaly_detected = False
def isAnomalyLess(self):
return self.__is_anomaly_detected
def __bang(self, message): def __bang(self, message):
""" """
Call bang if requested Call bang if requested
...@@ -451,41 +472,58 @@ class GenericPromise(with_metaclass(ABCMeta, object)): ...@@ -451,41 +472,58 @@ class GenericPromise(with_metaclass(ABCMeta, object)):
@param can_bang: Set to True if bang can be called, this parameter should @param can_bang: Set to True if bang can be called, this parameter should
be set to False if bang is already called by another promise. be set to False if bang is already called by another promise.
""" """
try: if not self.__is_tested and not self.__is_anomaly_detected:
self.sense() message = "It's not allowed to disable both Test and Anomaly in promise!"
except Exception as e: if check_anomaly:
# log the result result = AnomalyResult(problem=True, message=message)
self.logger.error(str(e))
if check_anomaly:
# run sense, anomaly
try:
result = self.anomaly()
if result is None:
raise ValueError("Promise anomaly method returned 'None'")
except Exception as e:
result = AnomalyResult(problem=True, message=str(e))
else: else:
if isinstance(result, AnomalyResult) and result.hasFailed() and can_bang: result = TestResult(problem=True, message=message)
try: self.__sendResult(PromiseQueueResult(
self.__bang("Promise %s is failing" % self.__title) path=self.__promise_path,
except: name=self.__name,
self.logger.warning(traceback.format_exc()) title=self.__title,
item=result
))
elif (not self.__is_tested and not check_anomaly) or \
(not self.__is_anomaly_detected and check_anomaly):
# Anomaly or Test is disabled on this promise, send empty result
self.__sendResult(PromiseQueueResult())
else: else:
# run sense, test
try: try:
result = self.test() self.sense()
if result is None:
raise ValueError("Promise test method returned 'None'")
except Exception as e: except Exception as e:
result = TestResult(problem=True, message=str(e)) # log the result
self.logger.error(str(e))
if check_anomaly:
# run sense, anomaly
try:
result = self.anomaly()
if result is None:
raise ValueError("Promise anomaly method returned 'None'")
except Exception as e:
result = AnomalyResult(problem=True, message=str(e))
else:
if isinstance(result, AnomalyResult) and result.hasFailed() and can_bang:
try:
self.__bang("Promise %s is failing" % self.__title)
except:
self.logger.warning(traceback.format_exc())
else:
# run sense, test
try:
result = self.test()
if result is None:
raise ValueError("Promise test method returned 'None'")
except Exception as e:
result = TestResult(problem=True, message=str(e))
# send the result of this promise
self.__sendResult(PromiseQueueResult(
path=self.__promise_path,
name=self.__name,
title=self.__title,
item=result
))
if self.__logger_buffer is not None: if self.__logger_buffer is not None:
self.__logger_buffer.close() self.__logger_buffer.close()
# send the result of this promise
self.__sendResult(PromiseQueueResult(
path=self.__promise_path,
name=self.__name,
title=self.__title,
item=result
))
...@@ -133,7 +133,7 @@ class TestSlapOSPromiseMixin(unittest.TestCase): ...@@ -133,7 +133,7 @@ class TestSlapOSPromiseMixin(unittest.TestCase):
os.chmod(path, mode) os.chmod(path, mode)
def generatePromiseScript(self, name, success=True, failure_count=1, content="", def generatePromiseScript(self, name, success=True, failure_count=1, content="",
periodicity=0.03): periodicity=0.03, is_tested=True, with_anomaly=True):
promise_content = """from zope.interface import implementer promise_content = """from zope.interface import implementer
from slapos.grid.promise import interface from slapos.grid.promise import interface
from slapos.grid.promise import GenericPromise from slapos.grid.promise import GenericPromise
...@@ -144,6 +144,10 @@ class RunPromise(GenericPromise): ...@@ -144,6 +144,10 @@ class RunPromise(GenericPromise):
def __init__(self, config): def __init__(self, config):
GenericPromise.__init__(self, config) GenericPromise.__init__(self, config)
self.setPeriodicity(minute=%(periodicity)s) self.setPeriodicity(minute=%(periodicity)s)
if not %(is_tested)s:
self.setTestLess()
if not %(with_anomaly)s:
self.setAnomalyLess()
def sense(self): def sense(self):
%(content)s %(content)s
...@@ -160,7 +164,7 @@ class RunPromise(GenericPromise): ...@@ -160,7 +164,7 @@ class RunPromise(GenericPromise):
return self._test(latest_minute=%(periodicity)s, failure_amount=%(failure_amount)s) return self._test(latest_minute=%(periodicity)s, failure_amount=%(failure_amount)s)
""" % {'success': success, 'content': content, 'failure_amount': failure_count, """ % {'success': success, 'content': content, 'failure_amount': failure_count,
'periodicity': periodicity} 'periodicity': periodicity, 'is_tested': is_tested, 'with_anomaly': with_anomaly}
with open(os.path.join(self.plugin_dir, name), 'w') as f: with open(os.path.join(self.plugin_dir, name), 'w') as f:
f.write(promise_content) f.write(promise_content)
...@@ -1076,6 +1080,95 @@ exit 1 ...@@ -1076,6 +1080,95 @@ exit 1
with self.assertRaises(PromiseError): with self.assertRaises(PromiseError):
self.launcher.run() self.launcher.run()
def test_runpromise_not_tested(self):
promise_name = 'my_promise.py'
def test_method(result):
self.called = True
self.called = False
self.configureLauncher(save_method=test_method, timeout=5, enable_anomaly=False)
self.generatePromiseScript(promise_name, success=True, content="""import time
time.sleep(20)""", periodicity=0.01, is_tested=False,)
# will not run the promise in test mode (so no sleep)
self.launcher.run()
# no result returned by the promise
self.assertFalse(self.called)
def test_runpromise_not_tested_with_anomaly(self):
promise_name = 'my_promise.py'
def test_method(result):
self.called = True
self.assertTrue(isinstance(result, PromiseQueueResult))
self.assertTrue(isinstance(result.item, AnomalyResult))
self.called = False
self.configureLauncher(save_method=test_method, timeout=1, enable_anomaly=True)
self.generatePromiseScript(promise_name, success=True,
periodicity=0.01, is_tested=False,)
# will run the promise because we are in anomaly mode
self.launcher.run()
# promise result is saved
self.assertTrue(self.called)
def test_runpromise_without_anomaly(self):
promise_name = 'my_promise.py'
def test_method(result):
self.called = True
self.called = False
self.configureLauncher(save_method=test_method, timeout=1, enable_anomaly=True)
self.generatePromiseScript(promise_name, success=True, content="""import time
time.sleep(20)""", periodicity=0.01, with_anomaly=False,)
# will not run the promise in anomaly mode (so no sleep)
self.launcher.run()
# no result returned by the promise
self.assertFalse(self.called)
def test_runpromise_without_anomaly_but_test(self):
promise_name = 'my_promise.py'
def test_method(result):
self.called = True
self.assertTrue(isinstance(result, PromiseQueueResult))
self.assertTrue(isinstance(result.item, TestResult))
self.called = False
self.configureLauncher(save_method=test_method, timeout=1, enable_anomaly=False)
self.generatePromiseScript(promise_name, success=True,
periodicity=0.01, with_anomaly=False,)
# will run the promise because we are in anomaly mode
self.launcher.run()
# promise result is saved
self.assertTrue(self.called)
def test_runpromise_not_tested_without_anomaly_fail(self):
promise_name = 'my_promise.py'
def test_method(result):
self.called = True
self.assertTrue(isinstance(result, PromiseQueueResult))
self.assertTrue(isinstance(result.item, AnomalyResult))
self.assertEqual(result.item.message, "It's not allowed to disable both Test and Anomaly in promise!")
self.assertEqual(result.item.hasFailed(), True)
self.called = False
self.configureLauncher(save_method=test_method, timeout=1, enable_anomaly=True)
self.generatePromiseScript(promise_name, success=True, content="""import time
time.sleep(20)""", periodicity=0.01, with_anomaly=False, is_tested=False)
# will fail because disable anomaly and test is not allowed
with self.assertRaises(PromiseError):
self.launcher.run()
# no result returned by the promise
self.assertTrue(self.called)
class TestSlapOSGenericPromise(TestSlapOSPromiseMixin): class TestSlapOSGenericPromise(TestSlapOSPromiseMixin):
def initialisePromise(self, promise_content="", success=True, timeout=60): def initialisePromise(self, promise_content="", success=True, timeout=60):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment