mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-23 15:40:01 -06:00
Added rpc.xmlclient backend plugin for forwarding; added corresponding unit tests
This commit is contained in:
parent
bae9dd7c07
commit
55fba5420d
@ -275,10 +275,27 @@ class VersionError(PublicError):
|
||||
format = _('%(cver)s client incompatible with %(sver)s server at %(server)r')
|
||||
|
||||
|
||||
class UnknownError(PublicError):
|
||||
"""
|
||||
**902** Raised when client does not know error it caught from server.
|
||||
|
||||
For example:
|
||||
|
||||
>>> raise UnknownError(code=57, server='localhost', error=u'a new error')
|
||||
...
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
UnknownError: unknown error 57 from localhost: a new error
|
||||
|
||||
"""
|
||||
|
||||
errno = 902
|
||||
format = _('unknown error %(code)d from %(server)s: %(error)s')
|
||||
|
||||
|
||||
class InternalError(PublicError):
|
||||
"""
|
||||
**902** Raised to conceal a non-public exception.
|
||||
**903** Raised to conceal a non-public exception.
|
||||
|
||||
For example:
|
||||
|
||||
@ -288,7 +305,7 @@ class InternalError(PublicError):
|
||||
InternalError: an internal error has occured
|
||||
"""
|
||||
|
||||
errno = 902
|
||||
errno = 903
|
||||
format = _('an internal error has occured')
|
||||
|
||||
def __init__(self, message=None):
|
||||
@ -300,7 +317,7 @@ class InternalError(PublicError):
|
||||
|
||||
class ServerInternalError(PublicError):
|
||||
"""
|
||||
**903** Raised when client catches an `InternalError` from server.
|
||||
**904** Raised when client catches an `InternalError` from server.
|
||||
|
||||
For example:
|
||||
|
||||
@ -310,13 +327,13 @@ class ServerInternalError(PublicError):
|
||||
ServerInternalError: an internal error has occured on server at 'https://localhost'
|
||||
"""
|
||||
|
||||
errno = 903
|
||||
errno = 904
|
||||
format = _('an internal error has occured on server at %(server)r')
|
||||
|
||||
|
||||
class CommandError(PublicError):
|
||||
"""
|
||||
**904** Raised when an unknown command is called.
|
||||
**905** Raised when an unknown command is called.
|
||||
|
||||
For example:
|
||||
|
||||
@ -326,13 +343,13 @@ class CommandError(PublicError):
|
||||
CommandError: unknown command 'foobar'
|
||||
"""
|
||||
|
||||
errno = 904
|
||||
errno = 905
|
||||
format = _('unknown command %(name)r')
|
||||
|
||||
|
||||
class ServerCommandError(PublicError):
|
||||
"""
|
||||
**905** Raised when client catches a `CommandError` from server.
|
||||
**906** Raised when client catches a `CommandError` from server.
|
||||
|
||||
For example:
|
||||
|
||||
@ -343,13 +360,13 @@ class ServerCommandError(PublicError):
|
||||
ServerCommandError: error on server 'https://localhost': unknown command 'foobar'
|
||||
"""
|
||||
|
||||
errno = 905
|
||||
errno = 906
|
||||
format = _('error on server %(server)r: %(error)s')
|
||||
|
||||
|
||||
class NetworkError(PublicError):
|
||||
"""
|
||||
**906** Raised when a network connection cannot be created.
|
||||
**907** Raised when a network connection cannot be created.
|
||||
|
||||
For example:
|
||||
|
||||
@ -359,13 +376,13 @@ class NetworkError(PublicError):
|
||||
NetworkError: cannot connect to 'ldap://localhost:389'
|
||||
"""
|
||||
|
||||
errno = 906
|
||||
errno = 907
|
||||
format = _('cannot connect to %(uri)r')
|
||||
|
||||
|
||||
class ServerNetworkError(PublicError):
|
||||
"""
|
||||
**907** Raised when client catches a `NetworkError` from server.
|
||||
**908** Raised when client catches a `NetworkError` from server.
|
||||
|
||||
For example:
|
||||
|
||||
@ -376,7 +393,7 @@ class ServerNetworkError(PublicError):
|
||||
ServerNetworkError: error on server 'https://localhost': cannot connect to 'ldap://localhost:389'
|
||||
"""
|
||||
|
||||
errno = 907
|
||||
errno = 908
|
||||
format = _('error on server %(server)r: %(error)s')
|
||||
|
||||
|
||||
|
@ -30,7 +30,11 @@ Also see the `ipaserver.rpcserver` module.
|
||||
"""
|
||||
|
||||
from types import NoneType
|
||||
import threading
|
||||
from xmlrpclib import Binary, Fault, dumps, loads
|
||||
from ipalib.backend import Backend
|
||||
from ipalib.errors2 import public_errors, PublicError, UnknownError
|
||||
from ipalib.request import context
|
||||
|
||||
|
||||
def xml_wrap(value):
|
||||
@ -155,3 +159,49 @@ def xml_loads(data):
|
||||
"""
|
||||
(params, method) = loads(data)
|
||||
return (xml_unwrap(params), method)
|
||||
|
||||
|
||||
class xmlclient(Backend):
|
||||
"""
|
||||
Forwarding backend for XML-RPC client.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super(xmlclient, self).__init__()
|
||||
self.__errors = dict((e.errno, e) for e in public_errors)
|
||||
|
||||
def forward(self, name, *args, **kw):
|
||||
"""
|
||||
Forward call to command named ``name`` over XML-RPC.
|
||||
|
||||
This method will encode and forward an XML-RPC request, and will then
|
||||
decode and return the corresponding XML-RPC response.
|
||||
|
||||
:param command: The name of the command being forwarded.
|
||||
:param args: Positional arguments to pass to remote command.
|
||||
:param kw: Keyword arguments to pass to remote command.
|
||||
"""
|
||||
if name not in self.Command:
|
||||
raise ValueError(
|
||||
'%s.forward(): %r not in api.Command' % (self.name, name)
|
||||
)
|
||||
if not hasattr(context, 'xmlconn'):
|
||||
raise StandardError(
|
||||
'%s.forward(%r): need context.xmlconn in thread %r' % (
|
||||
self.name, name, threading.currentThread().getName()
|
||||
)
|
||||
)
|
||||
command = getattr(context.xmlconn, name)
|
||||
params = args + (kw,)
|
||||
try:
|
||||
response = command(xml_wrap(params))
|
||||
return xml_unwrap(response)
|
||||
except Fault, e:
|
||||
if e.faultCode in self.__errors:
|
||||
error = self.__errors[e.faultCode]
|
||||
raise error(message=e.faultString)
|
||||
raise UnknownError(
|
||||
code=e.faultCode,
|
||||
error=e.faultString,
|
||||
server=self.env.xmlrpc_uri,
|
||||
)
|
||||
|
@ -44,8 +44,6 @@ class xmlserver(Backend):
|
||||
"""
|
||||
|
||||
def dispatch(self, method, params):
|
||||
assert type(method) is str
|
||||
assert type(params) is tuple
|
||||
self.debug('Received RPC call to %r', method)
|
||||
if method not in self.Command:
|
||||
raise CommandError(name=method)
|
||||
|
@ -21,10 +21,16 @@
|
||||
Test the `ipalib.rpc` module.
|
||||
"""
|
||||
|
||||
import threading
|
||||
from xmlrpclib import Binary, Fault, dumps, loads
|
||||
from tests.util import raises, assert_equal
|
||||
from tests.util import raises, assert_equal, PluginTester, DummyClass
|
||||
from tests.data import binary_bytes, utf8_bytes, unicode_str
|
||||
from ipalib import rpc
|
||||
from ipalib.frontend import Command
|
||||
from ipalib.request import context
|
||||
from ipalib import rpc, errors2
|
||||
|
||||
|
||||
std_compound = (binary_bytes, utf8_bytes, unicode_str)
|
||||
|
||||
|
||||
def dump_n_load(value):
|
||||
@ -170,3 +176,74 @@ def test_xml_loads():
|
||||
e = raises(Fault, f, data)
|
||||
assert e.faultCode == 69
|
||||
assert_equal(e.faultString, unicode_str)
|
||||
|
||||
|
||||
class test_xmlclient(PluginTester):
|
||||
"""
|
||||
Test the `ipalib.rpc.xmlclient` plugin.
|
||||
"""
|
||||
_plugin = rpc.xmlclient
|
||||
|
||||
def test_forward(self):
|
||||
"""
|
||||
Test the `ipalib.rpc.xmlclient.forward` method.
|
||||
"""
|
||||
class user_add(Command):
|
||||
pass
|
||||
|
||||
# Test that ValueError is raised when forwarding a command that is not
|
||||
# in api.Command:
|
||||
(o, api, home) = self.instance('Backend', in_server=False)
|
||||
e = raises(ValueError, o.forward, 'user_add')
|
||||
assert str(e) == '%s.forward(): %r not in api.Command' % (
|
||||
'xmlclient', 'user_add'
|
||||
)
|
||||
|
||||
# Test that StandardError is raised when context.xmlconn does not exist:
|
||||
(o, api, home) = self.instance('Backend', user_add, in_server=False)
|
||||
e = raises(StandardError, o.forward, 'user_add')
|
||||
assert str(e) == '%s.forward(%r): need context.xmlconn in thread %r' % (
|
||||
'xmlclient', 'user_add', threading.currentThread().getName()
|
||||
)
|
||||
|
||||
args = (binary_bytes, utf8_bytes, unicode_str)
|
||||
kw = dict(one=binary_bytes, two=utf8_bytes, three=unicode_str)
|
||||
params = args + (kw,)
|
||||
result = (unicode_str, binary_bytes, utf8_bytes)
|
||||
context.xmlconn = DummyClass(
|
||||
(
|
||||
'user_add',
|
||||
(rpc.xml_wrap(params),),
|
||||
{},
|
||||
rpc.xml_wrap(result),
|
||||
),
|
||||
(
|
||||
'user_add',
|
||||
(rpc.xml_wrap(params),),
|
||||
{},
|
||||
Fault(3005, u"'four' is required"), # RequirementError
|
||||
),
|
||||
(
|
||||
'user_add',
|
||||
(rpc.xml_wrap(params),),
|
||||
{},
|
||||
Fault(700, u'no such error'), # There is no error 700
|
||||
),
|
||||
|
||||
)
|
||||
|
||||
# Test with a successful return value:
|
||||
assert o.forward('user_add', *args, **kw) == result
|
||||
|
||||
# Test with an errno the client knows:
|
||||
e = raises(errors2.RequirementError, o.forward, 'user_add', *args, **kw)
|
||||
assert_equal(e.message, u"'four' is required")
|
||||
|
||||
# Test with an errno the client doesn't know
|
||||
e = raises(errors2.UnknownError, o.forward, 'user_add', *args, **kw)
|
||||
assert_equal(e.code, 700)
|
||||
assert_equal(e.error, u'no such error')
|
||||
|
||||
assert context.xmlconn._calledall() is True
|
||||
|
||||
del context.xmlconn
|
||||
|
@ -344,3 +344,48 @@ class dummy_ungettext(object):
|
||||
if n == 1:
|
||||
return self.translation_singular
|
||||
return self.translation_plural
|
||||
|
||||
|
||||
class DummyMethod(object):
|
||||
def __init__(self, callback, name):
|
||||
self.__callback = callback
|
||||
self.__name = name
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
return self.__callback(self.__name, args, kw)
|
||||
|
||||
|
||||
class DummyClass(object):
|
||||
def __init__(self, *calls):
|
||||
self.__calls = calls
|
||||
self.__i = 0
|
||||
for (name, args, kw, result) in calls:
|
||||
method = DummyMethod(self.__process, name)
|
||||
setattr(self, name, method)
|
||||
|
||||
def __process(self, name_, args_, kw_):
|
||||
if self.__i >= len(self.__calls):
|
||||
raise AssertionError(
|
||||
'extra call: %s, %r, %r' % (name, args, kw)
|
||||
)
|
||||
(name, args, kw, result) = self.__calls[self.__i]
|
||||
self.__i += 1
|
||||
i = self.__i
|
||||
if name_ != name:
|
||||
raise AssertionError(
|
||||
'call %d should be to method %r; got %r' % (i, name, name_)
|
||||
)
|
||||
if args_ != args:
|
||||
raise AssertionError(
|
||||
'call %d to %r should have args %r; got %r' % (i, name, args, args_)
|
||||
)
|
||||
if kw_ != kw:
|
||||
raise AssertionError(
|
||||
'call %d to %r should have kw %r, got %r' % (i, name, kw, kw_)
|
||||
)
|
||||
if isinstance(result, Exception):
|
||||
raise result
|
||||
return result
|
||||
|
||||
def _calledall(self):
|
||||
return self.__i == len(self.__calls)
|
||||
|
Loading…
Reference in New Issue
Block a user