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