Commit 95f7d240 authored by Chris McDonough's avatar Chris McDonough

Integrate DBTab into HEAD.

DBTab now obtains all values related to storages and databases from zope.conf.  It is also now just a package rather than a product.

A new product named ZODBMountPoint exposes the mount point functionality to the ZMI.
parent 4d0ec56e
......@@ -12,7 +12,7 @@
##############################################################################
__doc__="""System management components"""
__version__='$Revision: 1.88 $'[11:-2]
__version__='$Revision: 1.89 $'[11:-2]
import sys,os,time,Globals, Acquisition, os, Undo
from Globals import DTMLFile
......@@ -28,6 +28,7 @@ from Product import ProductFolder
from version_txt import version_txt
from cStringIO import StringIO
from AccessControl import getSecurityManager
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
import zLOG
import Lifetime
......@@ -39,7 +40,7 @@ class Fake:
def locked_in_version(self): return 0
class DatabaseManager(Fake, SimpleItem.Item, Acquisition.Implicit):
"""Database management"""
"""Database management (legacy) """
manage=manage_main=DTMLFile('dtml/dbMain', globals())
manage_main._setName('manage_main')
id ='DatabaseManagement'
......@@ -69,6 +70,70 @@ class DatabaseManager(Fake, SimpleItem.Item, Acquisition.Implicit):
Globals.default__class_init__(DatabaseManager)
class FakeConnection:
# Supports the methods of Connection that CacheManager needs
def __init__(self, db, parent_jar):
self._db = db
self.version = parent_jar.getVersion()
def db(self):
return self._db
def getVersion(self):
return self.version
class DatabaseChooser (SimpleItem.SimpleItem):
"""Lets you choose which database to view
"""
meta_type = 'Database Management'
name = title = 'Database Management'
icon = 'p_/DatabaseManagement_icon'
isPrincipiaFolderish = 1
manage_options=(
{'label':'Databases', 'action':'manage_main'},
)
manage_main = PageTemplateFile('www/chooseDatabase.pt', globals())
def __init__(self, id):
self.id = id
def getDatabaseNames(self):
configuration = getConfiguration()
names = configuration.dbtab.listDatabaseNames()
names.sort()
return names
def __getitem__(self, name):
configuration = getConfiguration()
db = configuration.dbtab.getDatabase(name=name)
m = AltDatabaseManager()
m.id = name
m._p_jar = FakeConnection(db, self.getPhysicalRoot()._p_jar)
return m.__of__(self)
def __bobo_traverse__(self, request, name):
configuration = getConfiguration()
if configuration.dbtab.hasDatabase(name):
return self[name]
return getattr(self, name)
def tpValues(self):
names = self.getDatabaseNames()
res = []
for name in names:
m = AltDatabaseManager()
m.id = name
# Avoid opening the database just for the tree widget.
m._p_jar = None
res.append(m.__of__(self))
return res
Globals.InitializeClass(DatabaseChooser)
class VersionManager(Fake, SimpleItem.Item, Acquisition.Implicit):
"""Version management"""
manage=manage_main=DTMLFile('dtml/versionManager', globals())
......@@ -207,7 +272,7 @@ class ApplicationManager(Folder,CacheManager):
__roles__=('Manager',)
isPrincipiaFolderish=1
Database= DatabaseManager()
Database= DatabaseChooser('Database') #DatabaseManager()
Versions= VersionManager()
DebugInfo=DebugManager()
DavLocks = DavLockManager()
......@@ -465,3 +530,11 @@ class ApplicationManager(Folder,CacheManager):
self._objects = tuple(lst)
return Folder.objectIds(self, spec)
class AltDatabaseManager(DatabaseManager, CacheManager):
"""Database management DBTab-style
"""
db_name = ApplicationManager.db_name
db_size = ApplicationManager.db_size
manage_pack = ApplicationManager.manage_pack
......@@ -11,11 +11,12 @@
#
##############################################################################
"""
$Id: Undo.py,v 1.32 2002/10/03 18:28:54 jeremy Exp $"""
__version__='$Revision: 1.32 $'[11:-2]
$Id: Undo.py,v 1.33 2003/07/20 02:55:51 chrism Exp $"""
__version__='$Revision: 1.33 $'[11:-2]
import base64
from Acquisition import aq_base, aq_parent, aq_inner
from AccessControl import getSecurityManager
from DateTime import DateTime
import Globals, ExtensionClass
......@@ -86,11 +87,17 @@ class UndoSupport(ExtensionClass.Base):
path=''
if path: spec['user_name']=Prefix(path)
# We also only want to undo things done here
opath='/'.join(self.getPhysicalPath())
if getattr(aq_parent(aq_inner(self)), '_p_jar', None) == self._p_jar:
# We only want to undo things done here (and not in mounted
# databases)
opath='/'.join(self.getPhysicalPath())
else:
# Special case: at the root of a database,
# allow undo of any path.
opath = None
if opath: spec['description']=Prefix(opath)
r=Globals.UndoManager.undoInfo(
r = self._p_jar.db().undoInfo(
first_transaction, last_transaction, spec)
encode = base64.encodestring
......@@ -114,7 +121,8 @@ class UndoSupport(ExtensionClass.Base):
def manage_undo_transactions(self, transaction_info=(), REQUEST=None):
"""
"""
undo=Globals.UndoManager.undo
undo=self._p_jar.db().undo
for tid in transaction_info:
tid=tid.split()
if tid:
......
<h1 tal:replace="structure here/manage_page_header" />
<h2 tal:replace="structure here/manage_tabs" />
<h3>Databases</h3>
<table tal:define="url_quote nocall:
modules/Products/PythonScripts/standard/url_quote">
<tr tal:repeat="name here/getDatabaseNames">
<td tal:define="qname python: url_quote(name)">
<img src="/p_/DatabaseManagement_icon" />
<a tal:content="name" tal:attributes="href
string:${here/absolute_url}/${qname}/manage_main">Main</a>
</td>
</tr>
</table>
<h1 tal:replace="structure here/manage_page_footer" />
HEAD
- Merged into Zope 2.7+. Split into two pieces: the ZODBMountPoint
Product and the DBTab package. Neither custom_zodb.py nor dbtab.conf
is now required (all configuration is performed via zope.conf).
Version 1.2.1
- Began unit tests.
- Fixed a race condition on connection close. The symptom was
spurious "Should not load state when connection closed" errors under
high load.
- DemoStorage configurations can now include a base_type option,
taking advantage of DemoStorage's layering feature.
- The mount status page (visible by selecting "Add DBTab Mount Point")
sometimes indicated there were objects mounted that really were not.
Fixed.
Version 1.2
- Fixed activity monitoring for mounted databases.
- Removed import of AdaptableStorage. Argument converters now work
when you specify the full module of a storage class.
- You can now specify a container_class to generate folderish
objects other than standard folders when mounting a new database.
See dbtab.conf.in.
Version 1.1
- Changed DBTab's mounting strategy so that mounted connections stay
bound to a root connection. This change is designed to:
- eliminate issues with volatile attributes in application code
that cross mount boundaries. Now it's safe to use cross-database
volatile attributes.
- eliminate the global registry of open connections, which seemed
to have a rare race condition (ugh!)
- go faster. :-) The mount point traversal penalty is much lower
now, since the mount point can keep a long-lived reference to the
mounted object.
Version 1.0.2
- Updated to work with the latest BDBStorage and AdaptableStorage.
Version 1.0.1
- Deferred startup until after MainConfiguration has been imported.
Needed for ZRS.
- Added AdaptableStorage and BerkeleyStorage to the list of
easily-configured storage types.
- Fixed bug reported by Robert Boulanger:
If the Storage/Databasename is the same like the mountpath it is
not possible to access the database management screens in the
Control Panel. Instead getting Admin screens for Cache and
Activity you will be redirected to the manage workspace of the
folder.
- Arranged for Zope to properly close open database connections on
clean shutdown.
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Available ZODB class factories.
$Id: ClassFactories.py,v 1.1 2003/07/20 02:55:58 chrism Exp $"""
import OFS.Uninstalled
class_factories = {}
def minimalClassFactory(jar, module, name,
_silly=('__doc__',), _globals={},
):
"""Minimal class factory.
If any class is not found, this class factory will propagate
the exception to the application, unlike the other class factories.
"""
m = __import__(module, _globals, _globals, _silly)
return getattr(m, name)
class_factories['minimal'] = minimalClassFactory
def simpleClassFactory(jar, module, name,
_silly=('__doc__',), _globals={},
):
"""Class factory without ZClass support.
"""
try:
m = __import__(module, _globals, _globals, _silly)
return getattr(m, name)
except:
return OFS.Uninstalled.Broken(jar, None, (module, name))
class_factories['simple'] = simpleClassFactory
def zopeClassFactory(jar, module, name,
_silly=('__doc__',), _globals={},
):
"""Class factory with ZClass support.
"""
try:
if module[:1]=='*':
# ZCLass! Yee ha!
return jar.root()['ZGlobals'][module]
else:
m=__import__(module, _globals, _globals, _silly)
return getattr(m, name)
except:
return OFS.Uninstalled.Broken(jar, None, (module, name))
class_factories['zope'] = zopeClassFactory
def autoClassFactory(jar, module, name):
"""Class factory with ZClasses and support for central class definitions.
"""
# If not the root connection, use the class factory from
# the root database, otherwise use the Zope class factory.
root_conn = getattr(jar, '_root_connection', None)
root_db = getattr(root_conn, '_db', None)
if root_db is not None:
return root_db._classFactory(root_conn, module, name)
else:
return zopeClassFactory(jar, module, name)
class_factories['auto'] = autoClassFactory
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""DBTab and DatabaseFactory classes.
$Id: DBTab.py,v 1.1 2003/07/20 02:55:58 chrism Exp $
"""
import sys
from thread import allocate_lock
from ZODB.ActivityMonitor import ActivityMonitor
import Globals
from Exceptions import DBTabConfigurationError
class DBTab:
"""A Zope database configuration, similar in purpose to /etc/fstab.
"""
def __init__(self, db_factories, mount_paths):
self._started = 0
self.opened = {} # { name -> Database instance }
self.lock = allocate_lock()
self.db_factories = db_factories # { name -> DatabaseFactory }
self.mount_paths = mount_paths # { virtual path -> name }
def startup(self):
"""Opens the databases set to open_at_startup."""
if self._started:
return
self._started = 1
for name, factory in self.db_factories.items():
if factory.getOpenAtStartup():
self.getDatabase(name=name)
def listMountPaths(self):
"""Returns a sequence of (virtual_mount_path, database_name).
"""
return self.mount_paths.items()
def listDatabaseNames(self):
"""Returns a sequence of names.
"""
return self.db_factories.keys()
def hasDatabase(self, name):
"""Returns true if name is the name of a configured database."""
return self.db_factories.has_key(name)
def _mountPathError(self, mount_path):
if mount_path == '/':
raise DBTabConfigurationError(
"No root database configured")
else:
raise DBTabConfigurationError(
"No database configured for mount point at %s"
% mount_path)
def getDatabase(self, mount_path=None, name=None, is_root=0):
"""Returns an opened database. Requires either mount_path or name.
"""
self.startup()
if name is None:
if mount_path is None:
raise ValueError('Either mount_path or name is required')
name = self.mount_paths.get(mount_path)
if name is None:
self._mountPathError(mount_path)
db = self.opened.get(name)
if db is None:
if not self.db_factories.has_key(name):
raise KeyError('%s is not a configured database' % repr(name))
self.lock.acquire()
try:
# Check again, since the database may have been created
# by another thread before the lock was acquired.
db = self.opened.get(name)
if db is None:
db = self._createDatabase(name, is_root)
finally:
self.lock.release()
return db
def getDatabaseFactory(self, mount_path=None, name=None):
if name is None:
if mount_path is None:
raise ValueError('Either mount_path or name is required')
name = self.mount_paths.get(mount_path)
if name is None:
self._mountPathError(mount_path)
return self.db_factories[name]
def _createDatabase(self, name, is_root):
factory = self.db_factories[name]
db = factory.open()
self.opened[name] = db
if not is_root:
Globals.opened.append(db)
# If it is the root database, Zope will add the database to
# Globals.opened. A database should not be listed twice.
return db
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""DBTab exception classes.
$Id: Exceptions.py,v 1.1 2003/07/20 02:55:58 chrism Exp $
"""
class DBTabConfigurationError (Exception):
"""Error in dbtab configuration"""
args = ()
class DBTabOverrideError (Exception):
"""DBTab has taken over some piece of functionality"""
args = ()
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""DBTab product.
$Id: __init__.py,v 1.1 2003/07/20 02:55:58 chrism Exp $
"""
# importing ThreadedAsync has the side effect of patching asyncore so
# that loop callbacks get invoked. You need this to
# mount a ZEO client connection if the main database is not a ZEO client.
# Otherwise ZEO never receives the message telling it to start using the
# main async loop.
import ThreadedAsync
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""ZODB Mounted database support, simplified for DBTab.
$Id: Mount.py,v 1.1 2003/07/20 02:56:01 chrism Exp $"""
import time, sys
import Persistence, Acquisition
from Acquisition import aq_base
from ZODB.POSException import MountedStorageError
from zLOG import LOG, ERROR, INFO, WARNING
class MountPoint(Persistence.Persistent, Acquisition.Implicit):
'''The base class for a Zope object which, when traversed,
accesses a different database.
'''
# Default values for non-persistent variables.
_v_data = None # An object in an open connection
_v_connect_error = None
def __init__(self, id):
self.id = id
def _getDB(self):
"""Hook for getting the DB object for this mount point.
"""
raise NotImplementedError
def _getDBName(self):
"""Hook for getting the name of the database for this mount point.
"""
raise NotImplementedError
def _getRootDBName(self):
"""Hook for getting the name of the root database.
"""
raise NotImplementedError
def _traverseToMountedRoot(self, root, mount_parent):
"""Hook for getting the object to be mounted.
"""
raise NotImplementedError
def __repr__(self):
return "%s(id=%s)" % (self.__class__.__name__, repr(self.id))
def _getMountedConnection(self, anyjar):
db_name = self._getDBName()
conn = anyjar._getMountedConnection(db_name)
if conn is None:
root_conn = anyjar._getRootConnection()
if db_name == self._getRootDBName():
conn = root_conn
else:
conn = self._getDB().open(version=root_conn.getVersion())
root_conn._addMountedConnection(db_name, conn)
return conn
def _getOrOpenObject(self, parent):
t = self._v_data
if t is not None:
data = t[0]
else:
self._v_connect_error = None
conn = None
try:
anyjar = self._p_jar
if anyjar is None:
anyjar = parent._p_jar
conn = self._getMountedConnection(anyjar)
root = conn.root()
obj = self._traverseToMountedRoot(root, parent)
data = aq_base(obj)
# Store the data object in a tuple to hide from acquisition.
self._v_data = (data,)
except:
# Possibly broken database.
self._logConnectException()
raise
try:
# XXX This method of finding the mount point is deprecated.
# Do not use the _v_mount_point_ attribute.
data._v_mount_point_ = (aq_base(self),)
except:
# Might be a read-only object.
pass
return data.__of__(parent)
def __of__(self, parent):
# Accesses the database, returning an acquisition
# wrapper around the connected object rather than around self.
try:
return self._getOrOpenObject(parent)
except:
return Acquisition.ImplicitAcquisitionWrapper(self, parent)
def _test(self, parent):
'''Tests the database connection.
'''
self._getOrOpenObject(parent)
return 1
def _logConnectException(self):
'''Records info about the exception that just occurred.
'''
try:
from cStringIO import StringIO
except:
from StringIO import StringIO
import traceback
exc = sys.exc_info()
LOG('ZODB', ERROR, 'Failed to mount database. %s (%s)' % exc[:2],
error=exc)
f=StringIO()
traceback.print_tb(exc[2], 100, f)
self._v_connect_error = (exc[0], exc[1], f.getvalue())
exc = None
class ConnectionPatches:
# Changes to Connection.py that might fold into ZODB
_root_connection = None
_mounted_connections = None
def _getRootConnection(self):
root_conn = self._root_connection
if root_conn is None:
return self
else:
return root_conn
def _getMountedConnection(self, name):
conns = self._getRootConnection()._mounted_connections
if conns is None:
return None
else:
return conns.get(name)
def _addMountedConnection(self, name, conn):
if conn._root_connection is not None:
raise ValueError, 'Connection %s is already mounted' % repr(conn)
root_conn = self._getRootConnection()
conns = root_conn._mounted_connections
if conns is None:
conns = {}
root_conn._mounted_connections = conns
if conns.has_key(name):
raise KeyError, 'A connection named %s already exists' % repr(name)
conn._root_connection = root_conn
conns[name] = conn
def _setDB(self, odb):
self._real_setDB(odb)
conns = self._mounted_connections
if conns:
for conn in conns.values():
conn._setDB(conn._db)
def close(self):
if self._root_connection is not None:
raise RuntimeError("Should not close mounted connections directly")
conns = self._mounted_connections
if conns:
for conn in conns.values():
# Notify the activity monitor
db = conn.db()
f = getattr(db, 'getActivityMonitor', None)
if f is not None:
am = f()
if am is not None:
am.closedConnection(conn)
conn._incrgc() # This is a good time to do some GC
# XXX maybe we ought to call the close callbacks.
conn._storage = conn._tmp = conn.new_oid = conn._opened = None
conn._debug_info = ()
# The mounted connection keeps a reference to
# its database, but nothing else.
# Note that mounted connections can not operate
# independently, so don't use _closeConnection() to
# return them to the pool. Only the root connection
# should be returned.
# Close this connection only after the mounted connections
# have been closed. Otherwise, this connection gets returned
# to the pool too early and another thread might use this
# connection before the mounted connections have all been
# closed.
self._real_close()
if 1:
# patch Connection.py.
from ZODB.Connection import Connection
Connection._real_setDB = Connection._setDB
Connection._real_close = Connection.close
for k, v in ConnectionPatches.__dict__.items():
setattr(Connection, k, v)
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""ZODBMountPoint product.
$Id: __init__.py,v 1.1 2003/07/20 02:56:01 chrism Exp $
"""
def initialize(context):
# Configure and load databases if not already done.
import MountedObject
context.registerClass(
MountedObject.MountedObject,
constructors=(MountedObject.manage_addMountsForm,
MountedObject.manage_getMountStatus,
MountedObject.manage_addMounts,),
)
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Tests of DBTab and ZODBMountPoint
"""
import os
import sys
import unittest
import Testing
import ZODB
from OFS.Application import Application
from OFS.Folder import Folder
from Products.ZODBMountPoint.MountedObject \
import setConfiguration, manage_addMounts, getMountPoint
from DBTab.DBTab import DBTab
try:
__file__
except NameError:
__file__ = os.path.abspath(sys.argv[0])
class TestDBConfig:
def __init__(self, fname, mpoints):
self.fname = fname
self.mpoints = mpoints
def getDB(self):
from ZODB.config import DemoStorage
from ZODB.Connection import Connection
from Zope.Startup.datatypes import ZopeDatabase
self.name = self.fname
self.base = None
self.path = os.path.join(os.path.dirname(__file__), self.fname)
self.create = None
self.read_only = None
self.quota = None
self.cache_size = 5000
self.pool_size = 7
self.version_pool_size = 3
self.version_cache_size = 100
self.mount_points = self.mpoints
self.connection_class = Connection
self.class_factory = None
self.storage = DemoStorage(self)
return ZopeDatabase(self)
def getSectionName(self):
return self.name
class DBTabTests (unittest.TestCase):
def setUp(self):
databases = [TestDBConfig('test_main.fs', ['/']).getDB(),
TestDBConfig('test_mount1.fs', ['/mount1']).getDB(),
TestDBConfig('test_mount2.fs', ['/mount2']).getDB(),
]
mount_points = {}
mount_factories = {}
for database in databases:
points = database.getVirtualMountPaths()
name = database.config.getSectionName()
mount_factories[name] = database
for point in points:
mount_points[point] = name
conf = DBTab(mount_factories, mount_points)
setConfiguration(conf)
self.conf = conf
db = conf.getDatabase('/')
self.db = db
conn = db.open()
root = conn.root()
root['Application'] = app = Application()
self.app = app
get_transaction().commit() # Get app._p_jar set
manage_addMounts(app, ('/mount1', '/mount2'))
get_transaction().commit() # Get the mount points ready
def tearDown(self):
setConfiguration(None)
get_transaction().abort()
self.app._p_jar.close()
del self.app
del self.db
for db in self.conf.opened.values():
db.close()
del self.conf
def testRead(self):
self.assertEqual(self.app.mount1.id, 'mount1')
self.assertEqual(self.app.mount2.id, 'mount2')
def testWrite(self):
app = self.app
app.mount1.a1 = '1'
app.mount2.a2 = '2'
app.a3 = '3'
self.assertEqual(app.mount1._p_changed, 1)
self.assertEqual(app.mount2._p_changed, 1)
self.assertEqual(app._p_changed, 1)
get_transaction().commit()
self.assertEqual(app.mount1._p_changed, 0)
self.assertEqual(app.mount2._p_changed, 0)
self.assertEqual(app._p_changed, 0)
def testRaceOnClose(self):
# There used to be a race condition in
# ConnectionPatches.close(). The root connection was returned
# to the pool before the mounted connections were closed. If
# another thread pulled the root connection out of the pool
# before the original thread finished closing mounted
# connections, when the original thread got control back it
# closed the mounted connections even though the new thread
# was using them.
# Test by patching to watch for a vulnerable moment.
from ZODB.DB import DB
def _closeConnection(self, connection):
self._real_closeConnection(connection)
mc = connection._mounted_connections
if mc is not None:
for c in mc.values():
if c._storage is not None:
raise AssertionError, "Connection remained partly open"
DB._real_closeConnection = DB._closeConnection
DB._closeConnection = _closeConnection
try:
conn = self.db.open()
conn.root()['Application']['mount1']
conn.root()['Application']['mount2']
conn.close()
finally:
DB._closeConnection = DB._real_closeConnection
del DB._real_closeConnection
def testGetMountPoint(self):
self.assert_(getMountPoint(self.app) is None)
self.assert_(getMountPoint(self.app.mount1) is not None)
self.assertEqual(getMountPoint(self.app.mount1)._path, '/mount1')
self.assert_(getMountPoint(self.app.mount2) is not None)
self.assertEqual(getMountPoint(self.app.mount2)._path, '/mount2')
del self.app.mount2
self.app.mount2 = Folder()
self.app.mount2.id = 'mount2'
self.assert_(getMountPoint(self.app.mount2) is None)
get_transaction().commit()
self.assert_(getMountPoint(self.app.mount2) is None)
def test_suite():
return unittest.makeSuite(DBTabTests, 'test')
if __name__ == '__main__':
unittest.main()
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h2 tal:define="form_title string:Add ZODB Mount Points"
tal:replace="structure here/manage_form_title">Form Title</h2>
<p class="form-help">
Use this form to finalize the mount points configured in zope.conf.
To make more mount points available, edit zope.conf.
</p>
<div tal:define="stats here/manage_getMountStatus">
<div tal:condition="stats">
<form action="manage_addMounts" method="POST">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<th>
</th>
<th align="left">
Path
</th>
<th align="left">
Database
</th>
<th align="left">
Status
</th>
</tr>
<tr tal:repeat="stat stats">
<td>
<input tal:condition="not: stat/exists" tal:attributes="value stat/path"
type="checkbox" name="paths:list" checked="checked" />
</td>
<td valign="top" nowrap="nowrap">
<span tal:content="stat/path">/virtual_hosts</span>
</td>
<td valign="top" nowrap="nowrap">
<span tal:content="stat/name">Virtual Hosts</span>
</td>
<td valign="top">
<span tal:content="stat/status">Ok</span>
</td>
</tr>
</table>
&nbsp;<br />
<input class="form-element" type="checkbox" name="create_mount_points" />
<span class="form-element">Create new folders if the mounted objects don't yet exist</span>
<br />
<input class="form-element" type="submit" name="submit"
value="Create selected mount points" />
</form>
</div>
<div tal:condition="not:stats">
<em>No mount points configured.</em>
</div>
</div>
<h1 tal:replace="structure here/manage_page_footer">Footer</h1>
<h1 tal:replace="structure here/manage_page_header" />
<h2 tal:replace="structure here/manage_tabs" />
<h3>Mount Failure Traceback</h3>
<div tal:define="exc here/mount_error_">
<strong>Error type:</strong>
<span tal:replace="python: exc[0]">Error</span><br />
<strong>Error value:</strong>
<span tal:replace="python: exc[1]">An error occurred.</span><br />
<pre tal:content="python: exc[2]">
Traceback
</pre>
</div>
<h1 tal:replace="structure here/manage_page_footer" />
......@@ -52,10 +52,9 @@ def startup():
m=imp.find_module('custom_zodb',[getConfiguration().instancehome])
except:
# if there is no custom_zodb, use the config file specified databases
config = getConfiguration()
name = config.db_mount_tab['/']
DB = config.db_name_tab[name].open()
Globals.BobobaseName = name
configuration = getConfiguration()
DB = configuration.dbtab.getDatabase('/', is_root=1)
Globals.BobobaseName = DB.getName()
else:
m=imp.load_module('Zope.custom_zodb', m[0], m[1], m[2])
if hasattr(m,'DB'):
......
......@@ -21,31 +21,6 @@ import re
import ZConfig
from cmdline import getOptions, getOptionDescriptions # exported
# global to hold config structures
_schema = None
_configuration = None
def getConfiguration():
return _configuration
def getSchema():
global _schema
if _schema is None:
here = os.path.dirname(__file__)
path = os.path.join(here, 'zopeschema.xml')
_schema = ZConfig.loadSchema(path)
return _schema
def configure(config_location, options):
global _configuration
import handlers
schema = getSchema()
_configuration, handler = ZConfig.loadConfig(schema, config_location)
handlers.handleConfig(_configuration, handler, options)
return _configuration
def start_zope(cfg):
# set up our initial logging environment (log everything to stderr
# if we're not in debug mode).
......
......@@ -103,7 +103,7 @@ def importable_name(name):
# Datatype for the root configuration object
# (adds the softwarehome and zopehome fields; default values for some
# computed paths)
# computed paths, configures dbtab)
def root_config(section):
from ZConfig import ConfigurationError
......@@ -125,31 +125,76 @@ def root_config(section):
if not section.databases:
section.databases = [getDefaultDatabaseFactory(section)]
section.db_mount_tab = db_mount_tab = {}
section.db_name_tab = db_name_tab = {}
mount_factories = {} # { name -> factory}
mount_points = {} # { virtual path -> name }
dup_err = ('Invalid configuration: ZODB databases named "%s" and "%s" are '
'both configured to use the same mount point, named "%s"')
for database in section.databases:
mount_points = database.config.mount_points
points = database.getVirtualMountPaths()
name = database.config.getSectionName()
db_name_tab[name] = database
for point in mount_points:
if db_mount_tab.has_key(point):
raise ConfigurationError(dup_err % (db_mount_tab[point], name,
point))
db_mount_tab[point] = name
mount_factories[name] = database
for point in points:
if mount_points.has_key(point):
raise ConfigurationError(dup_err % (mount_points[point],
name, point))
mount_points[point] = name
from DBTab.DBTab import DBTab
section.dbtab = DBTab(mount_factories, mount_points)
return section
class ZopeDatabase(ZODBDatabase):
""" A ZODB database datatype that can handle an extended set of
attributes """
attributes for use by DBTab """
container_class = 'OFS.Folder.Folder'
def open(self):
DB = ZODBDatabase.open(self)
# set the connection class
DB.klass = self.config.connection_class
if self.config.class_factory is not None:
DB.setClassFactory(self.config.class_factory)
from ZODB.ActivityMonitor import ActivityMonitor
DB.setActivityMonitor(ActivityMonitor())
return DB
def getName(self):
return self.name
def getOpenAtStartup(self):
# XXX implement
return 0
def computeMountPaths(self):
mps = []
for part in self.config.mount_points:
real_root = None
if ':' in part:
# 'virtual_path:real_path'
virtual_path, real_path = part.split(':', 1)
if real_path.startswith('~'):
# Use a special root.
# 'virtual_path:~real_root/real_path'
real_root, real_path = real_path[1:].split('/', 1)
else:
# Virtual path is the same as the real path.
virtual_path = real_path = part
mps.append((virtual_path, real_root, real_path))
return mps
def getVirtualMountPaths(self):
return [item[0] for item in self.computeMountPaths()]
def getMountParams(self, mount_path):
"""Returns (real_root, real_path, container_class) for a virtual
mount path.
"""
for (virtual_path, real_root, real_path) in self.computeMountPaths():
if virtual_path == mount_path:
return (real_root, real_path, self.container_class)
raise LookupError('Nothing known about mount path %s' % mount_path)
def getDefaultDatabaseFactory(context):
# default to a filestorage named 'Data.fs' in clienthome
......@@ -176,5 +221,6 @@ def getDefaultDatabaseFactory(context):
db_ns.version_cache_size = 100
db_ns.mount_points = ['/']
db_ns.connection_class = Connection
db_ns.class_factory = None
return ZopeDatabase(db_ns)
......@@ -139,16 +139,29 @@
so this is a multikey.
</description>
<multikey name="mount-point" required="yes" attribute="mount_points"
datatype=".mount_point"/>
datatype=".mount_point">
<description>
The mount point is the slash-separated path to which this database
will be mounted within the Zope application server.
</description>
</multikey>
<description>
We want to allow people to be able to change the connection
class a database uses on a per-database basis to support
different connection policies.
</description>
<key name="connection-class" datatype=".importable_name"
default="ZODB.Connection.Connection"/>
default="ZODB.Connection.Connection">
<description>
Change the connection class a database uses on a per-database basis to
support different connection policies. Use a Python dotted-path
name to specify the connection class.
</description>
</key>
<key name="class-factory" datatype=".importable_name">
<description>
Change the class factory function a database uses on a
per-database basis to support different class factory policy.
Use a Python dotted-path name to specify the class factory function.
</description>
</key>
</sectiontype>
<!-- end of type definitions -->
......
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