pytalloc: Make py_talloc_default_repr private (now exposed by talloc.Object).
[Samba.git] / wintest / wintest.py
blobcc24c0d22a6cb729a2b13f761f102645fa0f64d7
1 #!/usr/bin/env python
3 '''automated testing library for testing Samba against windows'''
5 import pexpect, subprocess
6 import sys, os, time, re
8 class wintest():
9 '''testing of Samba against windows VMs'''
11 def __init__(self):
12 self.vars = {}
13 self.list_mode = False
14 self.vms = None
15 os.putenv('PYTHONUNBUFFERED', '1')
17 def setvar(self, varname, value):
18 '''set a substitution variable'''
19 self.vars[varname] = value
21 def getvar(self, varname):
22 '''return a substitution variable'''
23 if not varname in self.vars:
24 return None
25 return self.vars[varname]
27 def setwinvars(self, vm, prefix='WIN'):
28 '''setup WIN_XX vars based on a vm name'''
29 for v in ['VM', 'HOSTNAME', 'USER', 'PASS', 'SNAPSHOT', 'BASEDN', 'REALM', 'DOMAIN', 'IP']:
30 vname = '%s_%s' % (vm, v)
31 if vname in self.vars:
32 self.setvar("%s_%s" % (prefix,v), self.substitute("${%s}" % vname))
33 else:
34 self.vars.pop("%s_%s" % (prefix,v), None)
36 def info(self, msg):
37 '''print some information'''
38 if not self.list_mode:
39 print(self.substitute(msg))
41 def load_config(self, fname):
42 '''load the config file'''
43 f = open(fname)
44 for line in f:
45 line = line.strip()
46 if len(line) == 0 or line[0] == '#':
47 continue
48 colon = line.find(':')
49 if colon == -1:
50 raise RuntimeError("Invalid config line '%s'" % line)
51 varname = line[0:colon].strip()
52 value = line[colon+1:].strip()
53 self.setvar(varname, value)
55 def list_steps_mode(self):
56 '''put wintest in step listing mode'''
57 self.list_mode = True
59 def set_skip(self, skiplist):
60 '''set a list of tests to skip'''
61 self.skiplist = skiplist.split(',')
63 def set_vms(self, vms):
64 '''set a list of VMs to test'''
65 self.vms = vms.split(',')
67 def skip(self, step):
68 '''return True if we should skip a step'''
69 if self.list_mode:
70 print("\t%s" % step)
71 return True
72 return step in self.skiplist
74 def substitute(self, text):
75 """Substitute strings of the form ${NAME} in text, replacing
76 with substitutions from vars.
77 """
78 if isinstance(text, list):
79 ret = text[:]
80 for i in range(len(ret)):
81 ret[i] = self.substitute(ret[i])
82 return ret
84 """We may have objects such as pexpect.EOF that are not strings"""
85 if not isinstance(text, str):
86 return text
87 while True:
88 var_start = text.find("${")
89 if var_start == -1:
90 return text
91 var_end = text.find("}", var_start)
92 if var_end == -1:
93 return text
94 var_name = text[var_start+2:var_end]
95 if not var_name in self.vars:
96 raise RuntimeError("Unknown substitution variable ${%s}" % var_name)
97 text = text.replace("${%s}" % var_name, self.vars[var_name])
98 return text
100 def have_var(self, varname):
101 '''see if a variable has been set'''
102 return varname in self.vars
104 def have_vm(self, vmname):
105 '''see if a VM should be used'''
106 if not self.have_var(vmname + '_VM'):
107 return False
108 if self.vms is None:
109 return True
110 return vmname in self.vms
112 def putenv(self, key, value):
113 '''putenv with substitution'''
114 os.putenv(key, self.substitute(value))
116 def chdir(self, dir):
117 '''chdir with substitution'''
118 os.chdir(self.substitute(dir))
120 def del_files(self, dirs):
121 '''delete all files in the given directory'''
122 for d in dirs:
123 self.run_cmd("find %s -type f | xargs rm -f" % d)
125 def write_file(self, filename, text, mode='w'):
126 '''write to a file'''
127 f = open(self.substitute(filename), mode=mode)
128 f.write(self.substitute(text))
129 f.close()
131 def run_cmd(self, cmd, dir=".", show=None, output=False, checkfail=True):
132 '''run a command'''
133 cmd = self.substitute(cmd)
134 if isinstance(cmd, list):
135 self.info('$ ' + " ".join(cmd))
136 else:
137 self.info('$ ' + cmd)
138 if output:
139 return subprocess.Popen([cmd], shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=dir).communicate()[0]
140 if isinstance(cmd, list):
141 shell=False
142 else:
143 shell=True
144 if checkfail:
145 return subprocess.check_call(cmd, shell=shell, cwd=dir)
146 else:
147 return subprocess.call(cmd, shell=shell, cwd=dir)
150 def run_child(self, cmd, dir="."):
151 '''create a child and return the Popen handle to it'''
152 cwd = os.getcwd()
153 cmd = self.substitute(cmd)
154 if isinstance(cmd, list):
155 self.info('$ ' + " ".join(cmd))
156 else:
157 self.info('$ ' + cmd)
158 if isinstance(cmd, list):
159 shell=False
160 else:
161 shell=True
162 os.chdir(dir)
163 ret = subprocess.Popen(cmd, shell=shell, stderr=subprocess.STDOUT)
164 os.chdir(cwd)
165 return ret
167 def cmd_output(self, cmd):
168 '''return output from and command'''
169 cmd = self.substitute(cmd)
170 return self.run_cmd(cmd, output=True)
172 def cmd_contains(self, cmd, contains, nomatch=False, ordered=False, regex=False,
173 casefold=False):
174 '''check that command output contains the listed strings'''
176 if isinstance(contains, str):
177 contains = [contains]
179 out = self.cmd_output(cmd)
180 self.info(out)
181 for c in self.substitute(contains):
182 if regex:
183 m = re.search(c, out)
184 if m is None:
185 start = -1
186 end = -1
187 else:
188 start = m.start()
189 end = m.end()
190 elif casefold:
191 start = out.upper().find(c.upper())
192 end = start + len(c)
193 else:
194 start = out.find(c)
195 end = start + len(c)
196 if nomatch:
197 if start != -1:
198 raise RuntimeError("Expected to not see %s in %s" % (c, cmd))
199 else:
200 if start == -1:
201 raise RuntimeError("Expected to see %s in %s" % (c, cmd))
202 if ordered and start != -1:
203 out = out[end:]
205 def retry_cmd(self, cmd, contains, retries=30, delay=2, wait_for_fail=False,
206 ordered=False, regex=False, casefold=False):
207 '''retry a command a number of times'''
208 while retries > 0:
209 try:
210 self.cmd_contains(cmd, contains, nomatch=wait_for_fail,
211 ordered=ordered, regex=regex, casefold=casefold)
212 return
213 except:
214 time.sleep(delay)
215 retries -= 1
216 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
217 raise RuntimeError("Failed to find %s" % contains)
219 def pexpect_spawn(self, cmd, timeout=60, crlf=True, casefold=True):
220 '''wrapper around pexpect spawn'''
221 cmd = self.substitute(cmd)
222 self.info("$ " + cmd)
223 ret = pexpect.spawn(cmd, logfile=sys.stdout, timeout=timeout)
225 def sendline_sub(line):
226 line = self.substitute(line)
227 if crlf:
228 line = line.replace('\n', '\r\n') + '\r'
229 return ret.old_sendline(line)
231 def expect_sub(line, timeout=ret.timeout, casefold=casefold):
232 line = self.substitute(line)
233 if casefold:
234 line = "(?i)" + line
235 return ret.old_expect(line, timeout=timeout)
237 ret.old_sendline = ret.sendline
238 ret.sendline = sendline_sub
239 ret.old_expect = ret.expect
240 ret.expect = expect_sub
242 return ret
244 def get_nameserver(self):
245 '''Get the current nameserver from /etc/resolv.conf'''
246 child = self.pexpect_spawn('cat /etc/resolv.conf', crlf=False)
247 i = child.expect(['Generated by wintest', 'nameserver'])
248 if i == 0:
249 child.expect('your original resolv.conf')
250 child.expect('nameserver')
251 child.expect('\d+.\d+.\d+.\d+')
252 return child.after
254 def vm_poweroff(self, vmname, checkfail=True):
255 '''power off a VM'''
256 self.setvar('VMNAME', vmname)
257 self.run_cmd("${VM_POWEROFF}", checkfail=checkfail)
259 def vm_reset(self, vmname):
260 '''reset a VM'''
261 self.setvar('VMNAME', vmname)
262 self.run_cmd("${VM_RESET}")
264 def vm_restore(self, vmname, snapshot):
265 '''restore a VM'''
266 self.setvar('VMNAME', vmname)
267 self.setvar('SNAPSHOT', snapshot)
268 self.run_cmd("${VM_RESTORE}")
270 def ping_wait(self, hostname):
271 '''wait for a hostname to come up on the network'''
272 hostname = self.substitute(hostname)
273 loops=10
274 while loops > 0:
275 try:
276 self.run_cmd("ping -c 1 -w 10 %s" % hostname)
277 break
278 except:
279 loops = loops - 1
280 if loops == 0:
281 raise RuntimeError("Failed to ping %s" % hostname)
282 self.info("Host %s is up" % hostname)
284 def port_wait(self, hostname, port, retries=200, delay=3, wait_for_fail=False):
285 '''wait for a host to come up on the network'''
286 self.retry_cmd("nc -v -z -w 1 %s %u" % (hostname, port), ['succeeded'],
287 retries=retries, delay=delay, wait_for_fail=wait_for_fail)
289 def run_net_time(self, child):
290 '''run net time on windows'''
291 child.sendline("net time \\\\${HOSTNAME} /set")
292 child.expect("Do you want to set the local computer")
293 child.sendline("Y")
294 child.expect("The command completed successfully")
296 def run_date_time(self, child, time_tuple=None):
297 '''run date and time on windows'''
298 if time_tuple is None:
299 time_tuple = time.localtime()
300 child.sendline("date")
301 child.expect("Enter the new date:")
302 i = child.expect(["dd-mm-yy", "mm-dd-yy"])
303 if i == 0:
304 child.sendline(time.strftime("%d-%m-%y", time_tuple))
305 else:
306 child.sendline(time.strftime("%m-%d-%y", time_tuple))
307 child.expect("C:")
308 child.sendline("time")
309 child.expect("Enter the new time:")
310 child.sendline(time.strftime("%H:%M:%S", time_tuple))
311 child.expect("C:")
313 def get_ipconfig(self, child):
314 '''get the IP configuration of the child'''
315 child.sendline("ipconfig /all")
316 child.expect('Ethernet adapter ')
317 child.expect("[\w\s]+")
318 self.setvar("WIN_NIC", child.after)
319 child.expect(['IPv4 Address', 'IP Address'])
320 child.expect('\d+.\d+.\d+.\d+')
321 self.setvar('WIN_IPV4_ADDRESS', child.after)
322 child.expect('Subnet Mask')
323 child.expect('\d+.\d+.\d+.\d+')
324 self.setvar('WIN_SUBNET_MASK', child.after)
325 child.expect('Default Gateway')
326 child.expect('\d+.\d+.\d+.\d+')
327 self.setvar('WIN_DEFAULT_GATEWAY', child.after)
328 child.expect("C:")
330 def run_tlntadmn(self, child):
331 '''remove the annoying telnet restrictions'''
332 child.sendline('tlntadmn config maxconn=1024')
333 child.expect("The settings were successfully updated")
334 child.expect("C:")
336 def disable_firewall(self, child):
337 '''remove the annoying firewall'''
338 child.sendline('netsh advfirewall set allprofiles state off')
339 i = child.expect(["Ok", "The following command was not found: advfirewall set allprofiles state off"])
340 child.expect("C:")
341 if i == 1:
342 child.sendline('netsh firewall set opmode mode = DISABLE profile = ALL')
343 i = child.expect(["Ok", "The following command was not found"])
344 if i != 0:
345 self.info("Firewall disable failed - ignoring")
346 child.expect("C:")
348 def set_dns(self, child):
349 child.sendline('netsh interface ip set dns "${WIN_NIC}" static ${INTERFACE_IP} primary')
350 i = child.expect(['C:', pexpect.EOF, pexpect.TIMEOUT], timeout=5)
351 if i > 0:
352 return True
353 else:
354 return False
356 def set_ip(self, child):
357 """fix the IP address to the same value it had when we
358 connected, but don't use DHCP, and force the DNS server to our
359 DNS server. This allows DNS updates to run"""
360 self.get_ipconfig(child)
361 if self.getvar("WIN_IPV4_ADDRESS") != self.getvar("WIN_IP"):
362 raise RuntimeError("ipconfig address %s != nmblookup address %s" % (self.getvar("WIN_IPV4_ADDRESS"),
363 self.getvar("WIN_IP")))
364 child.sendline('netsh')
365 child.expect('netsh>')
366 child.sendline('offline')
367 child.expect('netsh>')
368 child.sendline('routing ip add persistentroute dest=0.0.0.0 mask=0.0.0.0 name="${WIN_NIC}" nhop=${WIN_DEFAULT_GATEWAY}')
369 child.expect('netsh>')
370 child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1 store=persistent')
371 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)
372 if i == 0:
373 child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1')
374 child.expect('netsh>')
375 child.sendline('commit')
376 child.sendline('online')
377 child.sendline('exit')
379 child.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=5)
380 return True
383 def resolve_ip(self, hostname, retries=60, delay=5):
384 '''resolve an IP given a hostname, assuming NBT'''
385 while retries > 0:
386 child = self.pexpect_spawn("bin/nmblookup %s" % hostname)
387 i = child.expect(['\d+.\d+.\d+.\d+', "Lookup failed"])
388 if i == 0:
389 return child.after
390 retries -= 1
391 time.sleep(delay)
392 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
393 raise RuntimeError("Failed to resolve IP of %s" % hostname)
396 def open_telnet(self, hostname, username, password, retries=60, delay=5, set_time=False, set_ip=False,
397 disable_firewall=True, run_tlntadmn=True):
398 '''open a telnet connection to a windows server, return the pexpect child'''
399 set_route = False
400 set_dns = False
401 if self.getvar('WIN_IP'):
402 ip = self.getvar('WIN_IP')
403 else:
404 ip = self.resolve_ip(hostname)
405 self.setvar('WIN_IP', ip)
406 while retries > 0:
407 child = self.pexpect_spawn("telnet " + ip + " -l '" + username + "'")
408 i = child.expect(["Welcome to Microsoft Telnet Service",
409 "Denying new connections due to the limit on number of connections",
410 "No more connections are allowed to telnet server",
411 "Unable to connect to remote host",
412 "No route to host",
413 "Connection refused",
414 pexpect.EOF])
415 if i != 0:
416 child.close()
417 time.sleep(delay)
418 retries -= 1
419 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
420 continue
421 child.expect("password:")
422 child.sendline(password)
423 i = child.expect(["C:",
424 "Denying new connections due to the limit on number of connections",
425 "No more connections are allowed to telnet server",
426 "Unable to connect to remote host",
427 "No route to host",
428 "Connection refused",
429 pexpect.EOF])
430 if i != 0:
431 child.close()
432 time.sleep(delay)
433 retries -= 1
434 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
435 continue
436 if set_dns:
437 set_dns = False
438 if self.set_dns(child):
439 continue;
440 if set_route:
441 child.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
442 child.expect("C:")
443 set_route = False
444 if set_time:
445 self.run_date_time(child, None)
446 set_time = False
447 if run_tlntadmn:
448 self.run_tlntadmn(child)
449 run_tlntadmn = False
450 if disable_firewall:
451 self.disable_firewall(child)
452 disable_firewall = False
453 if set_ip:
454 set_ip = False
455 if self.set_ip(child):
456 set_route = True
457 set_dns = True
458 continue
459 return child
460 raise RuntimeError("Failed to connect with telnet")
462 def kinit(self, username, password):
463 '''use kinit to setup a credentials cache'''
464 self.run_cmd("kdestroy")
465 self.putenv('KRB5CCNAME', "${PREFIX}/ccache.test")
466 username = self.substitute(username)
467 s = username.split('@')
468 if len(s) > 0:
469 s[1] = s[1].upper()
470 username = '@'.join(s)
471 child = self.pexpect_spawn('kinit ' + username)
472 child.expect("Password")
473 child.sendline(password)
474 child.expect(pexpect.EOF)
475 child.close()
476 if child.exitstatus != 0:
477 raise RuntimeError("kinit failed with status %d" % child.exitstatus)
479 def get_domains(self):
480 '''return a dictionary of DNS domains and IPs for named.conf'''
481 ret = {}
482 for v in self.vars:
483 if v[-6:] == "_REALM":
484 base = v[:-6]
485 if base + '_IP' in self.vars:
486 ret[self.vars[base + '_REALM']] = self.vars[base + '_IP']
487 return ret
489 def wait_reboot(self, retries=3):
490 '''wait for a VM to reboot'''
492 # first wait for it to shutdown
493 self.port_wait("${WIN_IP}", 139, wait_for_fail=True, delay=6)
495 # now wait for it to come back. If it fails to come back
496 # then try resetting it
497 while retries > 0:
498 try:
499 self.port_wait("${WIN_IP}", 139)
500 return
501 except:
502 retries -= 1
503 self.vm_reset("${WIN_VM}")
504 self.info("retrying reboot (retries=%u)" % retries)
505 raise RuntimeError(self.substitute("VM ${WIN_VM} failed to reboot"))