[Freeipa-users] call implemented methods via xml-rpc
ALAHYANE Rachid
afkkir at gmail.com
Wed Apr 21 18:23:30 UTC 2010
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'): 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'): 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
==> /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 (by 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>
> 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
>>
>>
>>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://listman.redhat.com/archives/freeipa-users/attachments/20100421/1418893f/attachment.htm>
More information about the Freeipa-users
mailing list