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