| 1 | #!/usr/bin/python |
|---|
| 2 | # -*- coding: utf-8 -*- |
|---|
| 3 | # Copyright © 2009 by Karl Ramm |
|---|
| 4 | # |
|---|
| 5 | # All rights reserved. |
|---|
| 6 | # |
|---|
| 7 | # Permission to use, copy, modify, and distribute this software and |
|---|
| 8 | # its documentation for any purpose and without fee is hereby granted, |
|---|
| 9 | # provided that the above copyright notice appear in all copies and |
|---|
| 10 | # that both that copyright notice and this permission notice appear in |
|---|
| 11 | # supporting documentation, and that the name of Karl Ramm not be used |
|---|
| 12 | # in advertising or publicity pertaining to distribution of the |
|---|
| 13 | # software without specific, written prior permission. |
|---|
| 14 | # |
|---|
| 15 | # KARL RAMM DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, |
|---|
| 16 | # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN |
|---|
| 17 | # NO EVENT SHALL KARL RAMM BE LIABLE FOR ANY SPECIAL, INDIRECT OR |
|---|
| 18 | # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS |
|---|
| 19 | # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, |
|---|
| 20 | # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION |
|---|
| 21 | # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|---|
| 22 | |
|---|
| 23 | import sys |
|---|
| 24 | import os |
|---|
| 25 | import time |
|---|
| 26 | import traceback |
|---|
| 27 | import logging |
|---|
| 28 | import errno |
|---|
| 29 | import subprocess |
|---|
| 30 | import fcntl |
|---|
| 31 | import itertools |
|---|
| 32 | |
|---|
| 33 | |
|---|
| 34 | import cluster |
|---|
| 35 | import ztest |
|---|
| 36 | |
|---|
| 37 | class BaseTest(object): |
|---|
| 38 | def __init__(self): |
|---|
| 39 | self.log = logging.getLogger(self.__class__.__name__) |
|---|
| 40 | def setup(self): |
|---|
| 41 | self.log.info('starting setup') |
|---|
| 42 | def teardown(self): |
|---|
| 43 | self.log.info('starting teardown') |
|---|
| 44 | def test(self): |
|---|
| 45 | self.log.info('starting test') |
|---|
| 46 | def run(self): |
|---|
| 47 | failed = False |
|---|
| 48 | try: |
|---|
| 49 | self.setup() |
|---|
| 50 | except: |
|---|
| 51 | self.handle(sys.exc_info()) |
|---|
| 52 | failed = 'setup' |
|---|
| 53 | if not failed: |
|---|
| 54 | try: |
|---|
| 55 | self.test() |
|---|
| 56 | except: |
|---|
| 57 | self.handle(sys.exc_info()) |
|---|
| 58 | failed = 'test' |
|---|
| 59 | try: |
|---|
| 60 | self.teardown() |
|---|
| 61 | except: |
|---|
| 62 | self.handle(sys.exc_info()) |
|---|
| 63 | if not failed: |
|---|
| 64 | failed = 'teardown' |
|---|
| 65 | if not failed: |
|---|
| 66 | self.log.info('success') |
|---|
| 67 | else: |
|---|
| 68 | self.log.error(failed + ' failed') |
|---|
| 69 | sys.exit(1) |
|---|
| 70 | |
|---|
| 71 | def handle(self, failure): |
|---|
| 72 | self.log.error('exception:', exc_info=failure) |
|---|
| 73 | if '--no-wait' not in sys.argv: |
|---|
| 74 | print 'waiting for acknowledgement' |
|---|
| 75 | raw_input() |
|---|
| 76 | |
|---|
| 77 | class TestMultipleServers(BaseTest): |
|---|
| 78 | def setup(self): |
|---|
| 79 | super(TestMultipleServers, self).setup() |
|---|
| 80 | |
|---|
| 81 | self.nservers = 3 |
|---|
| 82 | |
|---|
| 83 | self.cluster = cluster.cluster(spec=[ztest.kdc] + [cluster.host]*(self.nservers+1)) # client + n servers |
|---|
| 84 | self.cluster.setup() |
|---|
| 85 | |
|---|
| 86 | self.hosts = [testmachine for (testmachine, prototype) in self.cluster.machines[cluster.host]] |
|---|
| 87 | self.servers = self.hosts[1:] |
|---|
| 88 | self.client = self.hosts[0] |
|---|
| 89 | self.kdc = self.cluster.machines[ztest.kdc][0][1] |
|---|
| 90 | |
|---|
| 91 | for h in self.hosts: |
|---|
| 92 | h.putstr('/etc/krb5.conf', self.kdc.krb5_conf) |
|---|
| 93 | h.script('mkdir /etc/zephyr') |
|---|
| 94 | h.putstr('/etc/zephyr/server.list', '\n'.join(t.params['hostname'] for t in self.servers) + '\n') |
|---|
| 95 | |
|---|
| 96 | ktname = self.kdc.keytab('zephyr/zephyr') |
|---|
| 97 | |
|---|
| 98 | for t in self.servers: |
|---|
| 99 | t.putstr('/etc/rsyslog.d/zephyrlog.conf', 'local6.* /var/log/zephyr.log\n') |
|---|
| 100 | t.script('/etc/init.d/rsyslog restart') |
|---|
| 101 | t.putfile(ktname, '/etc/zephyr/krb5.keytab') |
|---|
| 102 | t.script('touch /etc/zephyr/srvtab') # sigh, packaging bug |
|---|
| 103 | |
|---|
| 104 | #XXX need to cons up an apt repository |
|---|
| 105 | packagepath = '/home/kcr/src' |
|---|
| 106 | packageversion = '3.0.1~rc.HEAD' |
|---|
| 107 | arch = 'i386' |
|---|
| 108 | serverpackages = ['libzephyr4-krb5', 'zephyr-server-krb5', 'zephyr-clients'] |
|---|
| 109 | clientpackages = ['libzephyr4-krb5', 'zephyr-clients'] |
|---|
| 110 | serverdebs = [package + '_' + packageversion + '_' + arch + '.deb' |
|---|
| 111 | for package in serverpackages] |
|---|
| 112 | clientdebs = [package + '_' + packageversion + '_' + arch + '.deb' |
|---|
| 113 | for package in clientpackages] |
|---|
| 114 | |
|---|
| 115 | for t in self.servers: |
|---|
| 116 | for deb in serverdebs: |
|---|
| 117 | t.putfile(os.path.join(packagepath, deb), '/tmp') |
|---|
| 118 | t.script('DEBIAN_FRONTEND=noninteractive dpkg -i ' + os.path.join('/tmp', deb)) |
|---|
| 119 | time.sleep(1) # let things settle |
|---|
| 120 | |
|---|
| 121 | for deb in clientdebs: |
|---|
| 122 | self.client.putfile(os.path.join(packagepath, deb), '/tmp') |
|---|
| 123 | self.client.script('DEBIAN_FRONTEND=noninteractive dpkg -i ' + os.path.join('/tmp', deb)) |
|---|
| 124 | |
|---|
| 125 | self.serverwait(30) |
|---|
| 126 | def test(self): |
|---|
| 127 | super(TestMultipleServers, self).test() |
|---|
| 128 | self.verify_client() |
|---|
| 129 | |
|---|
| 130 | def teardown(self): |
|---|
| 131 | super(TestMultipleServers, self).teardown() |
|---|
| 132 | if self.servers: |
|---|
| 133 | for t in self.servers: |
|---|
| 134 | if t.alive: |
|---|
| 135 | t.getfile('/var/log/zephyr.log', t.params['hostname'] + '.zephyr.log') |
|---|
| 136 | self.cluster.teardown() |
|---|
| 137 | |
|---|
| 138 | def verify_client(self): |
|---|
| 139 | self.client.script(['echo test2 | kinit test2', |
|---|
| 140 | 'zwgc -ttymode > /tmp/zwgcout', |
|---|
| 141 | 'zwrite test2 -m FROTNITZ', |
|---|
| 142 | 'zctl wg_exit',]) |
|---|
| 143 | |
|---|
| 144 | ret, stdout, stderr = self.client.run('cat /tmp/zwgcout') |
|---|
| 145 | |
|---|
| 146 | assert 'FROTNITZ' in stdout, 'not receiving zephyrgrams' |
|---|
| 147 | assert 'Authentic' in stdout, 'zephyrgram not authentic' |
|---|
| 148 | |
|---|
| 149 | ret, stdout, stderr = self.client.run('ps gax | grep -v grep | grep zwgc') |
|---|
| 150 | |
|---|
| 151 | assert 'zwgc' not in stdout, 'zwgc still running' |
|---|
| 152 | |
|---|
| 153 | def serverwait(self, delay=120): |
|---|
| 154 | for i in range(delay / 5): |
|---|
| 155 | time.sleep(5) |
|---|
| 156 | ret, stdout, stderr = self.client.run('zstat') |
|---|
| 157 | if stdout.count('SERV_UP') == self.nservers: |
|---|
| 158 | break |
|---|
| 159 | self.log.info('servers not up yet') |
|---|
| 160 | else: |
|---|
| 161 | assert False, 'server failed to restart in a timely fashion' |
|---|
| 162 | |
|---|
| 163 | |
|---|
| 164 | class Zwgc(object): |
|---|
| 165 | def __init__(self, other): |
|---|
| 166 | self.counter = itertools.count().next |
|---|
| 167 | self.client = other.client |
|---|
| 168 | self.log = other.log |
|---|
| 169 | self.nservers = other.nservers |
|---|
| 170 | self.wgfile = '/tmp/wg.test.%d' % self.counter() |
|---|
| 171 | self.z = subprocess.Popen(self.client.params['ssh'].split() + |
|---|
| 172 | ['env', 'WGFILE=' + self.wgfile, 'zwgc', '-ttymode', '-nofork'], |
|---|
| 173 | stdin=open('/dev/null'), stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
|---|
| 174 | fcntl.fcntl(self.z.stdout.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) |
|---|
| 175 | fcntl.fcntl(self.z.stderr.fileno(), fcntl.F_SETFL, os.O_NONBLOCK) |
|---|
| 176 | time.sleep(1) |
|---|
| 177 | def read(self): |
|---|
| 178 | try: |
|---|
| 179 | return self.z.stdout.read() |
|---|
| 180 | except IOError, e: |
|---|
| 181 | if e.errno != errno.EAGAIN: |
|---|
| 182 | raise |
|---|
| 183 | return None |
|---|
| 184 | def receivetest(self): |
|---|
| 185 | for i in xrange(self.nservers+1): |
|---|
| 186 | message = 'foobly%d' % self.counter() |
|---|
| 187 | self.client.script('zwrite test2 -m '+message) |
|---|
| 188 | time.sleep(.25) |
|---|
| 189 | received = self.read() |
|---|
| 190 | self.log.info('received %s', repr(received)) |
|---|
| 191 | assert message in received |
|---|
| 192 | self.client.run('zctl new_server') |
|---|
| 193 | time.sleep(.5) |
|---|
| 194 | def ret(self): |
|---|
| 195 | 'not robust' |
|---|
| 196 | retval, stdout, stderr = self.client.run('env WGFILE=%s zctl ret' % self.wgfile) |
|---|
| 197 | return [tuple(l.split(' ')[1::2]) for l in stdout.splitlines()] |
|---|
| 198 | def sub(self, class_, instance='*', recipient='*'): |
|---|
| 199 | self.client.script('env WGFILE=%s zctl sub %s %s %s' % |
|---|
| 200 | (self.wgfile, class_, instance, recipient)) |
|---|
| 201 | return (class_, instance, recipient) in self.ret() |
|---|
| 202 | def subclasses(self, classlist): |
|---|
| 203 | self.client.putstr('/tmp/subs', ''.join('%s,*,*\n' % (class_,) for class_ in classlist)) |
|---|
| 204 | self.client.script('env WGFILE=%s zctl load /tmp/subs' % (self.wgfile,)) |
|---|
| 205 | def shutdown(self): |
|---|
| 206 | self.client.script('env WGFILE=%s zctl wg_exit' % self.wgfile) |
|---|
| 207 | def zwgc(self): |
|---|
| 208 | return self.Zwgc(self) |
|---|
| 209 | |
|---|
| 210 | if __name__ == '__main__': |
|---|
| 211 | TestMultipleServers().run() |
|---|