4 from sqlalchemy import MetaData, Table
5 from sqlalchemy.exc import NoSuchTableError
7 import migrate.versioning.api as migrate
9 from sfa.util.sfalogging import logger
10 import sfa.storage.model as model
12 ########## this class takes care of database upgrades
13 ### upgrade from a pre-2.1 db
14 # * 1.0 and up to 1.1-4: ('very old')
15 # was piggybacking the planetlab5 database
16 # this is kind of out of our scope here, we don't have the credentials
17 # to connect to planetlab5, but this is documented in
18 # https://svn.planet-lab.org/wiki/SFATutorialConfigureSFA#Upgradingnotes
19 # and essentially this is seamless to users
20 # * from 1.1-5 up to 2.0-x: ('old')
21 # uses the 'sfa' db and essentially the 'records' table,
22 # as well as record_types
23 # together with an 'sfa_db_version' table (version, subversion)
25 # we have an 'records' table, plus 'users' and the like
26 # and once migrate has kicked in there is a table named (see migrate.cfg)
27 # migrate_db_version (repository_id, repository_path, version)
29 # Starting with 2.1, we use sqlalchemy-migrate scripts in a standard way
30 # Note that the model defined in sfa.storage.model needs to be maintained
31 # as the 'current/latest' version, and newly installed deployments will
32 # then 'jump' to the latest version number without going through the migrations
34 # An initial attempt to run this as a 001_*.py migrate script
35 # did not quite work out (essentially we need to set the current version
36 # number out of the migrations logic)
37 # also this approach has less stuff in the initscript, which seems just right
41 header="Upgrading to 2.1 or higher"
44 from sfa.storage.alchemy import alchemy
46 self.engine=alchemy.engine
47 self.repository="/usr/share/sfa/migrations"
48 self.meta=MetaData (bind=self.engine)
50 def current_version (self):
52 return migrate.db_version (self.url, self.repository)
56 def table_exists (self, tablename):
58 table=Table (tablename, self.meta, autoload=True)
60 except NoSuchTableError:
63 def drop_table (self, tablename):
64 if self.table_exists (tablename):
65 print >>sys.stderr, "%s: Dropping table %s"%(DBSchema.header,tablename)
66 self.engine.execute ("drop table %s cascade"%tablename)
68 print >>sys.stderr, "%s: no need to drop table %s"%(DBSchema.header,tablename)
70 def handle_old_releases (self):
72 # try to find out which old version this can be
73 if not self.table_exists ('records'):
74 # this likely means we've just created the db, so it's either a fresh install
75 # or we come from a 'very old' depl.
76 # in either case, an import is required but there's nothing to clean up
77 print >> sys.stderr,"%s: make sure to run import"%(DBSchema.header,)
78 elif self.table_exists ('sfa_db_version'):
79 # we come from an 'old' version
80 self.drop_table ('records')
81 self.drop_table ('record_types')
82 self.drop_table ('sfa_db_version')
84 # we should be good here
87 print >> sys.stderr, "%s: unknown exception"%(DBSchema.header,)
88 traceback.print_exc ()
90 # after this call the db schema and the version as known by migrate should
91 # reflect the current data model and the latest known version
92 def init_or_upgrade (self):
93 # check if under version control, and initialize it otherwise
94 if self.current_version() is None:
96 # can be either a very old version, or a fresh install
97 # for very old versions:
98 self.handle_old_releases()
99 # in any case, initialize db from current code and reflect in migrate
100 model.init_tables(self.engine)
101 code_version = migrate.version (self.repository)
102 migrate.version_control (self.url, self.repository, code_version)
103 after="%s"%self.current_version()
104 logger.info("DBSchema : jumped to version %s"%(after))
106 # use migrate in the usual way
107 before="%s"%self.current_version()
108 migrate.upgrade (self.url, self.repository)
109 after="%s"%self.current_version()
111 logger.info("DBSchema : upgraded version from %s to %s"%(before,after))
113 # this trashes the db altogether, from the current model in sfa.storage.model
114 # I hope this won't collide with ongoing migrations and all
115 # actually, now that sfa uses its own db, this is essentially equivalent to
116 # dropping the db entirely, modulo a 'service sfa start'
118 model.drop_tables(self.engine)
119 # so in this case it's like we haven't initialized the db at all
120 migrate.drop_version_control (self.url, self.repository)
123 if __name__ == '__main__':
124 DBSchema().init_or_upgrade()