aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--rpkid/rpki-sql-upgrade187
1 files changed, 187 insertions, 0 deletions
diff --git a/rpkid/rpki-sql-upgrade b/rpkid/rpki-sql-upgrade
new file mode 100644
index 00000000..18987e82
--- /dev/null
+++ b/rpkid/rpki-sql-upgrade
@@ -0,0 +1,187 @@
+#!/usr/bin/env python
+
+# $Id$
+#
+# Copyright (C) 2014 Dragon Research Labs ("DRL")
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND DRL DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL DRL BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""
+Run upgrade scripts to drag installed version of RPKI-related SQL
+databases up to the current version.
+
+We ignore the IRDB, on the theory that the Django stuff can take care
+of itself, using South migrations if necessary. We could use South
+for everything if we were to rewrit the rpkid and pubd databases using
+the Django ORM, but that's a big change. Perhaps someday.
+"""
+
+import os
+import sys
+import time
+import glob
+import argparse
+import rpki.config
+import rpki.autoconf
+import rpki.version
+import rpki.sundial
+
+from rpki.mysql_import import MySQLdb, _mysql_exceptions
+
+ER_NO_SUCH_TABLE = 1146 # See mysqld_ername.h
+
+class Version(object):
+ """
+ A version number. This is a class in its own right to force the
+ comparision and string I/O behavior we want.
+ """
+
+ def __init__(self, v):
+ if v is None:
+ v = "0.0"
+ self.v = tuple(v.lower().split("."))
+
+ def __str__(self):
+ return ".".join(self.v)
+
+ def __cmp__(self, other):
+ return cmp(self.v, other.v)
+
+current_version = Version(rpki.version.VERSION)
+
+class Database(object):
+ """
+ One of the SQL databases we're whacking.
+
+ NB: The SQL definition for the upgrade_version table is embedded in
+ this class rather than being declared in any of the .sql files.
+ This is deliberate: nothing but the upgrade system should ever touch
+ this table, and it's simpler to keep everything in one place.
+
+ We have to be careful about SQL commits here, because CREATE TABLE
+ implies an automatic commit. So presence of the table per se isn't
+ significant, only its content (or lack thereof).
+ """
+
+ upgrade_version_table_schema = """
+ CREATE TABLE upgrade_version (
+ version TEXT NOT NULL,
+ updated DATETIME NOT NULL
+ ) ENGINE=InnoDB
+ """
+
+ def __init__(self, name):
+ self.name = name
+ self.enabled = cfg.getboolean("start_" + name, False)
+ if self.enabled:
+ if args.verbose:
+ print "Opening", name
+ self.db = MySQLdb.connect(
+ db = cfg.get("sql-database", section = name),
+ user = cfg.get("sql-username", section = name),
+ passwd = cfg.get("sql-password", section = name))
+ self.db.autocommit(False)
+ cur = self.db.cursor()
+ try:
+ cur.execute("SELECT version FROM upgrade_version")
+ except _mysql_exceptions.ProgrammingError, e:
+ if e.args[0] != ER_NO_SUCH_TABLE:
+ raise
+ if args.verbose:
+ print "Creating upgrade_version table"
+ cur.execute(self.upgrade_version_table_schema)
+ cur.close()
+ if args.verbose:
+ print "Current version of", name, "is", self.version
+
+ @property
+ def version(self):
+ if self.enabled:
+ cur = self.db.cursor()
+ cur.execute("SELECT version FROM upgrade_version")
+ v = self.cur.fetchone()
+ cur.close()
+ return Version(None if v is None else v[0])
+ else:
+ return current_version
+
+ @version.setter
+ def version(self, v):
+ if self.enabled and v > self.version:
+ cur = self.db.cursor()
+ cur.execute("DELETE FROM upgrade_version")
+ cur.execute("INSERT upgrade_version (version, updated) VALUES (%s, %s)", (v, sundial.datetime.now()))
+ cur.close()
+ self.db.commit()
+ if args.verbose:
+ print "Updated", self.name, "to", v
+
+ def close(self):
+ self.db.close()
+
+class Upgrade(object):
+ """
+ One upgrade script. Really, just its filename and the Version
+ object we parse from its filename, we don't need to read the script
+ itself except when applying it, but we do need to sort all the
+ available upgrade scripts into version order.
+ """
+
+ glob = os.path.join(rpki.autoconf.datarootdir,
+ "rpki", "upgrade-scripts",
+ "upgrade-to-*.py")
+
+ @classmethod
+ def load_all(cls):
+ for fn in glob.iglob(cls.glob):
+ yield cls(fn)
+
+ def __init__(self, fn):
+ head, sep, tail = self.glob.partition("*")
+ self.fn = fn
+ self.version = Version(fn[len(head):-len(tail)])
+
+ def __cmp__(self, other):
+ return cmp(self.version, other.version)
+
+ def apply(self):
+ if args.verbose:
+ print "Applying", fn
+ with open(self.fn, "r") as f:
+ exec f
+
+os.environ["TZ"] = "UTC"
+time.tzset()
+
+parser = argparse.ArgumentParser(description = __doc__)
+parser.add_argument("-c", "--config",
+ help = "override default location of configuration file")
+parser.add_argument("-v", "--verbose",
+ action = "store_true",
+ help = "natter about whatever we're doing")
+args = parser.parse_args()
+
+cfg = rpki.config.parser(args.config, "myrpki")
+
+rpkid_db = Database("rpkid")
+pubd_db = Database("pubd")
+
+for upgrade in sorted(Upgrade.load_all()):
+ if upgrade.version > rpkid_db.version upgrade.version > pubd_db.version:
+ upgrade.apply()
+ rpkid_db.version = upgrade.version
+ pubd_db.version = upgrade.version
+
+rpkid_db.close()
+pubd_db.close()
+