mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-24 16:10:02 -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')
|
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):
|
class InternalError(PublicError):
|
||||||
"""
|
"""
|
||||||
**902** Raised to conceal a non-public exception.
|
**903** Raised to conceal a non-public exception.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
@ -288,7 +305,7 @@ class InternalError(PublicError):
|
|||||||
InternalError: an internal error has occured
|
InternalError: an internal error has occured
|
||||||
"""
|
"""
|
||||||
|
|
||||||
errno = 902
|
errno = 903
|
||||||
format = _('an internal error has occured')
|
format = _('an internal error has occured')
|
||||||
|
|
||||||
def __init__(self, message=None):
|
def __init__(self, message=None):
|
||||||
@ -300,7 +317,7 @@ class InternalError(PublicError):
|
|||||||
|
|
||||||
class ServerInternalError(PublicError):
|
class ServerInternalError(PublicError):
|
||||||
"""
|
"""
|
||||||
**903** Raised when client catches an `InternalError` from server.
|
**904** Raised when client catches an `InternalError` from server.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
@ -310,13 +327,13 @@ class ServerInternalError(PublicError):
|
|||||||
ServerInternalError: an internal error has occured on server at 'https://localhost'
|
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')
|
format = _('an internal error has occured on server at %(server)r')
|
||||||
|
|
||||||
|
|
||||||
class CommandError(PublicError):
|
class CommandError(PublicError):
|
||||||
"""
|
"""
|
||||||
**904** Raised when an unknown command is called.
|
**905** Raised when an unknown command is called.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
@ -326,13 +343,13 @@ class CommandError(PublicError):
|
|||||||
CommandError: unknown command 'foobar'
|
CommandError: unknown command 'foobar'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
errno = 904
|
errno = 905
|
||||||
format = _('unknown command %(name)r')
|
format = _('unknown command %(name)r')
|
||||||
|
|
||||||
|
|
||||||
class ServerCommandError(PublicError):
|
class ServerCommandError(PublicError):
|
||||||
"""
|
"""
|
||||||
**905** Raised when client catches a `CommandError` from server.
|
**906** Raised when client catches a `CommandError` from server.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
@ -343,13 +360,13 @@ class ServerCommandError(PublicError):
|
|||||||
ServerCommandError: error on server 'https://localhost': unknown command 'foobar'
|
ServerCommandError: error on server 'https://localhost': unknown command 'foobar'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
errno = 905
|
errno = 906
|
||||||
format = _('error on server %(server)r: %(error)s')
|
format = _('error on server %(server)r: %(error)s')
|
||||||
|
|
||||||
|
|
||||||
class NetworkError(PublicError):
|
class NetworkError(PublicError):
|
||||||
"""
|
"""
|
||||||
**906** Raised when a network connection cannot be created.
|
**907** Raised when a network connection cannot be created.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
@ -359,13 +376,13 @@ class NetworkError(PublicError):
|
|||||||
NetworkError: cannot connect to 'ldap://localhost:389'
|
NetworkError: cannot connect to 'ldap://localhost:389'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
errno = 906
|
errno = 907
|
||||||
format = _('cannot connect to %(uri)r')
|
format = _('cannot connect to %(uri)r')
|
||||||
|
|
||||||
|
|
||||||
class ServerNetworkError(PublicError):
|
class ServerNetworkError(PublicError):
|
||||||
"""
|
"""
|
||||||
**907** Raised when client catches a `NetworkError` from server.
|
**908** Raised when client catches a `NetworkError` from server.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
@ -376,7 +393,7 @@ class ServerNetworkError(PublicError):
|
|||||||
ServerNetworkError: error on server 'https://localhost': cannot connect to 'ldap://localhost:389'
|
ServerNetworkError: error on server 'https://localhost': cannot connect to 'ldap://localhost:389'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
errno = 907
|
errno = 908
|
||||||
format = _('error on server %(server)r: %(error)s')
|
format = _('error on server %(server)r: %(error)s')
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,7 +30,11 @@ Also see the `ipaserver.rpcserver` module.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from types import NoneType
|
from types import NoneType
|
||||||
|
import threading
|
||||||
from xmlrpclib import Binary, Fault, dumps, loads
|
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):
|
def xml_wrap(value):
|
||||||
@ -155,3 +159,49 @@ def xml_loads(data):
|
|||||||
"""
|
"""
|
||||||
(params, method) = loads(data)
|
(params, method) = loads(data)
|
||||||
return (xml_unwrap(params), method)
|
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):
|
def dispatch(self, method, params):
|
||||||
assert type(method) is str
|
|
||||||
assert type(params) is tuple
|
|
||||||
self.debug('Received RPC call to %r', method)
|
self.debug('Received RPC call to %r', method)
|
||||||
if method not in self.Command:
|
if method not in self.Command:
|
||||||
raise CommandError(name=method)
|
raise CommandError(name=method)
|
||||||
|
@ -21,10 +21,16 @@
|
|||||||
Test the `ipalib.rpc` module.
|
Test the `ipalib.rpc` module.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import threading
|
||||||
from xmlrpclib import Binary, Fault, dumps, loads
|
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 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):
|
def dump_n_load(value):
|
||||||
@ -170,3 +176,74 @@ def test_xml_loads():
|
|||||||
e = raises(Fault, f, data)
|
e = raises(Fault, f, data)
|
||||||
assert e.faultCode == 69
|
assert e.faultCode == 69
|
||||||
assert_equal(e.faultString, unicode_str)
|
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:
|
if n == 1:
|
||||||
return self.translation_singular
|
return self.translation_singular
|
||||||
return self.translation_plural
|
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