Skip to content
Snippets Groups Projects
node.py 5.25 KiB
Newer Older
import time
import logging
from threading import Thread, Lock

Anael Beutot's avatar
Anael Beutot committed
from sjrpc.core import RpcConnection
from sjrpc.utils import ConnectionProxy, RpcHandler

from ccnode import __version__
from ccnode.tags import Tag, get_tags


logger = logging.getLogger(__name__)


Anael Beutot's avatar
Anael Beutot committed
DEFAULT_TAGS = (Tag(u'version', __version__, -1),)


class DefaultHandler(RpcHandler):
    """Base handler for :class:`Node` objects. Containing only a ``version``
    tag that returns current ``cc-node`` version.

    See `sjRpc documentation <http://google.fr>`_ for more information.

    """
    def __init__(self, *args, **kwargs):
        RpcHandler.__init__(self)

        self.tags = dict((t.name, t) for t in DEFAULT_TAGS)

    def get_tags(self, tags=None, noresolve_tags=None):
        """Method used from the ``cc-server`` to get tags.

        :param iterable tags: list of tags to return
        :param iterable noresolve_tags: list of tags to not return
        """
        return get_tags(self, tags, noresolve_tags)
    def set_tag(self, tag):
        self.tags[tag.name] = tag

    def remove_tag(self, tag_name):
        self.tags.pop(tag_name, None)


class Node(Thread):
    """Main class for ccnode."""
    def __init__(self, server_host, server_port, user_name, user_passwd):
        """
        :param string server_host: hostname for cc-server
        :param int server_port: port for cc-server
        :param string user_name: account name for authentication to cc-server
        :param string user_passwd: password for cc-server authentication
        """
        Thread.__init__(self)

Anael Beutot's avatar
Anael Beutot committed
        # run thread as daemon
        self.daemon = True

        # settings used as read only
        self.server_host = server_host
        self.server_port = int(server_port)
        self.user_name = user_name
        self.user_passwd = user_passwd

        #: ``sjRpc`` proxy
        self.proxy = None
Anael Beutot's avatar
Anael Beutot committed
        #: ``sjRpc`` connection
        self.connection = None
        #: role returned by cc-server (set to None unless the authentication
        #: has succeed)
        self.role = None

Anael Beutot's avatar
Anael Beutot committed
        self._connection_lock = Lock()

    def init_rpc(self):
Anael Beutot's avatar
Anael Beutot committed
        """Init a new connection to ``cc-server``, create a ``sjRpc`` proxy.
Anael Beutot's avatar
Anael Beutot committed
        self.connection = RpcConnection.from_addr_ssl(
            addr=self.server_host,
            port=self.server_port,
Anael Beutot's avatar
Anael Beutot committed
            handler=DefaultHandler(),
Anael Beutot's avatar
Anael Beutot committed
        self.proxy = ConnectionProxy(self.connection)

    def authentify(self):
        """Try to authenticate to the server. If successfull, import and then
        set :class:`Handler` corresponding to the role returned by the
        ``cc-server``.
        :raise: exception raised by :func:`proxy.authentify`
        """
        try:
            role = self.proxy.authentify(self.user_name, self.user_passwd)
        except Exception:
Anael Beutot's avatar
Anael Beutot committed
            logger.exception('Unknow exception while authentifying')
            raise

        # set handler according to which role was returned by the cc-server
        if role == u'host':
Anael Beutot's avatar
Anael Beutot committed
            logger.debug('Role host affected')
            from ccnode.host import Handler as HostHandler
Anael Beutot's avatar
Anael Beutot committed
            self.connection.rpc.set_handler(HostHandler())
            self.role = u'host'
        elif role == u'hv':
Anael Beutot's avatar
Anael Beutot committed
            logger.debug('Role hypervisor affected')
            from ccnode.hypervisor import Handler as HypervisorHandler
Anael Beutot's avatar
Anael Beutot committed
            self.connection.rpc.set_handler(HypervisorHandler(
                proxy=self.proxy, hypervisor_name=self.user_name))
            self.role = u'hv'
        else:
Anael Beutot's avatar
Anael Beutot committed
            logger.debug('Wrong role returned: %s', role)
            role = None
            time.sleep(2)

        self.role = role

    def rpc(self):
        """Runs ``sjRpc`` main loop. Catches exceptions. :func:`shutdown` the
Anael Beutot's avatar
Anael Beutot committed
        connection before returning."""
Anael Beutot's avatar
Anael Beutot committed
            self.connection.run()
        except Exception:
Anael Beutot's avatar
Anael Beutot committed
            logger.exception('Unknown exception:')
        finally:
            self.shutdown()

    def run(self):
        """Node main loop."""
        while True:
            # init rpc connection
            while True:
                try:
                    self.init_rpc()
                except Exception as e:
Anael Beutot's avatar
Anael Beutot committed
                    logger.exception('Error in init.')
                else:
                    break
                time.sleep(2)
            # launch main rpc thread
            rpc_thread = Thread(target=self.rpc)
            rpc_thread.daemon = True
            rpc_thread.start()
            # launch auth thread, make sure rpc is still running
            while rpc_thread.is_alive() and self.role is None:
                auth_thread = Thread(target=self.authentify)
                auth_thread.daemon = True
                auth_thread.start()
                auth_thread.join()
                # FIXME for debug
                time.sleep(4)

            # wait for rpc thread to terminates (it means error)
            rpc_thread.join()
            logger.error('Reconnecting to server.')
            # reset settings
            self.role = None

    def shutdown(self):
Anael Beutot's avatar
Anael Beutot committed
        """Shutdown the ``sjRpc`` connection and reset object state."""
        with self._connection_lock:
            if self.connection is not None:
                self.connection.shutdown()
                self.connection = None
                self.role = None