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 nexedi/slapos.core!93
parents 83fedcb4 1213a413
......@@ -492,6 +492,12 @@ class PromiseLauncher(object):
message="Error: No output returned by the promise",
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:
self._savePromiseResult(queue_item)
......
......@@ -58,7 +58,7 @@ class BaseResult(object):
# The promise message should be very short,
# a huge message size can freeze the process Pipe
# 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:]
self.__message = message
self.__date = date
......@@ -154,6 +154,9 @@ class GenericPromise(with_metaclass(ABCMeta, object)):
self.setPeriodicity(self.__config.pop('periodicity', 2))
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._configureLogger()
......@@ -227,6 +230,24 @@ class GenericPromise(with_metaclass(ABCMeta, object)):
def getPeriodicity(self):
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):
"""
Call bang if requested
......@@ -451,41 +472,58 @@ class GenericPromise(with_metaclass(ABCMeta, object)):
@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.
"""
try:
self.sense()
except Exception as 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))
if not self.__is_tested and not self.__is_anomaly_detected:
message = "It's not allowed to disable both Test and Anomaly in promise!"
if check_anomaly:
result = AnomalyResult(problem=True, message=message)
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())
result = TestResult(problem=True, message=message)
self.__sendResult(PromiseQueueResult(
path=self.__promise_path,
name=self.__name,
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:
# run sense, test
try:
result = self.test()
if result is None:
raise ValueError("Promise test method returned 'None'")
self.sense()
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:
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):
os.chmod(path, mode)
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
from slapos.grid.promise import interface
from slapos.grid.promise import GenericPromise
......@@ -144,6 +144,10 @@ class RunPromise(GenericPromise):
def __init__(self, config):
GenericPromise.__init__(self, config)
self.setPeriodicity(minute=%(periodicity)s)
if not %(is_tested)s:
self.setTestLess()
if not %(with_anomaly)s:
self.setAnomalyLess()
def sense(self):
%(content)s
......@@ -160,7 +164,7 @@ class RunPromise(GenericPromise):
return self._test(latest_minute=%(periodicity)s, failure_amount=%(failure_amount)s)
""" % {'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:
f.write(promise_content)
......@@ -1076,6 +1080,95 @@ exit 1
with self.assertRaises(PromiseError):
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):
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