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