should be able to upgrade from any version
[sfa.git] / sfa / storage / dbschema.py
1 import sys
2 import traceback
3
4 from sqlalchemy import MetaData, Table
5 from sqlalchemy.exc import NoSuchTableError
6
7 from migrate.versioning.api import version, db_version, version_control, upgrade
8
9 from sfa.util.sfalogging import logger
10 from sfa.storage.model import init_tables
11
12 ### this script will upgrade from a pre-2.1 db 
13 # * 1.0 and up to 1.1-4:  ('very old')    
14 #       was piggybacking the planetlab5 database
15 #       this is kind of out of our scope here, we don't have the credentials 
16 #       to connect to planetlab5, but this is documented in
17 #       https://svn.planet-lab.org/wiki/SFATutorialConfigureSFA#Upgradingnotes
18 #       and essentially this is seamless to users
19 # * from 1.1-5 up to 2.0-x: ('old')
20 #       uses the 'sfa' db and essentially the 'records' table,
21 #       as well as record_types
22 #       together with an 'sfa_db_version' table (version, subversion)
23 # * from 2.1:
24 #       we have an 'records' table, plus 'users' and the like
25 #       and once migrate has kicked in there is a table named 
26 #       migrate_db_version (repository_id, repository_path, version)
27 ####
28 # An initial attempt to run this as a 001_*.py migrate script 
29 # did not quite work out (essentially we need to set the current version
30 # number out of the migrations logic)
31 # also this approach has less stuff in the initscript, which seems just right
32
33 class DBSchema:
34
35     header="Upgrading to 2.1 or higher"
36
37     def __init__ (self):
38         from sfa.storage.alchemy import alchemy
39         self.url=alchemy.url
40         self.engine=alchemy.engine
41         self.repository="/usr/share/sfa/migrations"
42         self.meta=MetaData (bind=self.engine)
43
44     def current_version (self):
45         try:
46             return db_version (self.url, self.repository)
47         except:
48             return None
49
50     def table_exists (self, tablename):
51         try:
52             table=Table (tablename, self.meta, autoload=True)
53             return True
54         except NoSuchTableError:
55             return False
56
57     def drop_table (self, tablename):
58         if self.table_exists (tablename):
59             print >>sys.stderr, "%s: Dropping table %s"%(DBSchema.header,tablename)
60             self.engine.execute ("drop table %s cascade"%tablename)
61         else:
62             print >>sys.stderr, "%s: no need to drop table %s"%(DBSchema.header,tablename)
63         
64     def handle_old_releases (self):
65         try:
66             # try to find out which old version this can be
67             if not self.table_exists ('records'):
68                 # this likely means we've just created the db, so it's either a fresh install
69                 # or we come from a 'very old' depl.
70                 # in either case, an import is required but there's nothing to clean up
71                 print >> sys.stderr,"%s: make sure to run import"%(DBSchema.header,)
72             elif self.table_exists ('sfa_db_version'):
73                 # we come from an 'old' version
74                 self.drop_table ('records')
75                 self.drop_table ('record_types')
76                 self.drop_table ('sfa_db_version')
77             else:
78                 # we should be good here
79                 pass
80         except:
81             print >> sys.stderr, "%s: unknown exception"%(DBSchema.header,)
82             traceback.print_exc ()
83
84     # after this call the db schema and the version as known by migrate should 
85     # reflect the current data model and the latest known version
86     def init_or_upgrade (self):
87         # check if under version control, and initialize it otherwise
88         if self.current_version() is None:
89             before="Unknown"
90             # can be either a very old version, or a fresh install
91             # for very old versions:
92             self.handle_old_releases()
93             # in any case, initialize db from current code and reflect in migrate
94             init_tables(self.engine)
95             code_version = version (self.repository)
96             version_control (self.url, self.repository, code_version)
97         else:
98             # use migrate in the usual way
99             before="%s"%self.current_version()
100             upgrade (self.url, self.repository)
101         after="%s"%self.current_version()
102         if before != after:
103             logger.info("DBSchema : upgraded from %s to %s"%(before,after))
104     
105     # this call will trash the db altogether
106     def nuke (self):
107         drop_tables(self.engine)
108
109 if __name__ == '__main__':
110     DBSchema().init_or_upgrade()