summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/Core.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Bcfg2/Server/Core.py')
-rw-r--r--src/lib/Bcfg2/Server/Core.py285
1 files changed, 177 insertions, 108 deletions
diff --git a/src/lib/Bcfg2/Server/Core.py b/src/lib/Bcfg2/Server/Core.py
index 6dbab64bd..1ee01585c 100644
--- a/src/lib/Bcfg2/Server/Core.py
+++ b/src/lib/Bcfg2/Server/Core.py
@@ -6,38 +6,28 @@ import select
import sys
import threading
import time
+import inspect
+import lxml.etree
from traceback import format_exc
-
-try:
- import lxml.etree
-except ImportError:
- print("Failed to import lxml dependency. Shutting down server.")
- raise SystemExit(1)
-
-from Bcfg2.Component import Component, exposed
-from Bcfg2.Server.Plugin import PluginInitError, PluginExecutionError
import Bcfg2.Server
+import Bcfg2.Logger
import Bcfg2.Server.FileMonitor
import Bcfg2.Server.Plugins.Metadata
-# Compatibility imports
from Bcfg2.Bcfg2Py3k import xmlrpclib
+from Bcfg2.Server.Plugin import PluginInitError, PluginExecutionError
+
if sys.hexversion >= 0x03000000:
from functools import reduce
-logger = logging.getLogger('Bcfg2.Server.Core')
-
-
-def critical_error(operation):
- """Log and err, traceback and return an xmlrpc fault to client."""
- logger.error(operation, exc_info=1)
- raise xmlrpclib.Fault(xmlrpclib.APPLICATION_ERROR, "Critical unexpected failure: %s" % (operation))
-
try:
import psyco
psyco.full()
except:
pass
+def exposed(func):
+ func.exposed = True
+ return func
def sort_xml(node, key=None):
for child in node:
@@ -55,24 +45,31 @@ class CoreInitError(Exception):
pass
-class Core(Component):
+class BaseCore(object):
"""The Core object is the container for all
Bcfg2 Server logic and modules.
"""
- name = 'bcfg2-server'
- implementation = 'bcfg2-server'
- def __init__(self, repo, plugins, password, encoding,
- cfile='/etc/bcfg2.conf', ca=None, setup=None,
- filemonitor='default', start_fam_thread=False):
- Component.__init__(self)
- self.datastore = repo
+ def __init__(self, setup, start_fam_thread=False):
+ self.datastore = setup['repo']
+
+ self.logger = logging.getLogger('bcfg2-server')
+ if 'debug' in setup and setup['debug']:
+ level = logging.DEBUG
+ else:
+ level = logging.INFO
+ self.logger.setLevel(level)
+ Bcfg2.Logger.setup_logging('bcfg2-server',
+ to_console=True,
+ to_syslog=True,
+ to_file=setup['logging'],
+ level=level)
try:
- fm = Bcfg2.Server.FileMonitor.available[filemonitor]
+ fm = Bcfg2.Server.FileMonitor.available[setup['filemonitor']]
except KeyError:
- logger.error("File monitor driver %s not available; "
- "forcing to default" % filemonitor)
+ self.logger.error("File monitor driver %s not available; "
+ "forcing to default" % filemonitor)
fm = Bcfg2.Server.FileMonitor.available['default']
famargs = dict(ignore=[], debug=False)
if 'ignore' in setup:
@@ -82,68 +79,69 @@ class Core(Component):
try:
self.fam = fm(**famargs)
except IOError:
- msg = "Failed to instantiate fam driver %s" % filemonitor
- logger.error(msg, exc_info=1)
+ msg = "Failed to instantiate fam driver %s" % setup['filemonitor']
+ self.logger.error(msg, exc_info=1)
raise CoreInitError(msg)
self.pubspace = {}
- self.cfile = cfile
+ self.cfile = setup['configfile']
self.cron = {}
self.plugins = {}
self.plugin_blacklist = {}
self.revision = '-1'
- self.password = password
- self.encoding = encoding
+ self.password = setup['password']
+ self.encoding = setup['encoding']
self.setup = setup
atexit.register(self.shutdown)
# Create an event to signal worker threads to shutdown
self.terminate = threading.Event()
- if '' in plugins:
- plugins.remove('')
+ if '' in setup['plugins']:
+ setup['plugins'].remove('')
- for plugin in plugins:
+ for plugin in setup['plugins']:
if not plugin in self.plugins:
self.init_plugins(plugin)
# Remove blacklisted plugins
for p, bl in list(self.plugin_blacklist.items()):
if len(bl) > 0:
- logger.error("The following plugins conflict with %s;"
- "Unloading %s" % (p, bl))
+ self.logger.error("The following plugins conflict with %s;"
+ "Unloading %s" % (p, bl))
for plug in bl:
del self.plugins[plug]
# This section logs the experimental plugins
expl = [plug for (name, plug) in list(self.plugins.items())
if plug.experimental]
if expl:
- logger.info("Loading experimental plugin(s): %s" % \
- (" ".join([x.name for x in expl])))
- logger.info("NOTE: Interfaces subject to change")
+ self.logger.info("Loading experimental plugin(s): %s" %
+ (" ".join([x.name for x in expl])))
+ self.logger.info("NOTE: Interfaces subject to change")
# This section logs the deprecated plugins
depr = [plug for (name, plug) in list(self.plugins.items())
if plug.deprecated]
if depr:
- logger.info("Loading deprecated plugin(s): %s" % \
- (" ".join([x.name for x in depr])))
+ self.logger.info("Loading deprecated plugin(s): %s" %
+ (" ".join([x.name for x in depr])))
mlist = self.plugins_by_type(Bcfg2.Server.Plugin.Metadata)
if len(mlist) == 1:
self.metadata = mlist[0]
else:
- logger.error("No Metadata Plugin loaded; failed to instantiate Core")
+ self.logger.error("No Metadata Plugin loaded; "
+ "failed to instantiate Core")
raise CoreInitError("No Metadata Plugin")
self.statistics = self.plugins_by_type(Bcfg2.Server.Plugin.Statistics)
self.pull_sources = self.plugins_by_type(Bcfg2.Server.Plugin.PullSource)
self.generators = self.plugins_by_type(Bcfg2.Server.Plugin.Generator)
self.structures = self.plugins_by_type(Bcfg2.Server.Plugin.Structure)
self.connectors = self.plugins_by_type(Bcfg2.Server.Plugin.Connector)
- self.ca = ca
- self.fam_thread = threading.Thread(target=self._file_monitor_thread)
+ self.ca = setup['ca']
+ self.fam_thread = \
+ threading.Thread(name="%sFAMThread" % setup['filemonitor'],
+ target=self._file_monitor_thread)
+ self.lock = threading.Lock()
+
if start_fam_thread:
self.fam_thread.start()
- self.monitor_cfile()
-
- def monitor_cfile(self):
- if self.setup:
self.fam.AddMonitor(self.cfile, self.setup)
def plugins_by_type(self, base_cls):
@@ -179,6 +177,7 @@ class Core(Component):
def init_plugins(self, plugin):
"""Handling for the plugins."""
+ self.logger.debug("Loading plugin %s" % plugin)
try:
mod = getattr(__import__("Bcfg2.Server.Plugins.%s" %
(plugin)).Server.Plugins, plugin)
@@ -186,7 +185,7 @@ class Core(Component):
try:
mod = __import__(plugin)
except:
- logger.error("Failed to load plugin %s" % (plugin))
+ self.logger.error("Failed to load plugin %s" % plugin)
return
plug = getattr(mod, plugin)
# Blacklist conflicting plugins
@@ -196,10 +195,11 @@ class Core(Component):
try:
self.plugins[plugin] = plug(self, self.datastore)
except PluginInitError:
- logger.error("Failed to instantiate plugin %s" % (plugin))
+ self.logger.error("Failed to instantiate plugin %s" % plugin,
+ exc_info=1)
except:
- logger.error("Unexpected instantiation failure for plugin %s" %
- (plugin), exc_info=1)
+ self.logger.error("Unexpected instantiation failure for plugin %s" %
+ plugin, exc_info=1)
def shutdown(self):
"""Shutting down the plugins."""
@@ -216,12 +216,13 @@ class Core(Component):
getattr(plugin, hook)(metadata)
except AttributeError:
err = sys.exc_info()[1]
- logger.error("Unknown attribute: %s" % err)
+ self.logger.error("Unknown attribute: %s" % err)
raise
except:
err = sys.exc_info()[1]
- logger.error("%s: Error invoking hook %s: %s" % (plugin, hook,
- err))
+ self.logger.error("%s: Error invoking hook %s: %s" % (plugin,
+ hook,
+ err))
def validate_structures(self, metadata, data):
"""Checks the data structure."""
@@ -230,12 +231,12 @@ class Core(Component):
plugin.validate_structures(metadata, data)
except Bcfg2.Server.Plugin.ValidationError:
err = sys.exc_info()[1]
- logger.error("Plugin %s structure validation failed: %s" \
- % (plugin.name, err.message))
+ self.logger.error("Plugin %s structure validation failed: %s" %
+ (plugin.name, err))
raise
except:
- logger.error("Plugin %s: unexpected structure validation failure" \
- % (plugin.name), exc_info=1)
+ self.logger.error("Plugin %s: unexpected structure validation "
+ "failure" % plugin.name, exc_info=1)
def validate_goals(self, metadata, data):
"""Checks that the config matches the goals enforced by the plugins."""
@@ -244,23 +245,23 @@ class Core(Component):
plugin.validate_goals(metadata, data)
except Bcfg2.Server.Plugin.ValidationError:
err = sys.exc_info()[1]
- logger.error("Plugin %s goal validation failed: %s" \
- % (plugin.name, err.message))
+ self.logger.error("Plugin %s goal validation failed: %s" %
+ (plugin.name, err.message))
raise
except:
- logger.error("Plugin %s: unexpected goal validation failure" \
- % (plugin.name), exc_info=1)
+ self.logger.error("Plugin %s: unexpected goal validation "
+ "failure" % plugin.name, exc_info=1)
def GetStructures(self, metadata):
"""Get all structures for client specified by metadata."""
structures = reduce(lambda x, y: x + y,
- [struct.BuildStructures(metadata) for struct \
- in self.structures], [])
+ [struct.BuildStructures(metadata)
+ for struct in self.structures], [])
sbundles = [b.get('name') for b in structures if b.tag == 'Bundle']
missing = [b for b in metadata.bundles if b not in sbundles]
if missing:
- logger.error("Client %s configuration missing bundles: %s" \
- % (metadata.hostname, ':'.join(missing)))
+ self.logger.error("Client %s configuration missing bundles: %s" %
+ (metadata.hostname, ':'.join(missing)))
return structures
def BindStructure(self, structure, metadata):
@@ -275,14 +276,14 @@ class Core(Component):
exc = sys.exc_info()[1]
if 'failure' not in entry.attrib:
entry.set('failure', 'bind error: %s' % format_exc())
- logger.error("Failed to bind entry %s:%s: %s" %
- (entry.tag, entry.get('name'), exc))
+ self.logger.error("Failed to bind entry %s:%s: %s" %
+ (entry.tag, entry.get('name'), exc))
except Exception:
exc = sys.exc_info()[1]
if 'failure' not in entry.attrib:
entry.set('failure', 'bind error: %s' % format_exc())
- logger.error("Unexpected failure in BindStructure: %s %s" \
- % (entry.tag, entry.get('name')), exc_info=1)
+ self.logger.error("Unexpected failure in BindStructure: %s %s" %
+ (entry.tag, entry.get('name')), exc_info=1)
def Bind(self, entry, metadata):
"""Bind an entry using the appropriate generator."""
@@ -298,11 +299,11 @@ class Core(Component):
return ret
except:
entry.set('name', oldname)
- logger.error("Failed binding entry %s:%s with altsrc %s" \
- % (entry.tag, entry.get('name'),
- entry.get('altsrc')))
- logger.error("Falling back to %s:%s" % (entry.tag,
- entry.get('name')))
+ self.logger.error("Failed binding entry %s:%s with altsrc %s" %
+ (entry.tag, entry.get('name'),
+ entry.get('altsrc')))
+ self.logger.error("Falling back to %s:%s" % (entry.tag,
+ entry.get('name')))
glist = [gen for gen in self.generators if
entry.get('name') in gen.Entries.get(entry.tag, {})]
@@ -311,8 +312,8 @@ class Core(Component):
metadata)
elif len(glist) > 1:
generators = ", ".join([gen.name for gen in glist])
- logger.error("%s %s served by multiple generators: %s" % \
- (entry.tag, entry.get('name'), generators))
+ self.logger.error("%s %s served by multiple generators: %s" %
+ (entry.tag, entry.get('name'), generators))
g2list = [gen for gen in self.generators if
gen.HandlesEntry(entry, metadata)]
if len(g2list) == 1:
@@ -324,12 +325,13 @@ class Core(Component):
def BuildConfiguration(self, client):
"""Build configuration for clients."""
start = time.time()
- config = lxml.etree.Element("Configuration", version='2.0', \
+ config = lxml.etree.Element("Configuration", version='2.0',
revision=self.revision)
try:
meta = self.build_metadata(client)
except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
- logger.error("Metadata consistency error for client %s" % client)
+ self.logger.error("Metadata consistency error for client %s" %
+ client)
return lxml.etree.Element("error", type='metadata error')
self.client_run_hook("start_client_run", meta)
@@ -337,7 +339,7 @@ class Core(Component):
try:
structures = self.GetStructures(meta)
except:
- logger.error("error in GetStructures", exc_info=1)
+ self.logger.error("error in GetStructures", exc_info=1)
return lxml.etree.Element("error", type='structure error')
self.validate_structures(meta, structures)
@@ -349,7 +351,8 @@ class Core(Component):
key = (entry.tag, entry.get('name'))
if key in esrcs:
if esrcs[key] != entry.get('altsrc'):
- logger.error("Found inconsistent altsrc mapping for entry %s:%s" % key)
+ self.logger.error("Found inconsistent altsrc mapping "
+ "for entry %s:%s" % key)
else:
esrcs[key] = entry.get('altsrc', None)
del esrcs
@@ -359,17 +362,49 @@ class Core(Component):
self.BindStructure(astruct, meta)
config.append(astruct)
except:
- logger.error("error in BindStructure", exc_info=1)
+ self.logger.error("error in BindStructure", exc_info=1)
self.validate_goals(meta, config)
self.client_run_hook("end_client_run", meta)
sort_xml(config, key=lambda e: e.get('name'))
- logger.info("Generated config for %s in %.03f seconds" % \
- (client, time.time() - start))
+ self.logger.info("Generated config for %s in %.03f seconds" %
+ (client, time.time() - start))
return config
+ def run(self, **kwargs):
+ """ run the server core """
+ raise NotImplementedError
+
+ def _daemonize(self):
+ child_pid = os.fork()
+ if child_pid != 0:
+ return
+
+ os.setsid()
+
+ child_pid = os.fork()
+ if child_pid != 0:
+ os._exit(0)
+
+ redirect_file = open("/dev/null", "w+")
+ os.dup2(redirect_file.fileno(), sys.__stdin__.fileno())
+ os.dup2(redirect_file.fileno(), sys.__stdout__.fileno())
+ os.dup2(redirect_file.fileno(), sys.__stderr__.fileno())
+
+ os.chdir(os.sep)
+
+ pidfile = open(self.setup['daemon'] or "/dev/null", "w")
+ pidfile.write("%s\n" % os.getpid())
+ pidfile.close()
+
+ return os.getpid()
+
+ def critical_error(self, operation):
+ """ this should be overridden by child classes """
+ self.logger.fatal(operation, exc_info=1)
+
def GetDecisions(self, metadata, mode):
"""Get data for the decision list."""
result = []
@@ -377,8 +412,8 @@ class Core(Component):
try:
result += plugin.GetDecisions(metadata, mode)
except:
- logger.error("Plugin: %s failed to generate decision list" \
- % plugin.name, exc_info=1)
+ self.logger.error("Plugin: %s failed to generate decision list"
+ % plugin.name, exc_info=1)
return result
def build_metadata(self, client_name):
@@ -405,12 +440,12 @@ class Core(Component):
try:
plugin.process_statistics(meta, statistics)
except:
- logger.error("Plugin %s failed to process stats from %s" \
- % (plugin.name, meta.hostname),
- exc_info=1)
+ self.logger.error("Plugin %s failed to process stats from "
+ "%s" % (plugin.name, meta.hostname),
+ exc_info=1)
- logger.info("Client %s reported state %s" % (client_name,
- state.get('state')))
+ self.logger.info("Client %s reported state %s" % (client_name,
+ state.get('state')))
self.client_run_hook("end_statistics", meta)
def resolve_client(self, address, cleanup_cache=False, metadata=True):
@@ -422,13 +457,41 @@ class Core(Component):
else:
meta = None
except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
- critical_error("Client metadata resolution error for %s; "
- "check server log" % address[0])
+ err = sys.exc_info()[1]
+ self.critical_error("Client metadata resolution error for %s: %s" %
+ (address[0], err))
except Bcfg2.Server.Plugins.Metadata.MetadataRuntimeError:
- critical_error('Metadata system runtime failure')
+ err = sys.exc_info()[1]
+ self.critical_error('Metadata system runtime failure for %s: %s' %
+ (address[0], err))
return (client, meta)
+ def critical_error(self, operation):
+ """Log and err, traceback and return an xmlrpc fault to client."""
+ self.logger.error(operation, exc_info=1)
+ raise xmlrpclib.Fault(xmlrpclib.APPLICATION_ERROR,
+ "Critical failure: %s" % operation)
+
+ def _get_rmi(self):
+ rmi = dict()
+ if self.plugins:
+ for pname, pinst in list(self.plugins.items()):
+ for mname in pinst.__rmi__:
+ rmi["%s.%s" % (pname, mname)] = getattr(pinst, mname)
+ return rmi
+
# XMLRPC handlers start here
+ @exposed
+ def listMethods(self, address):
+ methods = [name
+ for name, func in inspect.getmembers(self, callable)
+ if getattr(func, "exposed", False)]
+ methods.extend(self._get_rmi().keys())
+ return methods
+
+ @exposed
+ def methodHelp(self, address, method_name):
+ raise NotImplementedError
@exposed
def DeclareVersion(self, address, version):
@@ -439,8 +502,8 @@ class Core(Component):
except (Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError,
Bcfg2.Server.Plugins.Metadata.MetadataRuntimeError):
err = sys.exc_info()[1]
- critical_error("Unable to set version for %s: %s" %
- (client, err))
+ self.critical_error("Unable to set version for %s: %s" %
+ (client, err))
return True
@exposed
@@ -455,7 +518,9 @@ class Core(Component):
return lxml.etree.tostring(resp, encoding='UTF-8',
xml_declaration=True)
except:
- critical_error("Error determining probes for %s" % client)
+ err = sys.exc_info()[1]
+ self.critical_error("Error determining probes for %s" %
+ (client, err))
@exposed
def RecvProbeData(self, address, probedata):
@@ -467,8 +532,9 @@ class Core(Component):
xpdata = lxml.etree.XML(probedata.encode('utf-8'),
parser=Bcfg2.Server.XMLParser)
except:
- critical_error("Failed to parse probe data from client %s" %
- client)
+ err = sys.exc_info()[1]
+ self.critical_error("Failed to parse probe data from client %s: %s"
+ % (client, err))
sources = []
[sources.append(data.get('source')) for data in xpdata
@@ -481,8 +547,10 @@ class Core(Component):
try:
self.plugins[source].ReceiveData(metadata, dl)
except:
- critical_error("Failed to process probe data from client %s" %
- client)
+ err = sys.exc_info()[1]
+ self.critical_error("Failed to process probe data from client "
+ "%s: %s" %
+ (client, err))
return True
@exposed
@@ -494,7 +562,7 @@ class Core(Component):
except (Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError,
Bcfg2.Server.Plugins.Metadata.MetadataRuntimeError):
err = sys.exc_info()[1]
- critical_error("Unable to assert profile for %s: %s" %
+ self.critical_error("Unable to assert profile for %s: %s" %
(client, err))
return True
@@ -507,7 +575,7 @@ class Core(Component):
return lxml.etree.tostring(config, encoding='UTF-8',
xml_declaration=True)
except Bcfg2.Server.Plugins.Metadata.MetadataConsistencyError:
- critical_error("Metadata consistency failure for %s" % client)
+ self.critical_error("Metadata consistency failure for %s" % client)
@exposed
def RecvStats(self, address, stats):
@@ -524,7 +592,8 @@ class Core(Component):
else:
# No ca, so no cert validation can be done
acert = None
- return self.metadata.AuthenticateConnection(acert, user, password, address)
+ return self.metadata.AuthenticateConnection(acert, user, password,
+ address)
@exposed
def GetDecisionList(self, address, mode):