summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ipalib/backend.py30
-rw-r--r--ipalib/request.py25
-rw-r--r--ipalib/rpc.py29
-rw-r--r--tests/test_ipalib/test_backend.py102
-rw-r--r--tests/test_ipalib/test_rpc.py29
5 files changed, 105 insertions, 110 deletions
diff --git a/ipalib/backend.py b/ipalib/backend.py
index e286c5079..bb8d31012 100644
--- a/ipalib/backend.py
+++ b/ipalib/backend.py
@@ -36,26 +36,36 @@ class Backend(plugable.Plugin):
class Connectible(Backend):
- # Override in subclass:
- connection_klass = None
-
def connect(self, *args, **kw):
"""
Create thread-local connection.
"""
if hasattr(context, self.name):
raise StandardError(
- "connection 'context.%s' already exists in thread %r" % (
+ "connect: 'context.%s' already exists in thread %r" % (
self.name, threading.currentThread().getName()
)
)
- if not issubclass(self.connection_klass, Connection):
- raise ValueError(
- '%s.connection_klass must be a request.Connection subclass' % self.name
+ conn = self.create_connection(*args, **kw)
+ setattr(context, self.name, Connection(conn, self.disconnect))
+ 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)
- assert self.conn is conn.conn
+ self.destroy_connection()
+ self.info('Destroyed connection context.%s' % self.name)
+
+ def destroy_connection(self):
+ raise NotImplementedError('%s.destroy_connection()' % self.name)
def isconnected(self):
"""
diff --git a/ipalib/request.py b/ipalib/request.py
index 812e526d6..d2c39c911 100644
--- a/ipalib/request.py
+++ b/ipalib/request.py
@@ -26,7 +26,7 @@ import threading
import locale
import gettext
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
@@ -38,22 +38,15 @@ class Connection(ReadOnly):
Base class for connection objects stored on `request.context`.
"""
- def __init__(self, *args, **kw):
- self.conn = self.create(*args, **kw)
+ def __init__(self, conn, disconnect):
+ self.conn = conn
+ if not callable(disconnect):
+ raise TypeError(
+ CALLABLE_ERROR % ('disconnect', disconnect, type(disconnect))
+ )
+ self.disconnect = disconnect
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():
"""
@@ -61,7 +54,7 @@ def destroy_context():
"""
for (name, value) in context.__dict__.items():
if isinstance(value, Connection):
- value.close()
+ value.disconnect()
delattr(context, name)
diff --git a/ipalib/rpc.py b/ipalib/rpc.py
index b6db683e3..a70b372bd 100644
--- a/ipalib/rpc.py
+++ b/ipalib/rpc.py
@@ -35,7 +35,7 @@ import threading
import socket
from xmlrpclib import Binary, Fault, dumps, loads, ServerProxy, SafeTransport
import kerberos
-from ipalib.backend import Backend
+from ipalib.backend import Connectible
from ipalib.errors2 import public_errors, PublicError, UnknownError, NetworkError
from ipalib import errors2
from ipalib.request import context
@@ -205,35 +205,26 @@ class KerbTransport(SafeTransport):
return (host, extra_headers, x509)
-class xmlclient(Backend):
+class xmlclient(Connectible):
"""
Forwarding backend plugin for XML-RPC client.
Also see the `ipaserver.rpcserver.xmlserver` plugin.
"""
- connection_name = 'xmlconn'
-
def __init__(self):
super(xmlclient, self).__init__()
self.__errors = dict((e.errno, e) for e in public_errors)
- def connect(self, ccache=None, user=None, password=None):
- if hasattr(context, self.connection_name):
- 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,
+ def create_connection(self, ccache=None):
+ return ServerProxy(self.env.xmlrpc_uri,
#transport=KerbTransport(),
allow_none=True,
encoding='UTF-8',
)
- setattr(context, self.connection_name, conn)
- def get_connection(self):
- return getattr(context, self.connection_name)
+ def destroy_connection(self):
+ pass
def forward(self, name, *args, **kw):
"""
@@ -250,13 +241,7 @@ class xmlclient(Backend):
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)
+ command = getattr(self.conn, name)
params = args + (kw,)
try:
response = command(*xml_wrap(params))
diff --git a/tests/test_ipalib/test_backend.py b/tests/test_ipalib/test_backend.py
index b7a9765dc..30928532b 100644
--- a/tests/test_ipalib/test_backend.py
+++ b/tests/test_ipalib/test_backend.py
@@ -42,17 +42,12 @@ class test_Backend(ClassChecker):
assert self.cls.__proxy__ is False
-class DummyConnection(Connection):
+class Disconnect(object):
+ called = False
- def create(self, *args, **kw):
- self.args = args
- self.kw = kw
- self.closed = False
- return 'The connection'
-
- def close(self):
- assert self.closed is False
- object.__setattr__(self, 'closed', True)
+ def __call__(self):
+ assert self.called is False
+ self.called = True
class test_Connectible(ClassChecker):
@@ -66,31 +61,26 @@ class test_Connectible(ClassChecker):
"""
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:
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()
args = ('Arg1', 'Arg2', 'Arg3')
kw = dict(key1='Val1', key2='Val2', key3='Val3')
assert not hasattr(context, 'example')
assert o.connect(*args, **kw) is None
conn = context.example
- assert type(conn) is DummyConnection
- assert conn.args == args
- assert conn.kw == kw
- assert conn.conn == 'The connection'
+ assert type(conn) is Connection
+ assert o.args == args
+ assert o.kw == kw
+ assert conn.conn == 'The connection.'
+ assert conn.disconnect == o.disconnect
# 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)
assert str(e) == m % ('example', threading.currentThread().getName())
@@ -98,6 +88,44 @@ class test_Connectible(ClassChecker):
del context.example
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):
"""
Test the `ipalib.backend.Connectible.isconnected` method.
@@ -107,7 +135,7 @@ class test_Connectible(ClassChecker):
for klass in (self.cls, example):
o = klass()
assert o.isconnected() is False
- conn = DummyConnection()
+ conn = 'whatever'
setattr(context, klass.__name__, conn)
assert o.isconnected() is True
delattr(context, klass.__name__)
@@ -125,7 +153,7 @@ class test_Connectible(ClassChecker):
assert str(e) == msg % (
klass.__name__, threading.currentThread().getName()
)
- conn = DummyConnection()
+ conn = Connection('The connection.', Disconnect())
setattr(context, klass.__name__, conn)
assert o.conn is conn.conn
delattr(context, klass.__name__)
@@ -170,11 +198,11 @@ class test_Executioner(ClassChecker):
o.finalize()
# Test that CommandError is raised:
- conn = DummyConnection()
+ conn = Connection('The connection.', Disconnect())
context.someconn = conn
e = raises(errors2.CommandError, o.execute, '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() == []
# Test with echo command:
@@ -183,30 +211,30 @@ class test_Executioner(ClassChecker):
args = (arg1,) + arg2
options = dict(option1=u'How are you?', option2=unicode_str)
- conn = DummyConnection()
+ conn = Connection('The connection.', Disconnect())
context.someconn = conn
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() == []
- conn = DummyConnection()
+ conn = Connection('The connection.', Disconnect())
context.someconn = conn
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() == []
# Test with good command:
- conn = DummyConnection()
+ conn = Connection('The connection.', Disconnect())
context.someconn = conn
e = raises(errors2.ValidationError, o.execute, 'good')
assert e.name == 'nurse'
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() == []
# Test with bad command:
- conn = DummyConnection()
+ conn = Connection('The connection.', Disconnect())
context.someconn = conn
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() == []
diff --git a/tests/test_ipalib/test_rpc.py b/tests/test_ipalib/test_rpc.py
index 351f483be..826f481e9 100644
--- a/tests/test_ipalib/test_rpc.py
+++ b/tests/test_ipalib/test_rpc.py
@@ -26,7 +26,7 @@ from xmlrpclib import Binary, Fault, dumps, loads, ServerProxy
from tests.util import raises, assert_equal, PluginTester, DummyClass
from tests.data import binary_bytes, utf8_bytes, unicode_str
from ipalib.frontend import Command
-from ipalib.request import context
+from ipalib.request import context, Connection
from ipalib import rpc, errors2
@@ -186,20 +186,6 @@ class test_xmlclient(PluginTester):
"""
_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):
"""
Test the `ipalib.rpc.xmlclient.forward` method.
@@ -215,18 +201,12 @@ class test_xmlclient(PluginTester):
'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(
+ conn = DummyClass(
(
'user_add',
rpc.xml_wrap(params),
@@ -247,6 +227,7 @@ class test_xmlclient(PluginTester):
),
)
+ context.xmlclient = Connection(conn, lambda: None)
# Test with a successful return value:
assert o.forward('user_add', *args, **kw) == result
@@ -260,6 +241,4 @@ class test_xmlclient(PluginTester):
assert_equal(e.code, 700)
assert_equal(e.error, u'no such error')
- assert context.xmlconn._calledall() is True
-
- del context.xmlconn
+ assert context.xmlclient.conn._calledall() is True