X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=PLC%2FAPI.py;h=8ba9367a024330592514b286a53d406bb252a2ad;hb=6bef7b35eec76ff66332cb20f58eb7703c2116f9;hp=308fa11a59febb0f26a2df6aa480b6e1c2569d7c;hpb=40785ed42aacff32beac8f18cb65e5534eb9d064;p=plcapi.git diff --git a/PLC/API.py b/PLC/API.py index 308fa11..8ba9367 100644 --- a/PLC/API.py +++ b/PLC/API.py @@ -1,3 +1,4 @@ +# pylint: disable=c0111, c0103 # # PLCAPI XML-RPC and SOAP interfaces # @@ -7,23 +8,12 @@ # Copyright (C) 2004-2006 The Trustees of Princeton University # -import sys -import traceback -import string -import xmlrpclib -import simplejson -try: - # Try to use jsonlib before using simpljson. This is a hack to get around - # the fact that the version of simplejson avaialble for f8 is slightly - # faster than xmlrpc but not as fast as jsonlib. There is no jsonlib - # pacakge available for f8, so this has to be installed manually and - # is not expected to always be available. Remove this once we move away - # from f8 based MyPLC's - import jsonlib - json = jsonlib -except: - json = simplejson + +import os +import json + +import xmlrpc.client # See "2.2 Characters" in the XML specification: # @@ -31,10 +21,20 @@ except: # avoiding # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF] -invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F)) -xml_escape_table = string.maketrans("".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii)) +invalid_codepoints = list(range(0x0, 0x8)) + [0xB, 0xC] + list(range(0xE, 0x1F)) +# broke with f24, somehow we get a unicode +# as an incoming string to be translated +str_xml_escape_table = \ + str.maketrans("".join((chr(x) for x in invalid_codepoints)), + "?" * len(invalid_codepoints)) +# loosely inspired from +# http://stackoverflow.com/questions/1324067/ +# how-do-i-get-str-translate-to-work-with-unicode-strings +unicode_xml_escape_table = \ + {invalid: "?" for invalid in invalid_codepoints} -def xmlrpclib_escape(s, replace = string.replace): + +def xmlrpclib_escape(s, replace=str.replace): """ xmlrpclib does not handle invalid 7-bit control characters. This function augments xmlrpclib.escape, which by default only replaces @@ -47,7 +47,25 @@ def xmlrpclib_escape(s, replace = string.replace): s = replace(s, ">", ">",) # Replace invalid 7-bit control characters with '?' - return s.translate(xml_escape_table) + if isinstance(s, str): + return s.translate(str_xml_escape_table) + else: + return s.translate(unicode_xml_escape_table) + + +def test_xmlrpclib_escape(): + incomings = [ + # full ASCII + "".join((chr(x) for x in range(128))), + # likewise but as a unicode string up to 256 + "".join((chr(x) for x in range(256))), + ] + for incoming in incomings: + print("==================== xmlrpclib_escape INPUT") + print(type(incoming), '->', incoming) + print("==================== xmlrpclib_escape OUTPUT") + print(xmlrpclib_escape(incoming)) + def xmlrpclib_dump(self, value, write): """ @@ -62,7 +80,7 @@ def xmlrpclib_dump(self, value, write): # Use our escape function args = [self, value, write] - if isinstance(value, (str, unicode)): + if isinstance(value, str): args.append(xmlrpclib_escape) try: @@ -70,22 +88,22 @@ def xmlrpclib_dump(self, value, write): f = self.dispatch[type(value)] except KeyError: # Try for an isinstance() match - for Type, f in self.dispatch.iteritems(): + for Type, f in self.dispatch.items(): if isinstance(value, Type): f(*args) return - raise TypeError, "cannot marshal %s objects" % type(value) + raise TypeError("cannot marshal %s objects" % type(value)) else: f(*args) + # You can't hide from me! -xmlrpclib.Marshaller._Marshaller__dump = xmlrpclib_dump +xmlrpc.client.Marshaller._Marshaller__dump = xmlrpclib_dump # SOAP support is optional try: import SOAPpy from SOAPpy.Parser import parseSOAPRPC - from SOAPpy.Types import faultType from SOAPpy.NS import NS from SOAPpy.SOAPBuilder import buildSOAP except ImportError: @@ -96,6 +114,7 @@ from PLC.Faults import * import PLC.Methods import PLC.Accessors + def import_deep(name): mod = __import__(name) components = name.split('.') @@ -103,6 +122,7 @@ def import_deep(name): mod = getattr(mod, comp) return mod + class PLCAPI: # flat list of method names @@ -110,19 +130,20 @@ class PLCAPI: # other_methods_map : dict {methodname: fullpath} # e.g. 'Accessors' -> 'PLC.Accessors.Accessors' - other_methods_map={} - for subdir in [ 'Accessors' ]: - path="PLC."+subdir + other_methods_map = {} + for subdir in ['Accessors']: + path = "PLC."+subdir # scan e.g. PLC.Accessors.__all__ pkg = __import__(path).__dict__[subdir] - for modulename in getattr(pkg,"__all__"): - fullpath=path+"."+modulename - for method in getattr(import_deep(fullpath),"methods"): + for modulename in getattr(pkg, "__all__"): + fullpath = path + "." + modulename + for method in getattr(import_deep(fullpath), "methods"): other_methods_map[method] = fullpath - all_methods = native_methods + other_methods_map.keys() + all_methods = native_methods + list(other_methods_map.keys()) - def __init__(self, config = "/etc/planetlab/plc_config", encoding = "utf-8"): + def __init__(self, config="/etc/planetlab/plc_config", + encoding="utf-8"): self.encoding = encoding # Better just be documenting the API @@ -131,48 +152,56 @@ class PLCAPI: # Load configuration self.config = Config(config) +# print("config has keys {}" +# .format(vars(self.config).keys())) # Initialize database connection if self.config.PLC_DB_TYPE == "postgresql": from PLC.PostgreSQL import PostgreSQL self.db = PostgreSQL(self) else: - raise PLCAPIError, "Unsupported database type " + self.config.PLC_DB_TYPE - - # Aspects modify the API by injecting code before, after or - # around method calls. -- http://github.com/baris/pyaspects/blob/master/README - # - # As of now we only have aspects for OMF integration, that's - # why we enable aspects only if PLC_OMF is set to true. - if self.config.PLC_OMF_ENABLED: - from aspects import apply_omf_aspect - apply_omf_aspect() - - if self.config.PLC_RATELIMIT_ENABLED: - from aspects import apply_ratelimit_aspect - apply_ratelimit_aspect() + raise PLCAPIError("Unsupported database type " + + self.config.PLC_DB_TYPE) + + # Enable Caching. Only for GetSlivers for the moment. + # TODO: we may consider to do this in an aspect like the ones above. + try: + if self.config.PLC_GETSLIVERS_CACHE: + getslivers_cache = True + except AttributeError: + getslivers_cache = False + + if getslivers_cache: + os.environ['DJANGO_SETTINGS_MODULE'] = 'plc_django_settings' + from cache_utils.decorators import cached + from PLC.Methods.GetSlivers import GetSlivers + @cached(7200) + def cacheable_call(cls, auth, node_id_or_hostname): + return cls.raw_call(auth, node_id_or_hostname) + + GetSlivers.call = cacheable_call def callable(self, method): """ Return a new instance of the specified method. """ - # Look up method if method not in self.all_methods: - raise PLCInvalidAPIMethod, method + raise PLCInvalidAPIMethod(method) # Get new instance of method try: classname = method.split(".")[-1] if method in self.native_methods: - fullpath="PLC.Methods." + method + fullpath = "PLC.Methods." + method else: - fullpath=self.other_methods_map[method] + fullpath = self.other_methods_map[method] module = __import__(fullpath, globals(), locals(), [classname]) return getattr(module, classname)(self) - except ImportError, AttributeError: - raise PLCInvalidAPIMethod, "import error %s for %s" % (AttributeError,fullpath) + except (ImportError, AttributeError): + raise PLCInvalidAPIMethod("import error %s for %s" + % (AttributeError, fullpath)) def call(self, source, method, *args): """ @@ -191,50 +220,57 @@ class PLCAPI: # Parse request into method name and arguments try: - interface = xmlrpclib - (args, method) = xmlrpclib.loads(data) - methodresponse = True - except Exception, e: + interface = xmlrpc.client + (args, method) = xmlrpc.client.loads(data) + except Exception as exc: if SOAPpy is not None: interface = SOAPpy - (r, header, body, attrs) = parseSOAPRPC(data, header = 1, body = 1, attrs = 1) + (r, header, body, attrs) = \ + parseSOAPRPC(data, header=1, body=1, attrs=1) method = r._name args = r._aslist() # XXX Support named arguments else: - raise e + raise exc try: result = self.call(source, method, *args) - except PLCFault, fault: + except PLCFault as fault: # Handle expected faults - if interface == xmlrpclib: + if interface == xmlrpc.client: result = fault methodresponse = None elif interface == SOAPpy: - result = faultParameter(NS.ENV_T + ":Server", "Method Failed", method) - result._setDetail("Fault %d: %s" % (fault.faultCode, fault.faultString)) + result = faultParameter(NS.ENV_T + ":Server", + "Method Failed", method) + result._setDetail("Fault %d: %s" + % (fault.faultCode, fault.faultString)) # Return result - if interface == xmlrpclib: + if interface == xmlrpc.client: if not isinstance(result, PLCFault): result = (result,) - data = xmlrpclib.dumps(result, methodresponse = True, encoding = self.encoding, allow_none = 1) + data = xmlrpc.client.dumps(result, methodresponse=True, + encoding=self.encoding, allow_none=1) elif interface == SOAPpy: - data = buildSOAP(kw = {'%sResponse' % method: {'Result': result}}, encoding = self.encoding) + data = buildSOAP( + kw={'%sResponse' % method: {'Result': result}}, + encoding=self.encoding) return data def handle_json(self, source, data): """ - Handle a JSON request + Handle a JSON request """ method, args = json.loads(data) try: result = self.call(source, method, *args) - except Exception, e: - result = str(e) - - return json.dumps(result) - - + except Exception as exc: + result = str(exc) + + return json.dumps(result) + +# one simple unit test +if __name__ == '__main__': + test_xmlrpclib_escape()