wintest: Allow Windows VM to have no default route
[nivanova/samba-autobuild/.git] / wintest / wintest.py
1 #!/usr/bin/env python
2
3 '''automated testing library for testing Samba against windows'''
4
5 import pexpect, subprocess
6 import optparse
7 import sys, os, time, re
8
9 class wintest():
10     '''testing of Samba against windows VMs'''
11
12     def __init__(self):
13         self.vars = {}
14         self.list_mode = False
15         self.vms = None
16         os.environ['PYTHONUNBUFFERED'] = '1'
17         self.parser = optparse.OptionParser("wintest")
18
19     def check_prerequesites(self):
20         self.info("Checking prerequesites")
21         self.setvar('HOSTNAME', self.cmd_output("hostname -s").strip())
22         if os.getuid() != 0:
23             raise Exception("You must run this script as root")
24         self.run_cmd('ifconfig ${INTERFACE} ${INTERFACE_NET} up')
25         if self.getvar('INTERFACE_IPV6'):
26             self.run_cmd('ifconfig ${INTERFACE} inet6 del ${INTERFACE_IPV6}/64', checkfail=False)
27             self.run_cmd('ifconfig ${INTERFACE} inet6 add ${INTERFACE_IPV6}/64 up')
28
29     def stop_vms(self):
30         '''Shut down any existing alive VMs, so they do not collide with what we are doing'''
31         self.info('Shutting down any of our VMs already running')
32         vms = self.get_vms()
33         for v in vms:
34             self.vm_poweroff(v, checkfail=False)
35
36     def setvar(self, varname, value):
37         '''set a substitution variable'''
38         self.vars[varname] = value
39
40     def getvar(self, varname):
41         '''return a substitution variable'''
42         if not varname in self.vars:
43             return None
44         return self.vars[varname]
45
46     def setwinvars(self, vm, prefix='WIN'):
47         '''setup WIN_XX vars based on a vm name'''
48         for v in ['VM', 'HOSTNAME', 'USER', 'PASS', 'SNAPSHOT', 'REALM', 'DOMAIN', 'IP']:
49             vname = '%s_%s' % (vm, v)
50             if vname in self.vars:
51                 self.setvar("%s_%s" % (prefix,v), self.substitute("${%s}" % vname))
52             else:
53                 self.vars.pop("%s_%s" % (prefix,v), None)
54
55         if self.getvar("WIN_REALM"):
56             self.setvar("WIN_REALM", self.getvar("WIN_REALM").upper())
57             self.setvar("WIN_LCREALM", self.getvar("WIN_REALM").lower())
58             dnsdomain = self.getvar("WIN_REALM")
59             self.setvar("WIN_BASEDN", "DC=" + dnsdomain.replace(".", ",DC="))
60         if self.getvar("WIN_USER") is None:
61             self.setvar("WIN_USER", "administrator")
62
63     def info(self, msg):
64         '''print some information'''
65         if not self.list_mode:
66             print(self.substitute(msg))
67
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)
81
82     def list_steps_mode(self):
83         '''put wintest in step listing mode'''
84         self.list_mode = True
85
86     def set_skip(self, skiplist):
87         '''set a list of tests to skip'''
88         self.skiplist = skiplist.split(',')
89
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)
97
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
104
105     def substitute(self, text):
106         """Substitute strings of the form ${NAME} in text, replacing
107         with substitutions from vars.
108         """
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
114
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
130
131     def have_var(self, varname):
132         '''see if a variable has been set'''
133         return varname in self.vars
134
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
142
143     def putenv(self, key, value):
144         '''putenv with substitution'''
145         os.environ[key] = self.substitute(value)
146
147     def chdir(self, dir):
148         '''chdir with substitution'''
149         os.chdir(self.substitute(dir))
150
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)
155
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()
161
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)
179
180
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
197
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)
202
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'''
206
207         if isinstance(contains, str):
208             contains = [contains]
209
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:]
238
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)
252
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)
258
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)
264
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)
275
276         ret.old_sendline = ret.sendline
277         ret.sendline = sendline_sub
278         ret.old_expect = ret.expect
279         ret.expect = expect_sub
280
281         return ret
282
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
292
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)
296
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
306
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
312
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';
324
325     def configure_bind(self, kerberos_support=False, include=None):
326         self.chdir('${PREFIX}')
327
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)
332
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)
338
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')
353
354         if include:
355             self.setvar("NAMED_INCLUDE", 'include "%s";' % include)
356         else:
357             self.setvar("NAMED_INCLUDE", '')
358
359         self.run_cmd("mkdir -p ${PREFIX}/etc")
360
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;
375
376         forward only;
377         forwarders {
378                   ${DNSSERVER};
379         };
380
381 };
382
383 key "rndc-key" {
384         algorithm hmac-md5;
385         secret "lA/cTrno03mt5Ju17ybEYw==";
386 };
387
388 controls {
389         inet ${INTERFACE_IP} port 953
390         allow { any; } keys { "rndc-key"; };
391 };
392
393 ${NAMED_INCLUDE}
394 ''')
395
396         # add forwarding for the windows domains
397         domains = self.get_domains()
398         for d in domains:
399             self.write_file('etc/named.conf',
400                          '''
401 zone "%s" IN {
402       type forward;
403       forward only;
404       forwarders {
405          %s;
406       };
407 };
408 ''' % (d, domains[d]),
409                      mode='a')
410
411
412         self.write_file("etc/rndc.conf", '''
413 # Start of rndc.conf
414 key "rndc-key" {
415         algorithm hmac-md5;
416         secret "lA/cTrno03mt5Ju17ybEYw==";
417 };
418
419 options {
420         default-key "rndc-key";
421         default-server  ${INTERFACE_IP};
422         default-port 953;
423 };
424 ''')
425
426
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)
431
432         self.run_cmd("rm -rf var/named")
433
434
435     def start_bind(self):
436         '''restart the test environment version of bind'''
437         self.info("Restarting bind9")
438         self.chdir('${PREFIX}')
439
440         self.set_nameserver(self.getvar('INTERFACE_IP'))
441
442         self.run_cmd("mkdir -p var/named/data")
443         self.run_cmd("chown -R ${BIND_USER} var/named")
444
445         self.bind_child = self.run_child("${BIND9} -u ${BIND_USER} -n 1 -c ${PREFIX}/etc/named.conf -g")
446
447         self.port_wait("${INTERFACE_IP}", 53)
448         self.rndc_cmd("flush")
449
450     def restart_bind(self, kerberos_support=False, include=None):
451         self.configure_bind(kerberos_support=kerberos_support, include=include)
452         self.stop_bind()
453         self.start_bind()
454
455     def restore_resolv_conf(self):
456         '''restore the /etc/resolv.conf after testing is complete'''
457         if getattr(self, 'resolv_conf_backup', False):
458             self.info("restoring /etc/resolv.conf")
459             self.run_cmd("mv -f %s /etc/resolv.conf" % self.resolv_conf_backup)
460
461
462     def vm_poweroff(self, vmname, checkfail=True):
463         '''power off a VM'''
464         self.setvar('VMNAME', vmname)
465         self.run_cmd("${VM_POWEROFF}", checkfail=checkfail)
466
467     def vm_reset(self, vmname):
468         '''reset a VM'''
469         self.setvar('VMNAME', vmname)
470         self.run_cmd("${VM_RESET}")
471
472     def vm_restore(self, vmname, snapshot):
473         '''restore a VM'''
474         self.setvar('VMNAME', vmname)
475         self.setvar('SNAPSHOT', snapshot)
476         self.run_cmd("${VM_RESTORE}")
477
478     def ping_wait(self, hostname):
479         '''wait for a hostname to come up on the network'''
480         hostname = self.substitute(hostname)
481         loops=10
482         while loops > 0:
483             try:
484                 self.run_cmd("ping -c 1 -w 10 %s" % hostname)
485                 break
486             except:
487                 loops = loops - 1
488         if loops == 0:
489             raise RuntimeError("Failed to ping %s" % hostname)
490         self.info("Host %s is up" % hostname)
491
492     def port_wait(self, hostname, port, retries=200, delay=3, wait_for_fail=False):
493         '''wait for a host to come up on the network'''
494
495         while retries > 0:
496             child = self.pexpect_spawn("nc -v -z -w 1 %s %u" % (hostname, port), crlf=False, timeout=1)
497             i = child.expect(['succeeded', 'failed', pexpect.EOF, pexpect.TIMEOUT])
498             if wait_for_fail:
499                 if i > 0:
500                     return
501             else:
502                 if i == 0:
503                     return
504
505             time.sleep(delay)
506             retries -= 1
507             self.info("retrying (retries=%u delay=%u)" % (retries, delay))
508
509         raise RuntimeError("gave up waiting for %s:%d" % (hostname, port))
510
511     def run_net_time(self, child):
512         '''run net time on windows'''
513         child.sendline("net time \\\\${HOSTNAME} /set")
514         child.expect("Do you want to set the local computer")
515         child.sendline("Y")
516         child.expect("The command completed successfully")
517
518     def run_date_time(self, child, time_tuple=None):
519         '''run date and time on windows'''
520         if time_tuple is None:
521             time_tuple = time.localtime()
522         child.sendline("date")
523         child.expect("Enter the new date:")
524         i = child.expect(["dd-mm-yy", "mm-dd-yy"])
525         if i == 0:
526             child.sendline(time.strftime("%d-%m-%y", time_tuple))
527         else:
528             child.sendline(time.strftime("%m-%d-%y", time_tuple))
529         child.expect("C:")
530         child.sendline("time")
531         child.expect("Enter the new time:")
532         child.sendline(time.strftime("%H:%M:%S", time_tuple))
533         child.expect("C:")
534
535     def get_ipconfig(self, child):
536         '''get the IP configuration of the child'''
537         child.sendline("ipconfig /all")
538         child.expect('Ethernet adapter ')
539         child.expect("[\w\s]+")
540         self.setvar("WIN_NIC", child.after)
541         child.expect(['IPv4 Address', 'IP Address'])
542         child.expect('\d+.\d+.\d+.\d+')
543         self.setvar('WIN_IPV4_ADDRESS', child.after)
544         child.expect('Subnet Mask')
545         child.expect('\d+.\d+.\d+.\d+')
546         self.setvar('WIN_SUBNET_MASK', child.after)
547         child.expect('Default Gateway')
548         i = child.expect(['\d+.\d+.\d+.\d+', "C:"])
549         if i == 0:
550             self.setvar('WIN_DEFAULT_GATEWAY', child.after)
551             child.expect("C:")
552
553     def get_is_dc(self, child):
554         '''check if a windows machine is a domain controller'''
555         child.sendline("dcdiag")
556         i = child.expect(["is not a Directory Server",
557                           "is not recognized as an internal or external command",
558                           "Home Server = ",
559                           "passed test Replications"])
560         if i == 0:
561             return False
562         if i == 1 or i == 3:
563             child.expect("C:")
564             child.sendline("net config Workstation")
565             child.expect("Workstation domain")
566             child.expect('[\S]+')
567             domain = child.after
568             i = child.expect(["Workstation Domain DNS Name", "Logon domain"])
569             '''If we get the Logon domain first, we are not in an AD domain'''
570             if i == 1:
571                 return False
572             if domain.upper() == self.getvar("WIN_DOMAIN").upper():
573                 return True
574
575         child.expect('[\S]+')
576         hostname = child.after
577         if hostname.upper() == self.getvar("WIN_HOSTNAME").upper():
578             return True
579
580     def set_noexpire(self, child, username):
581         """Ensure this user's password does not expire"""
582         child.sendline('wmic useraccount where name="%s" set PasswordExpires=FALSE' % username)
583         child.expect("update successful")
584         child.expect("C:")
585
586     def run_tlntadmn(self, child):
587         '''remove the annoying telnet restrictions'''
588         child.sendline('tlntadmn config maxconn=1024')
589         child.expect(["The settings were successfully updated", "Access is denied"])
590         child.expect("C:")
591
592     def disable_firewall(self, child):
593         '''remove the annoying firewall'''
594         child.sendline('netsh advfirewall set allprofiles state off')
595         i = child.expect(["Ok", "The following command was not found: advfirewall set allprofiles state off", "The requested operation requires elevation", "Access is denied"])
596         child.expect("C:")
597         if i == 1:
598             child.sendline('netsh firewall set opmode mode = DISABLE profile = ALL')
599             i = child.expect(["Ok", "The following command was not found", "Access is denied"])
600             if i != 0:
601                 self.info("Firewall disable failed - ignoring")
602             child.expect("C:")
603
604     def set_dns(self, child):
605         child.sendline('netsh interface ip set dns "${WIN_NIC}" static ${INTERFACE_IP} primary')
606         i = child.expect(['C:', pexpect.EOF, pexpect.TIMEOUT], timeout=5)
607         if i > 0:
608             return True
609         else:
610             return False
611
612     def set_ip(self, child):
613         """fix the IP address to the same value it had when we
614         connected, but don't use DHCP, and force the DNS server to our
615         DNS server.  This allows DNS updates to run"""
616         self.get_ipconfig(child)
617         if self.getvar("WIN_IPV4_ADDRESS") != self.getvar("WIN_IP"):
618             raise RuntimeError("ipconfig address %s != nmblookup address %s" % (self.getvar("WIN_IPV4_ADDRESS"),
619                                                                                 self.getvar("WIN_IP")))
620         child.sendline('netsh')
621         child.expect('netsh>')
622         child.sendline('offline')
623         child.expect('netsh>')
624         child.sendline('routing ip add persistentroute dest=0.0.0.0 mask=0.0.0.0 name="${WIN_NIC}" nhop=${WIN_DEFAULT_GATEWAY}')
625         child.expect('netsh>')
626         child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1 store=persistent')
627         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)
628         if i == 0:
629             child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1')
630             child.expect('netsh>')
631         child.sendline('commit')
632         child.sendline('online')
633         child.sendline('exit')
634
635         child.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=5)
636         return True
637
638
639     def resolve_ip(self, hostname, retries=60, delay=5):
640         '''resolve an IP given a hostname, assuming NBT'''
641         while retries > 0:
642             child = self.pexpect_spawn("bin/nmblookup %s" % hostname)
643             i = 0
644             while i == 0:
645                 i = child.expect(["querying", '\d+.\d+.\d+.\d+', hostname, "Lookup failed"])
646                 if i == 0:
647                     child.expect("\r")
648             if i == 1:
649                 return child.after
650             retries -= 1
651             time.sleep(delay)
652             self.info("retrying (retries=%u delay=%u)" % (retries, delay))
653         raise RuntimeError("Failed to resolve IP of %s" % hostname)
654
655
656     def open_telnet(self, hostname, username, password, retries=60, delay=5, set_time=False, set_ip=False,
657                     disable_firewall=True, run_tlntadmn=True, set_noexpire=False):
658         '''open a telnet connection to a windows server, return the pexpect child'''
659         set_route = False
660         set_dns = False
661         if self.getvar('WIN_IP'):
662             ip = self.getvar('WIN_IP')
663         else:
664             ip = self.resolve_ip(hostname)
665             self.setvar('WIN_IP', ip)
666         while retries > 0:
667             child = self.pexpect_spawn("telnet " + ip + " -l '" + username + "'")
668             i = child.expect(["Welcome to Microsoft Telnet Service",
669                               "Denying new connections due to the limit on number of connections",
670                               "No more connections are allowed to telnet server",
671                               "Unable to connect to remote host",
672                               "No route to host",
673                               "Connection refused",
674                               pexpect.EOF])
675             if i != 0:
676                 child.close()
677                 time.sleep(delay)
678                 retries -= 1
679                 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
680                 continue
681             child.expect("password:")
682             child.sendline(password)
683             i = child.expect(["C:",
684                               "Denying new connections due to the limit on number of connections",
685                               "No more connections are allowed to telnet server",
686                               "Unable to connect to remote host",
687                               "No route to host",
688                               "Connection refused",
689                               pexpect.EOF])
690             if i != 0:
691                 child.close()
692                 time.sleep(delay)
693                 retries -= 1
694                 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
695                 continue
696             if set_dns:
697                 set_dns = False
698                 if self.set_dns(child):
699                     continue;
700             if set_route:
701                 child.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
702                 child.expect("C:")
703                 set_route = False
704             if set_time:
705                 self.run_date_time(child, None)
706                 set_time = False
707             if run_tlntadmn:
708                 self.run_tlntadmn(child)
709                 run_tlntadmn = False
710             if set_noexpire:
711                 self.set_noexpire(child, username)
712                 set_noexpire = False
713             if disable_firewall:
714                 self.disable_firewall(child)
715                 disable_firewall = False
716             if set_ip:
717                 set_ip = False
718                 if self.set_ip(child):
719                     set_route = True
720                     set_dns = True
721                 continue
722             return child
723         raise RuntimeError("Failed to connect with telnet")
724
725     def kinit(self, username, password):
726         '''use kinit to setup a credentials cache'''
727         self.run_cmd("kdestroy")
728         self.putenv('KRB5CCNAME', "${PREFIX}/ccache.test")
729         username = self.substitute(username)
730         s = username.split('@')
731         if len(s) > 0:
732             s[1] = s[1].upper()
733         username = '@'.join(s)
734         child = self.pexpect_spawn('kinit ' + username)
735         child.expect("Password")
736         child.sendline(password)
737         child.expect(pexpect.EOF)
738         child.close()
739         if child.exitstatus != 0:
740             raise RuntimeError("kinit failed with status %d" % child.exitstatus)
741
742     def get_domains(self):
743         '''return a dictionary of DNS domains and IPs for named.conf'''
744         ret = {}
745         for v in self.vars:
746             if v[-6:] == "_REALM":
747                 base = v[:-6]
748                 if base + '_IP' in self.vars:
749                     ret[self.vars[base + '_REALM']] = self.vars[base + '_IP']
750         return ret
751
752     def wait_reboot(self, retries=3):
753         '''wait for a VM to reboot'''
754
755         # first wait for it to shutdown
756         self.port_wait("${WIN_IP}", 139, wait_for_fail=True, delay=6)
757
758         # now wait for it to come back. If it fails to come back
759         # then try resetting it
760         while retries > 0:
761             try:
762                 self.port_wait("${WIN_IP}", 139)
763                 return
764             except:
765                 retries -= 1
766                 self.vm_reset("${WIN_VM}")
767                 self.info("retrying reboot (retries=%u)" % retries)
768         raise RuntimeError(self.substitute("VM ${WIN_VM} failed to reboot"))
769
770     def get_vms(self):
771         '''return a dictionary of all the configured VM names'''
772         ret = []
773         for v in self.vars:
774             if v[-3:] == "_VM":
775                 ret.append(self.vars[v])
776         return ret
777
778
779     def run_dcpromo_as_first_dc(self, vm, func_level=None):
780         self.setwinvars(vm)
781         self.info("Configuring a windows VM ${WIN_VM} at the first DC in the domain using dcpromo")
782         child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_time=True)
783         if self.get_is_dc(child):
784             return
785
786         if func_level == '2008r2':
787             self.setvar("FUNCTION_LEVEL_INT", str(4))
788         elif func_level == '2003':
789             self.setvar("FUNCTION_LEVEL_INT", str(1))
790         else:
791             self.setvar("FUNCTION_LEVEL_INT", str(0))
792
793         child = self.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_ip=True, set_noexpire=True)
794
795         """This server must therefore not yet be a directory server, so we must promote it"""
796         child.sendline("copy /Y con answers.txt")
797         child.sendline('''
798 [DCInstall]
799 ; New forest promotion
800 ReplicaOrNewDomain=Domain
801 NewDomain=Forest
802 NewDomainDNSName=${WIN_REALM}
803 ForestLevel=${FUNCTION_LEVEL_INT}
804 DomainNetbiosName=${WIN_DOMAIN}
805 DomainLevel=${FUNCTION_LEVEL_INT}
806 InstallDNS=Yes
807 ConfirmGc=Yes
808 CreateDNSDelegation=No
809 DatabasePath="C:\Windows\NTDS"
810 LogPath="C:\Windows\NTDS"
811 SYSVOLPath="C:\Windows\SYSVOL"
812 ; Set SafeModeAdminPassword to the correct value prior to using the unattend file
813 SafeModeAdminPassword=${WIN_PASS}
814 ; Run-time flags (optional)
815 RebootOnCompletion=No
816 \1a
817 ''')
818         child.expect("copied.")
819         child.expect("C:")
820         child.expect("C:")
821         child.sendline("dcpromo /answer:answers.txt")
822         i = child.expect(["You must restart this computer", "failed", "Active Directory Domain Services was not installed", "C:"], timeout=240)
823         if i == 1 or i == 2:
824             raise Exception("dcpromo failed")
825         child.sendline("shutdown -r -t 0")
826         self.port_wait("${WIN_IP}", 139, wait_for_fail=True)
827         self.port_wait("${WIN_IP}", 139)
828         self.retry_cmd("host -t SRV _ldap._tcp.${WIN_REALM} ${WIN_IP}", ['has SRV record'] )
829
830
831     def start_winvm(self, vm):
832         '''start a Windows VM'''
833         self.setwinvars(vm)
834         
835         self.info("Joining a windows box to the domain")
836         self.vm_poweroff("${WIN_VM}", checkfail=False)
837         self.vm_restore("${WIN_VM}", "${WIN_SNAPSHOT}")
838
839     def run_winjoin(self, vm, domain, username="administrator", password="${PASSWORD1}"):
840         '''join a windows box to a domain'''
841         child = self.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time=True, set_ip=True, set_noexpire=True)
842         retries = 5
843         while retries > 0:
844             child.sendline("ipconfig /flushdns")
845             child.expect("C:")
846             child.sendline("netdom join ${WIN_HOSTNAME} /Domain:%s /UserD:%s /PasswordD:%s" % (domain, username, password))
847             i = child.expect(["The command completed successfully", 
848                              "The specified domain either does not exist or could not be contacted."])
849             if i == 0:
850                 break
851             time.sleep(10)
852             retries -= 1
853
854         child.expect("C:")
855         child.sendline("shutdown /r -t 0")
856         self.wait_reboot()
857         child = self.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time=True, set_ip=True)
858         child.sendline("ipconfig /registerdns")
859         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")
860         child.expect("C:")
861
862
863     def test_remote_smbclient(self, vm, username="${WIN_USER}", password="${WIN_PASS}", args=""):
864         '''test smbclient against remote server'''
865         self.setwinvars(vm)
866         self.info('Testing smbclient')
867         self.chdir('${PREFIX}')
868         self.cmd_contains("bin/smbclient --version", ["${SAMBA_VERSION}"])
869         self.retry_cmd('bin/smbclient -L ${WIN_HOSTNAME} -U%s%%%s %s' % (username, password, args), ["IPC"], retries=60, delay=5)
870
871     def test_net_use(self, vm, realm, domain, username, password):
872         self.setwinvars(vm)
873         self.info('Testing net use against Samba3 member')
874         child = self.open_telnet("${WIN_HOSTNAME}", "%s\\%s" % (domain, username), password)
875         child.sendline("net use t: \\\\${HOSTNAME}.%s\\test" % realm)
876         child.expect("The command completed successfully")
877
878
879     def setup(self, testname, subdir):
880         '''setup for main tests, parsing command line'''
881         self.parser.add_option("--conf", type='string', default='', help='config file')
882         self.parser.add_option("--skip", type='string', default='', help='list of steps to skip (comma separated)')
883         self.parser.add_option("--vms", type='string', default=None, help='list of VMs to use (comma separated)')
884         self.parser.add_option("--list", action='store_true', default=False, help='list the available steps')
885         self.parser.add_option("--rebase", action='store_true', default=False, help='do a git pull --rebase')
886         self.parser.add_option("--clean", action='store_true', default=False, help='clean the tree')
887         self.parser.add_option("--prefix", type='string', default=None, help='override install prefix')
888         self.parser.add_option("--sourcetree", type='string', default=None, help='override sourcetree location')
889         self.parser.add_option("--nocleanup", action='store_true', default=False, help='disable cleanup code')
890
891         self.opts, self.args = self.parser.parse_args()
892
893         if not self.opts.conf:
894             print("Please specify a config file with --conf")
895             sys.exit(1)
896
897         # we don't need fsync safety in these tests
898         self.putenv('TDB_NO_FSYNC', '1')
899
900         self.load_config(self.opts.conf)
901
902         self.set_skip(self.opts.skip)
903         self.set_vms(self.opts.vms)
904
905         if self.opts.list:
906             self.list_steps_mode()
907
908         if self.opts.prefix:
909             self.setvar('PREFIX', self.opts.prefix)
910
911         if self.opts.sourcetree:
912             self.setvar('SOURCETREE', self.opts.sourcetree)
913
914         if self.opts.rebase:
915             self.info('rebasing')
916             self.chdir('${SOURCETREE}')
917             self.run_cmd('git pull --rebase')
918
919         if self.opts.clean:
920             self.info('cleaning')
921             self.chdir('${SOURCETREE}/' + subdir)
922             self.run_cmd('make clean')