Commit d85eda60 authored by Stephen Morris's avatar Stephen Morris
Browse files

[963] Changes after review.

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