From edca0b698637c3fd0a70af7e4752a46afca938d3 Mon Sep 17 00:00:00 2001 From: Narayan Desai Date: Mon, 23 Jan 2006 22:35:40 +0000 Subject: last step of repo switches git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@1716 ce84e21b-d406-0410-9b95-82705330c041 --- src/sbin/bcfg2-server | 291 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100755 src/sbin/bcfg2-server (limited to 'src/sbin/bcfg2-server') diff --git a/src/sbin/bcfg2-server b/src/sbin/bcfg2-server new file mode 100755 index 000000000..2da6ff325 --- /dev/null +++ b/src/sbin/bcfg2-server @@ -0,0 +1,291 @@ +#!/usr/bin/env python + +'''The XML-RPC Bcfg2 Server''' +__revision__ = '$Revision$' + +from getopt import getopt, GetoptError +from sys import argv, exc_info +from syslog import openlog, LOG_LOCAL0, syslog, LOG_INFO, LOG_ERR +from Bcfg2.Server.Core import Core, CoreInitError +from Bcfg2.Server.Metadata import MetadataConsistencyError +from Bcfg2.Server.Component import Component +from threading import Lock +from select import select, error as selecterror +from signal import signal, SIGINT, SIGTERM +from traceback import extract_tb +from xmlrpclib import Fault +from socket import gethostbyaddr, herror +from lxml.etree import XML, Element, tostring +from M2Crypto.SSL import SSLError + +import os, sys + +def daemonize(filename): + '''Do the double fork/setsession dance''' + # Fork once + if os.fork() != 0: + os._exit(0) + os.setsid() # Create new session + pid = os.fork() + if pid != 0: + pidfile = open(filename, "w") + pidfile.write("%i" % pid) + pidfile.close() + os._exit(0) + os.chdir("/") + os.umask(0) + + null = open("/dev/null", "w+") + + os.dup2(null.fileno(), sys.__stdin__.fileno()) + os.dup2(null.fileno(), sys.__stdout__.fileno()) + os.dup2(null.fileno(), sys.__stderr__.fileno()) + + +def critical_error(operation): + '''Print tracebacks in unexpected cases''' + syslog(LOG_ERR, "Traceback information (please include in any bug report):") + (ttype, value, trace) = exc_info() + for line in extract_tb(trace): + syslog(LOG_ERR, "File %s, line %i, in %s\n %s" % (line)) + syslog(LOG_ERR, "%s: %s" % (ttype, value)) + warning_error("An unexpected failure occurred in %s" % (operation) ) + raise Fault, (7, "Critical unexpected failure: %s" % (operation)) + +def fatal_error(message): + '''Signal a fatal error''' + syslog(LOG_ERR, "Fatal error: %s" % (message)) + raise SystemExit, 1 + +def warning_error(message): + '''Warn about a problem but continue''' + syslog(LOG_ERR,"Warning: %s\n" % (message)) + +def usage_error(message, opt, vopt, descs, argDescs): + '''Die because script was called the wrong way''' + print "Usage error: %s" % (message) + print_usage(opt, vopt, descs, argDescs) + raise SystemExit, 2 + +verboseMode = False + +def verbose(message): + '''Conditionally output information in verbose mode''' + global verboseMode + + if(verboseMode == True): + syslog(LOG_INFO, "%s" % (message)) + +def print_usage(opt, vopt, descs, argDescs): + print "bcfg2-server usage:" + for arg in opt.iteritems(): + print " -%s\t\t\t%s" % (arg[0], descs[arg[0]]) + for arg in vopt.iteritems(): + print " -%s %s\t%s" % (arg[0], argDescs[arg[0]], descs[arg[0]]) + +def dgetopt(arglist, opt, vopt, descs, argDescs): + '''parse options into a dictionary''' + global verboseMode + + ret = {} + for optname in opt.values() + vopt.values(): + ret[optname] = False + + gstr = "".join(opt.keys()) + "".join([optionkey + ':' for optionkey in vopt.keys()]) + try: + ginfo = getopt(arglist, gstr) + except GetoptError, gerr: + usage_error(gerr, opt, vopt, descs, argDescs) + + for (gopt, garg) in ginfo[0]: + option = gopt[1:] + if opt.has_key(option): + ret[opt[option]] = True + else: + ret[vopt[option]] = garg + + if ret["help"] == True: + print_usage(opt, vopt, descs, argDescs) + raise SystemExit, 0 + + if ret["verbose"] == True: + verboseMode = True + + return ret + +class Bcfg2(Component): + """The Bcfg2 Server component providing XML-RPC access to Bcfg methods""" + __name__ = 'bcfg2' + __implementation__ = 'bcfg2' + + request_queue_size = 15 + + def __init__(self, setup): + Component.__init__(self, setup) + self.shut = False + # set shutdown handlers for sigint and sigterm + signal(SIGINT, self.start_shutdown) + signal(SIGTERM, self.start_shutdown) + try: + self.Core = Core(setup, setup['configfile']) + self.CoreLock = Lock() + except CoreInitError, msg: + fatal_error(msg) + + self.funcs.update({ + "GetConfig": self.Bcfg2GetConfig, + "GetProbes": self.Bcfg2GetProbes, + "RecvProbeData": self.Bcfg2RecvProbeData, + "RecvStats": self.Bcfg2RecvStats + }) + for plugin in self.Core.plugins.values(): + for method in plugin.__rmi__: + self.register_function(getattr(self.Core.plugins[plugin.__name__], method), + "%s.%s" % (plugin.__name__, method)) + + def get_request(self): + '''We need to do work between requests, so select with timeout instead of blocking in accept''' + rsockinfo = [] + famfd = self.Core.fam.fileno() + while self.socket not in rsockinfo: + if self.shut: + raise SSLError + try: + rsockinfo = select([self.socket, famfd], [], [], 15)[0] + except selecterror: + raise SSLError + + if famfd in rsockinfo: + self.Core.fam.Service() + if self.socket in rsockinfo: + # workaround for m2crypto 0.15 bug + self.socket.postConnectionCheck = None + return self.socket.accept() + + def serve_forever(self): + """Handle one request at a time until doomsday.""" + while not self.shut: + self.handle_request() + + def start_shutdown(self, signum, frame): + '''Shutdown on unexpected signals''' + self.shut = True + + def handle_error(self): + '''Catch error path for clean exit''' + return False + + def resolve_client(self, client): + if self.setup['client']: + return self.setup['client'] + try: + return gethostbyaddr(client)[0] + except herror: + warning = "host resolution error for %s" % (client) + warning_error(warning) + raise Fault, (5, warning) + + def Bcfg2GetProbes(self, address): + '''Fetch probes for a particular client''' + client = self.resolve_client(address[0]) + resp = Element('probes') + + try: + meta = self.Core.metadata.get_metadata(client) + + for generator in self.Core.generators: + for probe in generator.GetProbes(meta): + resp.append(probe) + return tostring(resp) + except MetadataConsistencyError: + warning = 'metadata consistency error' + warning_error(warning) + raise Fault, (6, warning) + except: + critical_error("determining client probes") + + + def Bcfg2RecvProbeData(self, address, probedata): + '''Receive probe data from clients''' + client = self.resolve_client(address[0]) + + for data in probedata: + try: + [generator] = [gen for gen in self.Core.generators if gen.__name__ == data.get('source')] + generator.ReceiveData(client, data) + except IndexError: + warning_error("Failed to locate plugin %s" % (data.get('source'))) + except: + critical_error("probe data receipt") + return True + + def Bcfg2GetConfig(self, address, image=False, profile=False): + '''Build config for a client''' + client = self.resolve_client(address[0]) + + if image and profile: + try: + self.Core.metadata.set_group(client, profile) + except MetadataConsistencyError: + warning = 'metadata consistency error' + warning_error(warning) + raise Fault, (6, warning) + return tostring(self.Core.BuildConfiguration(client)) + + def Bcfg2RecvStats(self, address, stats): + '''Act on statistics upload''' + sdata = XML(stats) + state = sdata.find(".//Statistics") + # Versioned stats to prevent tied client/server upgrade + if state.get('version') >= '2.0': + client = self.resolve_client(address[0]) + + # Update statistics + self.Core.stats.updateStats(sdata, client) + + syslog(LOG_INFO, "Client %s reported state %s" % + (client, state.attrib['state'])) + return "" + +if __name__ == '__main__': + openlog("Bcfg2", 0, LOG_LOCAL0) + options = { + 'v':'verbose', + 'd':'debug', + 'h':'help' + } + doptions = { + 'D':'daemon', + 'c':'configfile', + 'C':'client' + } + + descriptions = { + 'v': "enable verbose output", + 'd': "enable debugging output", + 'D': "daemonise the server, storing PID", + 'c': "set the server's config file", + 'C': "always return the given client's config (debug only)", + 'h': "display this usage information" + } + + argDescriptions = { + 'D': " ", + 'c': "", + 'C': "" + } + + ssetup = dgetopt(argv[1:], options, doptions, + descriptions, argDescriptions) + if ssetup['daemon']: + daemonize(ssetup['daemon']) + if not ssetup['configfile']: + ssetup['configfile'] = '/etc/bcfg2.conf' + s = Bcfg2(ssetup) + while not s.shut: + try: + s.serve_forever() + except: + critical_error("service loop") + + syslog(LOG_INFO, "Shutting down") -- cgit v1.2.3-1-g7c22