diff --git a/bin/cc-node b/bin/cc-node index c3a9b0efb9f68d239a0812f9fe095a9803c9c0cc..39d4b8610086a4d49842fb6261d1eee1ca81c6fc 100755 --- a/bin/cc-node +++ b/bin/cc-node @@ -1,28 +1,128 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Cloud-Control Node -# Copyright (C) 2011 SmartJog [http://www.smartjog.com] -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Authors: -# * 2011-06: Thibault VINCENT -# - -from ccnode.launcher import Launcher - -if __name__ == '__main__': - - Launcher().main() + +import sys +import signal +import atexit +import time +import logging +import logging.config +from optparse import OptionParser +from os.path import isfile + +from daemon import DaemonContext + +from ccnode import __version__ +from ccnode.node import Node +from ccnode.utils import signal_ +from ccnode.config import NodeConfigParser + + +logger = logging.getLogger('ccnode') + + +DEFAULT_CONFIG_FILE = '/etc/cc-node.conf' + + +# command line arguments... +oparser = OptionParser(version='%%prog %s' % __version__) +oparser.add_option('-d', '--daemonize', default=False, action='store_true', + help=u'run as daemon and write pid file') +oparser.add_option('-c', '--config', metavar='FILE', + default=DEFAULT_CONFIG_FILE, + help=u'configuration file ABSOLUTE path (default: %default)') +oparser.add_option('-p', '--pidfile', metavar='FILE', + help=u'pid file path for daemon mode') +options, args = oparser.parse_args() +# check argument configuration +if options.daemonize and not options.pidfile: + sys.exit(u'Please supply a pid file...') + +if not isfile(options.config): + sys.exit(u'Please supply a valid path to configuration file...') + + +# globals +need_reload = False +node = None +config_file = NodeConfigParser(options.config) + + +# configure logging +logging.config.fileConfig(options.config) + + +@signal_(signal.SIGUSR1) +def reload_config(signum, frame): + global need_reload + + need_reload = True + + +@signal_(signal.SIGTERM) +def exit_node(signum=None, frame=None): + # clean all current jobs + # TODO + if node is not None: + # clean node + node.shutdown() + # exit + logger.info(u'Exiting node...') + sys.exit() + + +def run_node(): + global need_reload, node + + try: + node = Node( + server_host=config_file.server_host, + server_port=config_file.server_port, + user_name=config_file.server_user, + user_passwd=config_file.server_passwd, + ) + node.start() + while True: + if need_reload: + logger.info(u'Reloading logging configuration...') + logging.config.fileConfig(options.config) + need_reload = False + + signal.pause() + except KeyboardInterrupt: + exit_node() + except Exception: + logger.exception(u'Unknown error:') + + +# take care of pid file if daemon +if options.daemonize: + pidfile = open(options.pidfile) + files_preserve = [pidfile] +else: + files_preserve = None + + +with DaemonContext(detach_process=options.daemonize, + files_preserve=files_preserve, + stderr=sys.stderr, + stdout=sys.stdout): + # reload log config + logging.config.fileConfig(options.config) + + # take care of pidfile + if options.daemonize: + pidfile.write('%s' % os.getpid()) + pidfile.flush() + + @atexit.register + def clean_pidfile(): + pidfile.seek(0) + pidfile.truncate() + pidfile.flush() + + logger.debug(u'Starting node') + while True: + run_node() + + logger.critical(u'Restarting node...') + time.sleep(2) diff --git a/ccnode/__init__.py b/ccnode/__init__.py index 769579afc1e7ef0a6816e1e4372699e8c1acad20..367e3ef9592b880d30d3e0d12b1956bbf64810ec 100644 --- a/ccnode/__init__.py +++ b/ccnode/__init__.py @@ -1,51 +1,3 @@ -# -*- coding: utf-8 -*- -# -# Cloud-Control Node -# Copyright (C) 2011 SmartJog [http://www.smartjog.com] -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Authors: -# * 2011-06: Thibault VINCENT -# - -__product__ = 'Cloud-Control Node' -__version__ = '0~dev' +__product__ = 'Cloud-Control Node' +__version__ = '0~dev' __canonical__ = 'cc-node' -__develmode__ = False - - -def git_version(): - global __version__ - import os - import sys - from subprocess import Popen, PIPE, CalledProcessError - cwd = os.getcwd() - try: - os.chdir(os.path.dirname(sys.argv[0])) - p = Popen(["git", "log", "--pretty=format:%H" ], stdout=PIPE, - stderr=open("/dev/null", "wb")) - p.wait() - if p.returncode == 0: - githash = p.stdout.readline().strip() - if len(githash) > 0: - __version__ += "-git-%s" % githash - except OSError: - pass - finally: - os.chdir(cwd) - -if __version__.find("dev") != -1: - __develmode__ = True - git_version() diff --git a/ccnode/config.py b/ccnode/config.py index a721a11b9435ffe27605f899f5713d2991f60814..8b3577ed5971eb41d42298ff79c74140492b9175 100644 --- a/ccnode/config.py +++ b/ccnode/config.py @@ -1,193 +1,21 @@ -# -*- coding: utf-8 -*- -# -# Cloud-Control Node -# Copyright (C) 2011 SmartJog [http://www.smartjog.com] -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Authors: -# * 2011-06: Thibault VINCENT -# +import logging +from ConfigParser import SafeConfigParser -from __future__ import absolute_import -import ConfigParser -from optparse import OptionParser -from . import __version__ +from ccnode import __version__ -ARGS_DEFAULTS = { - 'daemonize' : (bool, False), - 'stdout' : (bool, False), - 'config' : (str, '/etc/cc-node.conf'), - 'pidfile' : (str, '/var/run/cc-node.pid'), -} -FILE_DEFAULTS = { - 'address' : (str, ''), - 'port' : (int, 1984), - 'ssl' : (bool, True), - 'login' : (str, ''), - 'password' : (str, ''), - 'verbosity' : (int, 0), - 'virtualization' : (bool, True), - 'command_execution' : (bool, True), -} -SECURE_OPTIONS = [ - 'password', -] +logger = logging.getLogger(__name__) -class ConfigurationError(Exception): - - pass +class NodeConfigParser(object): + """ConfigParser for ccnode config file.""" + def __init__(self, file_path): + config = SafeConfigParser() + config.read(file_path) -class Configuration(object): - - def __init__(self, logger): - - self._logger = logger - - # populate default options - self._options = {} - for name, (vtype, value) in FILE_DEFAULTS.iteritems(): - self._options[name] = _ConfigValue(vtype, value) - for name, (vtype, value) in ARGS_DEFAULTS.iteritems(): - self._options[name] = _ConfigValue(vtype, value, locking=True) - - def parse_arguments(self): - - parser = OptionParser(version = '%%prog %s' % __version__) - - # --daemonize - parser.add_option('-d', - '--daemonize', - default = ARGS_DEFAULTS['daemonize'][1], - action = 'store_true', - help = 'run as daemon and write pid file') - # --stdout - parser.add_option('-s', - '--stdout', - action = 'store_true', - default = ARGS_DEFAULTS['stdout'][1], - help = 'log on standard output instead of syslog' - ' (when not running in daemon mode)') - # --config - parser.add_option('-c', - '--config', - default = ARGS_DEFAULTS['config'][1], - help = 'configuration file path (default: %default)') - # --pidfile - parser.add_option('-p', - '--pidfile', - default = ARGS_DEFAULTS['pidfile'][1], - help = 'pid file path for daemon mode (default: ' - '%default)') - - # parse command line - self._logger.info('reading command-line arguments') - options = parser.parse_args()[0] - - # apply arguments to current configuration - for name, value in options.__dict__.iteritems(): - if name in self._options: - self._options[name].update(value, unlock=True) - if name not in SECURE_OPTIONS: - self._logger.info(' * %s = %s' % (name, - self._options[name].value())) - - def parse_file(self): - - parser = ConfigParser.SafeConfigParser() - - # open configuration file - try: - self._logger.info('parsing configuration file') - parser.read(self._options['config'].value()) - options = dict(parser.items('node')) - except ConfigParser.Error as err: - raise ConfigurationError('failed to open or parse configuration' - ' file: %r' % err) - - # apply to current configuration - for name, value in options.iteritems(): - if name in self._options: - self._options[name].update(value, unlock=False) - if name not in SECURE_OPTIONS: - self._logger.info(' * %s = %s' % (name, - self._options[name].value())) - - def get(self, name): - - if name in self._options: - return self._options[name].value() - else: - return None - - def update(self, name, value): - - # TODO missing concurrency protection - if name in self._options: - self._options[name].update(value, unlock=False) - - -class _ConfigValue(object): - - def __init__(self, value_type, default_value, locking=False): - - self._type = value_type - self._default = default_value - self._value = default_value - self._locking = locking - - def __str__(self): - - return '' if self._value is None else str(self._value) - - def update(self, value, unlock=False): - - if not self._locking or (self._locking and unlock): - try: - # special case for boolean conversions - if self._type is bool and not isinstance(value, bool): - # any integer != 0 means True - if isinstance(value, int): - self._value = value != 0 - # a string meaning 'yes' - elif isinstance(value, str): - self._value = value.strip().lower() in ('yes', - 'y', '1', 'true', 'enable', - 'enabled', 'active', 'on') - # TODO rethink about a default value, or how to warn - else: - self._value = False - # or just let python do the conversion - else: - self._value = self._type(value) - except ValueError: - raise ConfigurationError('option has type `%r`, cannot' - ' convert from value `%s`' % (self._type, value)) - except TypeError: - raise ConfigurationError('option convertion to `%r` failed' - % (self._type)) - - def value(self): - - return self._value - - def default_value(self): - - return self._default - - def is_default(self): - - return self._value == self._default + # ccserver settings + self.server_host = config.get('ccserver', 'host') + self.server_port = config.getint('ccserver', 'port') + self.server_user = config.get('ccserver', 'user') + self.server_passwd = config.get('ccserver', 'password') + # TODO complete diff --git a/ccnode/exc.py b/ccnode/exc.py new file mode 100644 index 0000000000000000000000000000000000000000..5dab770d1973939c352336f27c8bbd027a39d996 --- /dev/null +++ b/ccnode/exc.py @@ -0,0 +1 @@ +"""Exceptions classes for ccnode.""" diff --git a/ccnode/host/__init__.py b/ccnode/host/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ccnode/host/handler.py b/ccnode/host/handler.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ccnode/hypervisor/__init__.py b/ccnode/hypervisor/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ccnode/hypervisor/handler.py b/ccnode/hypervisor/handler.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/ccnode/jobs.py b/ccnode/jobs.py index c157bda2df5c0329860c94687f327445bcff33c2..c1b23bc9595cc88daa16a1fa0ec546e39bf02a77 100644 --- a/ccnode/jobs.py +++ b/ccnode/jobs.py @@ -1,32 +1,9 @@ -# -*- coding: utf-8 -*- -# -# Cloud-Control Node -# Copyright (C) 2011 SmartJog [http://www.smartjog.com] -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Authors: -# * 2011-06: Thibault VINCENT -# - -from __future__ import absolute_import +import logging from threading import Thread -from .logging import Logger + + +logger = logging.getLogger(__name__) class JobManager(Thread, object): - - def __init__(self, logger): - - self._logger = logger + pass diff --git a/ccnode/kernel.py b/ccnode/kernel.py deleted file mode 100644 index 7bf4547228079efaf043f4dad62b5b7163d79f49..0000000000000000000000000000000000000000 --- a/ccnode/kernel.py +++ /dev/null @@ -1,655 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Cloud-Control Node -# Copyright (C) 2011 SmartJog [http://www.smartjog.com] -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Authors: -# * 2011-06: Thibault VINCENT -# - -from __future__ import absolute_import -from threading import Lock, Event, Thread -from time import sleep -from sjrpc.client import SimpleRpcClient -from sjrpc.utils import ConnectionProxy, pure -from sjrpc.core import RpcError -from .logging import Logger -from .jobs import JobManager -from .threading import RWLock -from . import modules - -pure = pure - - -class CoreError(Exception): - - pass - - -class Core(object): - - def __init__(self, logger, configuration): - - self._logger = logger - self._config = configuration - self._modmgr = ModuleManager(self._logger, self._config) - - # big lock for events synchronization - self._mutex = Lock() - # state initialized as shutdown - self._shutdown = Event() - self._shutdown.set() - - def run(self): - - # never run twice (should not happen, but let's be sure) - with self._mutex: - if self._shutdown.is_set(): - self._shutdown.clear() - else: - raise CoreError('already running') - - # exceptions in this block will force a shutdown - try: - ### create rpc client - rpc_handler = Handler(self._logger, "core") - rpc_manager = SimpleRpcClient.from_addr( - self._config.get('address'), - self._config.get('port'), - enable_ssl = self._config.get('ssl'), - default_handler = rpc_handler) - rpc_proxy = ConnectionProxy(rpc_manager) - # FIXME bad API workaround - self._rpc_manager = rpc_manager - - # FIXME bad API, move that to __init__ and clean handler each time - # we re-enter run() - self._modmgr.set_core_handler(rpc_handler) - - ### run the sjrpc main loop in a separate thread - ### because we need it for authentication - rpc_thread = Thread(name='sjrpc', target=rpc_manager.run) - rpc_thread.start() - - ### authenticate with the server and set node role - while rpc_thread.is_alive(): - try: - self._logger.debug(1, 'sending authentication request') - # send credential and fetch the node role - role = rpc_proxy.authentify(self._config.get('login'), - self._config.get('password')) - # authentication failed - except RpcError as err: - # bad credentials - if err.exception == 'AuthenticationError': - self._logger.info('authentication failed,' - ' retrying soon...') - # other errors - else: - self._logger.warning('unknown error during' - ' authentication: %s', err) - # wait before trying again - sleep(10) - - # authentication succeeded - else: - self._logger.info('authentication with server succeeded') - - # received hypervisor role: we obey local settings - if role == 'hv': - self._logger.info('server affected hypervisor role') - if not self._config.get('virtualization'): - self._logger.warning('virtualization features are' - ' locally disabled by configuration, cannot' - ' comply with server-affected role') - - # received host role: disabling virtualization - elif role == 'host': - self._logger.info('server affected node role,' - ' disabling virtualization features') - self._config.update('virtualization', False) - - # unknown role, core should be restarted to close connection - else: - self._logger.error('wrong role affected by' - ' server: %s', role) - raise CoreError('wrong role affected') - # quit auth loop - break - - ### check a last time that sjrpc is running - if not rpc_thread.is_alive(): - self._logger.critical('sjrpc main loop terminated too soon') - raise CoreError('lost sjrpc main loop') - - ### load every module usable on this system - try: - self._modmgr.load() - except ModuleManagerError as err: - self._logger.error('initial module load failure: %s', err) - - ### wait for the sjrpc main loop to die - while True: - rpc_thread.join(3600) - if not rpc_thread.is_alive(): - self._logger.info('''sjrpc returned from it's main loop''') - break - else: - self._logger.info('*** hourly life report ***') - - except: - raise - - finally: - self.shutdown() - - def shutdown(self): - - with self._mutex: - if not self._shutdown.is_set(): - # unload every module - # TODO currently will wait forever if a module blocks - self._logger.warning('unloading all modules') - self._modmgr.unload() - # unlock main loop - self._logger.warning('killing main loop') - # kill sjrpc main loop - # FIXME bad API - self._rpc_manager.shutdown() - self._shutdown.set() - - -class ModuleManagerError(Exception): - - pass - - -class ModuleManager(object): - - def __init__(self, logger, config): - - self._logger = logger - self._config = config - self._load_mutex = Lock() - self._dict_mutex = RWLock() - # maps a module name to the python module of this cc-node module :-) - # so it usually contains all available modules - self._mod_loaded = {} - # maps module names to module handles (instance of initialized module) - # so these are currently 'running' modules - self._mod_handles = {} - - # Note: the only methods (load and unload) doing writes on _mod_* dicts - # will lock _load_mutex, so for the sake of simplicity when we're - # in a block protected by this mutex, we'll only use - # _dict_mutex.write as _dict_mutex.read becomes useless there - - def _reload_list(self): - - self._logger.debug(1, 'reloading modules package') - # FIXME maybe use _reload_module and benefit from error checks - reload(modules) - - if not isinstance(modules.LIST, list): - self._logger.debug(1, 'no module list in module package') - modules.LIST = [] - - def _reload_module(self, name): - - self._logger.debug(1, 'reloading module code: %s', name) - with self._dict_mutex.write: - try: - # FIXME is there a better way to handle import VS reload ? - # FIXME do absolute import, but preserving package namespace - # so we can access module with 'modules.foobar' - # FIXME code injection (don't use exec or sanitize input) - if name not in self._mod_loaded: - exec('import ccnode.modules.%s' % name) - else: - exec('reload(modules.%s)' % name) - - except ImportError: - raise ModuleManagerError('%s: import error, the module or a' - ''' dependancy was not found''' % name) - - except KeyboardInterrupt: - raise - - except Exception: - self._logger.backtrace() - raise ModuleManagerError('%s: error in module code, backtrace' - ' was sent to debug logging' % name) - else: - self._mod_loaded[name] = getattr(modules, name) - - def _mod_validate(self, name, module): - - if not hasattr(module, 'Module'): - self._logger.error('module with no definition: %s', name) - return False - - elif not issubclass(module.Module, BaseModule): - self._logger.error('module does not inherint BaseModule: %s', name) - return False - - elif not (hasattr(module.Module, 'mod_dependancies') and - callable(module.Module.mod_dependancies)): - # TODO check the prototype too - self._logger.error('module is missing the mod_dependancies' - ' method: %s', name) - return False - - elif not (hasattr(module.Module, 'mod_initialize') and - callable(module.Module.mod_initialize)): - # TODO check the prototype too - self._logger.error('module is missing the mod_initialize' - ' method: %s', name) - return False - - return True - - def load(self, names=None): - - # read the note about locking in __init__() - - with self._load_mutex: - # reload module index - self._reload_list() - - # if no name is provided, we'll load all modules - if isinstance(names, list): - to_load = names - elif isinstance(names, str): - to_load = [names] - else: - to_load = modules.LIST - - for mod_name in to_load: - ### skip name if already loaded or not in package - if mod_name in self._mod_handles: - self._logger.debug(1, 'skipping module already' - ' initialized: %s', mod_name) - # abort loading this module - continue - elif mod_name not in modules.LIST: - self._logger.debug(1, 'not loading unindexed' - ' module: %s', mod_name) - # abort loading this module - continue - - ### (re)load module code - try: - self._reload_module(mod_name) - with self._dict_mutex.write: - mod = self._mod_loaded[mod_name] - if not self._mod_validate(mod_name, mod): - raise ModuleManagerError('validation failed') - except ModuleManagerError as err: - self._logger.error('failed to load module: %s', err) - # abort loading this module - continue - - ### (re)load dependancies and build deptree - deptree = _DepTree() - # add our current module and it's deps - deptree.add(mod_name, mod.Module.mod_dependancies()) - # deptree tells us what to load - load_error = False - while not load_error: - # get one name to load - dep_name = deptree.next_missing_dep() - # exit if over - if not dep_name: - break - # (re)load it, _only if not in use_ - try: - if not dep_name in self._mod_handles: - self._reload_module(dep_name) - with self._dict_mutex.write: - dep = self._mod_loaded[dep_name] - if not self._mod_validate(dep_name, dep): - raise ModuleManagerError('validation failed') - except ModuleManagerError as err: - self._logger.error('failed to load dependancy' - ' module: %s', err) - # stop the dep scan when load failed - load_error = True - continue - # add the deps of this new mod - deptree.add(dep_name, dep.Module.mod_dependancies()) - # abort all in case we couldn't load a dep - if load_error: - continue - - ### now that all modules are loaded, we should initialize - ### them in the right order, which is provided by _DepTree - try: - for name in deptree.ordered_init_list(): - # ONLY if not initialized yet - if name not in self._mod_handles: - self._logger.debug(1, 'initializing module: %s', - name) - mod = self._mod_loaded[name] - # init the module - if not mod.Module.mod_initialize(self): - # module doesn't wish to load, abort - # (yes it can, and that's not an error) - self._logger.info('module refused to load: %s', - name) - break - # add to list of initialized mods if we have an obj - if (hasattr(mod.Module, 'handle') and - isinstance(mod.Module.handle, mod.Module)): - self._mod_handles[name] = mod.Module.handle - self._logger.info('module initialized: %s', - name) - else: - self._logger.error('module did not provide a' - ' valid instance handle: %s', name) - break - # dependancy error means we couldn't init anything - except _DepTreeError as err: - self._logger.error('a dependancy error prevented loading' - ' of module %s: %s', mod_name, err) - # abort all, no module was initialized - continue - # if any error occur during initialization, can't continue - # with a missing module. we leave things as they are. - except ModuleError as err: - self._logger.error('failed to initialize module %s: %s', - name, err) - # abort all, hopefully the module cleaned it's ressources - continue - except KeyboardInterrupt: - raise - except Exception as err: - self._logger.error('miserable failure during module' - ' initialization of `%s` go check the code' - ' immediately, backtrace in debug log: %s', name, err) - self._logger.backtrace() - # abort all, hopefully the module cleaned it's ressources - continue - - # raise error if the job is not complete - # FIXME should not reporte error when mod_initialize() was False - missing = set(to_load) - set(self._mod_handles.keys()) - if len(missing): - raise ModuleManagerError('could not load: %s' % - ', '.join(missing)) - - def unload(self, names=None): - - # please read the note about locking in __init__() - - with self._load_mutex: - if isinstance(names, list): - to_unload = names - elif isinstance(names, str): - to_unload = [names] - else: - to_unload = self._mod_loaded.keys() - - # FIXME - # check tah _loaded do not contains modules with deps to to_unload - - # FIXME should disappear, see Core.run() - def set_core_handler(self, handler): - - self._core_handler = handler - - def get_handler(self): - - return self._core_handler - - def get_logger(self): - - return self._logger - - def get_configuration(self): - - return self._config - - def get(self, name): - - with self._dict_mutex.read: - if name in self._mod_handles: - return self._mod_handles[name] - else: - raise ModuleManagerError('access to uninitialized module: %s' - % name) - - -class _DepTreeError(Exception): - - pass - - -class _DepTree(object): - - def __init__(self): - - self._mod_deps = {} - - def add(self, name, deps): - - # save it in our mod->deps mapping - self._mod_deps[name] = deps - - def next_missing_dep(self): - - # return the first module present in a dependancies list - # but not registered in the mod->deps mapping - for mod, deps in self._mod_deps.iteritems(): - for dep in deps: - if dep not in self._mod_deps: - return dep - - # or None when we do not miss anything anymore - return None - - def ordered_init_list(self): - - # - # How it works: - # - # * first I create a directed graph of all modules linked - # by dependancies, despite the class name it's not always - # a tree - # * then I ensure that we have no cycle in it, or it means - # unresolvable deps and the process stops. actually this is - # done during the following step - # * finally the list is built starting with modules that have - # no child, level-by-level up to the last parent(s) - # - # Graph node: - # (mod_name, {deps name:@ref}) - # - - ### create the directed graph of dependancies - graph = [] - for mod_name, mod_deps_list in self._mod_deps.iteritems(): - # create node tuple - mod_deps = dict([(name, None) for name in mod_deps_list]) - mod = (mod_name, mod_deps) - # iter on nodes to make arcs - for node in graph: - n_name, n_deps = node - # add node->mod arc - if mod_name in n_deps: - n_deps[mod_name] = mod - # add mod->node arc - for dep_name, dep in mod_deps.iteritems(): - if n_name == dep_name: - dep = node - # add the new node to the graph - graph.append(mod) - - ### build an ideal module initialization list that obey dependancies - # scan for the initial level: modules with no dependancies - level = [] - for node in graph: - n_name, n_deps = node - if not len(n_deps): - level.append(node) - - # stop if we've lost already - if not len(level) and len(self._mod_deps): - raise _DepTreeError('cross-dependancies on all the modules') - - # now iter on next levels - load_list = [] - while True: - # extend the list with current level - for n_name, n_deps in level: - load_list.append(n_name) - # build the next level - next_level = [] - for node in graph: - n_name, n_deps = node - # only add _new_ nodes that are fulfiled with the current list - if (n_name not in load_list - and set(n_deps.keys()) <= set(load_list)): - next_level.append(node) - # stop when the next level is empty - if not len(next_level): - # if this is a cross-deps situation, abort - if len(load_list) < len(graph): - # FIXME enhance the message explaining what modules remain - # to load - raise _DepTreeError('cross-dependancies loop') - # we're finished - else: - break - # good to go to the next level - else: - level = next_level - - return load_list - - -class ModuleError(Exception): - - pass - - -class BaseModule(object): - - _DEPS = [] - - @classmethod - def mod_dependancies(cls): - - return cls._DEPS if isinstance(cls._DEPS, list) else [] - - @classmethod - def mod_initialize(cls, manager): - - if not hasattr(cls, 'loadable') or (callable(cls.loadable) - and cls.loadable()): - cls.handle = cls(manager) - return True - else: - return False - - @classmethod - def mod_destroy(cls): - - if hasattr(self, 'destroy') and callable(self.destroy): - self.destroy() - del cls.handle - - def __init__(self, manager): - - if hasattr(self, 'init') and callable(self.init): - self.init(manager) - - -class HandlerError(Exception): - - pass - - -class Handler(object): - - def __init__(self, logger, name): - - self._logger = logger - self._name = self.__class__.__name__ if name is None else name - self._mutex = RWLock() - self._procedures = {} - self._children = [] - - def __getitem__(self, name): - - if name.startswith('_'): - raise AttributeError('attribute `%s` is private' % name) - - search = self._search_procedure(name) - - if search is not None: - handler, procedure = search - self._logger.debug(1, 'handler access for: %s->%s', handler, name) - return procedure - else: - self._logger.info('procedure `%s` is not registered in subtree' - ' of handler `%s`' % (name, self._name)) - raise KeyError('procedure `%s` is not registered' % name) - - def _search_procedure(self, name): - - with self._mutex.read: - # return local procedure if present, with handler name - if name in self._procedures: - return (self._name, self._procedures[name]) - - # search in sub-handlers - for handler in self._children: - search = handler._search_procedure(name) - if search is not None: - return search - - # not found at all - return None - - def register(self, handler): - - with self._mutex.write: - if handler not in self._children: - self._children.append(handler) - else: - raise HandlerError('handler `%s` is already registered in `%s`' - % (handler._name, self._name)) - - def unregister(self, handler): - - with self._mutex.write: - while handler in self._children: - self._children.remove(handler) - - def procedure_register(self, name, procedure): - - with self._mutex.write: - if name not in self._procedures: - self._procedures[name] = procedure - else: - raise HandlerError('procedure `%s` is already registered' - ' in `%s`' % (name, self._name)) - - def procedure_unregister(self, name): - - with self._mutex.write: - if name in self._procedures: - self._procedures.pop(name) diff --git a/ccnode/launcher.py b/ccnode/launcher.py deleted file mode 100644 index ea45eef01eeff2ec93eac22f173def3ad6248038..0000000000000000000000000000000000000000 --- a/ccnode/launcher.py +++ /dev/null @@ -1,106 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Cloud-Control Node -# Copyright (C) 2011 SmartJog [http://www.smartjog.com] -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Authors: -# * 2011-06: Thibault VINCENT -# - -from __future__ import absolute_import -from signal import signal, SIGHUP, SIGUSR1 -from time import sleep -from .logging import Logger, DEBUG, INFO, ERROR -from .config import Configuration -from .kernel import Core -from . import __product__, __version__ - - -class Launcher(object): - # pylint: disable=R0903 - - def __init__(self): - - # initialize core components - self._logger = Logger() - self._logger.info('initializing %s version %s' % (__product__, - str(__version__))) - self._config = Configuration(self._logger) - self._core = Core(self._logger, self._config) - - def _on_sighup(self, signum, frame): - # pylint: disable=W0613 - - self._logger.warning('SIGHUP received, shutting down core') - self._core.shutdown() - - def _on_sigusr1(self, signum, frame): - # pylint: disable=W0613 - - self._logger.info('SIGUSR1 received, reloading configuration file and' - ' logging settings') - self._reload_conf() - - def _reload_conf(self): - - # (re)load configuration file - self._config.parse_file() - - # (re)apply logging configuration - # verbosity level - verbosity = self._config.get('verbosity') - if verbosity >= 1: - self._logger.set_level(DEBUG) - self._logger.set_debug_level(verbosity) - else: - self._logger.set_level(INFO) - # stderr/syslog output selection - std_output = self._config.get('stdout') - self._logger.set_stderr_enabled(std_output) - self._logger.set_syslog_enabled(not std_output) - - def main(self): - - # read command line arguments - self._config.parse_arguments() - - # register hangup signal to restart core - signal(SIGHUP, self._on_sighup) - - # register USR1 signal to reload configuration file - # and apply logging settings - signal(SIGUSR1, self._on_sigusr1) - - # run the core untill permanent shutdown is requested - while True: - try: - # (re)load configuration file and apply logging settings - self._reload_conf() - - # start core main loop - self._core.run() - sleep(0.5) - - except KeyboardInterrupt: - self._logger.warning('SIGINT received, shutting down core' - ' post-mortem') - self._core.shutdown() - break - - except Exception: - self._logger.error('unexpected error raised by core') - self._logger.backtrace(priority=ERROR) - sleep(5) diff --git a/ccnode/logging.py b/ccnode/logging.py deleted file mode 100644 index 5ace2ef86755acd3ceb3396f07eefcd516cc0b66..0000000000000000000000000000000000000000 --- a/ccnode/logging.py +++ /dev/null @@ -1,159 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Cloud-Control Node -# Copyright (C) 2011 SmartJog [http://www.smartjog.com] -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Authors: -# * 2011-06: Thibault VINCENT -# - -from __future__ import absolute_import -import syslog -import socket -from sys import stderr -from threading import Lock -from traceback import format_exc -from . import __canonical__, __develmode__ - -DEBUG = syslog.LOG_DEBUG -INFO = syslog.LOG_INFO -WARNING = syslog.LOG_WARNING -ERROR = syslog.LOG_ERR -CRITICAL = syslog.LOG_CRIT - -ALL_PRIO = (DEBUG, INFO, WARNING, ERROR, CRITICAL) -PRIO_TAGS = { - DEBUG : 'D', - INFO : 'I', - WARNING : 'W', - ERROR : 'E', - CRITICAL: 'C', -} - - -class Logger(object): - - def __init__(self): - - self._max_prio = INFO - self._max_debug = 1 - self._mutex = Lock() - self._mcast_sock = None - self._mcast_dst = None - self._syslog_enabled = not __develmode__ - self._stderr_enabled = __develmode__ - syslog.openlog(__canonical__, - syslog.LOG_PID ^ syslog.LOG_NDELAY ^ syslog.LOG_CONS, - syslog.LOG_DAEMON) - - def set_level(self, priority): - - if priority not in ALL_PRIO: - raise ValueError('not an allowed logging priority') - - self._max_prio = priority - - def set_debug_level(self, level): - - if level <= 0: - raise ValueError('debug level is a positive integer') - - self._max_debug = int(level) - - def set_syslog_enabled(self, state): - - self._syslog_enabled = bool(state) - - def set_stderr_enabled(self, state): - - self._stderr_enabled = bool(state) - - def mcast_enable(self, addr, port): - - with self._mutex: - self._mcast_dst = (addr, port) - self._mcast_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, - socket.IPPROTO_UDP) - self._mcast_sock.setsockopt(socket.IPPROTO_IP, - socket.IP_MULTICAST_TTL, 2) - - def mcast_disable(self): - - with self._mutex: - self._mcast_sock = None - self._mcast_dst = None - - def _log(self, priority, level, message, *args): - - if priority not in ALL_PRIO: - priority = CRITICAL - - printable = (priority <= self._max_prio and (level is None - or (priority == DEBUG and level <= self._max_debug))) - - if printable or self._mcast_sock is not None: - try: - if len(args): - message = message % args - - except TypeError: - message = '[PARTIAL] %s' % message - - finally: - if printable: - if self._syslog_enabled: - syslog.syslog(priority, message) - - if self._stderr_enabled: - if isinstance(level, int): - my_lvl = str(level) - else: - my_lvl = ' ' - stderr.write('%s[%s%s]: %s\n' % (__canonical__, - PRIO_TAGS[priority], my_lvl, message)) - - with self._mutex: - if self._mcast_sock is not None: - self._mcast_sock.sendto('%i %s' % - (level, message), self._mcast_dst) - - def debug(self, level, message, *args): - - self._log(DEBUG, level, message, *args) - - def info(self, message, *args): - - self._log(INFO, None, message, *args) - - def warning(self, message, *args): - - self._log(WARNING, None, message, *args) - - def error(self, message, *args): - - self._log(ERROR, None, message, *args) - - def critical(self, message, *args): - - self._log(CRITICAL, None, message, *args) - - def backtrace(self, priority=DEBUG, level=1): - - if priority != DEBUG: - level = None - - for line in format_exc().splitlines(): - self._log(priority, level, 'BT: %s', line) diff --git a/ccnode/modules/__init__.py b/ccnode/modules/__init__.py deleted file mode 100644 index 07fd94204f9341d9a1c29c4a85a13b3683d3a75b..0000000000000000000000000000000000000000 --- a/ccnode/modules/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Cloud-Control Node -# Copyright (C) 2011 SmartJog [http://www.smartjog.com] -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Authors: -# * 2011-06: Thibault VINCENT -# - -LIST = [ - 'tags', - 'jobs', - 'execute', - 'shutdown', - 'sysinfo', -] diff --git a/ccnode/modules/execute.py b/ccnode/modules/execute.py deleted file mode 100644 index 0c5fa476b8b7a4e2ed0ed30643f7dcc6f48c3d14..0000000000000000000000000000000000000000 --- a/ccnode/modules/execute.py +++ /dev/null @@ -1,72 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Cloud-Control Node -# Copyright (C) 2011 SmartJog [http://www.smartjog.com] -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Authors: -# * 2011-06: Thibault VINCENT -# - -from __future__ import absolute_import -from subprocess import Popen, PIPE, STDOUT -from ..kernel import BaseModule, ModuleError, Handler, pure - - -class Module(BaseModule): - - _handler_methods = { - 'execute_command' : '_h_execute_command', - } - - def init(self, manager): - - self._manager = manager - self._logger = manager.get_logger() - self._config = manager.get_configuration() - - # create and populate our own handler - self._handler = Handler(self._logger, 'execute') - for name, method in self._handler_methods.iteritems(): - self._handler.procedure_register(name, getattr(self, method)) - # register it in the core handler - self._manager.get_handler().register(self._handler) - - def destroy(self): - - # FIXME do something about running commands (wait? kill?) - - # unregister ourselves from the core handler - self._manager.get_handler().unregister(self._handler) - - @pure - def _h_execute_command(self, command): - - # do nothing when execution is disabled in configuration - if self._config.get('command_execution') is not True: - raise ModuleError('command execution is disabled by configuration') - - # synchronous execution - self._logger.info('executing command: %s', command) - output = None - try: - output = Popen(command, shell=True, bufsize=-1, stdin=PIPE, - stdout=PIPE, stderr=STDOUT).communicate()[0] - # command not found or related error - except OSError as err: - raise ModuleError('could not execute command: %s' % err) - # return output string, mix of stdout and stderr - else: - return output diff --git a/ccnode/modules/shutdown.py b/ccnode/modules/shutdown.py deleted file mode 100644 index 18ac5c1fc2fc7672c93bec83218a4db3f8a55a18..0000000000000000000000000000000000000000 --- a/ccnode/modules/shutdown.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Cloud-Control Node -# Copyright (C) 2011 SmartJog [http://www.smartjog.com] -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Authors: -# * 2011-06: Thibault VINCENT -# - -from __future__ import absolute_import -from subprocess import Popen, PIPE, STDOUT -from ..kernel import BaseModule, ModuleError, Handler, pure - - -class Module(BaseModule): - - _handler_methods = { - 'node_shutdown' : '_h_node_shutdown', - } - - def init(self, manager): - - self._manager = manager - self._logger = manager.get_logger() - self._config = manager.get_configuration() - - # create and populate our own handler - self._handler = Handler(self._logger, 'shutdown') - for name, method in self._handler_methods.iteritems(): - self._handler.procedure_register(name, getattr(self, method)) - # register it in the core handler - self._manager.get_handler().register(self._handler) - - def destroy(self): - - # unregister ourselves from the core handler - self._manager.get_handler().unregister(self._handler) - - @pure - def _h_node_shutdown(self, reboot=True, gracefull=True): - - self._logger.info('server requested shutdown of local host with' - ' options reboot=`%s` gracefull=`%s`', reboot, gracefull) - - # build option list for the GNU shutdown command - options = '' - if reboot is False: - options += ' -h -P' - options += '' if gracefull else ' -n' - else: - options += ' -r' - options += ' -f' if gracefull else ' -n' - options += ' 0' - - # shutdown machine - try: - Popen('/sbin/shutdown %s' % options, shell=True).communicate() - # command not found or related error - except OSError as err: - raise ModuleError('could not execute shutdown: %s' % err) diff --git a/ccnode/node.py b/ccnode/node.py new file mode 100644 index 0000000000000000000000000000000000000000..c936b28e83512437b82917ebe32417db71fbe4b3 --- /dev/null +++ b/ccnode/node.py @@ -0,0 +1,155 @@ +import time +import logging +from threading import Thread, Lock + +from sjrpc.client import SimpleRpcClient +from sjrpc.utils import ConnectionProxy, RpcHandler, pure + +from ccnode import __version__ +from ccnode.tags import Tag + + +logger = logging.getLogger(__name__) + + +DEFAULT_TAGS = (Tag(u'version', __version__, 1),) + + +class DefaultHandler(RpcHandler): + def __init__(self, *args, **kwargs): + RpcHandler.__init__(self, *args, **kwargs) + + self.tags = dict((t.name, t) for t in DEFAULT_TAGS) + + @pure + def get_tags(self, tags=None, noresolve_tags=None): + """ + :param iterable tags: list of tags to return + :param iterable noresolve_tags: list of tags to not return + """ + logger.debug('Tags: %s, %s' % (unicode(tags), unicode(noresolve_tags))) + + tags = set(tags) - set(noresolve_tags) if tags is not None else None + + if tags is None: + tags = self.tags.iterkeys() + else: + tags = tags & set(self.tags.iterkeys()) + + result = dict(( + t, # tag name + dict( + value=self.tags[t].value, + ttl=self.tags[t].ttl, + ), + ) for t in tags) + logger.debug(u'Returning: %s' % unicode(result)) + return result + + +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) + + # 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 + + self.daemon = True + + #: proxy + self.proxy = None + #: rpc connection manager + self.manager = None + #: role returned by cc-server + self.role = None + + self._manager_lock = Lock() + + def init_rpc(self): + self.manager = SimpleRpcClient.from_addr( + addr=self.server_host, + port=self.server_port, + enable_ssl=True, + default_handler=DefaultHandler(), + ) + self.proxy = ConnectionProxy(self.manager) + + def authentify(self): + """Try to authenticate to the server while the server returns a bad + role. + + """ + try: + role = self.proxy.authentify(self.user_name, self.user_passwd) + except Exception: + logger.exception(u'Unknow exception while authentifying.') + raise + + # set handler according to which role was returned by the cc-server + if role == u'host': + logger.debug(u'Role host affected.') + self.role = u'host' + elif role == u'hv': + logger.debug(u'Role hypervisor affected.') + self.role = u'hv' + else: + logger.debug(u'Wrong role returned: %s' % role) + role = None + time.sleep(2) + + self.role = role + + def rpc(self): + """Runs rpc main loop.""" + try: + self.manager.run() + except Exception: + logger.exception(u'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(u'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() + + # 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): + with self._manager_lock: + if self.manager is not None: + self.manager.shutdown() + self.manager = None + self.role = None diff --git a/ccnode/tags.py b/ccnode/tags.py new file mode 100644 index 0000000000000000000000000000000000000000..f64e13fc97b57b463e998f6243347d3beb9afa03 --- /dev/null +++ b/ccnode/tags.py @@ -0,0 +1,22 @@ +from inspect import isfunction + + +class Tag(object): + """Class that abstract tags.""" + def __init__(self, name, valuable, ttl): + """ + :param string name: tag name + :param valuable: something that gives tag value, string or function + :param None,int ttl: Time to live for caching the tags + """ + self.name = name + self._value = valuable + self.ttl = ttl + + @property + def value(self): + """Returns tag value.""" + if isfunction(self._value): + return self._value() + + return self._value diff --git a/ccnode/threading.py b/ccnode/threading.py deleted file mode 100644 index 2a3f395d381c8c61cdd2b76595253cbc6d571581..0000000000000000000000000000000000000000 --- a/ccnode/threading.py +++ /dev/null @@ -1,100 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Cloud-Control Node -# Copyright (C) 2011 SmartJog [http://www.smartjog.com] -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Authors: -# * 2011-06: Thibault VINCENT -# - -from __future__ import absolute_import -from threading import Lock - - -class RWLock(object): - # pylint: disable=R0903 - - def __init__(self): - - self._mutex = Lock() - self._writemutex = Lock() - self._readers = 0 - self._writers = 0 - self.read = self._RLock(self) - self.write = self._WLock(self) - - - class _Lock(object): - - def __init__(self, rwlock): - - self._parent = rwlock - - - class _WLock(_Lock): - - def __enter__(self): - - self.acquire() - - def __exit__(self, exc_type, exc_value, traceback): - - self.release() - - def acquire(self): - # pylint: disable=W0212 - - with self._parent._mutex: - self._parent._writers += 1 - self._parent._writemutex.acquire() - - def release(self): - # pylint: disable=W0212 - - with self._parent._mutex: - self._parent._writers -= 1 - self._parent._writemutex.release() - - - class _RLock(_Lock): - - def __enter__(self): - - self.acquire() - - def __exit__(self, exc_type, exc_value, traceback): - - self.release() - - def acquire(self): - # pylint: disable=W0212 - - self._parent._mutex.acquire() - if self._parent._writers > 0 or self._parent._readers == 0: - self._parent._mutex.release() - self._parent._writemutex.acquire() - self._parent._mutex.acquire() - self._parent._readers += 1 - self._parent._mutex.release() - - def release(self): - # pylint: disable=W0212 - - self._parent._mutex.acquire() - self._parent._readers -= 1 - if self._parent._readers == 0: - self._parent._writemutex.release() - self._parent._mutex.release() diff --git a/ccnode/utils.py b/ccnode/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..f4b1bfd0bfaf2588a1b0cccbfacc923fff07c12e --- /dev/null +++ b/ccnode/utils.py @@ -0,0 +1,11 @@ +import signal + + +def signal_(signum): + """Decorate a function to register as a handler to the signal.""" + + def decorator(func): + signal.signal(signum, func) + return func + + return decorator diff --git a/etc/cc-node.conf b/etc/cc-node.conf old mode 100755 new mode 100644 index 90d49f7ee916bfbebdd3575907e55cd95b901327..ef8b1fc4e887e213b2d15fbd7a92d827e372d343 --- a/etc/cc-node.conf +++ b/etc/cc-node.conf @@ -1,7 +1,38 @@ -[node] -address = cc-server.lab.fr.lan -login = tvincent_kvm -password = toto -verbosity = 3 -virtualization = no -command_execution = yes +[ccserver] +host=cc-server.lab.fr.lan +port=1984 +user=tvincent_kvm +password=toto + +[loggers] +keys=root,ccnode + +[handlers] +keys=syslog + +[formatters] +keys=simpleFormatter + +[logger_root] +level=ERROR +handlers=syslog +# handlers=fileHandler + +[logger_ccnode] +level=INFO +handlers= +qualname=ccnode + +[handler_syslog] +class=handlers.SysLogHandler +formatter=simpleFormatter +args=('/dev/log', handlers.SysLogHandler.LOG_DAEMON) + +# [handler_fileHandler] +# class=FileHandler +# formatter=simpleFormatter +# args=('/var/log/cc-node.log',) + +[formatter_simpleFormatter] +# format=%(asctime)s - %(name)s - %(levelname)s - %(message)s +format=cc-node - %(asctime)s - %(name)s - %(levelname)s - %(message)s diff --git a/examples/cc-node-debug.conf b/examples/cc-node-debug.conf new file mode 100644 index 0000000000000000000000000000000000000000..4eb259269102ffcee44456c696bba52166b3afb5 --- /dev/null +++ b/examples/cc-node-debug.conf @@ -0,0 +1,36 @@ +[ccserver] +host=__HOST__ +port=1984 +user=__USER__ +password=__PASSWD__ + +# TODO +virtualization = no +command_execution = yes +# END TODO + +[loggers] +keys=root,ccnode + +[handlers] +keys=consoleHandler + +[formatters] +keys=simpleFormatter + +[logger_root] +level=ERROR +handlers=consoleHandler + +[logger_ccnode] +level=DEBUG +handlers= +qualname=ccnode + +[handler_consoleHandler] +class=StreamHandler +formatter=simpleFormatter +args=(sys.stderr,) + +[formatter_simpleFormatter] +format=cc-node - %(asctime)s - %(name)s - %(levelname)s - %(message)s