Newer
Older
#!/usr/bin/env python
#coding=utf8
import logging
import socket
from copy import copy
from sjrpc.server import SimpleSslRpcServer
from handlers import WelcomeHandler
from conf import CCConf
from client import CCClient
from exceptions import AlreadyRegistered, NotConnectedAccountError
from orderedset import OrderedSet
from tql import TqlParser, TqlObject
from objectsdb import ObjectsDB
class CCServer(object):
'''
CloudControl server main class.
:param conf_dir: the directory that store the client configuration
:param certfile: the path to the ssl certificate
:param keyfile: the path to the ssl key
:param address: the interface to bind
:param port: the port to bind
'''
LISTEN_BACKLOG = 5
# These tags are reserved and cannot be setted by an user:
RESERVED_TAGS = ('id', 'a', 'r', 'close', 'con', 'ip')
def __init__(self, conf_dir, certfile=None, keyfile=None,
address='0.0.0.0', port=1984):
# Dict containing all connected accounts, the key is the login of
# the account and the value the :class:`RpcConnection` of the peer:
self._connected = {}
# The interface object to the configuration directory:
self.conf = CCConf(conf_dir)
# Create the server socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((address, port))
sock.listen(CCServer.LISTEN_BACKLOG)
if certfile:
logging.info('SSL Certificate: %s', certfile)
logging.info('SSL Key: %s', certfile)
logging.info('Started to listen on %s port %s', address, port)
self.objects = ObjectsDB(self)
# Create the connection manager:
self.manager = SimpleSslRpcServer(sock, certfile=certfile,
keyfile=keyfile,
default_handler=WelcomeHandler(self),
on_disconnect='on_disconnect')
# Register accounts on the database:
self._update_accounts()
def _update_accounts(self):
'''
Update the database with accounts.
'''
db_accounts = self.objects.get_ids()
accounts = set(self.conf.list_accounts())
to_register = accounts - db_accounts
to_unregister = db_accounts - accounts
for login in to_register:
conf = self.conf.show(login)
obj = TqlObject(id=login, r=conf['role'], a=login)
Antoine Millet
committed
self.objects.register(obj, cleanable=('ip', 'con'))
for login in to_unregister:
self.objects.unregister(login)
Generator to iter on each connected client with specified role. If role
is None, return all connected clients.
:param role: role to filter
'''
for login, client in self._connected.iteritems():
yield client
def register(self, login, role, connection):
'''
Register a new connected account on the server.
:param login: login of the account
:param connection: connection to register
:param tags: tags to add for the client
'''
if login in self._connected:
raise AlreadyRegistered('A client is already connected with this '
'account.')
else:
self._connected[login] = CCClient(login, role, self, connection)
def unregister(self, connection):
'''
Unregister an already connected account.
:param connection: the connection of the client to unregister
'''
client = self.search_client_by_connection(connection)
if client.login in self._connected:
del self._connected[client.login]
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
self.objects.unregister_children(client.login)
def sub_register(self, parent, name, role):
'''
Register a new node supervised by a parent.
:param parent: the parent login of the subnode
:param login: the name of the subnode
:param role: the role of the subnode
'''
obj_parent = self.objects.get_by_id(parent)
oid = '%s.%s' % (parent, name)
obj = TqlObject(id=oid, r=role, __parent=obj_parent)
self.objects.register(obj)
def sub_unregister(self, parent, name):
'''
Unregister a node supervised by a parent.
:param parent: the parent of the subnode
:param login: the name of the subnode
'''
oid = '%s.%s' % (parent, name)
self.objects.unregister(oid)
def search_client_by_connection(self, connection):
'''
Search a connected client by it connection. If no client is found,
return None.
:param connection: the connection of the client to search
:return: the found client or None
'''
for client in self._connected.values():
if client.connection is connection:
return client
else:
return None
def run(self):
'''
Run the server mainloop.
'''
logging.info('Running manager mainloop')
self.manager.run()
def get_connection(self, login):
'''
Get the connection of a connecter account login.
:param login: login of the connection to get
:return: :class:`RpcConnection` instance of the peer connection
'''
return self._connected[login]
def kill(self, login):
'''
Disconnect from the server the client identified by provided login.
:param login: the login of the user to disconnect
:throws NotConnectedAccount: when provided account is not connected (or
if account doesn't exists).
'''
client = self._connected.get(login)
if client is None:
raise NotConnectedAccountError('The account %s is not '
'connected' % login)
client.shutdown()
def check(self, client, method, tql):
rights = self.conf.show(client.login)['rights']
objects = self.list(tql, pure=True)
for right in rights:
if not (right['method'] is None or right['method'] == method):
continue
if right['tql'] is not None:
objects_right = self.list(right['tql'], pure=True)
if not objects <= objects_right:
continue
if right['target'] == 'allow':
return True
else:
return False
def list(self, query, show=set(), pure=False):
'''
List objects on the server.
:param query: the TQL to use to selection objects on list.
'''
self._update_accounts()
parser = TqlParser(query)
ast, to_show, to_get, to_check = parser.parse()
to_show += show
# Calculate the tags to get/check/show:
if to_get is not None:
to_get -= set((self.RESERVED_TAGS))
if to_check is not None:
to_check -= set((self.RESERVED_TAGS))
deny = set()
for tag in copy(to_show):
if tag == '*':
to_show = None
deny.clear()
elif tag.startswith('-'):
tag = tag[1:]
if to_show is not None and tag in to_show:
to_show.remove(tag)
if to_show is None:
to_display = None
else:
to_display = set(to_show) | to_get
if to_show is not None:
to_show = set(to_show)
to_show -= set((self.RESERVED_TAGS))
objects = OrderedSet(self.objects.all(to_get, to_check))
if ast is not None:
objects, _ = ast.eval(objects, objects)
objects_dicts = []
for obj in objects:
objects_dicts.append(obj.to_dict(to_display, deny=deny))
return objects_dicts