Commit ab5b4d74 authored by Anael Beutot's avatar Anael Beutot
Browse files

Base squeleton for new cc-node.

Initialization, re-authentication...
parent 7e731b86
Loading
Loading
Loading
Loading
+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)
+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()
+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."""
+0 −0

Empty file added.

Loading