<gh_stars>10-100
"""
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'])


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

    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

    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)

    def done(self):
        """
        Is the task still running or considered complete

        :rtype: bool
        """
        return self._done.is_set() or not self.isAlive()

    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

    def wait(self, timeout=None):
        """
        Blocking method to wait for thread
        """
        self.join(timeout)

    def stop(self):
        """
        Stop thread if it's still running
        """
        if self.isAlive():
            self._done.set()


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)


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)


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)

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


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

    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

    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)

    def done(self):
        """
        Is the task still running or considered complete

        :rtype: bool
        """
        return self._done.is_set() or not self.isAlive()

    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

    def wait(self, timeout=None):
        """
        Blocking method to wait for thread
        """
        self.join(timeout)

    def stop(self):
        """
        Stop thread if it's still running
        """
        if self.isAlive():
            self._done.set()


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)


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)


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)
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)) #: Configuration status constant values #: Node status constant values #: Node state constant values Node Waiter provides a common threaded interface to monitoring     a nodes status and wait for a specific response. #node resource # Raises NodeCommandFailed # Modified in 0.6.2 to support SMC 6.5 where the attribute name changed Add a callback to run after the task completes.         The callable must take 1 argument which will be         the completed Task.          :param callable callback Is the task still running or considered complete          :rtype: bool 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. Blocking method to wait for thread Stop thread if it's still running 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. 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 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.