[Freeipa-users] call implemented methods via xml-rpc

Rob Crittenden rcritten at redhat.com
Wed Apr 21 19:21:51 UTC 2010


ALAHYANE Rachid wrote:
> Here is my apache logs : 
> ------------------------------------------------------------------------------------------------
> ==> /var/log/httpd/error_log <==
> [Wed Apr 21 20:02:51 2010] [warn] mod_python (pid=1529, 
> interpreter='rpcserver.domain.org <http://rpcserver.domain.org>'): 
> Module directory listed in "sys.path". This may cause problems. Please 
> check code. File being imported is 
> "/usr/lib/python2.6/site-packages/webservices/account.py".
> [Wed Apr 21 20:02:51 2010] [notice] mod_python (pid=1529, 
> interpreter='rpcserver.domain.org <http://rpcserver.domain.org>'): 
> Importing module '/usr/lib/python2.6/site-packages/webservices/account.py'
> /usr/lib/python2.6/site-packages/mod_python/importer.py:32: 
> DeprecationWarning: the md5 module is deprecated; use hashlib instead
>   import md5
> ipa: ERROR: Could not create log_dir '/root/.ipa/log'
> ipa: ERROR: could not load plugin module 
> '/usr/lib/python2.6/site-packages/ipalib/plugins/migration.py'
> Traceback (most recent call last):
>   File "/usr/lib/python2.6/site-packages/ipalib/plugable.py", line 533, 
> in import_plugins
>     __import__(fullname)
>   File "/usr/lib/python2.6/site-packages/ipalib/plugins/migration.py", 
> line 33, in <module>
>     from ipaserver.plugins.ldap2 import ldap2
>   File "/usr/lib/python2.6/site-packages/ipaserver/__init__.py", line 
> 33, in <module>
>     api.bootstrap(context='server', debug=True, log=None)
>   File "/usr/lib/python2.6/site-packages/ipalib/plugable.py", line 380, 
> in bootstrap
>     self.__doing('bootstrap')
>   File "/usr/lib/python2.6/site-packages/ipalib/plugable.py", line 365, 
> in __doing
>     '%s.%s() already called' % (self.__class__.__name__, name)
> StandardError: API.bootstrap() already called

Very strange. You explicitly set the context to 'webservices' and this 
backtrace shows it as 'server' which is why migration.py is trying to 
load ldap2 (and blowing up).

Jason, any ideas?

rob

> 
> 
> ==> /var/log/httpd/access_log <==
> 172.30.0.135 - - [21/Apr/2010:20:02:51 +0200] "POST /xmlrpc HTTP/1.0" 
> 200 348 "-" "xmlrpclib.py/1.0.1 <http://xmlrpclib.py/1.0.1> (by 
> www.pythonware.com <http://www.pythonware.com>)"
> 
> ------------------------------------------------------------------------------------------------
> 
> 
> And here my xmlrpchandler
> ------------------------------------------------------------------------------------------------
> import sys
> import os
> from mod_python import apache
> import xmlrpclib
> import types
> 
> import imp
> import re
> 
> 
> # Functions we want callable via XML-RPC
> __all__ = ['listMethods', 'methodSignature', 'methodHelp', 'multicall']
> 
> # For method signatures
> INT = 'int'
> STRING = 'string'
> BOOLEAN = 'boolean'
> DOUBLE = 'double'
> DATETIME = 'dateTime.iso8601'
> BASE64 = 'base64'
> ARRAY = 'array'
> STRUCT = 'struct'
> 
> # Saw this done in mod_python's apache.py. I just fixed it up a little...
> _suffixes = map(lambda x: x[0].replace('.', '\\.'), imp.get_suffixes())
> _exp = '(' + '|'.join(_suffixes) + ')$'
> _suffix_re = re.compile(_exp)
> 
> _environ = {}
> 
> 
> 
> 
> def listMethods(env=None):
>     """Enumerates all available XML-RPC methods."""
>     __xmlrpc_signature = '[[ARRAY]]'
> 
>     method_list = []
> 
>     # scan the directory which this module resides in
>     path = os.path.dirname(sys.modules[__name__].__file__)
>     try:
>         module_list = []
>         
>         files = os.listdir(path)
>         for f in files:
>             # does it have a module suffix?
>             if not _suffix_re.search(f):
>                 continue
>             # strip module suffix
>             module_name = _suffix_re.sub('', f)
>             # ensure it's not private or this module
>             if module_name[0] == '_' or module_name == __name__:
>                 continue
>             if module_name not in module_list:
>                 module_list.append(module_name)
> 
>         for module_name in module_list:
>             try:
>                 module = apache.import_module(module_name, path=[path])
>             except:
>                 pass
>             else:
>                 # scan module for non-private functions
>                 func_list = getattr(module, '__all__', dir(module))
>                 for func_name in func_list:
>                     if func_name[0] != '_' and \
>                            callable(getattr(module, func_name, None)):
>                         method_list.append('%s.%s' % (module_name, 
> func_name))
>     except:
>         pass
>     
>     # add system methods
>     method_list.extend(map(lambda x: 'system.%s' % x, __all__))
> 
>     method_list.sort()
>     return method_list
> 
> def methodSignature(method, env=None):
>     """Returns an XML-RPC method's signature."""
>     __xmlrpc_signature = '[[ARRAY, STRING]]'
>     
>     func = _map_methodName(method)
>     if not func:
>         return xmlrpclib.Fault(1, '%s: not implemented' % method)
> 
>     return _get_signature(func)
> 
> def methodHelp(method, env=None):
>     """Returns an XML-RPC method's help string."""
>     __xmlrpc_signature = '[[STRING, STRING]]'
>     
>     func = _map_methodName(method)
>     if not func:
>         return xmlrpclib.Fault(1, '%s: not implemented' % method)
> 
>     if func.__doc__:
>         help = _strip_docstring(func.__doc__)
>     else:
>         help = ''
>         
>     return help
> 
> def multicall(call_params, env=None):
>     """Executes multiple method calls with a single request."""
>     __xmlrpc_signature = '[[ARRAY, ARRAY]]'
> 
>     result_list = []
> 
>     for param in call_params:
>         if type(param) != dict:
>             result_list.append(_fault_struct(1, 'struct expected'))
>             continue
> 
>         if not param.has_key('methodName') or \
>                not param.has_key('params'):
>             result_list.append(_fault_struct(1, 'methodName/params 
> members ' \
>                                              'missing'))
>             continue
>         
>         method, params = param['methodName'], param['params']
> 
>         if method == 'system.multicall':
>             result_list.append(_fault_struct(1, 'system.multicall: ' \
>                                              'recursion forbidden'))
>             continue
>         
>         try:
>             result = _dispatch(method, params)
>             if isinstance(result, xmlrpclib.Fault):
>                 result = _fault_struct(result.faultCode, result.faultString)
>             else:
>                 result = (result,)
>         except:
>             result_list.append(_fault_struct(2, '%s: %s: %s' %
>                                              (method, sys.exc_type,
>                                               sys.exc_value)))
>         else:
>             result_list.append(result)
>         
>     return result_list
> 
> def _expand_tabs(s, width=8):
>     """Expands tabs to spaces, assuming tabs are of the specified width."""
>     
>     o = ''
>     col = 0
>     for c in s:
>         if c == '\t':
>             next = width - (col % width)
>             o += '        '[:next]
>             col += next
>         else:
>             o += c
>             col += 1
>     return o
> 
> def _strip_docstring(s):
>     """Takes a docstring and removes any extraneous indentation."""
>     
>     # Break into lines and expand tabs.
>     s = s.split('\n')
>     s = map(_expand_tabs, s)
> 
>     # Convert lines with only spaces to empty strings.
>     for i in range(len(s)):
>         if s[i] and not s[i].strip():
>             s[i] = ''
> 
>     # Single line or empty docstring.
>     if len(s) == 1:
>         return s[0]
> 
>     # Take care of the first line.
>     o = ''
>     o += s[0] + '\n'
> 
>     # Go through each line. The first non-blank line determines the 
> indent for
>     # the entire docstring. Unindent each line.
>     indent = 0
>     for line in s[1:]:
>         if line:
>             if not indent:
>                 indent = len(line) - len(line.lstrip())
>             if line.startswith(' '*indent):
>                 line = line[indent:]
>             else:
>                 # Indent was short. Strip as much as we can anyway.
>                 line = line.lstrip()
>             o += line + '\n'
>         else:
>             o += '\n'
> 
>     # If docstring ends with two linefeeds, remove one of them.
>     if o[-2:] == '\n\n':
>         o = o[:-1]
>     
>     return o
> 
> def _get_func_const(func, name, default=None):
>     """Get the value of a constant variable defined in a function."""
> 
>     func_code = getattr(func, 'func_code', None)
>     if func_code:
>         if name in func_code.co_names:
>             i = list(func_code.co_names).index(name) + 1
>             return func_code.co_consts[i]
>     return default
> 
> def _get_signature(func):
>     """Return the parameter signature of a function.
> 
>     Will first check func.__xmlrpc_signature, expecting it to be a list
>     of signatures. Otherwise, it will check a constant variable called
>     __xmlrpc_signature defined within the function. The variable MUST
>     be a string and must evaluate to a list of signatures.
> 
>     Returns an empty list if none neither are a valid signature list.
>     """
>     
>     if hasattr(func, '__xmlrpc_signature'):
>         sig = func.__xmlrpc_signature
>     else:
>         sig_str = _get_func_const(func, '__xmlrpc_signature')
>         sig = []
>         if sig_str:
>             try:
>                 sig = eval(sig_str)
>                 # need to validate the signature someday...
>             except:
>                 pass
>         
>     return sig
> 
> _type_map = {
>     types.IntType: INT,
>     types.LongType: INT,
>     types.StringType: STRING,
>     types.FloatType: DOUBLE,
>     types.TupleType: ARRAY,
>     types.ListType: ARRAY,
>     types.DictType: STRUCT
>     }
> 
> def _xmlrpc_type(v):
>     """Returns an XML-RPC type for a given value."""
> 
>     t = type(v)
>     if _type_map.has_key(t):
>         return _type_map[t]
>     if t is types.InstanceType:
>         if isinstance(v, xmlrpclib.DateTime):
>             return DATETIME
>         elif isinstance(v, xmlrpclib.Binary):
>             return BASE64
>         elif isinstance(v, xmlrpclib.Boolean):
>             return BOOLEAN
>     # Huh?!
>     return STRING
> 
> def _match_signature(params, sig_list):
>     """Matches an argument list with a signature list.
> 
>     If signature list is empty, any sort of argument list is accepted.
>     """
>     
>     param_types = map(_xmlrpc_type, params)
>     empty_sig = 1
>     for sig in sig_list:
>         empty_sig = 0
> 
>         # skip return type
>         sig = sig[1:]
>         
>         if len(param_types) == len(sig) and param_types == sig:
>             return 1
>         
>     return empty_sig
> 
> def _fault_struct(faultCode, faultString):
>     """Returns a Fault as a dictionary."""
> 
>     return { 'faultCode': faultCode, 'faultString': faultString }
> 
> def _map_methodName(method):
>     """Maps a methodName in the form of module.function to a function."""
> 
>     # parse methodName as module.function
>     method = method.split('.')
>     if len(method) != 2:
>         return None
> 
>     module_name, func_name = method
> 
>     if module_name == 'system':
>         # reserved functions are implemented in this module
>         module = sys.modules[__name__]
>     else:
>         # attempt to load module from the same directory as this module
>         path = os.path.dirname(sys.modules[__name__].__file__)
> 
>         module = None
>         # ensure it is not private
>         if module_name[0] != '_' and module_name != __name__:
>             try:
>                 module = apache.import_module(module_name, path=[path])
>             except:
>                 pass
> 
>         if module is None:
>             return None
> 
>     # now see if module has callable function named func_name
>     if hasattr(module, '__all__') and func_name not in module.__all__:
>         return None
>     
>     if func_name[0] != '_' and hasattr(module, func_name):
>         func = getattr(module, func_name)
> 
>         if callable(func):
>             return func
> 
>     return None
> 
> def _dispatch(method, params):
>     """Calls an XML-RPC method."""
>     global _environ
> 
>     func = _map_methodName(method)
>     if func is None:
>         return xmlrpclib.Fault(1, '%s: not implemented' % method)
> 
>     # check arguments
>     sig_list = _get_signature(func)
>     if not _match_signature(params, sig_list):
>         return xmlrpclib.Fault(1, '%s: bad arguments' % method )
>     
>     # call the function
>     result = apply(func, params, {'env':_environ})
>     
>     # if result is None, set it to False
>     if result is None:
>         result = xmlrpclib.False
>                 
>     return result
> 
> # These HTTP headers are required according to the XML-RPC spec
> _required_headers = ['Host', 'User-Agent', 'Content-Type', 'Content-Length']
> 
> def handler(req):
>     """mod_python handler."""
>     global _environ
>     _environ = dict(apache.build_cgi_env(req))
>     # only accept POST requests
>     if req.method != 'POST':
>         # We set this even though it doesn't seem to do anything...
>         req.err_headers_out['Allow'] = 'POST'
>         return apache.HTTP_METHOD_NOT_ALLOWED
> 
>     # check that all required headers are present
>     for h in _required_headers:
>         if not req.headers_in.has_key(h):
>             return apache.HTTP_BAD_REQUEST
> 
>     if not req.headers_in['Content-Type'].startswith('text/xml'):
>         return apache.HTTP_UNSUPPORTED_MEDIA_TYPE
> 
>     # The structure of the following was inspired by Brian Quinlan's
>     # SimpleXMLRPCServer.py (which was inspired by Fredrik Lundh's code).
>     try:
>         length = int(req.headers_in['Content-Length'])
> 
>         # read and parse request
>         data = req.read(length)
>         params, method = xmlrpclib.loads(data)
> 
>         try:
>             # dispatch the method
>             response = _dispatch(method, params)
> 
>             # convert response to singleton, if necessary
>             if not isinstance(response, xmlrpclib.Fault):
>                 response = (response,)
>         except:
>             # caught an exception, return it as our fault response
>             response = xmlrpclib.Fault(2, '%s: %s: %s' %
>                                        (method, sys.exc_type, 
> sys.exc_value))
> 
>         # convert response to xml
>         response = xmlrpclib.dumps(response, methodresponse=1)
>     except:
>         # eh?!
>         return apache.HTTP_BAD_REQUEST
>     else:
>         # send response
>         req.content_type = 'text/xml'
>         req.headers_out['Content-Length'] = str(len(response))
>         req.send_http_header()
>         req.write(response)
>         return apache.OK
> ------------------------------------------------------------------------------------------------
> 
> 
> And here my code of client 
> ------------------------------------------------------------------------------------------------
> #!/usr/bin/python                                                       
>                                                                         
>                                  
> 
> import xmlrpclib as rpc
> remote = rpc.ServerProxy('http://rpcserver.domain.org/xmlrpc 
> <http://client2.gamma.agorabox.org/xmlrpc>')
> print "+++++++ remote.account.getUserInfos(u'admin')"
> print remote.account.getUserInfos(u'admin')
> ------------------------------------------------------------------------------------------------
> 
> 
> 
> ---
> Meilleures salutations / Best Regards
> 
> Rachid ALAHYANE
> 
> 
> 
> 2010/4/21 Rob Crittenden <rcritten at redhat.com <mailto:rcritten at redhat.com>>
> 
>     ALAHYANE Rachid wrote:
> 
>         Ok so, my end goal is to use the ipa methods with xml-rpc as
>         following,
> 
>          *  ipaServer : my ipa server, used to authenticate users and
>         serves response for xml-rpc calls from rpcServer
>          *  rpcServer : this host is my xml-rpc server, I installed
>         freeipa libraires on it, and an apache server with mod_python
>         and mod_auth_kerb. This hosts will be used as a client ipa, It
>         is for these reasons that i used `account.py` from within apache.
>          * myClient : this host is the one which will make the rpc calls
>         to rpcServer.
> 
>         NB : 'account.py' is called by xmlrpchandler (it is my python
>         handler) when getUserInfos is called by myclient .
> 
> 
>     Can you set LogLevel debug on the rpcServer web server and see if
>     you get a backtrace (or look in /var/log/httpd/error_log, you may
>     already have one).
> 
>     What is strange is that this code works fine standalone. Can you
>     show us all the code in your xmlrpchandler?
> 
>     BTW, your English is fine :-)
> 
>     rob
> 
> 
> 
> 
>         Example :
>         myClient calls the remote.account.getUserInfos(u'admin'),
>         rpcServer (in mode client) intercepts this call and forwards it
>         to the ipaServer. This last one sends the response to rpcServer
>         (via xml-rpc) and then rpcServer responds to myClient.
> 
> 
>         this is my configurations :
> 
>         == On rpcServer ==
>         --------- httpd conf ------------
>         <Files "xmlrpc">
>          ## python conf                                                
>                                                                        
>                                                           # ....
>          SetHandler python-program
>          PythonHandler xmlrpchandler
>          PythonDebug on
>         </Files>
>         ------------------------------------
> 
>         the handler xmlrpchandler calls the following method when the
>         client requests for the remote method getUserInfos().
> 
>         --------- account.py ------------
>         def getUserInfos(user_name, env=None):
> 
>            from ipalib import api
> 
>            api.bootstrap_with_global_options(context='webservices')
>            api.finalize()
>            # mode Server is False. I am not on the server ipa
>            api.Backend.xmlclient.connect()
>            return api.Command.user_show(user_name)
>          ------------------------------------
> 
> 
>         == On myClient ==
>         When I call  this method from my client, I get this exception :
>         ------------------------------------
>         <Fault 2: "remote.account.getUserInfos: <type
>         'exceptions.StandardError'>: API.bootstrap() already called">
>         ------------------------------------
> 
> 
>         I hope that is clearer now, despite my bad English ;)
> 
> 
>         ---
>         Meilleures salutations / Best Regards
> 
>         Rachid ALAHYANE
> 
> 
> 
> 
> 




More information about the Freeipa-users mailing list