Skip to content
__init__.py 7.43 KiB
Newer Older
""" Connected client management package.

This package store classes representing each client's role and the associated
sjRPC handler.
"""

import logging

from datetime import datetime

from sjrpc.utils import ConnectionProxy

from ccserver.handlers import CCHandler, listed
from ccserver.exceptions import RightError
from ccserver.db import RemoteTag
from cloudcontrol.common.tql.db.tag import CallbackTag


class RegisteredCCHandler(CCHandler):

    """ Basic handler for all registered clients.
    """

    def __getitem__(self, name):
        self.client.top()
        return super(RegisteredCCHandler, self).__getitem__(name)

    def on_disconnect(self, conn):
        logging.info('Client %s disconnected', self.client.login)
        self.client.shutdown()

    def check(self, method, tql=None):
        """ Check if the client have access to this method.
        """
        allow = self.client.server.check(self.client.login, method, tql)
        if not allow:
            raise RightError('You are not allowed to do this action.')

    #
    # Tags registration handler functions:
    #

    @listed
    def tags_register(self, name, ttl=None, value=None):
        """ Register a new tag on the calling node.

        :param name: name of the tag to register
        :param ttl: ttl of the tag (or None if not applicable)
        :param value: value to fill the tag (optionnal)
        """
        self.client.tags_register(name, ttl, value)

    @listed
    def tags_unregister(self, name):
        """ Unregister a tag on the calling node.

        :param name: name of the tag to unregister
        """
        self.client.tags_unregister(name)

    @listed
    def tags_drop(self, name):
        """ Drop the tag value of the specified tag on the calling node.

        :param name: name of the tag to drop
        """
        self.client.tags_drop(name)

    @listed
    def tags_update(self, name, value, ttl=None):
        """ Update the value of the specified tag on the calling node.

        :param name: name of the tag to update
        :param value: new tag value
        :param ttl: new ttl value
        """
        self.client.tags_update(name, value, ttl)


class Client(object):

    """ Base class for all types cc-server clients.

    :param login: login of the client
    :param server: server instance
    :param connection: rpc connection to the client
    """

    ROLE = None
    RPC_HANDLER = RegisteredCCHandler

    roles = {}

    def __init__(self, login, server, connection, tql_object):
        self._login = login
        self._server = server
        self._connection = connection
        self._tql_object = tql_object
        self._handler = self.RPC_HANDLER(self)
        self._proxy = ConnectionProxy(self._connection.rpc)
        self._last_action = datetime.now()
        self._connection_date = datetime.now()

        # Set the role's handler for the client:
        self._connection.rpc.set_handler(self._handler)

        # Remote tags registered:
        self._remote_tags = set()

        # Register the server defined client tags:
        self._tql_object.register(CallbackTag('con', lambda: self.uptime, ttl=0))
        self._tql_object.register(CallbackTag('idle', lambda: self.idle, ttl=0))
        self._tql_object.register(CallbackTag('ip', lambda: self.ip))

    @classmethod
    def register_client_class(cls, class_):
        """ Register a new client class.
        """
        cls.roles[class_.ROLE] = class_

    @classmethod
    def from_role(cls, role, login, server, connection, tql_object):
        return cls.roles[role](login, server, connection, tql_object)

    #
    # Properties
    #

    @property
    def proxy(self):
        """ Return a proxy to the rpc.
        """
        return self._proxy

    @property
    def object(self):
        """ Return the tql object of this client.
        """
        return self._tql_object

    @property
    def login(self):
        """ Return the login of this client.
        """
        return self._login

    @property
    def server(self):
        """ Return the cc-server binded to this client.
        """
        return self._server

    @property
    def conn(self):
        """ Return the sjrpc connection to the client.
        """
        return self._connection

    @property
    def uptime(self):
        """ Get the uptime of the client connection in seconds.

        :return: uptime of the client
        """

        dt = datetime.now() - self._connection_date
        return dt.seconds + dt.days * 86400

    @property
    def idle(self):
        """ Get the idle time of the client connection in seconds.

        :return: idle of the client
        """
        dt = datetime.now() - self._last_action
        return dt.seconds + dt.days * 86400

    @property
    def ip(self):
        """ Get client remote ip address.
        """
        peer = self.conn.getpeername()
        return ':'.join(peer.split(':')[:-1])

    def get_tags(self, tags):                                                   # DEPRECATED
        """ Get tags on the remote node.

        :param tags: tags is the list of tags to fetch
        """
        return self._connection.call('get_tags', tags)

    def shutdown(self):
        """ Shutdown the connection to the client.
        """
        # Unregister all remote tags:
        for tag in self._remote_tags.copy():
            self.tags_unregister(tag)

        # Unrefister all server defined client tags:
        self._tql_object.unregister('con')
        self._tql_object.unregister('idle')
        self._tql_object.unregister('ip')

        self._server.rpc.unregister(self.conn, shutdown=True)
        self._server.unregister(self)

    def top(self):
        """ Reset the "last action" date to now.
        """
        self._last_action = datetime.now()

    def get_remote_tags(self, tag):                                             # DEPRECATED
        return self.conn.call('get_tags', (tag,))[tag]

    def async_remote_tags(self, watcher, robj, tags):
        """ Asynchronously update tags from the remote client using
            specified watcher.
        """
        watcher.register(self.conn, 'get_tags', tags, _data=robj)

    def tags_register(self, name, ttl=None, value=None):
        """ Register a new remote tag for the client.

        :param name: name of the tag to register
        :param ttl: TTL of the tag if applicable (None = no TTL, the tag will
                never expire)
        :param value: value of the tag
        """

        tag = RemoteTag(name, self.async_remote_tags, ttl=ttl)
        self._tql_object.register(tag)
        self._remote_tags.add(name)

    def tags_unregister(self, name):
        """
        Unregister a remote tag for the client.

        :param name: name of the tag to unregister
        """

        self._tql_object.unregister(name)
        self._remote_tags.discard(name)

    def tags_drop(self, name):
        """ Drop the cached value of a remote tag for the client.

        :param name: name of the tag to drop
        """
        tag = self._tql_object.get(name)
        if tag is not None:
            tag.invalidate()

    def tags_update(self, name, value=None, ttl=None):
        """ Update a remote tag.

        :param name: name of the tag to update
        :param value: new value of the tag
        :param ttl: new ttl of the tag
        """
        tag = self._tql_object.get(name)
        if tag is not None:
            if value is not None:
            if ttl is not None:
                tag.ttl = ttl