summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/lib/Logging.py85
-rw-r--r--src/lib/Server/Component.py135
-rwxr-xr-xsrc/sbin/bcfg2-server23
3 files changed, 161 insertions, 82 deletions
diff --git a/src/lib/Logging.py b/src/lib/Logging.py
index 992989ec2..70270ff23 100644
--- a/src/lib/Logging.py
+++ b/src/lib/Logging.py
@@ -1,31 +1,61 @@
'''Bcfg2 logging support'''
-__revision__ = '$Revision: $'
+__revision__ = '$Revision$'
-import copy, fcntl, logging, logging.handlers, math, struct, termios, types
+import copy, fcntl, logging, logging.handlers, lxml.etree, math, struct, sys, termios, types
+
+def print_attributes(attrib):
+ ''' Add the attributes for an element'''
+ return ' '.join(['%s="%s"' % data for data in attrib.iteritems()])
+
+def print_text(text):
+ ''' Add text to the output (which will need normalising '''
+ charmap = {'<':'&lt;', '>':'&gt;', '&':'&amp;'}
+ return ''.join([charmap.get(char, char) for char in text]) + '\n'
+
+def xml_print(element, running_indent=0, indent=4):
+ ''' Add an element and its children to the return string '''
+ if (len(element.getchildren()) == 0) and (not element.text):
+ ret = (' ' * running_indent)
+ ret += '<%s %s/>\n' % (element.tag, print_attributes(element.attrib))
+ else:
+ child_indent = running_indent + indent
+ ret = (' ' * running_indent)
+ ret += '<%s%s>\n' % (element.tag, print_attributes(element))
+ if element.text:
+ ret += (' '* child_indent) + print_text(element.text)
+ for child in element.getchildren():
+ ret += xml_print(child, child_indent, indent)
+ ret += (' ' * running_indent) + '</%s>\n' % (element.tag)
+ if element.tail:
+ ret += (' ' * child_indent) + print_text(element.tail)
+ return ret
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)
+ if sys.stdout.isatty():
+ # now get termios info
+ try:
+ self.width = struct.unpack('hhhh', fcntl.ioctl(0, termios.TIOCGWINSZ,
+ "\000"*8))[1]
+ if self.width == 0:
+ self.width = 80
+ except:
+ self.width = 80
+ else:
+ # output to a pipe
+ self.width = sys.maxint
def format(self, record):
'''format a record for display'''
returns = []
- line_len = self.width - len(record.name) - 2
+ line_len = self.width
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))
+ returns.append(line)
else:
inner_lines = int(math.floor(float(len(line)) / line_len))+1
for i in xrange(inner_lines):
@@ -41,8 +71,10 @@ class TermiosFormatter(logging.Formatter):
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]))
+ elif type(record.msg) == lxml.etree._Element:
+ returns.append(str(xml_print(record.msg)))
else:
- # got unsupported type
+ returns.append("Got unsupported type %s" % (str(type(record.msg))))
returns.append(record.name + ':' + str(record.msg))
if record.exc_info:
returns.append(self.formatException(record.exc_info))
@@ -51,28 +83,40 @@ class TermiosFormatter(logging.Formatter):
class FragmentingSysLogHandler(logging.handlers.SysLogHandler):
'''This handler fragments messages into chunks smaller than 250 characters'''
+ def __init__(self, procname, path, facility):
+ self.procname = procname
+ logging.handlers.SysLogHandler.__init__(self, path, facility)
+
def emit(self, record):
'''chunk and deliver records'''
+ record.name = self.procname
if str(record.msg) > 250:
start = 0
+ error = None
+ if record.exc_info:
+ error = record.exc_info
+ record.exc_info = None
msgdata = str(record.msg)
while start < len(msgdata):
newrec = copy.deepcopy(record)
newrec.msg = msgdata[start:start+250]
+ newrec.exc_info = error
logging.handlers.SysLogHandler.emit(self, newrec)
+ # only send the traceback once
+ error = None
start += 250
else:
logging.handlers.SysLogHandler.emit(self, newrec)
-def setup_logging(to_console=True, to_syslog=True, level=0):
+def setup_logging(procname, to_console=True, to_syslog=True, syslog_facility='local0', level=0):
'''setup logging for bcfg2 software'''
- if hasattr(logging, 'enabled'):
+ if hasattr(logging, 'already_setup'):
return
console = logging.StreamHandler()
console.setLevel(logging.DEBUG)
# tell the handler to use this format
console.setFormatter(TermiosFormatter())
- syslog = FragmentingSysLogHandler('/dev/log', 'local0')
+ syslog = FragmentingSysLogHandler(procname, '/dev/log', syslog_facility)
syslog.setLevel(logging.DEBUG)
syslog.setFormatter(logging.Formatter('%(name)s[%(process)d]: %(message)s'))
# add the handler to the root logger
@@ -80,8 +124,5 @@ def setup_logging(to_console=True, to_syslog=True, level=0):
logging.root.addHandler(console)
if to_syslog:
logging.root.addHandler(syslog)
- logging.root.level = level
- logging.enabled = True
-
-
-
+ logging.root.setLevel(level)
+ logging.already_setup = True
diff --git a/src/lib/Server/Component.py b/src/lib/Server/Component.py
index de41a2277..4a4101e6e 100644
--- a/src/lib/Server/Component.py
+++ b/src/lib/Server/Component.py
@@ -1,20 +1,15 @@
'''Cobalt component base classes'''
__revision__ = '$Revision$'
-from M2Crypto import SSL
+import atexit, logging, select, signal, socket, sys, time, urlparse, xmlrpclib, cPickle, ConfigParser
+import BaseHTTPServer, Cobalt.Proxy, OpenSSL.SSL, SimpleXMLRPCServer, SocketServer
-import cPickle, logging, socket, urlparse, xmlrpclib, ConfigParser, SimpleXMLRPCServer
+log = logging.getLogger('Component')
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)
self.request.close()
def do_POST(self):
@@ -25,7 +20,7 @@ class CobaltXMLRPCRequestHandler(SimpleXMLRPCServer.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
- self.logger.error("Unexpected failure in handler", exc_info=1)
+ log.error("Unexcepted handler failure in do_POST", exc_info=1)
self.send_response(500)
self.end_headers()
else:
@@ -38,24 +33,45 @@ class CobaltXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
# shut down the connection
self.wfile.flush()
- self.connection.shutdown(1)
+ self.connection.shutdown()
-class Component(SSL.SSLServer,
+ def setup(self):
+ self.connection = self.request
+ self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
+ self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
+
+class SSLServer(BaseHTTPServer.HTTPServer):
+ '''This class encapsulates all of the ssl server stuff'''
+ def __init__(self, address, keyfile, handler):
+ SocketServer.BaseServer.__init__(self, address, handler)
+ ctxt = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
+ ctxt.use_privatekey_file (keyfile)
+ ctxt.use_certificate_file(keyfile)
+ self.socket = OpenSSL.SSL.Connection(ctxt,
+ socket.socket(self.address_family, self.socket_type))
+ self.server_bind()
+ self.server_activate()
+
+class Component(SSLServer,
SimpleXMLRPCServer.SimpleXMLRPCDispatcher):
"""Cobalt component providing XML-RPC access"""
__name__ = 'Component'
__implementation__ = 'Generic'
__statefields__ = []
+ async_funcs = ['assert_location']
def __init__(self, setup):
# need to get addr
self.setup = setup
+ self.shut = False
+ signal.signal(signal.SIGINT, self.start_shutdown)
+ signal.signal(signal.SIGTERM, self.start_shutdown)
+ self.logger = logging.getLogger('Component')
self.cfile = ConfigParser.ConfigParser()
- self.logger = logging.getLogger('Bcfg2.Server')
if setup['configfile']:
cfilename = setup['configfile']
else:
- cfilename = '/etc/cobalt.conf'
+ cfilename = '/etc/bcfg2.conf'
self.cfile.read([cfilename])
if not self.cfile.has_section('communication'):
print "Configfile missing communication section"
@@ -64,49 +80,36 @@ class Component(SSL.SSLServer,
if not self.cfile.has_section('components'):
print "Configfile missing components section"
raise SystemExit, 1
-
if self.cfile._sections['components'].has_key(self.__name__):
self.static = True
location = urlparse.urlparse(self.cfile.get('components', self.__name__))[1].split(':')
location = (location[0], int(location[1]))
else:
location = (socket.gethostname(), 0)
-
- self.password = self.cfile.get('communication', 'password')
- sslctx = SSL.Context('sslv23')
try:
keyfile = self.cfile.get('communication', 'key')
except ConfigParser.NoOptionError:
print "No key specified in cobalt.conf"
raise SystemExit, 1
- sslctx.load_cert_chain(keyfile)
- #sslctx.load_verify_locations('ca.pem')
- #sslctx.set_client_CA_list_from_file('ca.pem')
- sslctx.set_verify(SSL.verify_none, 15)
- #sslctx.set_allow_unknown_ca(1)
- sslctx.set_session_id_ctx(self.__name__)
- sslctx.set_info_callback(self.handle_sslinfo)
- #sslctx.set_tmp_dh('dh1024.pem')
- self.logRequests = 0
- # setup unhandled request syslog handling
+
+ self.password = self.cfile.get('communication', 'password')
+
+ SSLServer.__init__(self, location, keyfile, CobaltXMLRPCRequestHandler)
SimpleXMLRPCServer.SimpleXMLRPCDispatcher.__init__(self)
- try:
- SSL.SSLServer.__init__(self, location, CobaltXMLRPCRequestHandler, sslctx)
- except socket.error, serr:
- self.logger.error("Failed to bind to location %s" % (location,), exc_info=1)
- self.port = self.socket.socket.getsockname()[1]
+ self.logRequests = 1
+ self.port = self.socket.getsockname()[1]
+ self.url = "https://%s:%s" % (socket.gethostname(), self.port)
self.logger.info("Bound to port %s" % self.port)
self.funcs.update({'HandleEvents':self.HandleEvents,
- 'system.listMethods':self.system_listMethods})
+ 'system.listMethods':self.addr_system_listMethods})
+ self.atime = 0
+ self.assert_location()
+ atexit.register(self.deassert_location)
def HandleEvents(self, address, event_list):
'''Default event handler'''
return True
- def handle_sslinfo(self, where, ret, ssl_ptr):
- '''This is where we need to handle all ssl negotiation issues'''
- pass
-
def _cobalt_marshalled_dispatch(self, data, address):
"""Decode and dispatch XMLRPC requests. Overloaded to pass through
client address information
@@ -133,12 +136,13 @@ class Component(SSL.SSLServer,
response = xmlrpclib.dumps(fault)
except TypeError, terror:
self.logger.error("Client %s called function %s with wrong argument count" %
- (address[0], method))
+ (address[0], method), exc_info=1)
response = xmlrpclib.dumps(xmlrpclib.Fault(4, terror.args[0]))
except:
- self.logger.error("Unexpected failure in handler", exc_info=1)
+ self.logger.error("Unexpected handler failure", exc_info=1)
# report exception back to server
- response = xmlrpclib.dumps(xmlrpclib.Fault(1, "handler failure"))
+ response = xmlrpclib.dumps(xmlrpclib.Fault(1,
+ "%s:%s" % (sys.exc_type, sys.exc_value)))
return response
def _authenticate_connection(self, method, user, password, address):
@@ -169,6 +173,55 @@ class Component(SSL.SSLServer,
for field in self.__statefields__:
setattr(self, field, loaddata[self.__statefields__.index(field)])
- def system_listMethods(self, address):
+ def addr_system_listMethods(self, address):
"""get rid of the address argument and call the underlying dispatcher method"""
return SimpleXMLRPCServer.SimpleXMLRPCDispatcher.system_listMethods(self)
+
+ def get_request(self):
+ '''We need to do work between requests, so select with timeout instead of blocking in accept'''
+ rsockinfo = []
+ while self.socket not in rsockinfo:
+ if self.shut:
+ raise socket.error
+ for funcname in self.async_funcs:
+ func = getattr(self, funcname, False)
+ if callable(func):
+ func()
+ else:
+ self.logger.error("Cannot call uncallable method %s" % (funcname))
+ try:
+ rsockinfo = select.select([self.socket], [], [], 10)[0]
+ except select.error:
+ continue
+ if self.socket in rsockinfo:
+ return self.socket.accept()
+
+ def assert_location(self):
+ '''Assert component location with slp'''
+ if self.__name__ == 'service-location' or self.static:
+ return
+ if (time.time() - self.atime) > 240:
+ slp = Cobalt.Proxy.service_location()
+ slp.AssertService({'tag':'location', 'name':self.__name__, 'url':self.url})
+ self.atime = time.time()
+
+ def deassert_location(self):
+ '''remove registration from slp'''
+ if self.__name__ == 'service-location' or self.static:
+ return
+ slp = Cobalt.Proxy.service_location()
+ try:
+ slp.DeassertService([{'tag':'location', 'name':self.__name__, 'url':self.url}])
+ except xmlrpclib.Fault, fault:
+ if fault.faultCode == 11:
+ self.logger.error("Failed to deregister self; no matching component")
+
+ 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
+
diff --git a/src/sbin/bcfg2-server b/src/sbin/bcfg2-server
index a528f1864..dafc8be1e 100755
--- a/src/sbin/bcfg2-server
+++ b/src/sbin/bcfg2-server
@@ -9,7 +9,7 @@ from xmlrpclib import Fault
from lxml.etree import XML, Element, tostring
import getopt, logging, os, select, signal, socket, sys
-import Bcfg2.Logging, Bcfg2.Server.Component, M2Crypto.SSL
+import Bcfg2.Logging, Bcfg2.Server.Component
logger = logging.getLogger('bcfg2-server')
@@ -110,32 +110,17 @@ class Bcfg2Serv(Bcfg2.Server.Component.Component):
famfd = self.Core.fam.fileno()
while self.socket not in rsockinfo:
if self.shut:
- raise M2Crypto.SSL.SSLError
+ raise socket.error
try:
rsockinfo = select.select([self.socket, famfd], [], [], 15)[0]
except select.error:
- raise M2Crypto.SSL.SSLError
+ continue
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']
@@ -208,7 +193,7 @@ class Bcfg2Serv(Bcfg2.Server.Component.Component):
return "<ok/>"
if __name__ == '__main__':
- Bcfg2.Logging.setup_logging()
+ Bcfg2.Logging.setup_logging('bcfg2-server')
options = {
'v':'verbose',
'd':'debug',