libgpo: remove unused loadparm_context from functions.
[Samba.git] / wintest / wintest.py
blob61664ae87d8021eb4dff2ac2df5460560f81796a
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 if self.getvar('WIN_IP'):
683 ip = self.getvar('WIN_IP')
684 else:
685 ip = self.resolve_ip(hostname)
686 self.setvar('WIN_IP', ip)
687 while retries > 0:
688 child = self.pexpect_spawn("telnet " + ip + " -l '" + username + "'")
689 i = child.expect(["Welcome to Microsoft Telnet Service",
690 "Denying new connections due to the limit on number of connections",
691 "No more connections are allowed to telnet server",
692 "Unable to connect to remote host",
693 "No route to host",
694 "Connection refused",
695 pexpect.EOF])
696 if i != 0:
697 child.close()
698 time.sleep(delay)
699 retries -= 1
700 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
701 continue
702 child.expect("password:")
703 child.sendline(password)
704 i = child.expect(["C:",
705 "TelnetClients",
706 "Denying new connections due to the limit on number of connections",
707 "No more connections are allowed to telnet server",
708 "Unable to connect to remote host",
709 "No route to host",
710 "Connection refused",
711 pexpect.EOF])
712 if i == 1:
713 if set_telnetclients:
714 self.run_cmd('bin/net rpc group addmem TelnetClients "authenticated users" -S $WIN_IP -U$WIN_USER%$WIN_PASS')
715 child.close()
716 retries -= 1
717 set_telnetclients = False
718 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
719 continue
720 else:
721 raise RuntimeError("Failed to connect with telnet due to missing TelnetClients membership")
723 if i != 0:
724 child.close()
725 time.sleep(delay)
726 retries -= 1
727 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
728 continue
729 if set_dns:
730 set_dns = False
731 if self.set_dns(child):
732 continue;
733 if set_route:
734 child.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
735 child.expect("C:")
736 set_route = False
737 if set_time:
738 self.run_date_time(child, None)
739 set_time = False
740 if run_tlntadmn:
741 self.run_tlntadmn(child)
742 run_tlntadmn = False
743 if set_noexpire:
744 self.set_noexpire(child, username)
745 set_noexpire = False
746 if disable_firewall:
747 self.disable_firewall(child)
748 disable_firewall = False
749 if set_ip:
750 set_ip = False
751 if self.set_ip(child):
752 set_route = True
753 set_dns = True
754 continue
755 return child
756 raise RuntimeError("Failed to connect with telnet")
758 def kinit(self, username, password):
759 '''use kinit to setup a credentials cache'''
760 self.run_cmd("kdestroy")
761 self.putenv('KRB5CCNAME', "${PREFIX}/ccache.test")
762 username = self.substitute(username)
763 s = username.split('@')
764 if len(s) > 0:
765 s[1] = s[1].upper()
766 username = '@'.join(s)
767 child = self.pexpect_spawn('kinit ' + username)
768 child.expect("Password")
769 child.sendline(password)
770 child.expect(pexpect.EOF)
771 child.close()
772 if child.exitstatus != 0:
773 raise RuntimeError("kinit failed with status %d" % child.exitstatus)
775 def get_domains(self):
776 '''return a dictionary of DNS domains and IPs for named.conf'''
777 ret = {}
778 for v in self.vars:
779 if v[-6:] == "_REALM":
780 base = v[:-6]
781 if base + '_IP' in self.vars:
782 ret[self.vars[base + '_REALM']] = self.vars[base + '_IP']
783 return ret
785 def wait_reboot(self, retries=3):
786 '''wait for a VM to reboot'''
788 # first wait for it to shutdown
789 self.port_wait("${WIN_IP}", 139, wait_for_fail=True, delay=6)
791 # now wait for it to come back. If it fails to come back
792 # then try resetting it
793 while retries > 0:
794 try:
795 self.port_wait("${WIN_IP}", 139)
796 return
797 except:
798 retries -= 1
799 self.vm_reset("${WIN_VM}")
800 self.info("retrying reboot (retries=%u)" % retries)
801 raise RuntimeError(self.substitute("VM ${WIN_VM} failed to reboot"))
803 def get_vms(self):
804 '''return a dictionary of all the configured VM names'''
805 ret = []
806 for v in self.vars:
807 if v[-3:] == "_VM":
808 ret.append(self.vars[v])
809 return ret
812 def run_dcpromo_as_first_dc(self, vm, func_level=None):
813 self.setwinvars(vm)
814 self.info("Configuring a windows VM ${WIN_VM} at the first DC in the domain using dcpromo")
815 child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_time=True)
816 if self.get_is_dc(child):
817 return
819 if func_level == '2008r2':
820 self.setvar("FUNCTION_LEVEL_INT", str(4))
821 elif func_level == '2003':
822 self.setvar("FUNCTION_LEVEL_INT", str(1))
823 else:
824 self.setvar("FUNCTION_LEVEL_INT", str(0))
826 child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_ip=True, set_noexpire=True)
828 """This server must therefore not yet be a directory server, so we must promote it"""
829 child.sendline("copy /Y con answers.txt")
830 child.sendline('''
831 [DCInstall]
832 ; New forest promotion
833 ReplicaOrNewDomain=Domain
834 NewDomain=Forest
835 NewDomainDNSName=${WIN_REALM}
836 ForestLevel=${FUNCTION_LEVEL_INT}
837 DomainNetbiosName=${WIN_DOMAIN}
838 DomainLevel=${FUNCTION_LEVEL_INT}
839 InstallDNS=Yes
840 ConfirmGc=Yes
841 CreateDNSDelegation=No
842 DatabasePath="C:\Windows\NTDS"
843 LogPath="C:\Windows\NTDS"
844 SYSVOLPath="C:\Windows\SYSVOL"
845 ; Set SafeModeAdminPassword to the correct value prior to using the unattend file
846 SafeModeAdminPassword=${WIN_PASS}
847 ; Run-time flags (optional)
848 RebootOnCompletion=No
849 \x1a
850 ''')
851 child.expect("copied.")
852 child.expect("C:")
853 child.expect("C:")
854 child.sendline("dcpromo /answer:answers.txt")
855 i = child.expect(["You must restart this computer", "failed", "Active Directory Domain Services was not installed", "C:", pexpect.TIMEOUT], timeout=240)
856 if i == 1 or i == 2:
857 raise Exception("dcpromo failed")
858 if i == 4: # timeout
859 child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}")
861 child.sendline("shutdown -r -t 0")
862 self.port_wait("${WIN_IP}", 139, wait_for_fail=True)
863 self.port_wait("${WIN_IP}", 139)
865 child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}")
866 # Check if we became a DC by now
867 if not self.get_is_dc(child):
868 raise Exception("dcpromo failed (and wasn't a DC even after rebooting)")
869 # Give DNS registration a kick
870 child.sendline("ipconfig /registerdns")
872 self.retry_cmd("host -t SRV _ldap._tcp.${WIN_REALM} ${WIN_IP}", ['has SRV record'], retries=60, delay=5 )
875 def start_winvm(self, vm):
876 '''start a Windows VM'''
877 self.setwinvars(vm)
879 self.info("Joining a windows box to the domain")
880 self.vm_poweroff("${WIN_VM}", checkfail=False)
881 self.vm_restore("${WIN_VM}", "${WIN_SNAPSHOT}")
883 def run_winjoin(self, vm, domain, username="administrator", password="${PASSWORD1}"):
884 '''join a windows box to a domain'''
885 child = self.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time=True, set_ip=True, set_noexpire=True)
886 retries = 5
887 while retries > 0:
888 child.sendline("ipconfig /flushdns")
889 child.expect("C:")
890 child.sendline("netdom join ${WIN_HOSTNAME} /Domain:%s /UserD:%s /PasswordD:%s" % (domain, username, password))
891 i = child.expect(["The command completed successfully",
892 "The specified domain either does not exist or could not be contacted."], timeout=120)
893 if i == 0:
894 break
895 time.sleep(10)
896 retries -= 1
898 child.expect("C:")
899 child.sendline("shutdown /r -t 0")
900 self.wait_reboot()
901 child = self.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time=True, set_ip=True)
902 child.sendline("ipconfig /registerdns")
903 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")
904 child.expect("C:")
907 def test_remote_smbclient(self, vm, username="${WIN_USER}", password="${WIN_PASS}", args=""):
908 '''test smbclient against remote server'''
909 self.setwinvars(vm)
910 self.info('Testing smbclient')
911 self.chdir('${PREFIX}')
912 smbclient = self.getvar("smbclient")
913 self.cmd_contains("%s --version" % (smbclient), ["${SAMBA_VERSION}"])
914 self.retry_cmd('%s -L ${WIN_HOSTNAME} -U%s%%%s %s' % (smbclient, username, password, args), ["IPC"], retries=60, delay=5)
916 def test_net_use(self, vm, realm, domain, username, password):
917 self.setwinvars(vm)
918 self.info('Testing net use against Samba3 member')
919 child = self.open_telnet("${WIN_HOSTNAME}", "%s\\%s" % (domain, username), password)
920 child.sendline("net use t: \\\\${HOSTNAME}.%s\\test" % realm)
921 child.expect("The command completed successfully")
924 def setup(self, testname, subdir):
925 '''setup for main tests, parsing command line'''
926 self.parser.add_option("--conf", type='string', default='', help='config file')
927 self.parser.add_option("--skip", type='string', default='', help='list of steps to skip (comma separated)')
928 self.parser.add_option("--vms", type='string', default=None, help='list of VMs to use (comma separated)')
929 self.parser.add_option("--list", action='store_true', default=False, help='list the available steps')
930 self.parser.add_option("--rebase", action='store_true', default=False, help='do a git pull --rebase')
931 self.parser.add_option("--clean", action='store_true', default=False, help='clean the tree')
932 self.parser.add_option("--prefix", type='string', default=None, help='override install prefix')
933 self.parser.add_option("--sourcetree", type='string', default=None, help='override sourcetree location')
934 self.parser.add_option("--nocleanup", action='store_true', default=False, help='disable cleanup code')
935 self.parser.add_option("--use-ntvfs", action='store_true', default=False, help='use NTVFS for the fileserver')
936 self.parser.add_option("--dns-backend", type="choice",
937 choices=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
938 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), " \
939 "BIND9_FLATFILE uses bind9 text database to store zone information, " \
940 "BIND9_DLZ uses samba4 AD to store zone information, " \
941 "NONE skips the DNS setup entirely (not recommended)",
942 default="SAMBA_INTERNAL")
944 self.opts, self.args = self.parser.parse_args()
946 if not self.opts.conf:
947 print("Please specify a config file with --conf")
948 sys.exit(1)
950 # we don't need fsync safety in these tests
951 self.putenv('TDB_NO_FSYNC', '1')
953 self.load_config(self.opts.conf)
955 nameserver = self.get_nameserver()
956 if nameserver == self.getvar('NAMED_INTERFACE_IP'):
957 raise RuntimeError("old /etc/resolv.conf must not contain %s as a nameserver, this will create loops with the generated dns configuration" % nameserver)
958 self.setvar('DNSSERVER', nameserver)
960 self.set_skip(self.opts.skip)
961 self.set_vms(self.opts.vms)
963 if self.opts.list:
964 self.list_steps_mode()
966 if self.opts.prefix:
967 self.setvar('PREFIX', self.opts.prefix)
969 if self.opts.sourcetree:
970 self.setvar('SOURCETREE', self.opts.sourcetree)
972 if self.opts.rebase:
973 self.info('rebasing')
974 self.chdir('${SOURCETREE}')
975 self.run_cmd('git pull --rebase')
977 if self.opts.clean:
978 self.info('cleaning')
979 self.chdir('${SOURCETREE}/' + subdir)
980 self.run_cmd('make clean')
982 if self.opts.use_ntvfs:
983 self.setvar('USE_NTVFS', "--use-ntvfs")
984 else:
985 self.setvar('USE_NTVFS', "")
987 self.setvar('NAMESERVER_BACKEND', self.opts.dns_backend)
989 self.setvar('DNS_FORWARDER', "--option=dns forwarder=%s" % nameserver)