Further migration toward new xmlrcp code; fixed problem with unicode Fault.faultString; fixed problem where ServerProxy method was not called correctly

This commit is contained in:
Jason Gerard DeRose 2009-01-22 15:41:54 -07:00 committed by Rob Crittenden
parent 9f48612a56
commit 24b6cb89d4
10 changed files with 108 additions and 32 deletions

View File

@ -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]

View File

@ -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):
"""

View File

@ -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):
"""

View File

@ -22,7 +22,7 @@ Misc frontend plugins.
"""
import re
from ipalib import api, LocalOrRemote
from ipalib import api, LocalOrRemote, Bytes

View File

@ -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:

View File

@ -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)

54
lite-xmlrpc2.py Executable file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env python
# Authors:
# Jason Gerard DeRose <jderose@redhat.com>
#
# 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')

View File

@ -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):

View File

@ -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):
"""

View File

@ -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
),