Author: jure
Date: Mon Mar 4 10:36:14 2013
New Revision: 1452243
URL: http://svn.apache.org/r1452243
Log:
#406, initial implementation of multi product database upgrade
Modified:
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/api.py
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
Modified:
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/api.py
URL:
http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/api.py?rev=1452243&r1=1452242&r2=1452243&view=diff
==============================================================================
---
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/api.py
(original)
+++
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/api.py
Mon Mar 4 10:36:14 2013
@@ -25,7 +25,7 @@ import copy
from pkg_resources import resource_filename
from trac.config import Option, PathOption
-from trac.core import Component, TracError, implements
+from trac.core import Component, TracError, implements, Interface
from trac.db import Table, Column, DatabaseManager, Index
from trac.env import IEnvironmentSetupParticipant
from trac.perm import IPermissionRequestor
@@ -40,11 +40,28 @@ DB_VERSION = 4
DB_SYSTEM_KEY = 'bloodhound_multi_product_version'
PLUGIN_NAME = 'Bloodhound multi product'
+class ISupportMultiProductEnvironment(Interface):
+ """Extension point interface for components that are aware of multi
+ product environment and its specifics.
+
+ Component implementing this interface is handled in a special way in the
+ following scenarios:
+
+ * if implementing `IEnvironmentSetupParticipant` interface, the component
+ will only be invoked once per global environment creation/upgrade. It is
+ up to the component to install/update it's environment specifics (schema,
+ possibly files, etc.) for all products. In contrast, components that
don't
+ implement `ISupportMultiProductEnvironment` interface will be, during
+ install/update, invoked per product environment.
+ """
+ pass
+
class MultiProductSystem(Component):
"""Creates the database tables and template directories"""
implements(IEnvironmentSetupParticipant, ITemplateProvider,
- IPermissionRequestor, ITicketFieldProvider, IResourceManager)
+ IPermissionRequestor, ITicketFieldProvider, IResourceManager,
+ ISupportMultiProductEnvironment)
product_base_url = Option('multiproduct', 'product_base_url', '',
"""A pattern used to generate the base URL of product environments,
Modified:
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
URL:
http://svn.apache.org/viewvc/incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py?rev=1452243&r1=1452242&r2=1452243&view=diff
==============================================================================
---
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
(original)
+++
incubator/bloodhound/branches/bep_0003_multiproduct/bloodhound_multiproduct/multiproduct/env.py
Mon Mar 4 10:36:14 2013
@@ -23,20 +23,31 @@ from urlparse import urlsplit
from sqlite3 import OperationalError
from trac.config import BoolOption, ConfigSection, Option
-from trac.core import Component, ComponentManager, implements
+from trac.core import Component, ComponentManager, implements, Interface,
ExtensionPoint
from trac.db.api import TransactionContextManager, QueryContextManager,
DatabaseManager
from trac.util import get_pkginfo, lazy
from trac.util.compat import sha1
from trac.versioncontrol import RepositoryManager
from trac.web.href import Href
-from multiproduct.api import MultiProductSystem
+from multiproduct.api import MultiProductSystem,
ISupportMultiProductEnvironment
from multiproduct.config import Configuration
from multiproduct.dbcursor import ProductEnvContextManager,
BloodhoundConnectionWrapper
from multiproduct.model import Product
import trac.env
+class ComponentEnvironmentContext(object):
+ def __init__(self, env, component):
+ self._env = env
+ self._component = component
+ def __enter__(self):
+ self._old_env = self._component.env
+ self._env.component_activated(self._component)
+ return self
+ def __exit__(self, type, value, traceback):
+ self._old_env.component_activated(self._component)
+
class Environment(trac.env.Environment):
"""Bloodhound environment manager
@@ -46,12 +57,19 @@ class Environment(trac.env.Environment):
to ProductEnvironment that features per-product view of the database
(in the context of selected product).
"""
+
+ multi_product_support_components =
ExtensionPoint(ISupportMultiProductEnvironment)
+
def __init__(self, path, create=False, options=[]):
# global environment w/o parent, set these two before super.__init__
# as database access can take place within trac.env.Environment
self.parent = None
self.product = None
super(Environment, self).__init__(path, create=create, options=options)
+ self._global_setup_participants =
set.intersection(set(self.setup_participants),
+
set(self.multi_product_support_components))
+ self._product_setup_participants = [participant for participant in
self.setup_participants
+ if not participant in
self._global_setup_participants]
@property
def db_query(self):
@@ -69,6 +87,98 @@ class Environment(trac.env.Environment):
def db_direct_transaction(self):
return ProductEnvContextManager(super(Environment,
self).db_transaction)
+ def needs_upgrade(self):
+ """Return whether the environment needs to be upgraded."""
+ def needs_upgrade_in_env_list(env_list, participants):
+ for env in env_list:
+ for participant in participants:
+ # make sure to skip anything but global environment for
multi
+ # product aware components
+ if participant in self._global_setup_participants and \
+ not env == self:
+ continue
+ with ComponentEnvironmentContext(env, participant):
+ with env.db_query as db:
+ if participant.environment_needs_upgrade(db):
+ self.log.warn("component %s.%s requires
environment upgrade in environment %s...",
+ participant.__module__,
participant.__class__.__name__,
+ env)
+ return True
+ if needs_upgrade_in_env_list([self], self._global_setup_participants):
+ return True
+ product_envs = [self] + [ProductEnvironment(self, product) for product
in Product.select(self)]
+ if needs_upgrade_in_env_list(product_envs,
self._product_setup_participants):
+ return True
+ return False
+
+ def upgrade(self, backup=False, backup_dest=None):
+ """Upgrade database.
+
+ :param backup: whether or not to backup before upgrading
+ :param backup_dest: name of the backup file
+ :return: whether the upgrade was performed
+ """
+ def upgraders_for_env_list(env_list, participants):
+ upgraders = []
+ if not participants:
+ return upgraders
+ for env in env_list:
+ for participant in participants:
+ # skip global participants in non-global environments
+ if participant in self._global_setup_participants and \
+ not env == self:
+ continue
+ with ComponentEnvironmentContext(env, participant):
+ with env.db_query as db:
+ if participant.environment_needs_upgrade(db):
+ self.log.info("%s.%s needs upgrade in
environment %s...",
+ participant.__module__,
participant.__class__.__name__,
+ env)
+ upgraders.append((env, participant))
+ return upgraders
+
+ def upgraders_for_product_envs():
+ product_envs = [self] + [ProductEnvironment(self, product) for
product in Product.select(self)]
+ return upgraders_for_env_list(product_envs,
self._product_setup_participants)
+
+ # first enumerate components that are multi product aware and require
upgrade
+ # in global environment
+ global_upgraders = upgraders_for_env_list([self],
self._global_setup_participants)
+ product_upgraders = None
+ if not global_upgraders:
+ # if no upgrades required in global environment, enumerate
required upgrades
+ # for product environments
+ product_upgraders = upgraders_for_product_envs()
+
+ if not global_upgraders + (product_upgraders or []):
+ return False
+
+ if backup:
+ try:
+ self.backup(backup_dest)
+ except Exception, e:
+ raise trac.env.BackupError(e)
+
+ def execute_upgrades(upgraders_list):
+ for env, participant in upgraders_list:
+ self.log.info("%s.%s upgrading in environment %s...",
+ participant.__module__,
participant.__class__.__name__,
+ env)
+ with ComponentEnvironmentContext(env, participant):
+ with env.db_transaction as db:
+ participant.upgrade_environment(db)
+ # Database schema may have changed, so close all connections
+ DatabaseManager(env).shutdown()
+
+ # execute global upgrades first, product environment upgrades next
+ if global_upgraders:
+ execute_upgrades(global_upgraders)
+ if product_upgraders == None:
+ product_upgraders = upgraders_for_product_envs()
+ if product_upgraders:
+ execute_upgrades(product_upgraders)
+ return True
+
# replace trac.env.Environment with Environment
trac.env.Environment = Environment