From 2e06048a76a016e90baef2d7a6b218457f97e3a1 Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Mon, 19 Mar 2012 15:09:38 +0000 Subject: [PATCH 01/14] [963] Dabase utility program and tests --- configure.ac | 9 +- src/bin/dbutil/Makefile.am | 14 + src/bin/dbutil/dbutil.py.in | 561 ++++++++++++++++++ src/bin/dbutil/tests/Makefile.am | 6 + src/bin/dbutil/tests/dbutil_test.sh.in | 409 +++++++++++++ src/bin/dbutil/tests/testdata/README | 35 ++ .../tests/testdata/empty_schema.sqlite3 | Bin 0 -> 215040 bytes .../dbutil/tests/testdata/empty_v1.sqlite3 | Bin 0 -> 215040 bytes .../dbutil/tests/testdata/invalid_v1.sqlite3 | Bin 0 -> 215040 bytes src/bin/dbutil/tests/testdata/new_v1.sqlite3 | Bin 0 -> 215040 bytes .../dbutil/tests/testdata/no_schema.sqlite3 | Bin 0 -> 2048 bytes src/bin/dbutil/tests/testdata/old_v1.sqlite3 | Bin 0 -> 215040 bytes src/bin/dbutil/tests/testdata/v2_0.sqlite3 | Bin 0 -> 13312 bytes 13 files changed, 1032 insertions(+), 2 deletions(-) create mode 100644 src/bin/dbutil/Makefile.am create mode 100755 src/bin/dbutil/dbutil.py.in create mode 100644 src/bin/dbutil/tests/Makefile.am create mode 100755 src/bin/dbutil/tests/dbutil_test.sh.in create mode 100644 src/bin/dbutil/tests/testdata/README create mode 100644 src/bin/dbutil/tests/testdata/empty_schema.sqlite3 create mode 100644 src/bin/dbutil/tests/testdata/empty_v1.sqlite3 create mode 100644 src/bin/dbutil/tests/testdata/invalid_v1.sqlite3 create mode 100644 src/bin/dbutil/tests/testdata/new_v1.sqlite3 create mode 100644 src/bin/dbutil/tests/testdata/no_schema.sqlite3 create mode 100644 src/bin/dbutil/tests/testdata/old_v1.sqlite3 create mode 100644 src/bin/dbutil/tests/testdata/v2_0.sqlite3 diff --git a/configure.ac b/configure.ac index 5ce897df05..93667b1e9d 100644 --- a/configure.ac +++ b/configure.ac @@ -994,6 +994,8 @@ AC_CONFIG_FILES([Makefile src/bin/cfgmgr/plugins/Makefile src/bin/cfgmgr/plugins/tests/Makefile src/bin/cfgmgr/tests/Makefile + src/bin/dbutil/Makefile + src/bin/dbutil/tests/Makefile src/bin/host/Makefile src/bin/loadzone/Makefile src/bin/loadzone/tests/correct/Makefile @@ -1007,8 +1009,8 @@ AC_CONFIG_FILES([Makefile src/bin/ddns/tests/Makefile src/bin/dhcp6/Makefile src/bin/dhcp6/tests/Makefile - src/bin/dhcp4/Makefile - src/bin/dhcp4/tests/Makefile + src/bin/dhcp4/Makefile + src/bin/dhcp4/tests/Makefile src/bin/resolver/Makefile src/bin/resolver/tests/Makefile src/bin/sockcreator/Makefile @@ -1122,6 +1124,8 @@ AC_OUTPUT([doc/version.ent src/bin/cmdctl/run_b10-cmdctl.sh src/bin/cmdctl/tests/cmdctl_test src/bin/cmdctl/cmdctl.spec.pre + src/bin/dbutil/dbutil.py + src/bin/dbutil/tests/dbutil_test.sh src/bin/ddns/ddns.py src/bin/xfrin/tests/xfrin_test src/bin/xfrin/xfrin.py @@ -1205,6 +1209,7 @@ AC_OUTPUT([doc/version.ent chmod +x src/bin/zonemgr/run_b10-zonemgr.sh chmod +x src/bin/bind10/run_bind10.sh chmod +x src/bin/cmdctl/tests/cmdctl_test + chmod +x src/bin/dbutil/tests/dbutil_test.sh chmod +x src/bin/xfrin/tests/xfrin_test chmod +x src/bin/xfrout/tests/xfrout_test chmod +x src/bin/zonemgr/tests/zonemgr_test diff --git a/src/bin/dbutil/Makefile.am b/src/bin/dbutil/Makefile.am new file mode 100644 index 0000000000..5e7a942e79 --- /dev/null +++ b/src/bin/dbutil/Makefile.am @@ -0,0 +1,14 @@ +SUBDIRS = . tests + +bin_SCRIPTS = b10-dbutil + +CLEANFILES = b10-dbutil b10-dbutil.pyc + +b10-dbutil: dbutil.py + $(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" dbutil.py > $@ + chmod a+x $@ + +CLEANDIRS = __pycache__ + +clean-local: + rm -rf $(CLEANDIRS) diff --git a/src/bin/dbutil/dbutil.py.in b/src/bin/dbutil/dbutil.py.in new file mode 100755 index 0000000000..46cd0165fd --- /dev/null +++ b/src/bin/dbutil/dbutil.py.in @@ -0,0 +1,561 @@ +#!@PYTHON@ + +# Copyright (C) 2012 Internet Systems Consortium. +# +# 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 INTERNET SYSTEMS CONSORTIUM +# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL +# INTERNET SYSTEMS CONSORTIUM 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. + +# @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. + +import os, sqlite3, shutil, sys +from optparse import OptionParser + +# 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" + +# 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: +# +# a) from: (major, minor) version that the database is expected to be at +# to perform this upgrade. +# b) to: (major, minor) version of the database to which this set of statements +# upgrades the database to. (This is used for documentation purposes, +# and to update the schema_version table when the upgrade is complete.) +# c) statements: List of SQL statments to perform the upgrade. +# +# The incremental upgrades are performed one after the other. If the version +# of the database does not exactly match that required for the incremental +# upgrade, the upgrade is skipped. For this reason, the list must be in +# ascending order (e.g. upgrade 1.0 to 2.0, 2.0 to 2.1, 2.1 to 2.2 etc.). +# +# 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 = [ + {'from': (1, 0), 'to': (2, 0), + 'statements': [ + + # Move to the latest "V1" state of the database if not there + # already. + "CREATE TABLE IF NOT EXISTS diffs (" + + "id INTEGER PRIMARY KEY, " + + "zone_id INTEGER NOT NULL," + + "version INTEGER NOT NULL, " + + "operation INTEGER NOT NULL, " + + "name STRING NOT NULL COLLATE NOCASE, " + + "rrtype STRING NOT NULL COLLATE NOCASE, " + + "ttl INTEGER NOT NULL, " + + "rdata STRING NOT NULL)", + + # Within SQLite with can only rename tables and add columns; we + # can't drop columns nor can we alter column characteristics. + # So the strategy is to rename the table, create the new table, + # then copy all data across. This means creating new indexes + # as well; these are created after the data has been copied. + + # zones table + "DROP INDEX zones_byname", + "ALTER TABLE zones RENAME TO old_zones", + "CREATE TABLE zones (" + + "id INTEGER PRIMARY KEY, " + + "name TEXT NOT NULL COLLATE NOCASE, " + + "rdclass TEXT NOT NULL COLLATE NOCASE DEFAULT 'IN', " + + "dnssec BOOLEAN NOT NULL DEFAULT 0)", + "INSERT INTO ZONES " + + "SELECT id, name, rdclass, dnssec FROM old_zones", + "CREATE INDEX zones_byname ON zones (name)", + "DROP TABLE old_zones", + + # records table + "DROP INDEX records_byname", + "DROP INDEX records_byrname", + "ALTER TABLE records RENAME TO old_records", + "CREATE TABLE records (" + + "id INTEGER PRIMARY KEY, " + + "zone_id INTEGER NOT NULL, " + + "name TEXT NOT NULL COLLATE NOCASE, " + + "rname TEXT NOT NULL COLLATE NOCASE, " + + "ttl INTEGER NOT NULL, " + + "rdtype TEXT NOT NULL COLLATE NOCASE, " + + "sigtype TEXT COLLATE NOCASE, " + + "rdata TEXT NOT NULL)", + "INSERT INTO records " + + "SELECT id, zone_id, name, rname, ttl, rdtype, sigtype, " + + "rdata FROM old_records", + "CREATE INDEX records_byname ON records (name)", + "CREATE INDEX records_byrname ON records (rname)", + "CREATE INDEX records_bytype_and_rname ON records (rdtype, rname)", + "DROP TABLE old_records", + + # nsec3 table + "DROP INDEX nsec3_byhash", + "ALTER TABLE nsec3 RENAME TO old_nsec3", + "CREATE TABLE nsec3 (" + + "id INTEGER PRIMARY KEY, " + + "zone_id INTEGER NOT NULL, " + + "hash TEXT NOT NULL COLLATE NOCASE, " + + "owner TEXT NOT NULL COLLATE NOCASE, " + + "ttl INTEGER NOT NULL, " + + "rdtype TEXT NOT NULL COLLATE NOCASE, " + + "rdata TEXT NOT NULL)", + "INSERT INTO nsec3 " + + "SELECT id, zone_id, hash, owner, ttl, rdtype, rdata " + + "FROM old_nsec3", + "CREATE INDEX nsec3_byhash ON nsec3 (hash)", + "DROP TABLE old_nsec3", + + # diffs table + "ALTER TABLE diffs RENAME TO old_diffs", + "CREATE TABLE diffs (" + + "id INTEGER PRIMARY KEY, " + + "zone_id INTEGER NOT NULL, " + + "version INTEGER NOT NULL, " + + "operation INTEGER NOT NULL, " + + "name TEXT NOT NULL COLLATE NOCASE, " + + "rrtype TEXT NOT NULL COLLATE NOCASE, " + + "ttl INTEGER NOT NULL, " + + "rdata TEXT NOT NULL)", + "INSERT INTO diffs " + + "SELECT id, zone_id, version, operation, name, rrtype, " + + "ttl, rdata FROM old_diffs", + "DROP TABLE old_diffs", + + # Schema table. This is updated to include a second column for + # future changes. The idea is that if a version of BIND 10 is + # written for schema M.N, it should be able to work for all + # versions of N; if not, M must be incremented. + # + # For backwards compatibility, the column holding the major + # version number is left named "version". + "ALTER TABLE schema_version " + + "ADD COLUMN minor INTEGER NOT NULL DEFAULT 0" + ] + } + +# To extend this, leave the above statements in place and add another +# dictionary to the list. The "from" version should be (2, 0), the "to" +# version whatever the version the update is to, and the SQL statements are +# the statements required to perform the upgrade. This way, the upgrade +# program will be able to upgrade both a V1.0 and a V2.0 database. +] + +# Exception class to indicate error exit +class DbutilException(Exception): + pass + + +def info(text): + """ + @brief Write informational message to stdout. + """ + sys.stdout.write("INFO: " + text + "\n") + + +# @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: + def __init__(self, db_file, verbose = False): + """ + @brief Constructor + + @param db_file Name of the database file + @param verbose If True, print all SQL statements to stdout before + executing them. + """ + self.connection = None + self.cursor = None + self.db_file = db_file + self.backup_file = None + self.verbose = verbose + + def open(self): + """ + @brief Open Database + + Opens the passed file as an sqlite3 database and stores a connection + and a cursor. + """ + if not os.path.exists(self.db_file): + raise DbutilException("database " + self.db_file + + " does not exist"); + + try: + self.connection = sqlite3.connect(self.db_file) + self.connection.isolation_level = None # set autocommit + self.cursor = self.connection.cursor() + except sqlite3.OperationalError as ex: + raise DbutilException("unable to open " + self.db_file + + " - " + str(ex)) + + def close(self): + """ + @brief Closes the database + """ + if self.connection is not None: + self.connection.close() + + def execute(self, statement, what = None): + """ + @brief Execute Statement + + Executes the given statement, exiting the program on error. If + verbose mode is set, the statement is printed to stdout before + execution. + + @param statement SQL statement to execute + @param what Reason for the action (used in the error message if the + action fails) + """ + if self.verbose: + sys.stdout.write(statement + "\n") + + try: + self.cursor.execute(statement) + except Exception as ex: + if (what is None): + raise DbutilException("SQL Error - " + str(ex)) + else: + raise DbutilException("failed to " + what + " - " + str(ex)) + + def result(self): + """ + @brief Return result of last execute + + Returns a single row that is the result of the last "execute". + """ + return self.cursor.fetchone() + + def backup(self): + """ + @brief Backup Database + + Attempts to copy the given database file to a backup database, the + backup database file being the file name with ".backup" appended. + If the ".backup" file exists, a new name is constructed by appending + ".backup-n" (n starting at 1) and the action repeated until an + unused filename is found. + + @param db_file Database file to backup + """ + if not os.path.exists(self.db_file): + raise DbutilException("database " + self.db_file + + " does not exist"); + + self.backup_file = self.db_file + ".backup" + count = 0 + while os.path.exists(self.backup_file): + count = count + 1 + self.backup_file = self.db_file + ".backup-" + str(count) + + # Do the backup + shutil.copyfile(self.db_file, self.backup_file) + info("database " + self.db_file + " backed up to " + self.backup_file) + +def prompt_user(): + """ + @brief Prompt the User + + Explains about the upgrade and requests authorisation to continue. + + @return True if user entered 'Yes', False if 'No' + """ + sys.stdout.write( +"""You have selected the upgrade option. This will upgrade the schema of the +selected BIND 10 database to the latest version. + +The utility will take a copy of the database file before running so, in the +unlikely event of a problem, you will be able to restore the database from +the backup. To ensure that the integrity of this backup, please ensure that +BIND 10 is not running before proceeding. +""") + yes_entered = False + no_entered = False + while (not yes_entered) and (not no_entered): + sys.stdout.write("Enter 'Yes' to proceed with the upgrade, " + + "'No' to exit the program: ") + response = sys.stdin.readline() + if response.lower() == "yes\n": + yes_entered = True + elif response.lower() == "no\n": + no_entered = True + else: + sys.stdout.write("Please enter 'Yes' or 'No'\n") + + return yes_entered + + +def version_string(version): + """ + @brief Format Database Version + + Converts a (major, minor) tuple into a 'Vn.m' string. + + @param version Version tuple to convert + + @return Version string + """ + return "V" + str(version[0]) + "." + str(version[1]) + + +def get_latest_version(): + """ + @brief Returns the latest version of the database + + This is the 'to' version held in the last element of the upgrades list + """ + return upgrades[-1]['to'] + + +def get_version(db): + """ + @brief Return version of database + + @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", "get database 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", "get database version") + result = db.result() + major = result[0] + if (major == 1): + # If the version number is 1, there will be no "minor" column, so + # assume a minor version number of 0. + minor = 0 + else: + minor = result[1] + + return (major, minor) + + +def match_version(db, expected): + """ + @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. + + @param db Database + @param expected Expected version of the database in form (major, minor) + + @return True if the versions match, false if they don't. + """ + current = get_version(db) + return expected == current + + +def perform_upgrade(db, upgrade): + """ + @brief Perform upgrade + + Performs the upgrade. At the end of the upgrade, updates the schema_version + table with the expected version. + + @param db Database object + @param upgrade Upgrade dictionary, holding "from", "to" and "statements". + """ + increment = (version_string(upgrade['from']) + " to " + + version_string(upgrade['to'])) + action = "upgrading database from " + increment + info(action) + for statement in upgrade['statements']: + db.execute(statement, "upgrade database from " + increment) + + # Update the version information + db.execute("DELETE FROM schema_version", "update version information") + db.execute("INSERT INTO schema_version VALUES (" + + str(upgrade['to'][0]) + "," + str(upgrade['to'][1]) + ")", + "update version information") + + +def perform_all_upgrades(db): + """ + @brief Performs all the upgrades + + @brief db Database object + + 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()): + info("database already at latest version, no upgrade necessary") + + else: + # Work our way through all upgrade increments + count = 0 + for upgrade in upgrades: + if match_version(db, upgrade['from']): + perform_upgrade(db, upgrade) + count = count + 1 + + if count > 0: + info("database upgrade successfully completed") + 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") + + +def parse_command(): + """ + @brief Parse Command + + Parses the command line and sets the global command options. + + @return Tuple of parser options and parser arguments + """ + usage = ("usage: %prog --check [options] [db_file]\n" + + " %prog --upgrade [--noconfirm] [options] [db_file]") + parser = OptionParser(usage=usage) + parser.add_option("-c", "--check", action="store_true", + dest="check", default=False, + help="Print database version and check if it " + + "needs upgrading") + parser.add_option("-n", "--noconfirm", action="store_true", + dest="noconfirm", default=False, + help="Do not prompt for confirmation before upgrading") + parser.add_option("-u", "--upgrade", action="store_true", + dest="upgrade", default=False, + help="Upgrade the database file to the latest version") + parser.add_option("-v", "--verbose", action="store_true", + dest="verbose", default=False, + help="Print SQL statements as they are executed") + (options, args) = parser.parse_args() + + # Set the database file on which to operate + if (len(args) > 1): + sys.stderr.write(usage + "\n") + sys.exit(1) + elif len(args) == 0: + args.append(default_database_file) + + # Check for conflicting options. If some are found, output a suitable + # error message and print the usage. + if options.check and options.upgrade: + sys.stderr.write("cannot select both --check and --upgrade, " + + "please choose one") + elif (not options.check) and (not options.upgrade): + sys.stderr.write("must select one of --check or --upgrade") + elif (options.check and options.noconfirm): + sys.stderr.write("--noconfirm is not compatible with --check") + else: + return (options, args) + + # Only get here on conflicting options + parser.print_usage() + sys.exit(1) + + +if __name__ == "__main__": + (options, args) = parse_command() + db = Database(args[0], options.verbose) + + if options.check: + # Check database - open, report, and close + try: + db.open() + check_version(db) + db.close() + except Exception as ex: + sys.stderr.write("ERROR: unable to check database version - " + + str(ex) + "\n") + sys.exit(1) + + elif options.upgrade: + # Upgrade. Check if this is what they really want to do + if not options.noconfirm: + proceed = prompt_user() + if not proceed: + info("upgrade abandoned - database has not been changed\n") + sys.exit(0) + + # It is. Do a backup then do the upgrade. + in_progress = False + try: + db.backup() + db.open() + in_progress = True + perform_all_upgrades(db) + db.close() + except Exception as ex: + if in_progress: + sys.stderr.write("ERROR: upgrade failed - " + str(ex) + "\n") + sys.stderr.write("WARN: database may be corrupt, " + + "restore database from backup\n") + else: + sys.stderr.write("ERROR: upgrade preparation failed - " + + str(ex) + "\n") + sys.stderr.write("INFO: database upgrade was not attempted\n") + sys.exit(1) + else: + sys.stderr.write("ERROR: internal error, neither --check nor " + + " --upgrade selected") + sys.exit(1) diff --git a/src/bin/dbutil/tests/Makefile.am b/src/bin/dbutil/tests/Makefile.am new file mode 100644 index 0000000000..7e17e09ca8 --- /dev/null +++ b/src/bin/dbutil/tests/Makefile.am @@ -0,0 +1,6 @@ +SUBDIRS = . + +# Tests of the update script. + +check-local: + $(SHELL) $(abs_builddir)/dbutil_test.sh diff --git a/src/bin/dbutil/tests/dbutil_test.sh.in b/src/bin/dbutil/tests/dbutil_test.sh.in new file mode 100755 index 0000000000..e30e2b7dba --- /dev/null +++ b/src/bin/dbutil/tests/dbutil_test.sh.in @@ -0,0 +1,409 @@ +#!/bin/sh +# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC 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. + +# Checks that the logger will limit the output of messages less severe than +# the severity/debug setting. + +testname="Database Upgrade Test" +echo $testname + +failcount=0 +tempfile=@abs_builddir@/dbutil_test_tempfile_$$ +backupfile=${tempfile}.backup +testdata=@abs_srcdir@/testdata +verfile=@abs_builddir@/dbutil_test_verfile_$$ + +# @brief Record a success +succeed() { + echo "--- PASS" +} + + +# @brief Record a fail +# +# @param $1 Optional additional reason to output +fail() { + if [ "x$1" != "x" ] + then + echo "ERROR: $1" + fi + echo "*** FAIL" + failcount=`expr $failcount + 1` +} + + +# @brief Record a pass if the argument is zero +# +# @param $1 Value to test +passzero() { + if [ $1 -eq 0 ]; then + succeed + else + fail + fi +} + + +# @brief Record a fail if the argument is non-zero +# +# @param $1 Value to test +failzero() { + if [ $1 -ne 0 ]; then + succeed + else + fail + fi +} + + +# @brief Check backup file +# +# Record a failure if the backup file does not exist or if it is different +# to the data file. (N.B. No success is recorded if they are the same.) +# +# @param $1 Source database file +# @param $2 Backup file +check_backup() { + if [ ! -e $1 ] + then + fail "database file $1 not found" + + elif [ ! -e $2 ] + then + fail "backup file $2 not found" + + else + diff $1 $2 > /dev/null + if [ $? -ne 0 ] + then + fail "database file $1 different to backup file $2" + fi + fi +} + + +# @brief Check No Backup File +# +# Record a failure if the backup file exists. (N.B. No success is recorded if +# it does not.) +# +# @param $1 Source database file (unused, present for symmetry) +# @param $2 Backup file +check_no_backup() { + if [ -e $2 ] + then + fail "backup of database $2 exists when it should not" + fi +} + + +# @brief Get Database Schema +# +# As the schema stored in the database is format-dependent - how it is printed +# depends on how the commands were entered (on one line, split across two +# lines etc.) - comparing schema is awkward. +# +# The function sets the local variable db_schema to the output of the +# .schema command, with spaces removed and upper converted to lowercase. +# +# The database is copied before the schema is taken (and removed after) +# as SQLite3 assummes a writeable database, which may not be the case if +# getting the schema from a reference copy. +# +# @param $1 Database for which the schema is required +get_schema() { + db1=@abs_builddir@/dbutil_test_schema_$$ + cp $1 $db1 + + db_schema=`sqlite3 $db1 '.schema' | \ + awk '{line = line $0} END {print line}' | \ + sed -e 's/ //g' | \ + tr [:upper:] [:lower:]` + rm -f $db1 +} + + +# @brief Successful Schema Upgrade Test +# +# This test is done where the upgrade is expected to be successful - when +# the end result of the test is that the test database is upgraded to a +# database of the expected schema. +# +# Note: the caller must ensure that $tempfile and $backupfile do not exist +# on entry, and is responsible for removing them afterwards. +# +# @param $1 Database to upgrade +upgrade_ok_test() { + cp $1 $tempfile + ../b10-dbutil --upgrade --noconfirm $tempfile + if [ $? -eq 0 ] + then + # Compare schema with the reference + get_schema $testdata/v2_0.sqlite3 + expected_schema=$db_schema + get_schema $tempfile + actual_schema=$db_schema + if [ x$expected_schema = x$actual_schema ] + then + succeed + else + fail "upgraded schema not as expected" + fi + + # and check the version is set correctly + check_version $tempfile "V2.0" + else + # Error should have been output already + fail + fi +} + + +# @brief Record Count Test +# +# Checks that the count of records in each table is preserved in the upgrade. +# +# Note 1: This test assumes that the "diffs" table is present. +# Note 2: The caller must ensure that $tempfile and $backupfile do not exist +# on entry, and is responsible for removing them afterwards. +# +# @brief $1 Database to upgrade +record_count_test() { + cp $1 $tempfile + + diffs_count=`sqlite3 $tempfile 'select count(*) from diffs'` + nsec3_count=`sqlite3 $tempfile 'select count(*) from nsec3'` + records_count=`sqlite3 $tempfile 'select count(*) from records'` + zones_count=`sqlite3 $tempfile 'select count(*) from zones'` + + ../b10-dbutil --upgrade --noconfirm $tempfile + if [ $? -ne 0 ] + then + # Reason for failure should already have been output + fail + else + new_diffs_count=`sqlite3 $tempfile 'select count(*) from diffs'` + new_nsec3_count=`sqlite3 $tempfile 'select count(*) from nsec3'` + new_records_count=`sqlite3 $tempfile 'select count(*) from records'` + new_zones_count=`sqlite3 $tempfile 'select count(*) from zones'` + + if [ $diffs_count -ne $new_diffs_count ] + then + fail "diffs table was not completely copied" + fi + + if [ $nsec3_count -ne $new_nsec3_count ] + then + fail "nsec3 table was not completely copied" + fi + + if [ $records_count -ne $new_records_count ] + then + fail "records table was not completely copied" + fi + + if [ $zones_count -ne $new_zones_count ] + then + fail "zones table was not completely copied" + fi + + # As an extra check, test that the backup was successful + check_backup $1 $backupfile + fi +} + + +# @brief Version Check +# +# Checks that the database is at the specified version (and so checks the +# --check function). On success, a pass is recorded. +# +# @param $1 Database to check +# @param $2 Expected version string +check_version() { + cp $1 $verfile + ../b10-dbutil --check $verfile + if [ $? -ne 0 ] + then + fail "version check failed on database $1" + else + ../b10-dbutil --check $verfile | grep "$2" + if [ $? -ne 0 ] + then + fail "database $1 not at expected version $2" + else + succeed + fi + fi + rm -f $verfile +} + + +# Main test sequence + +rm -f $tempfile $backupfile + +# Test 1 - check that the utility fails if the database does not exist +echo "1.1. Non-existent database - check" +../b10-dbutil --check $tempfile +failzero $? +check_no_backup $tempfile $backupfile + +echo "1.2. Non-existent database - upgrade" +../b10-dbutil --upgrade --noconfirm $tempfile +failzero $? +check_no_backup $tempfile $backupfile +rm -f $tempfile $backupfile + + +# Test 2 - should fail to check an empty file and fail to upgrade it +echo "2.1. Database is an empty file - check" +touch $tempfile +../b10-dbutil --check $tempfile +failzero $? +check_no_backup $tempfile $backupfile +rm -f $tempfile $backupfile + +echo "2.2. Database is an empty file - upgrade" +touch $tempfile +../b10-dbutil --upgrade --noconfirm $tempfile +failzero $? +# A backup is performed before anything else, so the backup should exist. +check_backup $tempfile $backupfile +rm -f $tempfile $backupfile + + +echo "3.1. Database is not an SQLite file - check" +echo "This is not an sqlite3 database" > $tempfile +../b10-dbutil --check $tempfile +failzero $? +check_no_backup $tempfile $backupfile + +echo "3.2. Database is not an SQLite file - upgrade" +../b10-dbutil --upgrade --noconfirm $tempfile +failzero $? +# ...and as before, a backup should have been created +check_backup $tempfile $backupfile +rm -f $tempfile $backupfile + + +echo "4.1. Database is an SQLite3 file without the schema table - check" +cp $testdata/no_schema.sqlite3 $tempfile +../b10-dbutil --check $tempfile +failzero $? +check_no_backup $tempfile $backupfile +rm -f $tempfile $backupfile + +echo "4.1. Database is an SQLite3 file without the schema table - upgrade" +cp $testdata/no_schema.sqlite3 $tempfile +../b10-dbutil --upgrade --noconfirm $tempfile +failzero $? +check_backup $testdata/no_schema.sqlite3 $backupfile +rm -f $tempfile $backupfile + + +echo "5.1. Database is an old V1 database - check" +check_version $testdata/old_v1.sqlite3 "V1.0" +check_no_backup $tempfile $backupfile +rm -f $tempfile $backupfile + +echo "5.2. Database is an old V1 database - upgrade" +upgrade_ok_test $testdata/old_v1.sqlite3 +check_backup $testdata/old_v1.sqlite3 $backupfile +rm -f $tempfile $backupfile + + +echo "6.1. Database is new V1 database - check" +check_version $testdata/new_v1.sqlite3 "V1.0" +check_no_backup $tempfile $backupfile +rm -f $tempfile $backupfile + +echo "6.2. Database is a new V1 database - upgrade" +upgrade_ok_test $testdata/new_v1.sqlite3 +check_backup $testdata/new_v1.sqlite3 $backupfile +rm -f $tempfile $backupfile + + +echo "7.1. Database is V2.0 database - check" +check_version $testdata/v2_0.sqlite3 "V2.0" +check_no_backup $tempfile $backupfile +rm -f $tempfile $backupfile + +echo "7.2. Database is a V2.0 database - upgrade" +upgrade_ok_test $testdata/v2_0.sqlite3 +check_backup $testdata/v2_0.sqlite3 $backupfile +rm -f $tempfile $backupfile + + +echo "8. Record count test" +record_count_test testdata/new_v1.sqlite3 +rm -f $tempfile $backupfile + + +echo "9. Backup file already exists" +touch $backupfile +touch ${backupfile}-1 +upgrade_ok_test $testdata/v2_0.sqlite3 +check_backup $testdata/v2_0.sqlite3 ${backupfile}-2 +rm -f $tempfile $backupfile ${backupfile}-1 ${backupfile}-2 + + +echo "10.1 Incompatible flags" +cp $testdata/old_v1.sqlite3 $tempfile +../b10-util --upgrade --check $tempfile +failzero $? +../b10-util --upgrade --check $tempfile +failzero $? +../b10-util --noconfirm --check $tempfile +failzero $? +rm -f $tempfile $backupfile + +echo "10.2 verbose flag" +cp $testdata/old_v1.sqlite3 $tempfile +../b10-dbutil --upgrade --noconfirm --verbose $tempfile +passzero $? +rm -f $tempfile $backupfile + +echo "10.3 Interactive prompt - yes" +cp $testdata/old_v1.sqlite3 $tempfile +../b10-dbutil --upgrade $tempfile << . +Yes +. +passzero $? +check_version $tempfile "V2.0" +rm -f $tempfile $backupfile + +echo "10.4 Interactive prompt - no" +cp $testdata/old_v1.sqlite3 $tempfile +../b10-dbutil --upgrade $tempfile << . +no +. +passzero $? +diff $testdata/old_v1.sqlite3 $tempfile > /dev/null +passzero $? +rm -f $tempfile $backupfile + + +# Report the result +if [ $failcount -eq 0 ]; then + echo "PASS: $testname" +elif [ $failcount -eq 1 ]; then + echo "FAIL: $testname - 1 test failed" +else + echo "FAIL: $testname - $failcount tests failed" +fi + +# Exit with appropriate error status +exit $failcount diff --git a/src/bin/dbutil/tests/testdata/README b/src/bin/dbutil/tests/testdata/README new file mode 100644 index 0000000000..093bcbcc3a --- /dev/null +++ b/src/bin/dbutil/tests/testdata/README @@ -0,0 +1,35 @@ +The versioning of BIND 10 databases to date has not been the best: + +The original database is known here as the "old V1" schema. It had a +schema_version table, with the single "version" value set to 1. + +The schema was then updated with a "diffs" table. This is referred to +here as the "new V1" schema. + +The Spring 2012 release of BIND 10 modified the schema. The +schema_version table was updated to include a "minor" column, holding the +minor version number. Other changes to the database included redefining +"STRING" columns as "TEXT" columns. This is referred to as the "V2.0 +schema". + +The following test data files are present: + +empty_schema.sqlite3: A database conforming to the new V1 schema. +However, there is nothing in the schema_version table. + +empty_v1.sqlite3: A database conforming to the new V1 schema. +The database is empty, except for the schema_version table, where the +"version" column is set to 1. + +no_schema.sqlite3: A valid SQLite3 database, but without a schema_version +table. + +old_v1.sqlite3: A valid SQLite3 database conforming to the old V1 schema. +It does not have a diffs table. + +invalid_v1.sqlite3: A valid SQLite3 database that, although the schema +is marked as V1, does not have the nsec3 table. + +new_v1.sqlite3: A valid SQLite3 database with data in all the tables +(although the single rows in both the nsec3 and diffs table make no +sense, but are valid). diff --git a/src/bin/dbutil/tests/testdata/empty_schema.sqlite3 b/src/bin/dbutil/tests/testdata/empty_schema.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..b8031490a90e06e14f408e8aae3b85dc91b8c5a3 GIT binary patch literal 215040 zcmeI%`FC7p0mtE)v`J_cca0Lo>w;~h2wL}*w4K&Sn9wv;>O!10(>Bs3btVD@H>gy> zec$&D_kBU!72G%e1pf_x@jI7EO>%;koWMCX&zyW_xifc`=e={^dnS8!k511ui-WVR zqm8*@Yh`_FTYA}Fx!|AY^k!*w-M>2bZMf@V z>BHP6K;XU?=+DO%(OLqoYIDU)R$=FfdR(vA2AM zYiDPg?ec8hqUS3Y6`QB0PCdhA<0F?2kMAoksqNdcu~_Cb(>U5J_DqbA)OVeFxnld+ z=xFC%*2lIF@2M@nVr%Nakw&}yKh`aF)OHT<9i1pPjnp?S|3p(W?RN7(v2AQ@v^HG- z&zqQE^P-`h>-q-H8>miB&rCJ1DL>=n{^OlDUS40{B^asis9jzxyt^2y-*vm#+mTmEyeTzVD{>nW@|-j z-X{}OYihaKX6IQ84l+vH%rzZ?^7{Q2jp4$(I|JAqiq4~lP7G`)NAKj><>*aKA3WGD z&+fly^vXr|=IGtsvE=dxBeTbvt;SpqS5Nc!V8w?Zt=7VWgZYDy)7q@=$;PqV2>bnS zRrYwEguodo;Q0TH95!#7z^VwC|5t_4lMpzQ1#Zuqe?WR*dRTgRdPI6uIwuXLN2kZ8 zB0V8JF`bt-r=heZU67uVo|>MPwx(yMXQk(+=cVVT7p05SaN3b->BVUzy(C?l>S-*E zr#1FBilz&)MUYTB%UY#0gDm7E*H;n1@nshYHq}kL;?KGFJPS>RC((BV3(wo!u z>4tP;I+<=tH>bC!ccpiy_oVly52O#LkED;LPoz(#Po>YM&!sP>FQqT1ucoi1Z>Ddh z&VO3Im%g8VkbatemVTLjm42OWPj{r>r9Y-WrN5@XrN5_#q=%+QrgPKd)24KOx+py( zJtw^|ZBM(>Xxg3fqfh1KX)+y1KS_tvk@VVhRk}94DZM4VExj|nFMTL|Jbfm8A$=u% zBYijBntqXfoBojglI~2mr3a-4r-AgC^tkk-^yGA5dU|?xdO_NjcBV_xWoaVqOIM`* z=||~Mx-uP0uS>_%8`FvO*7T0_-t@urvGnQm`E*PAdiqZKVfuOcP5OQMbNWa6adIL+ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0{<5R{|4FZ BNvQw; literal 0 HcmV?d00001 diff --git a/src/bin/dbutil/tests/testdata/empty_v1.sqlite3 b/src/bin/dbutil/tests/testdata/empty_v1.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..5ad21363ce0db954cc4e0d0e310f6609c6727dca GIT binary patch literal 215040 zcmeI%`FB)j0mtE)goJ|DT}K2L1{g3los&wKBEZ<1X*N2cdm#lG40 zq2^q%xw58GtyV5Aib|!@pYBTMraRNRR80$)#Xm1BTmHSja?wA|>&wz=??1cutvmj0 z`NP~MK;ZNXtnKUW+}(ZCetG)!y&{4Cp+Il7r&{gq|C#h%2oN|61=e>zv;F@p9C;s^ z!0})1?fj=TxvJV<8yu`2nQQJn z*y>CkXdP-!T+?cIre|lC-s;&pRv#L#7vn=)M(V}V`-+Y8s}#eH@%oPXSkV|AFB-c? zMh0)Xp=WKaR;wP}UB1HgvoozudDXk*`N~Db#_6dO&v3=q@a02edy32Idp4~vR=CVG z54DP2<72~(9VcF{*g85g(tVeW(XB(f>MO6~#Liy2ZBo_MzP)a|cgs8UOjswWsEe96q%TqcgpK@wz9sEj8zw%eSP#MqhTSf$IKp*yjgi ze&FYC^)DIvr4Q*2{)tg6hu{3aYR5ZIMBt1Q=+6IZD!VKBy0z#0o_(wG0e1HH*Df5W zUcKxf)$Sf~<_|yT+|5y{e9V%=RJmmE=FZx3vcIKXPE#|P9XFR(>z7PX<)V9Yf;!Ob z99Vu(KB-fK*=uK7?NeIwv`kR#sg-7%?dL2y$tZ0z*K`TWpYO9|3>V+s9l-ga=stSz zXl-3NddJQ!M{jC+-@ZAhUS1!6YNAK>=C09NfnLXTUH|KJ?dXnb{r+f<1ZZAGL zSU3qesmb$9$`Q|#5I7?Rod2JZ!{$vBSQP>D|Ee&05&~zkz#Vz>_e=Lr z4@nPA4@-|o=cR%4$n@w`q{pSlrwh`?G?+G}i_(+Qlhaeu=JfRRjP&gEob=rEf^=yb zO50LBy)X@@7p2QnBaNo9v@5+hy(C?k@(+v3%hM~;D^oK~rB>?x4P!dJDjiBQX*RV} zC(Wg6({<^F^xE{g^oDd(x;fpFj-|Jxx2CtHccyoxcc=HI_ook~52uf$kEc(hPo~eL z&!#V=FQzZ0ucWW0Z=`Rg?tfaoo4%L6pMH{lntqXfnSPb-NOz{+raz=VroW`WroW{J zr3a^nr}NWe(}r|mx+FaMS zo|m?y?dh^~MH)|g(p711`eE9iu1<&3YtoVQ`gAnCDZM?tCw(A&G<_<4F5Q~GmcE^S zkbahaoqm`8l>VN6lw1f9AV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs R0RjXF5FkK+!2gB7zW~94OQ-+< literal 0 HcmV?d00001 diff --git a/src/bin/dbutil/tests/testdata/invalid_v1.sqlite3 b/src/bin/dbutil/tests/testdata/invalid_v1.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..e411fd088affeaaa07aab33b438bf8ddb3a501b7 GIT binary patch literal 215040 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%0DHTRQ?&kr1H-RCY7JaFl{Ou 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 literal 0 HcmV?d00001 diff --git a/src/bin/dbutil/tests/testdata/new_v1.sqlite3 b/src/bin/dbutil/tests/testdata/new_v1.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..9a885a481636ef2c7b7ee0c173daeb88084ecbc3 GIT binary patch literal 215040 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%0DESRDL4EWXgj-BA8Tul3-H# z#{`qgPZ3Ni|Ab&t`KJVv%0DBRRDPOZQu*fulgiIzm=9L}3xY}IUlL3zKT9yF{40V< z<>v?{m7gb=RQ@%=r1EbFCY66nFsb}PhIv=n-|f!zKQ!Hg4<^MeKTZw(;6o0KB^h%I z`DrZ4m|F;pB^h%Ifw3fGZXqz1WXvsEaB_`_TL_FL8FLGPvG8PWAuv>$TL=u5<`x1& zrMZQ`P-$){76L=1xrM+`X>K7fRGM1|43*{<0z;*_g}_j0 zZXqyKnp+4Am4ACA>wkLcMu~9E>`%;|C*4$#y4gb4t*3632-nPhdG}p+4JPW2C17(blrODMu~9E?3ZWHQxgB4XcJwx zp1M&YTr>OS+4GddzbD#6*R7{+l+icVrGk4+&~@vn8zsUuvsW;Cp1u-)#BO56TF=fZ zqwH)dxUU3jXL@#4S>%>PUP&-wk0}4AsoOG2-K2u2VGFZPQ@3T5x=96>1hY<4w`G*N zNd=b#vrbdDWt6%}1(yV~PE)sKl)6a;mjts;Q@3T5x=96>1hY<4w`G*NNd=b#vrbdD zWt6%}1(yV~PE)sKl)6a;mjts;Q@16xvF)*rf=hx~r>WaAO5LP_OM+RasoOG2-K2s` zf)#5$JFAScv#H>cVC_uL&MFa+on4FB^TdgQ?Ce&#Tl)WrOd6eozo$E%+zkxAL~!=) z!9Nh3yO_b33C`Dc@Q(!N&$7W+2+p6AgMT8JZppryVUEE3Gr@G@`89&G%MbpAU{d*Y zf=T7S5=<)pjbKvw?*x;|{~(xD{wKj%<*=7w+QmHV6P#5J2Lxx8!y&<0<#3MRta5lQ zg0sruaRg_T!)p_qRSvI1a8@}yo?udWLWXHq+3>mqlgjH6Oe(KWFsVF|U{ZMlf=T5` z1e3}e5=<&@v|HwX+wM{X=I@eXfpJUrMF++$nYo4hG;YbvEd<6bnYo3)xFs{U5E!>) z<`x3umdxBjVBC_KTL_FBPjibloFXvh76L=1xrM+`X>K7fRGM1|43*{<0z;*_g}_j0 zZXqyKnp+4AmF5-#uZ$*c(T3p~6Sok!7){(l;Bqu^3xTWA#4QA_M-#UYcwsbg3xO9$ z6Soj}X*6*QftN=Uw-6XA%`Mt6Tw~%E0z;*_g}_j0ZXqyKnp+4AmF5-#L*+?FaQ}Z$ zPu(b^)J-aQJPAVQSM4i@f=jZt zdQeZ@D5KO(D!3%*y7knJGD_W~f=hy~TTk66qts0*xFqPh_0)|rO5LP_OMP8u*Zc@P| zSy?@(r*4!{>LwLjlI7Kddg?|QrEXHeC0SZMsHbj}QR*fYT$07rgL>*l8KrJg!6jK( zJ*cN{l+@j}M;Hn&NxgbdPu(b^)J-b5B-QFcJ$0juQa7pKl9a0l_0)|rO5LP_OH!;J z)KfRgD0PzxF3J4rK|MRGjIy(-;E>Gk-Me~F&(12N>})EyBuA3}JE*DKGTL>U3eKNL zOn>#Drf$n<*KI1eB$#!Yx-Fw!x2fQgVAg5swv2Y&rh-d?S*NMnGTL>U3N8s|ou+Qf zXxD8jxFnc$nz}8cUAL*=l3>

b8t_-KK&|f?21j+cMg9n+h%oW}T*P%V^haD!3$= zb(*>@qg}VD;E~K@)@katjCS3of=jZsdQi{KDx>UdD!3%*o%QUjGRn@Tf=jZy@_&30 zhd0S99`ptcZ%QyN$=r-!T9P@LU|N#7Il;6fa|?oLN#>RW(~``s2&N^OTN6wRPq!hM z7M^aKVKUC)?Fc57dk7|#w$TL=u5<`x1&rMZQ`P-$)u!~pzGFCH_9k=lL{^gx^6vnql{8Fso;{J>()~@ zO8U@iy@#pblA!C>Q#Z;eb(0D%3A%1Qb)$?@H>u!~pzGFCH_9k=lL{UQzU%&=p1M&+ zshd=ANzircsT*aKx=96>1YNhDx=}`{n^bT~uwt!eXO&TQHWgeFtexrES!I-+O$C?a zNb-LNHFaA>shd>r{(Oa5r>Wa2O5LP_OM+RasoN?_-K2s`f?21j+bT-kq=HL=S*NMn zDoWj?f=hx~r>Wa2O5LP_OM+RasoN?_-K2s?g73ONsHxj3O5LP_OM+RasoN?_-K2s` zf?21jTW*nCo|p^_Nigd)bz4Qd zZd1V}!K~BNZ58dhO$Cnx-*tOXQ@2&L>oyf!63jYH-B!`A+f;B#FzYmRTSdEWQ^6&{ ztkcwO745oB1(yV~PE)s4wCgq%ToTMWP2E<}uG>^_Nigd)bz4QdZd1V}!HTt>ol!;E z*;H^z&^znd85PB}+MbAlOR~H2fBcC(T%i>YT9P@7U|N#7JHfOhbB_$uwH@A*U|N#7 z7s0e7b8muaNoGtiEyiFsZyh!KCs$ zf=T5A2qu*eB$!m5PcW%`5W%GK0)k29g#?qz2WOb}4i6tfFsZzVU{d)|f=T7Y1e3~# z5lku{PB5u_1i_^8kpz>$TL=u5<`x1&rMX2LhHFgRLSU#gw-6XA%`F6mN^=W=q0-z!V5l^=5Ev@W zEd+*2a|?l?(%eE|s5G}|!*Gp>TL=u5<`x1&rMZQ`P-$)K7fRGM1|43&>QlJ!45b)$+>H>u#Y9}DQZ_0)|jO5LP_OM1lT zgL>*l6{T)c!6iZ0t*35OQR*fYToQEMdg?|+xdwR;{SLE6*R7{+R8i_C72H>Xu3Jyt zsOXbZUWwmf*66zR)Qu`i-K2v1O3-!dsT)<4x=96>1YNhDx=}@`n^bT~&~@vn8&#CL zNd=b#E7p2;RuyGuQ^6&{+L@l6RYlp^RB%a-B>#6%Q@2&L>oyg

b8n@-KK&|f?21j+bY_1n+h%oW}T*P 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�LI9zY;vLrrue7l5E5V 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 M2fY3V0KexS07t*1r2qf` literal 0 HcmV?d00001 diff --git a/src/bin/dbutil/tests/testdata/no_schema.sqlite3 b/src/bin/dbutil/tests/testdata/no_schema.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..9dd06144e3e205e6a9e8c1e12a952477d302eef5 GIT binary patch literal 2048 zcmWFz^vNtqRY=P(%1ta$FlJz3U}R))P*7lCU|@n`AO!}DKoW#u0GSOFL#LS-bRS9a z0!0~_w=w|HC>RZa(GZ|C1Uwnp#1$18<4Y2ga#Hj1OX7=@Gg5OC5iBO>AXmo_SA`Hq zCm&Y@gt&r6S!z*nW`3T6r(cMxyK9gpByav^VE#?1qek_QhQMeD451KUW@KVy1ORou BBJKbH literal 0 HcmV?d00001 diff --git a/src/bin/dbutil/tests/testdata/old_v1.sqlite3 b/src/bin/dbutil/tests/testdata/old_v1.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..32dbb9b35ba35fd6e4ca45efdf3ad860f02b6cb2 GIT binary patch literal 215040 zcmeI(`G4DW0mt#AT|1njct)w>*8{i85Om%r-ImQtNLkk@^PqNZ*eY9#Nd*R8FhRis z@D}eI@B2W!54ZW6wPLYMO8I{U&*SzWIK#}g=1`Va2mfAP zcfWhjR^H5I0t8OH!1|%`#a+HOy)I9@zI!C_e-s$34pgh<>pzpe69EFJpg{Si-s}Gq z9C%NeKm^SHb^`*Zu7LUf)E#@zoIpAM4^>`K$tj>(Vrpz_qh9o1S8VF7Qj9mJ>O1R`MRQ`RXzpn= zMz6beV12Dts~*|Yy@zWS7h0X}ufe|Ubr%(z=I4%W;o`~hi^eAR78lm{Zr)I=a$1-< z)GBsQO^!Er9=p3@+eD*LKFa3Awz1vy)mLoK%^sZTbpFG-#rFD+u|18cV&iyo<_oOA-G4_sCmtn8`e=Z=B1a$zk_yM~8r=ZsV@ zUvZFXmq(o5;pen}ag^%brtdJ-T{3!oXMK0FzoFiprWP_gZt4Eo&^JYO7yU0Mr~@;d z11k^8_vx*{;#CW+_HnIwLMEv8+-kGUj?`c9)Xib!2AD`XRJI@0t5&UAh5;)cV~MKOb<#AOAk+vNRLWq zrjhjM^w?CSC!{B)v(u)Of8kX&r*qR&(o@sZ(w6kh^sMyU^t|-^^rCb@8cW+#J-s-M zrD6f_&81cE`tI^saPEx;4Egy*GUzeK36}eI$J}eJp)4eJXu6eJ*`I zeKCD0eKmb8mEW{{D}6hCC;c$}DE&13Ed4zFD&3vR|9|nj^!xOu^yl=K^pNz>^vHBp zdVJcL&PnH|XQbz(7p84#XKJKfDIfY&UY@4YZ2CbukPfETrYqAm=}qY^>22wq>D}r5 z>BH$0=`-mI=_~1*>CW_%^vm?y^v85hx+|TL9-M0FG3jyXN$JVyy!7<+?DT@PHSI_j zri;^5+M6y-`_lK){&ab|BE2phPH#*{(p%Fz(rxK|>Gt&T^yzd*`f~b4`fmDh`bGLp z`a}9_`hI#qaw0&0009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ MfB*pkC%wQw0Ct<~-T(jq literal 0 HcmV?d00001 diff --git a/src/bin/dbutil/tests/testdata/v2_0.sqlite3 b/src/bin/dbutil/tests/testdata/v2_0.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..18784fdea317a840be9bb1d557f82c5b12fa7eb9 GIT binary patch literal 13312 zcmeI0&u`i=6vyoVp$+96sa$-SN;D=->bcV>4HH@#R!XZ@JD`+=HX0;V60PfQbo(PF zP1=93e`v?;Z`fhlWwr?r7iiH`b)E7e2>$W%Y=3OOufzVH;YJSW`N6=7$R^4njL~yK z5JCm|EzTPrk3`m~rMet0;_q1RKZz0U+>C2&`t=2FICSh<@M# z0ze=w1adfou{09+Y+42l;{*agAW487>gk~$E+7B|Qb>Tm;KTk;;mpI3fdE3F|F8;x zKza$l{!j1R!^nXE?0;ATKp?#Y*!zDLA0m8+ze%5^AL%7=mm?oyoL4ms54R%gr0;~? zGiP9R-a0|(`kt7RYAwCm)=9g%ZRkX-BjxxJ(lFcl3%y0mW}BD?hEY)luIC3c% zR}YLfd8!ScNqJRO@zrbIhxfkcg#4BfTI5Bf?AjAwuUd`WYU`N1)Q>Af@vMPE+WHY~ zH*TBMnufu;Y&L7veVy6_cDHYZ;f-dKo^3SD4f>4jg`v|W+s&q-SIw#O=>X~(S$(A9 zj_cXZJKoLC$tCL!PiNy%G|V0Sh$O2?(;VeVnOSRB>)E_oD&da>4kB>6eqe|3Pfmal z7v6w_HPlJK8bhg&r9drV6h-~XR7|~7#?j^ZZ8H(Nr&Fsr(_}@KI7u4(|96-B9()0T z|BC?I|1$c5@J~sXK4oP5m1Z8yb+x8&4f}AxL$~erdLh3Rg|N+w{wioI3k8-a{<#xa z5f$ut;dqHat^~m}7>^_QtavVr_p`yezM4=(kILsJRm3wZJmcv#K@pSHOc8mWl$o_= z=44eX;Zv@xvF63v8s}uemuSMgO@xvw?4B&Ek4yN?HGcuCj4ksE9C91MBQMeV`MkPS z!o%V5y1mOIC@5rG+An;%a`~g!O B=TZOw literal 0 HcmV?d00001 From 7e7b44723b80637d14bbbb714b69fe4ef7ea1f9a Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Mon, 19 Mar 2012 17:07:06 +0000 Subject: [PATCH 02/14] [963] Miscellaneous changes 1. Fix minor gremlins in unit test and main program 2. Update to rename b10-dbutil process (and so avoid an error from the process_rename_test check). 3. With (2), add run_dbutil to run in test mode --- configure.ac | 2 + src/bin/Makefile.am | 2 +- src/bin/dbutil/Makefile.am | 6 +- src/bin/dbutil/dbutil.py.in | 96 +++++++++++++++++++------- src/bin/dbutil/run_dbutil.sh.in | 41 +++++++++++ src/bin/dbutil/tests/dbutil_test.sh.in | 40 ++++++----- 6 files changed, 141 insertions(+), 46 deletions(-) create mode 100755 src/bin/dbutil/run_dbutil.sh.in diff --git a/configure.ac b/configure.ac index 93667b1e9d..e69c2c7fdd 100644 --- a/configure.ac +++ b/configure.ac @@ -1125,6 +1125,7 @@ AC_OUTPUT([doc/version.ent src/bin/cmdctl/tests/cmdctl_test src/bin/cmdctl/cmdctl.spec.pre src/bin/dbutil/dbutil.py + src/bin/dbutil/run_dbutil.sh src/bin/dbutil/tests/dbutil_test.sh src/bin/ddns/ddns.py src/bin/xfrin/tests/xfrin_test @@ -1209,6 +1210,7 @@ AC_OUTPUT([doc/version.ent chmod +x src/bin/zonemgr/run_b10-zonemgr.sh chmod +x src/bin/bind10/run_bind10.sh chmod +x src/bin/cmdctl/tests/cmdctl_test + chmod +x src/bin/dbutil/run_dbutil.sh chmod +x src/bin/dbutil/tests/dbutil_test.sh chmod +x src/bin/xfrin/tests/xfrin_test chmod +x src/bin/xfrout/tests/xfrout_test diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am index 7c6cdb8bde..499e2097ec 100644 --- a/src/bin/Makefile.am +++ b/src/bin/Makefile.am @@ -1,4 +1,4 @@ SUBDIRS = bind10 bindctl cfgmgr ddns loadzone msgq host cmdctl auth xfrin \ - xfrout usermgr zonemgr stats tests resolver sockcreator dhcp4 dhcp6 + xfrout usermgr zonemgr stats tests resolver sockcreator dhcp4 dhcp6 dbutil check-recursive: all-recursive diff --git a/src/bin/dbutil/Makefile.am b/src/bin/dbutil/Makefile.am index 5e7a942e79..bf3a4889f5 100644 --- a/src/bin/dbutil/Makefile.am +++ b/src/bin/dbutil/Makefile.am @@ -2,10 +2,14 @@ SUBDIRS = . tests bin_SCRIPTS = b10-dbutil +noinst_SCRIPTS = run_dbutil.sh + CLEANFILES = b10-dbutil b10-dbutil.pyc b10-dbutil: dbutil.py - $(SED) -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" dbutil.py > $@ + $(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \ + -e "s|@@SYSCONFDIR@@|@sysconfdir@|" \ + -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" dbutil.py >$@ chmod a+x $@ CLEANDIRS = __pycache__ diff --git a/src/bin/dbutil/dbutil.py.in b/src/bin/dbutil/dbutil.py.in index 46cd0165fd..f7056ff31a 100755 --- a/src/bin/dbutil/dbutil.py.in +++ b/src/bin/dbutil/dbutil.py.in @@ -33,12 +33,21 @@ # ".backup" already exists). This is used to restore the database if the # upgrade fails. -import os, sqlite3, shutil, sys +import sys; sys.path.append("@@PYTHONPATH@@") +import os, sqlite3, shutil from optparse import OptionParser +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" +DEFAULT_DATABASE_FILE = "@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3" + +# 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. # @@ -174,12 +183,53 @@ upgrades = [ class DbutilException(Exception): pass +# Functions for outputting messages in a consistent format. As this is intended +# to be an interactive utility, it was not considered necessary to use the full +# logging framework for messages. -def info(text): +def output(writer, prefix, text, ex = None): + """ + @brief Write error message to output stream + + @param writer Function to do the writing + @param prefix Prefix to the message + @param text Text to output + @param ex Possible exception holding additiona information + """ + writer(prefix + ": " + text) + if ex is not None: + writer(" - " + str(ex)) + writer("\n") + + +def error(text, ex = None): + """ + @brief Write error message to stderr. + + @param text Text to output + @param ex Possible exception holding additiona information + """ + output(sys.stderr.write, "ERROR", text, ex) + + +def warn(text, ex = None): + """ + @brief Write warning message to stderr. + + @param text Text to output + @param ex Possible exception holding additiona information + """ + output(sys.stderr.write, "WARN", text, ex) + + +def info(text, ex = None): """ @brief Write informational message to stdout. + + @param text Text to output + @param ex Possible exception holding additiona information """ - sys.stdout.write("INFO: " + text + "\n") + output(sys.stdout.write, "INFO", text, ex) # @brief Database Encapsulation @@ -294,12 +344,12 @@ def prompt_user(): """ sys.stdout.write( """You have selected the upgrade option. This will upgrade the schema of the -selected BIND 10 database to the latest version. +selected BIND 10 zone database to the latest version. -The utility will take a copy of the database file before running so, in the -unlikely event of a problem, you will be able to restore the database from +The utility will take a copy of the zone database file before executing so, in +the event of a problem, you will be able to restore the zone database from the backup. To ensure that the integrity of this backup, please ensure that -BIND 10 is not running before proceeding. +BIND 10 is not running before continuing. """) yes_entered = False no_entered = False @@ -474,7 +524,7 @@ def parse_command(): """ usage = ("usage: %prog --check [options] [db_file]\n" + " %prog --upgrade [--noconfirm] [options] [db_file]") - parser = OptionParser(usage=usage) + parser = OptionParser(usage = usage, version = VERSION) parser.add_option("-c", "--check", action="store_true", dest="check", default=False, help="Print database version and check if it " + @@ -492,20 +542,20 @@ def parse_command(): # Set the database file on which to operate if (len(args) > 1): - sys.stderr.write(usage + "\n") + error("too many arguments to the command, maximum of one expected") + parser.print_usage() sys.exit(1) elif len(args) == 0: - args.append(default_database_file) + args.append(DEFAULT_DATABASE_FILE) # Check for conflicting options. If some are found, output a suitable # error message and print the usage. if options.check and options.upgrade: - sys.stderr.write("cannot select both --check and --upgrade, " + - "please choose one") + error("--upgrade is not compatible with --check") elif (not options.check) and (not options.upgrade): - sys.stderr.write("must select one of --check or --upgrade") + error("must select one of --check or --upgrade") elif (options.check and options.noconfirm): - sys.stderr.write("--noconfirm is not compatible with --check") + error("--noconfirm is not compatible with --check") else: return (options, args) @@ -525,8 +575,7 @@ if __name__ == "__main__": check_version(db) db.close() except Exception as ex: - sys.stderr.write("ERROR: unable to check database version - " + - str(ex) + "\n") + error("unable to check database version - " + str(ex)) sys.exit(1) elif options.upgrade: @@ -547,15 +596,12 @@ if __name__ == "__main__": db.close() except Exception as ex: if in_progress: - sys.stderr.write("ERROR: upgrade failed - " + str(ex) + "\n") - sys.stderr.write("WARN: database may be corrupt, " + - "restore database from backup\n") + error("upgrade failed - " + str(ex)) + warn("database may be corrupt, restore it from backup") else: - sys.stderr.write("ERROR: upgrade preparation failed - " + - str(ex) + "\n") - sys.stderr.write("INFO: database upgrade was not attempted\n") + error("upgrade preparation failed - " + str(ex)) + info("database upgrade was not attempted") sys.exit(1) else: - sys.stderr.write("ERROR: internal error, neither --check nor " + - " --upgrade selected") + error("internal error, neither --check nor --upgrade selected") sys.exit(1) diff --git a/src/bin/dbutil/run_dbutil.sh.in b/src/bin/dbutil/run_dbutil.sh.in new file mode 100755 index 0000000000..eaf4b0e915 --- /dev/null +++ b/src/bin/dbutil/run_dbutil.sh.in @@ -0,0 +1,41 @@ +#! /bin/sh + +# Copyright (C) 2010 Internet Systems Consortium. +# +# 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 INTERNET SYSTEMS CONSORTIUM +# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL +# INTERNET SYSTEMS CONSORTIUM 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. + +PYTHON_EXEC=${PYTHON_EXEC:-@PYTHON@} +export PYTHON_EXEC + +DBUTIL_PATH=@abs_top_builddir@/src/bin/dbutil + +PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python +export PYTHONPATH + +# If necessary (rare cases), explicitly specify paths to dynamic libraries +# required by loadable python modules. +SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@ +if test $SET_ENV_LIBRARY_PATH = yes; then + @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@ + export @ENV_LIBRARY_PATH@ +fi + +B10_FROM_SOURCE=@abs_top_srcdir@ +export B10_FROM_SOURCE + +BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket +export BIND10_MSGQ_SOCKET_FILE + +cd ${DBUTIL_PATH} +exec ${PYTHON_EXEC} -O b10-dbutil "$@" diff --git a/src/bin/dbutil/tests/dbutil_test.sh.in b/src/bin/dbutil/tests/dbutil_test.sh.in index e30e2b7dba..2b35c9f97c 100755 --- a/src/bin/dbutil/tests/dbutil_test.sh.in +++ b/src/bin/dbutil/tests/dbutil_test.sh.in @@ -147,7 +147,7 @@ get_schema() { # @param $1 Database to upgrade upgrade_ok_test() { cp $1 $tempfile - ../b10-dbutil --upgrade --noconfirm $tempfile + ../run_dbutil.sh --upgrade --noconfirm $tempfile if [ $? -eq 0 ] then # Compare schema with the reference @@ -188,7 +188,7 @@ record_count_test() { records_count=`sqlite3 $tempfile 'select count(*) from records'` zones_count=`sqlite3 $tempfile 'select count(*) from zones'` - ../b10-dbutil --upgrade --noconfirm $tempfile + ../run_dbutil.sh --upgrade --noconfirm $tempfile if [ $? -ne 0 ] then # Reason for failure should already have been output @@ -234,12 +234,12 @@ record_count_test() { # @param $2 Expected version string check_version() { cp $1 $verfile - ../b10-dbutil --check $verfile + ../run_dbutil.sh --check $verfile if [ $? -ne 0 ] then fail "version check failed on database $1" else - ../b10-dbutil --check $verfile | grep "$2" + ../run_dbutil.sh --check $verfile | grep "$2" > /dev/null if [ $? -ne 0 ] then fail "database $1 not at expected version $2" @@ -257,12 +257,12 @@ rm -f $tempfile $backupfile # Test 1 - check that the utility fails if the database does not exist echo "1.1. Non-existent database - check" -../b10-dbutil --check $tempfile +../run_dbutil.sh --check $tempfile failzero $? check_no_backup $tempfile $backupfile echo "1.2. Non-existent database - upgrade" -../b10-dbutil --upgrade --noconfirm $tempfile +../run_dbutil.sh --upgrade --noconfirm $tempfile failzero $? check_no_backup $tempfile $backupfile rm -f $tempfile $backupfile @@ -271,14 +271,14 @@ rm -f $tempfile $backupfile # Test 2 - should fail to check an empty file and fail to upgrade it echo "2.1. Database is an empty file - check" touch $tempfile -../b10-dbutil --check $tempfile +../run_dbutil.sh --check $tempfile failzero $? check_no_backup $tempfile $backupfile rm -f $tempfile $backupfile echo "2.2. Database is an empty file - upgrade" touch $tempfile -../b10-dbutil --upgrade --noconfirm $tempfile +../run_dbutil.sh --upgrade --noconfirm $tempfile failzero $? # A backup is performed before anything else, so the backup should exist. check_backup $tempfile $backupfile @@ -287,12 +287,12 @@ rm -f $tempfile $backupfile echo "3.1. Database is not an SQLite file - check" echo "This is not an sqlite3 database" > $tempfile -../b10-dbutil --check $tempfile +../run_dbutil.sh --check $tempfile failzero $? check_no_backup $tempfile $backupfile echo "3.2. Database is not an SQLite file - upgrade" -../b10-dbutil --upgrade --noconfirm $tempfile +../run_dbutil.sh --upgrade --noconfirm $tempfile failzero $? # ...and as before, a backup should have been created check_backup $tempfile $backupfile @@ -301,14 +301,14 @@ rm -f $tempfile $backupfile echo "4.1. Database is an SQLite3 file without the schema table - check" cp $testdata/no_schema.sqlite3 $tempfile -../b10-dbutil --check $tempfile +../run_dbutil.sh --check $tempfile failzero $? check_no_backup $tempfile $backupfile rm -f $tempfile $backupfile echo "4.1. Database is an SQLite3 file without the schema table - upgrade" cp $testdata/no_schema.sqlite3 $tempfile -../b10-dbutil --upgrade --noconfirm $tempfile +../run_dbutil.sh --upgrade --noconfirm $tempfile failzero $? check_backup $testdata/no_schema.sqlite3 $backupfile rm -f $tempfile $backupfile @@ -360,25 +360,27 @@ check_backup $testdata/v2_0.sqlite3 ${backupfile}-2 rm -f $tempfile $backupfile ${backupfile}-1 ${backupfile}-2 -echo "10.1 Incompatible flags" +echo "10.1 Command-line errors" cp $testdata/old_v1.sqlite3 $tempfile -../b10-util --upgrade --check $tempfile +../run_dbutil.sh $tempfile failzero $? -../b10-util --upgrade --check $tempfile +../run_dbutil.sh --upgrade --check $tempfile failzero $? -../b10-util --noconfirm --check $tempfile +../run_dbutil.sh --noconfirm --check $tempfile +failzero $? +../run_dbutil.sh --check $tempfile $backupfile failzero $? rm -f $tempfile $backupfile echo "10.2 verbose flag" cp $testdata/old_v1.sqlite3 $tempfile -../b10-dbutil --upgrade --noconfirm --verbose $tempfile +../run_dbutil.sh --upgrade --noconfirm --verbose $tempfile passzero $? rm -f $tempfile $backupfile echo "10.3 Interactive prompt - yes" cp $testdata/old_v1.sqlite3 $tempfile -../b10-dbutil --upgrade $tempfile << . +../run_dbutil.sh --upgrade $tempfile << . Yes . passzero $? @@ -387,7 +389,7 @@ rm -f $tempfile $backupfile echo "10.4 Interactive prompt - no" cp $testdata/old_v1.sqlite3 $tempfile -../b10-dbutil --upgrade $tempfile << . +../run_dbutil.sh --upgrade $tempfile << . no . passzero $? From 538350b7db14e063f716b040a5b0f0ca2aa35278 Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Mon, 19 Mar 2012 17:28:57 +0000 Subject: [PATCH 03/14] [963] Cosmetic upgrade to some error messages --- src/bin/dbutil/dbutil.py.in | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/bin/dbutil/dbutil.py.in b/src/bin/dbutil/dbutil.py.in index f7056ff31a..dc71b6b35c 100755 --- a/src/bin/dbutil/dbutil.py.in +++ b/src/bin/dbutil/dbutil.py.in @@ -277,7 +277,7 @@ class Database: if self.connection is not None: self.connection.close() - def execute(self, statement, what = None): + def execute(self, statement): """ @brief Execute Statement @@ -286,8 +286,6 @@ class Database: execution. @param statement SQL statement to execute - @param what Reason for the action (used in the error message if the - action fails) """ if self.verbose: sys.stdout.write(statement + "\n") @@ -295,10 +293,8 @@ class Database: try: self.cursor.execute(statement) except Exception as ex: - if (what is None): - raise DbutilException("SQL Error - " + str(ex)) - else: - raise DbutilException("failed to " + what + " - " + str(ex)) + error("failed to execute " + statement) + raise DbutilException(str(ex)) def result(self): """ @@ -397,7 +393,7 @@ def get_version(db): """ # Check only one row of data in the version table. - db.execute("SELECT COUNT(*) FROM schema_version", "get database version") + db.execute("SELECT COUNT(*) FROM schema_version") result = db.result() if result[0] == 0: raise DbutilException("unable to determine database version - " + @@ -407,7 +403,7 @@ def get_version(db): "too many rows in schema_version table") # Get the version information. - db.execute("SELECT * FROM schema_version", "get database version") + db.execute("SELECT * FROM schema_version") result = db.result() major = result[0] if (major == 1): @@ -451,13 +447,12 @@ def perform_upgrade(db, upgrade): action = "upgrading database from " + increment info(action) for statement in upgrade['statements']: - db.execute(statement, "upgrade database from " + increment) + db.execute(statement) # Update the version information - db.execute("DELETE FROM schema_version", "update version information") + db.execute("DELETE FROM schema_version") db.execute("INSERT INTO schema_version VALUES (" + - str(upgrade['to'][0]) + "," + str(upgrade['to'][1]) + ")", - "update version information") + str(upgrade['to'][0]) + "," + str(upgrade['to'][1]) + ")") def perform_all_upgrades(db): From 181d405a1e606d4581d84b7fb110875c6561387a Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Tue, 20 Mar 2012 12:25:17 +0100 Subject: [PATCH 04/14] [963] Minor whitespace fixes --- src/bin/dbutil/dbutil.py.in | 4 ++-- src/bin/dbutil/tests/dbutil_test.sh.in | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bin/dbutil/dbutil.py.in b/src/bin/dbutil/dbutil.py.in index dc71b6b35c..85d043da65 100755 --- a/src/bin/dbutil/dbutil.py.in +++ b/src/bin/dbutil/dbutil.py.in @@ -78,7 +78,7 @@ upgrades = [ # Move to the latest "V1" state of the database if not there # already. "CREATE TABLE IF NOT EXISTS diffs (" + - "id INTEGER PRIMARY KEY, " + + "id INTEGER PRIMARY KEY, " + "zone_id INTEGER NOT NULL," + "version INTEGER NOT NULL, " + "operation INTEGER NOT NULL, " + @@ -419,7 +419,7 @@ def get_version(db): def match_version(db, expected): """ @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. diff --git a/src/bin/dbutil/tests/dbutil_test.sh.in b/src/bin/dbutil/tests/dbutil_test.sh.in index 2b35c9f97c..ce469be67b 100755 --- a/src/bin/dbutil/tests/dbutil_test.sh.in +++ b/src/bin/dbutil/tests/dbutil_test.sh.in @@ -287,12 +287,12 @@ rm -f $tempfile $backupfile echo "3.1. Database is not an SQLite file - check" echo "This is not an sqlite3 database" > $tempfile -../run_dbutil.sh --check $tempfile +../run_dbutil.sh --check $tempfile failzero $? check_no_backup $tempfile $backupfile echo "3.2. Database is not an SQLite file - upgrade" -../run_dbutil.sh --upgrade --noconfirm $tempfile +../run_dbutil.sh --upgrade --noconfirm $tempfile failzero $? # ...and as before, a backup should have been created check_backup $tempfile $backupfile From d85eda60ea4f2304859ff935f998585dae2f4854 Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Tue, 20 Mar 2012 22:37:50 +0000 Subject: [PATCH 05/14] [963] Changes after review. --- configure.ac | 1 + src/bin/dbutil/Makefile.am | 10 + src/bin/dbutil/b10-dbutil.xml | 174 ++++++++++++++++ src/bin/dbutil/dbutil.py.in | 187 ++++++++++-------- src/bin/dbutil/tests/Makefile.am | 2 +- src/bin/dbutil/tests/dbutil_test.sh.in | 145 ++++++++++---- src/bin/dbutil/tests/testdata/Makefile.am | 12 ++ src/bin/dbutil/tests/testdata/README | 6 + src/bin/dbutil/tests/testdata/corrupt.sqlite3 | Bin 0 -> 215040 bytes .../tests/testdata/empty_version.sqlite3 | Bin 0 -> 13312 bytes .../tests/testdata/too_many_version.sqlite3 | Bin 0 -> 13312 bytes 11 files changed, 412 insertions(+), 125 deletions(-) create mode 100644 src/bin/dbutil/b10-dbutil.xml create mode 100644 src/bin/dbutil/tests/testdata/Makefile.am create mode 100644 src/bin/dbutil/tests/testdata/corrupt.sqlite3 create mode 100644 src/bin/dbutil/tests/testdata/empty_version.sqlite3 create mode 100644 src/bin/dbutil/tests/testdata/too_many_version.sqlite3 diff --git a/configure.ac b/configure.ac index e69c2c7fdd..8704f8ee7b 100644 --- a/configure.ac +++ b/configure.ac @@ -996,6 +996,7 @@ AC_CONFIG_FILES([Makefile src/bin/cfgmgr/tests/Makefile src/bin/dbutil/Makefile src/bin/dbutil/tests/Makefile + src/bin/dbutil/tests/testdata/Makefile src/bin/host/Makefile src/bin/loadzone/Makefile src/bin/loadzone/tests/correct/Makefile diff --git a/src/bin/dbutil/Makefile.am b/src/bin/dbutil/Makefile.am index bf3a4889f5..1ede4d6447 100644 --- a/src/bin/dbutil/Makefile.am +++ b/src/bin/dbutil/Makefile.am @@ -1,11 +1,21 @@ SUBDIRS = . tests bin_SCRIPTS = b10-dbutil +man_MANS = b10-dbutil.8 + +EXTRA_DIST = $(man_MANS) b10-dbutil.xml noinst_SCRIPTS = run_dbutil.sh CLEANFILES = b10-dbutil b10-dbutil.pyc +if ENABLE_MAN + +b10-dbutil.8: b10-dbutil.xml + xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/bindctl.xml + +endif + b10-dbutil: dbutil.py $(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \ -e "s|@@SYSCONFDIR@@|@sysconfdir@|" \ diff --git a/src/bin/dbutil/b10-dbutil.xml b/src/bin/dbutil/b10-dbutil.xml new file mode 100644 index 0000000000..8d7a5894f1 --- /dev/null +++ b/src/bin/dbutil/b10-dbutil.xml @@ -0,0 +1,174 @@ +]> + + + + + + March 20, 2012 + + + + b10-dbutil + 8 + BIND10 + + + + b10-dbutil + Zone Database Maintenance Utility + + + + + 2012 + Internet Systems Consortium, Inc. ("ISC") + + + + + + b10-dbutil --check + --verbose + dbfile + + + b10-dbutil --upgrade + --noconfirm + --verbose + dbfile + + + + + DESCRIPTION + + The b10-dbutil utility is a general administration + utility for SQL databases. (Currently only SQLite is supported by + BIND 10.) It can report the current verion of the schema, and upgrade + an existing database to the latest version of the schema. + + + + b10-dbutil operates in one of two modes, check mode + or upgrade mode. + + + + In check mode (b10-dbutil --check), the + utility reads the version of the database schema from the database + and prints it. It will tell you whether the schema is at the latest + version supported by BIND 10. + + + + When the upgrade function is selected + (b10-dbutil --upgrade), the + utility takes a copy of the database, then upgrades it to the latest + version of the schema. The contents of the database remain intact. + (The backup file is a file in the same directory as the database + file. It has the same name, with ".backup" appended to it. If a + file of that name already exists, the file will have the suffix + ".backup-1". If that exists, the file will be suffixed ".backup-2", + and so on.) + + + + When upgrading the database, it is strongly + recommended that BIND 10 not be running while the upgrade is in + progress. + + + + + + ARGUMENTS + + The arguments are as follows: + + + + + + + + Selects the version check function, which reports the + current version of the database. This is incompatible + with the --upgrade option. + + + + + + + + + + Only valid with --upgrade, this disables the prompt. + Normally the utility will print a warning that an upgrade is + about to take place and request that you type "Yes" to continue. + If this switch is given on the command line, no prompt will + be issued: the utility will just perform the upgrade. + + + + + + + + + + Selects the upgrade function, which upgrades the database + to the latest version of the schema. This is incompatible + with the --upgrade option. + + + The upgrade function will upgrade a BIND 10 database - no matter how + old the schema - preserving all data. A backup file is created + before the upgrade (with the same name as the database, but with + ".backup" suffixed to it). If the upgrade fails, this file can + be copied back to restore the original database. + + + + + + + + + + Enable verbose mode. Each SQL command issued by the + utility will be printed to before it is executed. + + + + + + + + + + Name of the database file to check of upgrade. + + + + + + + + diff --git a/src/bin/dbutil/dbutil.py.in b/src/bin/dbutil/dbutil.py.in index 85d043da65..a6e70a67b1 100755 --- a/src/bin/dbutil/dbutil.py.in +++ b/src/bin/dbutil/dbutil.py.in @@ -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 + @brief Check the version - Checks whether the version of the database matches that expected for - the upgrade. Both the major and minor versions must match. + Checks the version of the database and the latest version, and advises if + an upgrade is needed. - @param db Database - @param expected Expected version of the database in form (major, minor) - - @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) diff --git a/src/bin/dbutil/tests/Makefile.am b/src/bin/dbutil/tests/Makefile.am index 7e17e09ca8..c03b262aa2 100644 --- a/src/bin/dbutil/tests/Makefile.am +++ b/src/bin/dbutil/tests/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = . +SUBDIRS = . testdata # Tests of the update script. diff --git a/src/bin/dbutil/tests/dbutil_test.sh.in b/src/bin/dbutil/tests/dbutil_test.sh.in index ce469be67b..26b577c9fd 100755 --- a/src/bin/dbutil/tests/dbutil_test.sh.in +++ b/src/bin/dbutil/tests/dbutil_test.sh.in @@ -35,7 +35,7 @@ succeed() { # # @param $1 Optional additional reason to output fail() { - if [ "x$1" != "x" ] + if [ "$1" != "" ] then echo "ERROR: $1" fi @@ -68,6 +68,19 @@ failzero() { } +# @brief Copy File +# +# Executes a "cp" operation followed by a "chmod" to make the target writeable. +# +# @param $1 Source file +# @param $2 Target file +copy_file () { + cp $1 $2 + chmod a+w $2 +} + + + # @brief Check backup file # # Record a failure if the backup file does not exist or if it is different @@ -125,7 +138,7 @@ check_no_backup() { # @param $1 Database for which the schema is required get_schema() { db1=@abs_builddir@/dbutil_test_schema_$$ - cp $1 $db1 + copy_file $1 $db1 db_schema=`sqlite3 $db1 '.schema' | \ awk '{line = line $0} END {print line}' | \ @@ -145,8 +158,9 @@ get_schema() { # on entry, and is responsible for removing them afterwards. # # @param $1 Database to upgrade +# @param $2 Expected backup file upgrade_ok_test() { - cp $1 $tempfile + copy_file $1 $tempfile ../run_dbutil.sh --upgrade --noconfirm $tempfile if [ $? -eq 0 ] then @@ -155,15 +169,18 @@ upgrade_ok_test() { expected_schema=$db_schema get_schema $tempfile actual_schema=$db_schema - if [ x$expected_schema = x$actual_schema ] + if [ "$expected_schema" = "$actual_schema" ] then succeed else fail "upgraded schema not as expected" fi - # and check the version is set correctly + # Check the version is set correctly check_version $tempfile "V2.0" + + # Check that a backup was made + check_backup $1 $2 else # Error should have been output already fail @@ -171,6 +188,23 @@ upgrade_ok_test() { } +# @brief Unsuccessful Upgrade Test +# +# Checks that an upgrade of the specified database fails. +# +# Note: the caller must ensure that $tempfile and $backupfile do not exist +# on entry, and is responsible for removing them afterwards. +# +# @param $1 Database to upgrade +# @param $2 Expected backup file +upgrade_fail_test() { + copy_file $1 $tempfile + ../run_dbutil.sh --upgrade --noconfirm $tempfile + failzero $? + check_backup $1 $backupfile +} + + # @brief Record Count Test # # Checks that the count of records in each table is preserved in the upgrade. @@ -181,7 +215,7 @@ upgrade_ok_test() { # # @brief $1 Database to upgrade record_count_test() { - cp $1 $tempfile + copy_file $1 $tempfile diffs_count=`sqlite3 $tempfile 'select count(*) from diffs'` nsec3_count=`sqlite3 $tempfile 'select count(*) from nsec3'` @@ -233,7 +267,7 @@ record_count_test() { # @param $1 Database to check # @param $2 Expected version string check_version() { - cp $1 $verfile + copy_file $1 $verfile ../run_dbutil.sh --check $verfile if [ $? -ne 0 ] then @@ -251,6 +285,20 @@ check_version() { } +# @brief Version Check Fail +# +# Does a version check but expected the check to fail +# +# @param $1 Database to check +# @param $2 Backup file +check_version_fail() { + copy_file $1 $verfile + ../run_dbutil.sh --check $verfile + failzero $? + check_no_backup $tempfile $backupfile +} + + # Main test sequence rm -f $tempfile $backupfile @@ -271,9 +319,7 @@ rm -f $tempfile $backupfile # Test 2 - should fail to check an empty file and fail to upgrade it echo "2.1. Database is an empty file - check" touch $tempfile -../run_dbutil.sh --check $tempfile -failzero $? -check_no_backup $tempfile $backupfile +check_version_fail $tempfile $backupfile rm -f $tempfile $backupfile echo "2.2. Database is an empty file - upgrade" @@ -287,11 +333,11 @@ rm -f $tempfile $backupfile echo "3.1. Database is not an SQLite file - check" echo "This is not an sqlite3 database" > $tempfile -../run_dbutil.sh --check $tempfile -failzero $? -check_no_backup $tempfile $backupfile +check_version_fail $tempfile $backupfile +rm -f $tempfile $backupfile echo "3.2. Database is not an SQLite file - upgrade" +echo "This is not an sqlite3 database" > $tempfile ../run_dbutil.sh --upgrade --noconfirm $tempfile failzero $? # ...and as before, a backup should have been created @@ -300,17 +346,11 @@ rm -f $tempfile $backupfile echo "4.1. Database is an SQLite3 file without the schema table - check" -cp $testdata/no_schema.sqlite3 $tempfile -../run_dbutil.sh --check $tempfile -failzero $? -check_no_backup $tempfile $backupfile +check_version_fail $testdata/no_schema.sqlite3 $backupfile rm -f $tempfile $backupfile echo "4.1. Database is an SQLite3 file without the schema table - upgrade" -cp $testdata/no_schema.sqlite3 $tempfile -../run_dbutil.sh --upgrade --noconfirm $tempfile -failzero $? -check_backup $testdata/no_schema.sqlite3 $backupfile +upgrade_fail_test $testdata/no_schema.sqlite3 $backupfile rm -f $tempfile $backupfile @@ -320,8 +360,7 @@ check_no_backup $tempfile $backupfile rm -f $tempfile $backupfile echo "5.2. Database is an old V1 database - upgrade" -upgrade_ok_test $testdata/old_v1.sqlite3 -check_backup $testdata/old_v1.sqlite3 $backupfile +upgrade_ok_test $testdata/old_v1.sqlite3 $backupfile rm -f $tempfile $backupfile @@ -331,8 +370,7 @@ check_no_backup $tempfile $backupfile rm -f $tempfile $backupfile echo "6.2. Database is a new V1 database - upgrade" -upgrade_ok_test $testdata/new_v1.sqlite3 -check_backup $testdata/new_v1.sqlite3 $backupfile +upgrade_ok_test $testdata/new_v1.sqlite3 $backupfile rm -f $tempfile $backupfile @@ -342,44 +380,71 @@ check_no_backup $tempfile $backupfile rm -f $tempfile $backupfile echo "7.2. Database is a V2.0 database - upgrade" -upgrade_ok_test $testdata/v2_0.sqlite3 -check_backup $testdata/v2_0.sqlite3 $backupfile +upgrade_ok_test $testdata/v2_0.sqlite3 $backupfile rm -f $tempfile $backupfile -echo "8. Record count test" -record_count_test testdata/new_v1.sqlite3 +echo "8.1. Database is V2.0 database with empty schema table - check" +check_version_fail $testdata/empty_version.sqlite3 $backupfile +rm -f $tempfile $backupfile + +echo "8.2. Database is V2.0 database with empty schema table - upgrade" +upgrade_fail_test $testdata/empty_version.sqlite3 $backupfile rm -f $tempfile $backupfile -echo "9. Backup file already exists" +echo "9.1. Database is V2.0 database with over-full schema table - check" +check_version_fail $testdata/too_many_version.sqlite3 $backupfile +rm -f $tempfile $backupfile + +echo "9.2. Database is V2.0 database with over-full schema table - upgrade" +upgrade_fail_test $testdata/too_many_version.sqlite3 $backupfile +rm -f $tempfile $backupfile + + +echo "10.0. Upgrade corrupt database" +upgrade_fail_test $testdata/corrupt.sqlite3 $backupfile +rm -f $tempfile $backupfile + + +echo "11. Record count test" +record_count_test $testdata/new_v1.sqlite3 +rm -f $tempfile $backupfile + + +echo "12. Backup file already exists" touch $backupfile touch ${backupfile}-1 -upgrade_ok_test $testdata/v2_0.sqlite3 -check_backup $testdata/v2_0.sqlite3 ${backupfile}-2 +upgrade_ok_test $testdata/v2_0.sqlite3 ${backupfile}-2 rm -f $tempfile $backupfile ${backupfile}-1 ${backupfile}-2 -echo "10.1 Command-line errors" -cp $testdata/old_v1.sqlite3 $tempfile +echo "13.1 Command-line errors" +copy_file $testdata/old_v1.sqlite3 $tempfile ../run_dbutil.sh $tempfile failzero $? ../run_dbutil.sh --upgrade --check $tempfile failzero $? ../run_dbutil.sh --noconfirm --check $tempfile failzero $? +../run_dbutil.sh --check +failzero $? +../run_dbutil.sh --upgrade --noconfirm +failzero $? ../run_dbutil.sh --check $tempfile $backupfile failzero $? +../run_dbutil.sh --upgrade --noconfirm $tempfile $backupfile +failzero $? rm -f $tempfile $backupfile -echo "10.2 verbose flag" -cp $testdata/old_v1.sqlite3 $tempfile +echo "13.2 verbose flag" +copy_file $testdata/old_v1.sqlite3 $tempfile ../run_dbutil.sh --upgrade --noconfirm --verbose $tempfile passzero $? rm -f $tempfile $backupfile -echo "10.3 Interactive prompt - yes" -cp $testdata/old_v1.sqlite3 $tempfile +echo "13.3 Interactive prompt - yes" +copy_file $testdata/old_v1.sqlite3 $tempfile ../run_dbutil.sh --upgrade $tempfile << . Yes . @@ -387,8 +452,8 @@ passzero $? check_version $tempfile "V2.0" rm -f $tempfile $backupfile -echo "10.4 Interactive prompt - no" -cp $testdata/old_v1.sqlite3 $tempfile +echo "13.4 Interactive prompt - no" +copy_file $testdata/old_v1.sqlite3 $tempfile ../run_dbutil.sh --upgrade $tempfile << . no . diff --git a/src/bin/dbutil/tests/testdata/Makefile.am b/src/bin/dbutil/tests/testdata/Makefile.am new file mode 100644 index 0000000000..0d850a70f0 --- /dev/null +++ b/src/bin/dbutil/tests/testdata/Makefile.am @@ -0,0 +1,12 @@ +EXTRA_DIST = +EXTRA_DIST += corrupt.sqlite3 +EXTRA_DIST += empty_schema.sqlite3 +EXTRA_DIST += empty_v1.sqlite3 +EXTRA_DIST += empty_version.sqlite3 +EXTRA_DIST += invalid_v1.sqlite3 +EXTRA_DIST += new_v1.sqlite3 +EXTRA_DIST += no_schema.sqlite3 +EXTRA_DIST += old_v1.sqlite3 +EXTRA_DIST += README +EXTRA_DIST += too_many_version.sqlite3 +EXTRA_DIST += v2_0.sqlite3 diff --git a/src/bin/dbutil/tests/testdata/README b/src/bin/dbutil/tests/testdata/README index 093bcbcc3a..83ce01f550 100644 --- a/src/bin/dbutil/tests/testdata/README +++ b/src/bin/dbutil/tests/testdata/README @@ -21,6 +21,9 @@ empty_v1.sqlite3: A database conforming to the new V1 schema. The database is empty, except for the schema_version table, where the "version" column is set to 1. +empty_version.sqlite3: A database conforming to the V2.0 schema but without +anything in the schema_version table. + no_schema.sqlite3: A valid SQLite3 database, but without a schema_version table. @@ -33,3 +36,6 @@ is marked as V1, does not have the nsec3 table. new_v1.sqlite3: A valid SQLite3 database with data in all the tables (although the single rows in both the nsec3 and diffs table make no sense, but are valid). + +too_many_version.sqlite3: A database conforming to the V2.0 schema but with +too many rows of data. diff --git a/src/bin/dbutil/tests/testdata/corrupt.sqlite3 b/src/bin/dbutil/tests/testdata/corrupt.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..69683b74774faab262a38ea459e93176f9394a1b GIT binary patch literal 215040 zcmeI%`FC7p0mtE)q)ng*E}(-%;Q|5;6jAEFKtq|9K$y@pAQd5|&Co{L1Sb(FE^LAe zipr*nxd#wD!u8&bZ~km9h9nR*E0X>UF#O#_Et{*=Ml@Zw%Yx# z^05P7x>@`*_X!Z#_X54k%Y(Z-HXWDyUf=&n;QuJlUG2(TZy`W{009C7_B(;{^M8Km zw-6w(9}27}_u28kACA0TCQx47f4tINxu{aPsH@uj!}3qZR4%FZ)cX6YJ7>o?Z*R_Q z+1A`KK6XX3H8VLqweVio+L8LeXuTL6IAf?@EPSk3Ik!nM*ch#^ua6Xs;nAXT{?Jf= zE?lnb*qdF7FLYKX-t-Ntmi5;9`l>gb(jKQ)bIWvVVrK4U`GS#|TUo4}oY*}Y=Zy@W zJ20}TIH$g8)rw*d%hl6U&9QkK>>9p3KYMC?N3+;CIx^T;zk8sIwZlV0<@ZNpc zX>M8F{%6I4DXP8de>p*I8=u*>_@w-@CaCEvr<$$3+H#*vP^~@AHtP=PsU26Vj0af9Z^scS?W& z0RjX%Ebw$*?~wG$bXa9Idk&aDoO2?&@DL>&=R;82Eo6}pB5v>SX4fgE>0g#<7px_ zQ+W+zGJPcNNKAG}%x-s39K9@e9ZcewPFQqT1+tTgn ztLba$>*<^6Tj}m}Z@Mo%m>x(h2FL^p^DY^zL+8TAS9Vp|m08qEF?6X)J9?zfRlI z_Vm$oMY=j&lRlk3o4%NCO?Remq={xDs^yBoa^kn*DdM5oNy_g6~<48cmzhC24c|McSG!OP8mQrJd4x-~^o8`5bVs@? zeLLNs9!cL%KT1DOzfFHgPp7}97t$}&E7G#moh$?h5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N g0t5&UAV7cs0RjXF5FkK+009C72oNAZV95&n8-Zw-F#rGn literal 0 HcmV?d00001 diff --git a/src/bin/dbutil/tests/testdata/empty_version.sqlite3 b/src/bin/dbutil/tests/testdata/empty_version.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..b820fa99900e0e5396f95054c0531b34c59b4ace GIT binary patch literal 13312 zcmeI0&2HL25XZehOyZEUrEr-`6|7RJQqHMLh^bH=(-0CR>H#^}1gl_2w$(Jfk@g`f zRq7k`g?enCp{HKDyBP2WNKh4pRE#C;{TOD}zulex!r_78Mh>+7pl3y}EoCG^q?Z7a zB<1L5gMNmWLJMSAxlv9u3&ouD?C0hRGfw|u9qWUKV1pGTfCLsoU@gNpcxEBNF(?v1 z0t+U9`+va`fZ>tA0tv7~Jw5c}g#?hm5)wfDU&2YpS4ILc0n~q73P@mi3E=);-nqv& zM*_J2aSb4W)tj>w2l7L!-27=pgn%A^r+f&4&I;uY*}@fO%vXMWyF@elXDvdwQjGWHjKp)_;-8 zs;Ww^-|{hh^gSo!x3mzEH$lO*N50Mv^EOC-F{QvJh_dWVT0{<5Qw*O`6n?!!6 z^wgKMOuo~~qq(kg3fHhtmppabZo3`w+lG*~dDA~7ZDppwg5qB|ffZ4~o|ca1NaRWo zjFa&&lTV80%zQtYtSf5?Mf9kAZc|0LIuw|-X0FJpmM7<2S!2zM zwKcBEf-lj8c^e5OSJ(qtR-fj{yBq!j))`x7XE@|Gf=Aw>b+cJ@Cr|ntH+(XpP0O>J zN$0CGoQuX{6fUPl7)0w|t`64OK3`{?6~8I5=fUYLW|)wF;jA?KY@jggqVi5I1pm@= qg1d~Sua>zE2lNOrxuo#-|D#EEI1nW8-~{me|KP|mAQE^q1pWe-ZRJt` literal 0 HcmV?d00001 diff --git a/src/bin/dbutil/tests/testdata/too_many_version.sqlite3 b/src/bin/dbutil/tests/testdata/too_many_version.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..5dc8ae343f8aae5e8784ffe8e4fe2ebc7c10ae90 GIT binary patch literal 13312 zcmeI0&rjPh6vyqPO(B$XBzo~>qR~2O5@)6r2vfC?j#3b4hblAyBH9Ec(K>c(w?AUi zr2Pl`hj!flgGoC~yUcdeHl{696Jm_MNKO3X<=Ot&eqX11yQUYroH0D%WiAS=lj%P5QKL+T5B=7A3xh6ea?G?qTFW0QNtu0U$8H1lad~1|J}NfWJzgr0?@f;xH@`h;;v5pjzS4hRG8!wC+v8r`q?VDyv zZF_zYj+K)wW4pX>*2z=7`%KDdnudF?c^}>dz8mqIEVRgrNYQhKzFyTTJLTFDd1)M# zi0a#Im(-0z+HTS|*{qr->#|kdEbkfACUlxDJBqF~8}@9qVy)7596yTOCfTS~O`~j$ zoR0>uEi2k14L3aBao_T8HjXb@cX&FJjG|&~8HXfYO{!KuPm0W1@2zBVTA_eHEO8K_ z+YCY{N*-AOMpAeU4%Sd31#1AML}mgtgHar}hEp-}P8r9S7dOpBsxrV*(@X+meCnpiVEem0r7yVVx zR;LOqQG*LNv|}pRlfv-~fm{v4Q7|4v@^SH;8t=!0_4Z;)5q&D3Yg7@>?C6ZA^MWF# ztC=G5JSj43-O4JOUcjeZSrg4mv^B{of-lj8c^e8PSJ+)e(HNXS?I5`9lp*uD}GZG&;8R` z!Z0QO!dYqh*+6C3Mdck|2tmhp!<&pIua=n(2lNOrzNFy$|K0>U=m-$Fa{_SwzjNf! KAP~4W1pWXQ$mmi4 literal 0 HcmV?d00001 From 0f487a998063a01ea16e8be048076f53b11afd02 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Fri, 23 Mar 2012 11:29:09 +0100 Subject: [PATCH 06/14] [963] fix b10-dbutil.8 creation rule add it to git --- src/bin/dbutil/Makefile.am | 2 +- src/bin/dbutil/b10-dbutil.8 | 87 +++++++++++++++++++++++++++++++++++++ src/bin/xfrout/b10-xfrout.8 | 29 ++++--------- 3 files changed, 97 insertions(+), 21 deletions(-) create mode 100644 src/bin/dbutil/b10-dbutil.8 diff --git a/src/bin/dbutil/Makefile.am b/src/bin/dbutil/Makefile.am index 1ede4d6447..5c05757487 100644 --- a/src/bin/dbutil/Makefile.am +++ b/src/bin/dbutil/Makefile.am @@ -12,7 +12,7 @@ CLEANFILES = b10-dbutil b10-dbutil.pyc if ENABLE_MAN b10-dbutil.8: b10-dbutil.xml - xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/bindctl.xml + xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-dbutil.xml endif diff --git a/src/bin/dbutil/b10-dbutil.8 b/src/bin/dbutil/b10-dbutil.8 new file mode 100644 index 0000000000..3014610cc2 --- /dev/null +++ b/src/bin/dbutil/b10-dbutil.8 @@ -0,0 +1,87 @@ +'\" t +.\" Title: b10-dbutil +.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author] +.\" Generator: DocBook XSL Stylesheets v1.75.2 +.\" Date: March 20, 2012 +.\" Manual: BIND10 +.\" Source: BIND10 +.\" Language: English +.\" +.TH "B10\-DBUTIL" "8" "March 20, 2012" "BIND10" "BIND10" +.\" ----------------------------------------------------------------- +.\" * Define some portability stuff +.\" ----------------------------------------------------------------- +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.\" http://bugs.debian.org/507673 +.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" ----------------------------------------------------------------- +.\" * set default formatting +.\" ----------------------------------------------------------------- +.\" disable hyphenation +.nh +.\" disable justification (adjust text to left margin only) +.ad l +.\" ----------------------------------------------------------------- +.\" * MAIN CONTENT STARTS HERE * +.\" ----------------------------------------------------------------- +.SH "NAME" +b10-dbutil \- Zone Database Maintenance Utility +.SH "SYNOPSIS" +.HP \w'\fBb10\-dbutil\ \-\-check\fR\ 'u +\fBb10\-dbutil \-\-check\fR [\-\-verbose] [\fIdbfile\fR] +.HP \w'\fBb10\-dbutil\ \-\-upgrade\fR\ 'u +\fBb10\-dbutil \-\-upgrade\fR [\-\-noconfirm] [\-\-verbose] [\fIdbfile\fR] +.SH "DESCRIPTION" +.PP +The +\fBb10\-dbutil\fR +utility is a general administration utility for SQL databases\&. (Currently only SQLite is supported by BIND 10\&.) It can report the current verion of the schema, and upgrade an existing database to the latest version of the schema\&. +.PP + +\fBb10\-dbutil\fR +operates in one of two modes, check mode or upgrade mode\&. +.PP +In check mode (\fBb10\-dbutil \-\-check\fR), the utility reads the version of the database schema from the database and prints it\&. It will tell you whether the schema is at the latest version supported by BIND 10\&. +.PP +When the upgrade function is selected (\fBb10\-dbutil \-\-upgrade\fR), the utility takes a copy of the database, then upgrades it to the latest version of the schema\&. The contents of the database remain intact\&. (The backup file is a file in the same directory as the database file\&. It has the same name, with "\&.backup" appended to it\&. If a file of that name already exists, the file will have the suffix "\&.backup\-1"\&. If that exists, the file will be suffixed "\&.backup\-2", and so on\&.) +.PP +When upgrading the database, it is +\fIstrongly\fR +recommended that BIND 10 not be running while the upgrade is in progress\&. +.SH "ARGUMENTS" +.PP +The arguments are as follows: +.PP +\fB\-\-check\fR +.RS 4 +Selects the version check function, which reports the current version of the database\&. This is incompatible with the \-\-upgrade option\&. +.RE +.PP +\fB\-\-noconfirm\fR +.RS 4 +Only valid with \-\-upgrade, this disables the prompt\&. Normally the utility will print a warning that an upgrade is about to take place and request that you type "Yes" to continue\&. If this switch is given on the command line, no prompt will be issued: the utility will just perform the upgrade\&. +.RE +.PP +\fB\-\-upgrade\fR +.RS 4 +Selects the upgrade function, which upgrades the database to the latest version of the schema\&. This is incompatible with the \-\-upgrade option\&. +.sp +The upgrade function will upgrade a BIND 10 database \- no matter how old the schema \- preserving all data\&. A backup file is created before the upgrade (with the same name as the database, but with "\&.backup" suffixed to it)\&. If the upgrade fails, this file can be copied back to restore the original database\&. +.RE +.PP +\fB\-\-verbose\fR +.RS 4 +Enable verbose mode\&. Each SQL command issued by the utility will be printed to before it is executed\&. +.RE +.PP +\fB\fIdbfile\fR\fR +.RS 4 +Name of the database file to check of upgrade\&. +.RE +.SH "COPYRIGHT" +.br +Copyright \(co 2012 Internet Systems Consortium, Inc. ("ISC") +.br diff --git a/src/bin/xfrout/b10-xfrout.8 b/src/bin/xfrout/b10-xfrout.8 index 3670ec5067..69682a3235 100644 --- a/src/bin/xfrout/b10-xfrout.8 +++ b/src/bin/xfrout/b10-xfrout.8 @@ -9,6 +9,15 @@ .\" .TH "B10\-XFROUT" "8" "February 28\&. 2012" "BIND10" "BIND10" .\" ----------------------------------------------------------------- +.\" * Define some portability stuff +.\" ----------------------------------------------------------------- +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.\" http://bugs.debian.org/507673 +.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- .\" disable hyphenation @@ -82,26 +91,6 @@ A list of JSON objects (i\&.e\&. maps) that define per zone configuration concer \fBb10\-xfrout\fR\&. The supported names of each object are "origin" (the origin name of the zone), "class" (the RR class of the zone, optional, default to "IN"), and "transfer_acl" (ACL only applicable to transfer requests for that zone)\&. See the BIND 10 Guide for configuration examples\&. The default is an empty list, that is, no zone specific configuration\&. -.PP - -\fIlog_name\fR -.PP - -\fIlog_file\fR -The location of the log file if using a file channel\&. If undefined, then the file channel is closed\&. The default is -/usr/local/var/bind10\-devel/log/Xfrout\&.log\&. -.PP - -\fIlog_severity\fR -The default is "debug"\&. -.PP - -\fIlog_versions\fR -The default is 5\&. -.PP - -\fIlog_max_bytes\fR -The default is 1048576\&. .if n \{\ .sp .\} From b204a39d5b9003f991104c2bd6896013f19a05d0 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Fri, 23 Mar 2012 11:37:44 +0100 Subject: [PATCH 07/14] [963] fix run_dbutil.sh script to not change cwd with this change, you can pass run_dbutil.sh a relative path name for the db file --- src/bin/dbutil/run_dbutil.sh.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bin/dbutil/run_dbutil.sh.in b/src/bin/dbutil/run_dbutil.sh.in index eaf4b0e915..fea7482cbb 100755 --- a/src/bin/dbutil/run_dbutil.sh.in +++ b/src/bin/dbutil/run_dbutil.sh.in @@ -37,5 +37,4 @@ export B10_FROM_SOURCE BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket export BIND10_MSGQ_SOCKET_FILE -cd ${DBUTIL_PATH} -exec ${PYTHON_EXEC} -O b10-dbutil "$@" +exec ${PYTHON_EXEC} -O ${DBUTIL_PATH}/b10-dbutil "$@" From 2c0a8fc3bfa0fb8f5f6cb2df504b326741996025 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Fri, 23 Mar 2012 12:19:59 +0100 Subject: [PATCH 08/14] [963] improve exit status 0 on success (or upgrade aborted by user) 1 on check and need update 2 on check and version of db too high 3 on command-line error 4 on db read error 5 on upgrade error --- src/bin/dbutil/b10-dbutil.8 | 4 +-- src/bin/dbutil/b10-dbutil.xml | 8 ++++-- src/bin/dbutil/dbutil.py.in | 40 ++++++++++++++++++-------- src/bin/dbutil/tests/dbutil_test.sh.in | 4 +-- 4 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/bin/dbutil/b10-dbutil.8 b/src/bin/dbutil/b10-dbutil.8 index 3014610cc2..4fce304916 100644 --- a/src/bin/dbutil/b10-dbutil.8 +++ b/src/bin/dbutil/b10-dbutil.8 @@ -44,9 +44,9 @@ utility is a general administration utility for SQL databases\&. (Currently only \fBb10\-dbutil\fR operates in one of two modes, check mode or upgrade mode\&. .PP -In check mode (\fBb10\-dbutil \-\-check\fR), the utility reads the version of the database schema from the database and prints it\&. It will tell you whether the schema is at the latest version supported by BIND 10\&. +In check mode (\fBb10\-dbutil \-\-check\fR), the utility reads the version of the database schema from the database and prints it\&. It will tell you whether the schema is at the latest version supported by BIND 10\&. Exit status is 0 if the schema is at the correct version, 1 if the schema is at an older version, 2 if the schema is at a version not yet supported by this version of b10\-dbutil\&. Any higher value indicates a read or command\-line error\&. .PP -When the upgrade function is selected (\fBb10\-dbutil \-\-upgrade\fR), the utility takes a copy of the database, then upgrades it to the latest version of the schema\&. The contents of the database remain intact\&. (The backup file is a file in the same directory as the database file\&. It has the same name, with "\&.backup" appended to it\&. If a file of that name already exists, the file will have the suffix "\&.backup\-1"\&. If that exists, the file will be suffixed "\&.backup\-2", and so on\&.) +When the upgrade function is selected (\fBb10\-dbutil \-\-upgrade\fR), the utility takes a copy of the database, then upgrades it to the latest version of the schema\&. The contents of the database remain intact\&. (The backup file is a file in the same directory as the database file\&. It has the same name, with "\&.backup" appended to it\&. If a file of that name already exists, the file will have the suffix "\&.backup\-1"\&. If that exists, the file will be suffixed "\&.backup\-2", and so on)\&. Exit status is 0 if the upgrade is either succesful or aborted by the user, and non\-zero if there is an error\&. .PP When upgrading the database, it is \fIstrongly\fR diff --git a/src/bin/dbutil/b10-dbutil.xml b/src/bin/dbutil/b10-dbutil.xml index 8d7a5894f1..e3f75958fb 100644 --- a/src/bin/dbutil/b10-dbutil.xml +++ b/src/bin/dbutil/b10-dbutil.xml @@ -73,7 +73,10 @@ In check mode (b10-dbutil --check), the utility reads the version of the database schema from the database and prints it. It will tell you whether the schema is at the latest - version supported by BIND 10. + version supported by BIND 10. Exit status is 0 if the schema is at + the correct version, 1 if the schema is at an older version, 2 if + the schema is at a version not yet supported by this version of + b10-dbutil. Any higher value indicates a read or command-line error. @@ -85,7 +88,8 @@ file. It has the same name, with ".backup" appended to it. If a file of that name already exists, the file will have the suffix ".backup-1". If that exists, the file will be suffixed ".backup-2", - and so on.) + and so on). Exit status is 0 if the upgrade is either succesful or + aborted by the user, and non-zero if there is an error. diff --git a/src/bin/dbutil/dbutil.py.in b/src/bin/dbutil/dbutil.py.in index a6e70a67b1..215fe31e66 100755 --- a/src/bin/dbutil/dbutil.py.in +++ b/src/bin/dbutil/dbutil.py.in @@ -47,6 +47,15 @@ isc.util.process.rename() # configure.ac) VERSION = "b10-dbutil 20120319 (BIND 10 @PACKAGE_VERSION@)" +# Exit codes +EXIT_SUCCESS = 0 +EXIT_NEED_UPDATE = 1 +EXIT_VERSION_TOO_HIGH = 2 +EXIT_COMMAND_ERROR = 3 +EXIT_READ_ERROR = 4 +EXIT_UPGRADE_ERROR = 5 + + # @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 @@ -445,6 +454,12 @@ def check_version(db): an upgrade is needed. @param db Database object + + returns 0 if the database is up to date + returns EXIT_NEED_UPDATE if the database needs updating + returns EXIT_VERSION_TOO_HIGH if the database is at a later version + than this program knows about + These return values are intended to be passed on to sys.exit. """ current = get_version(db) latest = get_latest_version() @@ -454,18 +469,18 @@ def check_version(db): info("database version " + version_string(current)) info("this is the latest version of the database schema, " + "no upgrade is required") - + return EXIT_SUCCESS 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") - + return EXIT_NEED_UPDATE 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") - + return EXIT_VERSION_TOO_HIGH def perform_upgrade(db, upgrade): """ @@ -554,11 +569,11 @@ def parse_command(): if (len(args) > 1): error("too many arguments to the command, maximum of one expected") parser.print_usage() - sys.exit(1) + sys.exit(EXIT_COMMAND_ERROR) elif len(args) == 0: error("must supply name of the database file to upgrade") parser.print_usage() - sys.exit(1) + sys.exit(EXIT_COMMAND_ERROR) # Check for conflicting options. If some are found, output a suitable # error message and print the usage. @@ -573,22 +588,23 @@ def parse_command(): # Only get here on conflicting options parser.print_usage() - sys.exit(1) + sys.exit(EXIT_COMMAND_ERROR) if __name__ == "__main__": (options, args) = parse_command() db = Database(args[0], options.verbose) + exit_code = EXIT_SUCCESS if options.check: # Check database - open, report, and close try: db.open() - check_version(db) + exit_code = check_version(db) db.close() except Exception as ex: error("unable to check database version - " + str(ex)) - sys.exit(1) + exit_code = EXIT_READ_ERROR elif options.upgrade: # Upgrade. Check if this is what they really want to do @@ -596,7 +612,7 @@ if __name__ == "__main__": proceed = prompt_user() if not proceed: info("upgrade abandoned - database has not been changed\n") - sys.exit(0) + sys.exit(EXIT_SUCCESS) # It is. Do a backup then do the upgrade. in_progress = False @@ -613,9 +629,9 @@ if __name__ == "__main__": else: error("upgrade preparation failed - " + str(ex)) info("database upgrade was not attempted") - sys.exit(1) + exit_code = EXIT_UPGRADE_ERROR else: error("internal error, neither --check nor --upgrade selected") - sys.exit(1) + exit_code = EXIT_COMMAND_ERROR - sys.exit(0) + sys.exit(exit_code) diff --git a/src/bin/dbutil/tests/dbutil_test.sh.in b/src/bin/dbutil/tests/dbutil_test.sh.in index 26b577c9fd..7e13949041 100755 --- a/src/bin/dbutil/tests/dbutil_test.sh.in +++ b/src/bin/dbutil/tests/dbutil_test.sh.in @@ -269,9 +269,9 @@ record_count_test() { check_version() { copy_file $1 $verfile ../run_dbutil.sh --check $verfile - if [ $? -ne 0 ] + if [ $? -gt 2 ] then - fail "version check failed on database $1" + fail "version check failed on database $1; return code $?" else ../run_dbutil.sh --check $verfile | grep "$2" > /dev/null if [ $? -ne 0 ] From b91bb9719ff759417ce8f6412ca5f8be57a2e19c Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Fri, 23 Mar 2012 13:53:10 +0100 Subject: [PATCH 09/14] [963] fix type in b10-dbutil manpage source --- src/bin/dbutil/b10-dbutil.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/dbutil/b10-dbutil.xml b/src/bin/dbutil/b10-dbutil.xml index e3f75958fb..8178ac8451 100644 --- a/src/bin/dbutil/b10-dbutil.xml +++ b/src/bin/dbutil/b10-dbutil.xml @@ -157,7 +157,7 @@ Enable verbose mode. Each SQL command issued by the - utility will be printed to before it is executed. + utility will be printed to stdout before it is executed. From ab560987671d76c85ce36a09bfc66cf5eb9398dd Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Fri, 23 Mar 2012 16:11:17 +0100 Subject: [PATCH 10/14] [963] use logging framework for b10-dbutil Except for when the user is prompted for Yes or No, all output now goes through the logging framework --- src/bin/dbutil/Makefile.am | 12 +- src/bin/dbutil/b10-dbutil.8 | 2 +- src/bin/dbutil/dbutil.py.in | 117 +++++------------- src/bin/dbutil/dbutil_messages.mes | 117 ++++++++++++++++++ src/bin/dbutil/tests/dbutil_test.sh.in | 4 +- .../isc/log_messages/dbutil_messages.py | 1 + 6 files changed, 166 insertions(+), 87 deletions(-) create mode 100644 src/bin/dbutil/dbutil_messages.mes create mode 100644 src/lib/python/isc/log_messages/dbutil_messages.py diff --git a/src/bin/dbutil/Makefile.am b/src/bin/dbutil/Makefile.am index 5c05757487..001332064d 100644 --- a/src/bin/dbutil/Makefile.am +++ b/src/bin/dbutil/Makefile.am @@ -3,11 +3,16 @@ SUBDIRS = . tests bin_SCRIPTS = b10-dbutil man_MANS = b10-dbutil.8 +nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/dbutil_messages.py +pylogmessagedir = $(pyexecdir)/isc/log_messages/ + EXTRA_DIST = $(man_MANS) b10-dbutil.xml noinst_SCRIPTS = run_dbutil.sh CLEANFILES = b10-dbutil b10-dbutil.pyc +CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/dbutil_messages.py +CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/dbutil_messages.pyc if ENABLE_MAN @@ -16,7 +21,12 @@ b10-dbutil.8: b10-dbutil.xml endif -b10-dbutil: dbutil.py +# Define rule to build logging source files from message file +$(PYTHON_LOGMSGPKG_DIR)/work/dbutil_messages.py : dbutil_messages.mes + $(top_builddir)/src/lib/log/compiler/message \ + -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/dbutil_messages.mes + +b10-dbutil: dbutil.py $(PYTHON_LOGMSGPKG_DIR)/work/dbutil_messages.py $(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \ -e "s|@@SYSCONFDIR@@|@sysconfdir@|" \ -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" dbutil.py >$@ diff --git a/src/bin/dbutil/b10-dbutil.8 b/src/bin/dbutil/b10-dbutil.8 index 4fce304916..d706ee5c64 100644 --- a/src/bin/dbutil/b10-dbutil.8 +++ b/src/bin/dbutil/b10-dbutil.8 @@ -74,7 +74,7 @@ The upgrade function will upgrade a BIND 10 database \- no matter how old the sc .PP \fB\-\-verbose\fR .RS 4 -Enable verbose mode\&. Each SQL command issued by the utility will be printed to before it is executed\&. +Enable verbose mode\&. Each SQL command issued by the utility will be printed to stdout before it is executed\&. .RE .PP \fB\fIdbfile\fR\fR diff --git a/src/bin/dbutil/dbutil.py.in b/src/bin/dbutil/dbutil.py.in index 215fe31e66..3f97df9145 100755 --- a/src/bin/dbutil/dbutil.py.in +++ b/src/bin/dbutil/dbutil.py.in @@ -38,7 +38,11 @@ import sys; sys.path.append("@@PYTHONPATH@@") import os, sqlite3, shutil from optparse import OptionParser import isc.util.process +import isc.log +from isc.log_messages.dbutil_messages import * +isc.log.init("b10-dbutil") +logger = isc.log.Logger("dbutil") isc.util.process.rename() # @brief Version String @@ -55,7 +59,6 @@ EXIT_COMMAND_ERROR = 3 EXIT_READ_ERROR = 4 EXIT_UPGRADE_ERROR = 5 - # @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 @@ -190,55 +193,6 @@ class DbutilException(Exception): """ pass -# Functions for outputting messages in a consistent format. As this is intended -# to be an interactive utility, it was not considered necessary to use the full -# logging framework for messages. - -def output(writer, prefix, text, ex = None): - """ - @brief Write error message to output stream - - @param writer Function to do the writing - @param prefix Prefix to the message - @param text Text to output - @param ex Possible exception holding additiona information - """ - writer(prefix + ": " + text) - if ex is not None: - writer(" - " + str(ex)) - writer("\n") - - -def error(text, ex = None): - """ - @brief Write error message to stderr. - - @param text Text to output - @param ex Possible exception holding additiona information - """ - output(sys.stderr.write, "ERROR", text, ex) - - -def warn(text, ex = None): - """ - @brief Write warning message to stderr. - - @param text Text to output - @param ex Possible exception holding additiona information - """ - output(sys.stderr.write, "WARN", text, ex) - - -def info(text, ex = None): - """ - @brief Write informational message to stdout. - - @param text Text to output - @param ex Possible exception holding additiona information - """ - output(sys.stdout.write, "INFO", text, ex) - - class Database: """ @brief Database Encapsulation @@ -297,12 +251,12 @@ class Database: @param statement SQL statement to execute """ if self.verbose: - sys.stdout.write(statement + "\n") + logger.debug(40, DBUTIL_EXECUTE, statement) try: self.cursor.execute(statement) except Exception as ex: - error("failed to execute " + statement) + logger.error(DBUTIL_STATEMENT_ERROR, statement, ex) raise DbutilException(str(ex)) def result(self): @@ -337,7 +291,7 @@ class Database: # Do the backup shutil.copyfile(self.db_file, self.backup_file) - info("database " + self.db_file + " backed up to " + self.backup_file) + logger.info(DBUTIL_BACKUP, self.db_file, self.backup_file) def prompt_user(): """ @@ -360,7 +314,7 @@ BIND 10 is not running before continuing. no_entered = False while (not yes_entered) and (not no_entered): sys.stdout.write("Enter 'Yes' to proceed with the upgrade, " + - "'No' to exit the program: ") + "'No' to exit the program: \n") response = sys.stdin.readline() if response.lower() == "yes\n": yes_entered = True @@ -466,20 +420,18 @@ def check_version(db): 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") + logger.info(DBUTIL_VERSION_CURRENT, version_string(current)) + logger.info(DBUTIL_CHECK_OK) return EXIT_SUCCESS 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") + logger.info(DBUTIL_VERSION_LOW, version_string(current), + version_string(latest)) + logger.info(DBUTIL_CHECK_UPGRADE_NEEDED) return EXIT_NEED_UPDATE 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") + logger.warn(DBUTIL_VERSION_HIGH, version_string(current), + version_string(get_latest_version())) + logger.info(DBUTIL_UPGRADE_DBUTIL) return EXIT_VERSION_TOO_HIGH def perform_upgrade(db, upgrade): @@ -492,10 +444,8 @@ def perform_upgrade(db, upgrade): @param db Database object @param upgrade Upgrade dictionary, holding "from", "to" and "statements". """ - increment = (version_string(upgrade['from']) + " to " + - version_string(upgrade['to'])) - action = "upgrading database from " + increment - info(action) + logger.info(DBUTIL_UPGRADING, version_string(upgrade['from']), + version_string(upgrade['to'])) for statement in upgrade['statements']: db.execute(statement) @@ -516,10 +466,10 @@ def perform_all_upgrades(db): """ match = compare_versions(get_version(db), get_latest_version()) if match == 0: - info("database already at latest version, no upgrade necessary") + logger.info(DBUTIL_UPGRADE_NOT_NEEDED) elif match > 0: - warn("database at a later version than this utility can support") + logger.warn(DBUTIL_UPGRADE_NOT_POSSIBLE) else: # Work our way through all upgrade increments @@ -530,7 +480,7 @@ def perform_all_upgrades(db): count = count + 1 if count > 0: - info("database upgrade successfully completed") + logger.info(DBUTIL_UPGRADE_SUCCESFUL) else: # Should not get here, as we established earlier that the database # was not at the latest version so we should have upgraded. @@ -567,22 +517,22 @@ def parse_command(): # Set the database file on which to operate if (len(args) > 1): - error("too many arguments to the command, maximum of one expected") + logger.error(DBUTIL_TOO_MANY_ARGUMENTS) parser.print_usage() sys.exit(EXIT_COMMAND_ERROR) elif len(args) == 0: - error("must supply name of the database file to upgrade") + logger.error(DBUTIL_NO_FILE) parser.print_usage() sys.exit(EXIT_COMMAND_ERROR) # Check for conflicting options. If some are found, output a suitable # error message and print the usage. if options.check and options.upgrade: - error("--upgrade is not compatible with --check") + logger.error(DBUTIL_COMMAND_UPGRADE_CHECK) elif (not options.check) and (not options.upgrade): - error("must select one of --check or --upgrade") + logger.error(DBUTIL_COMMAND_NONE) elif (options.check and options.noconfirm): - error("--noconfirm is not compatible with --check") + logger.error(DBUTIL_CHECK_NOCONFIRM) else: return (options, args) @@ -596,6 +546,7 @@ if __name__ == "__main__": db = Database(args[0], options.verbose) exit_code = EXIT_SUCCESS + logger.info(DBUTIL_FILE, args[0]) if options.check: # Check database - open, report, and close try: @@ -603,7 +554,7 @@ if __name__ == "__main__": exit_code = check_version(db) db.close() except Exception as ex: - error("unable to check database version - " + str(ex)) + logger.error(DBUTIL_CHECK_ERROR, ex) exit_code = EXIT_READ_ERROR elif options.upgrade: @@ -611,7 +562,7 @@ if __name__ == "__main__": if not options.noconfirm: proceed = prompt_user() if not proceed: - info("upgrade abandoned - database has not been changed\n") + logger.info(DBUTIL_UPGRADE_CANCELED) sys.exit(EXIT_SUCCESS) # It is. Do a backup then do the upgrade. @@ -624,14 +575,14 @@ if __name__ == "__main__": db.close() except Exception as ex: if in_progress: - error("upgrade failed - " + str(ex)) - warn("database may be corrupt, restore it from backup") + logger.error(DBUTIL_UPGRADE_FAILED, ex) + logger.warn(DBUTIL_DATABASE_MAY_BE_CORRUPTED) else: - error("upgrade preparation failed - " + str(ex)) - info("database upgrade was not attempted") + logger.error(DBUTIL_UPGRADE_PREPARATION_FAILED, ex) + logger.info(DBUTIL_UPGRADE_NOT_ATTEMPTED) exit_code = EXIT_UPGRADE_ERROR else: - error("internal error, neither --check nor --upgrade selected") + logger.error(DBUTIL_NO_ACTION_SPECIFIED) exit_code = EXIT_COMMAND_ERROR sys.exit(exit_code) diff --git a/src/bin/dbutil/dbutil_messages.mes b/src/bin/dbutil/dbutil_messages.mes new file mode 100644 index 0000000000..f5496b08b0 --- /dev/null +++ b/src/bin/dbutil/dbutil_messages.mes @@ -0,0 +1,117 @@ +# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC 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. + +# No namespace declaration - these constants go in the global namespace +# of the ddns messages python module. + +# When you add a message to this file, it is a good idea to run +# /tools/reorder_message_file.py to make sure the +# messages are in the correct order. + +% DBUTIL_BACKUP created backup of %1 in %2 +A backup for the given database file was created. Same of original file and +backup are given in the output message. + +% DBUTIL_CHECK_ERROR unable to check database version: %1 +There was an error while trying to check the current version of the database +schema. The error is shown in the message. + +% DBUTIL_CHECK_NOCONFIRM --noconfirm is not compatible with --check +b10-dbutil was called with --check and --noconfirm. --noconfirm only has +meaning with --upgrade, so this is considered an error. + +% DBUTIL_CHECK_OK this is the latest version of the database schema. No upgrade is required +The database schema version has been checked, and is up to date. +No action is required. + +% DBUTIL_CHECK_UPGRADE_NEEDED re-run this program with the --upgrade switch to upgrade +The database schema version is not up to date, and an update is required. +Please run the dbutil tool again, with the --upgrade argument. + +% DBUTIL_COMMAND_NONE must select one of --check or --upgrade +b10-dbutil was called with neither --check nor --upgrade. One action must be +provided. + +% DBUTIL_COMMAND_UPGRADE_CHECK --upgrade is not compatible with --check +b10-dbutil was called with both the commands --upgrade and --check. Only one +action can be performed at a time. + +% DBUTIL_DATABASE_MAY_BE_CORRUPTED database may be corrupt, restore it from backup +The upgrade failed while it was in progress; the database may now be in an +inconsistent state, and it is advised to restore it from the backup that was +created when b10-dbutil started. + +% DBUTIL_EXECUTE Executing SQL statement: %1 +Debug message; the given SQL statement is executed + +% DBUTIL_FILE Database file: %1 +The database file that is being checked. + +% DBUTIL_NO_ACTION_SPECIFIED Command error: neither --check nor --upgrade selected +b10-dbutil was called without either --check or --upgrade. + +% DBUTIL_NO_FILE must supply name of the database file to upgrade +b10-dbutil was called without a database file. Currently, it cannot find this +file on its own, and it must be provided. + +% DBUTIL_STATEMENT_ERROR failed to execute %1: %2 +The given database statement failed to execute. The error is shown in the +message. + +% DBUTIL_TOO_MANY_ARGUMENTS too many arguments to the command, maximum of one expected +There were too many command-line arguments to b10-dbutil + +% DBUTIL_UPGRADE_CANCELED upgrade canceled; database has not been changed +The user aborted the upgrade, and b10-dbutil will now exit. + +% DBUTIL_UPGRADE_DBUTIL please get the latest version of b10-dbutil and re-run +A database schema was found that was newer than this version of dbutil, which +is apparently out of date and should be upgraded itself. + +% DBUTIL_UPGRADE_FAILED upgrade failed: %1 +While the upgrade was in progress, an unexpected error occurred. The error +is shown in the message. + +% DBUTIL_UPGRADE_NOT_ATTEMPTED database upgrade was not attempted +Due to the earlier failure, the database schema upgrade was not attempted, +and b10-dbutil will now exit. + +% DBUTIL_UPGRADE_NOT_NEEDED database already at latest version, no upgrade necessary +b10-dbutil was told to upgrade the database schema, but it is already at the +latest version. + +% DBUTIL_UPGRADE_NOT_POSSIBLE database at a later version than this utility can support +b10-dbutil was told to upgrade the database schema, but it is at a higher +version than this tool currently supports. Please update b10-dbutil and try +again. + +% DBUTIL_UPGRADE_PREPARATION_FAILED upgrade preparation failed: %1 +An unexpected error occurred while b10-dbutil was preparing to upgrade the +database schema. The error is shown in the message + +% DBUTIL_UPGRADE_SUCCESFUL database upgrade successfully completed +The database schema update was completed successfully. + +% DBUTIL_UPGRADING upgrading database from %1 to %2 +An upgrade is in progress, the versions of the current upgrade action are shown. + +% DBUTIL_VERSION_CURRENT database version %1 +The current version of the database schema. + +% DBUTIL_VERSION_HIGH database is at a later version (%1) than this program can cope with (%2) +The database schema is at a higher version than b10-dbutil knows about. + +% DBUTIL_VERSION_LOW database version %1, latest version is %2. +The database schema is not up to date, the current version and the latest +version are in the message. diff --git a/src/bin/dbutil/tests/dbutil_test.sh.in b/src/bin/dbutil/tests/dbutil_test.sh.in index 7e13949041..2a68777143 100755 --- a/src/bin/dbutil/tests/dbutil_test.sh.in +++ b/src/bin/dbutil/tests/dbutil_test.sh.in @@ -273,10 +273,10 @@ check_version() { then fail "version check failed on database $1; return code $?" else - ../run_dbutil.sh --check $verfile | grep "$2" > /dev/null + ../run_dbutil.sh --check $verfile 2>&1 | grep "$2" > /dev/null if [ $? -ne 0 ] then - fail "database $1 not at expected version $2" + fail "database $1 not at expected version $2 (output: $?)" else succeed fi diff --git a/src/lib/python/isc/log_messages/dbutil_messages.py b/src/lib/python/isc/log_messages/dbutil_messages.py new file mode 100644 index 0000000000..c06dfef6cf --- /dev/null +++ b/src/lib/python/isc/log_messages/dbutil_messages.py @@ -0,0 +1 @@ +from work.dbutil_messages import * From 5e70d900e46fb148acfebf8c2aa068159e58d91b Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Fri, 23 Mar 2012 16:31:38 +0100 Subject: [PATCH 11/14] [963] do --verbose through log (re)init --- src/bin/dbutil/Makefile.am | 2 +- src/bin/dbutil/dbutil.py.in | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/bin/dbutil/Makefile.am b/src/bin/dbutil/Makefile.am index 001332064d..f328cb0fef 100644 --- a/src/bin/dbutil/Makefile.am +++ b/src/bin/dbutil/Makefile.am @@ -6,7 +6,7 @@ man_MANS = b10-dbutil.8 nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/dbutil_messages.py pylogmessagedir = $(pyexecdir)/isc/log_messages/ -EXTRA_DIST = $(man_MANS) b10-dbutil.xml +EXTRA_DIST = $(man_MANS) b10-dbutil.xml dbutil_messages.mes noinst_SCRIPTS = run_dbutil.sh diff --git a/src/bin/dbutil/dbutil.py.in b/src/bin/dbutil/dbutil.py.in index 3f97df9145..de5c4bdc39 100755 --- a/src/bin/dbutil/dbutil.py.in +++ b/src/bin/dbutil/dbutil.py.in @@ -45,6 +45,8 @@ isc.log.init("b10-dbutil") logger = isc.log.Logger("dbutil") isc.util.process.rename() +TRACE_BASIC = logger.DBGLVL_TRACE_BASIC + # @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 @@ -200,19 +202,16 @@ class Database: 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): + def __init__(self, db_file): """ @brief Constructor @param db_file Name of the database file - @param verbose If True, print all SQL statements to stdout before - executing them. """ self.connection = None self.cursor = None self.db_file = db_file self.backup_file = None - self.verbose = verbose def open(self): """ @@ -244,14 +243,11 @@ class Database: """ @brief Execute Statement - Executes the given statement, exiting the program on error. If - verbose mode is set, the statement is printed to stdout before - execution. + Executes the given statement, exiting the program on error. @param statement SQL statement to execute """ - if self.verbose: - logger.debug(40, DBUTIL_EXECUTE, statement) + logger.debug(TRACE_BASIC, DBUTIL_EXECUTE, statement) try: self.cursor.execute(statement) @@ -543,7 +539,12 @@ def parse_command(): if __name__ == "__main__": (options, args) = parse_command() - db = Database(args[0], options.verbose) + + if options.verbose: + isc.log.init("b10-dbutil", "DEBUG", 99) + logger = isc.log.Logger("dbutil") + + db = Database(args[0]) exit_code = EXIT_SUCCESS logger.info(DBUTIL_FILE, args[0]) From 22a8be53ada3f90b8aa226f08081d579b377784a Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Fri, 23 Mar 2012 16:46:17 +0100 Subject: [PATCH 12/14] [963] add --quiet mode to b10-dbutil --- src/bin/dbutil/b10-dbutil.8 | 9 +++++++-- src/bin/dbutil/b10-dbutil.xml | 13 +++++++++++++ src/bin/dbutil/dbutil.py.in | 8 ++++++++ src/bin/dbutil/tests/dbutil_test.sh.in | 6 ++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/bin/dbutil/b10-dbutil.8 b/src/bin/dbutil/b10-dbutil.8 index d706ee5c64..ff7a2e5cfa 100644 --- a/src/bin/dbutil/b10-dbutil.8 +++ b/src/bin/dbutil/b10-dbutil.8 @@ -31,9 +31,9 @@ b10-dbutil \- Zone Database Maintenance Utility .SH "SYNOPSIS" .HP \w'\fBb10\-dbutil\ \-\-check\fR\ 'u -\fBb10\-dbutil \-\-check\fR [\-\-verbose] [\fIdbfile\fR] +\fBb10\-dbutil \-\-check\fR [\-\-verbose] [\-\-quiet] [\fIdbfile\fR] .HP \w'\fBb10\-dbutil\ \-\-upgrade\fR\ 'u -\fBb10\-dbutil \-\-upgrade\fR [\-\-noconfirm] [\-\-verbose] [\fIdbfile\fR] +\fBb10\-dbutil \-\-upgrade\fR [\-\-noconfirm] [\-\-verbose] [\-\-quiet] [\fIdbfile\fR] .SH "DESCRIPTION" .PP The @@ -77,6 +77,11 @@ The upgrade function will upgrade a BIND 10 database \- no matter how old the sc Enable verbose mode\&. Each SQL command issued by the utility will be printed to stdout before it is executed\&. .RE .PP +\fB\-\-quiet\fR +.RS 4 +Enable quiet mode\&. No output is printed, except errors during command\-line argument parsing, or the user confirmation dialog\&. +.RE +.PP \fB\fIdbfile\fR\fR .RS 4 Name of the database file to check of upgrade\&. diff --git a/src/bin/dbutil/b10-dbutil.xml b/src/bin/dbutil/b10-dbutil.xml index 8178ac8451..d1ce2fcad8 100644 --- a/src/bin/dbutil/b10-dbutil.xml +++ b/src/bin/dbutil/b10-dbutil.xml @@ -45,12 +45,14 @@ b10-dbutil --check --verbose + --quiet dbfile b10-dbutil --upgrade --noconfirm --verbose + --quiet dbfile @@ -161,6 +163,17 @@ + + + + + + Enable quiet mode. No output is printed, except errors during + command-line argument parsing, or the user confirmation dialog. + + + + diff --git a/src/bin/dbutil/dbutil.py.in b/src/bin/dbutil/dbutil.py.in index de5c4bdc39..a5e12ad67d 100755 --- a/src/bin/dbutil/dbutil.py.in +++ b/src/bin/dbutil/dbutil.py.in @@ -509,6 +509,9 @@ def parse_command(): parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="Print SQL statements as they are executed") + parser.add_option("-q", "--quiet", action="store_true", + dest="quiet", default=False, + help="Don't print any info, warnings or errors") (options, args) = parser.parse_args() # Set the database file on which to operate @@ -543,6 +546,11 @@ if __name__ == "__main__": if options.verbose: isc.log.init("b10-dbutil", "DEBUG", 99) logger = isc.log.Logger("dbutil") + elif options.quiet: + # We don't use FATAL, so setting the logger to use + # it should essentially make it silent. + isc.log.init("b10-dbutil", "FATAL") + logger = isc.log.Logger("dbutil") db = Database(args[0]) exit_code = EXIT_SUCCESS diff --git a/src/bin/dbutil/tests/dbutil_test.sh.in b/src/bin/dbutil/tests/dbutil_test.sh.in index 2a68777143..393d6e06f7 100755 --- a/src/bin/dbutil/tests/dbutil_test.sh.in +++ b/src/bin/dbutil/tests/dbutil_test.sh.in @@ -443,6 +443,12 @@ copy_file $testdata/old_v1.sqlite3 $tempfile passzero $? rm -f $tempfile $backupfile +echo "13.3 quiet flag" +copy_file $testdata/old_v1.sqlite3 $tempfile +../run_dbutil.sh --check --quiet $tempfile 2>&1 | grep dbutil +failzero $? +rm -f $tempfile $backupfile + echo "13.3 Interactive prompt - yes" copy_file $testdata/old_v1.sqlite3 $tempfile ../run_dbutil.sh --upgrade $tempfile << . From 46f994f729d0f554ad4f58abc82acbc7b526ffc5 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Fri, 23 Mar 2012 17:09:25 +0100 Subject: [PATCH 13/14] [963] forgot EXTRA_DIST line --- src/lib/python/isc/log_messages/Makefile.am | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/python/isc/log_messages/Makefile.am b/src/lib/python/isc/log_messages/Makefile.am index 4b7e1d1422..552f5d9e0a 100644 --- a/src/lib/python/isc/log_messages/Makefile.am +++ b/src/lib/python/isc/log_messages/Makefile.am @@ -14,6 +14,7 @@ EXTRA_DIST += config_messages.py EXTRA_DIST += notify_out_messages.py EXTRA_DIST += libxfrin_messages.py EXTRA_DIST += server_common_messages.py +EXTRA_DIST += dbutil_messages.py CLEANFILES = __init__.pyc CLEANFILES += bind10_messages.pyc From a7254a232bedb445f4c7dd47b8f623a94f7056b1 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 26 Mar 2012 16:08:39 +0200 Subject: [PATCH 14/14] [963] addressed review comments documentation changes different exit status on uncaught exceptions removed unused log message updated 'corrupt' log message improved 'quiet' test case --- src/bin/dbutil/b10-dbutil.8 | 4 +-- src/bin/dbutil/b10-dbutil.xml | 5 ++-- src/bin/dbutil/dbutil.py.in | 35 +++++++++++++++++--------- src/bin/dbutil/dbutil_messages.mes | 5 +--- src/bin/dbutil/tests/dbutil_test.sh.in | 2 +- 5 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/bin/dbutil/b10-dbutil.8 b/src/bin/dbutil/b10-dbutil.8 index ff7a2e5cfa..437a69d1ec 100644 --- a/src/bin/dbutil/b10-dbutil.8 +++ b/src/bin/dbutil/b10-dbutil.8 @@ -44,7 +44,7 @@ utility is a general administration utility for SQL databases\&. (Currently only \fBb10\-dbutil\fR operates in one of two modes, check mode or upgrade mode\&. .PP -In check mode (\fBb10\-dbutil \-\-check\fR), the utility reads the version of the database schema from the database and prints it\&. It will tell you whether the schema is at the latest version supported by BIND 10\&. Exit status is 0 if the schema is at the correct version, 1 if the schema is at an older version, 2 if the schema is at a version not yet supported by this version of b10\-dbutil\&. Any higher value indicates a read or command\-line error\&. +In check mode (\fBb10\-dbutil \-\-check\fR), the utility reads the version of the database schema from the database and prints it\&. It will tell you whether the schema is at the latest version supported by BIND 10\&. Exit status is 0 if the schema is at the correct version, 1 if the schema is at an older version, 2 if the schema is at a version not yet supported by this version of b10\-dbutil\&. Any higher value indicates an error during command\-line parsing or execution\&. .PP When the upgrade function is selected (\fBb10\-dbutil \-\-upgrade\fR), the utility takes a copy of the database, then upgrades it to the latest version of the schema\&. The contents of the database remain intact\&. (The backup file is a file in the same directory as the database file\&. It has the same name, with "\&.backup" appended to it\&. If a file of that name already exists, the file will have the suffix "\&.backup\-1"\&. If that exists, the file will be suffixed "\&.backup\-2", and so on)\&. Exit status is 0 if the upgrade is either succesful or aborted by the user, and non\-zero if there is an error\&. .PP @@ -74,7 +74,7 @@ The upgrade function will upgrade a BIND 10 database \- no matter how old the sc .PP \fB\-\-verbose\fR .RS 4 -Enable verbose mode\&. Each SQL command issued by the utility will be printed to stdout before it is executed\&. +Enable verbose mode\&. Each SQL command issued by the utility will be printed to stderr before it is executed\&. .RE .PP \fB\-\-quiet\fR diff --git a/src/bin/dbutil/b10-dbutil.xml b/src/bin/dbutil/b10-dbutil.xml index d1ce2fcad8..c1c0dee1ca 100644 --- a/src/bin/dbutil/b10-dbutil.xml +++ b/src/bin/dbutil/b10-dbutil.xml @@ -78,7 +78,8 @@ version supported by BIND 10. Exit status is 0 if the schema is at the correct version, 1 if the schema is at an older version, 2 if the schema is at a version not yet supported by this version of - b10-dbutil. Any higher value indicates a read or command-line error. + b10-dbutil. Any higher value indicates an error during command-line + parsing or execution. @@ -159,7 +160,7 @@ Enable verbose mode. Each SQL command issued by the - utility will be printed to stdout before it is executed. + utility will be printed to stderr before it is executed. diff --git a/src/bin/dbutil/dbutil.py.in b/src/bin/dbutil/dbutil.py.in index a5e12ad67d..81f351e7ad 100755 --- a/src/bin/dbutil/dbutil.py.in +++ b/src/bin/dbutil/dbutil.py.in @@ -34,7 +34,27 @@ The is the database name with ".backup" appended to it (or ".backup-n" if upgrade fails. """ +# Exit codes +# These are defined here because one of them is already used before most +# of the import statements. +EXIT_SUCCESS = 0 +EXIT_NEED_UPDATE = 1 +EXIT_VERSION_TOO_HIGH = 2 +EXIT_COMMAND_ERROR = 3 +EXIT_READ_ERROR = 4 +EXIT_UPGRADE_ERROR = 5 +EXIT_UNCAUGHT_EXCEPTION = 6 + import sys; sys.path.append("@@PYTHONPATH@@") + +# Normally, python exits with a status code of 1 on uncaught exceptions +# Since we reserve exit status 1 for 'database needs upgrade', we +# override the excepthook to exit with a different status +def my_except_hook(a, b, c): + sys.__excepthook__(a,b,c) + sys.exit(EXIT_UNCAUGHT_EXCEPTION) +sys.excepthook = my_except_hook + import os, sqlite3, shutil from optparse import OptionParser import isc.util.process @@ -47,20 +67,13 @@ isc.util.process.rename() TRACE_BASIC = logger.DBGLVL_TRACE_BASIC + # @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@)" -# Exit codes -EXIT_SUCCESS = 0 -EXIT_NEED_UPDATE = 1 -EXIT_VERSION_TOO_HIGH = 2 -EXIT_COMMAND_ERROR = 3 -EXIT_READ_ERROR = 4 -EXIT_UPGRADE_ERROR = 5 - # @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 @@ -585,13 +598,11 @@ if __name__ == "__main__": except Exception as ex: if in_progress: logger.error(DBUTIL_UPGRADE_FAILED, ex) - logger.warn(DBUTIL_DATABASE_MAY_BE_CORRUPTED) + logger.warn(DBUTIL_DATABASE_MAY_BE_CORRUPT, db.db_file, + db.backup_file) else: logger.error(DBUTIL_UPGRADE_PREPARATION_FAILED, ex) logger.info(DBUTIL_UPGRADE_NOT_ATTEMPTED) exit_code = EXIT_UPGRADE_ERROR - else: - logger.error(DBUTIL_NO_ACTION_SPECIFIED) - exit_code = EXIT_COMMAND_ERROR sys.exit(exit_code) diff --git a/src/bin/dbutil/dbutil_messages.mes b/src/bin/dbutil/dbutil_messages.mes index f5496b08b0..90ede92283 100644 --- a/src/bin/dbutil/dbutil_messages.mes +++ b/src/bin/dbutil/dbutil_messages.mes @@ -47,7 +47,7 @@ provided. b10-dbutil was called with both the commands --upgrade and --check. Only one action can be performed at a time. -% DBUTIL_DATABASE_MAY_BE_CORRUPTED database may be corrupt, restore it from backup +% DBUTIL_DATABASE_MAY_BE_CORRUPT database file %1 may be corrupt, restore it from backup (%2) The upgrade failed while it was in progress; the database may now be in an inconsistent state, and it is advised to restore it from the backup that was created when b10-dbutil started. @@ -58,9 +58,6 @@ Debug message; the given SQL statement is executed % DBUTIL_FILE Database file: %1 The database file that is being checked. -% DBUTIL_NO_ACTION_SPECIFIED Command error: neither --check nor --upgrade selected -b10-dbutil was called without either --check or --upgrade. - % DBUTIL_NO_FILE must supply name of the database file to upgrade b10-dbutil was called without a database file. Currently, it cannot find this file on its own, and it must be provided. diff --git a/src/bin/dbutil/tests/dbutil_test.sh.in b/src/bin/dbutil/tests/dbutil_test.sh.in index 393d6e06f7..92c5953f94 100755 --- a/src/bin/dbutil/tests/dbutil_test.sh.in +++ b/src/bin/dbutil/tests/dbutil_test.sh.in @@ -445,7 +445,7 @@ rm -f $tempfile $backupfile echo "13.3 quiet flag" copy_file $testdata/old_v1.sqlite3 $tempfile -../run_dbutil.sh --check --quiet $tempfile 2>&1 | grep dbutil +../run_dbutil.sh --check --quiet $tempfile 2>&1 | grep . failzero $? rm -f $tempfile $backupfile