Changeset 9460b1e


Ignore:
Timestamp:
Nov 21, 2008 10:43:39 AM (16 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:
51cc9df
Parents:
f8b118e
Message:

move remote_service out of fedd_util

Location:
fedd
Files:
1 added
6 edited

Legend:

Unmodified
Added
Removed
  • fedd/fedd_access.py

    rf8b118e r9460b1e  
    2424import parse_detail
    2525from service_error import *
     26from remote_service import xmlrpc_handler, soap_handler, service_caller
    2627import logging
    2728
  • fedd/fedd_allocate_project.py

    rf8b118e r9460b1e  
    2121from fedd_util import *
    2222from fixed_resource import read_key_db, read_project_db
     23from remote_service import xmlrpc_handler, soap_handler, service_caller
    2324from service_error import *
    2425import logging
  • fedd/fedd_client.py

    rf8b118e r9460b1e  
    2020import xmlrpclib
    2121
    22 from fedd_util import fedid, fedd_ssl_context, pack_id, unpack_id, \
    23         service_caller
     22from fedd_util import fedid, fedd_ssl_context, pack_id, unpack_id
     23from remote_service import service_caller
    2424from service_error import *
    2525
  • fedd/fedd_experiment_control.py

    rf8b118e r9460b1e  
    2727from fedd_internal_services import *
    2828from fedd_util import *
     29from remote_service import xmlrpc_handler, soap_handler, service_caller
    2930import parse_detail
    3031from service_error import *
  • fedd/fedd_split.py

    rf8b118e r9460b1e  
    2727from fedd_internal_services import *
    2828from fedd_util import *
     29from remote_service import xmlrpc_handler, soap_handler
    2930import parse_detail
    3031from service_error import *
  • fedd/fedd_util.py

    rf8b118e r9460b1e  
    88
    99from M2Crypto import SSL, X509, EVP
    10 from M2Crypto.m2xmlrpclib import SSL_Transport
    11 import M2Crypto.httpslib
    1210from pyasn1.codec.der import decoder
    13 
    14 from fedd_services import *
    15 from fedd_internal_services import *
    16 from service_error import *
    17 from xmlrpclib import ServerProxy, dumps, loads, Fault, Error, Binary
    18 
    1911
    2012# The version of M2Crypto on users is pretty old and doesn't have all the
     
    232224        if certpath: os.remove(certpath)
    233225
    234 # Used by the remote_service_base class.
    235 def to_binary(o):
    236     """
    237     A function that converts an object into an xmlrpclib.Binary using
    238     either its internal packing method, or the standard Binary constructor.
    239     """
    240     pack = getattr(o, 'pack_xmlrpc', None)
    241     if callable(pack): return Binary(pack())
    242     else: return Binary(o)
    243 
    244 # Classes that encapsulate the process of making and dealing with requests to
    245 # WSDL-generated and XMLRPC remote accesses. 
    246 
    247 class remote_service_base:
    248     """
    249     This invisible base class encapsulates the functions used to massage the
    250     dictionaries used to pass parameters into and out of the RPC formats.  It's
    251     mostly a container for the static methods to do that work, but defines some
    252     maps sued by sub classes on apply_to_tags
    253     """
    254     # A map used to convert fedid fields to fedid objects (when the field is
    255     # already a string)
    256     fedid_to_object = ( ('fedid', lambda x: fedid(bits=x)),)
    257     # A map used by apply_to_tags to convert fedids from xmlrpclib.Binary
    258     # objects to fedid objects in one sweep.
    259     decap_fedids = (('fedid', lambda x: fedid(bits=x.data)),)
    260     # A map used to encapsulate fedids into xmlrpclib.Binary objects
    261     encap_fedids = (('fedid', to_binary),)
    262 
    263     @staticmethod
    264     def pack_soap(container, name, contents):
    265         """
    266         Convert the dictionary in contents into a tree of ZSI classes.
    267 
    268         The holder classes are constructed from factories in container and
    269         assigned to either the element or attribute name.  This is used to
    270         recursively create the SOAP message.
    271         """
    272         if getattr(contents, "__iter__", None) != None:
    273             attr =getattr(container, "new_%s" % name, None)
    274             if attr: obj = attr()
    275             else:
    276                 raise TypeError("%s does not have a new_%s attribute" % \
    277                         (container, name))
    278             for e, v in contents.iteritems():
    279                 assign = getattr(obj, "set_element_%s" % e, None) or \
    280                         getattr(obj, "set_attribute_%s" % e, None)
    281                 if isinstance(v, type(dict())):
    282                     assign(remote_service_base.pack_soap(obj, e, v))
    283                 elif getattr(v, "__iter__", None) != None:
    284                     assign([ remote_service_base.pack_soap(obj, e, val ) \
    285                             for val in v])
    286                 elif getattr(v, "pack_soap", None) != None:
    287                     assign(v.pack_soap())
    288                 else:
    289                     assign(v)
    290             return obj
    291         else: return contents
    292 
    293     @staticmethod
    294     def unpack_soap(element):
    295         """
    296         Convert a tree of ZSI SOAP classes intro a hash.  The inverse of
    297         pack_soap
    298 
    299         Elements or elements that are empty are ignored.
    300         """
    301         methods = [ m for m in dir(element) \
    302                 if m.startswith("get_element") or m.startswith("get_attribute")]
    303         if len(methods) > 0:
    304             rv = { }
    305             for m in methods:
    306                 if m.startswith("get_element_"):
    307                     n = m.replace("get_element_","",1)
    308                 else:
    309                     n = m.replace("get_attribute_", "", 1)
    310                 sub = getattr(element, m)()
    311                 if sub != None:
    312                     if isinstance(sub, basestring):
    313                         rv[n] = sub
    314                     elif getattr(sub, "__iter__", None) != None:
    315                         if len(sub) > 0: rv[n] = \
    316                                 [remote_service_base.unpack_soap(e) \
    317                                     for e in sub]
    318                     else:
    319                         rv[n] = remote_service_base.unpack_soap(sub)
    320             return rv
    321         else:
    322             return element
    323 
    324     @staticmethod
    325     def apply_to_tags(e, map):
    326         """
    327         Map is an iterable of ordered pairs (tuples) that map a key to a
    328         function.
    329         This function walks the given message and replaces any object with a
    330         key in the map with the result of applying that function to the object.
    331         """
    332         if isinstance(e, dict):
    333             for k in e.keys():
    334                 for tag, fcn in map:
    335                     if k == tag:
    336                         if isinstance(e[k], list):
    337                             e[k] = [ fcn(b) for b in e[k]]
    338                         else:
    339                             e[k] = fcn(e[k])
    340                     elif isinstance(e[k], dict):
    341                         remote_service_base.apply_to_tags(e[k], map)
    342                     elif isinstance(e[k], list):
    343                         for ee in e[k]:
    344                             remote_service_base.apply_to_tags(ee, map)
    345         # Other types end the recursion - they should be leaves
    346         return e
    347 
    348     @staticmethod
    349     def strip_unicode(obj):
    350         """Walk through a message and convert all strings to non-unicode
    351         strings"""
    352         if isinstance(obj, dict):
    353             for k in obj.keys():
    354                 obj[k] = remote_service_base.strip_unicode(obj[k])
    355             return obj
    356         elif isinstance(obj, basestring) and not isinstance(obj, str):
    357             return str(obj)
    358         elif getattr(obj, "__iter__", None):
    359             return [ remote_service_base.strip_unicode(x) for x in obj]
    360         else:
    361             return obj
    362 
    363     @staticmethod
    364     def make_unicode(obj):
    365         """Walk through a message and convert all strings to unicode"""
    366         if isinstance(obj, dict):
    367             for k in obj.keys():
    368                 obj[k] = remote_service_base.make_unicode(obj[k])
    369             return obj
    370         elif isinstance(obj, basestring) and not isinstance(obj, unicode):
    371             return unicode(obj)
    372         elif getattr(obj, "__iter__", None):
    373             return [ remote_service_base.make_unicode(x) for x in obj]
    374         else:
    375             return obj
    376 
    377 
    378 
    379 class soap_handler(remote_service_base):
    380     """
    381     Encapsulate the handler code to unpack and pack SOAP requests and responses
    382     and call the given method.
    383 
    384     The code to decapsulate and encapsulate parameters encoded in SOAP is the
    385     same modulo a few parameters.  This is a functor that calls a fedd service
    386     trhough a soap interface.  The parameters are the typecode of the request
    387     parameters, the method to call (usually a bound instance of a method on a
    388     fedd service providing class), the constructor of a response packet and the
    389     name of the body element of that packet.  The handler takes a ParsedSoap
    390     object (the request) and returns an instance of the class created by
    391     constructor containing the response.  Failures of the constructor or badly
    392     created constructors will result in None being returned.
    393     """
    394     def __init__(self, typecode, method, constructor, body_name):
    395         self.typecode = typecode
    396         self.method = method
    397         self.constructor = constructor
    398         self.body_name = body_name
    399 
    400     def __call__(self, ps, fid):
    401         req = ps.Parse(self.typecode)
    402         # Convert the message to a dict with the fedid strings converted to
    403         # fedid objects
    404         req = self.apply_to_tags(self.unpack_soap(req), self.fedid_to_object)
    405 
    406         msg = self.method(req, fid)
    407 
    408         resp = self.constructor()
    409         set_element = getattr(resp, "set_element_%s" % self.body_name, None)
    410         if set_element and callable(set_element):
    411             try:
    412                 set_element(self.pack_soap(resp, self.body_name, msg))
    413                 return resp
    414             except (NameError, TypeError):
    415                 return None
    416         else:
    417             return None
    418 
    419 class xmlrpc_handler(remote_service_base):
    420     """
    421     Generate the handler code to unpack and pack XMLRPC requests and responses
    422     and call the given method.
    423 
    424     The code to marshall and unmarshall XMLRPC parameters to and from a fedd
    425     service is largely the same.  This helper creates such a handler.  The
    426     parameters are the method name, and the name of the body struct that
    427     contains the response.  A handler is created that takes the params response
    428     from an xmlrpclib.loads on the incoming rpc and a fedid and responds with
    429     a hash representing the struct ro be returned to the other side.  On error
    430     None is returned.  Fedid fields are decapsulated from binary and converted
    431     to fedid objects on input and encapsulated as Binaries on output.
    432     """
    433     def __init__(self, method, body_name):
    434         self.method = method
    435         self.body_name = body_name
    436 
    437     def __call__(self, params, fid):
    438         msg = None
    439 
    440         p = self.apply_to_tags(params[0], self.decap_fedids)
    441         try:
    442             msg = self.method(p, fid)
    443         except service_error, e:
    444             raise Fault(e.code_string(), e.desc)
    445         if msg != None:
    446             return self.make_unicode(self.apply_to_tags(\
    447                     { self.body_name: msg }, self.encap_fedids))
    448         else:
    449             return None
    450 
    451 class service_caller(remote_service_base):
    452     def __init__(self, service_name, port_name, locator, request_message,
    453             request_body_name, tracefile=None):
    454         self.service_name = service_name
    455         self.port_name = port_name
    456         self.locator = locator
    457         self.request_message = request_message
    458         self.request_body_name = request_body_name
    459         self.tracefile = tracefile
    460         self.__call__ = self.call_service
    461 
    462     def call_xmlrpc_service(self, url, req, cert_file=None, cert_pwd=None,
    463             trusted_certs=None, context=None, tracefile=None):
    464         """Send an XMLRPC request.  """
    465 
    466 
    467         # If a context is given, use it.  Otherwise construct one from
    468         # components.  The construction shouldn't call out for passwords.
    469         if context:
    470             ctx = context
    471         else:
    472             try:
    473                 ctx = fedd_ssl_context(cert_file, trusted_certs,
    474                         password=cert_pwd)
    475             except SSL.SSLError:
    476                 raise service_error(service_error.server_config,
    477                         "Certificates misconfigured")
    478 
    479         # Of all the dumbass things.  The XMLRPC library in use here won't
    480         # properly encode unicode strings, so we make a copy of req with
    481         # the unicode objects converted.  We also convert the url to a
    482         # basic string if it isn't one already.
    483         r = self.strip_unicode(copy.deepcopy(req))
    484         url = str(url)
    485        
    486         transport = SSL_Transport(ctx)
    487         port = ServerProxy(url, transport=transport)
    488         # Make the call, and convert faults back to service_errors
    489         try:
    490             remote_method = getattr(port, self.service_name, None)
    491             resp = remote_method(self.apply_to_tags(\
    492                     { self.request_body_name: r}, self.encap_fedids))
    493         except Fault, f:
    494             raise service_error(None, f.faultString, f.faultCode)
    495         except Error, e:
    496             raise service_error(service_error.protocol,
    497                     "Remote XMLRPC Fault: %s" % e)
    498 
    499         return self.apply_to_tags(resp, self.decap_fedids)
    500 
    501     def call_soap_service(self, url, req, cert_file=None, cert_pwd=None,
    502             trusted_certs=None, context=None, tracefile=None):
    503         """
    504         Send req on to the real destination in dt and return the response
    505 
    506         Req is just the requestType object.  This function re-wraps it.  It
    507         also rethrows any faults.
    508         """
    509 
    510         tf = tracefile or self.tracefile or None
    511 
    512         # If a context is given, use it.  Otherwise construct one from
    513         # components.  The construction shouldn't call out for passwords.
    514         if context:
    515             ctx = context
    516         else:
    517             try:
    518                 ctx = fedd_ssl_context(cert_file, trusted_certs,
    519                         password=cert_pwd)
    520             except SSL.SSLError:
    521                 raise service_error(service_error.server_config,
    522                         "Certificates misconfigured")
    523         loc = self.locator()
    524         get_port = getattr(loc, self.port_name, None)
    525         if not get_port:
    526             raise service_error(service_error.internal,
    527                     "Cannot get port %s from locator" % self.port_name)
    528         port = get_port(url,
    529                 transport=M2Crypto.httpslib.HTTPSConnection,
    530                 transdict={ 'ssl_context' : ctx },
    531                 tracefile=tf)
    532         remote_method = getattr(port, self.service_name, None)
    533         if not remote_method:
    534             raise service_error(service_error.internal,
    535                     "Cannot get service from SOAP port")
    536 
    537         # Reconstruct the full request message
    538         msg = self.request_message()
    539         set_element = getattr(msg, "set_element_%s" % self.request_body_name,
    540                 None)
    541         if not set_element:
    542             raise service_error(service_error.internal,
    543                     "Cannot get element setting method for %s" % \
    544                             self.request_body_name)
    545         set_element(self.pack_soap(msg, self.request_body_name, req))
    546         try:
    547             resp = remote_method(msg)
    548         except ZSI.ParseException, e:
    549             raise service_error(service_error.protocol,
    550                     "Bad format message (XMLRPC??): %s" % e)
    551         except ZSI.FaultException, e:
    552             ee = self.unpack_soap(e.fault.detail[0]).get('FeddFaultBody', { })
    553             if ee:
    554                 raise service_error(ee['code'], ee['desc'])
    555             else:
    556                 raise service_error(service_error.internal,
    557                         "Unexpected fault body")
    558         # Unpack and convert fedids to objects
    559         r = self.apply_to_tags(self.unpack_soap(resp), self.fedid_to_object)
    560         #  Make sure all strings are unicode
    561         r = self.make_unicode(r)
    562         return r
    563 
    564     def call_service(self, url, req, cert_file=None, cert_pwd=None,
    565         trusted_certs=None, context=None, tracefile=None):
    566         p_fault = None  # Any SOAP failure (sent unless XMLRPC works)
    567         resp = None
    568         try:
    569             # Try the SOAP request
    570             resp = self.call_soap_service(url, req,
    571                     cert_file, cert_pwd, trusted_certs, context, tracefile)
    572             return resp
    573         except service_error, e:
    574             if e.code == service_error.protocol: p_fault = None
    575             else: raise
    576         except ZSI.FaultException, f:
    577             p_fault = f.fault.detail[0]
    578                
    579 
    580         # If we could not get a valid SOAP response to the request above,
    581         # try the same address using XMLRPC and let any faults flow back
    582         # out.
    583         if p_fault == None:
    584             resp = self.call_xmlrpc_service(url, req, cert_file,
    585                     cert_pwd, trusted_certs, context, tracefile)
    586             return resp
    587         else:
    588             # Build the fault
    589             ee = unpack_soap(p_fault).get('FeddFaultBody', { })
    590             if ee:
    591                 raise service_error(ee['code'], ee['desc'])
    592             else:
    593                 raise service_error(service_error.internal,
    594                         "Unexpected fault body")
    595 
    596 
    597226def set_log_level(config, sect, log):
    598227    """ Set the logging level to the value passed in sect of config."""
Note: See TracChangeset for help on using the changeset viewer.