import time import logging from threading import Thread, Lock 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__) 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) # 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 #: ``sjRpc`` connection self.connection = None #: role returned by cc-server (set to None unless the authentication #: has succeed) self.role = None self._connection_lock = Lock() def init_rpc(self): """Init a new connection to ``cc-server``, create a ``sjRpc`` proxy. """ self.connection = RpcConnection.from_addr_ssl( addr=self.server_host, port=self.server_port, handler=DefaultHandler(), ) 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: logger.exception('Unknow exception while authentifying') raise # set handler according to which role was returned by the cc-server if role == u'host': logger.debug('Role host affected') from ccnode.host import Handler as HostHandler self.connection.rpc.set_handler(HostHandler()) self.role = u'host' elif role == u'hv': logger.debug('Role hypervisor affected') from ccnode.hypervisor import Handler as HypervisorHandler self.connection.rpc.set_handler(HypervisorHandler( proxy=self.proxy, hypervisor_name=self.user_name)) self.role = u'hv' else: 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 connection before returning.""" try: self.connection.run() except Exception: 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: 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): """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