krb5 commit: Add LMDB KDB module tests

Greg Hudson <[email protected]>
Newsgroups gmane.comp.encryption.kerberos.cvs
Message-ID <[email protected]>
https://github.com/krb5/krb5/commit/dafc1daeedb008faa9dc6246e33f37a5c45462ca
commit dafc1daeedb008faa9dc6246e33f37a5c45462ca
Author: Greg Hudson <[email protected]>
Date:   Wed Apr 25 23:50:06 2018 -0400

    Add LMDB KDB module tests
    
    Add a new k5test function multidb_realms() which yields a realm using
    BDB and a realm using LMDB.  Run the tests in t_dump.py, t_iprop.py,
    the lockout tests in t_policy.py, and kdbtest (in t_kdb.py) with both
    databases.
    
    If K5TEST_LMDB is set in the environment, use LMDB for all Python
    tests and all tests in tests/dejagnu, with two exceptions
    (t_unlockiter.py and t_kdb_locking.py) which test BDB-specific
    behavior.  To support those exceptions, add the "bdb_only" realm
    initialization flag.
    
    In t_iprop.py, be sure to properly clean up kpropd daemons, using a
    new k5test realm method stop_kpropd() to stop a kpropd daemon started
    with start_kpropd() and remove it from the realm's list.
    
    ticket: 8674

 src/Makefile.in                            |    6 +-
 src/tests/dejagnu/config/default.exp       |   15 +-
 src/tests/dejagnu/krb-standalone/kprop.exp |    9 +-
 src/tests/t_dump.py                        |  158 ++++----
 src/tests/t_iprop.py                       |  662 ++++++++++++++--------------
 src/tests/t_kdb.py                         |    6 +-
 src/tests/t_kdb_locking.py                 |    2 +-
 src/tests/t_policy.py                      |   48 +-
 src/tests/t_unlockiter.py                  |    4 +-
 src/util/k5test.py                         |   48 ++-
 10 files changed, 518 insertions(+), 440 deletions(-)

diff --git a/src/Makefile.in b/src/Makefile.in
index e2c178f..bd7a2dc 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -490,7 +490,7 @@ check-prerecurse: runenv.py
 	$(RM) $(SKIPTESTS)
 	touch $(SKIPTESTS)
 
-check-unix:
+check-unix: check-lmdb-$(HAVE_LMDB)
 	cat $(SKIPTESTS)
 
 check-pytests-no: check-postrecurse
@@ -501,6 +501,10 @@ check-cmocka-no: check-postrecurse
 	@echo 'Skipped cmocka tests: cmocka library or header not found' >> \
 		$(SKIPTESTS)
 
+check-lmdb-yes:
+check-lmdb-no:
+	@echo 'Skipped LMDB tests: LMDB KDB module not built' >> $(SKIPTESTS)
+
 # Create a test realm and spawn a shell in an environment pointing to it.
 # If CROSSNUM is set, create that many fully connected test realms and
 # point the shell at the first one.
diff --git a/src/tests/dejagnu/config/default.exp b/src/tests/dejagnu/config/default.exp
index 2d1686c..18b8c33 100644
--- a/src/tests/dejagnu/config/default.exp
+++ b/src/tests/dejagnu/config/default.exp
@@ -455,7 +455,8 @@ proc delete_db {} {
     global tmppwd
     # Master and slave db files
     file delete $tmppwd/kdc-db $tmppwd/kdc-db.ok $tmppwd/kdc-db.kadm5 \
-	$tmppwd/kdc-db.kadm5.lock \
+	$tmppwd/kdc-db.kadm5.lock $tmppwd/kdc-db.mdb $tmppwd/kdc-db.mdb-lock \
+	$tmppwd/kdc-db.lockout.mdb $tmppwd/kdc-db.lockout.mdb-lock \
 	$tmppwd/kdc-db.ulog \
 	$tmppwd/slave-db $tmppwd/slave-db.ok $tmppwd/slave-db.kadm5 $tmppwd/slave-db.kadm5.lock \
 	$tmppwd/slave-db~ $tmppwd/slave-db~.ok $tmppwd/slave-db~.kadm5 $tmppwd/slave-db~.kadm5.lock
@@ -933,6 +934,7 @@ proc setup_krb5_conf { {type client} } {
     global mode
     global portbase
     global srcdir
+    global env
 
     set pkinit_certs [findfile "[pwd]/$srcdir/pkinit-certs" "[pwd]/$srcdir/pkinit-certs" "$srcdir/pkinit-certs"]
     # Create a krb5.conf file.
@@ -980,7 +982,7 @@ proc setup_krb5_conf { {type client} } {
 	puts $conffile "		kdc = $hostname:[expr 1 + $portbase]"
 	puts $conffile "		admin_server = $hostname:[expr 4 + $portbase]"
 	puts $conffile "		kpasswd_server = $hostname:[expr 5 + $portbase]"
-	puts $conffile "		database_module = foo_db2"
+	puts $conffile "		database_module = db"
 	puts $conffile "	\}"
 	puts $conffile ""
 	puts $conffile "\[domain_realm\]"
@@ -993,8 +995,13 @@ proc setup_krb5_conf { {type client} } {
 	puts $conffile ""
 	puts $conffile "\[dbmodules\]"
 	puts $conffile "	db_module_dir = $tmppwd/../../../plugins/kdb"
-	puts $conffile "	foo_db2 = {"
-	puts $conffile "		db_library = db2"
+	puts $conffile "	db = {"
+	if [info exists env(K5TEST_LMDB)] {
+	    puts $conffile "		db_library = klmdb"
+	    puts $conffile "		nosync = true"
+	} else {
+	    puts $conffile "		db_library = db2"
+	}
 	puts $conffile "		database_name = $tmppwd/$type-db"
 	puts $conffile "	}"
 	close $conffile
diff --git a/src/tests/dejagnu/krb-standalone/kprop.exp b/src/tests/dejagnu/krb-standalone/kprop.exp
index cc1a26a..f23ebe1 100644
--- a/src/tests/dejagnu/krb-standalone/kprop.exp
+++ b/src/tests/dejagnu/krb-standalone/kprop.exp
@@ -6,7 +6,7 @@
 # processes when the procedure ends.
 
 proc setup_slave {} {
-    global tmppwd hostname REALMNAME
+    global tmppwd hostname REALMNAME env
     file delete $tmppwd/slave-stash $tmppwd/slave-acl
     file copy -force $tmppwd/stash:foo $tmppwd/slave-stash
     file copy -force $tmppwd/acl $tmppwd/slave-acl
@@ -16,7 +16,12 @@ proc setup_slave {} {
 	close $aclfile
     }
     file copy -force $tmppwd/adb.lock $tmppwd/slave-adb.lock
-    foreach suffix { {} .kadm5 .kadm5.lock .ok } {
+    if [info exists env(K5TEST_LMDB)] {
+	set suffixes { .mdb .mdb-lock .lockout.mdb .lockout.mdb-lock }
+    } else {
+	set suffixes { {} .kadm5 .kadm5.lock .ok }
+    }
+    foreach suffix $suffixes {
 	file copy -force $tmppwd/kdc-db$suffix $tmppwd/slave-db$suffix
     }
 }
diff --git a/src/tests/t_dump.py b/src/tests/t_dump.py
index 2a424d9..22cff00 100755
--- a/src/tests/t_dump.py
+++ b/src/tests/t_dump.py
@@ -2,82 +2,12 @@
 from k5test import *
 from filecmp import cmp
 
-# Make sure we can dump and load an ordinary database, and that
-# principals and policies survive a dump/load cycle.
-
-realm = K5Realm(start_kdc=False)
-realm.run([kadminl, 'addpol', 'fred'])
-
-# Create a dump file.
-dumpfile = os.path.join(realm.testdir, 'dump')
-realm.run([kdb5_util, 'dump', dumpfile])
-
-# Write additional policy records to the dump.  Use the 1.8 format for
-# one of them, to test retroactive compatibility (for issue #8213).
-f = open('testdir/dump', 'a')
-f.write('policy	compat	0	0	3	4	5	0	'
-        '0	0	0\n')
-f.write('policy	barney	0	0	1	1	1	0	'
-        '0	0	0	0	0	0	-	1	'
-        '2	28	'
-        'fd100f5064625f6372656174696f6e404b5242544553542e434f4d00\n')
-f.close()
-
-# Destroy and load the database; check that the policies exist.
-# Spot-check principal and policy fields.
-mark('reload after dump')
-realm.run([kdb5_util, 'destroy', '-f'])
-realm.run([kdb5_util, 'load', dumpfile])
-out = realm.run([kadminl, 'getprincs'])
-if realm.user_princ not in out or realm.host_princ not in out:
-    fail('Missing principal after load')
-out = realm.run([kadminl, 'getprinc', realm.user_princ])
-if 'Expiration date: [never]' not in out or 'MKey: vno 1' not in out:
-    fail('Principal has wrong value after load')
-out = realm.run([kadminl, 'getpols'])
-if 'fred\n' not in out or 'barney\n' not in out:
-    fail('Missing policy after load')
-realm.run([kadminl, 'getpol', 'compat'],
-          expected_msg='Number of old keys kept: 5')
-realm.run([kadminl, 'getpol', 'barney'],
-          expected_msg='Number of old keys kept: 1')
-
-# Dump/load again, and make sure everything is still there.
-mark('second reload')
-realm.run([kdb5_util, 'dump', dumpfile])
-realm.run([kdb5_util, 'load', dumpfile])
-out = realm.run([kadminl, 'getprincs'])
-if realm.user_princ not in out or realm.host_princ not in out:
-    fail('Missing principal after load')
-out = realm.run([kadminl, 'getpols'])
-if 'compat\n' not in out or 'fred\n' not in out or 'barney\n' not in out:
-    fail('Missing policy after second load')
-
-srcdumpdir = os.path.join(srctop, 'tests', 'dumpfiles')
-srcdump = os.path.join(srcdumpdir, 'dump')
-srcdump_r18 = os.path.join(srcdumpdir, 'dump.r18')
-srcdump_r13 = os.path.join(srcdumpdir, 'dump.r13')
-srcdump_b7 = os.path.join(srcdumpdir, 'dump.b7')
-srcdump_ov = os.path.join(srcdumpdir, 'dump.ov')
-
-# Load a dump file from the source directory.
-realm.run([kdb5_util, 'destroy', '-f'])
-realm.run([kdb5_util, 'load', srcdump])
-realm.run([kdb5_util, 'stash', '-P', 'master'])
-
 def dump_compare(realm, opt, srcfile):
     mark('dump comparison against %s' % os.path.basename(srcfile))
     realm.run([kdb5_util, 'dump'] + opt + [dumpfile])
     if not cmp(srcfile, dumpfile, False):
         fail('Dump output does not match %s' % srcfile)
 
-# Dump the resulting DB in each non-iprop format and compare with
-# expected outputs.
-dump_compare(realm, [], srcdump)
-dump_compare(realm, ['-r18'], srcdump_r18)
-dump_compare(realm, ['-r13'], srcdump_r13)
-dump_compare(realm, ['-b7'], srcdump_b7)
-dump_compare(realm, ['-ov'], srcdump_ov)
 
 def load_dump_check_compare(realm, opt, srcfile):
     mark('load check from %s' % os.path.basename(srcfile))
@@ -89,15 +19,85 @@ def load_dump_check_compare(realm, opt, srcfile):
     realm.run([kadminl, 'getpols'], expected_msg='testpol')
     dump_compare(realm, opt, srcfile)
 
-# Load each format of dump, check it, re-dump it, and compare.
-load_dump_check_compare(realm, ['-r18'], srcdump_r18)
-load_dump_check_compare(realm, ['-r13'], srcdump_r13)
-load_dump_check_compare(realm, ['-b7'], srcdump_b7)
 
-# Loading the last (-b7 format) dump won't have loaded the
-# per-principal kadm data.  Load that incrementally with -ov.
-realm.run([kadminl, 'getprinc', 'user'], expected_msg='Policy: [none]')
-realm.run([kdb5_util, 'load', '-update', '-ov', srcdump_ov])
-realm.run([kadminl, 'getprinc', 'user'], expected_msg='Policy: testpol')
+for realm in multidb_realms(start_kdc=False):
+
+    # Make sure we can dump and load an ordinary database, and that
+    # principals and policies survive a dump/load cycle.
+
+    realm.run([kadminl, 'addpol', 'fred'])
+
+    # Create a dump file.
+    dumpfile = os.path.join(realm.testdir, 'dump')
+    realm.run([kdb5_util, 'dump', dumpfile])
+
+    # Write additional policy records to the dump.  Use the 1.8 format for
+    # one of them, to test retroactive compatibility (for issue #8213).
+    f = open('testdir/dump', 'a')
+    f.write('policy\tcompat\t0\t0\t3\t4\t5\t0\t0\t0\t0\n')
+    f.write('policy\tbarney\t0\t0\t1\t1\t1\t0\t0\t0\t0\t0\t0\t0\t-\t1\t2\t28\t'
+            'fd100f5064625f6372656174696f6e404b5242544553542e434f4d00\n')
+    f.close()
+
+    # Destroy and load the database; check that the policies exist.
+    # Spot-check principal and policy fields.
+    mark('reload after dump')
+    realm.run([kdb5_util, 'destroy', '-f'])
+    realm.run([kdb5_util, 'load', dumpfile])
+    out = realm.run([kadminl, 'getprincs'])
+    if realm.user_princ not in out or realm.host_princ not in out:
+        fail('Missing principal after load')
+    out = realm.run([kadminl, 'getprinc', realm.user_princ])
+    if 'Expiration date: [never]' not in out or 'MKey: vno 1' not in out:
+        fail('Principal has wrong value after load')
+    out = realm.run([kadminl, 'getpols'])
+    if 'fred\n' not in out or 'barney\n' not in out:
+        fail('Missing policy after load')
+    realm.run([kadminl, 'getpol', 'compat'],
+              expected_msg='Number of old keys kept: 5')
+    realm.run([kadminl, 'getpol', 'barney'],
+              expected_msg='Number of old keys kept: 1')
+
+    # Dump/load again, and make sure everything is still there.
+    mark('second reload')
+    realm.run([kdb5_util, 'dump', dumpfile])
+    realm.run([kdb5_util, 'load', dumpfile])
+    out = realm.run([kadminl, 'getprincs'])
+    if realm.user_princ not in out or realm.host_princ not in out:
+        fail('Missing principal after load')
+    out = realm.run([kadminl, 'getpols'])
+    if 'compat\n' not in out or 'fred\n' not in out or 'barney\n' not in out:
+        fail('Missing policy after second load')
+
+    srcdumpdir = os.path.join(srctop, 'tests', 'dumpfiles')
+    srcdump = os.path.join(srcdumpdir, 'dump')
+    srcdump_r18 = os.path.join(srcdumpdir, 'dump.r18')
+    srcdump_r13 = os.path.join(srcdumpdir, 'dump.r13')
+    srcdump_b7 = os.path.join(srcdumpdir, 'dump.b7')
+    srcdump_ov = os.path.join(srcdumpdir, 'dump.ov')
+
+    # Load a dump file from the source directory.
+    realm.run([kdb5_util, 'destroy', '-f'])
+    realm.run([kdb5_util, 'load', srcdump])
+    realm.run([kdb5_util, 'stash', '-P', 'master'])
+
+    # Dump the resulting DB in each non-iprop format and compare with
+    # expected outputs.
+    dump_compare(realm, [], srcdump)
+    dump_compare(realm, ['-r18'], srcdump_r18)
+    dump_compare(realm, ['-r13'], srcdump_r13)
+    dump_compare(realm, ['-b7'], srcdump_b7)
+    dump_compare(realm, ['-ov'], srcdump_ov)
+
+    # Load each format of dump, check it, re-dump it, and compare.
+    load_dump_check_compare(realm, ['-r18'], srcdump_r18)
+    load_dump_check_compare(realm, ['-r13'], srcdump_r13)
+    load_dump_check_compare(realm, ['-b7'], srcdump_b7)
+
+    # Loading the last (-b7 format) dump won't have loaded the
+    # per-principal kadm data.  Load that incrementally with -ov.
+    realm.run([kadminl, 'getprinc', 'user'], expected_msg='Policy: [none]')
+    realm.run([kdb5_util, 'load', '-update', '-ov', srcdump_ov])
+    realm.run([kadminl, 'getprinc', 'user'], expected_msg='Policy: testpol')
 
 success('Dump/load tests')
diff --git a/src/tests/t_iprop.py b/src/tests/t_iprop.py
index 13ef1ca..54b4098 100755
--- a/src/tests/t_iprop.py
+++ b/src/tests/t_iprop.py
@@ -129,340 +129,354 @@ conf_slave2 = {'realms': {'$realm': {'iprop_slave_poll': '600',
 
 conf_foo = {'libdefaults': {'default_realm': 'FOO'},
             'domain_realm': {hostname: 'FOO'}}
-
-realm = K5Realm(kdc_conf=conf, create_user=False, start_kadmind=True)
-slave1 = realm.special_env('slave1', True, kdc_conf=conf_slave1)
-slave1m = realm.special_env('slave1m', True, krb5_conf=conf_foo,
-                            kdc_conf=conf_slave1m)
-slave2 = realm.special_env('slave2', True, kdc_conf=conf_slave2)
-
-# A default_realm and domain_realm that do not match the KDC's realm.
-# The FOO realm iprop_logfile setting is needed to run kproplog during
-# a slave3 test, since kproplog has no realm option.
 conf_slave3 = {'realms': {'$realm': {'iprop_slave_poll': '600',
                                      'iprop_logfile': '$testdir/ulog.slave3',
                                      'iprop_port': '$port8'},
                           'FOO': {'iprop_logfile': '$testdir/ulog.slave3'}},
                'dbmodules': {'db': {'database_name': '$testdir/db.slave3'}}}
-slave3 = realm.special_env('slave3', True, krb5_conf=conf_foo,
-                           kdc_conf=conf_slave3)
 
-# A default realm and a domain realm map that differ.
 krb5_conf_slave4 = {'domain_realm': {hostname: 'FOO'}}
 conf_slave4 = {'realms': {'$realm': {'iprop_slave_poll': '600',
                                      'iprop_logfile': '$testdir/ulog.slave4',
                                      'iprop_port': '$port8'}},
                'dbmodules': {'db': {'database_name': '$testdir/db.slave4'}}}
-slave4 = realm.special_env('slave4', True, krb5_conf=krb5_conf_slave4,
-                            kdc_conf=conf_slave4)
-
-# Define some principal names.  pr3 is long enough to cause internal
-# reallocs, but not long enough to grow the basic ulog entry size.
-pr1 = 'wakawaka@' + realm.realm
-pr2 = 'w@' + realm.realm
-c = 'chocolate-flavored-school-bus'
-cs = c + '/'
-pr3 = (cs + cs + cs + cs + cs + cs + cs + cs + cs + cs + cs + cs + c +
-       '@' + realm.realm)
-
-# Create the kpropd ACL file.
-acl_file = os.path.join(realm.testdir, 'kpropd-acl')
-acl = open(acl_file, 'w')
-acl.write(realm.host_princ + '\n')
-acl.close()
-
-ulog = os.path.join(realm.testdir, 'db.ulog')
-if not os.path.exists(ulog):
-    fail('update log not created: ' + ulog)
-
-# Create the principal used to authenticate kpropd to kadmind.
-kiprop_princ = 'kiprop/' + hostname
-realm.extract_keytab(kiprop_princ, realm.keytab)
-
-# Create the initial slave databases.
-dumpfile = os.path.join(realm.testdir, 'dump')
-realm.run([kdb5_util, 'dump', dumpfile])
-realm.run([kdb5_util, 'load', dumpfile], slave1)
-realm.run([kdb5_util, 'load', dumpfile], slave2)
-realm.run([kdb5_util, '-r', realm.realm, 'load', dumpfile], slave3)
-realm.run([kdb5_util, 'load', dumpfile], slave4)
-
-# Reinitialize the master ulog so we know exactly what to expect in
-# it.
-realm.run([kproplog, '-R'])
-check_ulog(1, 1, 1, [None])
-
-# Make some changes to the master DB.
-realm.addprinc(pr1)
-realm.addprinc(pr3)
-realm.addprinc(pr2)
-realm.run([kadminl, 'modprinc', '-allow_tix', pr2])
-realm.run([kadminl, 'modprinc', '+allow_tix', pr2])
-check_ulog(6, 1, 6, [None, pr1, pr3, pr2, pr2, pr2])
-
-# Start kpropd for slave1 and get a full dump from master.
-mark('propagate M->1 full')
-kpropd1 = realm.start_kpropd(slave1, ['-d'])
-wait_for_prop(kpropd1, True, 1, 6)
-out = realm.run([kadminl, 'listprincs'], env=slave1)
-if pr1 not in out or pr2 not in out or pr3 not in out:
-    fail('slave1 does not have all principals from master')
-check_ulog(1, 6, 6, [None], slave1)
-
-# Make a change and check that it propagates incrementally.
-mark('propagate M->1 incremental')
-realm.run([kadminl, 'modprinc', '-allow_tix', pr2])
-check_ulog(7, 1, 7, [None, pr1, pr3, pr2, pr2, pr2, pr2])
-kpropd1.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd1, False, 6, 7)
-check_ulog(2, 6, 7, [None, pr2], slave1)
-realm.run([kadminl, 'getprinc', pr2], env=slave1,
-          expected_msg='Attributes: DISALLOW_ALL_TIX')
-
-# Start kadmind -proponly for slave1.  (Use the slave1m environment
-# which defines iprop_port to $port8.)
-slave1_out_dump_path = os.path.join(realm.testdir, 'dump.slave1.out')
-slave2_in_dump_path = os.path.join(realm.testdir, 'dump.slave2.in')
-slave2_kprop_port = str(realm.portbase + 9)
-realm.start_server([kadmind, '-r', realm.realm, '-nofork', '-proponly', '-W',
-                    '-p', kdb5_util, '-K', kprop, '-k', slave2_kprop_port,
-                    '-F', slave1_out_dump_path], 'starting...', slave1m)
-
-# Test similar default_realm and domain_realm map settings with -r realm.
-mark('propagate 1->3 full')
-slave3_in_dump_path = os.path.join(realm.testdir, 'dump.slave3.in')
-kpropd3 = realm.start_server([kpropd, '-d', '-D', '-r', realm.realm, '-P',
-                              slave2_kprop_port, '-f', slave3_in_dump_path,
-                              '-p', kdb5_util, '-a', acl_file, '-A', hostname],
-                             'ready', slave3)
-wait_for_prop(kpropd3, True, 1, 7)
-out = realm.run([kadminl, '-r', realm.realm, 'listprincs'], env=slave3)
-if pr1 not in out or pr2 not in out or pr3 not in out:
-    fail('slave3 does not have all principals from slave1')
-check_ulog(1, 7, 7, [None], env=slave3)
-
-# Test an incremental propagation for the kpropd -r case.
-mark('propagate M->1->3 incremental')
-realm.run([kadminl, 'modprinc', '-maxlife', '20 minutes', pr1])
-check_ulog(8, 1, 8, [None, pr1, pr3, pr2, pr2, pr2, pr2, pr1])
-kpropd1.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd1, False, 7, 8)
-check_ulog(3, 6, 8, [None, pr2, pr1], slave1)
-realm.run([kadminl, 'getprinc', pr1], env=slave1,
-          expected_msg='Maximum ticket life: 0 days 00:20:00')
-kpropd3.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd3, False, 7, 8)
-check_ulog(2, 7, 8, [None, pr1], slave3)
-realm.run([kadminl, '-r', realm.realm, 'getprinc', pr1], env=slave3,
-          expected_msg='Maximum ticket life: 0 days 00:20:00')
-stop_daemon(kpropd3)
-
-# Test dissimilar default_realm and domain_realm map settings (no -r realm).
-mark('propagate 1->4 full')
-slave4_in_dump_path = os.path.join(realm.testdir, 'dump.slave4.in')
-kpropd4 = realm.start_server([kpropd, '-d', '-D', '-P', slave2_kprop_port,
-                              '-f', slave4_in_dump_path, '-p', kdb5_util,
-                              '-a', acl_file, '-A', hostname], 'ready', slave4)
-wait_for_prop(kpropd4, True, 1, 8)
-out = realm.run([kadminl, 'listprincs'], env=slave4)
-if pr1 not in out or pr2 not in out or pr3 not in out:
-    fail('slave4 does not have all principals from slave1')
-stop_daemon(kpropd4)
-
-# Start kpropd for slave2.  The -A option isn't needed since we're
-# talking to the same host as master (we specify it anyway to exercise
-# the code), but slave2 defines iprop_port to $port8 so it will talk
-# to slave1.  Get a full dump from slave1.
-mark('propagate 1->2 full')
-kpropd2 = realm.start_server([kpropd, '-d', '-D', '-P', slave2_kprop_port,
-                              '-f', slave2_in_dump_path, '-p', kdb5_util,
-                              '-a', acl_file, '-A', hostname], 'ready', slave2)
-wait_for_prop(kpropd2, True, 1, 8)
-check_ulog(2, 7, 8, [None, pr1], slave2)
-out = realm.run([kadminl, 'listprincs'], env=slave1)
-if pr1 not in out or pr2 not in out or pr3 not in out:
-    fail('slave2 does not have all principals from slave1')
-
-# Make another change and check that it propagates incrementally to
-# both slaves.
-mark('propagate M->1->2 incremental')
-realm.run([kadminl, 'modprinc', '-maxrenewlife', '22 hours', pr1])
-check_ulog(9, 1, 9, [None, pr1, pr3, pr2, pr2, pr2, pr2, pr1, pr1])
-kpropd1.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd1, False, 8, 9)
-check_ulog(4, 6, 9, [None, pr2, pr1, pr1], slave1)
-realm.run([kadminl, 'getprinc', pr1], env=slave1,
-          expected_msg='Maximum renewable life: 0 days 22:00:00\n')
-kpropd2.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd2, False, 8, 9)
-check_ulog(3, 7, 9, [None, pr1, pr1], slave2)
-realm.run([kadminl, 'getprinc', pr1], env=slave2,
-          expected_msg='Maximum renewable life: 0 days 22:00:00\n')
-
-# Reset the ulog on slave1 to force a full resync from master.  The
-# resync will use the old dump file and then propagate changes.
-# slave2 should still be in sync with slave1 after the resync, so make
-# sure it doesn't take a full resync.
-mark('propagate M->1->2 full')
-realm.run([kproplog, '-R'], slave1)
-check_ulog(1, 1, 1, [None], slave1)
-kpropd1.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd1, True, 1, 9)
-check_ulog(4, 6, 9, [None, pr2, pr1, pr1], slave1)
-kpropd2.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd2, False, 9, 9)
-check_ulog(3, 7, 9, [None, pr1, pr1], slave2)
-
-# Make another change and check that it propagates incrementally to
-# both slaves.
-mark('propagate M->1->2 incremental (after reset)')
-realm.run([kadminl, 'modprinc', '+allow_tix', pr2])
-check_ulog(10, 1, 10, [None, pr1, pr3, pr2, pr2, pr2, pr2, pr1, pr1, pr2])
-kpropd1.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd1, False, 9, 10)
-check_ulog(5, 6, 10, [None, pr2, pr1, pr1, pr2], slave1)
-realm.run([kadminl, 'getprinc', pr2], env=slave1, expected_msg='Attributes:\n')
-kpropd2.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd2, False, 9, 10)
-check_ulog(4, 7, 10, [None, pr1, pr1, pr2], slave2)
-realm.run([kadminl, 'getprinc', pr2], env=slave2, expected_msg='Attributes:\n')
-
-# Create a policy and check that it propagates via full resync.
-mark('propagate M->1->2 full (new policy)')
-realm.run([kadminl, 'addpol', '-minclasses', '2', 'testpol'])
-check_ulog(1, 1, 1, [None])
-kpropd1.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd1, True, 10, 1)
-check_ulog(1, 1, 1, [None], slave1)
-realm.run([kadminl, 'getpol', 'testpol'], env=slave1,
-          expected_msg='Minimum number of password character classes: 2')
-kpropd2.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd2, True, 10, 1)
-check_ulog(1, 1, 1, [None], slave2)
-realm.run([kadminl, 'getpol', 'testpol'], env=slave2,
-          expected_msg='Minimum number of password character classes: 2')
-
-# Modify the policy and test that it also propagates via full resync.
-mark('propagate M->1->2 full (policy change)')
-realm.run([kadminl, 'modpol', '-minlength', '17', 'testpol'])
-check_ulog(1, 1, 1, [None])
-kpropd1.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd1, True, 1, 1)
-check_ulog(1, 1, 1, [None], slave1)
-realm.run([kadminl, 'getpol', 'testpol'], env=slave1,
-          expected_msg='Minimum password length: 17')
-kpropd2.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd2, True, 1, 1)
-check_ulog(1, 1, 1, [None], slave2)
-realm.run([kadminl, 'getpol', 'testpol'], env=slave2,
-          expected_msg='Minimum password length: 17')
-
-# Delete the policy and test that it propagates via full resync.
-mark('propgate M->1->2 full (policy delete)')
-realm.run([kadminl, 'delpol', 'testpol'])
-check_ulog(1, 1, 1, [None])
-kpropd1.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd1, True, 1, 1)
-check_ulog(1, 1, 1, [None], slave1)
-realm.run([kadminl, 'getpol', 'testpol'], env=slave1, expected_code=1,
-          expected_msg='Policy does not exist')
-kpropd2.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd2, True, 1, 1)
-check_ulog(1, 1, 1, [None], slave2)
-realm.run([kadminl, 'getpol', 'testpol'], env=slave2, expected_code=1,
-          expected_msg='Policy does not exist')
-
-# Modify a principal on the master and test that it propagates incrementally.
-mark('propagate M->1->2 incremental (after policy changes)')
-realm.run([kadminl, 'modprinc', '-maxlife', '10 minutes', pr1])
-check_ulog(2, 1, 2, [None, pr1])
-kpropd1.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd1, False, 1, 2)
-check_ulog(2, 1, 2, [None, pr1], slave1)
-realm.run([kadminl, 'getprinc', pr1], env=slave1,
-          expected_msg='Maximum ticket life: 0 days 00:10:00')
-kpropd2.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd2, False, 1, 2)
-check_ulog(2, 1, 2, [None, pr1], slave2)
-realm.run([kadminl, 'getprinc', pr1], env=slave2,
-          expected_msg='Maximum ticket life: 0 days 00:10:00')
-
-# Delete a principal and test that it propagates incrementally.
-mark('propagate M->1->2 incremental (princ delete)')
-realm.run([kadminl, 'delprinc', pr3])
-check_ulog(3, 1, 3, [None, pr1, pr3])
-kpropd1.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd1, False, 2, 3)
-check_ulog(3, 1, 3, [None, pr1, pr3], slave1)
-realm.run([kadminl, 'getprinc', pr3], env=slave1, expected_code=1,
-          expected_msg='Principal does not exist')
-kpropd2.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd2, False, 2, 3)
-check_ulog(3, 1, 3, [None, pr1, pr3], slave2)
-realm.run([kadminl, 'getprinc', pr3], env=slave2, expected_code=1,
-          expected_msg='Principal does not exist')
-
-# Rename a principal and test that it propagates incrementally.
-mark('propagate M->1->2 incremental (princ rename)')
-renpr = "quacked@" + realm.realm
-realm.run([kadminl, 'renprinc', pr1, renpr])
-check_ulog(6, 1, 6, [None, pr1, pr3, renpr, pr1, renpr])
-kpropd1.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd1, False, 3, 6)
-check_ulog(6, 1, 6, [None, pr1, pr3, renpr, pr1, renpr], slave1)
-realm.run([kadminl, 'getprinc', pr1], env=slave1, expected_code=1,
-          expected_msg='Principal does not exist')
-realm.run([kadminl, 'getprinc', renpr], env=slave1)
-kpropd2.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd2, False, 3, 6)
-check_ulog(6, 1, 6, [None, pr1, pr3, renpr, pr1, renpr], slave2)
-realm.run([kadminl, 'getprinc', pr1], env=slave2, expected_code=1,
-          expected_msg='Principal does not exist')
-realm.run([kadminl, 'getprinc', renpr], env=slave2)
-
-pr1 = renpr
-
-# Reset the ulog on the master to force a full resync.
-mark('propagate M->1->2 full (ulog reset)')
-realm.run([kproplog, '-R'])
-check_ulog(1, 1, 1, [None])
-kpropd1.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd1, True, 6, 1)
-check_ulog(1, 1, 1, [None], slave1)
-kpropd2.send_signal(signal.SIGUSR1)
-wait_for_prop(kpropd2, True, 6, 1)
-check_ulog(1, 1, 1, [None], slave2)
-
-# Stop the kprop daemons so we can test kpropd -t.
-stop_daemon(kpropd1)
-stop_daemon(kpropd2)
-mark('kpropd -t')
-
-# Test the case where no updates are needed.
-out = realm.run_kpropd_once(slave1, ['-d'])
-if 'KDC is synchronized' not in out:
-    fail('Expected synchronized from kpropd -t')
-check_ulog(1, 1, 1, [None], slave1)
-
-# Make a change on the master and fetch it incrementally.
-realm.run([kadminl, 'modprinc', '-maxlife', '5 minutes', pr1])
-check_ulog(2, 1, 2, [None, pr1])
-out = realm.run_kpropd_once(slave1, ['-d'])
-if 'Got incremental updates (sno=2 ' not in out:
-    fail('Expected full dump and synchronized from kpropd -t')
-check_ulog(2, 1, 2, [None, pr1], slave1)
-realm.run([kadminl, 'getprinc', pr1], env=slave1,
-          expected_msg='Maximum ticket life: 0 days 00:05:00')
-
-# Propagate a policy change via full resync.
-realm.run([kadminl, 'addpol', '-minclasses', '3', 'testpol'])
-check_ulog(1, 1, 1, [None])
-out = realm.run_kpropd_once(slave1, ['-d'])
-if ('Full propagation transfer finished' not in out or
-    'KDC is synchronized' not in out):
-    fail('Expected full dump and synchronized from kpropd -t')
-check_ulog(1, 1, 1, [None], slave1)
-realm.run([kadminl, 'getpol', 'testpol'], env=slave1,
-          expected_msg='Minimum number of password character classes: 3')
+
+for realm in multidb_realms(kdc_conf=conf, create_user=False,
+                            start_kadmind=True):
+    slave1 = realm.special_env('slave1', True, kdc_conf=conf_slave1)
+    slave1m = realm.special_env('slave1m', True, krb5_conf=conf_foo,
+                                kdc_conf=conf_slave1m)
+    slave2 = realm.special_env('slave2', True, kdc_conf=conf_slave2)
+
+    # A default_realm and domain_realm that do not match the KDC's
+    # realm.  The FOO realm iprop_logfile setting is needed to run
+    # kproplog during a slave3 test, since kproplog has no realm
+    # option.
+    slave3 = realm.special_env('slave3', True, krb5_conf=conf_foo,
+                               kdc_conf=conf_slave3)
+
+    # A default realm and a domain realm map that differ.
+    slave4 = realm.special_env('slave4', True, krb5_conf=krb5_conf_slave4,
+                               kdc_conf=conf_slave4)
+
+    # Define some principal names.  pr3 is long enough to cause internal
+    # reallocs, but not long enough to grow the basic ulog entry size.
+    pr1 = 'wakawaka@' + realm.realm
+    pr2 = 'w@' + realm.realm
+    c = 'chocolate-flavored-school-bus'
+    cs = c + '/'
+    pr3 = (cs + cs + cs + cs + cs + cs + cs + cs + cs + cs + cs + cs + c +
+           '@' + realm.realm)
+
+    # Create the kpropd ACL file.
+    acl_file = os.path.join(realm.testdir, 'kpropd-acl')
+    acl = open(acl_file, 'w')
+    acl.write(realm.host_princ + '\n')
+    acl.close()
+
+    ulog = os.path.join(realm.testdir, 'db.ulog')
+    if not os.path.exists(ulog):
+        fail('update log not created: ' + ulog)
+
+    # Create the principal used to authenticate kpropd to kadmind.
+    kiprop_princ = 'kiprop/' + hostname
+    realm.extract_keytab(kiprop_princ, realm.keytab)
+
+    # Create the initial slave databases.
+    dumpfile = os.path.join(realm.testdir, 'dump')
+    realm.run([kdb5_util, 'dump', dumpfile])
+    realm.run([kdb5_util, 'load', dumpfile], slave1)
+    realm.run([kdb5_util, 'load', dumpfile], slave2)
+    realm.run([kdb5_util, '-r', realm.realm, 'load', dumpfile], slave3)
+    realm.run([kdb5_util, 'load', dumpfile], slave4)
+
+    # Reinitialize the master ulog so we know exactly what to expect in
+    # it.
+    realm.run([kproplog, '-R'])
+    check_ulog(1, 1, 1, [None])
+
+    # Make some changes to the master DB.
+    realm.addprinc(pr1)
+    realm.addprinc(pr3)
+    realm.addprinc(pr2)
+    realm.run([kadminl, 'modprinc', '-allow_tix', pr2])
+    realm.run([kadminl, 'modprinc', '+allow_tix', pr2])
+    check_ulog(6, 1, 6, [None, pr1, pr3, pr2, pr2, pr2])
+
+    # Start kpropd for slave1 and get a full dump from master.
+    mark('propagate M->1 full')
+    kpropd1 = realm.start_kpropd(slave1, ['-d'])
+    wait_for_prop(kpropd1, True, 1, 6)
+    out = realm.run([kadminl, 'listprincs'], env=slave1)
+    if pr1 not in out or pr2 not in out or pr3 not in out:
+        fail('slave1 does not have all principals from master')
+    check_ulog(1, 6, 6, [None], slave1)
+
+    # Make a change and check that it propagates incrementally.
+    mark('propagate M->1 incremental')
+    realm.run([kadminl, 'modprinc', '-allow_tix', pr2])
+    check_ulog(7, 1, 7, [None, pr1, pr3, pr2, pr2, pr2, pr2])
+    kpropd1.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd1, False, 6, 7)
+    check_ulog(2, 6, 7, [None, pr2], slave1)
+    realm.run([kadminl, 'getprinc', pr2], env=slave1,
+              expected_msg='Attributes: DISALLOW_ALL_TIX')
+
+    # Start kadmind -proponly for slave1.  (Use the slave1m environment
+    # which defines iprop_port to $port8.)
+    slave1_out_dump_path = os.path.join(realm.testdir, 'dump.slave1.out')
+    slave2_in_dump_path = os.path.join(realm.testdir, 'dump.slave2.in')
+    slave2_kprop_port = str(realm.portbase + 9)
+    kadmind_proponly = realm.start_server([kadmind, '-r', realm.realm,
+                                           '-nofork', '-proponly',
+                                           '-W', '-p', kdb5_util,
+                                           '-K', kprop, '-k',
+                                           slave2_kprop_port,
+                                           '-F', slave1_out_dump_path],
+                                          'starting...', slave1m)
+
+    # Test similar default_realm and domain_realm map settings with -r realm.
+    mark('propagate 1->3 full')
+    slave3_in_dump_path = os.path.join(realm.testdir, 'dump.slave3.in')
+    kpropd3 = realm.start_server([kpropd, '-d', '-D', '-r', realm.realm, '-P',
+                                  slave2_kprop_port, '-f', slave3_in_dump_path,
+                                  '-p', kdb5_util, '-a', acl_file,
+                                  '-A', hostname], 'ready', slave3)
+    wait_for_prop(kpropd3, True, 1, 7)
+    out = realm.run([kadminl, '-r', realm.realm, 'listprincs'], env=slave3)
+    if pr1 not in out or pr2 not in out or pr3 not in out:
+        fail('slave3 does not have all principals from slave1')
+    check_ulog(1, 7, 7, [None], env=slave3)
+
+    # Test an incremental propagation for the kpropd -r case.
+    mark('propagate M->1->3 incremental')
+    realm.run([kadminl, 'modprinc', '-maxlife', '20 minutes', pr1])
+    check_ulog(8, 1, 8, [None, pr1, pr3, pr2, pr2, pr2, pr2, pr1])
+    kpropd1.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd1, False, 7, 8)
+    check_ulog(3, 6, 8, [None, pr2, pr1], slave1)
+    realm.run([kadminl, 'getprinc', pr1], env=slave1,
+              expected_msg='Maximum ticket life: 0 days 00:20:00')
+    kpropd3.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd3, False, 7, 8)
+    check_ulog(2, 7, 8, [None, pr1], slave3)
+    realm.run([kadminl, '-r', realm.realm, 'getprinc', pr1], env=slave3,
+              expected_msg='Maximum ticket life: 0 days 00:20:00')
+    stop_daemon(kpropd3)
+
+    # Test dissimilar default_realm and domain_realm map settings (no
+    # -r realm).
+    mark('propagate 1->4 full')
+    slave4_in_dump_path = os.path.join(realm.testdir, 'dump.slave4.in')
+    kpropd4 = realm.start_server([kpropd, '-d', '-D', '-P', slave2_kprop_port,
+                                  '-f', slave4_in_dump_path, '-p', kdb5_util,
+                                  '-a', acl_file, '-A', hostname], 'ready',
+                                 slave4)
+    wait_for_prop(kpropd4, True, 1, 8)
+    out = realm.run([kadminl, 'listprincs'], env=slave4)
+    if pr1 not in out or pr2 not in out or pr3 not in out:
+        fail('slave4 does not have all principals from slave1')
+    stop_daemon(kpropd4)
+
+    # Start kpropd for slave2.  The -A option isn't needed since we're
+    # talking to the same host as master (we specify it anyway to
+    # exercise the code), but slave2 defines iprop_port to $port8 so
+    # it will talk to slave1.  Get a full dump from slave1.
+    mark('propagate 1->2 full')
+    kpropd2 = realm.start_server([kpropd, '-d', '-D', '-P', slave2_kprop_port,
+                                  '-f', slave2_in_dump_path, '-p', kdb5_util,
+                                  '-a', acl_file, '-A', hostname], 'ready',
+                                 slave2)
+    wait_for_prop(kpropd2, True, 1, 8)
+    check_ulog(2, 7, 8, [None, pr1], slave2)
+    out = realm.run([kadminl, 'listprincs'], env=slave1)
+    if pr1 not in out or pr2 not in out or pr3 not in out:
+        fail('slave2 does not have all principals from slave1')
+
+    # Make another change and check that it propagates incrementally
+    # to both slaves.
+    mark('propagate M->1->2 incremental')
+    realm.run([kadminl, 'modprinc', '-maxrenewlife', '22 hours', pr1])
+    check_ulog(9, 1, 9, [None, pr1, pr3, pr2, pr2, pr2, pr2, pr1, pr1])
+    kpropd1.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd1, False, 8, 9)
+    check_ulog(4, 6, 9, [None, pr2, pr1, pr1], slave1)
+    realm.run([kadminl, 'getprinc', pr1], env=slave1,
+              expected_msg='Maximum renewable life: 0 days 22:00:00\n')
+    kpropd2.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd2, False, 8, 9)
+    check_ulog(3, 7, 9, [None, pr1, pr1], slave2)
+    realm.run([kadminl, 'getprinc', pr1], env=slave2,
+              expected_msg='Maximum renewable life: 0 days 22:00:00\n')
+
+    # Reset the ulog on slave1 to force a full resync from master.
+    # The resync will use the old dump file and then propagate
+    # changes.  slave2 should still be in sync with slave1 after the
+    # resync, so make sure it doesn't take a full resync.
+    mark('propagate M->1->2 full')
+    realm.run([kproplog, '-R'], slave1)
+    check_ulog(1, 1, 1, [None], slave1)
+    kpropd1.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd1, True, 1, 9)
+    check_ulog(4, 6, 9, [None, pr2, pr1, pr1], slave1)
+    kpropd2.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd2, False, 9, 9)
+    check_ulog(3, 7, 9, [None, pr1, pr1], slave2)
+
+    # Make another change and check that it propagates incrementally to
+    # both slaves.
+    mark('propagate M->1->2 incremental (after reset)')
+    realm.run([kadminl, 'modprinc', '+allow_tix', pr2])
+    check_ulog(10, 1, 10, [None, pr1, pr3, pr2, pr2, pr2, pr2, pr1, pr1, pr2])
+    kpropd1.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd1, False, 9, 10)
+    check_ulog(5, 6, 10, [None, pr2, pr1, pr1, pr2], slave1)
+    realm.run([kadminl, 'getprinc', pr2], env=slave1,
+              expected_msg='Attributes:\n')
+    kpropd2.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd2, False, 9, 10)
+    check_ulog(4, 7, 10, [None, pr1, pr1, pr2], slave2)
+    realm.run([kadminl, 'getprinc', pr2], env=slave2,
+              expected_msg='Attributes:\n')
+
+    # Create a policy and check that it propagates via full resync.
+    mark('propagate M->1->2 full (new policy)')
+    realm.run([kadminl, 'addpol', '-minclasses', '2', 'testpol'])
+    check_ulog(1, 1, 1, [None])
+    kpropd1.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd1, True, 10, 1)
+    check_ulog(1, 1, 1, [None], slave1)
+    realm.run([kadminl, 'getpol', 'testpol'], env=slave1,
+              expected_msg='Minimum number of password character classes: 2')
+    kpropd2.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd2, True, 10, 1)
+    check_ulog(1, 1, 1, [None], slave2)
+    realm.run([kadminl, 'getpol', 'testpol'], env=slave2,
+              expected_msg='Minimum number of password character classes: 2')
+
+    # Modify the policy and test that it also propagates via full resync.
+    mark('propagate M->1->2 full (policy change)')
+    realm.run([kadminl, 'modpol', '-minlength', '17', 'testpol'])
+    check_ulog(1, 1, 1, [None])
+    kpropd1.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd1, True, 1, 1)
+    check_ulog(1, 1, 1, [None], slave1)
+    realm.run([kadminl, 'getpol', 'testpol'], env=slave1,
+              expected_msg='Minimum password length: 17')
+    kpropd2.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd2, True, 1, 1)
+    check_ulog(1, 1, 1, [None], slave2)
+    realm.run([kadminl, 'getpol', 'testpol'], env=slave2,
+              expected_msg='Minimum password length: 17')
+
+    # Delete the policy and test that it propagates via full resync.
+    mark('propgate M->1->2 full (policy delete)')
+    realm.run([kadminl, 'delpol', 'testpol'])
+    check_ulog(1, 1, 1, [None])
+    kpropd1.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd1, True, 1, 1)
+    check_ulog(1, 1, 1, [None], slave1)
+    realm.run([kadminl, 'getpol', 'testpol'], env=slave1, expected_code=1,
+              expected_msg='Policy does not exist')
+    kpropd2.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd2, True, 1, 1)
+    check_ulog(1, 1, 1, [None], slave2)
+    realm.run([kadminl, 'getpol', 'testpol'], env=slave2, expected_code=1,
+              expected_msg='Policy does not exist')
+
+    # Modify a principal on the master and test that it propagates
+    # incrementally.
+    mark('propagate M->1->2 incremental (after policy changes)')
+    realm.run([kadminl, 'modprinc', '-maxlife', '10 minutes', pr1])
+    check_ulog(2, 1, 2, [None, pr1])
+    kpropd1.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd1, False, 1, 2)
+    check_ulog(2, 1, 2, [None, pr1], slave1)
+    realm.run([kadminl, 'getprinc', pr1], env=slave1,
+              expected_msg='Maximum ticket life: 0 days 00:10:00')
+    kpropd2.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd2, False, 1, 2)
+    check_ulog(2, 1, 2, [None, pr1], slave2)
+    realm.run([kadminl, 'getprinc', pr1], env=slave2,
+              expected_msg='Maximum ticket life: 0 days 00:10:00')
+
+    # Delete a principal and test that it propagates incrementally.
+    mark('propagate M->1->2 incremental (princ delete)')
+    realm.run([kadminl, 'delprinc', pr3])
+    check_ulog(3, 1, 3, [None, pr1, pr3])
+    kpropd1.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd1, False, 2, 3)
+    check_ulog(3, 1, 3, [None, pr1, pr3], slave1)
+    realm.run([kadminl, 'getprinc', pr3], env=slave1, expected_code=1,
+              expected_msg='Principal does not exist')
+    kpropd2.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd2, False, 2, 3)
+    check_ulog(3, 1, 3, [None, pr1, pr3], slave2)
+    realm.run([kadminl, 'getprinc', pr3], env=slave2, expected_code=1,
+              expected_msg='Principal does not exist')
+
+    # Rename a principal and test that it propagates incrementally.
+    mark('propagate M->1->2 incremental (princ rename)')
+    renpr = "quacked@" + realm.realm
+    realm.run([kadminl, 'renprinc', pr1, renpr])
+    check_ulog(6, 1, 6, [None, pr1, pr3, renpr, pr1, renpr])
+    kpropd1.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd1, False, 3, 6)
+    check_ulog(6, 1, 6, [None, pr1, pr3, renpr, pr1, renpr], slave1)
+    realm.run([kadminl, 'getprinc', pr1], env=slave1, expected_code=1,
+              expected_msg='Principal does not exist')
+    realm.run([kadminl, 'getprinc', renpr], env=slave1)
+    kpropd2.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd2, False, 3, 6)
+    check_ulog(6, 1, 6, [None, pr1, pr3, renpr, pr1, renpr], slave2)
+    realm.run([kadminl, 'getprinc', pr1], env=slave2, expected_code=1,
+              expected_msg='Principal does not exist')
+    realm.run([kadminl, 'getprinc', renpr], env=slave2)
+
+    pr1 = renpr
+
+    # Reset the ulog on the master to force a full resync.
+    mark('propagate M->1->2 full (ulog reset)')
+    realm.run([kproplog, '-R'])
+    check_ulog(1, 1, 1, [None])
+    kpropd1.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd1, True, 6, 1)
+    check_ulog(1, 1, 1, [None], slave1)
+    kpropd2.send_signal(signal.SIGUSR1)
+    wait_for_prop(kpropd2, True, 6, 1)
+    check_ulog(1, 1, 1, [None], slave2)
+
+    # Stop the kprop daemons so we can test kpropd -t.
+    realm.stop_kpropd(kpropd1)
+    stop_daemon(kpropd2)
+    stop_daemon(kadmind_proponly)
+    mark('kpropd -t')
+
+    # Test the case where no updates are needed.
+    out = realm.run_kpropd_once(slave1, ['-d'])
+    if 'KDC is synchronized' not in out:
+        fail('Expected synchronized from kpropd -t')
+    check_ulog(1, 1, 1, [None], slave1)
+
+    # Make a change on the master and fetch it incrementally.
+    realm.run([kadminl, 'modprinc', '-maxlife', '5 minutes', pr1])
+    check_ulog(2, 1, 2, [None, pr1])
+    out = realm.run_kpropd_once(slave1, ['-d'])
+    if 'Got incremental updates (sno=2 ' not in out:
+        fail('Expected full dump and synchronized from kpropd -t')
+    check_ulog(2, 1, 2, [None, pr1], slave1)
+    realm.run([kadminl, 'getprinc', pr1], env=slave1,
+              expected_msg='Maximum ticket life: 0 days 00:05:00')
+
+    # Propagate a policy change via full resync.
+    realm.run([kadminl, 'addpol', '-minclasses', '3', 'testpol'])
+    check_ulog(1, 1, 1, [None])
+    out = realm.run_kpropd_once(slave1, ['-d'])
+    if ('Full propagation transfer finished' not in out or
+        'KDC is synchronized' not in out):
+        fail('Expected full dump and synchronized from kpropd -t')
+    check_ulog(1, 1, 1, [None], slave1)
+    realm.run([kadminl, 'getpol', 'testpol'], env=slave1,
+              expected_msg='Minimum number of password character classes: 3')
 
 success('iprop tests')
diff --git a/src/tests/t_kdb.py b/src/tests/t_kdb.py
index 8438a45..78de223 100755
--- a/src/tests/t_kdb.py
+++ b/src/tests/t_kdb.py
@@ -3,9 +3,9 @@ from k5test import *
 import time
 from itertools import imap
 
-# Run kdbtest against the BDB module.
-realm = K5Realm(create_kdb=False)
-realm.run(['./kdbtest'])
+# Run kdbtest against the non-LDAP KDB modules.
+for realm in multidb_realms(create_kdb=False):
+    realm.run(['./kdbtest'])
 
 # Set up an OpenLDAP test server if we can.
 
diff --git a/src/tests/t_kdb_locking.py b/src/tests/t_kdb_locking.py
index aac0a22..ea32ad7 100755
--- a/src/tests/t_kdb_locking.py
+++ b/src/tests/t_kdb_locking.py
@@ -13,7 +13,7 @@ import os
 from k5test import *
 
 p = 'foo'
-realm = K5Realm(create_user=False)
+realm = K5Realm(create_user=False, bdb_only=True)
 realm.addprinc(p, p)
 
 kadm5_lock = os.path.join(realm.testdir, 'db.kadm5.lock')
diff --git a/src/tests/t_policy.py b/src/tests/t_policy.py
index 9d92ebd..5c9e87c 100755
--- a/src/tests/t_policy.py
+++ b/src/tests/t_policy.py
@@ -60,34 +60,36 @@ realm.run([kadminl, 'cpw', '-pw', 'aa', 'pwuser'])
 
 # Test basic password lockout support.
 mark('password lockout')
-realm.run([kadminl, 'addpol', '-maxfailure', '2', '-failurecountinterval',
-           '5m', 'lockout'])
-realm.run([kadminl, 'modprinc', '+requires_preauth', '-policy', 'lockout',
-           'user'])
-
-# kinit twice with the wrong password.
-realm.run([kinit, realm.user_princ], input='wrong\n', expected_code=1,
-          expected_msg='Password incorrect while getting initial credentials')
-realm.run([kinit, realm.user_princ], input='wrong\n', expected_code=1,
-          expected_msg='Password incorrect while getting initial credentials')
-
-# Now the account should be locked out.
-m = 'Client\'s credentials have been revoked while getting initial credentials'
-realm.run([kinit, realm.user_princ], expected_code=1, expected_msg=m)
-
-# Check that modprinc -unlock allows a further attempt.
-realm.run([kadminl, 'modprinc', '-unlock', 'user'])
-realm.kinit(realm.user_princ, password('user'))
-
-# Make sure a nonexistent policy reference doesn't prevent authentication.
-realm.run([kadminl, 'delpol', 'lockout'])
-realm.kinit(realm.user_princ, password('user'))
+realm.stop()
+for realm in multidb_realms(create_host=False):
+    realm.run([kadminl, 'addpol', '-maxfailure', '2', '-failurecountinterval',
+               '5m', 'lockout'])
+    realm.run([kadminl, 'modprinc', '+requires_preauth', '-policy', 'lockout',
+               'user'])
+
+    # kinit twice with the wrong password.
+    msg = 'Password incorrect while getting initial credentials'
+    realm.run([kinit, realm.user_princ], input='wrong\n', expected_code=1,
+              expected_msg=msg)
+    realm.run([kinit, realm.user_princ], input='wrong\n', expected_code=1,
+              expected_msg=msg)
+
+    # Now the account should be locked out.
+    msg = 'credentials have been revoked while getting initial credentials'
+    realm.run([kinit, realm.user_princ], expected_code=1, expected_msg=msg)
+
+    # Check that modprinc -unlock allows a further attempt.
+    realm.run([kadminl, 'modprinc', '-unlock', 'user'])
+    realm.kinit(realm.user_princ, password('user'))
+
+    # Make sure a nonexistent policy reference doesn't prevent authentication.
+    realm.run([kadminl, 'delpol', 'lockout'])
+    realm.kinit(realm.user_princ, password('user'))
 
 # Regression test for issue #7099: databases created prior to krb5 1.3 have
 # multiple history keys, and kadmin prior to 1.7 didn't necessarily use the
 # first one to create history entries.
 mark('#7099 regression test')
-realm.stop()
 realm = K5Realm(start_kdc=False)
 # Create a history principal with two keys.
 realm.run(['./hist', 'make'])
diff --git a/src/tests/t_unlockiter.py b/src/tests/t_unlockiter.py
index 2a438e9..60ed77c 100755
--- a/src/tests/t_unlockiter.py
+++ b/src/tests/t_unlockiter.py
@@ -3,7 +3,8 @@ from k5test import *
 
 # Default KDB iteration is locked.  Expect write lock failure unless
 # unlocked iteration is explicitly requested.
-realm = K5Realm(create_user=False, create_host=False, start_kdc=False)
+realm = K5Realm(create_user=False, create_host=False, start_kdc=False,
+                bdb_only=True)
 realm.run(['./unlockiter'], expected_code=1)
 realm.run(['./unlockiter', '-u'])
 realm.run(['./unlockiter', '-l'], expected_code=1)
@@ -11,6 +12,7 @@ realm.run(['./unlockiter', '-l'], expected_code=1)
 # Set default to unlocked iteration.  Only explicitly requested locked
 # iteration should block the write lock.
 realm = K5Realm(create_user=False, create_host=False, start_kdc=False,
+                bdb_only=True,
                 krb5_conf={'dbmodules': {'db': {'unlockiter': 'true'}}})
 realm.run(['./unlockiter'])
 realm.run(['./unlockiter', '-u'])
diff --git a/src/util/k5test.py b/src/util/k5test.py
index bc32877..8d635af 100644
--- a/src/util/k5test.py
+++ b/src/util/k5test.py
@@ -118,6 +118,9 @@ keyword arguments:
 
 * get_creds=False: Don't get user credentials.
 
+* bdb_only=True: Use the DB2 KDB module even if K5TEST_LMDB is set in
+  the environment.
+
 Scripts may use the following functions and variables:
 
 * fail(message): Display message (plus leading marker and trailing
@@ -165,6 +168,12 @@ Scripts may use the following functions and variables:
   honored.  If keywords contains krb5_conf and/or kdc_conf fragments,
   they will be merged with the default and per-pass specifications.
 
+* multidb_realms(**keywords): Yields a realm for multiple DB modules.
+  Currently DB2 and LMDB are included.  Ideally LDAP would be
+  included, but setting up a test LDAP server currently requires a
+  one-second delay, so all LDAP tests are currently confined to
+  t_kdb.py.  keywords may contain any K5Realm initializer.
+
 * cross_realms(num, xtgts=None, args=None, **keywords): This function
   returns a list of num realms, where each realm's configuration knows
   how to contact all of the realms.  By default, each realm will
@@ -390,6 +399,8 @@ def fail(msg):
         sys.stdout.write(_last_cmd_output)
     if _current_pass:
         print "*** Failed in test pass:", _current_pass
+    if _current_db:
+        print "*** Failed with db:", _current_db
     sys.exit(1)
 
 
@@ -810,8 +821,9 @@ class K5Realm(object):
                  krb5_conf=None, kdc_conf=None, create_kdb=True,
                  krbtgt_keysalt=None, create_user=True, get_creds=True,
                  create_host=True, start_kdc=True, start_kadmind=False,
-                 start_kpropd=False):
+                 start_kpropd=False, bdb_only=False):
         global hostname, _default_krb5_conf, _default_kdc_conf
+        global _lmdb_kdc_conf, _current_db
 
         self.realm = realm
         self.testdir = os.path.join(os.getcwd(), testdir)
@@ -826,7 +838,11 @@ class K5Realm(object):
         self.ccache = os.path.join(self.testdir, 'ccache')
         self.kadmin_ccache = os.path.join(self.testdir, 'kadmin_ccache')
         self._krb5_conf = _cfg_merge(_default_krb5_conf, krb5_conf)
-        self._kdc_conf = _cfg_merge(_default_kdc_conf, kdc_conf)
+        base_kdc_conf = _default_kdc_conf
+        if (os.getenv('K5TEST_LMDB') is not None and
+            not bdb_only and not _current_db):
+            base_kdc_conf = _cfg_merge(base_kdc_conf, _lmdb_kdc_conf)
+        self._kdc_conf = _cfg_merge(base_kdc_conf, kdc_conf)
         self._kdc_proc = None
         self._kadmind_proc = None
         self._kpropd_procs = []
@@ -1008,6 +1024,10 @@ class K5Realm(object):
         self._kpropd_procs.append(proc)
         return proc
 
+    def stop_kpropd(self, proc):
+        stop_daemon(proc)
+        self._kpropd_procs.remove(proc)
+
     def run_kpropd_once(self, env, args=[]):
         return self.run(self._kpropd_args() + ['-t'] + args, env=env)
 
@@ -1102,6 +1122,20 @@ def multipass_realms(**keywords):
         _current_pass = None
 
 
+def multidb_realms(**keywords):
+    global _current_db, _dbpasses
+    caller_kdc_conf = keywords.get('kdc_conf')
+    for p in _dbpasses:
+        (name, kdc_conf) = p
+        output('*** Using DB type %s\n' % name)
+        keywords['kdc_conf'] = _cfg_merge(kdc_conf, caller_kdc_conf)
+        _current_db = name
+        realm = K5Realm(**keywords)
+        yield realm
+        realm.stop()
+        _current_db = None
+
+
 def cross_realms(num, xtgts=None, args=None, **keywords):
     # Build keyword args for each realm.
     realm_args = []
@@ -1198,6 +1232,10 @@ _default_kdc_conf = {
         'default': 'FILE:$testdir/others.log'}}
 
 
+_lmdb_kdc_conf = {'dbmodules': {'db': {'db_library': 'klmdb',
+                                       'nosync': 'true'}}}
+
+
 # A pass is a tuple of: name, krbtgt_keysalt, krb5_conf, kdc_conf.
 _passes = [
     # No special settings; exercises AES256.
@@ -1282,6 +1320,7 @@ _passes = [
 
 _success = False
 _current_pass = None
+_current_db = None
 _daemons = []
 _parse_args()
 atexit.register(_onexit)
@@ -1298,6 +1337,11 @@ runenv = _import_runenv()
 hostname = _get_hostname()
 null_input = open(os.devnull, 'r')
 
+# A DB pass is a tuple of: name, kdc_conf.
+_dbpasses = [('db2', None)]
+if runenv.have_lmdb == 'yes':
+    _dbpasses.append(('lmdb', _lmdb_kdc_conf))
+
 krb5kdc = os.path.join(buildtop, 'kdc', 'krb5kdc')
 kadmind = os.path.join(buildtop, 'kadmin', 'server', 'kadmind')
 kadmin = os.path.join(buildtop, 'kadmin', 'cli', 'kadmin')
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.