"""
Waiters are convenience classes that use blocking or non-blocking threads to
monitor for a particular state of an engine node.
A waiter can have a callback added that will be executed after either
the state has matched, a number of iterations exceeded or an exception is
caught while monitoring. The callback should be a callable that takes a single
argument.
They provide the ability to perform logical actions such as "wait for the engine to
have status 'Configured', then fire a policy upload task".
Example of waiting for an engine to be ready, then send policy::
class ContainerPolicyCallback(object):
def __init__(self, container):
self.engine = engine
def __call__(self, status):
if status == 'Configured':
self.engine.upload(policy='MyPolicy')
engine = Engine('myengine')
callback = ContainerPolicyCallback(engine)
waiter = ConfigurationStatusWaiter(engine.nodes[0], 'Configured')
waiter.add_done_callback(callback)
Waiters can also be blocking while waiting for status. Example of using a waiter
to block input while waiting for the engine to reach a specific status::
waiter = ConfigurationStatusWaiter(node, 'Initial', max_wait=5)
while not waiter.done():
print("Status after 5 sec wait: %s" % waiter.result(5))
"""
import time
import threading
#: Configuration status constant values
CFG_STATUS = frozenset(['Initial', 'Declared', 'Configured', 'Installed'])
#: Node status constant values
STATUS = frozenset(['Not Monitored', 'Unknown', 'Online', 'Going Online',
'Locked Online', 'Going Locked Online','Offline','Going Offline',
'Locked Offline', 'Going Locked Offline','Standby','Going Standby',
'No Policy Installed', 'Policy Out Of Date'])
#: Node state constant values
STATE = frozenset(['INITIAL', 'READY', 'ERROR', 'SERVER_ERROR', 'NO_STATUS',
'TIMEOUT', 'DELETED', 'DUMMY'])
[docs]class NodeWaiter(threading.Thread):
"""
Node Waiter provides a common threaded interface to monitoring
a nodes status and wait for a specific response.
"""
def __init__(self, resource, status, timeout=5,
max_wait=36, **kw):
threading.Thread.__init__(self)
self._desired_status = status
self._resource = resource #node resource
self._status = None
self._max_wait = max_wait
self._timeout = timeout
self.callbacks = []
self._done = threading.Event()
self.daemon = True
self.start()
[docs] def run(self):
while not self.finished():
time.sleep(self._timeout)
try:
self._status = self._get_status()
self._max_wait -= 1
except Exception as e:
self._status = e
break
self._done.set()
for call in self.callbacks:
call(self._status)
def _get_status(self):
# Raises NodeCommandFailed
# Modified in 0.6.2 to support SMC 6.5 where the attribute name changed
status = self._resource.status()
latest = [getattr(status, attr) for attr in self.value if getattr(status, attr)]
if self._desired_status in latest:
return self._desired_status
else:
return latest[0] or None
def finished(self):
return self._done.is_set() or \
self._status == self._desired_status or \
self._max_wait == 0
[docs] def add_done_callback(self, callback):
"""
Add a callback to run after the task completes.
The callable must take 1 argument which will be
the completed Task.
:param callable callback
"""
if self._done.is_set():
raise ValueError('Thread has already terminated, cannot add callback.')
if callable(callback):
self.callbacks.append(callback)
[docs] def done(self):
"""
Is the task still running or considered complete
:rtype: bool
"""
return self._done.is_set() or not self.isAlive()
[docs] def result(self, timeout=None):
"""
Get current status result after waiting timeout
Result does a join on the thread to get a status update. It
is possible the first couple of statuses are None if an
update has not yet been joined.
"""
self.wait(timeout)
return self._status
[docs] def wait(self, timeout=None):
"""
Blocking method to wait for thread
"""
self.join(timeout)
[docs] def stop(self):
"""
Stop thread if it's still running
"""
if self.isAlive():
self._done.set()
[docs]class ConfigurationStatusWaiter(NodeWaiter):
"""
Configuration status waiter provides a current engine status
with respects to having a configuration.
:param Node resource: Engine node to check for status
:param str status: used defined status to wait for.
:raises NodeCommandFailed: Failure to obtain a status back
from the engine. This can be thrown when getting initial
status. If thrown after the thread has started, it is caught
and returned in the ``result`` after ending the thread.
"""
value = ('configuration_status',)
def __init__(self, resource, status, **kw):
if status not in CFG_STATUS:
raise ValueError(
'Status is invalid. Valid options are: %s' % CFG_STATUS)
super(ConfigurationStatusWaiter, self).__init__(resource, status, **kw)
[docs]class NodeStatusWaiter(NodeWaiter):
"""
Node Status specifies the current state of the engine such as offline, online,
locked offline, no policy installed, etc.
:param Node resource: Engine node to check for status
:param str status: used defined status to wait for.
:raises NodeCommandFailed: Failure to obtain a status back
from the engine. This can be thrown when getting initial
status. If thrown after the thread has started, it is caught
and returned in the ``result`` after ending the thread.
"""
# Node status changed in SMC 6.5 from status to monitoring_status so
# value was changed to an iterable to check for a status other than
# None for both attributes
value = ('status', 'monitoring_status', 'engine_node_status')
def __init__(self, resource, status, **kw):
if status not in STATUS:
raise ValueError(
'Status is invalid. Valid options are: %s' % STATUS)
super(NodeStatusWaiter, self).__init__(resource, status, **kw)
[docs]class NodeStateWaiter(NodeWaiter):
"""
Node State specifies where the engine is within it's lifecycle, such as
initial state, ready state, error, timeout, etc.
:param Node resource: Engine node to check for status
:param str status: used defined status to wait for.
:raises NodeCommandFailed: Failure to obtain a status back
from the engine. This can be thrown when getting initial
status. If thrown after the thread has started, it is caught
and returned in the ``result`` after ending the thread.
"""
value = ('state', 'monitoring_state')
def __init__(self, resource, status, **kw):
if status not in STATE:
raise ValueError(
'Status is invalid. Valid options are: %s' % STATE)
super(NodeStateWaiter, self).__init__(resource, status, **kw)