#!/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)
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)
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)
# clear dynamic groups
self.Core.metadata.cgroups[meta.hostname] = []
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[0]), 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)
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)
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)
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 ""
def _authenticate_connection(self, _, user, password, address):
return self.Core.metadata.AuthenticateConnection(user, password, address)
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")