[Checkins] SVN: gocept.zeoraid/trunk/src/gocept/zeoraid/ - Started testing infrastructure to simulate storages that fail.

Christian Theune ct at gocept.com
Thu Jan 10 06:00:33 EST 2008


Log message for revision 82777:
  - Started testing infrastructure to simulate storages that fail.
  - Started testing the IStorage methods
  - Fixed _apply_all_storages to also check whether a storage was closed after
    a method was used (e.g. when the server closes the connection due to an
    error) but no ClientDisconnected exception is thrown.
  

Changed:
  U   gocept.zeoraid/trunk/src/gocept/zeoraid/storage.py
  A   gocept.zeoraid/trunk/src/gocept/zeoraid/tests/component.xml
  A   gocept.zeoraid/trunk/src/gocept/zeoraid/tests/failingstorage.py
  U   gocept.zeoraid/trunk/src/gocept/zeoraid/tests/test_basics.py

-=-
Modified: gocept.zeoraid/trunk/src/gocept/zeoraid/storage.py
===================================================================
--- gocept.zeoraid/trunk/src/gocept/zeoraid/storage.py	2008-01-10 09:22:54 UTC (rev 82776)
+++ gocept.zeoraid/trunk/src/gocept/zeoraid/storage.py	2008-01-10 11:00:33 UTC (rev 82777)
@@ -104,53 +104,52 @@
 
     # IStorage
 
-    # XXX
     def close(self):
+        """Close the storage."""
         if self.closed:
             # Storage may be closed more than once, e.g. by tear-down methods
             # of tests.
             return
-        self._apply_all_storages('close')
-        self.storages_optimal = []
-        self.closed = True
+        try:
+            self._apply_all_storages('close', _raid_expect_connected=False)
+        finally:
+            self.closed = True
+            del self.storages_optimal[:]
 
-    # XXX
     def getName(self):
+        """The name of the storage."""
         return self.__name__
 
-    # XXX
     def getSize(self):
+        """An approximate size of the database, in bytes."""
         return self._apply_single_storage('getSize')
 
-    # XXX
     def history(self, oid, version=None, size=1):
+        """Return a sequence of history information dictionaries."""
         return self._apply_single_storage('history', oid, version, size)
 
-    # XXX
     def isReadOnly(self):
-        """
-        XXX Revisit this approach?
-        """
+        """Test whether a storage allows committing new transactions."""
         return self.read_only
 
-    # XXX
     def lastTransaction(self):
-        return self._apply_single_storage('lastTransaction')
+        """Return the id of the last committed transaction."""
+        return self._last_tid
 
-    # XXX
     def __len__(self):
+        """The approximate number of objects in the storage."""
         return self._apply_single_storage('__len__')
 
-    # XXX
     def load(self, oid, version):
+        """Load data for an object id and version."""
         return self._apply_single_storage('load', oid, version)
 
-    # XXX
     def loadBefore(self, oid, tid):
+        """Load the object data written before a transaction id."""
         return self._apply_single_storage('loadBefore', oid, tid)
 
-    # XXX
     def loadSerial(self, oid, serial):
+        """Load the object record for the give transaction id."""
         return self._apply_single_storage('loadSerial', oid, serial)
 
     # XXX
@@ -450,10 +449,16 @@
             except ZEO.ClientStorage.ClientDisconnected:
                 # XXX find other possible exceptions
                 self._degrade_storage(name)
+            else:
+                if not storage.is_connected():
+                    self._degrade_storage(name)
 
     def _apply_all_storages(self, method_name, *args, **kw):
         if self.closed:
             raise gocept.zeoraid.interfaces.RAIDClosedError("Storage has been closed.")
+        # kw might contain special parameters. We need to do this
+        # to avoid interfering with the actual arguments that we proxy.
+        expect_connected = kw.pop('_raid_expect_connected', True)
         results = []
         storages = self.storages_optimal[:]
         if not storages:
@@ -466,6 +471,9 @@
                 results.append(method(*args, **kw))
             except ZEO.ClientStorage.ClientDisconnected:
                 self._degrade_storage(name)
+            else:
+                if expect_connected and not storage.is_connected():
+                    self._degrade_storage(name)
 
         res = results[:]
         for test1 in res:

Added: gocept.zeoraid/trunk/src/gocept/zeoraid/tests/component.xml
===================================================================
--- gocept.zeoraid/trunk/src/gocept/zeoraid/tests/component.xml	                        (rev 0)
+++ gocept.zeoraid/trunk/src/gocept/zeoraid/tests/component.xml	2008-01-10 11:00:33 UTC (rev 82777)
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+
+<!-- Support for unit testing with storages that simulate errors. -->
+
+<component prefix="gocept.zeoraid.tests.failingstorage">
+
+    <sectiontype
+        name="failingstorage"
+        implements="ZODB.storage"
+        datatype=".Opener">
+    </sectiontype>
+
+</component>

Added: gocept.zeoraid/trunk/src/gocept/zeoraid/tests/failingstorage.py
===================================================================
--- gocept.zeoraid/trunk/src/gocept/zeoraid/tests/failingstorage.py	                        (rev 0)
+++ gocept.zeoraid/trunk/src/gocept/zeoraid/tests/failingstorage.py	2008-01-10 11:00:33 UTC (rev 82777)
@@ -0,0 +1,28 @@
+# vim:fileencoding=utf-8
+# Copyright (c) 2007 gocept gmbh & co. kg
+# See also LICENSE.txt
+# $Id$
+"""Unit test support."""
+
+import ZODB.utils
+import ZODB.config
+import ZODB.MappingStorage
+
+
+class Opener(ZODB.config.BaseConfig):
+
+    def open(self):
+        return FailingStorage(self.name)
+
+
+class FailingStorage(ZODB.MappingStorage.MappingStorage):
+
+    def getExtensionMethods(self):
+        return dict(fail=None)
+
+    def fail(self, method):
+        old_method = getattr(self, method)
+        def failing_method(*args, **kw):
+            setattr(self, method, old_method)
+            raise Exception()
+        setattr(self, method, failing_method)


Property changes on: gocept.zeoraid/trunk/src/gocept/zeoraid/tests/failingstorage.py
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: gocept.zeoraid/trunk/src/gocept/zeoraid/tests/test_basics.py
===================================================================
--- gocept.zeoraid/trunk/src/gocept/zeoraid/tests/test_basics.py	2008-01-10 09:22:54 UTC (rev 82776)
+++ gocept.zeoraid/trunk/src/gocept/zeoraid/tests/test_basics.py	2008-01-10 11:00:33 UTC (rev 82777)
@@ -19,7 +19,11 @@
 import ZODB.interfaces
 import ZEO.interfaces
 
+# Uncomment this to get helpful logging from the ZEO servers on the console
+#import logging
+#logging.getLogger().addHandler(logging.StreamHandler())
 
+
 class ZEOOpener(object):
 
     def __init__(self, name, **kwargs):
@@ -95,7 +99,86 @@
             self.assert_(zope.interface.verify.verifyObject(iface,
                                                             self._storage))
 
+class FailingStorageTestsBase(unittest.TestCase):
 
+    backend_count = None
+
+    def setUp(self):
+        # Ensure compatibility
+        gocept.zeoraid.compatibility.setup()
+
+        self._servers = []
+        self._storages = []
+        for i in xrange(self.backend_count):
+            port = get_port()
+            zconf = forker.ZEOConfig(('', port))
+            zport, adminaddr, pid, path = forker.start_zeo_server(
+                """%import gocept.zeoraid.tests
+                <failingstorage 1>
+                </failingstorage>""",
+                zconf, port)
+            self._servers.append(adminaddr)
+            self._storages.append(ZEOOpener(zport, storage='1',
+                                            cache_size=2000000,
+                                            min_disconnect_poll=0.5, wait=1,
+                                            wait_timeout=60))
+        self._storage = gocept.zeoraid.storage.RAIDStorage('teststorage',
+                                                           self._storages)
+
+    def tearDown(self):
+        try:
+            self._storage.close()
+        except:
+            pass
+        for server in self._servers:
+            forker.shutdown_zeo_server(server)
+        # XXX wait for servers to come down
+
+
+class FailingStorageTests1Backend(FailingStorageTestsBase):
+
+    backend_count = 1
+
+    def test_close(self):
+        self._storage.close()
+        self.assertEquals(self._storage.closed, True)
+
+    def test_double_close(self):
+        self._storage.close()
+        self.assertEquals(self._storage.closed, True)
+        self._storage.close()
+        self.assertEquals(self._storage.closed, True)
+
+    def test_close_failing(self):
+        # Even though we make the server-side storage fail, we do not get
+        # receive an error or a degradation because the result of the failure
+        # is that the connection is closed. This is actually what we wanted.
+        # Unfortunately that means that an error can be hidden while closing.
+        self._storage.storages[self._storage.storages_optimal[0]].fail('close')
+        self._storage.close()
+        self.assertEquals(True, self._storage.closed)
+
+
+class FailingStorageTests2Backends(FailingStorageTestsBase):
+
+    backend_count = 2
+
+    def test_close_degrading(self):
+        # See the comment on `test_close_failing`.
+        self._storage.storages[self._storage.storages_optimal[0]].fail('close')
+        self._storage.close()
+        self.assertEquals([], self._storage.storages_degraded)
+        self.assertEquals(True, self._storage.closed)
+
+    def test_close_server_missing(self):
+        # See the comment on `test_close_failing`.
+        forker.shutdown_zeo_server(self._servers[0])
+        del self._servers[0]
+        self._storage.close()
+        self.assertEquals([], self._storage.storages_degraded)
+        self.assertEquals(True, self._storage.closed)
+
+
 class ZEOReplicationStorageTests(ZEOStorageBackendTests,
                                  ReplicationStorageTests,
                                  ThreadTests.ThreadTests):
@@ -105,6 +188,8 @@
 def test_suite():
     suite = unittest.TestSuite()
     suite.addTest(unittest.makeSuite(ZEOReplicationStorageTests, "check"))
+    suite.addTest(unittest.makeSuite(FailingStorageTests1Backend))
+    suite.addTest(unittest.makeSuite(FailingStorageTests2Backends))
     return suite
 
 if __name__=='__main__':



More information about the Checkins mailing list