|
@@ -15,23 +15,24 @@
|
|
|
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
|
|
|
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
|
-# @file Dabase Utilities
|
|
|
-#
|
|
|
-# This file holds the "dbutil" program, a general utility program for doing
|
|
|
-# management of the BIND 10 database. There are two modes of operation:
|
|
|
-#
|
|
|
-# b10-dbutil --check [database]
|
|
|
-# b10-dbutil --upgrade [--noconfirm] [database]
|
|
|
-#
|
|
|
-# The first form checks the version of the given database. The second form
|
|
|
-# upgrades the database to the latest version of the schema, omitting the
|
|
|
-# warning prompt if --noconfirm is given. In both cases, if the databas
|
|
|
-# file is not given on the command line, the default database will be accessed.
|
|
|
-#
|
|
|
-# For maximum safety, prior to the upgrade a backup database is created.
|
|
|
-# The is the database name with ".backup" appended to it (or ".backup-n" if
|
|
|
-# ".backup" already exists). This is used to restore the database if the
|
|
|
-# upgrade fails.
|
|
|
+"""
|
|
|
+@file Dabase Utilities
|
|
|
+
|
|
|
+This file holds the "dbutil" program, a general utility program for doing
|
|
|
+management of the BIND 10 database. There are two modes of operation:
|
|
|
+
|
|
|
+ b10-dbutil --check [--verbose] database
|
|
|
+ b10-dbutil --upgrade [--noconfirm] [--verbose] database
|
|
|
+
|
|
|
+The first form checks the version of the given database. The second form
|
|
|
+upgrades the database to the latest version of the schema, omitting the
|
|
|
+warning prompt if --noconfirm is given.
|
|
|
+
|
|
|
+For maximum safety, prior to the upgrade a backup database is created.
|
|
|
+The is the database name with ".backup" appended to it (or ".backup-n" if
|
|
|
+".backup" already exists). This is used to restore the database if the
|
|
|
+upgrade fails.
|
|
|
+"""
|
|
|
|
|
|
import sys; sys.path.append("@@PYTHONPATH@@")
|
|
|
import os, sqlite3, shutil
|
|
@@ -40,17 +41,13 @@ import isc.util.process
|
|
|
|
|
|
isc.util.process.rename()
|
|
|
|
|
|
-# Default database to use if the database is not given on the command line.
|
|
|
-# (This is the same string as in "auth.spec.pre.in".)
|
|
|
-DEFAULT_DATABASE_FILE = "@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3"
|
|
|
-
|
|
|
+# @brief Version String
|
|
|
# This is the version displayed to the user. It comprises the module name,
|
|
|
# the module version number, and the overall BIND 10 version number (set in
|
|
|
# configure.ac)
|
|
|
VERSION = "b10-dbutil 20120319 (BIND 10 @PACKAGE_VERSION@)"
|
|
|
|
|
|
-# Statements to update the database.
|
|
|
-#
|
|
|
+# @brief Statements to Update the Database
|
|
|
# These are in the form of a list of dictionaries, each of which contains the
|
|
|
# information to perform an incremental upgrade from one version of the
|
|
|
# database to the next. The information is:
|
|
@@ -70,8 +67,7 @@ VERSION = "b10-dbutil 20120319 (BIND 10 @PACKAGE_VERSION@)"
|
|
|
# Note that apart from the 1.0 to 2.0 upgrade, no upgrade need alter the
|
|
|
# schema_version table: that is done by the upgrade process using the
|
|
|
# information in the "to" field.
|
|
|
-
|
|
|
-upgrades = [
|
|
|
+UPGRADES = [
|
|
|
{'from': (1, 0), 'to': (2, 0),
|
|
|
'statements': [
|
|
|
|
|
@@ -179,8 +175,10 @@ upgrades = [
|
|
|
# program will be able to upgrade both a V1.0 and a V2.0 database.
|
|
|
]
|
|
|
|
|
|
-# Exception class to indicate error exit
|
|
|
class DbutilException(Exception):
|
|
|
+ """
|
|
|
+ @brief Exception class to indicate error exit
|
|
|
+ """
|
|
|
pass
|
|
|
|
|
|
# Functions for outputting messages in a consistent format. As this is intended
|
|
@@ -232,11 +230,13 @@ def info(text, ex = None):
|
|
|
output(sys.stdout.write, "INFO", text, ex)
|
|
|
|
|
|
|
|
|
-# @brief Database Encapsulation
|
|
|
-#
|
|
|
-# Encapsulates the SQL database, both the connection and the cursor. The
|
|
|
-# methods will cause a program exit on any error.
|
|
|
class Database:
|
|
|
+ """
|
|
|
+ @brief Database Encapsulation
|
|
|
+
|
|
|
+ Encapsulates the SQL database, both the connection and the cursor. The
|
|
|
+ methods will cause a program exit on any error.
|
|
|
+ """
|
|
|
def __init__(self, db_file, verbose = False):
|
|
|
"""
|
|
|
@brief Constructor
|
|
@@ -376,13 +376,37 @@ def version_string(version):
|
|
|
return "V" + str(version[0]) + "." + str(version[1])
|
|
|
|
|
|
|
|
|
+def compare_versions(first, second):
|
|
|
+ """
|
|
|
+ @brief Compare Versions
|
|
|
+
|
|
|
+ Compares two database version numbers.
|
|
|
+
|
|
|
+ @param first First version number to check (in the form of a
|
|
|
+ "(major, minor)" tuple).
|
|
|
+ @param second Second version number to check (in the form of a
|
|
|
+ "(major, minor)" tuple).
|
|
|
+
|
|
|
+ @return -1, 0, +1 if "first" is <, ==, > "second"
|
|
|
+ """
|
|
|
+ if first == second:
|
|
|
+ return 0
|
|
|
+
|
|
|
+ elif ((first[0] < second[0]) or
|
|
|
+ ((first[0] == second[0]) and (first[1] < second[1]))):
|
|
|
+ return -1
|
|
|
+
|
|
|
+ else:
|
|
|
+ return 1
|
|
|
+
|
|
|
+
|
|
|
def get_latest_version():
|
|
|
"""
|
|
|
- @brief Returns the latest version of the database
|
|
|
+ @brief Returns the version to which this utility can upgrade the database
|
|
|
|
|
|
This is the 'to' version held in the last element of the upgrades list
|
|
|
"""
|
|
|
- return upgrades[-1]['to']
|
|
|
+ return UPGRADES[-1]['to']
|
|
|
|
|
|
|
|
|
def get_version(db):
|
|
@@ -392,19 +416,12 @@ def get_version(db):
|
|
|
@return Version of database in form (major version, minor version)
|
|
|
"""
|
|
|
|
|
|
- # Check only one row of data in the version table.
|
|
|
- db.execute("SELECT COUNT(*) FROM schema_version")
|
|
|
- result = db.result()
|
|
|
- if result[0] == 0:
|
|
|
- raise DbutilException("unable to determine database version - " +
|
|
|
- "nothing in schema_version table")
|
|
|
- elif result[0] > 1:
|
|
|
- raise DbutilException("unable to determine database version - " +
|
|
|
- "too many rows in schema_version table")
|
|
|
-
|
|
|
# Get the version information.
|
|
|
db.execute("SELECT * FROM schema_version")
|
|
|
result = db.result()
|
|
|
+ if result is None:
|
|
|
+ raise DbutilException("nothing in schema_version table")
|
|
|
+
|
|
|
major = result[0]
|
|
|
if (major == 1):
|
|
|
# If the version number is 1, there will be no "minor" column, so
|
|
@@ -413,23 +430,41 @@ def get_version(db):
|
|
|
else:
|
|
|
minor = result[1]
|
|
|
|
|
|
+ result = db.result()
|
|
|
+ if result is not None:
|
|
|
+ raise DbutilException("too many rows in schema_version table")
|
|
|
+
|
|
|
return (major, minor)
|
|
|
|
|
|
|
|
|
-def match_version(db, expected):
|
|
|
+def check_version(db):
|
|
|
"""
|
|
|
- @brief Check database version against that expected
|
|
|
-
|
|
|
- Checks whether the version of the database matches that expected for
|
|
|
- the upgrade. Both the major and minor versions must match.
|
|
|
+ @brief Check the version
|
|
|
|
|
|
- @param db Database
|
|
|
- @param expected Expected version of the database in form (major, minor)
|
|
|
+ Checks the version of the database and the latest version, and advises if
|
|
|
+ an upgrade is needed.
|
|
|
|
|
|
- @return True if the versions match, false if they don't.
|
|
|
+ @param db Database object
|
|
|
"""
|
|
|
current = get_version(db)
|
|
|
- return expected == current
|
|
|
+ latest = get_latest_version()
|
|
|
+
|
|
|
+ match = compare_versions(current, latest)
|
|
|
+ if match == 0:
|
|
|
+ info("database version " + version_string(current))
|
|
|
+ info("this is the latest version of the database schema, " +
|
|
|
+ "no upgrade is required")
|
|
|
+
|
|
|
+ elif match < 0:
|
|
|
+ info("database version " + version_string(current) +
|
|
|
+ ", latest version is " + version_string(latest))
|
|
|
+ info("re-run this program with the --upgrade switch to upgrade")
|
|
|
+
|
|
|
+ else:
|
|
|
+ warn("database is at a later version (" + version_string(current) +
|
|
|
+ ") than this program can cope with (" +
|
|
|
+ version_string(get_latest_version()) + ")")
|
|
|
+ info("please get the latest version of b10-dbutil and re-run")
|
|
|
|
|
|
|
|
|
def perform_upgrade(db, upgrade):
|
|
@@ -464,14 +499,18 @@ def perform_all_upgrades(db):
|
|
|
For each upgrade, checks that the database is at the expected version.
|
|
|
If so, calls perform_upgrade to update the database.
|
|
|
"""
|
|
|
- if match_version(db, get_latest_version()):
|
|
|
+ match = compare_versions(get_version(db), get_latest_version())
|
|
|
+ if match == 0:
|
|
|
info("database already at latest version, no upgrade necessary")
|
|
|
|
|
|
+ elif match > 0:
|
|
|
+ warn("database at a later version than this utility can support")
|
|
|
+
|
|
|
else:
|
|
|
# Work our way through all upgrade increments
|
|
|
count = 0
|
|
|
- for upgrade in upgrades:
|
|
|
- if match_version(db, upgrade['from']):
|
|
|
+ for upgrade in UPGRADES:
|
|
|
+ if compare_versions(get_version(db), upgrade['from']) == 0:
|
|
|
perform_upgrade(db, upgrade)
|
|
|
count = count + 1
|
|
|
|
|
@@ -480,33 +519,9 @@ def perform_all_upgrades(db):
|
|
|
else:
|
|
|
# Should not get here, as we established earlier that the database
|
|
|
# was not at the latest version so we should have upgraded.
|
|
|
- # (Although it is possible that as version checks are for equality,
|
|
|
- # an older version of dbutil was being run against a newer version
|
|
|
- # of the database.)
|
|
|
- raise DbutilException("database not at latest version but no " +
|
|
|
- "upgrade was performed")
|
|
|
-
|
|
|
-
|
|
|
-def check_version(db):
|
|
|
- """
|
|
|
- @brief Check the version
|
|
|
-
|
|
|
- Checks the version of the database and the latest version, and advises if
|
|
|
- an upgrade is needed.
|
|
|
-
|
|
|
- @param db Database object
|
|
|
- """
|
|
|
- current = get_version(db);
|
|
|
- latest = get_latest_version()
|
|
|
-
|
|
|
- if current == latest:
|
|
|
- info("database version " + version_string(current))
|
|
|
- info("this is the latest version of the database schema, " +
|
|
|
- "no upgrade is required")
|
|
|
- else:
|
|
|
- info("database version " + version_string(current) +
|
|
|
- ", latest version is " + version_string(latest))
|
|
|
- info("re-run this program with the --upgrade switch to upgrade")
|
|
|
+ raise DbutilException("internal error in upgrade tool - no " +
|
|
|
+ "upgrade was performed on an old version " +
|
|
|
+ "the database")
|
|
|
|
|
|
|
|
|
def parse_command():
|
|
@@ -517,8 +532,8 @@ def parse_command():
|
|
|
|
|
|
@return Tuple of parser options and parser arguments
|
|
|
"""
|
|
|
- usage = ("usage: %prog --check [options] [db_file]\n" +
|
|
|
- " %prog --upgrade [--noconfirm] [options] [db_file]")
|
|
|
+ usage = ("usage: %prog --check [options] db_file\n" +
|
|
|
+ " %prog --upgrade [--noconfirm] [options] db_file")
|
|
|
parser = OptionParser(usage = usage, version = VERSION)
|
|
|
parser.add_option("-c", "--check", action="store_true",
|
|
|
dest="check", default=False,
|
|
@@ -541,7 +556,9 @@ def parse_command():
|
|
|
parser.print_usage()
|
|
|
sys.exit(1)
|
|
|
elif len(args) == 0:
|
|
|
- args.append(DEFAULT_DATABASE_FILE)
|
|
|
+ error("must supply name of the database file to upgrade")
|
|
|
+ parser.print_usage()
|
|
|
+ sys.exit(1)
|
|
|
|
|
|
# Check for conflicting options. If some are found, output a suitable
|
|
|
# error message and print the usage.
|
|
@@ -600,3 +617,5 @@ if __name__ == "__main__":
|
|
|
else:
|
|
|
error("internal error, neither --check nor --upgrade selected")
|
|
|
sys.exit(1)
|
|
|
+
|
|
|
+ sys.exit(0)
|