wintest flush DNS on Windows clients to improve reliablity
[kai/samba.git] / wintest / wintest.py
1 #!/usr/bin/env python
2
3 '''automated testing library for testing Samba against windows'''
4
5 import pexpect, subprocess
6 import optparse
7 import sys, os, time, re
8
9 class wintest():
10     '''testing of Samba against windows VMs'''
11
12     def __init__(self):
13         self.vars = {}
14         self.list_mode = False
15         self.vms = None
16         os.putenv('PYTHONUNBUFFERED', '1')
17         self.parser = optparse.OptionParser("wintest")
18
19     def check_prerequesites(self):
20         self.info("Checking prerequesites")
21         self.setvar('HOSTNAME', self.cmd_output("hostname -s").strip())
22         if os.getuid() != 0:
23             raise Exception("You must run this script as root")
24         self.run_cmd('ifconfig ${INTERFACE} ${INTERFACE_NET} up')
25         if self.getvar('INTERFACE_IPV6'):
26             self.run_cmd('ifconfig ${INTERFACE} inet6 del ${INTERFACE_IPV6}/64', checkfail=False)
27             self.run_cmd('ifconfig ${INTERFACE} inet6 add ${INTERFACE_IPV6}/64 up')
28
29     def stop_vms(self):
30         '''Shut down any existing alive VMs, so they do not collide with what we are doing'''
31         self.info('Shutting down any of our VMs already running')
32         vms = self.get_vms()
33         for v in vms:
34             self.vm_poweroff(v, checkfail=False)
35
36     def setvar(self, varname, value):
37         '''set a substitution variable'''
38         self.vars[varname] = value
39
40     def getvar(self, varname):
41         '''return a substitution variable'''
42         if not varname in self.vars:
43             return None
44         return self.vars[varname]
45
46     def setwinvars(self, vm, prefix='WIN'):
47         '''setup WIN_XX vars based on a vm name'''
48         for v in ['VM', 'HOSTNAME', 'USER', 'PASS', 'SNAPSHOT', 'REALM', 'DOMAIN', 'IP']:
49             vname = '%s_%s' % (vm, v)
50             if vname in self.vars:
51                 self.setvar("%s_%s" % (prefix,v), self.substitute("${%s}" % vname))
52             else:
53                 self.vars.pop("%s_%s" % (prefix,v), None)
54
55         if self.getvar("WIN_REALM"):
56             self.setvar("WIN_REALM", self.getvar("WIN_REALM").upper())
57             self.setvar("WIN_LCREALM", self.getvar("WIN_REALM").lower())
58             dnsdomain = self.getvar("WIN_REALM")
59             self.setvar("WIN_BASEDN", "DC=" + dnsdomain.replace(".", ",DC="))
60
61     def info(self, msg):
62         '''print some information'''
63         if not self.list_mode:
64             print(self.substitute(msg))
65
66     def load_config(self, fname):
67         '''load the config file'''
68         f = open(fname)
69         for line in f:
70             line = line.strip()
71             if len(line) == 0 or line[0] == '#':
72                 continue
73             colon = line.find(':')
74             if colon == -1:
75                 raise RuntimeError("Invalid config line '%s'" % line)
76             varname = line[0:colon].strip()
77             value   = line[colon+1:].strip()
78             self.setvar(varname, value)
79
80     def list_steps_mode(self):
81         '''put wintest in step listing mode'''
82         self.list_mode = True
83
84     def set_skip(self, skiplist):
85         '''set a list of tests to skip'''
86         self.skiplist = skiplist.split(',')
87
88     def set_vms(self, vms):
89         '''set a list of VMs to test'''
90         if vms is not None:
91             self.vms = vms.split(',')
92
93     def skip(self, step):
94         '''return True if we should skip a step'''
95         if self.list_mode:
96             print("\t%s" % step)
97             return True
98         return step in self.skiplist
99
100     def substitute(self, text):
101         """Substitute strings of the form ${NAME} in text, replacing
102         with substitutions from vars.
103         """
104         if isinstance(text, list):
105             ret = text[:]
106             for i in range(len(ret)):
107                 ret[i] = self.substitute(ret[i])
108             return ret
109
110         """We may have objects such as pexpect.EOF that are not strings"""
111         if not isinstance(text, str):
112             return text
113         while True:
114             var_start = text.find("${")
115             if var_start == -1:
116                 return text
117             var_end = text.find("}", var_start)
118             if var_end == -1:
119                 return text
120             var_name = text[var_start+2:var_end]
121             if not var_name in self.vars:
122                 raise RuntimeError("Unknown substitution variable ${%s}" % var_name)
123             text = text.replace("${%s}" % var_name, self.vars[var_name])
124         return text
125
126     def have_var(self, varname):
127         '''see if a variable has been set'''
128         return varname in self.vars
129
130     def have_vm(self, vmname):
131         '''see if a VM should be used'''
132         if not self.have_var(vmname + '_VM'):
133             return False
134         if self.vms is None:
135             return True
136         return vmname in self.vms
137
138     def putenv(self, key, value):
139         '''putenv with substitution'''
140         os.putenv(key, self.substitute(value))
141
142     def chdir(self, dir):
143         '''chdir with substitution'''
144         os.chdir(self.substitute(dir))
145
146     def del_files(self, dirs):
147         '''delete all files in the given directory'''
148         for d in dirs:
149             self.run_cmd("find %s -type f | xargs rm -f" % d)
150
151     def write_file(self, filename, text, mode='w'):
152         '''write to a file'''
153         f = open(self.substitute(filename), mode=mode)
154         f.write(self.substitute(text))
155         f.close()
156
157     def run_cmd(self, cmd, dir=".", show=None, output=False, checkfail=True):
158         '''run a command'''
159         cmd = self.substitute(cmd)
160         if isinstance(cmd, list):
161             self.info('$ ' + " ".join(cmd))
162         else:
163             self.info('$ ' + cmd)
164         if output:
165             return subprocess.Popen([cmd], shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=dir).communicate()[0]
166         if isinstance(cmd, list):
167             shell=False
168         else:
169             shell=True
170         if checkfail:
171             return subprocess.check_call(cmd, shell=shell, cwd=dir)
172         else:
173             return subprocess.call(cmd, shell=shell, cwd=dir)
174
175
176     def run_child(self, cmd, dir="."):
177         '''create a child and return the Popen handle to it'''
178         cwd = os.getcwd()
179         cmd = self.substitute(cmd)
180         if isinstance(cmd, list):
181             self.info('$ ' + " ".join(cmd))
182         else:
183             self.info('$ ' + cmd)
184         if isinstance(cmd, list):
185             shell=False
186         else:
187             shell=True
188         os.chdir(dir)
189         ret = subprocess.Popen(cmd, shell=shell, stderr=subprocess.STDOUT)
190         os.chdir(cwd)
191         return ret
192
193     def cmd_output(self, cmd):
194         '''return output from and command'''
195         cmd = self.substitute(cmd)
196         return self.run_cmd(cmd, output=True)
197
198     def cmd_contains(self, cmd, contains, nomatch=False, ordered=False, regex=False,
199                      casefold=True):
200         '''check that command output contains the listed strings'''
201
202         if isinstance(contains, str):
203             contains = [contains]
204
205         out = self.cmd_output(cmd)
206         self.info(out)
207         for c in self.substitute(contains):
208             if regex:
209                 if casefold:
210                     c = c.upper()
211                     out = out.upper()
212                 m = re.search(c, out)
213                 if m is None:
214                     start = -1
215                     end = -1
216                 else:
217                     start = m.start()
218                     end = m.end()
219             elif casefold:
220                 start = out.upper().find(c.upper())
221                 end = start + len(c)
222             else:
223                 start = out.find(c)
224                 end = start + len(c)
225             if nomatch:
226                 if start != -1:
227                     raise RuntimeError("Expected to not see %s in %s" % (c, cmd))
228             else:
229                 if start == -1:
230                     raise RuntimeError("Expected to see %s in %s" % (c, cmd))
231             if ordered and start != -1:
232                 out = out[end:]
233
234     def retry_cmd(self, cmd, contains, retries=30, delay=2, wait_for_fail=False,
235                   ordered=False, regex=False, casefold=True):
236         '''retry a command a number of times'''
237         while retries > 0:
238             try:
239                 self.cmd_contains(cmd, contains, nomatch=wait_for_fail,
240                                   ordered=ordered, regex=regex, casefold=casefold)
241                 return
242             except:
243                 time.sleep(delay)
244                 retries -= 1
245                 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
246         raise RuntimeError("Failed to find %s" % contains)
247
248     def pexpect_spawn(self, cmd, timeout=60, crlf=True, casefold=True):
249         '''wrapper around pexpect spawn'''
250         cmd = self.substitute(cmd)
251         self.info("$ " + cmd)
252         ret = pexpect.spawn(cmd, logfile=sys.stdout, timeout=timeout)
253
254         def sendline_sub(line):
255             line = self.substitute(line)
256             if crlf:
257                 line = line.replace('\n', '\r\n') + '\r'
258             return ret.old_sendline(line)
259
260         def expect_sub(line, timeout=ret.timeout, casefold=casefold):
261             line = self.substitute(line)
262             if casefold:
263                 if isinstance(line, list):
264                     for i in range(len(line)):
265                         if isinstance(line[i], str):
266                             line[i] = '(?i)' + line[i]
267                 elif isinstance(line, str):
268                     line = '(?i)' + line
269             return ret.old_expect(line, timeout=timeout)
270
271         ret.old_sendline = ret.sendline
272         ret.sendline = sendline_sub
273         ret.old_expect = ret.expect
274         ret.expect = expect_sub
275
276         return ret
277
278     def get_nameserver(self):
279         '''Get the current nameserver from /etc/resolv.conf'''
280         child = self.pexpect_spawn('cat /etc/resolv.conf', crlf=False)
281         i = child.expect(['Generated by wintest', 'nameserver'])
282         if i == 0:
283             child.expect('your original resolv.conf')
284             child.expect('nameserver')
285         child.expect('\d+.\d+.\d+.\d+')
286         return child.after
287
288     def rndc_cmd(self, cmd, checkfail=True):
289         '''run a rndc command'''
290         self.run_cmd("${RNDC} -c ${PREFIX}/etc/rndc.conf %s" % cmd, checkfail=checkfail)
291
292     def named_supports_gssapi_keytab(self):
293         '''see if named supports tkey-gssapi-keytab'''
294         self.write_file("${PREFIX}/named.conf.test",
295                      'options { tkey-gssapi-keytab "test"; };')
296         try:
297             self.run_cmd("${NAMED_CHECKCONF} ${PREFIX}/named.conf.test")
298         except subprocess.CalledProcessError:
299             return False
300         return True
301
302     def set_nameserver(self, nameserver):
303         '''set the nameserver in resolv.conf'''
304         self.write_file("/etc/resolv.conf.wintest", '''
305 # Generated by wintest, the Samba v Windows automated testing system
306 nameserver %s
307
308 # your original resolv.conf appears below:
309 ''' % self.substitute(nameserver))
310         child = self.pexpect_spawn("cat /etc/resolv.conf", crlf=False)
311         i = child.expect(['your original resolv.conf appears below:', pexpect.EOF])
312         if i == 0:
313             child.expect(pexpect.EOF)
314         contents = child.before.lstrip().replace('\r', '')
315         self.write_file('/etc/resolv.conf.wintest', contents, mode='a')
316         self.write_file('/etc/resolv.conf.wintest-bak', contents)
317         self.run_cmd("mv -f /etc/resolv.conf.wintest /etc/resolv.conf")
318         self.resolv_conf_backup = '/etc/resolv.conf.wintest-bak';
319
320     def configure_bind(self, kerberos_support=False, include=None):
321         self.chdir('${PREFIX}')
322
323         nameserver = self.get_nameserver()
324         if nameserver == self.getvar('INTERFACE_IP'):
325             raise RuntimeError("old /etc/resolv.conf must not contain %s as a nameserver, this will create loops with the generated dns configuration" % nameserver)
326         self.setvar('DNSSERVER', nameserver)
327
328         if self.getvar('INTERFACE_IPV6'):
329             ipv6_listen = 'listen-on-v6 port 53 { ${INTERFACE_IPV6}; };'
330         else:
331             ipv6_listen = ''
332         self.setvar('BIND_LISTEN_IPV6', ipv6_listen)
333
334         if not kerberos_support:
335             self.setvar("NAMED_TKEY_OPTION", "")
336         else:
337             if self.named_supports_gssapi_keytab():
338                 self.setvar("NAMED_TKEY_OPTION",
339                          'tkey-gssapi-keytab "${PREFIX}/private/dns.keytab";')
340             else:
341                 self.info("LCREALM=${LCREALM}")
342                 self.setvar("NAMED_TKEY_OPTION",
343                          '''tkey-gssapi-credential "DNS/${LCREALM}";
344                             tkey-domain "${LCREALM}";
345                  ''')
346             self.putenv("KRB5_CONFIG", '${PREFIX}/private/krb5.conf')
347             self.putenv('KEYTAB_FILE', '${PREFIX}/private/dns.keytab')
348             self.putenv('KRB5_KTNAME', '${PREFIX}/private/dns.keytab')
349
350         if include:
351             self.setvar("NAMED_INCLUDE", 'include "%s";' % include)
352         else:
353             self.setvar("NAMED_INCLUDE", '')
354
355         self.run_cmd("mkdir -p ${PREFIX}/etc")
356
357         self.write_file("etc/named.conf", '''
358 options {
359         listen-on port 53 { ${INTERFACE_IP};  };
360         ${BIND_LISTEN_IPV6}
361         directory       "${PREFIX}/var/named";
362         dump-file       "${PREFIX}/var/named/data/cache_dump.db";
363         pid-file        "${PREFIX}/var/named/named.pid";
364         statistics-file "${PREFIX}/var/named/data/named_stats.txt";
365         memstatistics-file "${PREFIX}/var/named/data/named_mem_stats.txt";
366         allow-query     { any; };
367         recursion yes;
368         ${NAMED_TKEY_OPTION}
369         max-cache-ttl 10;
370         max-ncache-ttl 10;
371
372         forward only;
373         forwarders {
374                   ${DNSSERVER};
375         };
376
377 };
378
379 key "rndc-key" {
380         algorithm hmac-md5;
381         secret "lA/cTrno03mt5Ju17ybEYw==";
382 };
383
384 controls {
385         inet ${INTERFACE_IP} port 953
386         allow { any; } keys { "rndc-key"; };
387 };
388
389 ${NAMED_INCLUDE}
390 ''')
391
392         # add forwarding for the windows domains
393         domains = self.get_domains()
394         for d in domains:
395             self.write_file('etc/named.conf',
396                          '''
397 zone "%s" IN {
398       type forward;
399       forward only;
400       forwarders {
401          %s;
402       };
403 };
404 ''' % (d, domains[d]),
405                      mode='a')
406
407
408         self.write_file("etc/rndc.conf", '''
409 # Start of rndc.conf
410 key "rndc-key" {
411         algorithm hmac-md5;
412         secret "lA/cTrno03mt5Ju17ybEYw==";
413 };
414
415 options {
416         default-key "rndc-key";
417         default-server  ${INTERFACE_IP};
418         default-port 953;
419 };
420 ''')
421
422
423     def stop_bind(self):
424         '''Stop our private BIND from listening and operating'''
425         self.rndc_cmd("stop", checkfail=False)
426         self.port_wait("${INTERFACE_IP}", 53, wait_for_fail=True)
427
428         self.run_cmd("rm -rf var/named")
429
430
431     def start_bind(self):
432         '''restart the test environment version of bind'''
433         self.info("Restarting bind9")
434         self.chdir('${PREFIX}')
435
436         self.set_nameserver(self.getvar('INTERFACE_IP'))
437
438         self.run_cmd("mkdir -p var/named/data")
439         self.run_cmd("chown -R ${BIND_USER} var/named")
440
441         self.bind_child = self.run_child("${BIND9} -u ${BIND_USER} -n 1 -c ${PREFIX}/etc/named.conf -g")
442
443         self.port_wait("${INTERFACE_IP}", 53)
444         self.rndc_cmd("flush")
445
446     def restart_bind(self, kerberos_support=False, include=None):
447         self.configure_bind(keberos_support=kerberos_support, include=include)
448         self.stop_bind()
449         self.start_bind()
450
451     def restore_resolv_conf(self):
452         '''restore the /etc/resolv.conf after testing is complete'''
453         if getattr(self, 'resolv_conf_backup', False):
454             self.info("restoring /etc/resolv.conf")
455             self.run_cmd("mv -f %s /etc/resolv.conf" % self.resolv_conf_backup)
456
457
458     def vm_poweroff(self, vmname, checkfail=True):
459         '''power off a VM'''
460         self.setvar('VMNAME', vmname)
461         self.run_cmd("${VM_POWEROFF}", checkfail=checkfail)
462
463     def vm_reset(self, vmname):
464         '''reset a VM'''
465         self.setvar('VMNAME', vmname)
466         self.run_cmd("${VM_RESET}")
467
468     def vm_restore(self, vmname, snapshot):
469         '''restore a VM'''
470         self.setvar('VMNAME', vmname)
471         self.setvar('SNAPSHOT', snapshot)
472         self.run_cmd("${VM_RESTORE}")
473
474     def ping_wait(self, hostname):
475         '''wait for a hostname to come up on the network'''
476         hostname = self.substitute(hostname)
477         loops=10
478         while loops > 0:
479             try:
480                 self.run_cmd("ping -c 1 -w 10 %s" % hostname)
481                 break
482             except:
483                 loops = loops - 1
484         if loops == 0:
485             raise RuntimeError("Failed to ping %s" % hostname)
486         self.info("Host %s is up" % hostname)
487
488     def port_wait(self, hostname, port, retries=200, delay=3, wait_for_fail=False):
489         '''wait for a host to come up on the network'''
490         self.retry_cmd("nc -v -z -w 1 %s %u" % (hostname, port), ['succeeded'],
491                        retries=retries, delay=delay, wait_for_fail=wait_for_fail)
492
493     def run_net_time(self, child):
494         '''run net time on windows'''
495         child.sendline("net time \\\\${HOSTNAME} /set")
496         child.expect("Do you want to set the local computer")
497         child.sendline("Y")
498         child.expect("The command completed successfully")
499
500     def run_date_time(self, child, time_tuple=None):
501         '''run date and time on windows'''
502         if time_tuple is None:
503             time_tuple = time.localtime()
504         child.sendline("date")
505         child.expect("Enter the new date:")
506         i = child.expect(["dd-mm-yy", "mm-dd-yy"])
507         if i == 0:
508             child.sendline(time.strftime("%d-%m-%y", time_tuple))
509         else:
510             child.sendline(time.strftime("%m-%d-%y", time_tuple))
511         child.expect("C:")
512         child.sendline("time")
513         child.expect("Enter the new time:")
514         child.sendline(time.strftime("%H:%M:%S", time_tuple))
515         child.expect("C:")
516
517     def get_ipconfig(self, child):
518         '''get the IP configuration of the child'''
519         child.sendline("ipconfig /all")
520         child.expect('Ethernet adapter ')
521         child.expect("[\w\s]+")
522         self.setvar("WIN_NIC", child.after)
523         child.expect(['IPv4 Address', 'IP Address'])
524         child.expect('\d+.\d+.\d+.\d+')
525         self.setvar('WIN_IPV4_ADDRESS', child.after)
526         child.expect('Subnet Mask')
527         child.expect('\d+.\d+.\d+.\d+')
528         self.setvar('WIN_SUBNET_MASK', child.after)
529         child.expect('Default Gateway')
530         child.expect('\d+.\d+.\d+.\d+')
531         self.setvar('WIN_DEFAULT_GATEWAY', child.after)
532         child.expect("C:")
533
534     def get_is_dc(self, child):
535         '''check if a windows machine is a domain controller'''
536         child.sendline("dcdiag")
537         i = child.expect(["is not a Directory Server",
538                           "is not recognized as an internal or external command",
539                           "Home Server = ",
540                           "passed test Replications"])
541         if i == 0:
542             return False
543         if i == 1 or i == 3:
544             child.expect("C:")
545             child.sendline("net config Workstation")
546             child.expect("Workstation domain")
547             child.expect('[\S]+')
548             domain = child.after
549             i = child.expect(["Workstation Domain DNS Name", "Logon domain"])
550             '''If we get the Logon domain first, we are not in an AD domain'''
551             if i == 1:
552                 return False
553             if domain.upper() == self.getvar("WIN_DOMAIN").upper():
554                 return True
555
556         child.expect('[\S]+')
557         hostname = child.after
558         if hostname.upper() == self.getvar("WIN_HOSTNAME").upper():
559             return True
560
561     def set_noexpire(self, child, username):
562         '''Ensure this user's password does not expire'''
563         child.sendline('wmic useraccount where name="%s" set PasswordExpires=FALSE' % username)
564         child.expect("update successful")
565         child.expect("C:")
566
567     def run_tlntadmn(self, child):
568         '''remove the annoying telnet restrictions'''
569         child.sendline('tlntadmn config maxconn=1024')
570         child.expect("The settings were successfully updated")
571         child.expect("C:")
572
573     def disable_firewall(self, child):
574         '''remove the annoying firewall'''
575         child.sendline('netsh advfirewall set allprofiles state off')
576         i = child.expect(["Ok", "The following command was not found: advfirewall set allprofiles state off"])
577         child.expect("C:")
578         if i == 1:
579             child.sendline('netsh firewall set opmode mode = DISABLE profile = ALL')
580             i = child.expect(["Ok", "The following command was not found"])
581             if i != 0:
582                 self.info("Firewall disable failed - ignoring")
583             child.expect("C:")
584  
585     def set_dns(self, child):
586         child.sendline('netsh interface ip set dns "${WIN_NIC}" static ${INTERFACE_IP} primary')
587         i = child.expect(['C:', pexpect.EOF, pexpect.TIMEOUT], timeout=5)
588         if i > 0:
589             return True
590         else:
591             return False
592
593     def set_ip(self, child):
594         """fix the IP address to the same value it had when we
595         connected, but don't use DHCP, and force the DNS server to our
596         DNS server.  This allows DNS updates to run"""
597         self.get_ipconfig(child)
598         if self.getvar("WIN_IPV4_ADDRESS") != self.getvar("WIN_IP"):
599             raise RuntimeError("ipconfig address %s != nmblookup address %s" % (self.getvar("WIN_IPV4_ADDRESS"),
600                                                                                 self.getvar("WIN_IP")))
601         child.sendline('netsh')
602         child.expect('netsh>')
603         child.sendline('offline')
604         child.expect('netsh>')
605         child.sendline('routing ip add persistentroute dest=0.0.0.0 mask=0.0.0.0 name="${WIN_NIC}" nhop=${WIN_DEFAULT_GATEWAY}')
606         child.expect('netsh>')
607         child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1 store=persistent')
608         i = child.expect(['The syntax supplied for this command is not valid. Check help for the correct syntax', 'netsh>', pexpect.EOF, pexpect.TIMEOUT], timeout=5)
609         if i == 0:
610             child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1')
611             child.expect('netsh>')
612         child.sendline('commit')
613         child.sendline('online')
614         child.sendline('exit')
615
616         child.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=5)
617         return True
618
619
620     def resolve_ip(self, hostname, retries=60, delay=5):
621         '''resolve an IP given a hostname, assuming NBT'''
622         while retries > 0:
623             child = self.pexpect_spawn("bin/nmblookup %s" % hostname)
624             i = child.expect(['\d+.\d+.\d+.\d+', "Lookup failed"])
625             if i == 0:
626                 return child.after
627             retries -= 1
628             time.sleep(delay)
629             self.info("retrying (retries=%u delay=%u)" % (retries, delay))
630         raise RuntimeError("Failed to resolve IP of %s" % hostname)
631
632
633     def open_telnet(self, hostname, username, password, retries=60, delay=5, set_time=False, set_ip=False,
634                     disable_firewall=True, run_tlntadmn=True, set_noexpire=False):
635         '''open a telnet connection to a windows server, return the pexpect child'''
636         set_route = False
637         set_dns = False
638         if self.getvar('WIN_IP'):
639             ip = self.getvar('WIN_IP')
640         else:
641             ip = self.resolve_ip(hostname)
642             self.setvar('WIN_IP', ip)
643         while retries > 0:
644             child = self.pexpect_spawn("telnet " + ip + " -l '" + username + "'")
645             i = child.expect(["Welcome to Microsoft Telnet Service",
646                               "Denying new connections due to the limit on number of connections",
647                               "No more connections are allowed to telnet server",
648                               "Unable to connect to remote host",
649                               "No route to host",
650                               "Connection refused",
651                               pexpect.EOF])
652             if i != 0:
653                 child.close()
654                 time.sleep(delay)
655                 retries -= 1
656                 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
657                 continue
658             child.expect("password:")
659             child.sendline(password)
660             i = child.expect(["C:",
661                               "Denying new connections due to the limit on number of connections",
662                               "No more connections are allowed to telnet server",
663                               "Unable to connect to remote host",
664                               "No route to host",
665                               "Connection refused",
666                               pexpect.EOF])
667             if i != 0:
668                 child.close()
669                 time.sleep(delay)
670                 retries -= 1
671                 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
672                 continue
673             if set_dns:
674                 set_dns = False
675                 if self.set_dns(child):
676                     continue;
677             if set_route:
678                 child.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
679                 child.expect("C:")
680                 set_route = False
681             if set_time:
682                 self.run_date_time(child, None)
683                 set_time = False
684             if run_tlntadmn:
685                 self.run_tlntadmn(child)
686                 run_tlntadmn = False
687             if set_noexpire:
688                 self.set_noexpire(child, username)
689                 set_noexpire = False
690             if disable_firewall:
691                 self.disable_firewall(child)
692                 disable_firewall = False
693             if set_ip:
694                 set_ip = False
695                 if self.set_ip(child):
696                     set_route = True
697                     set_dns = True
698                 continue
699             return child
700         raise RuntimeError("Failed to connect with telnet")
701
702     def kinit(self, username, password):
703         '''use kinit to setup a credentials cache'''
704         self.run_cmd("kdestroy")
705         self.putenv('KRB5CCNAME', "${PREFIX}/ccache.test")
706         username = self.substitute(username)
707         s = username.split('@')
708         if len(s) > 0:
709             s[1] = s[1].upper()
710         username = '@'.join(s)
711         child = self.pexpect_spawn('kinit ' + username)
712         child.expect("Password")
713         child.sendline(password)
714         child.expect(pexpect.EOF)
715         child.close()
716         if child.exitstatus != 0:
717             raise RuntimeError("kinit failed with status %d" % child.exitstatus)
718
719     def get_domains(self):
720         '''return a dictionary of DNS domains and IPs for named.conf'''
721         ret = {}
722         for v in self.vars:
723             if v[-6:] == "_REALM":
724                 base = v[:-6]
725                 if base + '_IP' in self.vars:
726                     ret[self.vars[base + '_REALM']] = self.vars[base + '_IP']
727         return ret
728
729     def wait_reboot(self, retries=3):
730         '''wait for a VM to reboot'''
731
732         # first wait for it to shutdown
733         self.port_wait("${WIN_IP}", 139, wait_for_fail=True, delay=6)
734
735         # now wait for it to come back. If it fails to come back
736         # then try resetting it
737         while retries > 0:
738             try:
739                 self.port_wait("${WIN_IP}", 139)
740                 return
741             except:
742                 retries -= 1
743                 self.vm_reset("${WIN_VM}")
744                 self.info("retrying reboot (retries=%u)" % retries)
745         raise RuntimeError(self.substitute("VM ${WIN_VM} failed to reboot"))
746
747     def get_vms(self):
748         '''return a dictionary of all the configured VM names'''
749         ret = []
750         for v in self.vars:
751             if v[-3:] == "_VM":
752                 ret.append(self.vars[v])
753         return ret
754
755
756     def run_dcpromo_as_first_dc(self, vm, func_level=None):
757         self.setwinvars(vm)
758         self.info("Configuring a windows VM ${WIN_VM} at the first DC in the domain using dcpromo")
759         child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_time=True)
760         if self.get_is_dc(child):
761             return
762
763         if func_level == '2008r2':
764             self.setvar("FUNCTION_LEVEL_INT", str(4))
765         elif func_level == '2003':
766             self.setvar("FUNCTION_LEVEL_INT", str(1))
767         else:
768             self.setvar("FUNCTION_LEVEL_INT", str(0))
769
770         child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_ip=True, set_noexpire=True)
771
772         """This server must therefore not yet be a directory server, so we must promote it"""
773         child.sendline("copy /Y con answers.txt")
774         child.sendline('''
775 [DCInstall]
776 ; New forest promotion
777 ReplicaOrNewDomain=Domain
778 NewDomain=Forest
779 NewDomainDNSName=${WIN_REALM}
780 ForestLevel=${FUNCTION_LEVEL_INT}
781 DomainNetbiosName=${WIN_DOMAIN}
782 DomainLevel=${FUNCTION_LEVEL_INT}
783 InstallDNS=Yes
784 ConfirmGc=Yes
785 CreateDNSDelegation=No
786 DatabasePath="C:\Windows\NTDS"
787 LogPath="C:\Windows\NTDS"
788 SYSVOLPath="C:\Windows\SYSVOL"
789 ; Set SafeModeAdminPassword to the correct value prior to using the unattend file
790 SafeModeAdminPassword=${WIN_PASS}
791 ; Run-time flags (optional)
792 RebootOnCompletion=No
793 \1a
794 ''')
795         child.expect("copied.")
796         child.expect("C:")
797         child.expect("C:")
798         child.sendline("dcpromo /answer:answers.txt")
799         i = child.expect(["You must restart this computer", "failed", "Active Directory Domain Services was not installed", "C:"], timeout=240)
800         if i == 1 or i == 2:
801             raise Exception("dcpromo failed")
802         child.sendline("shutdown -r -t 0")
803         self.port_wait("${WIN_IP}", 139, wait_for_fail=True)
804         self.port_wait("${WIN_IP}", 139)
805
806
807     def setup(self, testname, subdir):
808         '''setup for main tests, parsing command line'''
809         self.parser.add_option("--conf", type='string', default='', help='config file')
810         self.parser.add_option("--skip", type='string', default='', help='list of steps to skip (comma separated)')
811         self.parser.add_option("--vms", type='string', default=None, help='list of VMs to use (comma separated)')
812         self.parser.add_option("--list", action='store_true', default=False, help='list the available steps')
813         self.parser.add_option("--rebase", action='store_true', default=False, help='do a git pull --rebase')
814         self.parser.add_option("--clean", action='store_true', default=False, help='clean the tree')
815         self.parser.add_option("--prefix", type='string', default=None, help='override install prefix')
816         self.parser.add_option("--sourcetree", type='string', default=None, help='override sourcetree location')
817         self.parser.add_option("--nocleanup", action='store_true', default=False, help='disable cleanup code')
818
819         self.opts, self.args = self.parser.parse_args()
820
821         if not self.opts.conf:
822             print("Please specify a config file with --conf")
823             sys.exit(1)
824
825         # we don't need fsync safety in these tests
826         self.putenv('TDB_NO_FSYNC', '1')
827
828         self.load_config(self.opts.conf)
829
830         self.set_skip(self.opts.skip)
831         self.set_vms(self.opts.vms)
832
833         if self.opts.list:
834             self.list_steps_mode()
835
836         if self.opts.prefix:
837             self.setvar('PREFIX', self.opts.prefix)
838
839         if self.opts.sourcetree:
840             self.setvar('SOURCETREE', self.opts.sourcetree)
841
842         if self.opts.rebase:
843             self.info('rebasing')
844             self.chdir('${SOURCETREE}')
845             self.run_cmd('git pull --rebase')
846
847         if self.opts.clean:
848             self.info('cleaning')
849             self.chdir('${SOURCETREE}/' + subdir)
850             self.run_cmd('make clean')