diff --git a/configure.ac b/configure.ac index 6e3b9d8d53..3acbca6446 100644 --- a/configure.ac +++ b/configure.ac @@ -1015,6 +1015,9 @@ 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/dbutil/tests/testdata/Makefile src/bin/host/Makefile src/bin/loadzone/Makefile src/bin/loadzone/tests/correct/Makefile @@ -1028,8 +1031,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 @@ -1143,6 +1146,9 @@ 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/run_dbutil.sh + src/bin/dbutil/tests/dbutil_test.sh src/bin/ddns/ddns.py src/bin/xfrin/tests/xfrin_test src/bin/xfrin/xfrin.py @@ -1226,6 +1232,8 @@ 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 chmod +x src/bin/zonemgr/tests/zonemgr_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 new file mode 100644 index 0000000000..f328cb0fef --- /dev/null +++ b/src/bin/dbutil/Makefile.am @@ -0,0 +1,38 @@ +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 dbutil_messages.mes + +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 + +b10-dbutil.8: b10-dbutil.xml + xsltproc --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-dbutil.xml + +endif + +# 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 >$@ + chmod a+x $@ + +CLEANDIRS = __pycache__ + +clean-local: + rm -rf $(CLEANDIRS) diff --git a/src/bin/dbutil/b10-dbutil.8 b/src/bin/dbutil/b10-dbutil.8 new file mode 100644 index 0000000000..437a69d1ec --- /dev/null +++ b/src/bin/dbutil/b10-dbutil.8 @@ -0,0 +1,92 @@ +'\" 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] [\-\-quiet] [\fIdbfile\fR] +.HP \w'\fBb10\-dbutil\ \-\-upgrade\fR\ 'u +\fBb10\-dbutil \-\-upgrade\fR [\-\-noconfirm] [\-\-verbose] [\-\-quiet] [\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\&. 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 +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 stderr 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\&. +.RE +.SH "COPYRIGHT" +.br +Copyright \(co 2012 Internet Systems Consortium, Inc. ("ISC") +.br diff --git a/src/bin/dbutil/b10-dbutil.xml b/src/bin/dbutil/b10-dbutil.xml new file mode 100644 index 0000000000..c1c0dee1ca --- /dev/null +++ b/src/bin/dbutil/b10-dbutil.xml @@ -0,0 +1,192 @@ +]> + + + + + + March 20, 2012 + + + + b10-dbutil + 8 + BIND10 + + + + b10-dbutil + Zone Database Maintenance Utility + + + + + 2012 + Internet Systems Consortium, Inc. ("ISC") + + + + + + b10-dbutil --check + --verbose + --quiet + dbfile + + + b10-dbutil --upgrade + --noconfirm + --verbose + --quiet + 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. 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. + + + + 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). Exit status is 0 if the upgrade is either succesful or + aborted by the user, and non-zero if there is an error. + + + + 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 stderr before it is executed. + + + + + + + + + Enable quiet mode. No output is printed, except errors during + command-line argument parsing, or the user confirmation dialog. + + + + + + + + + + + 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 new file mode 100755 index 0000000000..81f351e7ad --- /dev/null +++ b/src/bin/dbutil/dbutil.py.in @@ -0,0 +1,608 @@ +#!@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 [--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. +""" + +# 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 +import isc.log +from isc.log_messages.dbutil_messages import * + +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 +# configure.ac) +VERSION = "b10-dbutil 20120319 (BIND 10 @PACKAGE_VERSION@)" + +# @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: +# +# 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. +] + +class DbutilException(Exception): + """ + @brief Exception class to indicate error exit + """ + pass + +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): + """ + @brief Constructor + + @param db_file Name of the database file + """ + self.connection = None + self.cursor = None + self.db_file = db_file + self.backup_file = None + + 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): + """ + @brief Execute Statement + + Executes the given statement, exiting the program on error. + + @param statement SQL statement to execute + """ + logger.debug(TRACE_BASIC, DBUTIL_EXECUTE, statement) + + try: + self.cursor.execute(statement) + except Exception as ex: + logger.error(DBUTIL_STATEMENT_ERROR, statement, ex) + raise DbutilException(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) + logger.info(DBUTIL_BACKUP, self.db_file, 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 zone database to the latest version. + +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 continuing. +""") + 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: \n") + 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 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 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'] + + +def get_version(db): + """ + @brief Return version of database + + @return Version of database in form (major version, minor version) + """ + + # 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 + # assume a minor version number of 0. + minor = 0 + 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 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 + + 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() + + match = compare_versions(current, latest) + if match == 0: + logger.info(DBUTIL_VERSION_CURRENT, version_string(current)) + logger.info(DBUTIL_CHECK_OK) + return EXIT_SUCCESS + elif match < 0: + logger.info(DBUTIL_VERSION_LOW, version_string(current), + version_string(latest)) + logger.info(DBUTIL_CHECK_UPGRADE_NEEDED) + return EXIT_NEED_UPDATE + else: + 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): + """ + @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". + """ + logger.info(DBUTIL_UPGRADING, version_string(upgrade['from']), + version_string(upgrade['to'])) + for statement in upgrade['statements']: + db.execute(statement) + + # Update the version information + db.execute("DELETE FROM schema_version") + db.execute("INSERT INTO schema_version VALUES (" + + str(upgrade['to'][0]) + "," + str(upgrade['to'][1]) + ")") + + +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. + """ + match = compare_versions(get_version(db), get_latest_version()) + if match == 0: + logger.info(DBUTIL_UPGRADE_NOT_NEEDED) + + elif match > 0: + logger.warn(DBUTIL_UPGRADE_NOT_POSSIBLE) + + else: + # Work our way through all upgrade increments + count = 0 + for upgrade in UPGRADES: + if compare_versions(get_version(db), upgrade['from']) == 0: + perform_upgrade(db, upgrade) + count = count + 1 + + if count > 0: + 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. + raise DbutilException("internal error in upgrade tool - no " + + "upgrade was performed on an old version " + + "the database") + + +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, version = VERSION) + 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") + 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 + if (len(args) > 1): + logger.error(DBUTIL_TOO_MANY_ARGUMENTS) + parser.print_usage() + sys.exit(EXIT_COMMAND_ERROR) + elif len(args) == 0: + 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: + logger.error(DBUTIL_COMMAND_UPGRADE_CHECK) + elif (not options.check) and (not options.upgrade): + logger.error(DBUTIL_COMMAND_NONE) + elif (options.check and options.noconfirm): + logger.error(DBUTIL_CHECK_NOCONFIRM) + else: + return (options, args) + + # Only get here on conflicting options + parser.print_usage() + sys.exit(EXIT_COMMAND_ERROR) + + +if __name__ == "__main__": + (options, args) = parse_command() + + 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 + + logger.info(DBUTIL_FILE, args[0]) + if options.check: + # Check database - open, report, and close + try: + db.open() + exit_code = check_version(db) + db.close() + except Exception as ex: + logger.error(DBUTIL_CHECK_ERROR, ex) + exit_code = EXIT_READ_ERROR + + elif options.upgrade: + # Upgrade. Check if this is what they really want to do + if not options.noconfirm: + proceed = prompt_user() + if not proceed: + logger.info(DBUTIL_UPGRADE_CANCELED) + sys.exit(EXIT_SUCCESS) + + # 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: + logger.error(DBUTIL_UPGRADE_FAILED, ex) + 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 + + 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..90ede92283 --- /dev/null +++ b/src/bin/dbutil/dbutil_messages.mes @@ -0,0 +1,114 @@ +# 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_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. + +% 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_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/run_dbutil.sh.in b/src/bin/dbutil/run_dbutil.sh.in new file mode 100755 index 0000000000..fea7482cbb --- /dev/null +++ b/src/bin/dbutil/run_dbutil.sh.in @@ -0,0 +1,40 @@ +#! /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 + +exec ${PYTHON_EXEC} -O ${DBUTIL_PATH}/b10-dbutil "$@" diff --git a/src/bin/dbutil/tests/Makefile.am b/src/bin/dbutil/tests/Makefile.am new file mode 100644 index 0000000000..c03b262aa2 --- /dev/null +++ b/src/bin/dbutil/tests/Makefile.am @@ -0,0 +1,6 @@ +SUBDIRS = . testdata + +# 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..92c5953f94 --- /dev/null +++ b/src/bin/dbutil/tests/dbutil_test.sh.in @@ -0,0 +1,482 @@ +#!/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 [ "$1" != "" ] + 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 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 +# 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_$$ + copy_file $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 +# @param $2 Expected backup file +upgrade_ok_test() { + copy_file $1 $tempfile + ../run_dbutil.sh --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 [ "$expected_schema" = "$actual_schema" ] + then + succeed + else + fail "upgraded schema not as expected" + fi + + # 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 + fi +} + + +# @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. +# +# 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() { + copy_file $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'` + + ../run_dbutil.sh --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() { + copy_file $1 $verfile + ../run_dbutil.sh --check $verfile + if [ $? -gt 2 ] + then + fail "version check failed on database $1; return code $?" + else + ../run_dbutil.sh --check $verfile 2>&1 | grep "$2" > /dev/null + if [ $? -ne 0 ] + then + fail "database $1 not at expected version $2 (output: $?)" + else + succeed + fi + fi + rm -f $verfile +} + + +# @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 + +# Test 1 - check that the utility fails if the database does not exist +echo "1.1. Non-existent database - check" +../run_dbutil.sh --check $tempfile +failzero $? +check_no_backup $tempfile $backupfile + +echo "1.2. Non-existent database - upgrade" +../run_dbutil.sh --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 +check_version_fail $tempfile $backupfile +rm -f $tempfile $backupfile + +echo "2.2. Database is an empty file - upgrade" +touch $tempfile +../run_dbutil.sh --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 +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 +check_backup $tempfile $backupfile +rm -f $tempfile $backupfile + + +echo "4.1. Database is an SQLite3 file without the schema table - check" +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" +upgrade_fail_test $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 $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 $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 $backupfile +rm -f $tempfile $backupfile + + +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.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 ${backupfile}-2 +rm -f $tempfile $backupfile ${backupfile}-1 ${backupfile}-2 + + +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 "13.2 verbose flag" +copy_file $testdata/old_v1.sqlite3 $tempfile +../run_dbutil.sh --upgrade --noconfirm --verbose $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 . +failzero $? +rm -f $tempfile $backupfile + +echo "13.3 Interactive prompt - yes" +copy_file $testdata/old_v1.sqlite3 $tempfile +../run_dbutil.sh --upgrade $tempfile << . +Yes +. +passzero $? +check_version $tempfile "V2.0" +rm -f $tempfile $backupfile + +echo "13.4 Interactive prompt - no" +copy_file $testdata/old_v1.sqlite3 $tempfile +../run_dbutil.sh --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/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 new file mode 100644 index 0000000000..83ce01f550 --- /dev/null +++ b/src/bin/dbutil/tests/testdata/README @@ -0,0 +1,41 @@ +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. + +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. + +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). + +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 0000000000..69683b7477 Binary files /dev/null and b/src/bin/dbutil/tests/testdata/corrupt.sqlite3 differ 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 0000000000..b8031490a9 Binary files /dev/null and b/src/bin/dbutil/tests/testdata/empty_schema.sqlite3 differ 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 0000000000..5ad21363ce Binary files /dev/null and b/src/bin/dbutil/tests/testdata/empty_v1.sqlite3 differ 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 0000000000..b820fa9990 Binary files /dev/null and b/src/bin/dbutil/tests/testdata/empty_version.sqlite3 differ 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 0000000000..e411fd088a Binary files /dev/null and b/src/bin/dbutil/tests/testdata/invalid_v1.sqlite3 differ 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 0000000000..9a885a4816 Binary files /dev/null and b/src/bin/dbutil/tests/testdata/new_v1.sqlite3 differ 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 0000000000..9dd06144e3 Binary files /dev/null and b/src/bin/dbutil/tests/testdata/no_schema.sqlite3 differ 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 0000000000..32dbb9b35b Binary files /dev/null and b/src/bin/dbutil/tests/testdata/old_v1.sqlite3 differ 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 0000000000..5dc8ae343f Binary files /dev/null and b/src/bin/dbutil/tests/testdata/too_many_version.sqlite3 differ 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 0000000000..18784fdea3 Binary files /dev/null and b/src/bin/dbutil/tests/testdata/v2_0.sqlite3 differ diff --git a/src/bin/xfrout/b10-xfrout.8 b/src/bin/xfrout/b10-xfrout.8 index 483e2c05f8..b3200b588f 100644 --- a/src/bin/xfrout/b10-xfrout.8 +++ b/src/bin/xfrout/b10-xfrout.8 @@ -9,6 +9,15 @@ .\" .TH "B10\-XFROUT" "8" "March 16\&. 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 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 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 *