#!/usr/bin/env python '''The XML-RPC Bcfg2 Server''' __revision__ = '$Revision$' import Bcfg2.Server.Plugins.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 SetupError(Exception): '''Used when the server cant be setup''' pass 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: raise SetupError # 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)) famfd = self.Core.fam.fileno() while True: try: rsockinfo = select.select([famfd], [], [], 15)[0] if not rsockinfo: break self.Core.Service() except socket.error: continue 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 Bcfg2GetProbes(self, address): '''Fetch probes for a particular client''' resp = Element('probes') try: name = self.Core.metadata.resolve_client(address[0]) meta = self.Core.metadata.get_metadata(name) for plugin in self.Core.plugins.values(): for probe in plugin.GetProbes(meta): resp.append(probe) return tostring(resp) except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError: warning = 'Client metadata resolution error for %s' % address[0] 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: name = self.Core.metadata.resolve_client(address[0]) meta = self.Core.metadata.get_metadata(name) except Bcfg2.Server.Plugins.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" % (address[0])) 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" % (address), 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''' try: client = self.Core.metadata.resolve_client(address[0]) self.Core.metadata.set_profile(client, profile) except (Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError, Bcfg2.Server.Plugins.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''' try: client = self.Core.metadata.resolve_client(address[0]) return tostring(self.Core.BuildConfiguration(client)) except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError: self.logger.warning("Metadata consistency failure for %s" % (address)) raise Fault, (6, "Metadata consistency failure") 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.Core.metadata.resolve_client(address[0]) meta = self.Core.metadata.get_metadata(client) # Update statistics self.Core.stats.updateStats(sdata, meta.hostname) self.logger.info("Client %s reported state %s" % (client, state.attrib['state'])) return "" if __name__ == '__main__': 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), } SSETUP = Bcfg2.Options.OptionParser('bcfg2', OPTINFO).parse() level = 0 if '-D' in sys.argv: Bcfg2.Logging.setup_logging('bcfg2-server', to_console=False, level=level) else: Bcfg2.Logging.setup_logging('bcfg2-server', level=level) if SSETUP['daemon']: daemonize(SSETUP['daemon']) try: BSERV = Bcfg2Serv(SSETUP) except SetupError: raise SystemExit, 1 while not BSERV.shut: try: BSERV.serve_forever() except: critical_error('error in service loop') logger.info("Shutting down")