Index: fedd/fedd_util.py
===================================================================
--- fedd/fedd_util.py	(revision c922f23933185172b4e16710ef2d4751c92e6353)
+++ fedd/fedd_util.py	(revision 058f58e973a0a88bc6106dbaec3f9760e7823f79)
@@ -368,30 +368,35 @@
 
 
-def make_soap_handler(typecode, method, constructor, body_name):
-    """
-    Generate the handler code to unpack and pack SOAP requests and responses
+class soap_handler:
+    """
+    Encapsulate the handler code to unpack and pack SOAP requests and responses
     and call the given method.
 
     The code to decapsulate and encapsulate parameters encoded in SOAP is the
-    same modulo a few parameters.  This is basically a stub compiler for
-    calling a fedd service trhough a soap interface.  The parameters are the
-    typecode of the request parameters, the method to call (usually a bound
-    instance of a method on a fedd service providing class), the constructor of
-    a response packet and the name of the body element of that packet.  The
-    handler takes a ParsedSoap object (the request) and returns an instance of
-    the class created by constructor containing the response.  Failures of the
-    constructor or badly created constructors will result in None being
-    returned.
-    """
-    def handler(ps, fid):
-	req = ps.Parse(typecode)
-
-	msg = method(fedids_to_obj(unpack_soap(req)), fid)
-
-	resp = constructor()
-	set_element = getattr(resp, "set_element_%s" % body_name, None)
+    same modulo a few parameters.  This is a functor that calls a fedd service
+    trhough a soap interface.  The parameters are the typecode of the request
+    parameters, the method to call (usually a bound instance of a method on a
+    fedd service providing class), the constructor of a response packet and the
+    name of the body element of that packet.  The handler takes a ParsedSoap
+    object (the request) and returns an instance of the class created by
+    constructor containing the response.  Failures of the constructor or badly
+    created constructors will result in None being returned.
+    """
+    def __init__(self, typecode, method, constructor, body_name):
+	self.typecode = typecode
+	self.method = method
+	self.constructor = constructor
+	self.body_name = body_name
+
+    def __call__(self, ps, fid):
+	req = ps.Parse(self.typecode)
+
+	msg = self.method(fedids_to_obj(unpack_soap(req)), fid)
+
+	resp = self.constructor()
+	set_element = getattr(resp, "set_element_%s" % self.body_name, None)
 	if set_element and callable(set_element):
 	    try:
-		set_element(pack_soap(resp, body_name, msg))
+		set_element(pack_soap(resp, self.body_name, msg))
 		return resp
 	    except (NameError, TypeError):
@@ -400,9 +405,7 @@
 	    return None
 
-    return handler
-
-def make_xmlrpc_handler(method, body_name):
-    """
-    Generate the handler code to unpack and pack SOAP requests and responses
+class xmlrpc_handler:
+    """
+    Generate the handler code to unpack and pack XMLRPC requests and responses
     and call the given method.
 
@@ -416,37 +419,58 @@
     to fedid objects on input and encapsulated as Binaries on output.
     """
-    def handler(params, fid):
-	decap_fedids = (('fedid', lambda x: fedid(bits=x.data)),)
-
-	p = apply_to_tags(params[0], decap_fedids)
-	msg = method(p, fid)
-
+    def __init__(self, method, body_name):
+	self.method = method
+	self.body_name = body_name
+	# A map used by apply_to_tags to convert fedids from xmlrpclib.Binary
+	# objects to fedid objects in one sweep.
+	self.decap_fedids = (('fedid', lambda x: fedid(bits=x.data)),)
+
+    def __call__(self, params, fid):
+	msg = None
+
+	p = apply_to_tags(params[0], self.decap_fedids)
+	try:
+	    msg = self.method(p, fid)
+	except service_error, e:
+	    raise Fault(e.code_string(), e.desc)
 	if msg != None:
-	    return make_unicode(encapsulate_binaries({ body_name: msg }, ('fedid',)))
+	    return make_unicode(encapsulate_binaries(\
+		    { self.body_name: msg }, ('fedid',)))
 	else:
 	    return None
 
-    return handler
-
-def make_service_callers(service_name, port_name, request_message, 
-	request_body_name):
+class service_caller:
+    def __init__(self, service_name, port_name, locator, request_message, 
+	    request_body_name, tracefile=None):
+	self.service_name = service_name
+	self.port_name = port_name
+	self.locator = locator
+	self.request_message = request_message
+	self.request_body_name = request_body_name
+	self.tracefile = tracefile
+	self.__call__ = self.call_service
 
     def call_xmlrpc_service(self, url, req, cert_file=None, cert_pwd=None, 
-	    trusted_certs=None):
+	    trusted_certs=None, context=None, tracefile=None):
 	"""Send an XMLRPC request.  """
 	decap_fedids = (('fedid', lambda x: fedid(bits=x.data)),)
 
-	# No retry loop here.  Proxy servers must correctly authenticate
-	# themselves without help
-	try:
-	    ctx = fedd_ssl_context(cert_file, trusted_certs, password=cert_pwd)
-	except SSL.SSLError:
-	    raise service_error(service_error.server_config,
-		    "Server certificates misconfigured")
+
+	# If a context is given, use it.  Otherwise construct one from
+	# components.  The construction shouldn't call out for passwords.
+	if context:
+	    ctx = context
+	else:
+	    try:
+		ctx = fedd_ssl_context(cert_file, trusted_certs, 
+			password=cert_pwd)
+	    except SSL.SSLError:
+		raise service_error(service_error.server_config,
+			"Certificates misconfigured")
 
 	# Of all the dumbass things.  The XMLRPC library in use here won't
-	# properly encode unicode strings, so we make a copy of req with the
-	# unicode objects converted.  We also convert the url to a basic string
-	# if it isn't one already.
+	# properly encode unicode strings, so we make a copy of req with
+	# the unicode objects converted.  We also convert the url to a
+	# basic string if it isn't one already.
 	r = strip_unicode(copy.deepcopy(req))
 	url = str(url)
@@ -454,18 +478,19 @@
 	transport = SSL_Transport(ctx)
 	port = ServerProxy(url, transport=transport)
+	# Make the call, and convert faults back to service_errors
 	try:
-	    remote_method = getattr(port, service_name, None)
-	    resp = remote_method(encapsulate_binaries({ request_body_name: r},
-		('fedid',)))
+	    remote_method = getattr(port, self.service_name, None)
+	    resp = remote_method(encapsulate_binaries(\
+		    { self.request_body_name: r}, ('fedid',)))
 	except Fault, f:
 	    raise service_error(None, f.faultString, f.faultCode)
 	except Error, e:
-	    raise service_error(service_error.proxy, 
+	    raise service_error(service_error.protocol, 
 		    "Remote XMLRPC Fault: %s" % e)
 
 	return apply_to_tags(resp, decap_fedids) 
 
-    def call_soap_service(self, url, req, loc_const, cert_file=None, cert_pwd=None, 
-	    trusted_certs=None):
+    def call_soap_service(self, url, req, cert_file=None, cert_pwd=None,
+	    trusted_certs=None, context=None, tracefile=None):
 	"""
 	Send req on to the real destination in dt and return the response
@@ -474,21 +499,28 @@
 	also rethrows any faults.
 	"""
-	# No retry loop here.  Proxy servers must correctly authenticate
-	# themselves without help
-	try:
-	    ctx = fedd_ssl_context(cert_file, trusted_certs, password=cert_pwd)
-	except SSL.SSLError:
-	    raise service_error(service_error.server_config, 
-		    "Server certificates misconfigured")
-
-	loc = loc_const()
-	get_port = getattr(loc, port_name, None)
+
+	tf = tracefile or self.tracefile or None
+
+	# If a context is given, use it.  Otherwise construct one from
+	# components.  The construction shouldn't call out for passwords.
+	if context:
+	    ctx = context
+	else:
+	    try:
+		ctx = fedd_ssl_context(cert_file, trusted_certs, 
+			password=cert_pwd)
+	    except SSL.SSLError:
+		raise service_error(service_error.server_config,
+			"Certificates misconfigured")
+	loc = self.locator()
+	get_port = getattr(loc, self.port_name, None)
 	if not get_port:
 	    raise service_error(service_error.internal, 
-		    "Cannot get port %s from locator" % port_name)
+		    "Cannot get port %s from locator" % self.port_name)
 	port = get_port(url,
 		transport=M2Crypto.httpslib.HTTPSConnection, 
-		transdict={ 'ssl_context' : ctx })
-	remote_method = getattr(port, service_name, None)
+		transdict={ 'ssl_context' : ctx },
+		tracefile=tf)
+	remote_method = getattr(port, self.service_name, None)
 	if not remote_method:
 	    raise service_error(service_error.internal,
@@ -496,21 +528,59 @@
 
 	# Reconstruct the full request message
-	msg = request_message()
-	set_element = getattr(msg, "set_element_%s" % request_body_name, None)
+	msg = self.request_message()
+	set_element = getattr(msg, "set_element_%s" % self.request_body_name,
+		None)
 	if not set_element:
 	    raise service_error(service_error.internal,
 		    "Cannot get element setting method for %s" % \
-			    request_body_name)
-	set_element(pack_soap(msg, request_body_name, req))
+			    self.request_body_name)
+	set_element(pack_soap(msg, self.request_body_name, req))
 	try:
 	    resp = remote_method(msg)
 	except ZSI.ParseException, e:
-	    raise service_error(service_error.proxy,
-		    "Bad format message (XMLRPC??): %s" %
-		    str(e))
-	r = unpack_soap(resp)
+	    raise service_error(service_error.protocol,
+		    "Bad format message (XMLRPC??): %s" % e)
+	except ZSI.FaultException, e:
+	    ee = unpack_soap(e.fault.detail[0]).get('FeddFaultBody', { })
+	    if ee:
+		raise service_error(ee['code'], ee['desc'])
+	    else:
+		raise service_error(service_error.internal,
+			"Unexpected fault body")
+	r = make_unicode(fedids_to_obj(unpack_soap(resp)))
 	return r
 
-    return (call_soap_service, call_xmlrpc_service)
+    def call_service(self, url, req, cert_file=None, cert_pwd=None, 
+	trusted_certs=None, context=None, tracefile=None):
+	p_fault = None	# Any SOAP failure (sent unless XMLRPC works)
+	resp = None
+	try:
+	    # Try the SOAP request
+	    resp = self.call_soap_service(url, req, 
+		    cert_file, cert_pwd, trusted_certs, context, tracefile)
+	    return resp
+	except service_error, e:
+	    if e.code == service_error.protocol: p_fault = None
+	    else: raise
+	except ZSI.FaultException, f:
+	    p_fault = f.fault.detail[0]
+		
+
+	# If we could not get a valid SOAP response to the request above,
+	# try the same address using XMLRPC and let any faults flow back
+	# out.
+	if p_fault == None:
+	    resp = self.call_xmlrpc_service(url, req, cert_file,
+		    cert_pwd, trusted_certs, context, tracefile)
+	    return resp
+	else:
+	    # Build the fault
+	    ee = unpack_soap(p_fault).get('FeddFaultBody', { })
+	    if ee:
+		raise service_error(ee['code'], ee['desc'])
+	    else:
+		raise service_error(service_error.internal,
+			"Unexpected fault body")
+
 
 def set_log_level(config, sect, log):
