diff options
author | Narayan Desai <desai@mcs.anl.gov> | 2006-01-25 16:48:06 +0000 |
---|---|---|
committer | Narayan Desai <desai@mcs.anl.gov> | 2006-01-25 16:48:06 +0000 |
commit | e3759d2a2e5fdb0e0a7f7dfa4f8244fdbb3ffe92 (patch) | |
tree | 31d523f4849b2a3232f92c2142cdd35b96beb5e1 /src | |
parent | edca0b698637c3fd0a70af7e4752a46afca938d3 (diff) | |
download | bcfg2-e3759d2a2e5fdb0e0a7f7dfa4f8244fdbb3ffe92.tar.gz bcfg2-e3759d2a2e5fdb0e0a7f7dfa4f8244fdbb3ffe92.tar.bz2 bcfg2-e3759d2a2e5fdb0e0a7f7dfa4f8244fdbb3ffe92.zip |
Introduce the new logging infrastructure and convert the server (and bcfg2-info) over to using it
git-svn-id: https://svn.mcs.anl.gov/repos/bcfg/trunk/bcfg2@1717 ce84e21b-d406-0410-9b95-82705330c041
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/Logging.py | 87 | ||||
-rw-r--r-- | src/lib/Server/Component.py | 88 | ||||
-rw-r--r-- | src/lib/Server/Core.py | 79 | ||||
-rw-r--r-- | src/lib/Server/Metadata.py | 31 | ||||
-rw-r--r-- | src/lib/Server/Plugin.py | 37 | ||||
-rw-r--r-- | src/lib/Server/Plugins/Account.py | 12 | ||||
-rw-r--r-- | src/lib/Server/Plugins/Base.py | 2 | ||||
-rw-r--r-- | src/lib/Server/Plugins/Bundler.py | 10 | ||||
-rw-r--r-- | src/lib/Server/Plugins/Cfg.py | 62 | ||||
-rw-r--r-- | src/lib/Server/Plugins/Chiba.py | 21 | ||||
-rw-r--r-- | src/lib/Server/Plugins/Hostbase.py | 15 | ||||
-rw-r--r-- | src/lib/Server/Plugins/Pkgmgr.py | 8 | ||||
-rw-r--r-- | src/lib/Server/Plugins/SSHbase.py | 35 | ||||
-rw-r--r-- | src/lib/Server/Plugins/TCheetah.py | 10 | ||||
-rw-r--r-- | src/lib/Server/Statistics.py | 9 | ||||
-rw-r--r-- | src/lib/__init__.py | 2 | ||||
-rwxr-xr-x | src/sbin/bcfg2-info | 26 | ||||
-rwxr-xr-x | src/sbin/bcfg2-server | 116 |
18 files changed, 326 insertions, 324 deletions
diff --git a/src/lib/Logging.py b/src/lib/Logging.py new file mode 100644 index 000000000..992989ec2 --- /dev/null +++ b/src/lib/Logging.py @@ -0,0 +1,87 @@ +'''Bcfg2 logging support''' +__revision__ = '$Revision: $' + +import copy, fcntl, logging, logging.handlers, math, struct, termios, types + +class TermiosFormatter(logging.Formatter): + '''The termios formatter displays output in a terminal-sensitive fashion''' + + def __init__(self, fmt=None, datefmt=None): + logging.Formatter.__init__(self, fmt, datefmt) + # now get termios info + try: + self.height, self.width = struct.unpack('hhhh', + fcntl.ioctl(0, termios.TIOCGWINSZ, + "\000"*8))[0:2] + if self.height == 0 or self.width == 0: + self.height, self.width = (25, 80) + except: + self.height, self.width = (25, 80) + + def format(self, record): + '''format a record for display''' + returns = [] + line_len = self.width - len(record.name) - 2 + if type(record.msg) in types.StringTypes: + for line in record.msg.split('\n'): + if len(line) <= line_len: + returns.append("%s: %s" % (record.name, line)) + else: + inner_lines = int(math.floor(float(len(line)) / line_len))+1 + for i in xrange(inner_lines): + returns.append("%s: %s" % (record.name, line[i*line_len:(i+1)*line_len])) + elif type(record.msg) == types.ListType: + record.msg.sort() + msgwidth = self.width - len(record.name) - 2 + columnWidth = max([len(item) for item in record.msg]) + columns = int(math.floor(float(msgwidth) / (columnWidth+2))) + lines = int(math.ceil(float(len(record.msg)) / columns)) + for lineNumber in xrange(lines): + indices = [idx for idx in [(colNum * lines) + lineNumber + for colNum in range(columns)] if idx < len(record.msg)] + format = record.name + ':' + (len(indices) * (" %%-%ds " % columnWidth)) + returns.append(format % tuple([record.msg[idx] for idx in indices])) + else: + # got unsupported type + returns.append(record.name + ':' + str(record.msg)) + if record.exc_info: + returns.append(self.formatException(record.exc_info)) + return '\n'.join(returns) + +class FragmentingSysLogHandler(logging.handlers.SysLogHandler): + '''This handler fragments messages into chunks smaller than 250 characters''' + + def emit(self, record): + '''chunk and deliver records''' + if str(record.msg) > 250: + start = 0 + msgdata = str(record.msg) + while start < len(msgdata): + newrec = copy.deepcopy(record) + newrec.msg = msgdata[start:start+250] + logging.handlers.SysLogHandler.emit(self, newrec) + start += 250 + else: + logging.handlers.SysLogHandler.emit(self, newrec) + +def setup_logging(to_console=True, to_syslog=True, level=0): + '''setup logging for bcfg2 software''' + if hasattr(logging, 'enabled'): + return + console = logging.StreamHandler() + console.setLevel(logging.DEBUG) + # tell the handler to use this format + console.setFormatter(TermiosFormatter()) + syslog = FragmentingSysLogHandler('/dev/log', 'local0') + syslog.setLevel(logging.DEBUG) + syslog.setFormatter(logging.Formatter('%(name)s[%(process)d]: %(message)s')) + # add the handler to the root logger + if to_console: + logging.root.addHandler(console) + if to_syslog: + logging.root.addHandler(syslog) + logging.root.level = level + logging.enabled = True + + + diff --git a/src/lib/Server/Component.py b/src/lib/Server/Component.py index 5c19c3bdd..01b4e1b0a 100644 --- a/src/lib/Server/Component.py +++ b/src/lib/Server/Component.py @@ -1,25 +1,17 @@ '''Cobalt component base classes''' __revision__ = '$Revision$' -from ConfigParser import ConfigParser, NoOptionError -from cPickle import loads, dumps from M2Crypto import SSL -from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler -from socket import gethostname -from sys import exc_info -import sys -from syslog import openlog, syslog, LOG_INFO, LOG_ERR, LOG_LOCAL0 -from traceback import extract_tb -from xmlrpclib import dumps, loads, Fault -from urlparse import urlparse -try: - from SimpleXMLRPCServer import SimpleXMLRPCDispatcher -except ImportError: - SimpleXMLRPCDispatcher = object +import cPickle, logging, socket, urlparse, xmlrpclib, ConfigParser, SimpleXMLRPCServer -class CobaltXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): +class CobaltXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): '''CobaltXMLRPCRequestHandler takes care of ssl xmlrpc requests''' + def __init__(self, request, client_address, server): + SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.__init__(self, + request, client_address, server) + self.logger = logging.getLogger('Bcfg2.Server.Handler') + def finish(self): '''Finish HTTPS connections properly''' self.request.set_shutdown(SSL.SSL_RECEIVED_SHUTDOWN | SSL.SSL_SENT_SHUTDOWN) @@ -33,12 +25,7 @@ class CobaltXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): response = self.server._cobalt_marshalled_dispatch(data, self.client_address) except: # This should only happen if the module is buggy # internal error, report as HTTP server error - (trace, val, trb) = exc_info() - syslog(LOG_ERR, "Unexpected failure in handler") - for line in extract_tb(trb): - syslog(LOG_ERR, ' File "%s", line %i, in %s\n %s\n' % line) - syslog(LOG_ERR, "%s: %s\n"%(trace, val)) - del trace, val, trb + self.logger.error("Unexpected failure in handler", exc_info=1) self.send_response(500) self.end_headers() else: @@ -54,7 +41,7 @@ class CobaltXMLRPCRequestHandler(SimpleXMLRPCRequestHandler): self.connection.shutdown(1) class Component(SSL.SSLServer, - SimpleXMLRPCDispatcher): + SimpleXMLRPCServer.SimpleXMLRPCDispatcher): """Cobalt component providing XML-RPC access""" __name__ = 'Component' __implementation__ = 'Generic' @@ -63,8 +50,8 @@ class Component(SSL.SSLServer, def __init__(self, setup): # need to get addr self.setup = setup - self.cfile = ConfigParser() - openlog(self.__implementation__, 0, LOG_LOCAL0) + self.cfile = ConfigParser.ConfigParser() + self.logger = logging.getLogger('Bcfg2.Server') if setup['configfile']: cfilename = setup['configfile'] else: @@ -80,16 +67,16 @@ class Component(SSL.SSLServer, if self.cfile._sections['components'].has_key(self.__name__): self.static = True - location = urlparse(self.cfile.get('components', self.__name__))[1].split(':') + location = urlparse.urlparse(self.cfile.get('components', self.__name__))[1].split(':') location = (location[0], int(location[1])) else: - location = (gethostname(), 0) + location = (socket.gethostname(), 0) self.password = self.cfile.get('communication', 'password') sslctx = SSL.Context('sslv23') try: keyfile = self.cfile.get('communication', 'key') - except NoOptionError: + except ConfigParser.NoOptionError: print "No key specified in cobalt.conf" raise SystemExit, 1 sslctx.load_cert_chain(keyfile) @@ -102,10 +89,10 @@ class Component(SSL.SSLServer, #sslctx.set_tmp_dh('dh1024.pem') self.logRequests = 0 # setup unhandled request syslog handling - SimpleXMLRPCDispatcher.__init__(self) + SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self) SSL.SSLServer.__init__(self, location, CobaltXMLRPCRequestHandler, sslctx) self.port = self.socket.socket.getsockname()[1] - syslog(LOG_INFO, "Bound to port %s" % self.port) + self.logger.info("Bound to port %s" % self.port) self.funcs.update({'HandleEvents':self.HandleEvents, 'system.listMethods':self.system_listMethods}) @@ -121,41 +108,34 @@ class Component(SSL.SSLServer, """Decode and dispatch XMLRPC requests. Overloaded to pass through client address information """ - rawparams, method = loads(data) + rawparams, method = xmlrpclib.loads(data) if len(rawparams) < 2: - syslog(LOG_ERR, "No authentication included with request from %s" % address[0]) - return dumps(Fault(2, "No Authentication Info")) + self.logger.error("No authentication included with request from %s" % address[0]) + return xmlrpclib.dumps(xmlrpclib.Fault(2, "No Authentication Info")) user = rawparams[0] password = rawparams[1] params = rawparams[2:] # check authentication if not self._authenticate_connection(method, user, password, address): - syslog(LOG_ERR, "Authentication failure from %s" % address[0]) - return dumps(Fault(3, "Authentication Failure")) + self.logger.error("Authentication failure from %s" % address[0]) + return xmlrpclib.dumps(xmlrpclib.Fault(3, "Authentication Failure")) # generate response try: # all handlers must take address as the first argument response = self._dispatch(method, (address, ) + params) # wrap response in a singleton tuple response = (response,) - response = dumps(response, methodresponse=1) - except Fault, fault: - response = dumps(fault) - except TypeError, t: - syslog(LOG_ERR, "Client %s called function %s with wrong argument count" % + response = xmlrpclib.dumps(response, methodresponse=1) + except xmlrpclib.Fault, fault: + response = xmlrpclib.dumps(fault) + except TypeError, terror: + self.logger.error("Client %s called function %s with wrong argument count" % (address[0], method)) - response = dumps(Fault(4, t.args[0])) + response = xmlrpclib.dumps(xmlrpclib.Fault(4, terror.args[0])) except: - (trace, val, trb) = exc_info() - syslog(LOG_ERR, "Unexpected failure in handler") - for line in extract_tb(trb): - syslog(LOG_ERR, ' File "%s", line %i, in %s\n %s\n' % line) - syslog(LOG_ERR, "%s: %s\n"%(trace, val)) - del trace, val, trb + self.logger.error("Unexpected failure in handler", exc_info=1) # report exception back to server - response = dumps(Fault(1, - "%s:%s" % (sys.exc_type, sys.exc_value))) - + response = xmlrpclib.dumps(xmlrpclib.Fault(1, "handler failure")) return response def _authenticate_connection(self, method, user, password, address): @@ -170,22 +150,22 @@ class Component(SSL.SSLServer, try: statefile = open("/var/spool/cobalt/%s" % self.__implementation__, 'w') # need to flock here - statefile.write(dumps(savedata)) + statefile.write(cPickle.dumps(savedata)) except: - syslog(LOG_INFO, "Statefile save failed; data persistence disabled") + self.logger.info("Statefile save failed; data persistence disabled") self.__statefields__ = [] def load_state(self): '''Load fields defined in __statefields__ from /var/spool/cobalt/__implementation__''' if self.__statefields__: try: - loaddata = loads(open("/var/spool/cobalt/%s" % self.__implementation__).read()) + loaddata = cPickle.loads(open("/var/spool/cobalt/%s" % self.__implementation__).read()) except: - syslog(LOG_INFO, "Statefile load failed") + self.logger.info("Statefile load failed") return for field in self.__statefields__: setattr(self, field, loaddata[self.__statefields__.index(field)]) def system_listMethods(self, address): """get rid of the address argument and call the underlying dispatcher method""" - return SimpleXMLRPCDispatcher.system_listMethods(self) + return SimpleXMLRPCServer.SimpleXMLRPCDispatcher.system_listMethods(self) diff --git a/src/lib/Server/Core.py b/src/lib/Server/Core.py index 91da366b8..81e4188ed 100644 --- a/src/lib/Server/Core.py +++ b/src/lib/Server/Core.py @@ -1,27 +1,13 @@ '''Bcfg2.Server.Core provides the runtime support for bcfg2 modules''' __revision__ = '$Revision$' -from os import stat -from stat import ST_MODE, S_ISDIR -from sys import exc_info -from syslog import syslog, LOG_ERR, LOG_INFO -from traceback import extract_tb from time import time -from ConfigParser import ConfigParser -from lxml.etree import Element - from Bcfg2.Server.Plugin import PluginInitError, PluginExecutionError - from Bcfg2.Server.Statistics import Statistics -import Bcfg2.Server.Metadata +import logging, lxml.etree, os, stat, Bcfg2.Server.Metadata, ConfigParser -def log_failure(msg): - syslog(LOG_ERR, "Unexpected failure in %s" % (msg)) - (trace, val, trb) = exc_info() - for line in extract_tb(trb): - syslog(LOG_ERR, ' File "%s", line %i, in %s\n %s\n'%line) - syslog(LOG_ERR, "%s: %s\n"%(trace, val)) +logger = logging.getLogger('Bcfg2.Core') class CoreInitError(Exception): '''This error is raised when the core cannot be initialized''' @@ -42,8 +28,8 @@ class FamFam(object): def AddMonitor(self, path, obj): '''add a monitor to path, installing a callback to obj.HandleEvent''' - mode = stat(path)[ST_MODE] - if S_ISDIR(mode): + mode = os.stat(path)[stat.ST_MODE] + if stat.S_ISDIR(mode): handle = self.fm.monitorDirectory(path, None) #print "adding callback for directory %s to %s, handle :%s:" % ( path, obj, handle.requestID()) else: @@ -62,7 +48,7 @@ class FamFam(object): try: self.users[reqid].HandleEvent(event) except: - log_failure("handling event for file %s" % (event.filename)) + logger.error("handling event for file %s" % (event.filename), exc_info=1) def Service(self): '''Handle all fam work''' @@ -94,10 +80,10 @@ class FamFam(object): try: self.users[event.requestID].HandleEvent(event) except: - log_failure("handling event for file %s" % (event.filename)) + logger.error("handling event for file %s" % (event.filename), exc_info=1) end = time() - syslog(LOG_INFO, "Processed %s fam events in %03.03f seconds. %s coalesced" % - (count, (end - start), collapsed)) + logger.info("Processed %s fam events in %03.03f seconds. %s coalesced" % + (count, (end - start), collapsed)) class GaminEvent(object): '''This class provides an event analogous to python-fam events based on gamin sources''' @@ -134,8 +120,8 @@ class GaminFam(object): '''add a monitor to path, installing a callback to obj.HandleEvent''' handle = self.counter self.counter += 1 - mode = stat(path)[ST_MODE] - if S_ISDIR(mode): + mode = os.stat(path)[stat.ST_MODE] + if stat.S_ISDIR(mode): self.mon.watch_directory(path, self.queue, handle) #print "adding callback for directory %s to %s, handle :%s:" % ( path, obj, handle.requestID()) else: @@ -173,12 +159,13 @@ class GaminFam(object): try: self.handles[event.requestID].HandleEvent(event) except: - log_failure("handling of gamin event for %s" % (event.filename)) + logger.error("error in handling of gamin event for %s" % (event.filename), exc_info=1) else: - syslog(LOG_INFO, "Got event for unexpected id %s, file %s" % (event.requestID, event.filename)) + logger.info("Got event for unexpected id %s, file %s" % + (event.requestID, event.filename)) end = time() - syslog(LOG_INFO, "Processed %s gamin events in %03.03f seconds. %s collapsed" % - (count, (end - start), collapsed)) + logger.info("Processed %s gamin events in %03.03f seconds. %s collapsed" % + (count, (end - start), collapsed)) try: from gamin import WatchMonitor, GAMCreated, GAMExists, GAMEndExist, GAMChanged, GAMDeleted @@ -196,7 +183,7 @@ class Core(object): '''The Core object is the container for all Bcfg2 Server logic, and modules''' def __init__(self, setup, configfile): object.__init__(self) - cfile = ConfigParser() + cfile = ConfigParser.ConfigParser() cfile.read([configfile]) self.datastore = cfile.get('server','repository') try: @@ -227,26 +214,26 @@ class Core(object): mod = getattr(__import__("Bcfg2.Server.Plugins.%s" % (plugin)).Server.Plugins, plugin) except ImportError: - syslog(LOG_ERR, "Failed to load plugin %s" % (plugin)) + logger.error("Failed to load plugin %s" % (plugin)) continue struct = getattr(mod, plugin) try: self.plugins[plugin] = struct(self, self.datastore) except PluginInitError: - syslog(LOG_ERR, "Failed to instantiate plugin %s" % (plugin)) + logger.error("Failed to instantiate plugin %s" % (plugin)) except: - log_failure("Unexpected initiantiation failure for plugin %s" % (plugin)) + logger.error("Unexpected initiantiation failure for plugin %s" % (plugin), exc_info=1) for plugin in structures: if self.plugins.has_key(plugin): self.structures.append(self.plugins[plugin]) else: - syslog(LOG_ERR, "Plugin %s not loaded. Not enabled as a Structure" % (plugin)) + logger.error("Plugin %s not loaded. Not enabled as a Structure" % (plugin)) for plugin in generators: if self.plugins.has_key(plugin): self.generators.append(self.plugins[plugin]) else: - syslog(LOG_ERR, "Plugin %s not loaded. Not enabled as a Generator" % (plugin)) + logger.error("Plugin %s not loaded. Not enabled as a Generator" % (plugin)) def GetStructures(self, metadata): '''Get all structures for client specified by metadata''' @@ -259,7 +246,7 @@ class Core(object): try: self.Bind(entry, metadata) except PluginExecutionError: - syslog(LOG_ERR, "Failed to bind entry: %s %s" % (entry.tag, entry.get('name'))) + logger.error("Failed to bind entry: %s %s" % (entry.tag, entry.get('name'))) def Bind(self, entry, metadata): '''Bind an entry using the appropriate generator''' @@ -269,34 +256,34 @@ class Core(object): return glist[0].Entries[entry.tag][entry.get('name')](entry, metadata) elif len(glist) > 1: generators = ", ".join([gen.__name__ for gen in glist]) - syslog(LOG_ERR, "%s %s served by multiple generators: %s" % (entry.tag, - entry.get('name'), generators)) + logger.error("%s %s served by multiple generators: %s" % (entry.tag, + entry.get('name'), generators)) raise PluginExecutionError, (entry.tag, entry.get('name')) def BuildConfiguration(self, client): '''Build Configuration for client''' start = time() - config = Element("Configuration", version='2.0') + config = lxml.etree.Element("Configuration", version='2.0') try: meta = self.metadata.get_metadata(client) except Bcfg2.Server.Metadata.MetadataConsistencyError: - syslog(LOG_ERR, "Metadata consistency error for client %s" % client) - return Element("error", type='metadata error') + logger.error("Metadata consistency error for client %s" % client) + return lxml.etree.Element("error", type='metadata error') config.set('toolset', meta.toolset) try: structures = self.GetStructures(meta) except: - log_failure("GetStructures") - return Element("error", type='structure error') + logger.error("error in GetStructures", exc_info=1) + return lxml.etree.Element("error", type='structure error') for astruct in structures: try: self.BindStructure(astruct, meta) config.append(astruct) except: - log_failure("BindStructure") - syslog(LOG_INFO, "Generated config for %s in %s seconds"%(client, time() - start)) + logger.error("error in BindStructure", exc_info=1) + logger.info("Generated config for %s in %s seconds"%(client, time() - start)) return config def Service(self): @@ -305,9 +292,9 @@ class Core(object): try: self.fam.HandleEvent() except: - log_failure("FamEvent") + logger.error("error in FamEvent", exc_info=1) try: self.stats.WriteBack() except: - log_failure("Statistics") + logger.error("error in Statistics", exc_info=1) diff --git a/src/lib/Server/Metadata.py b/src/lib/Server/Metadata.py index ecf636476..f62cef65a 100644 --- a/src/lib/Server/Metadata.py +++ b/src/lib/Server/Metadata.py @@ -1,9 +1,7 @@ '''This file stores persistent metadata for the BCFG Configuration Repository''' __revision__ = '$Revision$' -from syslog import syslog, LOG_ERR, LOG_INFO - -import lxml.etree, os, time, threading +import logging, lxml.etree, os, time class MetadataConsistencyError(Exception): '''This error gets raised when metadata is internally inconsistent''' @@ -41,6 +39,7 @@ class Metadata: self.categories = {} self.clientdata = None self.default = None + self.logger = logging.getLogger('Bcfg2.Server.Metadata') def HandleEvent(self, event): '''Handle update events for data files''' @@ -52,7 +51,7 @@ class Metadata: try: xdata = lxml.etree.parse("%s/%s" % (self.data, filename)) except lxml.etree.XMLSyntaxError: - syslog(LOG_ERR, 'Metadata: Failed to parse %s' % (filename)) + self.logger.error('Failed to parse %s' % (filename)) return if filename == 'clients.xml': self.clients = {} @@ -106,9 +105,9 @@ class Metadata: real = self.groups.keys() for client in self.clients.keys(): if self.clients[client] not in real or self.clients[client] not in self.profiles: - syslog(LOG_ERR, "Metadata: Client %s set as nonexistant or incomplete group %s" \ - % (client, self.clients[client])) - syslog(LOG_ERR, "Metadata: Removing client mapping for %s" % (client)) + self.logger.error("Client %s set as nonexistant or incomplete group %s" \ + % (client, self.clients[client])) + self.logger.error("Removing client mapping for %s" % (client)) del self.clients[client] def set_group(self, client, group): @@ -116,12 +115,10 @@ class Metadata: if False in self.states.values(): raise MetadataRuntimeError if group not in self.public: - syslog(LOG_ERR, "Metadata: Failed to set client %s to private group %s" % (client, - group)) + self.logger.error("Failed to set client %s to private group %s" % (client, group)) raise MetadataConsistencyError if self.clients.has_key(client): - syslog(LOG_INFO, "Metadata: Changing %s group from %s to %s" % (client, - self.clients[client], group)) + self.logger.info("Changing %s group from %s to %s" % (client, self.clients[client], group)) cli = self.clientdata.xpath('/Clients/Client[@name="%s"]' % (client)) cli[0].set('group', group) else: @@ -134,7 +131,7 @@ class Metadata: try: datafile = open("%s/%s" % (self.data, 'clients.xml'), 'w') except IOError: - syslog(LOG_ERR, "Metadata: Failed to write clients.xml") + self.logger.error("Failed to write clients.xml") raise MetadataRuntimeError datafile.write(lxml.etree.tostring(self.clientdata)) datafile.close() @@ -145,10 +142,10 @@ class Metadata: if len(tgroups) == 1: return tgroups[0] elif len(tgroups) == 0: - syslog(LOG_ERR, "Metadata: Couldn't find toolset for client %s" % (client)) + self.logger.error("Couldn't find toolset for client %s" % (client)) raise MetadataConsistencyError else: - syslog(LOG_ERR, "Metadata: Got goofy toolset result for client %s" % (client)) + self.logger.error("Got goofy toolset result for client %s" % (client)) raise MetadataConsistencyError def get_config_template(self, client): @@ -163,14 +160,14 @@ class Metadata: [bundles, groups] = self.groups[self.clients[client]] else: if self.default == None: - syslog(LOG_ERR, "Cannot set group for client %s; no default group set" % (client)) + self.logger.error("Cannot set group for client %s; no default group set" % (client)) raise MetadataConsistencyError [bundles, groups] = self.groups[self.default] toolinfo = [self.toolsets[group] for group in groups if self.toolsets.has_key(group)] if len(toolinfo) > 1: - syslog(LOG_ERR, "Metadata: Found multiple toolsets for client %s; choosing one" % (client)) + self.logger.error("Found multiple toolsets for client %s; choosing one" % (client)) elif len(toolinfo) == 0: - syslog(LOG_ERR, "Metadata: Cannot determine toolset for client %s" % (client)) + self.logger.error("Cannot determine toolset for client %s" % (client)) raise MetadataConsistencyError toolset = toolinfo[0] return ClientMetadata(client, groups, bundles, toolset) diff --git a/src/lib/Server/Plugin.py b/src/lib/Server/Plugin.py index 890084c98..be4d7ba23 100644 --- a/src/lib/Server/Plugin.py +++ b/src/lib/Server/Plugin.py @@ -1,13 +1,12 @@ '''This module provides the baseclass for Bcfg2 Server Plugins''' __revision__ = '$Revision$' -import lxml.etree -import os -import stat -import syslog +import logging, lxml.etree, os, stat from lxml.etree import XML, XMLSyntaxError +logger = logging.getLogger('Bcfg2.Plugin') + class PluginInitError(Exception): '''Error raised in cases of Plugin initialization errors''' pass @@ -38,10 +37,7 @@ class Plugin(object): self.Entries = {} self.core = core self.data = "%s/%s" % (datastore, self.__name__) - - def LogError(self, msg): - '''Log error message tagged with Plugin name''' - syslog.syslog(syslog.LOG_ERR, "%s: %s" % (self.__name__, msg)) + self.logger = logging.getLogger('Bcfg2.Plugins.%s' % (self.__name__)) def BuildStructures(self, metadata): '''Build a set of structures tailored to the client metadata''' @@ -77,13 +73,13 @@ class FileBacked(object): try: self.mtime = os.stat(self.name)[stat.ST_MTIME] except OSError: - syslog.syslog(syslog.LOG_ERR, "Failed to stat file %s" % (self.name)) + logger.error("Failed to stat file %s" % (self.name)) try: self.data = file(self.name).read() self.Index() except IOError: - syslog.syslog(syslog.LOG_ERR, "Failed to read file %s" % (self.name)) + logger.error("Failed to read file %s" % (self.name)) def Index(self): '''Update local data structures based on current file state''' @@ -110,9 +106,9 @@ class DirectoryBacked(object): def AddEntry(self, name): '''Add new entry to data structures upon file creation''' if name == '': - syslog.syslog(syslog.LOG_INFO, "got add for empty name") + logger.info("got add for empty name") elif self.entries.has_key(name): - syslog.syslog(syslog.LOG_INFO, "got multiple adds for %s" % name) + logger.info("got multiple adds for %s" % name) else: if ((name[-1] == '~') or (name[:2] == '.#') or (name[-4:] == '.swp') or (name in ['SCCS', '.svn'])): return @@ -123,7 +119,7 @@ class DirectoryBacked(object): '''Propagate fam events to underlying objects''' action = event.code2str() if event.filename == '': - syslog.syslog(syslog.LOG_INFO, "Got event for blank filename") + logger.info("Got event for blank filename") return if action == 'exists': if event.filename != self.name: @@ -155,7 +151,7 @@ class XMLFileBacked(FileBacked): try: xdata = XML(self.data) except XMLSyntaxError: - syslog.syslog(syslog.LOG_ERR, "Failed to parse %s"%(self.name)) + logger.error("Failed to parse %s"%(self.name)) return self.label = xdata.attrib[self.__identifier__] self.entries = xdata.getchildren() @@ -180,7 +176,7 @@ class StructFile(XMLFileBacked): try: xdata = lxml.etree.XML(self.data) except lxml.etree.XMLSyntaxError: - syslog.syslog(syslog.LOG_ERR, "Failed to parse file %s" % self.name) + logger.error("Failed to parse file %s" % self.name) return self.fragments = {} work = {lambda x:True: xdata.getchildren()} @@ -255,8 +251,7 @@ class XMLSrc(XMLFileBacked): if self.cache == None or self.cache[0] != metadata: cache = (metadata, {}) if self.pnode == None: - syslog.syslog(syslog.LOG_ERR, - "Cache method called early for %s; forcing data load" % (self.name)) + logger.error("Cache method called early for %s; forcing data load" % (self.name)) self.HandleEvent() return self.pnode.Match(metadata, cache[1]) @@ -274,7 +269,7 @@ class XMLPrioDir(Plugin, DirectoryBacked): try: DirectoryBacked.__init__(self, self.data, self.core.fam) except OSError: - self.LogError("Failed to load %s indices" % (self.__element__.lower())) + self.logger.error("Failed to load %s indices" % (self.__element__.lower())) raise PluginInitError def HandleEvent(self, event): @@ -289,7 +284,7 @@ class XMLPrioDir(Plugin, DirectoryBacked): [src.Cache(metadata) for src in self.entries.values()] name = entry.get('name') if not src.cache: - self.LogError("Called before data loaded") + self.logger.error("Called before data loaded") raise PluginExecutionError matching = [src for src in self.entries.values() if src.cache[1].has_key(name)] @@ -300,8 +295,8 @@ class XMLPrioDir(Plugin, DirectoryBacked): else: prio = [int(src.priority) for src in matching] if prio.count(max(prio)) > 1: - self.LogError("Found multiple %s sources with same priority for %s, pkg %s" % - (self.__element__.lower(), metadata.hostname, entry.get('name'))) + self.logger.error("Found multiple %s sources with same priority for %s, pkg %s" % + (self.__element__.lower(), metadata.hostname, entry.get('name'))) raise PluginExecutionError index = prio.index(max(prio)) diff --git a/src/lib/Server/Plugins/Account.py b/src/lib/Server/Plugins/Account.py index 05174486d..076afa032 100644 --- a/src/lib/Server/Plugins/Account.py +++ b/src/lib/Server/Plugins/Account.py @@ -1,9 +1,9 @@ '''This handles authentication setup''' __revision__ = '$Revision$' -from Bcfg2.Server.Plugin import Plugin, PluginInitError, DirectoryBacked +import Bcfg2.Server.Plugin -class Account(Plugin): +class Account(Bcfg2.Server.Plugin.Plugin): '''This module generates account config files, based on an internal data repo: static.(passwd|group|limits.conf) -> static entries @@ -17,16 +17,16 @@ class Account(Plugin): __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): - Plugin.__init__(self, core, datastore) + Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) self.Entries = {'ConfigFile':{'/etc/passwd':self.from_yp_cb, '/etc/group':self.from_yp_cb, '/etc/security/limits.conf':self.gen_limits_cb, '/root/.ssh/authorized_keys':self.gen_root_keys_cb}} try: - self.repository = DirectoryBacked(self.data, self.core.fam) + self.repository = Bcfg2.Server.Plugin.DirectoryBacked(self.data, self.core.fam) except: - self.LogError("Failed to load repos: %s, %s" % (self.data, "%s/ssh" % (self.data))) - raise PluginInitError + self.logger.error("Failed to load repos: %s, %s" % (self.data, "%s/ssh" % (self.data))) + raise Bcfg2.Server.Plugin.PluginInitError def from_yp_cb(self, entry, metadata): '''Build password file from cached yp data''' diff --git a/src/lib/Server/Plugins/Base.py b/src/lib/Server/Plugins/Base.py index 3be30bc6a..5c32eb15a 100644 --- a/src/lib/Server/Plugins/Base.py +++ b/src/lib/Server/Plugins/Base.py @@ -19,7 +19,7 @@ class Base(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.DirectoryBacked): try: Bcfg2.Server.Plugin.DirectoryBacked.__init__(self, self.data, self.core.fam) except OSError: - self.LogError("Failed to load Base repository") + self.logger.error("Failed to load Base repository") raise Bcfg2.Server.Plugin.PluginInitError def BuildStructures(self, metadata): diff --git a/src/lib/Server/Plugins/Bundler.py b/src/lib/Server/Plugins/Bundler.py index cbbb6c671..59b0dead4 100644 --- a/src/lib/Server/Plugins/Bundler.py +++ b/src/lib/Server/Plugins/Bundler.py @@ -1,9 +1,7 @@ '''This provides bundle clauses with translation functionality''' __revision__ = '$Revision$' -import Bcfg2.Server.Plugin -import copy -import lxml.etree +import copy, lxml.etree, Bcfg2.Server.Plugin class Bundler(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.DirectoryBacked): '''The bundler creates dependent clauses based on the bundle/translation scheme from bcfg1''' @@ -17,7 +15,7 @@ class Bundler(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.DirectoryBacked): try: Bcfg2.Server.Plugin.DirectoryBacked.__init__(self, self.data, self.core.fam) except OSError: - self.LogError("Failed to load Bundle repository") + self.logger.error("Failed to load Bundle repository") raise Bcfg2.Server.Plugin.PluginInitError def BuildStructures(self, metadata): @@ -25,8 +23,8 @@ class Bundler(Bcfg2.Server.Plugin.Plugin, Bcfg2.Server.Plugin.DirectoryBacked): bundleset = [] for bundlename in metadata.bundles: if not self.entries.has_key("%s.xml"%(bundlename)): - self.LogError("Client %s requested nonexistent bundle %s" % \ - (metadata.hostname, bundlename)) + self.logger.error("Client %s requested nonexistent bundle %s" % \ + (metadata.hostname, bundlename)) continue bundle = lxml.etree.Element('Bundle', name=bundlename) [bundle.append(copy.deepcopy(item)) diff --git a/src/lib/Server/Plugins/Cfg.py b/src/lib/Server/Plugins/Cfg.py index b325144e5..157243e50 100644 --- a/src/lib/Server/Plugins/Cfg.py +++ b/src/lib/Server/Plugins/Cfg.py @@ -1,29 +1,23 @@ '''This module implements a config file repository''' __revision__ = '$Revision$' -from os import stat -from re import compile as regcompile -from stat import S_ISDIR, ST_MODE -from syslog import syslog, LOG_INFO, LOG_ERR +import binascii, exceptions, logging, os, re, stat, Bcfg2.Server.Plugin -from Bcfg2.Server.Plugin import Plugin, PluginExecutionError, FileBacked +logger = logging.getLogger('Bcfg2.Plugins.Cfg') -import binascii -import exceptions - -specific = regcompile('(.*/)(?P<filename>[\S\-.]+)\.((H_(?P<hostname>\S+))|' + +specific = re.compile('(.*/)(?P<filename>[\S\-.]+)\.((H_(?P<hostname>\S+))|' + '(G(?P<prio>\d+)_(?P<group>\S+)))$') class SpecificityError(Exception): '''Thrown in case of filename parse failure''' pass -class FileEntry(FileBacked): +class FileEntry(Bcfg2.Server.Plugin.FileBacked): '''The File Entry class pertains to the config files contained in a particular directory. This includes :info, all base files and deltas''' def __init__(self, myid, name): - FileBacked.__init__(self, name) + Bcfg2.Server.Plugin.FileBacked.__init__(self, name) self.name = name self.identity = myid self.all = False @@ -39,7 +33,7 @@ class FileEntry(FileBacked): else: data = specific.match(name) if not data: - syslog(LOG_ERR, "Cfg: Failed to match %s" % name) + logger.error("Failed to match %s" % name) raise SpecificityError if data.group('hostname') != None: self.hostname = data.group('hostname') @@ -62,7 +56,7 @@ class FileEntry(FileBacked): return 0 else: pass - syslog(LOG_ERR, "Cfg: Critical: Ran off of the end of the world sorting %s" % (self.name)) + logger.critical("Ran off of the end of the world sorting %s" % (self.name)) def applies(self, metadata): '''Predicate if fragment matches client metadata''' @@ -75,7 +69,7 @@ class FileEntry(FileBacked): class ConfigFileEntry(object): '''ConfigFileEntry is a repository entry for a single file, containing all data for all clients.''' - info = regcompile('^owner:(\s)*(?P<owner>\w+)|group:(\s)*(?P<group>\w+)|' + + info = re.compile('^owner:(\s)*(?P<owner>\w+)|group:(\s)*(?P<group>\w+)|' + 'perms:(\s)*(?P<perms>\w+)|encoding:(\s)*(?P<encoding>\w+)|' + '(?P<paranoid>paranoid(\s)*)$') @@ -128,28 +122,28 @@ class ConfigFileEntry(object): return self.read_info() if event.filename != self.path.split('/')[-1]: if not specific.match('/' + event.filename): - syslog(LOG_INFO, 'Cfg: Suppressing event for bogus file %s' % event.filename) + logger.info('Suppressing event for bogus file %s' % event.filename) return entries = [entry for entry in self.fragments if entry.name.split('/')[-1] == event.filename] if len(entries) == 0: - syslog(LOG_ERR, "Cfg: Failed to match entry for spec %s" % (event.filename)) + logger.error("Failed to match entry for spec %s" % (event.filename)) elif len(entries) > 1: - syslog(LOG_ERR, "Cfg: Matched multiple entries for spec %s" % (event.filename)) + logger.error("Matched multiple entries for spec %s" % (event.filename)) if action == 'deleted': - syslog(LOG_INFO, "Cfg: Removing entry %s" % event.filename) + logger.info("Removing entry %s" % event.filename) for entry in entries: - syslog(LOG_INFO, "Cfg: Removing entry %s" % (entry.name)) + logger.info("Removing entry %s" % (entry.name)) self.fragments.remove(entry) self.fragments.sort() - syslog(LOG_INFO, "Cfg: Entry deletion completed") + logger.info("Entry deletion completed") elif action in ['changed', 'exists', 'created']: [entry.HandleEvent(event) for entry in entries] else: - syslog(LOG_ERR, "Cfg: Unhandled Action %s for file %s" % (action, event.filename)) + logger.error("Unhandled Action %s for file %s" % (action, event.filename)) def GetConfigFile(self, entry, metadata): '''Fetch config file from repository''' @@ -159,8 +153,8 @@ class ConfigFileEntry(object): try: basefile = [bfile for bfile in self.fragments if bfile.applies(metadata) and not bfile.op][-1] except IndexError: - syslog(LOG_ERR, "Cfg: Failed to locate basefile for %s" % name) - raise PluginExecutionError, ('basefile', name) + logger.error("Failed to locate basefile for %s" % name) + raise Bcfg2.Server.Plugin.PluginExecutionError, ('basefile', name) filedata += basefile.data for delta in [delta for delta in self.fragments if delta.applies(metadata) and delta.op]: @@ -186,17 +180,17 @@ class ConfigFileEntry(object): try: entry.text = filedata except exceptions.AttributeError: - syslog(LOG_ERR, "Failed to marshall file %s. Mark it as base64" % (entry.get('name'))) + logger.error("Failed to marshall file %s. Mark it as base64" % (entry.get('name'))) -class Cfg(Plugin): +class Cfg(Bcfg2.Server.Plugin.Plugin): '''This generator in the configuration file repository for bcfg2''' __name__ = 'Cfg' __version__ = '$Id$' __author__ = 'bcfg-dev@mcs.anl.gov' - tempfile = regcompile("^.*~$|^.*\.swp") + tempfile = re.compile("^.*~$|^.*\.swp") def __init__(self, core, datastore): - Plugin.__init__(self, core, datastore) + Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) self.entries = {} self.Entries = {'ConfigFile':{}} self.famID = {} @@ -209,9 +203,9 @@ class Cfg(Plugin): '''Add new directory to FAM structures''' if name not in self.directories: try: - stat(name) + os.stat(name) except OSError: - self.LogError("Failed to open directory %s" % (name)) + logger.error("Failed to open directory %s" % (name)) return reqid = self.core.fam.AddMonitor(name, self) self.famID[reqid] = name @@ -220,11 +214,11 @@ class Cfg(Plugin): def AddEntry(self, name, event): '''Add new entry to FAM structures''' try: - sdata = stat(name)[ST_MODE] + sdata = os.stat(name)[stat.ST_MODE] except OSError: return - if S_ISDIR(sdata): + if stat.S_ISDIR(sdata): self.AddDirectoryMonitor(name) else: # file entries shouldn't contain path-to-repo @@ -240,7 +234,7 @@ class Cfg(Plugin): '''Handle FAM updates''' action = event.code2str() if self.tempfile.match(event.filename): - syslog(LOG_INFO, "Cfg: Suppressed event for file %s" % event.filename) + logger.info("Suppressed event for file %s" % event.filename) return if event.filename[0] != '/': filename = "%s/%s" % (self.famID[event.requestID], event.filename) @@ -258,11 +252,11 @@ class Cfg(Plugin): if filename != self.data: self.AddEntry(filename, event) else: - self.LogError("Ignoring event for %s"%(configfile)) + logger.error("Ignoring event for %s"%(configfile)) elif action == 'deleted': if self.entries.has_key(configfile): self.entries[configfile].HandleEvent(event) elif action in ['exists', 'endExist']: pass else: - self.LogError("Got unknown event %s %s:%s" % (action, event.requestID, event.filename)) + logger.error("Got unknown event %s %s:%s" % (action, event.requestID, event.filename)) diff --git a/src/lib/Server/Plugins/Chiba.py b/src/lib/Server/Plugins/Chiba.py index e74036d1b..92ad05ab5 100644 --- a/src/lib/Server/Plugins/Chiba.py +++ b/src/lib/Server/Plugins/Chiba.py @@ -1,14 +1,13 @@ '''This module configures files in a Chiba City specific way''' -__revision__ = '$Revision:$' +__revision__ = '$Revision$' -from socket import gethostbyname, gaierror -from Bcfg2.Server.Plugin import Plugin, DirectoryBacked, SingleXMLFileBacked, PluginExecutionError +import socket, Bcfg2.Server.Plugin -class ChibaConf(SingleXMLFileBacked): +class ChibaConf(Bcfg2.Server.Plugin.SingleXMLFileBacked): '''This class encapsulates all information needed for all Chiba config ops''' pass -class Chiba(Plugin): +class Chiba(Bcfg2.Server.Plugin.Plugin): '''the Chiba generator builds the following files: -> /etc/fstab -> /etc/network/interfaces @@ -20,8 +19,8 @@ class Chiba(Plugin): __author__ = 'bcfg-dev@mcs.anl.gov' def __init__(self, core, datastore): - Plugin.__init__(self, core, datastore) - self.repo = DirectoryBacked(self.data, self.core.fam) + Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) + self.repo = Bcfg2.Server.Plugin.DirectoryBacked(self.data, self.core.fam) self.Entries = {'ConfigFile': {'/etc/network/interfaces':self.build_interfaces}} def build_interfaces(self, entry, metadata): @@ -32,9 +31,9 @@ class Chiba(Plugin): try: myriname = "%s-myr.%s" % (metadata.hostname.split('.')[0], ".".join(metadata.hostname.split('.')[1:])) - myriaddr = gethostbyname(myriname) - except gaierror: - self.LogError("Failed to resolve %s"% myriname) - raise PluginExecutionError, (myriname, 'lookup') + myriaddr = socket.gethostbyname(myriname) + except socket.gaierror: + self.logger.error("Failed to resolve %s"% myriname) + raise Bcfg2.Server.Plugin.PluginExecutionError, (myriname, 'lookup') entry.text = self.repo.entries['interfaces-template'].data % myriaddr diff --git a/src/lib/Server/Plugins/Hostbase.py b/src/lib/Server/Plugins/Hostbase.py index e729030fb..96f215451 100644 --- a/src/lib/Server/Plugins/Hostbase.py +++ b/src/lib/Server/Plugins/Hostbase.py @@ -1,7 +1,6 @@ '''This file provides the Hostbase plugin. It manages dns/dhcp/nis host information''' __revision__ = '$Revision$' -from syslog import syslog, LOG_INFO from lxml.etree import XML, SubElement from Cheetah.Template import Template from Bcfg2.Server.Plugin import Plugin, PluginExecutionError, PluginInitError, DirectoryBacked @@ -9,6 +8,10 @@ from time import strftime from sets import Set import re +import logging + +logger = logging.getLogger('Bcfg2.Plugins.Hostbase') + class DataNexus(DirectoryBacked): '''DataNexus is an object that watches multiple files and handles changes in an intelligent fashion.''' @@ -23,7 +26,7 @@ class DataNexus(DirectoryBacked): action = event.code2str() if action in ['exists', 'created']: if (event.filename != self.name) and (event.filename not in self.files): - syslog(LOG_INFO, "%s:Got event for unexpected file %s" % (self.__name__, event.filename)) + logger.info("%s:Got event for unexpected file %s" % (self.__name__, event.filename)) return DirectoryBacked.HandleEvent(self, event) if action != 'endExist' and event.filename != self.name: @@ -48,7 +51,7 @@ class Hostbase(Plugin, DataNexus): DataNexus.__init__(self, datastore + '/Hostbase/data', files, self.core.fam) except: - self.LogError("Failed to load data directory") + logger.error("Failed to load data directory") raise PluginInitError self.xdata = {} self.filedata = {} @@ -96,12 +99,12 @@ class Hostbase(Plugin, DataNexus): todaydate = (strftime('%Y%m%d')) try: if todaydate == zone.get('serial')[:8]: - serial = atoi(zone.get('serial')) + 1 + serial = int(zone.get('serial')) + 1 else: - serial = atoi(todaydate) * 100 + serial = int(todaydate) * 100 return str(serial) except (KeyError): - serial = atoi(todaydate) * 100 + serial = int(todaydate) * 100 return str(serial) if self.entries.has_key(event.filename) and not self.xdata.has_key(event.filename): diff --git a/src/lib/Server/Plugins/Pkgmgr.py b/src/lib/Server/Plugins/Pkgmgr.py index e77dd99e5..2367c7c22 100644 --- a/src/lib/Server/Plugins/Pkgmgr.py +++ b/src/lib/Server/Plugins/Pkgmgr.py @@ -1,9 +1,9 @@ '''This module implements a package management scheme for all images''' __revision__ = '$Revision$' -import re -from syslog import syslog, LOG_ERR -import Bcfg2.Server.Plugin +import logging, re, Bcfg2.Server.Plugin + +logger = logging.getLogger('Bcfg2.Plugins.Pkgmgr') class PNode(Bcfg2.Server.Plugin.LNode): '''PNode has a list of packages available at a particular group intersection''' @@ -29,7 +29,7 @@ class PNode(Bcfg2.Server.Plugin.LNode): if self.splitters.has_key(pkg.get('type')): mdata = self.splitters[pkg.get('type')].match(pkg.get('file')) if not mdata: - syslog(LOG_ERR, "Pkgmgr: Failed to match pkg %s" % pkg.get('file')) + logger.error("Failed to match pkg %s" % pkg.get('file')) continue pkgname = mdata.group('name') self.contents[pkgname] = mdata.groupdict() diff --git a/src/lib/Server/Plugins/SSHbase.py b/src/lib/Server/Plugins/SSHbase.py index 6cab373b0..87e9225af 100644 --- a/src/lib/Server/Plugins/SSHbase.py +++ b/src/lib/Server/Plugins/SSHbase.py @@ -1,13 +1,9 @@ '''This module manages ssh key files for bcfg2''' __revision__ = '$Revision$' -from binascii import b2a_base64 -from os import system, popen -from socket import gethostbyname, gaierror +import binascii, os, socket, Bcfg2.Server.Plugin -from Bcfg2.Server.Plugin import Plugin, DirectoryBacked, PluginExecutionError - -class SSHbase(Plugin): +class SSHbase(Bcfg2.Server.Plugin.Plugin): '''The sshbase generator manages ssh host keys (both v1 and v2) for hosts. It also manages the ssh_known_hosts file. It can integrate host keys from other management domains and similarly @@ -35,8 +31,8 @@ class SSHbase(Plugin): "ssh_host_rsa_key.H_%s", "ssh_host_key.H_%s"] def __init__(self, core, datastore): - Plugin.__init__(self, core, datastore) - self.repository = DirectoryBacked(self.data, self.core.fam) + Bcfg2.Server.Plugin.Plugin.__init__(self, core, datastore) + self.repository = Bcfg2.Server.Plugin.DirectoryBacked(self.data, self.core.fam) try: prefix = open("%s/prefix" % (self.data)).read().strip() except IOError: @@ -65,20 +61,20 @@ class SSHbase(Plugin): else: # need to add entry try: - ipaddr = gethostbyname(client) + ipaddr = socket.gethostbyname(client) self.ipcache[client] = (ipaddr, client) return (ipaddr, client) - except gaierror: - (client) + except socket.gaierror: + pass try: - ipaddr = popen("getent hosts %s" % client).read().strip().split() + ipaddr = os.popen("getent hosts %s" % client).read().strip().split() except: ipaddr = '' if ipaddr: self.ipcache[client] = (ipaddr, client) return (ipaddr, client) - self.LogError("Failed to find IP address for %s" % client) - raise gaierror + self.logger.error("Failed to find IP address for %s" % client) + raise socket.gaierror def cache_skn(self): '''build memory cache of the ssh known hosts file''' @@ -87,7 +83,7 @@ class SSHbase(Plugin): hostname = pubkey.split('H_')[1] try: (ipaddr, fqdn) = self.get_ipcache_entry(hostname) - except gaierror: + except socket.gaierror: continue shortname = hostname.split('.')[0] self.static_skn += "%s,%s,%s %s" % (shortname, fqdn, ipaddr, @@ -114,8 +110,8 @@ class SSHbase(Plugin): if hasattr(self, 'static_skn'): del self.static_skn if not self.repository.entries.has_key(filename): - self.LogError("%s still not registered" % filename) - raise PluginExecutionError + self.logger.error("%s still not registered" % filename) + raise Bcfg2.Server.Plugin.PluginExecutionError keydata = self.repository.entries[filename].data permdata = {'owner':'root', 'group':'root'} permdata['perms'] = '0600' @@ -124,7 +120,7 @@ class SSHbase(Plugin): [entry.attrib.__setitem__(x, permdata[x]) for x in permdata] if "ssh_host_key.H_" == filename[:15]: entry.attrib['encoding'] = 'base64' - entry.text = b2a_base64(keydata) + entry.text = binascii.b2a_base64(keydata) else: entry.text = keydata @@ -143,7 +139,8 @@ class SSHbase(Plugin): fileloc = "%s/%s" % (self.data, hostkey) publoc = self.data + '/' + ".".join([hostkey.split('.')[0]]+['pub', "H_%s" % client]) temploc = "/tmp/%s" % hostkey - system('ssh-keygen -q -f %s -N "" -t %s -C root@%s < /dev/null' % (temploc, keytype, client)) + os.system('ssh-keygen -q -f %s -N "" -t %s -C root@%s < /dev/null' % + (temploc, keytype, client)) open(fileloc, 'w').write(open(temploc).read()) open(publoc, 'w').write(open("%s.pub" % temploc).read()) self.repository.AddEntry(hostkey) diff --git a/src/lib/Server/Plugins/TCheetah.py b/src/lib/Server/Plugins/TCheetah.py index 1ebdb6c94..beff8e869 100644 --- a/src/lib/Server/Plugins/TCheetah.py +++ b/src/lib/Server/Plugins/TCheetah.py @@ -2,11 +2,14 @@ __revision__ = '$Revision$' from posixpath import isdir -from syslog import syslog, LOG_ERR from Bcfg2.Server.Plugin import Plugin, PluginExecutionError, FileBacked, SingleXMLFileBacked from lxml.etree import XML, XMLSyntaxError from Cheetah.Template import Template +import logging + +logger = logging.getLogger('Bcfg2.Plugins.TCheetah') + class TemplateFile(FileBacked): '''Template file creates Cheetah template structures for the loaded file''' def __init__(self, name, properties): @@ -25,7 +28,7 @@ class TemplateFile(FileBacked): try: entry.text = str(self.template) except: - syslog(LOG_ERR, "TCheetah: Failed to template %s" % entry.get('name')) + logger.error("Failed to template %s" % entry.get('name')) raise PluginExecutionError perms = {'owner':'root', 'group':'root', 'perms':'0644'} [entry.attrib.__setitem__(key, value) for (key, value) in perms.iteritems()] @@ -38,7 +41,7 @@ class CheetahProperties(SingleXMLFileBacked): self.properties = XML(self.data) del self.data except XMLSyntaxError: - syslog(LOG_ERR, "TCheetah: Failed to parse properties") + logger.error("Failed to parse properties") class TCheetah(Plugin): '''The TCheetah generator implements a templating mechanism for configuration files''' @@ -80,7 +83,6 @@ class TCheetah(Plugin): self.entries[identifier].HandleEvent(event) self.Entries['ConfigFile'][identifier] = self.BuildEntry #except: - # syslog(LOG_ERR, "TCheetah: bad format for file %s" % identifier) elif action == 'changed': if self.entries.has_key(identifier): self.entries[identifier].HandleEvent(event) diff --git a/src/lib/Server/Statistics.py b/src/lib/Server/Statistics.py index 15f1586ef..4a86cdf35 100644 --- a/src/lib/Server/Statistics.py +++ b/src/lib/Server/Statistics.py @@ -2,9 +2,10 @@ __revision__ = '$Revision$' from lxml.etree import XML, SubElement, Element, XMLSyntaxError -from syslog import syslog, LOG_ERR from time import asctime, localtime, time +import logging + class Statistics(object): '''Manages the memory and file copy of statistics collected about client runs''' __min_write_delay__ = 30 @@ -15,6 +16,7 @@ class Statistics(object): self.dirty = 0 self.lastwrite = 0 self.ReadFromFile() + self.logger = logging.getLogger('Bcfg2.Server.Statistics') def pretty_print(self, element, level=0): '''Produce a pretty-printed text representation of element''' @@ -53,12 +55,11 @@ class Statistics(object): self.dirty = 0 #syslog(LOG_INFO, "Statistics: Read in statistics.xml") except (IOError, XMLSyntaxError): - syslog(LOG_ERR, "Statistics: Failed to parse %s"%(self.filename)) + self.logger.error("Failed to parse %s"%(self.filename)) self.element = Element('ConfigStatistics') self.WriteBack() self.dirty = 0 - def updateStats(self, xml, client): '''Updates the statistics of a current node with new data''' @@ -91,7 +92,7 @@ class Statistics(object): node.remove(elem) else: # Shouldn't be reached - syslog(LOG_ERR, "Statistics: Duplicate node entry for %s"%(client)) + self.logger.error("Duplicate node entry for %s"%(client)) # Set current time for stats newstat.set('time', asctime(localtime())) diff --git a/src/lib/__init__.py b/src/lib/__init__.py index 6fe48add9..241355fdd 100644 --- a/src/lib/__init__.py +++ b/src/lib/__init__.py @@ -1,4 +1,4 @@ '''base modules definition''' __revision__ = '$Revision$' -all = ['Server', 'Client'] +all = ['Server', 'Client', 'Logging'] diff --git a/src/sbin/bcfg2-info b/src/sbin/bcfg2-info index e325b2fd0..7479f85a1 100755 --- a/src/sbin/bcfg2-info +++ b/src/sbin/bcfg2-info @@ -2,10 +2,7 @@ '''This tool loads the Bcfg2 core into an interactive debugger''' __revision__ = '$Revision$' -from sys import argv -from time import sleep -from Bcfg2.Server.Core import Core, CoreInitError -from lxml.etree import tostring +import logging, lxml.etree, sys, time, Bcfg2.Logging, Bcfg2.Server.Core def print_tabular(rows): '''print data in tabular format''' @@ -28,7 +25,7 @@ def do_build(cmd, core): '''build client configuration''' if len(cmd) == 3: output = open(cmd[2], 'w') - output.write(tostring(core.BuildConfiguration(cmd[1]))) + output.write(lxml.etree.tostring(core.BuildConfiguration(cmd[1]))) output.close() else: print 'Usage: build <hostname> <output file>' @@ -126,29 +123,36 @@ def do_version(cmd, core): print __revision__ if __name__ == '__main__': + Bcfg2.Logging.setup_logging(to_syslog=False) + logger = logging.getLogger('bcfg2-info') dispatch = {'build': do_build, 'bundles': do_bundles, 'clients': do_clients, 'generators': do_generators, 'groups': do_groups, 'help': do_help, 'mappings': do_mappings, 'quit': do_quit, 'update': do_update, 'version': do_version} - if '-c' in argv: - cfile = argv[-1] + if '-c' in sys.argv: + cfile = sys.argv[-1] else: cfile = '/etc/bcfg2.conf' try: - bcore = Core({}, cfile) - except CoreInitError, msg: + bcore = Bcfg2.Server.Core.Core({}, cfile) + except Bcfg2.Server.Core.CoreInitError, msg: print "Core load failed because %s" % msg raise SystemExit, 1 for i in range(25): bcore.fam.Service() - sleep(0.5) + time.sleep(0.5) ucmd = get_input() while True: if ucmd[0] == 'debug': break else: if dispatch.has_key(ucmd[0]): - dispatch[ucmd[0]](ucmd, bcore) + try: + dispatch[ucmd[0]](ucmd, bcore) + except SystemExit, code: + raise SystemExit, code + except: + logger.error("command failure", exc_info=1) else: print "Unknown command %s" % ucmd[0] ucmd = get_input() diff --git a/src/sbin/bcfg2-server b/src/sbin/bcfg2-server index 2da6ff325..e3a73ab7b 100755 --- a/src/sbin/bcfg2-server +++ b/src/sbin/bcfg2-server @@ -3,22 +3,15 @@ '''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 +import getopt, logging, os, select, signal, socket, sys +import Bcfg2.Logging, Bcfg2.Server.Component, M2Crypto.SSL + +logger = logging.getLogger('bcfg2-server') def daemonize(filename): '''Do the double fork/setsession dance''' @@ -41,61 +34,33 @@ def daemonize(filename): 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) ) + '''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)) def fatal_error(message): '''Signal a fatal error''' - syslog(LOG_ERR, "Fatal error: %s" % (message)) + logger.critical("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) +def usage(message, opts, vopts, odescs, vargDescs): + logger.critical(message) + [logger.critical(" -%s\t\t\t%s" % (arg, odescs[arg])) for arg in opts] + [logger.critical(" -%s %s\t%s" % (arg, vargDescs[arg], odescs[arg])) for arg in vopts] 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) + ginfo = getopt.getopt(arglist, gstr) + except getopt.GetoptError, gerr: + usage("Usage error: %s" % gett, opt, vopt, descs, argsDescs) for (gopt, garg) in ginfo[0]: option = gopt[1:] @@ -105,15 +70,11 @@ def dgetopt(arglist, opt, vopt, descs, argDescs): ret[vopt[option]] = garg if ret["help"] == True: - print_usage(opt, vopt, descs, argDescs) - raise SystemExit, 0 + print_usage("Usage information", opt, vopt, descs, argDescs) - if ret["verbose"] == True: - verboseMode = True - return ret -class Bcfg2(Component): +class Bcfg2(Bcfg2.Server.Component.Component): """The Bcfg2 Server component providing XML-RPC access to Bcfg methods""" __name__ = 'bcfg2' __implementation__ = 'bcfg2' @@ -121,14 +82,13 @@ class Bcfg2(Component): request_queue_size = 15 def __init__(self, setup): - Component.__init__(self, setup) + Bcfg2.Server.Component.Component.__init__(self, setup) self.shut = False # set shutdown handlers for sigint and sigterm - signal(SIGINT, self.start_shutdown) - signal(SIGTERM, self.start_shutdown) + signal.signal(signal.SIGINT, self.start_shutdown) + signal.signal(signal.SIGTERM, self.start_shutdown) try: self.Core = Core(setup, setup['configfile']) - self.CoreLock = Lock() except CoreInitError, msg: fatal_error(msg) @@ -149,11 +109,11 @@ class Bcfg2(Component): famfd = self.Core.fam.fileno() while self.socket not in rsockinfo: if self.shut: - raise SSLError + raise M2Crypto.SSL.SSLError try: - rsockinfo = select([self.socket, famfd], [], [], 15)[0] - except selecterror: - raise SSLError + rsockinfo = select.select([self.socket, famfd], [], [], 15)[0] + except select.error: + raise M2Crypto.SSL.SSLError if famfd in rsockinfo: self.Core.fam.Service() @@ -179,10 +139,10 @@ class Bcfg2(Component): if self.setup['client']: return self.setup['client'] try: - return gethostbyaddr(client)[0] - except herror: + return socket.gethostbyaddr(client)[0] + except socket.herror: warning = "host resolution error for %s" % (client) - warning_error(warning) + self.logger.warning(warning) raise Fault, (5, warning) def Bcfg2GetProbes(self, address): @@ -199,11 +159,10 @@ class Bcfg2(Component): return tostring(resp) except MetadataConsistencyError: warning = 'metadata consistency error' - warning_error(warning) + self.logger.warning(warning) raise Fault, (6, warning) except: - critical_error("determining client probes") - + critical_error("error determining client probes") def Bcfg2RecvProbeData(self, address, probedata): '''Receive probe data from clients''' @@ -214,9 +173,9 @@ class Bcfg2(Component): [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'))) + self.logger.warning("Failed to locate plugin %s" % (data.get('source'))) except: - critical_error("probe data receipt") + critical_error('error in probe data receipt') return True def Bcfg2GetConfig(self, address, image=False, profile=False): @@ -228,7 +187,7 @@ class Bcfg2(Component): self.Core.metadata.set_group(client, profile) except MetadataConsistencyError: warning = 'metadata consistency error' - warning_error(warning) + self.logger.warning(warning) raise Fault, (6, warning) return tostring(self.Core.BuildConfiguration(client)) @@ -243,12 +202,12 @@ class Bcfg2(Component): # Update statistics self.Core.stats.updateStats(sdata, client) - syslog(LOG_INFO, "Client %s reported state %s" % - (client, state.attrib['state'])) + self.logger.info("Client %s reported state %s" % + (client, state.attrib['state'])) return "<ok/>" if __name__ == '__main__': - openlog("Bcfg2", 0, LOG_LOCAL0) + Bcfg2.Logging.setup_logging() options = { 'v':'verbose', 'd':'debug', @@ -275,7 +234,7 @@ if __name__ == '__main__': 'C': "<client hostname>" } - ssetup = dgetopt(argv[1:], options, doptions, + ssetup = dgetopt(sys.argv[1:], options, doptions, descriptions, argDescriptions) if ssetup['daemon']: daemonize(ssetup['daemon']) @@ -286,6 +245,5 @@ if __name__ == '__main__': try: s.serve_forever() except: - critical_error("service loop") - - syslog(LOG_INFO, "Shutting down") + critical_error('error in service loop') + logger.info("Shutting down") |