#!/usr/bin/env python '''The XML-RPC Bcfg2 Server''' __revision__ = '$Revision$' import Bcfg2.Server.Metadata from Bcfg2.Server.Core import Core, CoreInitError from xmlrpclib import Fault from lxml.etree import XML, Element, tostring import logging, os, select, signal, socket, sys import Bcfg2.Logging, Bcfg2.Options, Bcfg2.Server.Component logger = logging.getLogger('bcfg2-server') def daemonize(filename): '''Do the double fork/setsession dance''' # Check if the pid is active try: pidfile = open(filename, "r") oldpid = int(pidfile.readline()) # getpgid() will retun an IO error if all fails os.getpgid(oldpid) pidfile.close() # If we got this far without exceptions, there is another instance # running. Exit gracefully. logger.critical("PID File (%s) exists and listed PID (%d) is active." % \ (filename, oldpid) ) raise SystemExit, 1 except OSError: pidfile.close() except IOError: # pid file doesn't pass # 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): '''Log and err, traceback and return an xmlrpc fault to client''' logger.error(operation, exc_info=1) raise Fault, (7, "Critical unexpected failure: %s" % (operation)) class Bcfg2Serv(Bcfg2.Server.Component.Component): """The Bcfg2 Server component providing XML-RPC access to Bcfg methods""" __name__ = 'bcfg2' __implementation__ = 'bcfg2' request_queue_size = 15 def __init__(self, setup): try: Bcfg2.Server.Component.Component.__init__(self, setup) self.shut = False except Bcfg2.Server.Component.ComponentInitError: logger.critical("Failed to setup server") raise SystemExit, 1 # set shutdown handlers for sigint and sigterm signal.signal(signal.SIGINT, self.start_shutdown) signal.signal(signal.SIGTERM, self.start_shutdown) try: self.Core = Core(setup, setup['configfile']) except CoreInitError, msg: logger.critical("Fatal error: %s" % (msg)) raise SystemExit, 1 self.funcs.update({ "AssertProfile": self.Bcfg2AssertProfile, "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 socket.error try: rsockinfo = select.select([self.socket, famfd], [], [], 15)[0] except select.error: continue if famfd in rsockinfo: self.Core.Service() if self.socket in rsockinfo: return self.socket.accept() def resolve_client(self, client): if self.setup['client']: return self.setup['client'] try: return socket.gethostbyaddr(client)[0] except socket.herror: warning = "host resolution error for %s" % (client) self.logger.warning(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 Bcfg2.Server.Metadata.MetadataConsistencyError: warning = 'metadata consistency error' self.logger.warning(warning) raise Fault, (6, warning) except: critical_error("error determining client probes") def Bcfg2RecvProbeData(self, address, probedata): '''Receive probe data from clients''' try: client = self.resolve_client(address[0]) meta = self.Core.metadata.get_metadata(client) except Bcfg2.Server.metadata.MetadataConsistencyError: warning = 'metadata consistency error' self.logger.warning(warning) raise Fault, (6, warning) try: xpdata = XML(probedata) except: self.logger.error("Failed to parse probe data from client %s" % (client)) return False for data in xpdata: if self.Core.plugins.has_key(data.get('source')): try: self.Core.plugins[data.get('source')].ReceiveData(meta, data) except: self.logger.error("Failed to process probe data from client %s" % (client), exc_info=1) else: self.logger.warning("Failed to locate plugin %s" % (data.get('source'))) return True def Bcfg2AssertProfile(self, address, profile): '''Set profile for a client''' client = self.resolve_client(address[0]) try: self.Core.metadata.set_profile(client, profile) except (Bcfg2.Server.Metadata.MetadataConsistencyError, Bcfg2.Server.Metadata.MetadataRuntimeError): warning = 'metadata consistency error' self.logger.warning(warning) raise Fault, (6, warning) return True def Bcfg2GetConfig(self, address, _=False, profile=False): '''Build config for a client''' client = self.resolve_client(address[0]) 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) self.logger.info("Client %s reported state %s" % (client, state.attrib['state'])) return "" if __name__ == '__main__': if '-D' in sys.argv: Bcfg2.Logging.setup_logging('bcfg2-server', to_console=False) else: Bcfg2.Logging.setup_logging('bcfg2-server') OPTINFO = { 'verbose': (('-v', False, 'enable verbose output'), False, False, False, True), 'debug': (('-d', False, 'enable debugging output'), False, False, False, True), 'help': (('-h', False, 'display this usage information'), False, False, False, True), 'daemon': (('-D', '', 'daemonize the server, storing PID'), False, False, False, False), 'configfile': (('-C', '', 'use this config file'), False, False, '/etc/bcfg2.conf', False), 'client': (('-c', '', 'hard set the client name (for debugging)'), False, False, False, False) } SSETUP = Bcfg2.Options.OptionParser('bcfg2', OPTINFO).parse() if SSETUP['daemon']: daemonize(SSETUP['daemon']) try: BSERV = Bcfg2Serv(SSETUP) except: critical_error("Failed to setup server; probably a key problem") while not BSERV.shut: try: BSERV.serve_forever() except: critical_error('error in service loop') logger.info("Shutting down")