Ported xmlclient to subclass from Connectible

This commit is contained in:
Jason Gerard DeRose 2009-01-23 18:02:32 -07:00 committed by Rob Crittenden
parent 0a3ae60038
commit 66b6029e40
5 changed files with 105 additions and 110 deletions

View File

@ -36,26 +36,36 @@ class Backend(plugable.Plugin):
class Connectible(Backend): class Connectible(Backend):
# Override in subclass:
connection_klass = None
def connect(self, *args, **kw): def connect(self, *args, **kw):
""" """
Create thread-local connection. Create thread-local connection.
""" """
if hasattr(context, self.name): if hasattr(context, self.name):
raise StandardError( raise StandardError(
"connection 'context.%s' already exists in thread %r" % ( "connect: 'context.%s' already exists in thread %r" % (
self.name, threading.currentThread().getName() self.name, threading.currentThread().getName()
) )
) )
if not issubclass(self.connection_klass, Connection): conn = self.create_connection(*args, **kw)
raise ValueError( setattr(context, self.name, Connection(conn, self.disconnect))
'%s.connection_klass must be a request.Connection subclass' % self.name assert self.conn is conn
self.info('Created connection context.%s' % self.name)
def create_connection(self, *args, **kw):
raise NotImplementedError('%s.create_connection()' % self.name)
def disconnect(self):
if not hasattr(context, self.name):
raise StandardError(
"disconnect: 'context.%s' does not exist in thread %r" % (
self.name, threading.currentThread().getName()
) )
conn = self.connection_klass(*args, **kw) )
setattr(context, self.name, conn) self.destroy_connection()
assert self.conn is conn.conn self.info('Destroyed connection context.%s' % self.name)
def destroy_connection(self):
raise NotImplementedError('%s.destroy_connection()' % self.name)
def isconnected(self): def isconnected(self):
""" """

View File

@ -26,7 +26,7 @@ import threading
import locale import locale
import gettext import gettext
from base import ReadOnly, lock from base import ReadOnly, lock
from constants import OVERRIDE_ERROR from constants import OVERRIDE_ERROR, CALLABLE_ERROR
# Thread-local storage of most per-request information # Thread-local storage of most per-request information
@ -38,22 +38,15 @@ class Connection(ReadOnly):
Base class for connection objects stored on `request.context`. Base class for connection objects stored on `request.context`.
""" """
def __init__(self, *args, **kw): def __init__(self, conn, disconnect):
self.conn = self.create(*args, **kw) self.conn = conn
if not callable(disconnect):
raise TypeError(
CALLABLE_ERROR % ('disconnect', disconnect, type(disconnect))
)
self.disconnect = disconnect
lock(self) lock(self)
def create(self, *args, **kw):
"""
Create and return the connection (implement in subclass).
"""
raise NotImplementedError('%s.create()' % self.__class__.__name__)
def close(self):
"""
Close the connection (implement in subclass).
"""
raise NotImplementedError('%s.close()' % self.__class__.__name__)
def destroy_context(): def destroy_context():
""" """
@ -61,7 +54,7 @@ def destroy_context():
""" """
for (name, value) in context.__dict__.items(): for (name, value) in context.__dict__.items():
if isinstance(value, Connection): if isinstance(value, Connection):
value.close() value.disconnect()
delattr(context, name) delattr(context, name)

View File

@ -35,7 +35,7 @@ import threading
import socket import socket
from xmlrpclib import Binary, Fault, dumps, loads, ServerProxy, SafeTransport from xmlrpclib import Binary, Fault, dumps, loads, ServerProxy, SafeTransport
import kerberos import kerberos
from ipalib.backend import Backend from ipalib.backend import Connectible
from ipalib.errors2 import public_errors, PublicError, UnknownError, NetworkError from ipalib.errors2 import public_errors, PublicError, UnknownError, NetworkError
from ipalib import errors2 from ipalib import errors2
from ipalib.request import context from ipalib.request import context
@ -205,35 +205,26 @@ class KerbTransport(SafeTransport):
return (host, extra_headers, x509) return (host, extra_headers, x509)
class xmlclient(Backend): class xmlclient(Connectible):
""" """
Forwarding backend plugin for XML-RPC client. Forwarding backend plugin for XML-RPC client.
Also see the `ipaserver.rpcserver.xmlserver` plugin. Also see the `ipaserver.rpcserver.xmlserver` plugin.
""" """
connection_name = 'xmlconn'
def __init__(self): def __init__(self):
super(xmlclient, self).__init__() super(xmlclient, self).__init__()
self.__errors = dict((e.errno, e) for e in public_errors) self.__errors = dict((e.errno, e) for e in public_errors)
def connect(self, ccache=None, user=None, password=None): def create_connection(self, ccache=None):
if hasattr(context, self.connection_name): return ServerProxy(self.env.xmlrpc_uri,
raise StandardError(
'%s.connect(): context.%s already exists in thread %r' % (
self.name, self.connection_name, threading.currentThread().getName()
)
)
conn = ServerProxy(self.env.xmlrpc_uri,
#transport=KerbTransport(), #transport=KerbTransport(),
allow_none=True, allow_none=True,
encoding='UTF-8', encoding='UTF-8',
) )
setattr(context, self.connection_name, conn)
def get_connection(self): def destroy_connection(self):
return getattr(context, self.connection_name) pass
def forward(self, name, *args, **kw): def forward(self, name, *args, **kw):
""" """
@ -250,13 +241,7 @@ class xmlclient(Backend):
raise ValueError( raise ValueError(
'%s.forward(): %r not in api.Command' % (self.name, name) '%s.forward(): %r not in api.Command' % (self.name, name)
) )
if not hasattr(context, 'xmlconn'): command = getattr(self.conn, name)
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,) params = args + (kw,)
try: try:
response = command(*xml_wrap(params)) response = command(*xml_wrap(params))

View File

@ -42,17 +42,12 @@ class test_Backend(ClassChecker):
assert self.cls.__proxy__ is False assert self.cls.__proxy__ is False
class DummyConnection(Connection): class Disconnect(object):
called = False
def create(self, *args, **kw): def __call__(self):
self.args = args assert self.called is False
self.kw = kw self.called = True
self.closed = False
return 'The connection'
def close(self):
assert self.closed is False
object.__setattr__(self, 'closed', True)
class test_Connectible(ClassChecker): class test_Connectible(ClassChecker):
@ -66,31 +61,26 @@ class test_Connectible(ClassChecker):
""" """
Test the `ipalib.backend.Connectible.connect` method. Test the `ipalib.backend.Connectible.connect` method.
""" """
# Test that TypeError is raised when connection_klass isn't a
# Connection subclass:
class bad(self.cls):
connection_klass = base.ReadOnly
o = bad()
m = '%s.connection_klass must be a request.Connection subclass'
e = raises(ValueError, o.connect)
assert str(e) == m % 'bad'
# Test that connection is created: # Test that connection is created:
class example(self.cls): class example(self.cls):
connection_klass = DummyConnection def create_connection(self, *args, **kw):
object.__setattr__(self, 'args', args)
object.__setattr__(self, 'kw', kw)
return 'The connection.'
o = example() o = example()
args = ('Arg1', 'Arg2', 'Arg3') args = ('Arg1', 'Arg2', 'Arg3')
kw = dict(key1='Val1', key2='Val2', key3='Val3') kw = dict(key1='Val1', key2='Val2', key3='Val3')
assert not hasattr(context, 'example') assert not hasattr(context, 'example')
assert o.connect(*args, **kw) is None assert o.connect(*args, **kw) is None
conn = context.example conn = context.example
assert type(conn) is DummyConnection assert type(conn) is Connection
assert conn.args == args assert o.args == args
assert conn.kw == kw assert o.kw == kw
assert conn.conn == 'The connection' assert conn.conn == 'The connection.'
assert conn.disconnect == o.disconnect
# Test that StandardError is raised if already connected: # Test that StandardError is raised if already connected:
m = "connection 'context.%s' already exists in thread %r" m = "connect: 'context.%s' already exists in thread %r"
e = raises(StandardError, o.connect, *args, **kw) e = raises(StandardError, o.connect, *args, **kw)
assert str(e) == m % ('example', threading.currentThread().getName()) assert str(e) == m % ('example', threading.currentThread().getName())
@ -98,6 +88,44 @@ class test_Connectible(ClassChecker):
del context.example del context.example
assert o.connect(*args, **kw) is None assert o.connect(*args, **kw) is None
def test_create_connection(self):
"""
Test the `ipalib.backend.Connectible.create_connection` method.
"""
class example(self.cls):
pass
for klass in (self.cls, example):
o = klass()
e = raises(NotImplementedError, o.create_connection)
assert str(e) == '%s.create_connection()' % klass.__name__
def test_disconnect(self):
"""
Test the `ipalib.backend.Connectible.disconnect` method.
"""
class example(self.cls):
destroy_connection = Disconnect()
o = example()
m = "disconnect: 'context.%s' does not exist in thread %r"
e = raises(StandardError, o.disconnect)
assert str(e) == m % ('example', threading.currentThread().getName())
context.example = 'The connection.'
assert o.disconnect() is None
assert example.destroy_connection.called is True
def test_destroy_connection(self):
"""
Test the `ipalib.backend.Connectible.destroy_connection` method.
"""
class example(self.cls):
pass
for klass in (self.cls, example):
o = klass()
e = raises(NotImplementedError, o.destroy_connection)
assert str(e) == '%s.destroy_connection()' % klass.__name__
def test_isconnected(self): def test_isconnected(self):
""" """
Test the `ipalib.backend.Connectible.isconnected` method. Test the `ipalib.backend.Connectible.isconnected` method.
@ -107,7 +135,7 @@ class test_Connectible(ClassChecker):
for klass in (self.cls, example): for klass in (self.cls, example):
o = klass() o = klass()
assert o.isconnected() is False assert o.isconnected() is False
conn = DummyConnection() conn = 'whatever'
setattr(context, klass.__name__, conn) setattr(context, klass.__name__, conn)
assert o.isconnected() is True assert o.isconnected() is True
delattr(context, klass.__name__) delattr(context, klass.__name__)
@ -125,7 +153,7 @@ class test_Connectible(ClassChecker):
assert str(e) == msg % ( assert str(e) == msg % (
klass.__name__, threading.currentThread().getName() klass.__name__, threading.currentThread().getName()
) )
conn = DummyConnection() conn = Connection('The connection.', Disconnect())
setattr(context, klass.__name__, conn) setattr(context, klass.__name__, conn)
assert o.conn is conn.conn assert o.conn is conn.conn
delattr(context, klass.__name__) delattr(context, klass.__name__)
@ -170,11 +198,11 @@ class test_Executioner(ClassChecker):
o.finalize() o.finalize()
# Test that CommandError is raised: # Test that CommandError is raised:
conn = DummyConnection() conn = Connection('The connection.', Disconnect())
context.someconn = conn context.someconn = conn
e = raises(errors2.CommandError, o.execute, 'nope') e = raises(errors2.CommandError, o.execute, 'nope')
assert e.name == 'nope' assert e.name == 'nope'
assert conn.closed is True # Make sure destroy_context() was called assert conn.disconnect.called is True # Make sure destroy_context() was called
assert context.__dict__.keys() == [] assert context.__dict__.keys() == []
# Test with echo command: # Test with echo command:
@ -183,30 +211,30 @@ class test_Executioner(ClassChecker):
args = (arg1,) + arg2 args = (arg1,) + arg2
options = dict(option1=u'How are you?', option2=unicode_str) options = dict(option1=u'How are you?', option2=unicode_str)
conn = DummyConnection() conn = Connection('The connection.', Disconnect())
context.someconn = conn context.someconn = conn
assert o.execute('echo', arg1, arg2, **options) == (arg1, arg2, options) assert o.execute('echo', arg1, arg2, **options) == (arg1, arg2, options)
assert conn.closed is True # Make sure destroy_context() was called assert conn.disconnect.called is True # Make sure destroy_context() was called
assert context.__dict__.keys() == [] assert context.__dict__.keys() == []
conn = DummyConnection() conn = Connection('The connection.', Disconnect())
context.someconn = conn context.someconn = conn
assert o.execute('echo', *args, **options) == (arg1, arg2, options) assert o.execute('echo', *args, **options) == (arg1, arg2, options)
assert conn.closed is True # Make sure destroy_context() was called assert conn.disconnect.called is True # Make sure destroy_context() was called
assert context.__dict__.keys() == [] assert context.__dict__.keys() == []
# Test with good command: # Test with good command:
conn = DummyConnection() conn = Connection('The connection.', Disconnect())
context.someconn = conn context.someconn = conn
e = raises(errors2.ValidationError, o.execute, 'good') e = raises(errors2.ValidationError, o.execute, 'good')
assert e.name == 'nurse' assert e.name == 'nurse'
assert e.error == u'Not naughty!' assert e.error == u'Not naughty!'
assert conn.closed is True # Make sure destroy_context() was called assert conn.disconnect.called is True # Make sure destroy_context() was called
assert context.__dict__.keys() == [] assert context.__dict__.keys() == []
# Test with bad command: # Test with bad command:
conn = DummyConnection() conn = Connection('The connection.', Disconnect())
context.someconn = conn context.someconn = conn
e = raises(errors2.InternalError, o.execute, 'bad') e = raises(errors2.InternalError, o.execute, 'bad')
assert conn.closed is True # Make sure destroy_context() was called assert conn.disconnect.called is True # Make sure destroy_context() was called
assert context.__dict__.keys() == [] assert context.__dict__.keys() == []

View File

@ -26,7 +26,7 @@ from xmlrpclib import Binary, Fault, dumps, loads, ServerProxy
from tests.util import raises, assert_equal, PluginTester, DummyClass 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.frontend import Command from ipalib.frontend import Command
from ipalib.request import context from ipalib.request import context, Connection
from ipalib import rpc, errors2 from ipalib import rpc, errors2
@ -186,20 +186,6 @@ class test_xmlclient(PluginTester):
""" """
_plugin = rpc.xmlclient _plugin = rpc.xmlclient
def test_connect(self):
(o, api, home) = self.instance('Backend', in_server=False)
# Test that StandardError is raised if conntext.xmlconn already exists:
context.xmlconn = 'The xmlrpclib.ServerProxy instance'
e = raises(StandardError, o.connect)
assert str(e) == '%s.connect(): context.%s already exists in thread %r' % (
'xmlclient', 'xmlconn', threading.currentThread().getName()
)
del context.xmlconn
o.connect()
assert isinstance(context.xmlconn, ServerProxy)
def test_forward(self): def test_forward(self):
""" """
Test the `ipalib.rpc.xmlclient.forward` method. Test the `ipalib.rpc.xmlclient.forward` method.
@ -215,18 +201,12 @@ class test_xmlclient(PluginTester):
'xmlclient', 'user_add' '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) (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) args = (binary_bytes, utf8_bytes, unicode_str)
kw = dict(one=binary_bytes, two=utf8_bytes, three=unicode_str) kw = dict(one=binary_bytes, two=utf8_bytes, three=unicode_str)
params = args + (kw,) params = args + (kw,)
result = (unicode_str, binary_bytes, utf8_bytes) result = (unicode_str, binary_bytes, utf8_bytes)
context.xmlconn = DummyClass( conn = DummyClass(
( (
'user_add', 'user_add',
rpc.xml_wrap(params), rpc.xml_wrap(params),
@ -247,6 +227,7 @@ class test_xmlclient(PluginTester):
), ),
) )
context.xmlclient = Connection(conn, lambda: None)
# Test with a successful return value: # Test with a successful return value:
assert o.forward('user_add', *args, **kw) == result assert o.forward('user_add', *args, **kw) == result
@ -260,6 +241,4 @@ class test_xmlclient(PluginTester):
assert_equal(e.code, 700) assert_equal(e.code, 700)
assert_equal(e.error, u'no such error') assert_equal(e.error, u'no such error')
assert context.xmlconn._calledall() is True assert context.xmlclient.conn._calledall() is True
del context.xmlconn