summaryrefslogtreecommitdiffstats
path: root/src/sbin/bcfg2-server
diff options
context:
space:
mode:
authorNarayan Desai <desai@mcs.anl.gov>2006-01-23 22:35:40 +0000
committerNarayan Desai <desai@mcs.anl.gov>2006-01-23 22:35:40 +0000
commitedca0b698637c3fd0a70af7e4752a46afca938d3 (patch)
tree658fad717833200ccb4e3725c811ccce7c10fc8d /src/sbin/bcfg2-server
parent8ca8a153dfc6bd81ede9f5cff1ee3f111ae053ee (diff)
downloadbcfg2-edca0b698637c3fd0a70af7e4752a46afca938d3.tar.gz
bcfg2-edca0b698637c3fd0a70af7e4752a46afca938d3.tar.bz2
bcfg2-edca0b698637c3fd0a70af7e4752a46afca938d3.zip
last step of repo switches
git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@1716 ce84e21b-d406-0410-9b95-82705330c041
Diffstat (limited to 'src/sbin/bcfg2-server')
-rwxr-xr-xsrc/sbin/bcfg2-server291
1 files changed, 291 insertions, 0 deletions
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 "<ok/>"
+
+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': "<PID file> ",
+ 'c': "<config file>",
+ 'C': "<client hostname>"
+ }
+
+ 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")