X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=PLC%2FMethod.py;h=81a17479c51892396a0937252690667e9a947279;hb=6e915d8a9ac5474c20482751ab6d24e6ce13aec9;hp=a84f9a200c1f840cc37169622501d067f622a0ca;hpb=93f2f793538d71e126d21436e6626ea82fd8b743;p=plcapi.git diff --git a/PLC/Method.py b/PLC/Method.py index a84f9a2..81a1747 100644 --- a/PLC/Method.py +++ b/PLC/Method.py @@ -4,21 +4,23 @@ # Mark Huang # Copyright (C) 2006 The Trustees of Princeton University # -# $Id: Method.py,v 1.11 2006/10/19 21:38:08 tmack Exp $ -# - -import xmlrpclib +import xmlrpc.client from types import * import textwrap import os import time +import pprint from PLC.Faults import * -from PLC.Parameter import Parameter, Mixed +from PLC.Parameter import Parameter, Mixed, python_type, xmlrpc_type from PLC.Auth import Auth -from PLC.Debug import profile, log +from PLC.Debug import profile +from PLC.Events import Event, Events +from PLC.Nodes import Node, Nodes +from PLC.Persons import Person, Persons -class Method: +# we inherit object because we use new-style classes for legacy methods +class Method (object): """ Base class for all PLCAPI functions. At a minimum, all PLCAPI functions must define: @@ -55,18 +57,22 @@ class Method: return True - def __init__(self, api): + def __init__(self, api,caller=None): self.name = self.__class__.__name__ self.api = api - # Auth may set this to a Person instance (if an anonymous - # method, will remain None). - self.caller = None + if caller: + # let a method call another one by propagating its caller + self.caller=caller + else: + # Auth may set this to a Person instance (if an anonymous + # method, will remain None). + self.caller = None + # API may set this to a (addr, port) tuple if known self.source = None - def __call__(self, *args, **kwds): """ Main entry point for all PLCAPI functions. Type checks @@ -74,76 +80,113 @@ class Method: """ try: - start = time.time() - (min_args, max_args, defaults) = self.args() - - # Check that the right number of arguments were passed in - if len(args) < len(min_args) or len(args) > len(max_args): - raise PLCInvalidArgumentCount(len(args), len(min_args), len(max_args)) - - for name, value, expected in zip(max_args, args, self.accepts): - self.type_check(name, value, expected, args) - - result = self.call(*args, **kwds) - runtime = time.time() - start - - if self.api.config.PLC_API_DEBUG: - self.log(0, runtime, *args) - - return result - - except PLCFault, fault: - # Prepend method name to expected faults - fault.faultString = self.name + ": " + fault.faultString - runtime = time.time() - start - self.log(fault.faultCode, runtime, *args) - raise fault + start = time.time() + + # legacy code cannot be type-checked, due to the way Method.args() works + # as of 5.0-rc16 we don't use skip_type_check anymore + if not hasattr(self,"skip_type_check"): + (min_args, max_args, defaults) = self.args() + + # Check that the right number of arguments were passed in + if len(args) < len(min_args) or len(args) > len(max_args): + raise PLCInvalidArgumentCount(len(args), len(min_args), len(max_args)) + + for name, value, expected in zip(max_args, args, self.accepts): + self.type_check(name, value, expected, args) + + result = self.call(*args, **kwds) + runtime = time.time() - start + if self.api.config.PLC_API_DEBUG or hasattr(self, 'message'): + self.log(None, runtime, *args) - def log(self, fault_code, runtime, *args): + return result + + except PLCFault as fault: + + caller = "" + if isinstance(self.caller, Person): + caller = 'person_id %s' % self.caller['person_id'] + elif isinstance(self.caller, Node): + caller = 'node_id %s' % self.caller['node_id'] + + # Prepend caller and method name to expected faults + fault.faultString = caller + ": " + self.name + ": " + fault.faultString + runtime = time.time() - start + + if self.api.config.PLC_API_DEBUG: + self.log(fault, runtime, *args) + + raise fault + + def log(self, fault, runtime, *args): + """ + Log the transaction """ - Log the transaction - """ - # Gather necessary logging variables - event_type = 'Unknown' - object_type = 'Unknown' - person_id = None - object_ids = [] - call_name = self.name - call_args = ", ".join([unicode(arg) for arg in list(args)[1:]]) - call = "%s(%s)" % (call_name, call_args) - - if hasattr(self, 'event_type'): - event_type = self.event_type - if hasattr(self, 'object_type'): - object_type = self.object_type - if self.caller: - person_id = self.caller['person_id'] - if hasattr(self, 'object_ids'): - object_ids = self.object_ids - - # do not log system calls - if call_name.startswith('system'): - return False - # do not log get calls - if call_name.startswith('Get'): - return False - - sql_event = "INSERT INTO events " \ - " (person_id, event_type, object_type, fault_code, call, runtime) VALUES" \ - " (%(person_id)s, %(event_type)s, %(object_type)s," \ - " %(fault_code)d, %(call)s, %(runtime)f)" - self.api.db.do(sql_event, locals()) - - # log objects affected - event_id = self.api.db.last_insert_id('events', 'event_id') - for object_id in object_ids: - sql_objects = "INSERT INTO event_object (event_id, object_id) VALUES" \ - " (%(event_id)d, %(object_id)d) " - self.api.db.do(sql_objects, locals()) - - self.api.db.commit() - + + # Do not log system or Get calls + #if self.name.startswith('system') or self.name.startswith('Get'): + # return False + # Do not log ReportRunlevel + if self.name.startswith('system'): + return False + if self.name.startswith('ReportRunlevel'): + return False + + # Create a new event + event = Event(self.api) + event['fault_code'] = 0 + if fault: + event['fault_code'] = fault.faultCode + event['runtime'] = runtime + + # Redact passwords and sessions + newargs = args + if args: + newargs = [] + for arg in args: + if not isinstance(arg, dict): + newargs.append(arg) + continue + # what type of auth this is + if 'AuthMethod' in arg: + auth_methods = ['session', 'password', 'capability', 'gpg', 'hmac','anonymous'] + auth_method = arg['AuthMethod'] + if auth_method in auth_methods: + event['auth_type'] = auth_method + for password in 'AuthString', 'session', 'password': + if password in arg: + arg = arg.copy() + arg[password] = "Removed by API" + newargs.append(arg) + + # Log call representation + # XXX Truncate to avoid DoS + event['call'] = self.name + pprint.saferepr(newargs) + event['call_name'] = self.name + + # Both users and nodes can call some methods + if isinstance(self.caller, Person): + event['person_id'] = self.caller['person_id'] + elif isinstance(self.caller, Node): + event['node_id'] = self.caller['node_id'] + + event.sync(commit = False) + + if hasattr(self, 'event_objects') and isinstance(self.event_objects, dict): + for key in list(self.event_objects.keys()): + for object_id in self.event_objects[key]: + event.add_object(key, object_id, commit = False) + + + # Set the message for this event + if fault: + event['message'] = fault.faultString + elif hasattr(self, 'message'): + event['message'] = self.message + + # Commit + event.sync() def help(self, indent = " "): """ @@ -195,12 +238,12 @@ class Method: # Indent struct fields and mixed types if isinstance(param, dict): - for name, subparam in param.iteritems(): + for name, subparam in param.items(): text += param_text(name, subparam, indent + step, step) elif isinstance(param, Mixed): for subparam in param: text += param_text(name, subparam, indent + step, step) - elif isinstance(param, (list, tuple)): + elif isinstance(param, (list, tuple, set)): for subparam in param: text += param_text("", subparam, indent + step, step) @@ -228,14 +271,14 @@ class Method: """ # Inspect call. Remove self from the argument list. - max_args = self.call.func_code.co_varnames[1:self.call.func_code.co_argcount] - defaults = self.call.func_defaults + max_args = self.call.__code__.co_varnames[1:self.call.__code__.co_argcount] + defaults = self.call.__defaults__ if defaults is None: defaults = () min_args = max_args[0:len(max_args) - len(defaults)] defaults = tuple([None for arg in min_args]) + defaults - + return (min_args, max_args, defaults) def type_check(self, name, value, expected, args): @@ -244,7 +287,7 @@ class Method: which may be a Python type, a typed value, a Parameter, a Mixed type, or a list or dictionary of possibly mixed types, values, Parameters, or Mixed types. - + Extraneous members of lists must be of the same type as the last specified type. For example, if the expected argument type is [int, bool], then [1, False] and [14, True, False, @@ -260,13 +303,9 @@ class Method: try: self.type_check(name, value, item, args) return - except PLCInvalidArgument, fault: + except PLCInvalidArgument as fault: pass - xmlrpc_types = [xmlrpc_type(item) for item in expected] - raise PLCInvalidArgument("expected %s, got %s" % \ - (" or ".join(xmlrpc_types), - xmlrpc_type(type(value))), - name) + raise fault # If an authentication structure is expected, save it and # authenticate after basic type checking is done. @@ -279,16 +318,22 @@ class Method: if isinstance(expected, Parameter): min = expected.min max = expected.max + nullok = expected.nullok expected = expected.type else: min = None max = None + nullok = False expected_type = python_type(expected) + # If value can be NULL + if value is None and nullok: + return + # Strings are a special case. Accept either unicode or str # types if a string is expected. - if expected_type in StringTypes and isinstance(value, StringTypes): + if expected_type is str and isinstance(value, str): pass # Integers and long integers are also special types. Accept @@ -303,80 +348,44 @@ class Method: name) # If a minimum or maximum (length, value) has been specified - if expected_type in StringTypes: + if expected_type is str: if min is not None and \ len(value.encode(self.api.encoding)) < min: - raise PLCInvalidArgument, "%s must be at least %d bytes long" % (name, min) + raise PLCInvalidArgument("%s must be at least %d bytes long" % (name, min)) if max is not None and \ len(value.encode(self.api.encoding)) > max: - raise PLCInvalidArgument, "%s must be at most %d bytes long" % (name, max) + raise PLCInvalidArgument("%s must be at most %d bytes long" % (name, max)) + elif expected_type in (list, tuple, set): + if min is not None and len(value) < min: + raise PLCInvalidArgument("%s must contain at least %d items" % (name, min)) + if max is not None and len(value) > max: + raise PLCInvalidArgument("%s must contain at most %d items" % (name, max)) else: if min is not None and value < min: - raise PLCInvalidArgument, "%s must be > %s" % (name, str(min)) + raise PLCInvalidArgument("%s must be > %s" % (name, str(min))) if max is not None and value > max: - raise PLCInvalidArgument, "%s must be < %s" % (name, str(max)) + raise PLCInvalidArgument("%s must be < %s" % (name, str(max))) # If a list with particular types of items is expected - if isinstance(expected, (list, tuple)): + if isinstance(expected, (list, tuple, set)): for i in range(len(value)): if i >= len(expected): - i = len(expected) - 1 - self.type_check(name + "[]", value[i], expected[i], args) + j = len(expected) - 1 + else: + j = i + self.type_check(name + "[]", value[i], expected[j], args) # If a struct with particular (or required) types of items is # expected. elif isinstance(expected, dict): - for key in value.keys(): + for key in list(value.keys()): if key in expected: self.type_check(name + "['%s']" % key, value[key], expected[key], args) - for key, subparam in expected.iteritems(): + for key, subparam in expected.items(): if isinstance(subparam, Parameter) and \ subparam.optional is not None and \ - not subparam.optional and key not in value.keys(): + not subparam.optional and key not in list(value.keys()): raise PLCInvalidArgument("'%s' not specified" % key, name) if auth is not None: auth.check(self, *args) - -def python_type(arg): - """ - Returns the Python type of the specified argument, which may be a - Python type, a typed value, or a Parameter. - """ - - if isinstance(arg, Parameter): - arg = arg.type - - if isinstance(arg, type): - return arg - else: - return type(arg) - -def xmlrpc_type(arg): - """ - Returns the XML-RPC type of the specified argument, which may be a - Python type, a typed value, or a Parameter. - """ - - arg_type = python_type(arg) - - if arg_type == NoneType: - return "nil" - elif arg_type == IntType or arg_type == LongType: - return "int" - elif arg_type == bool: - return "boolean" - elif arg_type == FloatType: - return "double" - elif arg_type in StringTypes: - return "string" - elif arg_type == ListType or arg_type == TupleType: - return "array" - elif arg_type == DictType: - return "struct" - elif arg_type == Mixed: - # Not really an XML-RPC type but return "mixed" for - # documentation purposes. - return "mixed" - else: - raise PLCAPIError, "XML-RPC cannot marshal %s objects" % arg_type