Changeset 058f58e for fedd/fedd_util.py


Ignore:
Timestamp:
Nov 20, 2008 7:14:58 PM (15 years ago)
Author:
Ted Faber <faber@…>
Branches:
axis_example, compt_changes, info-ops, master, version-1.30, version-2.00, version-3.01, version-3.02
Children:
cfabc40
Parents:
c922f23
Message:

Unify the code for calling SOAP and XMLRPC services into a couple classes.
Before there were slightly different semantics everywhere.

Also make the handlers classes rather than the output of stub compiling
functions.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • fedd/fedd_util.py

    rc922f23 r058f58e  
    368368
    369369
    370 def make_soap_handler(typecode, method, constructor, body_name):
    371     """
    372     Generate the handler code to unpack and pack SOAP requests and responses
     370class soap_handler:
     371    """
     372    Encapsulate the handler code to unpack and pack SOAP requests and responses
    373373    and call the given method.
    374374
    375375    The code to decapsulate and encapsulate parameters encoded in SOAP is the
    376     same modulo a few parameters.  This is basically a stub compiler for
    377     calling a fedd service trhough a soap interface.  The parameters are the
    378     typecode of the request parameters, the method to call (usually a bound
    379     instance of a method on a fedd service providing class), the constructor of
    380     a response packet and the name of the body element of that packet.  The
    381     handler takes a ParsedSoap object (the request) and returns an instance of
    382     the class created by constructor containing the response.  Failures of the
    383     constructor or badly created constructors will result in None being
    384     returned.
    385     """
    386     def handler(ps, fid):
    387         req = ps.Parse(typecode)
    388 
    389         msg = method(fedids_to_obj(unpack_soap(req)), fid)
    390 
    391         resp = constructor()
    392         set_element = getattr(resp, "set_element_%s" % body_name, None)
     376    same modulo a few parameters.  This is a functor that calls a fedd service
     377    trhough a soap interface.  The parameters are the typecode of the request
     378    parameters, the method to call (usually a bound instance of a method on a
     379    fedd service providing class), the constructor of a response packet and the
     380    name of the body element of that packet.  The handler takes a ParsedSoap
     381    object (the request) and returns an instance of the class created by
     382    constructor containing the response.  Failures of the constructor or badly
     383    created constructors will result in None being returned.
     384    """
     385    def __init__(self, typecode, method, constructor, body_name):
     386        self.typecode = typecode
     387        self.method = method
     388        self.constructor = constructor
     389        self.body_name = body_name
     390
     391    def __call__(self, ps, fid):
     392        req = ps.Parse(self.typecode)
     393
     394        msg = self.method(fedids_to_obj(unpack_soap(req)), fid)
     395
     396        resp = self.constructor()
     397        set_element = getattr(resp, "set_element_%s" % self.body_name, None)
    393398        if set_element and callable(set_element):
    394399            try:
    395                 set_element(pack_soap(resp, body_name, msg))
     400                set_element(pack_soap(resp, self.body_name, msg))
    396401                return resp
    397402            except (NameError, TypeError):
     
    400405            return None
    401406
    402     return handler
    403 
    404 def make_xmlrpc_handler(method, body_name):
    405     """
    406     Generate the handler code to unpack and pack SOAP requests and responses
     407class xmlrpc_handler:
     408    """
     409    Generate the handler code to unpack and pack XMLRPC requests and responses
    407410    and call the given method.
    408411
     
    416419    to fedid objects on input and encapsulated as Binaries on output.
    417420    """
    418     def handler(params, fid):
    419         decap_fedids = (('fedid', lambda x: fedid(bits=x.data)),)
    420 
    421         p = apply_to_tags(params[0], decap_fedids)
    422         msg = method(p, fid)
    423 
     421    def __init__(self, method, body_name):
     422        self.method = method
     423        self.body_name = body_name
     424        # A map used by apply_to_tags to convert fedids from xmlrpclib.Binary
     425        # objects to fedid objects in one sweep.
     426        self.decap_fedids = (('fedid', lambda x: fedid(bits=x.data)),)
     427
     428    def __call__(self, params, fid):
     429        msg = None
     430
     431        p = apply_to_tags(params[0], self.decap_fedids)
     432        try:
     433            msg = self.method(p, fid)
     434        except service_error, e:
     435            raise Fault(e.code_string(), e.desc)
    424436        if msg != None:
    425             return make_unicode(encapsulate_binaries({ body_name: msg }, ('fedid',)))
     437            return make_unicode(encapsulate_binaries(\
     438                    { self.body_name: msg }, ('fedid',)))
    426439        else:
    427440            return None
    428441
    429     return handler
    430 
    431 def make_service_callers(service_name, port_name, request_message,
    432         request_body_name):
     442class service_caller:
     443    def __init__(self, service_name, port_name, locator, request_message,
     444            request_body_name, tracefile=None):
     445        self.service_name = service_name
     446        self.port_name = port_name
     447        self.locator = locator
     448        self.request_message = request_message
     449        self.request_body_name = request_body_name
     450        self.tracefile = tracefile
     451        self.__call__ = self.call_service
    433452
    434453    def call_xmlrpc_service(self, url, req, cert_file=None, cert_pwd=None,
    435             trusted_certs=None):
     454            trusted_certs=None, context=None, tracefile=None):
    436455        """Send an XMLRPC request.  """
    437456        decap_fedids = (('fedid', lambda x: fedid(bits=x.data)),)
    438457
    439         # No retry loop here.  Proxy servers must correctly authenticate
    440         # themselves without help
    441         try:
    442             ctx = fedd_ssl_context(cert_file, trusted_certs, password=cert_pwd)
    443         except SSL.SSLError:
    444             raise service_error(service_error.server_config,
    445                     "Server certificates misconfigured")
     458
     459        # If a context is given, use it.  Otherwise construct one from
     460        # components.  The construction shouldn't call out for passwords.
     461        if context:
     462            ctx = context
     463        else:
     464            try:
     465                ctx = fedd_ssl_context(cert_file, trusted_certs,
     466                        password=cert_pwd)
     467            except SSL.SSLError:
     468                raise service_error(service_error.server_config,
     469                        "Certificates misconfigured")
    446470
    447471        # Of all the dumbass things.  The XMLRPC library in use here won't
    448         # properly encode unicode strings, so we make a copy of req with the
    449         # unicode objects converted.  We also convert the url to a basic string
    450         # if it isn't one already.
     472        # properly encode unicode strings, so we make a copy of req with
     473        # the unicode objects converted.  We also convert the url to a
     474        # basic string if it isn't one already.
    451475        r = strip_unicode(copy.deepcopy(req))
    452476        url = str(url)
     
    454478        transport = SSL_Transport(ctx)
    455479        port = ServerProxy(url, transport=transport)
     480        # Make the call, and convert faults back to service_errors
    456481        try:
    457             remote_method = getattr(port, service_name, None)
    458             resp = remote_method(encapsulate_binaries({ request_body_name: r},
    459                 ('fedid',)))
     482            remote_method = getattr(port, self.service_name, None)
     483            resp = remote_method(encapsulate_binaries(\
     484                    { self.request_body_name: r}, ('fedid',)))
    460485        except Fault, f:
    461486            raise service_error(None, f.faultString, f.faultCode)
    462487        except Error, e:
    463             raise service_error(service_error.proxy,
     488            raise service_error(service_error.protocol,
    464489                    "Remote XMLRPC Fault: %s" % e)
    465490
    466491        return apply_to_tags(resp, decap_fedids)
    467492
    468     def call_soap_service(self, url, req, loc_const, cert_file=None, cert_pwd=None,
    469             trusted_certs=None):
     493    def call_soap_service(self, url, req, cert_file=None, cert_pwd=None,
     494            trusted_certs=None, context=None, tracefile=None):
    470495        """
    471496        Send req on to the real destination in dt and return the response
     
    474499        also rethrows any faults.
    475500        """
    476         # No retry loop here.  Proxy servers must correctly authenticate
    477         # themselves without help
    478         try:
    479             ctx = fedd_ssl_context(cert_file, trusted_certs, password=cert_pwd)
    480         except SSL.SSLError:
    481             raise service_error(service_error.server_config,
    482                     "Server certificates misconfigured")
    483 
    484         loc = loc_const()
    485         get_port = getattr(loc, port_name, None)
     501
     502        tf = tracefile or self.tracefile or None
     503
     504        # If a context is given, use it.  Otherwise construct one from
     505        # components.  The construction shouldn't call out for passwords.
     506        if context:
     507            ctx = context
     508        else:
     509            try:
     510                ctx = fedd_ssl_context(cert_file, trusted_certs,
     511                        password=cert_pwd)
     512            except SSL.SSLError:
     513                raise service_error(service_error.server_config,
     514                        "Certificates misconfigured")
     515        loc = self.locator()
     516        get_port = getattr(loc, self.port_name, None)
    486517        if not get_port:
    487518            raise service_error(service_error.internal,
    488                     "Cannot get port %s from locator" % port_name)
     519                    "Cannot get port %s from locator" % self.port_name)
    489520        port = get_port(url,
    490521                transport=M2Crypto.httpslib.HTTPSConnection,
    491                 transdict={ 'ssl_context' : ctx })
    492         remote_method = getattr(port, service_name, None)
     522                transdict={ 'ssl_context' : ctx },
     523                tracefile=tf)
     524        remote_method = getattr(port, self.service_name, None)
    493525        if not remote_method:
    494526            raise service_error(service_error.internal,
     
    496528
    497529        # Reconstruct the full request message
    498         msg = request_message()
    499         set_element = getattr(msg, "set_element_%s" % request_body_name, None)
     530        msg = self.request_message()
     531        set_element = getattr(msg, "set_element_%s" % self.request_body_name,
     532                None)
    500533        if not set_element:
    501534            raise service_error(service_error.internal,
    502535                    "Cannot get element setting method for %s" % \
    503                             request_body_name)
    504         set_element(pack_soap(msg, request_body_name, req))
     536                            self.request_body_name)
     537        set_element(pack_soap(msg, self.request_body_name, req))
    505538        try:
    506539            resp = remote_method(msg)
    507540        except ZSI.ParseException, e:
    508             raise service_error(service_error.proxy,
    509                     "Bad format message (XMLRPC??): %s" %
    510                     str(e))
    511         r = unpack_soap(resp)
     541            raise service_error(service_error.protocol,
     542                    "Bad format message (XMLRPC??): %s" % e)
     543        except ZSI.FaultException, e:
     544            ee = unpack_soap(e.fault.detail[0]).get('FeddFaultBody', { })
     545            if ee:
     546                raise service_error(ee['code'], ee['desc'])
     547            else:
     548                raise service_error(service_error.internal,
     549                        "Unexpected fault body")
     550        r = make_unicode(fedids_to_obj(unpack_soap(resp)))
    512551        return r
    513552
    514     return (call_soap_service, call_xmlrpc_service)
     553    def call_service(self, url, req, cert_file=None, cert_pwd=None,
     554        trusted_certs=None, context=None, tracefile=None):
     555        p_fault = None  # Any SOAP failure (sent unless XMLRPC works)
     556        resp = None
     557        try:
     558            # Try the SOAP request
     559            resp = self.call_soap_service(url, req,
     560                    cert_file, cert_pwd, trusted_certs, context, tracefile)
     561            return resp
     562        except service_error, e:
     563            if e.code == service_error.protocol: p_fault = None
     564            else: raise
     565        except ZSI.FaultException, f:
     566            p_fault = f.fault.detail[0]
     567               
     568
     569        # If we could not get a valid SOAP response to the request above,
     570        # try the same address using XMLRPC and let any faults flow back
     571        # out.
     572        if p_fault == None:
     573            resp = self.call_xmlrpc_service(url, req, cert_file,
     574                    cert_pwd, trusted_certs, context, tracefile)
     575            return resp
     576        else:
     577            # Build the fault
     578            ee = unpack_soap(p_fault).get('FeddFaultBody', { })
     579            if ee:
     580                raise service_error(ee['code'], ee['desc'])
     581            else:
     582                raise service_error(service_error.internal,
     583                        "Unexpected fault body")
     584
    515585
    516586def set_log_level(config, sect, log):
Note: See TracChangeset for help on using the changeset viewer.