wintest Share more of the S4 test code with the s3 test
[Samba.git] / wintest / wintest.py
blobaf4588f5c8ee988fbc5a9466c15a5972082e69e3
1 #!/usr/bin/env python
3 '''automated testing library for testing Samba against windows'''
5 import pexpect, subprocess
6 import optparse
7 import sys, os, time, re
9 class wintest():
10 '''testing of Samba against windows VMs'''
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")
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')
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)
36 def setvar(self, varname, value):
37 '''set a substitution variable'''
38 self.vars[varname] = value
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]
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)
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="))
61 def info(self, msg):
62 '''print some information'''
63 if not self.list_mode:
64 print(self.substitute(msg))
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)
80 def list_steps_mode(self):
81 '''put wintest in step listing mode'''
82 self.list_mode = True
84 def set_skip(self, skiplist):
85 '''set a list of tests to skip'''
86 self.skiplist = skiplist.split(',')
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(',')
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
100 def substitute(self, text):
101 """Substitute strings of the form ${NAME} in text, replacing
102 with substitutions from vars.
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
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
126 def have_var(self, varname):
127 '''see if a variable has been set'''
128 return varname in self.vars
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
138 def putenv(self, key, value):
139 '''putenv with substitution'''
140 os.putenv(key, self.substitute(value))
142 def chdir(self, dir):
143 '''chdir with substitution'''
144 os.chdir(self.substitute(dir))
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)
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()
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)
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
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)
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'''
202 if isinstance(contains, str):
203 contains = [contains]
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:]
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)
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)
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)
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)
271 ret.old_sendline = ret.sendline
272 ret.sendline = sendline_sub
273 ret.old_expect = ret.expect
274 ret.expect = expect_sub
276 return ret
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
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)
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
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
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';
320 def configure_bind(self, kerberos_support=False, include=None):
321 self.chdir('${PREFIX}')
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)
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)
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')
350 if include:
351 self.setvar("NAMED_INCLUDE", 'include "%s";' % include)
352 else:
353 self.setvar("NAMED_INCLUDE", '')
355 self.run_cmd("mkdir -p ${PREFIX}/etc")
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;
372 forward only;
373 forwarders {
374 ${DNSSERVER};
379 key "rndc-key" {
380 algorithm hmac-md5;
381 secret "lA/cTrno03mt5Ju17ybEYw==";
384 controls {
385 inet ${INTERFACE_IP} port 953
386 allow { any; } keys { "rndc-key"; };
389 ${NAMED_INCLUDE}
390 ''')
392 # add forwarding for the windows domains
393 domains = self.get_domains()
394 for d in domains:
395 self.write_file('etc/named.conf',
397 zone "%s" IN {
398 type forward;
399 forward only;
400 forwarders {
404 ''' % (d, domains[d]),
405 mode='a')
408 self.write_file("etc/rndc.conf", '''
409 # Start of rndc.conf
410 key "rndc-key" {
411 algorithm hmac-md5;
412 secret "lA/cTrno03mt5Ju17ybEYw==";
415 options {
416 default-key "rndc-key";
417 default-server ${INTERFACE_IP};
418 default-port 953;
420 ''')
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)
428 self.run_cmd("rm -rf var/named")
431 def start_bind(self):
432 '''restart the test environment version of bind'''
433 self.info("Restarting bind9")
434 self.chdir('${PREFIX}')
436 self.set_nameserver(self.getvar('INTERFACE_IP'))
438 self.run_cmd("mkdir -p var/named/data")
439 self.run_cmd("chown -R ${BIND_USER} var/named")
441 self.bind_child = self.run_child("${BIND9} -u ${BIND_USER} -n 1 -c ${PREFIX}/etc/named.conf -g")
443 self.port_wait("${INTERFACE_IP}", 53)
444 self.rndc_cmd("flush")
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()
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)
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)
463 def vm_reset(self, vmname):
464 '''reset a VM'''
465 self.setvar('VMNAME', vmname)
466 self.run_cmd("${VM_RESET}")
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}")
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)
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)
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")
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:")
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:")
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
556 child.expect('[\S]+')
557 hostname = child.after
558 if hostname.upper() == self.getvar("WIN_HOSTNAME").upper():
559 return True
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:")
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:")
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:")
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
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')
616 child.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=5)
617 return True
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)
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")
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)
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
729 def wait_reboot(self, retries=3):
730 '''wait for a VM to reboot'''
732 # first wait for it to shutdown
733 self.port_wait("${WIN_IP}", 139, wait_for_fail=True, delay=6)
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"))
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
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
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))
770 child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_ip=True, set_noexpire=True)
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 \x1a
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)
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')
819 self.opts, self.args = self.parser.parse_args()
821 if not self.opts.conf:
822 print("Please specify a config file with --conf")
823 sys.exit(1)
825 # we don't need fsync safety in these tests
826 self.putenv('TDB_NO_FSYNC', '1')
828 self.load_config(self.opts.conf)
830 self.set_skip(self.opts.skip)
831 self.set_vms(self.opts.vms)
833 if self.opts.list:
834 self.list_steps_mode()
836 if self.opts.prefix:
837 self.setvar('PREFIX', self.opts.prefix)
839 if self.opts.sourcetree:
840 self.setvar('SOURCETREE', self.opts.sourcetree)
842 if self.opts.rebase:
843 self.info('rebasing')
844 self.chdir('${SOURCETREE}')
845 self.run_cmd('git pull --rebase')
847 if self.opts.clean:
848 self.info('cleaning')
849 self.chdir('${SOURCETREE}/' + subdir)
850 self.run_cmd('make clean')