Newer
Older
from __future__ import absolute_import
from sjrpc.core import RpcError
from ccserver.conf import CCConf
from ccserver.exceptions import (AlreadyRegistered, AuthenticationError,
RightError, ReservedTagError, BadObjectError,
BadRoleError, NotConnectedAccountError)
from ccserver import __version__
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)
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)
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)
def version(self, conn):
return __version__
class OnlineCCHandler(CCHandler):
def on_disconnect(self, conn):
self._server.unregister(conn)
def _check(self, conn, method, tql=None):
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.')
class HypervisorHandler(OnlineCCHandler):
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
@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)
Antoine Millet
committed
self._server.sub_unregister(client.login, obj_id)
class CliHandler(OnlineCCHandler):
@listed
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)
return self._server.list(query)
Antoine Millet
committed
def _vm_action(self, query, method, *args, **kwargs):
vms = self._server.list(query, show=set(('r', 'h')))
hypervisors = list(self._server.iter_connected_role('hv'))
Antoine Millet
committed
for hv in hypervisors:
Antoine Millet
committed
if vm_to_start:
hv.connection.call(method, vm_to_start, *args, **kwargs)
@listed
def start(self, conn, query):
self._check(conn, 'start', query)
Antoine Millet
committed
@listed
self._check(conn, 'stop', query)
self._vm_action(query, 'vm_stop', force=False)
Antoine Millet
committed
Antoine Millet
committed
@listed
def destroy(self, conn, query):
self._check(conn, 'destroy', query)
self._vm_action(query, 'vm_stop', force=True)
Antoine Millet
committed
Antoine Millet
committed
@listed
def pause(self, conn, query):
self._check(conn, 'pause', query)
Antoine Millet
committed
@listed
def resume(self, conn, query):
self._check(conn, 'resume', query)
def passwd(self, conn, query, password, method='ssha'):
'''
Define a new password for specified user.
'''
self._check(conn, 'passwd', query)
Antoine Millet
committed
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)
errs.success(obj['id'], 'password updated')
return errs.get_dict()
def addaccount(self, conn, login, role, password=None):
'''
Create a new account with specified login.
'''
self._check(conn, 'addaccount')
if role in WelcomeHandler.ROLES:
self._server.conf.create_account(login, role, password)
else:
raise BadRoleError('%r is not a legal role.' % role)
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)
Antoine Millet
committed
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)
Antoine Millet
committed
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)
Antoine Millet
committed
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.')
for obj in objects:
otags = self._server.conf.show(obj['a'])['tags']
otags.update({'id': obj['id']})
tags.append(otags)
@listed
def delaccount(self, conn, query):
'''
Delete the account with specified login.
'''
self._check(conn, 'delaccount', query)
Antoine Millet
committed
objects = self._server.list(query, show=set(('a',)))
client = self._server.search_client_by_connection(conn)
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')
self._server.jobs.create('kill', author=client.login,
account=obj['a'], gracetime=1)
return errs.get_dict()
@listed
def close(self, conn, query):
'''
Close an account without deleting it.
'''
self._check(conn, 'close', query)
Antoine Millet
committed
objects = self._server.list(query, show=set(('a',)))
client = self._server.search_client_by_connection(conn)
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')
continue
errs.success(obj['id'], 'closed')
self._server.conf.add_tag(obj['a'], 'close', 'yes')
self._server.jobs.create('kill', author=client.login,
account=obj['a'], gracetime=1)
return errs.get_dict()
@listed
def declose(self, conn, query):
'''
Re-open an closed account.
'''
self._check(conn, 'declose', query)
Antoine Millet
committed
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)
Antoine Millet
committed
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.kill(obj['a'])
except NotConnectedAccountError:
errs.error(obj['id'], 'account is not connected')
else:
errs.success(obj['id'], 'account killed')
return errs.get_dict()
def rights(self, conn, query):
'''
Get the rights of an object set.
'''
self._check(conn, 'rights', query)
Antoine Millet
committed
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)
Antoine Millet
committed
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 self._server.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)
Antoine Millet
committed
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 self._server.conf.UnknownAccount:
errs.error(obj['id'], 'unknown account')
except self._server.conf.OutOfRangeIndexError:
errs.error(obj['id'], 'index out of range')
else:
errs.success(obj['id'], 'right rule deleted')
return errs.get_dict()
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
@listed
def execute(self, conn, query, command):
'''
Execute command on matched objects (must be roles hv or host).
:param query: the tql query to select objects.
:param command: the command to execute on each object
:return: a dict where key is the id of a selected object, and the value
a tuple (errcode, message) where errcode is (success|error|warn) and
message an error message or the output of the command in case of
success.
'''
self._check(conn, 'execute', query)
objects = self._server.list(query, show=set(('r',)))
errs = Reporter()
for obj in objects:
if obj['r'] not in ('hv', 'host'):
errs.error(obj['id'], 'bad role')
continue
try:
objcon = self._server.get_connection(obj['id'])
except KeyError:
errs.error(obj['id'], 'node not connected')
else:
returned = objcon.connection.call('execute_command', command)
errs.success(obj['id'], returned)
return errs.get_dict()
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
@listed
def shutdown(self, conn, query, reboot=True, gracefull=True):
'''
Execute a shutdown on selected objects (must be roles hv or host).
:param query: the tql query to select objects.
:param reboot: reboot the host instead of just shut it off
:param gracefull: properly shutdown the host
:return: a dict where key is the id of a selected object, and the value
a tuple (errcode, message) where errcode is (success|error|warn) and
message an error message.
'''
self._check(conn, 'execute', query)
objects = self._server.list(query, show=set(('r',)))
errs = Reporter()
for obj in objects:
if obj['r'] not in ('hv', 'host'):
errs.error(obj['id'], 'bad role')
continue
try:
objcon = self._server.get_connection(obj['id'])
except KeyError:
errs.error(obj['id'], 'node not connected')
else:
try:
objcon.connection.call('node_shutdown',
reboot, gracefull)
except RpcError as err:
errs.error(obj['id'], '%s (exc: %s)' % (err.message,
err.exception))
else:
errs.success(obj['id'], 'ok')
return errs.get_dict()
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
@listed
def jobs(self, conn, done=None):
'''
Return all jobs.
'''
jobs = []
for job in self._server.jobs.iterjobs(done):
jobs.append(job.export())
return jobs
@listed
def cancel(self, conn, jobid):
'''
Cancel a job.
'''
self._server.jobs.cancel(jobid)
@listed
def jobspurge(self, conn):
'''
Purge all done jobs from the job list.
'''
self._server.jobs.purge()
def threads(self, conn):
import threading
return threading.active_count()
'''
Return statistics about current database status.
'''
return self._server.objects.stats()
def proxy_client(self, conn, login, command, *args, **kwargs):
client = self._server.get_connection(login)
return client.connection.call(command, *args, **kwargs)
class WelcomeHandler(CCHandler):
'''
Default handler used on client connections of the server.
:cvar ROLES: role name/handler mapping
'''
ROLES = {
'cli': CliHandler,
'hv': HypervisorHandler,
def authentify(self, conn, login, password):
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')
logging.info('New authentication from %s: failure',
login.encode('ascii', 'ignore'))
raise AuthenticationError('Bad login/password')
if role not in WelcomeHandler.ROLES:
raise BadRoleError('%r is not a legal role' % role)
# If authentication is a success, try to register the client:
try:
self._server.register(login, role, conn)
except AlreadyRegistered:
raise AuthenticationError('Already connected')
conn.set_handler(WelcomeHandler.ROLES.get(role)(self._server))