Loading bin/cc-node +127 −27 Original line number Diff line number Diff line #!/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 <http://www.gnu.org/licenses/>. # # Authors: # * 2011-06: Thibault VINCENT <thibault.vincent@smartjog.com> # 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) ccnode/__init__.py +2 −50 Original line number Diff line number Diff line # -*- 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 <http://www.gnu.org/licenses/>. # # Authors: # * 2011-06: Thibault VINCENT <thibault.vincent@smartjog.com> # __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() ccnode/config.py +15 −187 Original line number Diff line number Diff line # -*- 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 <http://www.gnu.org/licenses/>. # # Authors: # * 2011-06: Thibault VINCENT <thibault.vincent@smartjog.com> # 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 ccnode/exc.py 0 → 100644 +1 −0 Original line number Diff line number Diff line """Exceptions classes for ccnode.""" ccnode/host/__init__.py 0 → 100644 +0 −0 Empty file added. Loading
bin/cc-node +127 −27 Original line number Diff line number Diff line #!/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 <http://www.gnu.org/licenses/>. # # Authors: # * 2011-06: Thibault VINCENT <thibault.vincent@smartjog.com> # 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)
ccnode/__init__.py +2 −50 Original line number Diff line number Diff line # -*- 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 <http://www.gnu.org/licenses/>. # # Authors: # * 2011-06: Thibault VINCENT <thibault.vincent@smartjog.com> # __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()
ccnode/config.py +15 −187 Original line number Diff line number Diff line # -*- 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 <http://www.gnu.org/licenses/>. # # Authors: # * 2011-06: Thibault VINCENT <thibault.vincent@smartjog.com> # 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
ccnode/exc.py 0 → 100644 +1 −0 Original line number Diff line number Diff line """Exceptions classes for ccnode."""