wintest: check netcats exitstatus instead of output
[Samba/gebeck_regimport.git] / wintest / wintest.py
blob93da47a6a622befa0d8c881a5a894b8564edf4e3
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.environ['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="))
60 if self.getvar("WIN_USER") is None:
61 self.setvar("WIN_USER", "administrator")
63 def info(self, msg):
64 '''print some information'''
65 if not self.list_mode:
66 print(self.substitute(msg))
68 def load_config(self, fname):
69 '''load the config file'''
70 f = open(fname)
71 for line in f:
72 line = line.strip()
73 if len(line) == 0 or line[0] == '#':
74 continue
75 colon = line.find(':')
76 if colon == -1:
77 raise RuntimeError("Invalid config line '%s'" % line)
78 varname = line[0:colon].strip()
79 value = line[colon+1:].strip()
80 self.setvar(varname, value)
82 def list_steps_mode(self):
83 '''put wintest in step listing mode'''
84 self.list_mode = True
86 def set_skip(self, skiplist):
87 '''set a list of tests to skip'''
88 self.skiplist = skiplist.split(',')
90 def set_vms(self, vms):
91 '''set a list of VMs to test'''
92 if vms is not None:
93 self.vms = []
94 for vm in vms.split(','):
95 vm = vm.upper()
96 self.vms.append(vm)
98 def skip(self, step):
99 '''return True if we should skip a step'''
100 if self.list_mode:
101 print("\t%s" % step)
102 return True
103 return step in self.skiplist
105 def substitute(self, text):
106 """Substitute strings of the form ${NAME} in text, replacing
107 with substitutions from vars.
109 if isinstance(text, list):
110 ret = text[:]
111 for i in range(len(ret)):
112 ret[i] = self.substitute(ret[i])
113 return ret
115 """We may have objects such as pexpect.EOF that are not strings"""
116 if not isinstance(text, str):
117 return text
118 while True:
119 var_start = text.find("${")
120 if var_start == -1:
121 return text
122 var_end = text.find("}", var_start)
123 if var_end == -1:
124 return text
125 var_name = text[var_start+2:var_end]
126 if not var_name in self.vars:
127 raise RuntimeError("Unknown substitution variable ${%s}" % var_name)
128 text = text.replace("${%s}" % var_name, self.vars[var_name])
129 return text
131 def have_var(self, varname):
132 '''see if a variable has been set'''
133 return varname in self.vars
135 def have_vm(self, vmname):
136 '''see if a VM should be used'''
137 if not self.have_var(vmname + '_VM'):
138 return False
139 if self.vms is None:
140 return True
141 return vmname in self.vms
143 def putenv(self, key, value):
144 '''putenv with substitution'''
145 os.environ[key] = self.substitute(value)
147 def chdir(self, dir):
148 '''chdir with substitution'''
149 os.chdir(self.substitute(dir))
151 def del_files(self, dirs):
152 '''delete all files in the given directory'''
153 for d in dirs:
154 self.run_cmd("find %s -type f | xargs rm -f" % d)
156 def write_file(self, filename, text, mode='w'):
157 '''write to a file'''
158 f = open(self.substitute(filename), mode=mode)
159 f.write(self.substitute(text))
160 f.close()
162 def run_cmd(self, cmd, dir=".", show=None, output=False, checkfail=True):
163 '''run a command'''
164 cmd = self.substitute(cmd)
165 if isinstance(cmd, list):
166 self.info('$ ' + " ".join(cmd))
167 else:
168 self.info('$ ' + cmd)
169 if output:
170 return subprocess.Popen([cmd], shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=dir).communicate()[0]
171 if isinstance(cmd, list):
172 shell=False
173 else:
174 shell=True
175 if checkfail:
176 return subprocess.check_call(cmd, shell=shell, cwd=dir)
177 else:
178 return subprocess.call(cmd, shell=shell, cwd=dir)
181 def run_child(self, cmd, dir="."):
182 '''create a child and return the Popen handle to it'''
183 cwd = os.getcwd()
184 cmd = self.substitute(cmd)
185 if isinstance(cmd, list):
186 self.info('$ ' + " ".join(cmd))
187 else:
188 self.info('$ ' + cmd)
189 if isinstance(cmd, list):
190 shell=False
191 else:
192 shell=True
193 os.chdir(dir)
194 ret = subprocess.Popen(cmd, shell=shell, stderr=subprocess.STDOUT)
195 os.chdir(cwd)
196 return ret
198 def cmd_output(self, cmd):
199 '''return output from and command'''
200 cmd = self.substitute(cmd)
201 return self.run_cmd(cmd, output=True)
203 def cmd_contains(self, cmd, contains, nomatch=False, ordered=False, regex=False,
204 casefold=True):
205 '''check that command output contains the listed strings'''
207 if isinstance(contains, str):
208 contains = [contains]
210 out = self.cmd_output(cmd)
211 self.info(out)
212 for c in self.substitute(contains):
213 if regex:
214 if casefold:
215 c = c.upper()
216 out = out.upper()
217 m = re.search(c, out)
218 if m is None:
219 start = -1
220 end = -1
221 else:
222 start = m.start()
223 end = m.end()
224 elif casefold:
225 start = out.upper().find(c.upper())
226 end = start + len(c)
227 else:
228 start = out.find(c)
229 end = start + len(c)
230 if nomatch:
231 if start != -1:
232 raise RuntimeError("Expected to not see %s in %s" % (c, cmd))
233 else:
234 if start == -1:
235 raise RuntimeError("Expected to see %s in %s" % (c, cmd))
236 if ordered and start != -1:
237 out = out[end:]
239 def retry_cmd(self, cmd, contains, retries=30, delay=2, wait_for_fail=False,
240 ordered=False, regex=False, casefold=True):
241 '''retry a command a number of times'''
242 while retries > 0:
243 try:
244 self.cmd_contains(cmd, contains, nomatch=wait_for_fail,
245 ordered=ordered, regex=regex, casefold=casefold)
246 return
247 except:
248 time.sleep(delay)
249 retries -= 1
250 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
251 raise RuntimeError("Failed to find %s" % contains)
253 def pexpect_spawn(self, cmd, timeout=60, crlf=True, casefold=True):
254 '''wrapper around pexpect spawn'''
255 cmd = self.substitute(cmd)
256 self.info("$ " + cmd)
257 ret = pexpect.spawn(cmd, logfile=sys.stdout, timeout=timeout)
259 def sendline_sub(line):
260 line = self.substitute(line)
261 if crlf:
262 line = line.replace('\n', '\r\n') + '\r'
263 return ret.old_sendline(line)
265 def expect_sub(line, timeout=ret.timeout, casefold=casefold):
266 line = self.substitute(line)
267 if casefold:
268 if isinstance(line, list):
269 for i in range(len(line)):
270 if isinstance(line[i], str):
271 line[i] = '(?i)' + line[i]
272 elif isinstance(line, str):
273 line = '(?i)' + line
274 return ret.old_expect(line, timeout=timeout)
276 ret.old_sendline = ret.sendline
277 ret.sendline = sendline_sub
278 ret.old_expect = ret.expect
279 ret.expect = expect_sub
281 return ret
283 def get_nameserver(self):
284 '''Get the current nameserver from /etc/resolv.conf'''
285 child = self.pexpect_spawn('cat /etc/resolv.conf', crlf=False)
286 i = child.expect(['Generated by wintest', 'nameserver'])
287 if i == 0:
288 child.expect('your original resolv.conf')
289 child.expect('nameserver')
290 child.expect('\d+.\d+.\d+.\d+')
291 return child.after
293 def rndc_cmd(self, cmd, checkfail=True):
294 '''run a rndc command'''
295 self.run_cmd("${RNDC} -c ${PREFIX}/etc/rndc.conf %s" % cmd, checkfail=checkfail)
297 def named_supports_gssapi_keytab(self):
298 '''see if named supports tkey-gssapi-keytab'''
299 self.write_file("${PREFIX}/named.conf.test",
300 'options { tkey-gssapi-keytab "test"; };')
301 try:
302 self.run_cmd("${NAMED_CHECKCONF} ${PREFIX}/named.conf.test")
303 except subprocess.CalledProcessError:
304 return False
305 return True
307 def set_nameserver(self, nameserver):
308 '''set the nameserver in resolv.conf'''
309 self.write_file("/etc/resolv.conf.wintest", '''
310 # Generated by wintest, the Samba v Windows automated testing system
311 nameserver %s
313 # your original resolv.conf appears below:
314 ''' % self.substitute(nameserver))
315 child = self.pexpect_spawn("cat /etc/resolv.conf", crlf=False)
316 i = child.expect(['your original resolv.conf appears below:', pexpect.EOF])
317 if i == 0:
318 child.expect(pexpect.EOF)
319 contents = child.before.lstrip().replace('\r', '')
320 self.write_file('/etc/resolv.conf.wintest', contents, mode='a')
321 self.write_file('/etc/resolv.conf.wintest-bak', contents)
322 self.run_cmd("mv -f /etc/resolv.conf.wintest /etc/resolv.conf")
323 self.resolv_conf_backup = '/etc/resolv.conf.wintest-bak';
325 def configure_bind(self, kerberos_support=False, include=None):
326 self.chdir('${PREFIX}')
328 nameserver = self.get_nameserver()
329 if nameserver == self.getvar('INTERFACE_IP'):
330 raise RuntimeError("old /etc/resolv.conf must not contain %s as a nameserver, this will create loops with the generated dns configuration" % nameserver)
331 self.setvar('DNSSERVER', nameserver)
333 if self.getvar('INTERFACE_IPV6'):
334 ipv6_listen = 'listen-on-v6 port 53 { ${INTERFACE_IPV6}; };'
335 else:
336 ipv6_listen = ''
337 self.setvar('BIND_LISTEN_IPV6', ipv6_listen)
339 if not kerberos_support:
340 self.setvar("NAMED_TKEY_OPTION", "")
341 else:
342 if self.named_supports_gssapi_keytab():
343 self.setvar("NAMED_TKEY_OPTION",
344 'tkey-gssapi-keytab "${PREFIX}/private/dns.keytab";')
345 else:
346 self.info("LCREALM=${LCREALM}")
347 self.setvar("NAMED_TKEY_OPTION",
348 '''tkey-gssapi-credential "DNS/${LCREALM}";
349 tkey-domain "${LCREALM}";
350 ''')
351 self.putenv('KEYTAB_FILE', '${PREFIX}/private/dns.keytab')
352 self.putenv('KRB5_KTNAME', '${PREFIX}/private/dns.keytab')
354 if include:
355 self.setvar("NAMED_INCLUDE", 'include "%s";' % include)
356 else:
357 self.setvar("NAMED_INCLUDE", '')
359 self.run_cmd("mkdir -p ${PREFIX}/etc")
361 self.write_file("etc/named.conf", '''
362 options {
363 listen-on port 53 { ${INTERFACE_IP}; };
364 ${BIND_LISTEN_IPV6}
365 directory "${PREFIX}/var/named";
366 dump-file "${PREFIX}/var/named/data/cache_dump.db";
367 pid-file "${PREFIX}/var/named/named.pid";
368 statistics-file "${PREFIX}/var/named/data/named_stats.txt";
369 memstatistics-file "${PREFIX}/var/named/data/named_mem_stats.txt";
370 allow-query { any; };
371 recursion yes;
372 ${NAMED_TKEY_OPTION}
373 max-cache-ttl 10;
374 max-ncache-ttl 10;
376 forward only;
377 forwarders {
378 ${DNSSERVER};
383 key "rndc-key" {
384 algorithm hmac-md5;
385 secret "lA/cTrno03mt5Ju17ybEYw==";
388 controls {
389 inet ${INTERFACE_IP} port 953
390 allow { any; } keys { "rndc-key"; };
393 ${NAMED_INCLUDE}
394 ''')
396 # add forwarding for the windows domains
397 domains = self.get_domains()
398 for d in domains:
399 self.write_file('etc/named.conf',
401 zone "%s" IN {
402 type forward;
403 forward only;
404 forwarders {
408 ''' % (d, domains[d]),
409 mode='a')
412 self.write_file("etc/rndc.conf", '''
413 # Start of rndc.conf
414 key "rndc-key" {
415 algorithm hmac-md5;
416 secret "lA/cTrno03mt5Ju17ybEYw==";
419 options {
420 default-key "rndc-key";
421 default-server ${INTERFACE_IP};
422 default-port 953;
424 ''')
427 def stop_bind(self):
428 '''Stop our private BIND from listening and operating'''
429 self.rndc_cmd("stop", checkfail=False)
430 self.port_wait("${INTERFACE_IP}", 53, wait_for_fail=True)
432 self.run_cmd("rm -rf var/named")
435 def start_bind(self):
436 '''restart the test environment version of bind'''
437 self.info("Restarting bind9")
438 self.chdir('${PREFIX}')
440 self.run_cmd("mkdir -p var/named/data")
441 self.run_cmd("chown -R ${BIND_USER} var/named")
443 self.bind_child = self.run_child("${BIND9} -u ${BIND_USER} -n 1 -c ${PREFIX}/etc/named.conf -g")
445 self.port_wait("${INTERFACE_IP}", 53)
446 self.rndc_cmd("flush")
448 def restart_bind(self, kerberos_support=False, include=None):
449 self.configure_bind(kerberos_support=kerberos_support, include=include)
450 self.stop_bind()
451 self.start_bind()
453 def restore_resolv_conf(self):
454 '''restore the /etc/resolv.conf after testing is complete'''
455 if getattr(self, 'resolv_conf_backup', False):
456 self.info("restoring /etc/resolv.conf")
457 self.run_cmd("mv -f %s /etc/resolv.conf" % self.resolv_conf_backup)
460 def vm_poweroff(self, vmname, checkfail=True):
461 '''power off a VM'''
462 self.setvar('VMNAME', vmname)
463 self.run_cmd("${VM_POWEROFF}", checkfail=checkfail)
465 def vm_reset(self, vmname):
466 '''reset a VM'''
467 self.setvar('VMNAME', vmname)
468 self.run_cmd("${VM_RESET}")
470 def vm_restore(self, vmname, snapshot):
471 '''restore a VM'''
472 self.setvar('VMNAME', vmname)
473 self.setvar('SNAPSHOT', snapshot)
474 self.run_cmd("${VM_RESTORE}")
476 def ping_wait(self, hostname):
477 '''wait for a hostname to come up on the network'''
478 hostname = self.substitute(hostname)
479 loops=10
480 while loops > 0:
481 try:
482 self.run_cmd("ping -c 1 -w 10 %s" % hostname)
483 break
484 except:
485 loops = loops - 1
486 if loops == 0:
487 raise RuntimeError("Failed to ping %s" % hostname)
488 self.info("Host %s is up" % hostname)
490 def port_wait(self, hostname, port, retries=200, delay=3, wait_for_fail=False):
491 '''wait for a host to come up on the network'''
493 while retries > 0:
494 child = self.pexpect_spawn("nc -v -z -w 1 %s %u" % (hostname, port), crlf=False, timeout=1)
495 child.expect([pexpect.EOF, pexpect.TIMEOUT])
496 child.close()
497 i = child.exitstatus
498 if wait_for_fail:
499 #wait for timeout or fail
500 if i == None or i > 0:
501 return
502 else:
503 if i == 0:
504 return
506 time.sleep(delay)
507 retries -= 1
508 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
510 raise RuntimeError("gave up waiting for %s:%d" % (hostname, port))
512 def run_net_time(self, child):
513 '''run net time on windows'''
514 child.sendline("net time \\\\${HOSTNAME} /set")
515 child.expect("Do you want to set the local computer")
516 child.sendline("Y")
517 child.expect("The command completed successfully")
519 def run_date_time(self, child, time_tuple=None):
520 '''run date and time on windows'''
521 if time_tuple is None:
522 time_tuple = time.localtime()
523 child.sendline("date")
524 child.expect("Enter the new date:")
525 i = child.expect(["dd-mm-yy", "mm-dd-yy"])
526 if i == 0:
527 child.sendline(time.strftime("%d-%m-%y", time_tuple))
528 else:
529 child.sendline(time.strftime("%m-%d-%y", time_tuple))
530 child.expect("C:")
531 child.sendline("time")
532 child.expect("Enter the new time:")
533 child.sendline(time.strftime("%H:%M:%S", time_tuple))
534 child.expect("C:")
536 def get_ipconfig(self, child):
537 '''get the IP configuration of the child'''
538 child.sendline("ipconfig /all")
539 child.expect('Ethernet adapter ')
540 child.expect("[\w\s]+")
541 self.setvar("WIN_NIC", child.after)
542 child.expect(['IPv4 Address', 'IP Address'])
543 child.expect('\d+.\d+.\d+.\d+')
544 self.setvar('WIN_IPV4_ADDRESS', child.after)
545 child.expect('Subnet Mask')
546 child.expect('\d+.\d+.\d+.\d+')
547 self.setvar('WIN_SUBNET_MASK', child.after)
548 child.expect('Default Gateway')
549 i = child.expect(['\d+.\d+.\d+.\d+', "C:"])
550 if i == 0:
551 self.setvar('WIN_DEFAULT_GATEWAY', child.after)
552 child.expect("C:")
554 def get_is_dc(self, child):
555 '''check if a windows machine is a domain controller'''
556 child.sendline("dcdiag")
557 i = child.expect(["is not a Directory Server",
558 "is not recognized as an internal or external command",
559 "Home Server = ",
560 "passed test Replications"])
561 if i == 0:
562 return False
563 if i == 1 or i == 3:
564 child.expect("C:")
565 child.sendline("net config Workstation")
566 child.expect("Workstation domain")
567 child.expect('[\S]+')
568 domain = child.after
569 i = child.expect(["Workstation Domain DNS Name", "Logon domain"])
570 '''If we get the Logon domain first, we are not in an AD domain'''
571 if i == 1:
572 return False
573 if domain.upper() == self.getvar("WIN_DOMAIN").upper():
574 return True
576 child.expect('[\S]+')
577 hostname = child.after
578 if hostname.upper() == self.getvar("WIN_HOSTNAME").upper():
579 return True
581 def set_noexpire(self, child, username):
582 """Ensure this user's password does not expire"""
583 child.sendline('wmic useraccount where name="%s" set PasswordExpires=FALSE' % username)
584 child.expect("update successful")
585 child.expect("C:")
587 def run_tlntadmn(self, child):
588 '''remove the annoying telnet restrictions'''
589 child.sendline('tlntadmn config maxconn=1024')
590 child.expect(["The settings were successfully updated", "Access is denied"])
591 child.expect("C:")
593 def disable_firewall(self, child):
594 '''remove the annoying firewall'''
595 child.sendline('netsh advfirewall set allprofiles state off')
596 i = child.expect(["Ok", "The following command was not found: advfirewall set allprofiles state off", "The requested operation requires elevation", "Access is denied"])
597 child.expect("C:")
598 if i == 1:
599 child.sendline('netsh firewall set opmode mode = DISABLE profile = ALL')
600 i = child.expect(["Ok", "The following command was not found", "Access is denied"])
601 if i != 0:
602 self.info("Firewall disable failed - ignoring")
603 child.expect("C:")
605 def set_dns(self, child):
606 child.sendline('netsh interface ip set dns "${WIN_NIC}" static ${INTERFACE_IP} primary')
607 i = child.expect(['C:', pexpect.EOF, pexpect.TIMEOUT], timeout=5)
608 if i > 0:
609 return True
610 else:
611 return False
613 def set_ip(self, child):
614 """fix the IP address to the same value it had when we
615 connected, but don't use DHCP, and force the DNS server to our
616 DNS server. This allows DNS updates to run"""
617 self.get_ipconfig(child)
618 if self.getvar("WIN_IPV4_ADDRESS") != self.getvar("WIN_IP"):
619 raise RuntimeError("ipconfig address %s != nmblookup address %s" % (self.getvar("WIN_IPV4_ADDRESS"),
620 self.getvar("WIN_IP")))
621 child.sendline('netsh')
622 child.expect('netsh>')
623 child.sendline('offline')
624 child.expect('netsh>')
625 child.sendline('routing ip add persistentroute dest=0.0.0.0 mask=0.0.0.0 name="${WIN_NIC}" nhop=${WIN_DEFAULT_GATEWAY}')
626 child.expect('netsh>')
627 child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1 store=persistent')
628 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)
629 if i == 0:
630 child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1')
631 child.expect('netsh>')
632 child.sendline('commit')
633 child.sendline('online')
634 child.sendline('exit')
636 child.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=5)
637 return True
640 def resolve_ip(self, hostname, retries=60, delay=5):
641 '''resolve an IP given a hostname, assuming NBT'''
642 while retries > 0:
643 child = self.pexpect_spawn("bin/nmblookup %s" % hostname)
644 i = 0
645 while i == 0:
646 i = child.expect(["querying", '\d+.\d+.\d+.\d+', hostname, "Lookup failed"])
647 if i == 0:
648 child.expect("\r")
649 if i == 1:
650 return child.after
651 retries -= 1
652 time.sleep(delay)
653 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
654 raise RuntimeError("Failed to resolve IP of %s" % hostname)
657 def open_telnet(self, hostname, username, password, retries=60, delay=5, set_time=False, set_ip=False,
658 disable_firewall=True, run_tlntadmn=True, set_noexpire=False):
659 '''open a telnet connection to a windows server, return the pexpect child'''
660 set_route = False
661 set_dns = False
662 set_telnetclients = True
663 if self.getvar('WIN_IP'):
664 ip = self.getvar('WIN_IP')
665 else:
666 ip = self.resolve_ip(hostname)
667 self.setvar('WIN_IP', ip)
668 while retries > 0:
669 child = self.pexpect_spawn("telnet " + ip + " -l '" + username + "'")
670 i = child.expect(["Welcome to Microsoft Telnet Service",
671 "Denying new connections due to the limit on number of connections",
672 "No more connections are allowed to telnet server",
673 "Unable to connect to remote host",
674 "No route to host",
675 "Connection refused",
676 pexpect.EOF])
677 if i != 0:
678 child.close()
679 time.sleep(delay)
680 retries -= 1
681 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
682 continue
683 child.expect("password:")
684 child.sendline(password)
685 i = child.expect(["C:",
686 "TelnetClients",
687 "Denying new connections due to the limit on number of connections",
688 "No more connections are allowed to telnet server",
689 "Unable to connect to remote host",
690 "No route to host",
691 "Connection refused",
692 pexpect.EOF])
693 if i == 1:
694 if set_telnetclients:
695 self.run_cmd('bin/net rpc group addmem TelnetClients "authenticated users" -S $WIN_IP -U$WIN_USER%$WIN_PASS')
696 child.close()
697 retries -= 1
698 set_telnetclients = False
699 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
700 continue
701 else:
702 raise RuntimeError("Failed to connect with telnet due to missing TelnetClients membership")
704 if i != 0:
705 child.close()
706 time.sleep(delay)
707 retries -= 1
708 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
709 continue
710 if set_dns:
711 set_dns = False
712 if self.set_dns(child):
713 continue;
714 if set_route:
715 child.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
716 child.expect("C:")
717 set_route = False
718 if set_time:
719 self.run_date_time(child, None)
720 set_time = False
721 if run_tlntadmn:
722 self.run_tlntadmn(child)
723 run_tlntadmn = False
724 if set_noexpire:
725 self.set_noexpire(child, username)
726 set_noexpire = False
727 if disable_firewall:
728 self.disable_firewall(child)
729 disable_firewall = False
730 if set_ip:
731 set_ip = False
732 if self.set_ip(child):
733 set_route = True
734 set_dns = True
735 continue
736 return child
737 raise RuntimeError("Failed to connect with telnet")
739 def kinit(self, username, password):
740 '''use kinit to setup a credentials cache'''
741 self.run_cmd("kdestroy")
742 self.putenv('KRB5CCNAME', "${PREFIX}/ccache.test")
743 username = self.substitute(username)
744 s = username.split('@')
745 if len(s) > 0:
746 s[1] = s[1].upper()
747 username = '@'.join(s)
748 child = self.pexpect_spawn('kinit ' + username)
749 child.expect("Password")
750 child.sendline(password)
751 child.expect(pexpect.EOF)
752 child.close()
753 if child.exitstatus != 0:
754 raise RuntimeError("kinit failed with status %d" % child.exitstatus)
756 def get_domains(self):
757 '''return a dictionary of DNS domains and IPs for named.conf'''
758 ret = {}
759 for v in self.vars:
760 if v[-6:] == "_REALM":
761 base = v[:-6]
762 if base + '_IP' in self.vars:
763 ret[self.vars[base + '_REALM']] = self.vars[base + '_IP']
764 return ret
766 def wait_reboot(self, retries=3):
767 '''wait for a VM to reboot'''
769 # first wait for it to shutdown
770 self.port_wait("${WIN_IP}", 139, wait_for_fail=True, delay=6)
772 # now wait for it to come back. If it fails to come back
773 # then try resetting it
774 while retries > 0:
775 try:
776 self.port_wait("${WIN_IP}", 139)
777 return
778 except:
779 retries -= 1
780 self.vm_reset("${WIN_VM}")
781 self.info("retrying reboot (retries=%u)" % retries)
782 raise RuntimeError(self.substitute("VM ${WIN_VM} failed to reboot"))
784 def get_vms(self):
785 '''return a dictionary of all the configured VM names'''
786 ret = []
787 for v in self.vars:
788 if v[-3:] == "_VM":
789 ret.append(self.vars[v])
790 return ret
793 def run_dcpromo_as_first_dc(self, vm, func_level=None):
794 self.setwinvars(vm)
795 self.info("Configuring a windows VM ${WIN_VM} at the first DC in the domain using dcpromo")
796 child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_time=True)
797 if self.get_is_dc(child):
798 return
800 if func_level == '2008r2':
801 self.setvar("FUNCTION_LEVEL_INT", str(4))
802 elif func_level == '2003':
803 self.setvar("FUNCTION_LEVEL_INT", str(1))
804 else:
805 self.setvar("FUNCTION_LEVEL_INT", str(0))
807 child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_ip=True, set_noexpire=True)
809 """This server must therefore not yet be a directory server, so we must promote it"""
810 child.sendline("copy /Y con answers.txt")
811 child.sendline('''
812 [DCInstall]
813 ; New forest promotion
814 ReplicaOrNewDomain=Domain
815 NewDomain=Forest
816 NewDomainDNSName=${WIN_REALM}
817 ForestLevel=${FUNCTION_LEVEL_INT}
818 DomainNetbiosName=${WIN_DOMAIN}
819 DomainLevel=${FUNCTION_LEVEL_INT}
820 InstallDNS=Yes
821 ConfirmGc=Yes
822 CreateDNSDelegation=No
823 DatabasePath="C:\Windows\NTDS"
824 LogPath="C:\Windows\NTDS"
825 SYSVOLPath="C:\Windows\SYSVOL"
826 ; Set SafeModeAdminPassword to the correct value prior to using the unattend file
827 SafeModeAdminPassword=${WIN_PASS}
828 ; Run-time flags (optional)
829 RebootOnCompletion=No
830 \x1a
831 ''')
832 child.expect("copied.")
833 child.expect("C:")
834 child.expect("C:")
835 child.sendline("dcpromo /answer:answers.txt")
836 i = child.expect(["You must restart this computer", "failed", "Active Directory Domain Services was not installed", "C:"], timeout=240)
837 if i == 1 or i == 2:
838 raise Exception("dcpromo failed")
839 child.sendline("shutdown -r -t 0")
840 self.port_wait("${WIN_IP}", 139, wait_for_fail=True)
841 self.port_wait("${WIN_IP}", 139)
842 self.retry_cmd("host -t SRV _ldap._tcp.${WIN_REALM} ${WIN_IP}", ['has SRV record'], retries=60, delay=5 )
845 def start_winvm(self, vm):
846 '''start a Windows VM'''
847 self.setwinvars(vm)
849 self.info("Joining a windows box to the domain")
850 self.vm_poweroff("${WIN_VM}", checkfail=False)
851 self.vm_restore("${WIN_VM}", "${WIN_SNAPSHOT}")
853 def run_winjoin(self, vm, domain, username="administrator", password="${PASSWORD1}"):
854 '''join a windows box to a domain'''
855 child = self.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time=True, set_ip=True, set_noexpire=True)
856 retries = 5
857 while retries > 0:
858 child.sendline("ipconfig /flushdns")
859 child.expect("C:")
860 child.sendline("netdom join ${WIN_HOSTNAME} /Domain:%s /UserD:%s /PasswordD:%s" % (domain, username, password))
861 i = child.expect(["The command completed successfully",
862 "The specified domain either does not exist or could not be contacted."])
863 if i == 0:
864 break
865 time.sleep(10)
866 retries -= 1
868 child.expect("C:")
869 child.sendline("shutdown /r -t 0")
870 self.wait_reboot()
871 child = self.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time=True, set_ip=True)
872 child.sendline("ipconfig /registerdns")
873 child.expect("Registration of the DNS resource records for all adapters of this computer has been initiated. Any errors will be reported in the Event Viewer")
874 child.expect("C:")
877 def test_remote_smbclient(self, vm, username="${WIN_USER}", password="${WIN_PASS}", args=""):
878 '''test smbclient against remote server'''
879 self.setwinvars(vm)
880 self.info('Testing smbclient')
881 self.chdir('${PREFIX}')
882 smbclient = self.getvar("smbclient")
883 self.cmd_contains("%s --version" % (smbclient), ["${SAMBA_VERSION}"])
884 self.retry_cmd('%s -L ${WIN_HOSTNAME} -U%s%%%s %s' % (smbclient, username, password, args), ["IPC"], retries=60, delay=5)
886 def test_net_use(self, vm, realm, domain, username, password):
887 self.setwinvars(vm)
888 self.info('Testing net use against Samba3 member')
889 child = self.open_telnet("${WIN_HOSTNAME}", "%s\\%s" % (domain, username), password)
890 child.sendline("net use t: \\\\${HOSTNAME}.%s\\test" % realm)
891 child.expect("The command completed successfully")
894 def setup(self, testname, subdir):
895 '''setup for main tests, parsing command line'''
896 self.parser.add_option("--conf", type='string', default='', help='config file')
897 self.parser.add_option("--skip", type='string', default='', help='list of steps to skip (comma separated)')
898 self.parser.add_option("--vms", type='string', default=None, help='list of VMs to use (comma separated)')
899 self.parser.add_option("--list", action='store_true', default=False, help='list the available steps')
900 self.parser.add_option("--rebase", action='store_true', default=False, help='do a git pull --rebase')
901 self.parser.add_option("--clean", action='store_true', default=False, help='clean the tree')
902 self.parser.add_option("--prefix", type='string', default=None, help='override install prefix')
903 self.parser.add_option("--sourcetree", type='string', default=None, help='override sourcetree location')
904 self.parser.add_option("--nocleanup", action='store_true', default=False, help='disable cleanup code')
905 self.parser.add_option("--use-ntvfs", action='store_true', default=False, help='use NTVFS for the fileserver')
906 self.parser.add_option("--dns-backend", type="choice",
907 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
908 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server, " \
909 "BIND9_FLATFILE uses bind9 text database to store zone information, " \
910 "BIND9_DLZ uses samba4 AD to store zone information (default), " \
911 "NONE skips the DNS setup entirely (not recommended)",
912 default="BIND9_DLZ")
914 self.opts, self.args = self.parser.parse_args()
916 if not self.opts.conf:
917 print("Please specify a config file with --conf")
918 sys.exit(1)
920 # we don't need fsync safety in these tests
921 self.putenv('TDB_NO_FSYNC', '1')
923 self.load_config(self.opts.conf)
925 self.set_skip(self.opts.skip)
926 self.set_vms(self.opts.vms)
928 if self.opts.list:
929 self.list_steps_mode()
931 if self.opts.prefix:
932 self.setvar('PREFIX', self.opts.prefix)
934 if self.opts.sourcetree:
935 self.setvar('SOURCETREE', self.opts.sourcetree)
937 if self.opts.rebase:
938 self.info('rebasing')
939 self.chdir('${SOURCETREE}')
940 self.run_cmd('git pull --rebase')
942 if self.opts.clean:
943 self.info('cleaning')
944 self.chdir('${SOURCETREE}/' + subdir)
945 self.run_cmd('make clean')
947 if self.opts.use_ntvfs:
948 self.setvar('USE_NTVFS', "--use-ntvfs")
949 else:
950 self.setvar('USE_NTVFS', "")
952 self.setvar('NAMESERVER_BACKEND', self.opts.dns_backend)
954 if self.opts.dns_backend == 'SAMBA_INTERNAL':
955 self.setvar('ALLOW_DNS_UPDATES', '--option=allow dns updates = True')
956 # we need recursive queries, since host expects answers with RA-bit
957 self.setvar('DNS_RECURSIVE_QUERIES', '--option=dns recursive queries = Yes')
958 else:
959 self.setvar('ALLOW_DNS_UPDATES', '')
960 self.setvar('DNS_RECURSIVE_QUERIES', '')