Skip to content
handlers.py 14.9 KiB
Newer Older
Antoine Millet's avatar
Antoine Millet committed
#!/usr/bin/env python
#coding=utf8

import inspect
import logging
from sjrpc.utils import RpcHandler, pure
from exceptions import (AlreadyRegistered, AuthenticationError, RightError,
                        ReservedTagError, BadObjectError,
                        NotConnectedAccountError)
Antoine Millet's avatar
Antoine Millet committed

def listed(func):
    func.__listed__ = True
    return func


class Reporter(object):
    '''
    Simple class used to report error, warning and success of command execution.
    '''

    def __init__(self):
        self._reports = {}

    def get_dict(self):
        return self._reports.copy()

    def success(self, oid, message):
        self._reports[oid] = ('success', message)
        
    def warn(self, oid, message):
        self._reports[oid] = ('warn', message)
        
    def error(self, oid, message):
        self._reports[oid] = ('error', message)


Antoine Millet's avatar
Antoine Millet committed
class CCHandler(RpcHandler):
    '''
    Base class for handlers of CloudControl server.
    '''
    
    def __init__(self, server):
        self._server = server

    def __getitem__(self, name):
        if name.startswith('_'):
            # Filter the private members access:
            raise KeyError('Remote name %s is private.' % repr(name))
        else:
            logging.debug('Called %s.%s' % (self.__class__.__name__, name))
            return super(CCHandler, self).__getitem__(name)
Antoine Millet's avatar
Antoine Millet committed

Antoine Millet's avatar
Antoine Millet committed

    def list_commands(self, conn):
Antoine Millet's avatar
Antoine Millet committed
        cmd_list = []

        for attr in dir(self):
            attr = getattr(self, attr, None)
            if getattr(attr, '__listed__', False):
                cmd = {}
                cmd['name'] = attr.__name__
                doc = inspect.getdoc(attr)
                if doc:
                    cmd['description'] = inspect.cleandoc(doc)
Antoine Millet's avatar
Antoine Millet committed
                cmd_list.append(cmd)

        return cmd_list

Antoine Millet's avatar
Antoine Millet committed

class OnlineCCHandler(CCHandler):

    def on_disconnect(self, conn):
        self._server.unregister(conn)
Antoine Millet's avatar
Antoine Millet committed
    def _check(self, conn, method, tql):
        client = self._server.search_client_by_connection(conn)
        allow = self._server.check(client, method, tql)
        if not allow:
            raise RightError('You are not allowed to do this action.')

Antoine Millet's avatar
Antoine Millet committed

class HypervisorHandler(OnlineCCHandler):
Antoine Millet's avatar
Antoine Millet committed
    '''
    Handler binded to 'node' role.
    '''

    @listed
    def register(self, conn, obj_id, role):
        '''
        Register an object managed by the calling node.

        .. note:
           the obj_id argument passed to this handler is the object id of the
           registered object (not the fully qualified id, the server will
           preprend the id by "node_id." itself).

        :param obj_id: the id of the object to register
        :param role: the role of the object to register
        '''

        client = self._server.search_client_by_connection(conn)
        self._server.sub_register(client.login, obj_id, role)

    @listed
    def unregister(self, conn, obj_id):
        '''
        Unregister an object managed by the calling node.

        .. note:
           the obj_id argument passed to this handler is the object id of the
           unregistered object (not the fully qualified id, the server will
           preprend the id by "node_id." itself).

        :param obj_id: the id of the object to unregister
        '''

        client = self._server.search_client_by_connection(conn)
        self._server.sub_unregister(client.login, obj_id)
Antoine Millet's avatar
Antoine Millet committed

Antoine Millet's avatar
Antoine Millet committed

class CliHandler(OnlineCCHandler):
Antoine Millet's avatar
Antoine Millet committed
    '''
    Handler binded to 'cli' role.
    '''

    def list(self, conn, query):
        '''
        List all objects registered on this instance.
        '''

        self._check(conn, 'list', query)
        logging.debug('Executed list function with query %s' % query)
    def _vm_action(self, query, method, *args, **kwargs):
        vms = self._server.list(query, show=set(('vm', 'role')))
        hypervisors = list(self._server.iter_connected_role('hypervisor'))
            vm_to_start = []
            for vm in vms:
                if vm['role'] != 'vm':
                    pass
                elif vm['id'].split('.')[0] == hv.login:
                    vm_to_start.append(vm['vm'])
            if vm_to_start:
                hv.connection.call(method, vm_to_start, *args, **kwargs)

    @listed
    def start(self, conn, query):
        self._check(conn, 'start', query)
        self._vm_action(query, 'vm_start')
    def stop(self, conn, query, force=False):
        self._check(conn, 'stop', query)
        self._vm_action(query, 'vm_stop', force)
    def destroy(self, conn, query):
        self._check(conn, 'destroy', query)
    def pause(self, conn, query):
        self._check(conn, 'pause', query)
        self._vm_action(query, 'vm_suspend')
    def resume(self, conn, query):
        self._check(conn, 'resume', query)
        self._vm_action(query, 'vm_resume')
    def passwd(self, conn, query, password, method='ssha'):
        '''
        Define a new password for specified user.
        '''
        self._check(conn, 'passwd', query)
        objects = self._server.list(query, show=set(('a',)))
        errs = Reporter()
        with self._server.conf:
            for obj in objects:
                if 'a' not in obj:
                    errs.error(obj['id'], 'not an account')
                    continue

                self._server.conf.set_password(obj['a'], password, method)

        return errs.get_dict()
    def addaccount(self, conn, login, role, password=None):
        '''
        Create a new account with specified login.
        '''
Antoine Millet's avatar
Antoine Millet committed
        self._server.conf.create_account(login, role, password)
    def addtag(self, conn, query, tag_name, tag_value):
        Add a tag to the account which match the specified query.

        self._check(conn, 'addtag', query)

        if tag_name in self._server.RESERVED_TAGS:
            raise ReservedTagError('Tag %r is read-only' % tag_name)
        objects = self._server.list(query, show=set(('a',)))
        errs = Reporter()
        with self._server.conf:
            for obj in objects:
                if 'a' not in obj:
                    errs.error(obj['id'], 'not an account')
                    continue
                tags = self._server.conf.show(obj['a'])['tags']
                if tag_name in tags:
                    errs.warn(obj['id'], 'tag already exists, changed from %r'
                                         ' to %r' % (tags[tag_name], tag_value))
                else:
                    errs.success(obj['id'], 'tag created')
                self._server.conf.add_tag(obj['a'], tag_name, tag_value)

        return errs.get_dict()
    def deltag(self, conn, query, tag_name):
        '''
        Remove a tag of the account with specified login.
        '''

        self._check(conn, 'deltag', query)

        if tag_name in self._server.RESERVED_TAGS:
            raise ReservedTagError('Tag %r is read-only' % tag_name)
        objects = self._server.list(query, show=set(('a',)))
        errs = Reporter()
        with self._server.conf:
            for obj in objects:
                if 'a' not in obj:
                    errs.error(obj['id'], 'not an account')
                    continue
                tags = self._server.conf.show(obj['a'])['tags']
                if tag_name in tags:
                    errs.success(obj['id'], 'tag deleted')
                else:
                    errs.warn(obj['id'], 'unknown tag')
                self._server.conf.remove_tag(obj['a'], tag_name)

        return errs.get_dict()
    def tags(self, conn, query):
        '''
        Return all static tags attached to the specified login.
        '''

        self._check(conn, 'tags', query)
        objects = self._server.list(query, show=set(('a',)))
        for obj in objects:
            if 'a' not in obj:
                raise BadObjectError('All objects must have the "a" tag.')
            otags = self._server.conf.show(obj['a'])['tags']
            otags.update({'id': obj['id']})
            tags.append(otags)
    def delaccount(self, conn, query):
        '''
        Delete the account with specified login.
        '''

        self._check(conn, 'delaccount', query)
        objects = self._server.list(query, show=set(('a',)))
        errs = Reporter()
        with self._server.conf:
            for obj in objects:
                if 'a' not in obj:
                    errs.error(obj['id'], 'not an account')
                    continue
                try:
                    self._server.conf.remove_account(obj['a'])
                except CCConf.UnknownAccount:
                    errs.error(obj['id'], 'unknown account')
                else:
                    errs.success(obj['id'], 'account deleted')
                
        return errs.get_dict()
    @listed
    def close(self, conn, query):
        '''
        Close an account without deleting it.
        '''

        self._check(conn, 'close', query)
        objects = self._server.list(query, show=set(('a',)))
        errs = Reporter()
        with self._server.conf:
            for obj in objects:
                if 'a' not in obj:
                    errs.error(obj['id'], 'not an account')
                    continue
                tags = self._server.conf.show(obj['a'])['tags']
                if 'close' in tags:
                    errs.warn(obj['id'], 'account already closed')
                else:
                    errs.success(obj['id'], 'closed')
                self._server.conf.add_tag(obj['a'], 'close', 'yes')

        return errs.get_dict()

    @listed
    def declose(self, conn, query):
        '''
        Re-open an closed account.
        '''

        self._check(conn, 'declose', query)
        objects = self._server.list(query, show=set(('a',)))
        errs = Reporter()
        with self._server.conf:
            for obj in objects:
                if 'a' not in obj:
                    errs.error(obj['id'], 'not an account')
                    continue
                tags = self._server.conf.show(obj['a'])['tags']
                if 'close' in tags:
                    errs.success(obj['id'], 'account declosed')
                else:
                    errs.warn(obj['id'], 'account not closed')
                self._server.conf.remove_tag(obj['a'], 'close')

        return errs.get_dict()
    @listed
    def kill(self, conn, query):
        '''
        Disconnect all connected accounts selected by query.
        '''

        self._check(conn, 'kill', query)
        objects = self._server.list(query, show=set(('a',)))
        errs = Reporter()
        with self._server.conf:
            for obj in objects:
                print obj
                if 'a' not in obj:
                    errs.error(obj['id'], 'not an account')
                    continue
                try:
                    self._server.kill(obj['a'])
                except NotConnectedAccountError:
                    errs.error(obj['id'], 'account is not connected')
                else:
                    errs.success(obj['id'], 'account killed')

        return errs.get_dict()
    @listed
    def rights(self, conn, query):
        '''
        Get the rights of an object set.
        '''

        self._check(conn, 'rights', query)
        objects = self._server.list(query, show=set(('a',)))
        rules = {}
        for obj in objects:
            if 'a' in obj:
                rules[obj['a']] = self._server.conf.show(obj['a'])['rights']
            else:
                raise BadObjectError('All objects must have the "a" tag.')
        
        return rules
    def addright(self, conn, query, tql, method=None, allow=True, index=None):
        '''
        Add a right rule to the selected objects.
        '''

        self._check(conn, 'addright', query)
        objects = self._server.list(query, show=set(('a',)))
        errs = Reporter()
        with self._server.conf:
            for obj in objects:
                if 'a' not in obj:
                    errs.error(obj['id'], 'not an account')
                    continue
                try:
                    self._server.conf.add_right(obj['a'], tql, method,
                                                allow, index)
                except conf.UnknownAccount:
                    errs.error(obj['id'], 'unknown account')
                else:
                    errs.success(obj['id'], 'right rule added')
                
        return errs.get_dict()
    def delright(self, conn, query, index):
        '''
        Remove a right rule from the selected objects.
        '''

        self._check(conn, 'delright', query)
        objects = self._server.list(query, show=set(('a',)))
        errs = Reporter()
        with self._server.conf:
            for obj in objects:
                if 'a' not in obj:
                    errs.error(obj['id'], 'not an account')
                    continue
                try:
                    self._server.conf.remove_right(obj['a'], index)
                except conf.UnknownAccount:
                    errs.error(obj['id'], 'unknown account')
                except conf.OutOfRangeIndexError:
                    errs.error(obj['id'], 'index out of range')
                else:
                    errs.success(obj['id'], 'right rule deleted')
                
        return errs.get_dict()
    def proxy_client(self, conn, login, command, *args, **kwargs):
        client = self._server.get_connection(login)
        return client.connection.call(command, *args, **kwargs)
Antoine Millet's avatar
Antoine Millet committed

Antoine Millet's avatar
Antoine Millet committed

Antoine Millet's avatar
Antoine Millet committed
class WelcomeHandler(CCHandler):
    '''
    Default handler used on client connections of the server.

    :cvar ROLES: role name/handler mapping
    '''
    
    ROLES = {
        'cli': CliHandler,
        'hv': HypervisorHandler,
Antoine Millet's avatar
Antoine Millet committed
    }

    @listed
    def authentify(self, conn, login, password):
Antoine Millet's avatar
Antoine Millet committed
        '''
        Authenticate the client.
        '''
        conf = self._server.conf
        with conf:
            try:
                role = self._server.conf.authentify(login, password)
            except CCConf.UnknownAccount:
                raise AuthenticationError('Unknown login')
            else:
                if 'close' in self._server.conf.show(login)['tags']:
                    raise AuthenticationError('Account is closed')
Antoine Millet's avatar
Antoine Millet committed

        if role is None:
            logging.info('New authentication from %s: '
                         'failure' % login.encode('ascii', 'ignore'))
            raise AuthenticationError('Bad login/password')
Antoine Millet's avatar
Antoine Millet committed
        else:
            # If authentication is a success, try to register the client:
            try:
                self._server.register(login, role, conn)
                raise AuthenticationError('Already connected')
            conn.set_handler(WelcomeHandler.ROLES.get(role)(self._server))
Antoine Millet's avatar
Antoine Millet committed
            
            return role