Source code for smc.core.waiters

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