Commit aa195678 authored by Jeremy Hylton's avatar Jeremy Hylton

Merge changes from Zope-2_7-branch to the trunk.

parent b765fc5a
...@@ -207,13 +207,6 @@ then this command will install the new ZEO and ZODB: ...@@ -207,13 +207,6 @@ then this command will install the new ZEO and ZODB:
The install command should create a /home/zope/lib/python/ZEO directoy. The install command should create a /home/zope/lib/python/ZEO directoy.
Simple configuration
--------------------
mkzeoinst.py
Or, do it step-by-step.
Configuring server Configuring server
------------------ ------------------
...@@ -407,13 +400,9 @@ Running the ZEO server as a daemon ...@@ -407,13 +400,9 @@ Running the ZEO server as a daemon
In an operational setting, you will want to run the ZEO server a In an operational setting, you will want to run the ZEO server a
daemon process that is restarted when it dies. The zdaemon package daemon process that is restarted when it dies. The zdaemon package
provides two tools for running daemons: zdrun.py and zdctl.py. provides two tools for running daemons: zdrun.py and zdctl.py. The
The document "Using zdctl and zdrun to manage server processes" document "Using zdctl and zdrun to manage server processes"
explains how to use these scripts to manage daemons. (Doc/zdctl.txt) explains how to use these scripts to manage daemons.
XXX example of how to use zdrun
XXX mkzeoinst.py docs should probably go here
Rotating log files Rotating log files
~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~
...@@ -437,10 +426,6 @@ manages a ZEO servers password database. ...@@ -437,10 +426,6 @@ manages a ZEO servers password database.
Diagnosing problems Diagnosing problems
------------------- -------------------
How to use the debug logs.
Common gotchas.
If an exception occurs on the server, the server will log a traceback If an exception occurs on the server, the server will log a traceback
and send an exception to the client. The traceback on the client will and send an exception to the client. The traceback on the client will
show a ZEO protocol library as the source of the error. If you need show a ZEO protocol library as the source of the error. If you need
......
Client Cache Tracing ZEO Client Cache Tracing
==================== ========================
An important question for ZEO users is: how large should the ZEO An important question for ZEO users is: how large should the ZEO
client cache be? ZEO 2 (as of ZEO 2.0b2) has a new feature that lets client cache be? ZEO 2 (as of ZEO 2.0b2) has a new feature that lets
......
...@@ -121,4 +121,3 @@ if __name__ == '__main__': ...@@ -121,4 +121,3 @@ if __name__ == '__main__':
# Wait for a few seconds # Wait for a few seconds
pause = random.randint( 1, 4 ) pause = random.randint( 1, 4 )
time.sleep( pause ) time.sleep( pause )
...@@ -25,7 +25,4 @@ and less in others. ...@@ -25,7 +25,4 @@ and less in others.
\\ \\
\url{http://www.zope.org/Members/bwarsaw/ipc10-slides} \url{http://www.zope.org/Members/bwarsaw/ipc10-slides}
Download link for ZEO: \\
\url{http://www.zope.org/Products/ZEO/}
...@@ -9,36 +9,28 @@ ...@@ -9,36 +9,28 @@
\subsection{Installing ZODB} \subsection{Installing ZODB}
The ZODB forms part of Zope, but it's difficult and somewhat painful ZODB is packaged using the standard distutils tools.
to extract the bits from Zope needed to support just the ZODB.
Therefore I've assembled a distribution containing only the packages
required to use the ZODB and ZEO, so you can install it and start
programming right away.
To download the distribution, go to my ZODB page at
\url{http://www.amk.ca/zodb/}.
The distribution is still experimental, so don't be surprised if the
installation process runs into problems. Please inform me of any
difficulties you encounter.
\subsubsection{Requirements} \subsubsection{Requirements}
You will need Python 2.1 or higher. The code is packaged using You will need Python 2.2 or higher. Since the code is packaged using
Distutils. So it is simply a matter of untarring or unzipping the distutils, it is simply a matter of untarring or unzipping the release
release package, and then running \code{python setup.py install}. package, and then running \code{python setup.py install}.
You'll need a C compiler to build the packages, because there are You'll need a C compiler to build the packages, because there are
various C extension modules. At the moment no one is making Windows various C extension modules. Binary installers are provided for
binaries available, so you'll need a Windows development environment Windows users.
to build ZODB.
\subsubsection{Installing the Packages} \subsubsection{Installing the Packages}
Download the ZODB tarball containing all the packages for both ZODB Download the ZODB tarball containing all the packages for both ZODB
and ZEO from \url{http://www.zope.org/Products/StandaloneZODB}. See and ZEO from \url{http://www.zope.org/Products/ZODB3.2}. See
the \file{README.txt} file in the top level of the release directory the \file{README.txt} file in the top level of the release directory
for details on building, testing, and installing. for details on building, testing, and installing.
You can find information about ZODB and the most current releases in
the ZODB Wiki at \url{http://www.zope.org/Wikis/ZODB}.
\subsection{How ZODB Works} \subsection{How ZODB Works}
The ZODB is conceptually simple. Python classes subclass a The ZODB is conceptually simple. Python classes subclass a
...@@ -59,8 +51,10 @@ don't get corrupted by software or hardware crashes, and most database ...@@ -59,8 +51,10 @@ don't get corrupted by software or hardware crashes, and most database
software offers protection against such corruption by supporting four software offers protection against such corruption by supporting four
useful properties, Atomicity, Consistency, Isolation, and Durability. useful properties, Atomicity, Consistency, Isolation, and Durability.
In computer science jargon these four terms are collectively dubbed In computer science jargon these four terms are collectively dubbed
the ACID properties, forming an acronym from their names. The the ACID properties, forming an acronym from their names.
definitions of the ACID properties are:
The ZODB provides all of the ACID properties. Definitions of the
ACID properties are:
\begin{itemize} \begin{itemize}
...@@ -73,18 +67,13 @@ forgotten. That's bad, but it's better than having a ...@@ -73,18 +67,13 @@ forgotten. That's bad, but it's better than having a
partially-applied modification put the database into an inconsistent partially-applied modification put the database into an inconsistent
state. state.
\item[Consistency] means that the data cannot be placed into a \item[Consistency] means that each transaction executes a valid
logically invalid state; sanity checks can be written and enforced. transformation of the database state. Some databases, but not ZODB,
Usually this is done by defining a database schema, and requiring provide a variety of consistency checks in the database or language;
the data always matches the schema. There are two typical for example, a relational database constraint columns to be of
approaches to consistency. One is to enforce rules about the types particular types and can enforce relations across tables. Viewed more
of objects and attribute; for example, enforce that the generally, atomicity and isolation make it possible for applications
\code{order_number} attribute is always an integer, and not a to provide consistency.
string, tuple, or other object. Another is to guarantee consistency
across data structures; for example, that any object with an
\code{order_number} attribute must also appear in the
\code{orders_table} object. In general, atomicity and isolation make
it possible for applications to provide consistency.
\item[Isolation] means that two programs or threads running in two \item[Isolation] means that two programs or threads running in two
different transactions cannot see each other's changes until they different transactions cannot see each other's changes until they
...@@ -95,10 +84,6 @@ a subsequent crash will not cause any data to be lost or corrupted. ...@@ -95,10 +84,6 @@ a subsequent crash will not cause any data to be lost or corrupted.
\end{itemize} \end{itemize}
The ZODB provides 3 of the ACID properties. Only Consistency is not
supported; the ZODB has no notion of a database schema, and therefore
has no way of enforcing consistency with a schema.
\subsection{Opening a ZODB} \subsection{Opening a ZODB}
There are 3 main interfaces supplied by the ZODB: There are 3 main interfaces supplied by the ZODB:
...@@ -132,9 +117,9 @@ implement the \class{Storage} interface. ...@@ -132,9 +117,9 @@ implement the \class{Storage} interface.
\end{itemize} \end{itemize}
Preparing to use a ZODB requires 3 steps: you have to open the Preparing to use a ZODB requires 3 steps: you have to open the
\class{Storage}, then create a \class{DB} instance that uses the \class{Storage}, and then get \class{Storage}, then create a \class{DB} instance that uses the
a \class{Connection} from the \class{DB instance}. All this is only a few lines of \class{Storage}, and then get a \class{Connection} from the \class{DB
code: instance}. All this is only a few lines of code:
\begin{verbatim} \begin{verbatim}
from ZODB import FileStorage, DB from ZODB import FileStorage, DB
...@@ -189,7 +174,7 @@ retrieving a \class{User} object given the user's ID. First, we ...@@ -189,7 +174,7 @@ retrieving a \class{User} object given the user's ID. First, we
retrieve the primary root object of the ZODB using the \method{root()} retrieve the primary root object of the ZODB using the \method{root()}
method of the \class{Connection} instance. The root object behaves method of the \class{Connection} instance. The root object behaves
like a Python dictionary, so you can just add a new key/value pair for like a Python dictionary, so you can just add a new key/value pair for
your application's root object. We'll insert a \class{BTree} object your application's root object. We'll insert an \class{OOBTree} object
that will contain all the \class{User} objects. (The that will contain all the \class{User} objects. (The
\class{BTree} module is also included as part of Zope.) \class{BTree} module is also included as part of Zope.)
...@@ -199,8 +184,8 @@ dbroot = conn.root() ...@@ -199,8 +184,8 @@ dbroot = conn.root()
# Ensure that a 'userdb' key is present # Ensure that a 'userdb' key is present
# in the root # in the root
if not dbroot.has_key('userdb'): if not dbroot.has_key('userdb'):
import BTree from BTrees.OOBTree import OOBTree
dbroot['userdb'] = BTree.BTree() dbroot['userdb'] = OOBTree()
userdb = dbroot['userdb'] userdb = dbroot['userdb']
\end{verbatim} \end{verbatim}
...@@ -426,13 +411,11 @@ different class instances, then there's no longer any easy way to find ...@@ -426,13 +411,11 @@ different class instances, then there's no longer any easy way to find
them all, short of writing a generalized object traversal function them all, short of writing a generalized object traversal function
that would walk over every single object in a ZODB, checking each one that would walk over every single object in a ZODB, checking each one
to see if it's an instance of \class{User}. to see if it's an instance of \class{User}.
\footnote{XXX is there a convenience method for walking the object graph hiding
somewhere inside DC's code? Should there be a utility method for
doing this? Should I write one and include it in this section?}
Some OODBs support a feature called extents, which allow quickly Some OODBs support a feature called extents, which allow quickly
finding all the instances of a given class, no matter where they are finding all the instances of a given class, no matter where they are
in the object graph; unfortunately the ZODB doesn't offer extents as a in the object graph; unfortunately the ZODB doesn't offer extents as a
feature. feature.
XXX Rest of section not written yet: __getstate__/__setstate__ % XXX Rest of section not written yet: __getstate__/__setstate__
\documentclass{howto} \documentclass{howto}
\title{ZODB/ZEO Programming Guide} \title{ZODB/ZEO Programming Guide}
\release{0.1} \release{0.2}
\date{\today} \date{\today}
\author{A.M.\ Kuchling} \author{A.M.\ Kuchling}
...@@ -23,9 +23,6 @@ ...@@ -23,9 +23,6 @@
\input{prog-zodb} \input{prog-zodb}
\input{zeo} \input{zeo}
\input{transactions} \input{transactions}
%\input{storages}
%\input{indexing}
%\input{admin}
\input{modules} \input{modules}
\appendix \appendix
......
...@@ -25,7 +25,7 @@ register_loop_callback() to register interest. When the mainloop ...@@ -25,7 +25,7 @@ register_loop_callback() to register interest. When the mainloop
thread calls loop(), each registered callback will be called with the thread calls loop(), each registered callback will be called with the
socket map as its first argument. socket map as its first argument.
""" """
__version__ = '$Revision: 1.8 $'[11:-2] __version__ = '$Revision: 1.9 $'[11:-2]
import asyncore import asyncore
import select import select
...@@ -58,6 +58,16 @@ def register_loop_callback(callback, args=(), kw=None): ...@@ -58,6 +58,16 @@ def register_loop_callback(callback, args=(), kw=None):
finally: finally:
_loop_lock.release() _loop_lock.release()
def remove_loop_callback(callback):
"""Remove a callback function registered earlier.
This is useful if loop() was never called.
"""
for i in range(len(_loop_callbacks)):
if _loop_callbacks[i][0] == callback:
del _loop_callbacks[i]
return
def _start_loop(map): def _start_loop(map):
_loop_lock.acquire() _loop_lock.acquire()
try: try:
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Manage the asyncore mainloop in a multi-threaded app. """Manage the asyncore mainloop in a multi-threaded app.
$Id: __init__.py,v 1.6 2003/02/25 15:17:09 fdrake Exp $ $Id: __init__.py,v 1.7 2003/10/02 18:17:26 jeremy Exp $
""" """
from LoopCallback import register_loop_callback, loop from LoopCallback import register_loop_callback, loop, remove_loop_callback
...@@ -99,7 +99,7 @@ class ClientStorage(object): ...@@ -99,7 +99,7 @@ class ClientStorage(object):
name='', client=None, debug=0, var=None, name='', client=None, debug=0, var=None,
min_disconnect_poll=5, max_disconnect_poll=300, min_disconnect_poll=5, max_disconnect_poll=300,
wait_for_server_on_startup=None, # deprecated alias for wait wait_for_server_on_startup=None, # deprecated alias for wait
wait=None, # defaults to 1 wait=None, wait_timeout=None,
read_only=0, read_only_fallback=0, read_only=0, read_only_fallback=0,
username='', password='', realm=None): username='', password='', realm=None):
"""ClientStorage constructor. """ClientStorage constructor.
...@@ -152,6 +152,9 @@ class ClientStorage(object): ...@@ -152,6 +152,9 @@ class ClientStorage(object):
wait -- A flag indicating whether to wait until a connection wait -- A flag indicating whether to wait until a connection
with a server is made, defaulting to true. with a server is made, defaulting to true.
wait_timeout -- Maximum time to wait for a connection before
giving up. Only meaningful if wait is True.
read_only -- A flag indicating whether this should be a read_only -- A flag indicating whether this should be a
read-only storage, defaulting to false (i.e. writing is read-only storage, defaulting to false (i.e. writing is
allowed by default). allowed by default).
...@@ -302,7 +305,7 @@ class ClientStorage(object): ...@@ -302,7 +305,7 @@ class ClientStorage(object):
tmax=max_disconnect_poll) tmax=max_disconnect_poll)
if wait: if wait:
self._wait() self._wait(wait_timeout)
else: else:
# attempt_connect() will make an attempt that doesn't block # attempt_connect() will make an attempt that doesn't block
# "too long," for a very vague notion of too long. If that # "too long," for a very vague notion of too long. If that
...@@ -313,7 +316,9 @@ class ClientStorage(object): ...@@ -313,7 +316,9 @@ class ClientStorage(object):
if not self._ready.isSet(): if not self._ready.isSet():
self._cache.open() self._cache.open()
def _wait(self): def _wait(self, timeout=None):
if timeout is not None:
deadline = time.time() + timeout
# Wait for a connection to be established. # Wait for a connection to be established.
self._rpc_mgr.connect(sync=1) self._rpc_mgr.connect(sync=1)
# When a synchronous connect() call returns, there is # When a synchronous connect() call returns, there is
...@@ -326,6 +331,9 @@ class ClientStorage(object): ...@@ -326,6 +331,9 @@ class ClientStorage(object):
self._ready.wait(30) self._ready.wait(30)
if self._ready.isSet(): if self._ready.isSet():
break break
if timeout and time.time() > deadline:
log2(PROBLEM, "Timed out waiting for connection")
break
log2(INFO, "Waiting for cache verification to finish") log2(INFO, "Waiting for cache verification to finish")
else: else:
self._wait_sync() self._wait_sync()
...@@ -434,8 +442,10 @@ class ClientStorage(object): ...@@ -434,8 +442,10 @@ class ClientStorage(object):
auth = stub.getAuthProtocol() auth = stub.getAuthProtocol()
log2(INFO, "Server authentication protocol %r" % auth) log2(INFO, "Server authentication protocol %r" % auth)
if auth: if auth:
if self.doAuth(auth, stub): skey = self.doAuth(auth, stub)
if skey:
log2(INFO, "Client authentication successful") log2(INFO, "Client authentication successful")
conn.setSessionKey(skey)
else: else:
log2(ERROR, "Authentication failed") log2(ERROR, "Authentication failed")
raise AuthError, "Authentication failed" raise AuthError, "Authentication failed"
......
...@@ -42,3 +42,8 @@ class CommitLog: ...@@ -42,3 +42,8 @@ class CommitLog:
self.read = 1 self.read = 1
self.file.seek(0) self.file.seek(0)
return self.stores, cPickle.Unpickler(self.file) return self.stores, cPickle.Unpickler(self.file)
def close(self):
if self.file:
self.file.close()
self.file = None
...@@ -32,9 +32,13 @@ class StorageServer: ...@@ -32,9 +32,13 @@ class StorageServer:
zrpc.connection.Connection class. zrpc.connection.Connection class.
""" """
self.rpc = rpc self.rpc = rpc
if self.rpc.peer_protocol_version == 'Z200': # Wait until we know what version the other side is using.
while rpc.peer_protocol_version is None:
rpc.pending()
if rpc.peer_protocol_version == 'Z200':
self.lastTransaction = lambda: None self.lastTransaction = lambda: None
self.getInvalidations = lambda tid: None self.getInvalidations = lambda tid: None
self.getAuthProtocol = lambda: None
def extensionMethod(self, name): def extensionMethod(self, name):
return ExtensionMethodWrapper(self.rpc, name).call return ExtensionMethodWrapper(self.rpc, name).call
......
...@@ -43,7 +43,7 @@ from ZODB.POSException import StorageError, StorageTransactionError ...@@ -43,7 +43,7 @@ from ZODB.POSException import StorageError, StorageTransactionError
from ZODB.POSException import TransactionError, ReadOnlyError, ConflictError from ZODB.POSException import TransactionError, ReadOnlyError, ConflictError
from ZODB.referencesf import referencesf from ZODB.referencesf import referencesf
from ZODB.Transaction import Transaction from ZODB.Transaction import Transaction
from ZODB.utils import u64 from ZODB.utils import u64, oid_repr
_label = "ZSS" # Default label used for logging. _label = "ZSS" # Default label used for logging.
...@@ -398,6 +398,7 @@ class ZEOStorage: ...@@ -398,6 +398,7 @@ class ZEOStorage:
def _clear_transaction(self): def _clear_transaction(self):
# Common code at end of tpc_finish() and tpc_abort() # Common code at end of tpc_finish() and tpc_abort()
self.transaction = None self.transaction = None
self.txnlog.close()
if self.locked: if self.locked:
self.locked = 0 self.locked = 0
self.timeout.end(self) self.timeout.end(self)
...@@ -483,6 +484,8 @@ class ZEOStorage: ...@@ -483,6 +484,8 @@ class ZEOStorage:
self.store_failed = 1 self.store_failed = 1
if isinstance(err, ConflictError): if isinstance(err, ConflictError):
self.stats.conflicts += 1 self.stats.conflicts += 1
self.log("conflict error oid=%s msg=%s" %
(oid_repr(oid), str(err)), zLOG.BLATHER)
if not isinstance(err, TransactionError): if not isinstance(err, TransactionError):
# Unexpected errors are logged and passed to the client # Unexpected errors are logged and passed to the client
exc_info = sys.exc_info() exc_info = sys.exc_info()
...@@ -506,6 +509,7 @@ class ZEOStorage: ...@@ -506,6 +509,7 @@ class ZEOStorage:
self.invalidated.append((oid, version)) self.invalidated.append((oid, version))
if newserial == ResolvedSerial: if newserial == ResolvedSerial:
self.stats.conflicts_resolved += 1 self.stats.conflicts_resolved += 1
self.log("conflict resolved oid=%s" % oid_repr(oid), zLOG.BLATHER)
self.serials.append((oid, newserial)) self.serials.append((oid, newserial))
return err is None return err is None
...@@ -928,6 +932,7 @@ class TimeoutThread(threading.Thread): ...@@ -928,6 +932,7 @@ class TimeoutThread(threading.Thread):
self._cond.acquire() self._cond.acquire()
try: try:
assert self._client is not None assert self._client is not None
assert self._client is client
self._client = None self._client = None
self._deadline = None self._deadline = None
finally: finally:
...@@ -953,7 +958,6 @@ class TimeoutThread(threading.Thread): ...@@ -953,7 +958,6 @@ class TimeoutThread(threading.Thread):
self._trigger.pull_trigger(lambda: client.connection.close()) self._trigger.pull_trigger(lambda: client.connection.close())
else: else:
time.sleep(howlong) time.sleep(howlong)
self.trigger.close()
def run_in_thread(method, *args): def run_in_thread(method, *args):
t = SlowMethodThread(method, args) t = SlowMethodThread(method, args)
......
...@@ -21,4 +21,4 @@ ZEO is now part of ZODB; ZODB's home on the web is ...@@ -21,4 +21,4 @@ ZEO is now part of ZODB; ZODB's home on the web is
""" """
version = "2.1b2" version = "2.2c1"
...@@ -28,4 +28,3 @@ def register_module(name, storage_class, client, db): ...@@ -28,4 +28,3 @@ def register_module(name, storage_class, client, db):
if _auth_modules.has_key(name): if _auth_modules.has_key(name):
raise TypeError, "%s is already registred" % name raise TypeError, "%s is already registred" % name
_auth_modules[name] = storage_class, client, db _auth_modules[name] = storage_class, client, db
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Monitor behavior of ZEO server and record statistics. """Monitor behavior of ZEO server and record statistics.
$Id: monitor.py,v 1.3 2003/01/15 21:23:16 jeremy Exp $ $Id: monitor.py,v 1.4 2003/10/02 18:17:22 jeremy Exp $
""" """
import asyncore import asyncore
......
...@@ -27,7 +27,7 @@ from ZEO.tests.TestThread import TestThread ...@@ -27,7 +27,7 @@ from ZEO.tests.TestThread import TestThread
ZERO = '\0'*8 ZERO = '\0'*8
class DummyDB: class DummyDB:
def invalidate(self, *args): def invalidate(self, *args, **kwargs):
pass pass
class WorkerThread(TestThread): class WorkerThread(TestThread):
...@@ -204,7 +204,8 @@ class CommitLockTests: ...@@ -204,7 +204,8 @@ class CommitLockTests:
def _begin_threads(self): def _begin_threads(self):
# Start a second transaction on a different connection without # Start a second transaction on a different connection without
# blocking the test thread. # blocking the test thread. Returns only after each thread has
# set it's ready event.
self._storages = [] self._storages = []
self._threads = [] self._threads = []
......
...@@ -347,8 +347,8 @@ class InvalidationTests: ...@@ -347,8 +347,8 @@ class InvalidationTests:
# Run two threads that update the BTree # Run two threads that update the BTree
cd = {} cd = {}
t1 = self.StressThread(self, db1, stop, 1, cd, 1, sleep=0.001) t1 = self.StressThread(self, db1, stop, 1, cd, 1, sleep=0.01)
t2 = self.StressThread(self, db1, stop, 2, cd, 2, sleep=0.001) t2 = self.StressThread(self, db1, stop, 2, cd, 2, sleep=0.01)
self.go(stop, cd, t1, t2) self.go(stop, cd, t1, t2)
cn.sync() cn.sync()
...@@ -375,8 +375,8 @@ class InvalidationTests: ...@@ -375,8 +375,8 @@ class InvalidationTests:
cd = {} cd = {}
t1 = self.StressThread(self, db1, stop, 1, cd, 1, 3) t1 = self.StressThread(self, db1, stop, 1, cd, 1, 3)
t2 = self.StressThread(self, db2, stop, 2, cd, 2, 3, 0.001) t2 = self.StressThread(self, db2, stop, 2, cd, 2, 3, 0.01)
t3 = self.StressThread(self, db2, stop, 3, cd, 3, 3, 0.001) t3 = self.StressThread(self, db2, stop, 3, cd, 3, 3, 0.01)
self.go(stop, cd, t1, t2, t3) self.go(stop, cd, t1, t2, t3)
cn.sync() cn.sync()
...@@ -404,8 +404,8 @@ class InvalidationTests: ...@@ -404,8 +404,8 @@ class InvalidationTests:
cd = {} cd = {}
t1 = VersionStressThread(self, db1, stop, 1, cd, 1, 3) t1 = VersionStressThread(self, db1, stop, 1, cd, 1, 3)
t2 = VersionStressThread(self, db2, stop, 2, cd, 2, 3, 0.001) t2 = VersionStressThread(self, db2, stop, 2, cd, 2, 3, 0.01)
t3 = VersionStressThread(self, db2, stop, 3, cd, 3, 3, 0.001) t3 = VersionStressThread(self, db2, stop, 3, cd, 3, 3, 0.01)
self.go(stop, cd, t1, t2, t3) self.go(stop, cd, t1, t2, t3)
cn.sync() cn.sync()
...@@ -435,9 +435,9 @@ class InvalidationTests: ...@@ -435,9 +435,9 @@ class InvalidationTests:
# at the same time. # at the same time.
cd = {} cd = {}
t1 = LargeUpdatesThread(self, db1, stop, 1, cd, 1, 3, 0.001) t1 = LargeUpdatesThread(self, db1, stop, 1, cd, 1, 3, 0.02)
t2 = LargeUpdatesThread(self, db2, stop, 2, cd, 2, 3, 0.001) t2 = LargeUpdatesThread(self, db2, stop, 2, cd, 2, 3, 0.01)
t3 = LargeUpdatesThread(self, db2, stop, 3, cd, 3, 3, 0.001) t3 = LargeUpdatesThread(self, db2, stop, 3, cd, 3, 3, 0.01)
self.go(stop, cd, t1, t2, t3) self.go(stop, cd, t1, t2, t3)
cn.sync() cn.sync()
......
...@@ -16,8 +16,8 @@ an SHA hash in the Database. The client sends over the plaintext ...@@ -16,8 +16,8 @@ an SHA hash in the Database. The client sends over the plaintext
password, and the SHA hashing is done on the server side. password, and the SHA hashing is done on the server side.
This mechanism offers *no network security at all*; the only security This mechanism offers *no network security at all*; the only security
is provided by not storing plaintext passwords on disk. (See the is provided by not storing plaintext passwords on disk.
auth_srp module for a secure mechanism)""" """
import sha import sha
...@@ -25,6 +25,9 @@ from ZEO.StorageServer import ZEOStorage ...@@ -25,6 +25,9 @@ from ZEO.StorageServer import ZEOStorage
from ZEO.auth import register_module from ZEO.auth import register_module
from ZEO.auth.base import Client, Database from ZEO.auth.base import Client, Database
def session_key(username, realm, password):
return sha.new("%s:%s:%s" % (username, realm, password)).hexdigest()
class StorageClass(ZEOStorage): class StorageClass(ZEOStorage):
def auth(self, username, password): def auth(self, username, password):
try: try:
...@@ -32,13 +35,21 @@ class StorageClass(ZEOStorage): ...@@ -32,13 +35,21 @@ class StorageClass(ZEOStorage):
except LookupError: except LookupError:
return 0 return 0
password = sha.new(password).hexdigest() password_dig = sha.new(password).hexdigest()
return self.finish_auth(dbpw == password) if dbpw == password_dig:
self.connection.setSessionKey(session_key(username,
self.database.realm,
password))
return self.finish_auth(dbpw == password_dig)
class PlaintextClient(Client): class PlaintextClient(Client):
extensions = ["auth"] extensions = ["auth"]
def start(self, username, realm, password): def start(self, username, realm, password):
return self.stub.auth(username, password) if self.stub.auth(username, password):
return session_key(username, realm, password)
else:
return None
register_module("plaintext", StorageClass, PlaintextClient, Database) register_module("plaintext", StorageClass, PlaintextClient, Database)
...@@ -18,8 +18,11 @@ import tempfile ...@@ -18,8 +18,11 @@ import tempfile
import time import time
import unittest import unittest
import zLOG
from ThreadedAsync import LoopCallback from ThreadedAsync import LoopCallback
from ZEO.ClientStorage import ClientStorage from ZEO.ClientStorage import ClientStorage
from ZEO.Exceptions import ClientDisconnected
from ZEO.StorageServer import StorageServer from ZEO.StorageServer import StorageServer
from ZEO.tests.ConnectionTests import CommonSetupTearDown from ZEO.tests.ConnectionTests import CommonSetupTearDown
...@@ -74,6 +77,8 @@ class AuthTest(CommonSetupTearDown): ...@@ -74,6 +77,8 @@ class AuthTest(CommonSetupTearDown):
self.assert_(self._storage._connection) self.assert_(self._storage._connection)
self._storage._connection.poll() self._storage._connection.poll()
self.assert_(self._storage.is_connected()) self.assert_(self._storage.is_connected())
# Make a call to make sure the mechanism is working
self._storage.versions()
def testNOK(self): def testNOK(self):
self._storage = self.openClientStorage(wait=0, username="foo", self._storage = self.openClientStorage(wait=0, username="foo",
...@@ -83,6 +88,20 @@ class AuthTest(CommonSetupTearDown): ...@@ -83,6 +88,20 @@ class AuthTest(CommonSetupTearDown):
# If the test established a connection, then it failed. # If the test established a connection, then it failed.
self.failIf(self._storage._connection) self.failIf(self._storage._connection)
def testUnauthenticatedMessage(self):
# Test that an unauthenticated message is rejected by the server
# if it was sent after the connection was authenticated.
# Sleep for 0.2 seconds to give the server some time to start up
# seems to be needed before and after creating the storage
self._storage = self.openClientStorage(wait=0, username="foo",
password="bar", realm=self.realm)
self.wait()
self._storage.versions()
# Manually clear the state of the hmac connection
self._storage._connection._SizedMessageAsyncConnection__hmac_send = None
# Once the client stops using the hmac, it should be disconnected.
self.assertRaises(ClientDisconnected, self._storage.versions)
class PlainTextAuth(AuthTest): class PlainTextAuth(AuthTest):
import ZEO.tests.auth_plaintext import ZEO.tests.auth_plaintext
protocol = "plaintext" protocol = "plaintext"
...@@ -108,4 +127,3 @@ def test_suite(): ...@@ -108,4 +127,3 @@ def test_suite():
if __name__ == "__main__": if __name__ == "__main__":
unittest.main(defaultTest='test_suite') unittest.main(defaultTest='test_suite')
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Test that the monitor produce sensible results. """Test that the monitor produce sensible results.
$Id: testMonitor.py,v 1.6 2003/05/30 19:20:56 jeremy Exp $ $Id: testMonitor.py,v 1.7 2003/10/02 18:17:21 jeremy Exp $
""" """
import socket import socket
......
...@@ -132,7 +132,8 @@ class GenericTests( ...@@ -132,7 +132,8 @@ class GenericTests(
self._servers = [adminaddr] self._servers = [adminaddr]
self._conf_path = path self._conf_path = path
self._storage = ClientStorage(zport, '1', cache_size=20000000, self._storage = ClientStorage(zport, '1', cache_size=20000000,
min_disconnect_poll=0.5, wait=1) min_disconnect_poll=0.5, wait=1,
wait_timeout=60)
self._storage.registerDB(DummyDB(), None) self._storage.registerDB(DummyDB(), None)
def tearDown(self): def tearDown(self):
......
...@@ -122,8 +122,11 @@ class Suicide(threading.Thread): ...@@ -122,8 +122,11 @@ class Suicide(threading.Thread):
self._adminaddr = addr self._adminaddr = addr
def run(self): def run(self):
# If this process doesn't exit in 300 seconds, commit suicide # If this process doesn't exit in 330 seconds, commit suicide.
time.sleep(300) # The client threads in the ConcurrentUpdate tests will run for
# as long as 300 seconds. Set this timeout to 330 to minimize
# chance that the server gives up before the clients.
time.sleep(330)
log("zeoserver", "suicide thread invoking shutdown") log("zeoserver", "suicide thread invoking shutdown")
from ZEO.tests.forker import shutdown_zeo_server from ZEO.tests.forker import shutdown_zeo_server
# XXX If the -k option was given to zeoserver, then the process will # XXX If the -k option was given to zeoserver, then the process will
......
...@@ -17,16 +17,12 @@ ...@@ -17,16 +17,12 @@
usage: python zeopasswd.py [options] username [password] usage: python zeopasswd.py [options] username [password]
-C/--configuration URL -- configuration file or URL -C/--configuration URL -- configuration file or URL
-p/--protocol -- authentication protocol name
-f/--filename -- authentication database filename
-r/--realm -- authentication database realm
-d/--delete -- delete user instead of updating password -d/--delete -- delete user instead of updating password
""" """
import getopt import getopt
import getpass import getpass
import sys import sys
import os
import ZConfig import ZConfig
import ZEO import ZEO
...@@ -39,41 +35,22 @@ def usage(msg): ...@@ -39,41 +35,22 @@ def usage(msg):
def options(args): def options(args):
"""Password-specific options loaded from regular ZEO config file.""" """Password-specific options loaded from regular ZEO config file."""
schema = ZConfig.loadSchema(os.path.join(os.path.dirname(ZEO.__file__),
"schema.xml"))
try: try:
options, args = getopt.getopt(args, "dr:p:f:C:", ["configure=", options, args = getopt.getopt(args, "C:", ["configure="])
"protocol=",
"filename=",
"realm"])
except getopt.error, msg: except getopt.error, msg:
usage(msg) usage(msg)
config = None config = None
delete = 0 delete = False
auth_protocol = None
auth_db = ""
auth_realm = None
for k, v in options: for k, v in options:
if k == '-C' or k == '--configure': if k == '-C' or k == '--configure':
schemafile = os.path.join(os.path.dirname(ZEO.__file__),
"schema.xml")
schema = ZConfig.loadSchema(schemafile)
config, nil = ZConfig.loadConfig(schema, v) config, nil = ZConfig.loadConfig(schema, v)
if k == '-d' or k == '--delete': if k == '-d' or k == '--delete':
delete = 1 delete = True
if k == '-p' or k == '--protocol': if config is None:
auth_protocol = v usage("Must specifiy configuration file")
if k == '-f' or k == '--filename':
auth_db = v
if k == '-r' or k == '--realm':
auth_realm = v
if config is not None:
if auth_protocol or auth_db:
usage("Conflicting options; use either -C *or* -p and -f")
auth_protocol = config.zeo.authentication_protocol
auth_db = config.zeo.authentication_database
auth_realm = config.zeo.authentication_realm
elif not (auth_protocol and auth_db):
usage("Must specifiy configuration file or protocol and database")
password = None password = None
if delete: if delete:
...@@ -92,21 +69,20 @@ def options(args): ...@@ -92,21 +69,20 @@ def options(args):
else: else:
username, password = args username, password = args
return auth_protocol, auth_db, auth_realm, delete, username, password return config.zeo, delete, username, password
def main(args=None): def main(args=None):
p, auth_db, auth_realm, delete, username, password = options(args) options, delete, username, password = options(args)
p = options.authentication_protocol
if p is None: if p is None:
usage("ZEO configuration does not specify authentication-protocol") usage("ZEO configuration does not specify authentication-protocol")
if p == "digest": if p == "digest":
from ZEO.auth.auth_digest import DigestDatabase as Database from ZEO.auth.auth_digest import DigestDatabase as Database
elif p == "srp": elif p == "srp":
from ZEO.auth.auth_srp import SRPDatabase as Database from ZEO.auth.auth_srp import SRPDatabase as Database
else: if options.authentication_database is None:
raise ValueError, "Unknown database type %r" % p
if auth_db is None:
usage("ZEO configuration does not specify authentication-database") usage("ZEO configuration does not specify authentication-database")
db = Database(auth_db, auth_realm) db = Database(options.authentication_database)
if delete: if delete:
db.del_user(username) db.del_user(username)
else: else:
...@@ -116,5 +92,4 @@ def main(args=None): ...@@ -116,5 +92,4 @@ def main(args=None):
db.save() db.save()
if __name__ == "__main__": if __name__ == "__main__":
main(sys.argv[1:]) main(sys.argv)
...@@ -105,6 +105,7 @@ class ConnectionManager(object): ...@@ -105,6 +105,7 @@ class ConnectionManager(object):
if self.trigger is not None: if self.trigger is not None:
self.trigger.close() self.trigger.close()
self.trigger = None self.trigger = None
ThreadedAsync.remove_loop_callback(self.set_async)
def set_async(self, map): def set_async(self, map):
# This is the callback registered with ThreadedAsync. The # This is the callback registered with ThreadedAsync. The
......
...@@ -126,11 +126,15 @@ class Connection(smac.SizedMessageAsyncConnection, object): ...@@ -126,11 +126,15 @@ class Connection(smac.SizedMessageAsyncConnection, object):
# Protocol history: # Protocol history:
# #
# Z200 -- original ZEO 2.0 protocol # Z200 -- Original ZEO 2.0 protocol
# #
# Z201 -- added invalidateTransaction() to client; # Z201 -- Added invalidateTransaction() to client.
# renamed several client methods; # Renamed several client methods.
# added lastTransaction() to server # Added several sever methods:
# lastTransaction()
# getAuthProtocol() and scheme-specific authentication methods
# getExtensionMethods().
# getInvalidations().
def __init__(self, sock, addr, obj=None): def __init__(self, sock, addr, obj=None):
self.obj = None self.obj = None
......
...@@ -31,7 +31,6 @@ class Dispatcher(asyncore.dispatcher): ...@@ -31,7 +31,6 @@ class Dispatcher(asyncore.dispatcher):
self.__super_init() self.__super_init()
self.addr = addr self.addr = addr
self.factory = factory self.factory = factory
self.clients = []
self._open_socket() self._open_socket()
def _open_socket(self): def _open_socket(self):
...@@ -58,4 +57,3 @@ class Dispatcher(asyncore.dispatcher): ...@@ -58,4 +57,3 @@ class Dispatcher(asyncore.dispatcher):
return return
c = self.factory(sock, addr) c = self.factory(sock, addr)
log("connect from %s: %s" % (repr(addr), c)) log("connect from %s: %s" % (repr(addr), c))
self.clients.append(c)
...@@ -64,7 +64,7 @@ del tmp_dict ...@@ -64,7 +64,7 @@ del tmp_dict
# that we could pass to send() without blocking. # that we could pass to send() without blocking.
SEND_SIZE = 60000 SEND_SIZE = 60000
MAC_BIT = 0x80000000 MAC_BIT = 0x80000000L
class SizedMessageAsyncConnection(asyncore.dispatcher): class SizedMessageAsyncConnection(asyncore.dispatcher):
__super_init = asyncore.dispatcher.__init__ __super_init = asyncore.dispatcher.__init__
...@@ -96,12 +96,33 @@ class SizedMessageAsyncConnection(asyncore.dispatcher): ...@@ -96,12 +96,33 @@ class SizedMessageAsyncConnection(asyncore.dispatcher):
self.__output_lock = threading.Lock() # Protects __output self.__output_lock = threading.Lock() # Protects __output
self.__output = [] self.__output = []
self.__closed = 0 self.__closed = 0
self.__hmac = None # Each side of the connection sends and receives messages. A
# MAC is generated for each message and depends on each
# previous MAC; the state of the MAC generator depends on the
# history of operations it has performed. So the MACs must be
# generated in the same order they are verified.
# Each side is guaranteed to receive messages in the order
# they are sent, but there is no ordering constraint between
# message sends and receives. If the two sides are A and B
# and message An indicates the nth message sent by A, then
# A1 A2 B1 B2 and A1 B1 B2 A2 are both legitimate total
# orderings of the messages.
# As a result, there must be seperate MAC generators for each
# side of the connection. If not, the generator state would
# be different after A1 A2 B1 B2 than it would be after
# A1 B1 B2 A2; if the generator state was different, the MAC
# could not be verified.
self.__hmac_send = None
self.__hmac_recv = None
self.__super_init(sock, map) self.__super_init(sock, map)
def setSessionKey(self, sesskey): def setSessionKey(self, sesskey):
log("set session key %r" % sesskey) log("set session key %r" % sesskey)
self.__hmac = hmac.HMAC(sesskey, digestmod=sha) self.__hmac_send = hmac.HMAC(sesskey, digestmod=sha)
self.__hmac_recv = hmac.HMAC(sesskey, digestmod=sha)
def get_addr(self): def get_addr(self):
return self.addr return self.addr
...@@ -150,16 +171,18 @@ class SizedMessageAsyncConnection(asyncore.dispatcher): ...@@ -150,16 +171,18 @@ class SizedMessageAsyncConnection(asyncore.dispatcher):
inp = "".join(inp) inp = "".join(inp)
offset = 0 offset = 0
expect_mac = 0 has_mac = 0
while (offset + msg_size) <= input_len: while (offset + msg_size) <= input_len:
msg = inp[offset:offset + msg_size] msg = inp[offset:offset + msg_size]
offset = offset + msg_size offset = offset + msg_size
if not state: if not state:
msg_size = struct.unpack(">i", msg)[0] msg_size = struct.unpack(">I", msg)[0]
expect_mac = msg_size & MAC_BIT has_mac = msg_size & MAC_BIT
if expect_mac: if has_mac:
msg_size ^= MAC_BIT msg_size ^= MAC_BIT
msg_size += 20 msg_size += 20
elif self.__hmac_send:
raise ValueError("Received message without MAC")
state = 1 state = 1
else: else:
msg_size = 4 msg_size = 4
...@@ -174,12 +197,12 @@ class SizedMessageAsyncConnection(asyncore.dispatcher): ...@@ -174,12 +197,12 @@ class SizedMessageAsyncConnection(asyncore.dispatcher):
# incoming call to be handled. During all this # incoming call to be handled. During all this
# time, the __input_lock is held. That's a good # time, the __input_lock is held. That's a good
# thing, because it serializes incoming calls. # thing, because it serializes incoming calls.
if expect_mac: if has_mac:
mac = msg[:20] mac = msg[:20]
msg = msg[20:] msg = msg[20:]
if self.__hmac: if self.__hmac_recv:
self.__hmac.update(msg) self.__hmac_recv.update(msg)
_mac = self.__hmac.digest() _mac = self.__hmac_recv.digest()
if mac != _mac: if mac != _mac:
raise ValueError("MAC failed: %r != %r" raise ValueError("MAC failed: %r != %r"
% (_mac, mac)) % (_mac, mac))
...@@ -245,8 +268,9 @@ class SizedMessageAsyncConnection(asyncore.dispatcher): ...@@ -245,8 +268,9 @@ class SizedMessageAsyncConnection(asyncore.dispatcher):
def message_output(self, message): def message_output(self, message):
if __debug__: if __debug__:
if self._debug: if self._debug:
log('message_output %d bytes: %s' % log("message_output %d bytes: %s hmac=%d" %
(len(message), short_repr(message)), (len(message), short_repr(message),
self.__hmac_send and 1 or 0),
level=zLOG.TRACE) level=zLOG.TRACE)
if self.__closed: if self.__closed:
...@@ -255,12 +279,12 @@ class SizedMessageAsyncConnection(asyncore.dispatcher): ...@@ -255,12 +279,12 @@ class SizedMessageAsyncConnection(asyncore.dispatcher):
self.__output_lock.acquire() self.__output_lock.acquire()
try: try:
# do two separate appends to avoid copying the message string # do two separate appends to avoid copying the message string
if self.__hmac: if self.__hmac_send:
self.__output.append(struct.pack(">i", len(message) | MAC_BIT)) self.__output.append(struct.pack(">I", len(message) | MAC_BIT))
self.__hmac.update(message) self.__hmac_send.update(message)
self.__output.append(self.__hmac.digest()) self.__output.append(self.__hmac_send.digest())
else: else:
self.__output.append(struct.pack(">i", len(message))) self.__output.append(struct.pack(">I", len(message)))
if len(message) <= SEND_SIZE: if len(message) <= SEND_SIZE:
self.__output.append(message) self.__output.append(message)
else: else:
......
...@@ -167,6 +167,15 @@ else: ...@@ -167,6 +167,15 @@ else:
self.lock = thread.allocate_lock() self.lock = thread.allocate_lock()
self.thunks = [] self.thunks = []
self._trigger_connected = 0 self._trigger_connected = 0
self._closed = 0
def close(self):
if not self._closed:
self._closed = 1
self.del_channel()
# self.socket is a, self.trigger is w from __init__
self.socket.close()
self.trigger.close()
def __repr__(self): def __repr__(self):
return '<select-trigger (loopback) at %x>' % id(self) return '<select-trigger (loopback) at %x>' % id(self)
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Handy standard storage machinery """Handy standard storage machinery
$Id: BaseStorage.py,v 1.35 2003/09/15 16:29:15 jeremy Exp $ $Id: BaseStorage.py,v 1.36 2003/10/02 18:17:19 jeremy Exp $
""" """
import cPickle import cPickle
import time import time
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Database connection support """Database connection support
$Id: Connection.py,v 1.99 2003/09/15 16:29:15 jeremy Exp $""" $Id: Connection.py,v 1.100 2003/10/02 18:17:19 jeremy Exp $"""
from __future__ import nested_scopes from __future__ import nested_scopes
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
############################################################################## ##############################################################################
"""Database objects """Database objects
$Id: DB.py,v 1.54 2003/09/15 16:29:15 jeremy Exp $""" $Id: DB.py,v 1.55 2003/10/02 18:17:19 jeremy Exp $"""
__version__='$Revision: 1.54 $'[11:-2] __version__='$Revision: 1.55 $'[11:-2]
import cPickle, cStringIO, sys, POSException, UndoLogCompatible import cPickle, cStringIO, sys, POSException, UndoLogCompatible
from Connection import Connection from Connection import Connection
......
...@@ -79,7 +79,7 @@ method:: ...@@ -79,7 +79,7 @@ method::
and call it to monitor the storage. and call it to monitor the storage.
""" """
__version__='$Revision: 1.20 $'[11:-2] __version__='$Revision: 1.21 $'[11:-2]
import base64, time, string import base64, time, string
from ZODB import POSException, BaseStorage, utils from ZODB import POSException, BaseStorage, utils
......
...@@ -115,7 +115,7 @@ ...@@ -115,7 +115,7 @@
# may have a back pointer to a version record or to a non-version # may have a back pointer to a version record or to a non-version
# record. # record.
# #
__version__='$Revision: 1.137 $'[11:-2] __version__='$Revision: 1.138 $'[11:-2]
import base64 import base64
from cPickle import Pickler, Unpickler, loads from cPickle import Pickler, Unpickler, loads
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""ZODB-defined exceptions """ZODB-defined exceptions
$Id: POSException.py,v 1.20 2003/06/10 15:46:31 shane Exp $""" $Id: POSException.py,v 1.21 2003/10/02 18:17:19 jeremy Exp $"""
from types import StringType, DictType from types import StringType, DictType
from ZODB.utils import oid_repr, serial_repr from ZODB.utils import oid_repr, serial_repr
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Transaction management """Transaction management
$Id: Transaction.py,v 1.49 2003/06/10 15:46:31 shane Exp $ $Id: Transaction.py,v 1.50 2003/10/02 18:17:19 jeremy Exp $
""" """
import time, sys, struct, POSException import time, sys, struct, POSException
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
# #
############################################################################## ##############################################################################
__version__ = '3.2b2' __version__ = '3.2c1'
import sys import sys
import cPersistence, Persistence import cPersistence, Persistence
......
...@@ -90,7 +90,7 @@ process must skip such objects, rather than deactivating them. ...@@ -90,7 +90,7 @@ process must skip such objects, rather than deactivating them.
static char cPickleCache_doc_string[] = static char cPickleCache_doc_string[] =
"Defines the PickleCache used by ZODB Connection objects.\n" "Defines the PickleCache used by ZODB Connection objects.\n"
"\n" "\n"
"$Id: cPickleCache.c,v 1.85 2003/05/30 19:20:55 jeremy Exp $\n"; "$Id: cPickleCache.c,v 1.86 2003/10/02 18:17:19 jeremy Exp $\n";
#define ASSIGN(V,E) {PyObject *__e; __e=(E); Py_XDECREF(V); (V)=__e;} #define ASSIGN(V,E) {PyObject *__e; __e=(E); Py_XDECREF(V); (V)=__e;}
#define UNLESS(E) if(!(E)) #define UNLESS(E) if(!(E))
...@@ -499,6 +499,17 @@ cc_clear(ccobject *self, PyObject *args) ...@@ -499,6 +499,17 @@ cc_clear(ccobject *self, PyObject *args)
Py_DECREF(o); Py_DECREF(o);
} }
self->ring_lock = 0; self->ring_lock = 0;
/* Free two references to the Connection, which can't be discovered
via garbage collection.
*/
Py_XDECREF(self->setklassstate);
self->setklassstate = NULL;
Py_XDECREF(self->jar);
self->jar = NULL;
Py_INCREF(Py_None); Py_INCREF(Py_None);
return Py_None; return Py_None;
} }
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
############################################################################## ##############################################################################
"""Open database and storage from a configuration. """Open database and storage from a configuration.
$Id: config.py,v 1.14 2003/09/15 16:29:15 jeremy Exp $""" $Id: config.py,v 1.15 2003/10/02 18:17:19 jeremy Exp $"""
import os import os
from cStringIO import StringIO from cStringIO import StringIO
......
...@@ -878,4 +878,3 @@ class FileStoragePacker(FileStorageFormatter): ...@@ -878,4 +878,3 @@ class FileStoragePacker(FileStorageFormatter):
if self._lock_counter % 20 == 0: if self._lock_counter % 20 == 0:
self._commit_lock_acquire() self._commit_lock_acquire()
return ipos return ipos
...@@ -372,4 +372,3 @@ def recover(inp, outp, verbose=0, partial=0, force=0, pack=0): ...@@ -372,4 +372,3 @@ def recover(inp, outp, verbose=0, partial=0, force=0, pack=0):
if __name__ == "__main__": if __name__ == "__main__":
main() main()
...@@ -96,7 +96,6 @@ class ZODBClientThread(TestThread): ...@@ -96,7 +96,6 @@ class ZODBClientThread(TestThread):
return root.get(name) return root.get(name)
except ConflictError: except ConflictError:
get_transaction().abort() get_transaction().abort()
root._p_jar.sync()
class StorageClientThread(TestThread): class StorageClientThread(TestThread):
......
...@@ -789,5 +789,3 @@ class TransactionalUndoStorage: ...@@ -789,5 +789,3 @@ class TransactionalUndoStorage:
self.assertEqual(d['description'],'t1') self.assertEqual(d['description'],'t1')
self.assertEqual(d['k2'],'this is transaction metadata') self.assertEqual(d['k2'],'this is transaction metadata')
self.assertEqual(d['user_name'],'p3 u3') self.assertEqual(d['user_name'],'p3 u3')
...@@ -528,4 +528,3 @@ class VersionStorage: ...@@ -528,4 +528,3 @@ class VersionStorage:
cn2 = db.open(version="b") cn2 = db.open(version="b")
rt2 = cn2.root() rt2 = cn2.root()
self.assertEqual(rt2["b"].value.value, "still version") self.assertEqual(rt2["b"].value.value, "still version")
...@@ -56,6 +56,7 @@ class ZODBConfigTest(ConfigTestBase): ...@@ -56,6 +56,7 @@ class ZODBConfigTest(ConfigTestBase):
""") """)
def test_file_config1(self): def test_file_config1(self):
import ZODB.FileStorage
path = tempfile.mktemp() path = tempfile.mktemp()
self._test( self._test(
""" """
...@@ -65,9 +66,10 @@ class ZODBConfigTest(ConfigTestBase): ...@@ -65,9 +66,10 @@ class ZODBConfigTest(ConfigTestBase):
</filestorage> </filestorage>
</zodb> </zodb>
""" % path) """ % path)
os.unlink(path) ZODB.FileStorage.cleanup(path)
def test_file_config2(self): def test_file_config2(self):
import ZODB.FileStorage
path = tempfile.mktemp() path = tempfile.mktemp()
cfg = """ cfg = """
<zodb> <zodb>
...@@ -79,6 +81,7 @@ class ZODBConfigTest(ConfigTestBase): ...@@ -79,6 +81,7 @@ class ZODBConfigTest(ConfigTestBase):
</zodb> </zodb>
""" % path """ % path
self.assertRaises(ReadOnlyError, self._test, cfg) self.assertRaises(ReadOnlyError, self._test, cfg)
ZODB.FileStorage.cleanup(path)
def test_zeo_config(self): def test_zeo_config(self):
# We're looking for a port that doesn't exist so a connection attempt # We're looking for a port that doesn't exist so a connection attempt
......
...@@ -151,5 +151,3 @@ class RecoverTest(unittest.TestCase): ...@@ -151,5 +151,3 @@ class RecoverTest(unittest.TestCase):
def test_suite(): def test_suite():
return unittest.makeSuite(RecoverTest) return unittest.makeSuite(RecoverTest)
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
""" """
Revision information: Revision information:
$Id: testTransaction.py,v 1.12 2003/01/27 20:29:50 bwarsaw Exp $ $Id: testTransaction.py,v 1.13 2003/10/02 18:17:17 jeremy Exp $
""" """
""" """
......
...@@ -96,4 +96,3 @@ def oid_repr(oid): ...@@ -96,4 +96,3 @@ def oid_repr(oid):
return repr(oid) return repr(oid)
serial_repr = oid_repr serial_repr = oid_repr
This directory contains a collect of utilities for managing ZODB This directory contains a collection of utilities for managing ZODB
databases. Some are more useful than others. If you install ZODB databases. Some are more useful than others. If you install ZODB
using distutils ("python setup.py install"), fsdump.py, fstest.py, using distutils ("python setup.py install"), fsdump.py, fstest.py,
repozo.py, and zeopack.py will be installed in /usr/local/bin. repozo.py, and zeopack.py will be installed in /usr/local/bin.
...@@ -7,27 +7,27 @@ Unless otherwise noted, these scripts are invoked with the name of the ...@@ -7,27 +7,27 @@ Unless otherwise noted, these scripts are invoked with the name of the
Data.fs file as their only argument. Example: checkbtrees.py data.fs. Data.fs file as their only argument. Example: checkbtrees.py data.fs.
analyze.py -- A transaction analyzer for FileStorage analyze.py -- a transaction analyzer for FileStorage
Reports on the data in a FileStorage. The report is organized by Reports on the data in a FileStorage. The report is organized by
class. It shows total data, as well as separate reports for current class. It shows total data, as well as separate reports for current
and historical revisions of objects. and historical revisions of objects.
checkbtrees.py -- Checks BTrees in a FileStorage for corruption. checkbtrees.py -- checks BTrees in a FileStorage for corruption
Attempts to find all the BTrees contained in a Data.fs and calls their Attempts to find all the BTrees contained in a Data.fs, calls their
_check() methods. _check() methods, and runs them through BTrees.check.check().
fsdump.py -- Summarize FileStorage contents, one line per revision. fsdump.py -- summarize FileStorage contents, one line per revision
Prints a report of FileStorage contents, with one line for each Prints a report of FileStorage contents, with one line for each
transaction and one line for each data record in that transaction. transaction and one line for each data record in that transaction.
Includes time stamps, file positions, and class names. Includes time stamps, file positions, and class names.
fstest.py -- Simple consistency checker for FileStorage fstest.py -- simple consistency checker for FileStorage
usage: fstest.py [-v] data.fs usage: fstest.py [-v] data.fs
...@@ -46,7 +46,14 @@ possible for the damage to occur only in the part of the file that ...@@ -46,7 +46,14 @@ possible for the damage to occur only in the part of the file that
stores object pickles. Those errors will go undetected. stores object pickles. Those errors will go undetected.
netspace.py -- Hackish attempt to report on size of objects space.py -- report space used by objects in a FileStorage
usage: space.py [-v] data.fs
This ignores revisions and versions.
netspace.py -- hackish attempt to report on size of objects
usage: netspace.py [-P | -v] data.fs usage: netspace.py [-P | -v] data.fs
...@@ -57,7 +64,7 @@ Traverses objects from the database root and attempts to calculate ...@@ -57,7 +64,7 @@ Traverses objects from the database root and attempts to calculate
size of object, including all reachable subobjects. size of object, including all reachable subobjects.
parsezeolog.py -- Parse BLATHER logs from ZEO server. parsezeolog.py -- parse BLATHER logs from ZEO server
This script may be obsolete. It has not been tested against the This script may be obsolete. It has not been tested against the
current log output of the ZEO server. current log output of the ZEO server.
...@@ -66,12 +73,12 @@ Reports on the time and size of transactions committed by a ZEO ...@@ -66,12 +73,12 @@ Reports on the time and size of transactions committed by a ZEO
server, by inspecting log messages at BLATHER level. server, by inspecting log messages at BLATHER level.
repozo.py -- Incremental backup utility for FileStorage. repozo.py -- incremental backup utility for FileStorage
Run the script with the -h option to see usage details. Run the script with the -h option to see usage details.
timeout.py -- Script to test transaction timeout timeout.py -- script to test transaction timeout
usage: timeout.py address delay [storage-name] usage: timeout.py address delay [storage-name]
...@@ -80,13 +87,13 @@ and tpc_vote(), and then sleeps forever. This should trigger the ...@@ -80,13 +87,13 @@ and tpc_vote(), and then sleeps forever. This should trigger the
transaction timeout feature of the server. transaction timeout feature of the server.
zeopack.py -- Script to pack a ZEO server. zeopack.py -- pack a ZEO server
The script connects to a server and calls pack() on a specific The script connects to a server and calls pack() on a specific
storage. See the script for usage details. storage. See the script for usage details.
zeoreplay.py -- Experimental script to replay transactions from a ZEO log. zeoreplay.py -- experimental script to replay transactions from a ZEO log
Like parsezeolog.py, this may be obsolete because it was written Like parsezeolog.py, this may be obsolete because it was written
against an earlier version of the ZEO server. See the script for against an earlier version of the ZEO server. See the script for
...@@ -95,7 +102,7 @@ usage details. ...@@ -95,7 +102,7 @@ usage details.
zeoup.py zeoup.py
Usage: zeoup.py [options] usage: zeoup.py [options]
The test will connect to a ZEO server, load the root object, and The test will connect to a ZEO server, load the root object, and
attempt to update the zeoup counter in the root. It will report attempt to update the zeoup counter in the root. It will report
...@@ -106,13 +113,34 @@ start a transaction. ...@@ -106,13 +113,34 @@ start a transaction.
See the script for details about the options. See the script for details about the options.
zodbload.py - exercise ZODB under a heavy synthesized Zope-like load zodbload.py -- exercise ZODB under a heavy synthesized Zope-like load
See the module docstring for details. Note that this script requires See the module docstring for details. Note that this script requires
Zope. New in ZODB3 3.1.4. Zope. New in ZODB3 3.1.4.
zeoserverlog.py - analyze ZEO server log for performance statistics zeoserverlog.py -- analyze ZEO server log for performance statistics
See the module docstring for details; there are a large number of See the module docstring for details; there are a large number of
options. New in ZODB3 3.1.4. options. New in ZODB3 3.1.4.
fsrefs.py -- check FileStorage for dangling references
fstail.py -- display the most recent transactions in a FileStorage
usage: fstail.py [-n nxtn] data.fs
The most recent ntxn transactions are displayed, to stdout.
Optional argument -n specifies ntxn, and defaults to 10.
migrate.py -- do a storage migration and gather statistics
See the module docstring for details.
zeoqueue.py -- report number of clients currently waiting in the ZEO queue
See the module docstring for details.
...@@ -180,4 +180,3 @@ class TestCorruptedFS(unittest.TestCase): ...@@ -180,4 +180,3 @@ class TestCorruptedFS(unittest.TestCase):
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
...@@ -140,7 +140,7 @@ Commands: ...@@ -140,7 +140,7 @@ Commands:
- wall time to verify - wall time to verify
- average miliseconds to verify per object. - average miliseconds to verify per object.
$Id: zeoserverlog.py,v 1.2 2003/09/15 16:29:19 jeremy Exp $ $Id: zeoserverlog.py,v 1.3 2003/10/02 18:17:26 jeremy Exp $
""" """
import datetime, sys, re, os import datetime, sys, re, os
......
...@@ -88,7 +88,7 @@ Usage: loadmail2 [options] ...@@ -88,7 +88,7 @@ Usage: loadmail2 [options]
Specify the mailbox for getting input data. Specify the mailbox for getting input data.
$Id: zodbload.py,v 1.2 2003/09/15 16:29:19 jeremy Exp $ $Id: zodbload.py,v 1.3 2003/10/02 18:17:26 jeremy Exp $
""" """
import mailbox import mailbox
......
...@@ -21,4 +21,3 @@ class BaseLogger: ...@@ -21,4 +21,3 @@ class BaseLogger:
for handler in self.logger.handlers: for handler in self.logger.handlers:
if hasattr(handler, 'reopen') and callable(handler.reopen): if hasattr(handler, 'reopen') and callable(handler.reopen):
handler.reopen() handler.reopen()
...@@ -75,8 +75,3 @@ class StartupHandler(Handler): ...@@ -75,8 +75,3 @@ class StartupHandler(Handler):
for record in self.buffer: for record in self.buffer:
target.handle(record) target.handle(record)
self.buffer = [] self.buffer = []
...@@ -17,6 +17,7 @@ import sys ...@@ -17,6 +17,7 @@ import sys
import tempfile import tempfile
import unittest import unittest
import zLOG import zLOG
import logging
severity_string = { severity_string = {
-300: 'TRACE', -300: 'TRACE',
...@@ -50,13 +51,24 @@ class StupidLogTest(unittest.TestCase): ...@@ -50,13 +51,24 @@ class StupidLogTest(unittest.TestCase):
self.wipeEnvironment() self.wipeEnvironment()
self.path = tempfile.mktemp() self.path = tempfile.mktemp()
self._severity = 0 self._severity = 0
# Windows cannot remove a file that's open. The logging code
# keeps the log file open, and I can't find an advertised API
# to tell the logger to close a log file. So here we cheat:
# tearDown() will close and remove all the handlers that pop
# into existence after setUp() runs. This breaks into internals,
# but I couldn't find a sane way to do it.
self.handlers = logging._handlers.keys() # capture current handlers
def tearDown(self): def tearDown(self):
try: # Close and remove all the handlers that came into existence
# since setUp ran.
for h in logging._handlers.keys():
if h not in self.handlers:
h.close()
del logging._handlers[h]
os.remove(self.path) os.remove(self.path)
except os.error:
pass
self.wipeEnvironment() self.wipeEnvironment()
zLOG.initialize()
def setLog(self, severity=0): def setLog(self, severity=0):
os.environ['%s_LOG_FILE' % self.prefix] = self.path os.environ['%s_LOG_FILE' % self.prefix] = self.path
...@@ -111,14 +123,20 @@ class StupidLogTest(unittest.TestCase): ...@@ -111,14 +123,20 @@ class StupidLogTest(unittest.TestCase):
self.setLog() self.setLog()
zLOG.LOG("basic", zLOG.INFO, "summary") zLOG.LOG("basic", zLOG.INFO, "summary")
f = self.getLogFile() f = self.getLogFile()
try:
self.verifyEntry(f, subsys="basic", summary="summary") self.verifyEntry(f, subsys="basic", summary="summary")
finally:
f.close()
def checkDetail(self): def checkDetail(self):
self.setLog() self.setLog()
zLOG.LOG("basic", zLOG.INFO, "xxx", "this is a detail") zLOG.LOG("basic", zLOG.INFO, "xxx", "this is a detail")
f = self.getLogFile() f = self.getLogFile()
try:
self.verifyEntry(f, subsys="basic", detail="detail") self.verifyEntry(f, subsys="basic", detail="detail")
finally:
f.close()
def checkError(self): def checkError(self):
self.setLog() self.setLog()
...@@ -131,9 +149,13 @@ class StupidLogTest(unittest.TestCase): ...@@ -131,9 +149,13 @@ class StupidLogTest(unittest.TestCase):
zLOG.LOG("basic", zLOG.ERROR, "raised exception", error=err) zLOG.LOG("basic", zLOG.ERROR, "raised exception", error=err)
f = self.getLogFile() f = self.getLogFile()
try:
self.verifyEntry(f, subsys="basic", summary="summary") self.verifyEntry(f, subsys="basic", summary="summary")
self.verifyEntry(f, subsys="basic", severity=zLOG.ERROR, self.verifyEntry(f, subsys="basic", severity=zLOG.ERROR,
error=err) error=err)
finally:
f.close()
class EventLogTest(StupidLogTest): class EventLogTest(StupidLogTest):
""" Test alternate envvars EVENT_LOG_FILE and EVENT_LOG_SEVERITY """ """ Test alternate envvars EVENT_LOG_FILE and EVENT_LOG_SEVERITY """
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment