From cf090890b4219483412ab89b39a60d92515191eb Mon Sep 17 00:00:00 2001 From: Greg Hudson Date: Mon, 27 Jan 2014 12:50:12 -0500 Subject: Test iprop slave ulog management Check the ulog more thoroughly after each operation, including the principal names we expect in each update entry. Verify that the slave ulog contains actual update entries received from master. Add a second slave which receives updates from the first. Test a wider variety of principal operations. Add two additional operations after the full resync to test that incremental updates resume after a full resync (albeit with some lag). ticket: 7855 --- src/tests/t_iprop.py | 336 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 238 insertions(+), 98 deletions(-) diff --git a/src/tests/t_iprop.py b/src/tests/t_iprop.py index bd9ab8984d..1dfc55bf27 100644 --- a/src/tests/t_iprop.py +++ b/src/tests/t_iprop.py @@ -1,6 +1,7 @@ #!/usr/bin/python import os +import re from k5test import * @@ -46,27 +47,72 @@ def wait_for_prop(kpropd, full_expected): fail('kadmind returned invalid result') -# Verify the iprop log last serial number against an expected value, -# on either the master or slave. -def check_serial(realm, expected, env=None): - out = realm.run([kproplog, '-h'], env=env) - if 'Last serial # : ' not in out: - fail('Unexpected serial number') +# Verify the output of kproplog against the expected number of +# entries, first and last serial number, and a list of principal names +# for the update entrires. +def check_ulog(num, first, last, entries, env=None): + out = realm.run([kproplog], env=env) + if 'Number of entries : ' + str(num) + '\n' not in out: + fail('Expected %d entries' % num) + if last: + firststr = first and str(first) or 'None' + if 'First serial # : ' + firststr + '\n' not in out: + fail('Expected first serial number %d' % first) + laststr = last and str(last) or 'None' + if 'Last serial # : ' + laststr + '\n' not in out: + fail('Expected last serial number %d' % last) + assert(len(entries) == num) + ser = first - 1 + entindex = 0 + for line in out.splitlines(): + m = re.match(r'\tUpdate serial # : (\d+)$', line) + if m: + ser = ser + 1 + if m.group(1) != str(ser): + fail('Expected serial number %d in update entry' % ser) + m = re.match(r'\tUpdate principal : (.*)$', line) + if m: + eprinc = entries[ser - first] + if m.group(1) != eprinc: + fail('Expected princ %s in update entry %d' % (eprinc, ser)) +# slave1 will receive updates from master, and slave2 will receive +# updates from slave1. Because of the awkward way iprop and kprop +# port configuration currently works, we need separate config files +# for the slave and master sides of slave1, but they use the same DB +# and ulog file. +conf = {'realms': {'$realm': {'iprop_enable': 'true', + 'iprop_logfile': '$testdir/db.ulog'}}} +conf_slave1 = {'realms': {'$realm': {'iprop_slave_poll': '600', + 'iprop_logfile': '$testdir/ulog.slave1'}}, + 'dbmodules': {'db': {'database_name': '$testdir/db.slave1'}}} +conf_slave1m = {'realms': {'$realm': {'iprop_logfile': '$testdir/ulog.slave1', + 'iprop_port': '$port8'}}, + 'dbmodules': {'db': {'database_name': '$testdir/db.slave1'}}} +conf_slave2 = {'realms': {'$realm': {'iprop_slave_poll': '600', + 'iprop_logfile': '$testdir/ulog.slave2', + 'iprop_port': '$port8'}}, + 'dbmodules': {'db': {'database_name': '$testdir/db.slave2'}}} -conf = { - 'realms': {'$realm': { - 'iprop_enable': 'true', - 'iprop_logfile' : '$testdir/db.ulog'}}} +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, kdc_conf=conf_slave1m) +slave2 = realm.special_env('slave2', True, kdc_conf=conf_slave2) -conf_slave = { - 'realms': {'$realm': { - 'iprop_slave_poll': '600', - 'iprop_logfile' : '$testdir/db.slave.ulog'}}, - 'dbmodules': {'db': {'database_name': '$testdir/db.slave'}}} +# 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) -realm = K5Realm(kdc_conf=conf, create_user=False, start_kadmind=True) -slave = realm.special_env('slave', True, kdc_conf=conf_slave) +# 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): @@ -77,113 +123,207 @@ kiprop_princ = 'kiprop/' + hostname realm.addprinc(kiprop_princ) realm.extract_keytab(kiprop_princ, realm.keytab) -# Create the slave db. +# Create the initial slave1 and slave2 databases. dumpfile = os.path.join(realm.testdir, 'dump') realm.run([kdb5_util, 'dump', dumpfile]) -realm.run([kdb5_util, 'load', dumpfile], slave) -realm.run([kdb5_util, 'stash', '-P', 'master'], slave) +realm.run([kdb5_util, 'load', dumpfile], slave1) +realm.run([kdb5_util, 'load', dumpfile], slave2) -# Make some changes to the master db. -realm.addprinc('wakawaka') -# Add a principal enough to make realloc likely, but not enough to grow -# basic ulog entry size. -c = 'chocolate-flavored-school-bus' -cs = c + '/' -longname = cs + cs + cs + cs + cs + cs + cs + cs + cs + cs + cs + cs + c -realm.addprinc(longname) -realm.addprinc('w') -realm.run_kadminl('modprinc -allow_tix w') -realm.run_kadminl('modprinc +allow_tix w') +# Reinitialize the master ulog so we know exactly what to expect in +# it. +realm.run([kproplog, '-R']) +check_ulog(0, 0, 0, []) -check_serial(realm, '7') +# 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(5, 1, 5, [pr1, pr3, pr2, pr2, pr2]) -# Set up 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() - -# Start kpropd and get a full dump from master. -kpropd = realm.start_kpropd(slave, ['-d']) -wait_for_prop(kpropd, True) -out = realm.run_kadminl('listprincs', slave) -if longname not in out or 'wakawaka' not in out or 'w@' not in out: - fail('Slave does not have all principals from master') +# Start kpropd for slave1 and get a full dump from master. +kpropd1 = realm.start_kpropd(slave1, ['-d']) +wait_for_prop(kpropd1, True) +out = realm.run_kadminl('listprincs', 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(0, 0, 5, [], slave1) # Make a change and check that it propagates incrementally. -realm.run_kadminl('modprinc -allow_tix w') -check_serial(realm, '8') -kpropd.send_signal(signal.SIGUSR1) -wait_for_prop(kpropd, False) -check_serial(realm, '8', slave) -out = realm.run_kadminl('getprinc w', slave) +realm.run_kadminl('modprinc -allow_tix ' + pr2) +check_ulog(6, 1, 6, [pr1, pr3, pr2, pr2, pr2, pr2]) +kpropd1.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd1, False) +check_ulog(1, 6, 6, [pr2], slave1) +out = realm.run_kadminl('getprinc ' + pr2, slave1) if 'Attributes: DISALLOW_ALL_TIX' not in out: - fail('Slave does not have modification from master') + fail('slave1 does not have modification from master') -# Make another change and check that it propagates incrementally. -realm.run_kadminl('modprinc +allow_tix w') -check_serial(realm, '9') -kpropd.send_signal(signal.SIGUSR1) -wait_for_prop(kpropd, False) -check_serial(realm, '9', slave) -out = realm.run_kadminl('getprinc w', slave) -if 'Attributes:\n' not in out: - fail('Slave does not have modification from master') +# 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) +slave1m['KPROP_PORT'] = slave2_kprop_port +realm.start_server([kadmind, '-nofork', '-proponly', '-W', '-p', kdb5_util, + '-K', kprop, '-F', slave1_out_dump_path], 'starting...', + slave1m) + +# 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. +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) +check_ulog(0, 0, 6, [], slave2) +out = realm.run_kadminl('listprincs', 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. +realm.run_kadminl('modprinc -maxrenewlife "22 hours" ' + pr1) +check_ulog(7, 1, 7, [pr1, pr3, pr2, pr2, pr2, pr2, pr1]) +kpropd1.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd1, False) +check_ulog(2, 6, 7, [pr2, pr1], slave1) +out = realm.run_kadminl('getprinc ' + pr1, slave1) +if 'Maximum renewable life: 0 days 22:00:00\n' not in out: + fail('slave1 does not have modification from master') +kpropd2.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd2, False) +check_ulog(1, 7, 7, [pr1], slave2) +out = realm.run_kadminl('getprinc ' + pr1, slave2) +if 'Maximum renewable life: 0 days 22:00:00\n' not in out: + fail('slave2 does not have modification from slave1') -# Reset the ulog on the slave side to force a full resync to the slave. -realm.run([kproplog, '-R'], slave) -check_serial(realm, 'None', slave) -kpropd.send_signal(signal.SIGUSR1) -wait_for_prop(kpropd, True) -check_serial(realm, '9', slave) +# 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. +realm.run([kproplog, '-R'], slave1) +check_ulog(0, 0, 0, [], slave1) +kpropd1.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd1, True) +check_ulog(2, 6, 7, [pr2, pr1], slave1) +kpropd2.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd2, False) +check_ulog(1, 7, 7, [pr1], slave2) -# Make another change and check that it propagates incrementally. +# Make another change and check that it propagates incrementally to +# both slaves. realm.run_kadminl('modprinc +allow_tix w') -check_serial(realm, '10') -kpropd.send_signal(signal.SIGUSR1) -wait_for_prop(kpropd, False) -check_serial(realm, '10', slave) -out = realm.run_kadminl('getprinc w', slave) +check_ulog(8, 1, 8, [pr1, pr3, pr2, pr2, pr2, pr2, pr1, pr2]) +kpropd1.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd1, False) +check_ulog(3, 6, 8, [pr2, pr1, pr2], slave1) +out = realm.run_kadminl('getprinc ' + pr2, slave1) if 'Attributes:\n' not in out: - fail('Slave has different state from master') + fail('slave1 does not have modification from master') +kpropd2.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd2, False) +check_ulog(2, 7, 8, [pr1, pr2], slave2) +out = realm.run_kadminl('getprinc ' + pr2, slave2) +if 'Attributes:\n' not in out: + fail('slave2 does not have modification from slave1') # Create a policy and check that it propagates via full resync. realm.run_kadminl('addpol -minclasses 2 testpol') -check_serial(realm, 'None') -kpropd.send_signal(signal.SIGUSR1) -wait_for_prop(kpropd, True) -check_serial(realm, 'None', slave) -out = realm.run_kadminl('getpol testpol', slave) +check_ulog(0, 0, 0, []) +kpropd1.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd1, True) +check_ulog(0, 0, 0, [], slave1) +out = realm.run_kadminl('getpol testpol', slave1) +if 'Minimum number of password character classes: 2' not in out: + fail('slave1 does not have policy from master') +kpropd2.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd2, True) +check_ulog(0, 0, 0, [], slave2) +out = realm.run_kadminl('getpol testpol', slave2) if 'Minimum number of password character classes: 2' not in out: - fail('Slave does not have policy from master') + fail('slave2 does not have policy from slave1') # Modify the policy and test that it also propagates via full resync. realm.run_kadminl('modpol -minlength 17 testpol') -check_serial(realm, 'None') -kpropd.send_signal(signal.SIGUSR1) -wait_for_prop(kpropd, True) -check_serial(realm, 'None', slave) -out = realm.run_kadminl('getpol testpol', slave) +check_ulog(0, 0, 0, []) +kpropd1.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd1, True) +check_ulog(0, 0, 0, [], slave1) +out = realm.run_kadminl('getpol testpol', slave1) +if 'Minimum password length: 17' not in out: + fail('slave1 does not have policy change from master') +kpropd2.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd2, True) +check_ulog(0, 0, 0, [], slave2) +out = realm.run_kadminl('getpol testpol', slave2) if 'Minimum password length: 17' not in out: - fail('Slave does not have policy change from master') + fail('slave2 does not have policy change from slave1') # Delete the policy and test that it propagates via full resync. realm.run_kadminl('delpol -force testpol') -check_serial(realm, 'None') -kpropd.send_signal(signal.SIGUSR1) -wait_for_prop(kpropd, True) -check_serial(realm, 'None', slave) -out = realm.run_kadminl('getpol testpol', slave) +check_ulog(0, 0, 0, []) +kpropd1.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd1, True) +check_ulog(0, 0, 0, [], slave1) +out = realm.run_kadminl('getpol testpol', slave1) if 'Policy does not exist' not in out: - fail('Slave did not get policy deletion from master') + fail('slave1 did not get policy deletion from master') +kpropd2.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd2, True) +check_ulog(0, 0, 0, [], slave2) +out = realm.run_kadminl('getpol testpol', slave2) +if 'Policy does not exist' not in out: + fail('slave2 did not get policy deletion from slave1') + +# Modify a principal on the master and test that it propagates via +# full resync. (The master's ulog does not remember the timestamp it +# had at serial number 0, so it does not know that an incremental +# propagation is possible.) +realm.run_kadminl('modprinc -maxlife "10 minutes" ' + pr1) +check_ulog(1, 1, 1, [pr1]) +kpropd1.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd1, True) +check_ulog(0, 0, 1, [], slave1) +out = realm.run_kadminl('getprinc ' + pr1, slave1) +if 'Maximum ticket life: 0 days 00:10:00' not in out: + fail('slave1 does not have modification from master') +kpropd2.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd2, True) +check_ulog(0, 0, 1, [], slave2) +out = realm.run_kadminl('getprinc ' + pr1, slave2) +if 'Maximum ticket life: 0 days 00:10:00' not in out: + fail('slave2 does not have modification from slave1') + +# Delete a principal and test that it propagates incrementally to +# slave1. slave2 needs another full resync because slave1 no longer +# has serial number 1 in its ulog after processing its first +# incremental update. +realm.run_kadminl('delprinc -force ' + pr3) +check_ulog(2, 1, 2, [pr1, pr3]) +kpropd1.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd1, False) +check_ulog(1, 2, 2, [pr3], slave1) +out = realm.run_kadminl('getprinc ' + pr3, slave1) +if 'Principal does not exist' not in out: + fail('slave1 does not have principal deletion from master') +kpropd2.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd2, True) +check_ulog(0, 0, 2, [], slave2) +out = realm.run_kadminl('getprinc ' + pr3, slave2) +if 'Principal does not exist' not in out: + fail('slave2 does not have principal deletion from slave1') -# Reset the ulog on the master side to force a full resync to all slaves. -# XXX Note that we only have one slave in this test, so we can't really -# test this. +# Reset the ulog on the master to force a full resync. realm.run([kproplog, '-R']) -check_serial(realm, 'None') -kpropd.send_signal(signal.SIGUSR1) -wait_for_prop(kpropd, True) -check_serial(realm, 'None', slave) +check_ulog(0, 0, 0, []) +kpropd1.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd1, True) +check_ulog(0, 0, 0, [], slave1) +kpropd2.send_signal(signal.SIGUSR1) +wait_for_prop(kpropd2, True) +check_ulog(0, 0, 0, [], slave2) success('iprop tests') -- cgit