From 24b6cb89d443384cb432f01265c45bc18d9cf2fc Mon Sep 17 00:00:00 2001 From: Jason Gerard DeRose Date: Thu, 22 Jan 2009 15:41:54 -0700 Subject: Further migration toward new xmlrcp code; fixed problem with unicode Fault.faultString; fixed problem where ServerProxy method was not called correctly --- ipalib/base.py | 2 +- ipalib/cli.py | 9 ++++-- ipalib/frontend.py | 20 +++++++------ ipalib/plugins/misc.py | 2 +- ipalib/rpc.py | 19 +++++++++---- ipaserver/rpcserver.py | 12 ++++++-- lite-xmlrpc2.py | 54 ++++++++++++++++++++++++++++++++++++ tests/test_ipalib/test_frontend.py | 2 -- tests/test_ipalib/test_parameters.py | 2 ++ tests/test_ipalib/test_rpc.py | 18 ++++++------ 10 files changed, 108 insertions(+), 32 deletions(-) create mode 100755 lite-xmlrpc2.py diff --git a/ipalib/base.py b/ipalib/base.py index bff8f1951..e0951e41e 100644 --- a/ipalib/base.py +++ b/ipalib/base.py @@ -455,7 +455,7 @@ class NameSpace(ReadOnly): :param key: The name or index of a member, or a slice object. """ - if type(key) is str: + if isinstance(key, basestring): return self.__map[key] if type(key) in (int, slice): return self.__members[key] diff --git a/ipalib/cli.py b/ipalib/cli.py index 809c0e551..62b8b9304 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -35,6 +35,7 @@ import struct import frontend import backend import errors +import errors2 import plugable import util from constants import CLI_TAB @@ -534,9 +535,9 @@ class CLI(object): print '' self.api.log.info('operation aborted') sys.exit() - except errors.IPAError, e: - self.api.log.error(unicode(e)) - sys.exit(e.faultCode) + except errors2.PublicError, e: + self.api.log.error(e.strerror) + sys.exit(e.errno) def run_real(self): """ @@ -620,6 +621,8 @@ class CLI(object): (c.name.replace('_', '-'), c) for c in self.api.Command() ) self.textui = self.api.Backend.textui + if self.api.env.in_server is False and 'xmlclient' in self.api.Backend: + self.api.Backend.xmlclient.connect() def load_plugins(self): """ diff --git a/ipalib/frontend.py b/ipalib/frontend.py index 2277c7a09..6efccbb4d 100644 --- a/ipalib/frontend.py +++ b/ipalib/frontend.py @@ -96,14 +96,14 @@ class Command(plugable.Plugin): If not in a server context, the call will be forwarded over XML-RPC and the executed an the nearest IPA server. """ - self.debug(make_repr(self.name, *args, **options)) params = self.args_options_2_params(*args, **options) params = self.normalize(**params) params = self.convert(**params) params.update(self.get_default(**params)) self.validate(**params) (args, options) = self.params_2_args_options(**params) - self.debug(make_repr(self.name, *args, **options)) + # FIXME: don't log passords! + self.info(make_repr(self.name, *args, **options)) result = self.run(*args, **options) self.debug('%s result: %r', self.name, result) return result @@ -200,6 +200,11 @@ class Command(plugable.Plugin): (k, self.params[k].convert(v)) for (k, v) in kw.iteritems() ) + def __convert_iter(self, kw): + for param in self.params(): + if kw.get(param.name, None) is None: + continue + def get_default(self, **kw): """ Return a dictionary of defaults for all missing required values. @@ -245,7 +250,7 @@ class Command(plugable.Plugin): elif param.required: raise errors.RequirementError(param.name) - def run(self, *args, **kw): + def run(self, *args, **options): """ Dispatch to `Command.execute` or `Command.forward`. @@ -258,11 +263,8 @@ class Command(plugable.Plugin): performs is executed remotely. """ if self.api.env.in_server: - target = self.execute - else: - target = self.forward - object.__setattr__(self, 'run', target) - return target(*args, **kw) + return self.execute(*args, **options) + return self.forward(*args, **options) def execute(self, *args, **kw): """ @@ -283,7 +285,7 @@ class Command(plugable.Plugin): """ Forward call over XML-RPC to this same command on server. """ - return self.Backend.xmlrpc.forward_call(self.name, *args, **kw) + return self.Backend.xmlclient.forward(self.name, *args, **kw) def finalize(self): """ diff --git a/ipalib/plugins/misc.py b/ipalib/plugins/misc.py index a2f0fa4e4..327e52b56 100644 --- a/ipalib/plugins/misc.py +++ b/ipalib/plugins/misc.py @@ -22,7 +22,7 @@ Misc frontend plugins. """ import re -from ipalib import api, LocalOrRemote +from ipalib import api, LocalOrRemote, Bytes diff --git a/ipalib/rpc.py b/ipalib/rpc.py index e845b8939..aa8002094 100644 --- a/ipalib/rpc.py +++ b/ipalib/rpc.py @@ -135,8 +135,13 @@ def xml_dumps(params, methodname=None, methodresponse=False, encoding='UTF-8'): allow_none=True, ) +def decode_fault(e, encoding='UTF-8'): + assert isinstance(e, Fault) + if type(e.faultString) is str: + return Fault(e.faultCode, e.faultString.decode(encoding)) + return e -def xml_loads(data): +def xml_loads(data, encoding='UTF-8'): """ Decode the XML-RPC packet in ``data``, transparently unwrapping its params. @@ -159,8 +164,11 @@ def xml_loads(data): :param data: The XML-RPC packet to decode. """ - (params, method) = loads(data) - return (xml_unwrap(params), method) + try: + (params, method) = loads(data) + return (xml_unwrap(params), method) + except Fault, e: + raise decode_fault(e) class KerbTransport(SafeTransport): @@ -211,8 +219,8 @@ class xmlclient(Backend): ) ) conn = ServerProxy(self.env.xmlrpc_uri, - transport=KerbTransport(), allow_none=True, + encoding='UTF-8', ) setattr(context, self.connection_name, conn) @@ -243,9 +251,10 @@ class xmlclient(Backend): command = getattr(context.xmlconn, name) params = args + (kw,) try: - response = command(xml_wrap(params)) + response = command(*xml_wrap(params)) return xml_unwrap(response) except Fault, e: + e = decode_fault(e) self.debug('Caught fault %d from server %s: %s', e.faultCode, self.env.xmlrpc_uri, e.faultString) if e.faultCode in self.__errors: diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py index 225173675..f5fb3c623 100644 --- a/ipaserver/rpcserver.py +++ b/ipaserver/rpcserver.py @@ -27,6 +27,7 @@ from xmlrpclib import Fault from ipalib import Backend from ipalib.errors2 import PublicError, InternalError, CommandError from ipalib.rpc import xml_dumps, xml_loads +from ipalib.util import make_repr def params_2_args_options(params): @@ -47,21 +48,26 @@ class xmlserver(Backend): self.debug('Received RPC call to %r', method) if method not in self.Command: raise CommandError(name=method) + self.info('params = %r', params) (args, options) = params_2_args_options(params) + self.info('args = %r', args) + self.info('options = %r', options) + self.debug(make_repr(method, *args, **options)) result = self.Command[method](*args, **options) return (result,) # Must wrap XML-RPC response in a tuple singleton - def execute(self, data, ccache=None, client_version=None, - client_ip=None, languages=None): + def execute(self, data): """ Execute the XML-RPC request in contained in ``data``. """ try: (params, method) = xml_loads(data) response = self.dispatch(method, params) + print 'okay' except Exception, e: if not isinstance(e, PublicError): e = InternalError() assert isinstance(e, PublicError) + self.debug('Returning %r exception', e.__class__.__name__) response = Fault(e.errno, e.strerror) - return dumps(response) + return xml_dumps(response, methodresponse=True) diff --git a/lite-xmlrpc2.py b/lite-xmlrpc2.py new file mode 100755 index 000000000..cb216456c --- /dev/null +++ b/lite-xmlrpc2.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +# Authors: +# Jason Gerard DeRose +# +# Copyright (C) 2008 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" +In-tree XML-RPC server using SimpleXMLRPCServer. +""" + +from SimpleXMLRPCServer import SimpleXMLRPCServer, SimpleXMLRPCRequestHandler +from ipalib import api + +api.bootstrap_with_global_options(context='server') +api.finalize() + + +class Instance(object): + def _listMethods(self): + return list(api.Command) + + +class Server(SimpleXMLRPCServer): + def _marshaled_dispatch(self, data, dispatch_method=None): + return api.Backend.xmlserver.execute(data) + + +address = ('', api.env.lite_xmlrpc_port) +server = Server(address, + logRequests=False, + allow_none=True, + encoding='UTF-8', +) +server.register_introspection_functions() +server.register_instance(Instance()) +try: + server.serve_forever() +except KeyboardInterrupt: + api.log.info('Server stopped') diff --git a/tests/test_ipalib/test_frontend.py b/tests/test_ipalib/test_frontend.py index 1e90dbb34..72fc889e6 100644 --- a/tests/test_ipalib/test_frontend.py +++ b/tests/test_ipalib/test_frontend.py @@ -378,7 +378,6 @@ class test_Command(ClassChecker): o.set_api(api) assert o.run.im_func is self.cls.run.im_func assert ('execute', args, kw) == o.run(*args, **kw) - assert o.run.im_func is my_cmd.execute.im_func # Test in non-server context (api, home) = create_test_api(in_server=False) @@ -387,7 +386,6 @@ class test_Command(ClassChecker): o.set_api(api) assert o.run.im_func is self.cls.run.im_func assert ('forward', args, kw) == o.run(*args, **kw) - assert o.run.im_func is my_cmd.forward.im_func class test_LocalOrRemote(ClassChecker): diff --git a/tests/test_ipalib/test_parameters.py b/tests/test_ipalib/test_parameters.py index e9e514eb1..b3ab996df 100644 --- a/tests/test_ipalib/test_parameters.py +++ b/tests/test_ipalib/test_parameters.py @@ -335,6 +335,7 @@ class test_Param(ClassChecker): o = Subclass('my_param') for value in NULLS: assert o.convert(value) is None + assert o.convert(None) is None for value in okay: assert o.convert(value) is value @@ -821,6 +822,7 @@ class test_Str(ClassChecker): assert e.name == 'my_str' assert e.index == 18 assert_equal(e.error, u'must be Unicode text') + assert o.convert(None) is None def test_rule_minlength(self): """ diff --git a/tests/test_ipalib/test_rpc.py b/tests/test_ipalib/test_rpc.py index 30175e3bf..351f483be 100644 --- a/tests/test_ipalib/test_rpc.py +++ b/tests/test_ipalib/test_rpc.py @@ -171,11 +171,13 @@ def test_xml_loads(): assert_equal(tup[0], params) # Test un-serializing an RPC response containing a Fault: - fault = Fault(69, unicode_str) - data = dumps(fault, methodresponse=True, allow_none=True) - e = raises(Fault, f, data) - assert e.faultCode == 69 - assert_equal(e.faultString, unicode_str) + for error in (unicode_str, u'hello'): + fault = Fault(69, error) + data = dumps(fault, methodresponse=True, allow_none=True, encoding='UTF-8') + e = raises(Fault, f, data) + assert e.faultCode == 69 + assert_equal(e.faultString, error) + assert type(e.faultString) is unicode class test_xmlclient(PluginTester): @@ -227,19 +229,19 @@ class test_xmlclient(PluginTester): context.xmlconn = DummyClass( ( 'user_add', - (rpc.xml_wrap(params),), + rpc.xml_wrap(params), {}, rpc.xml_wrap(result), ), ( 'user_add', - (rpc.xml_wrap(params),), + rpc.xml_wrap(params), {}, Fault(3007, u"'four' is required"), # RequirementError ), ( 'user_add', - (rpc.xml_wrap(params),), + rpc.xml_wrap(params), {}, Fault(700, u'no such error'), # There is no error 700 ), -- cgit