buildtools: private_libraries should not have a version in the soname
[Samba/gebeck_regimport.git] / wintest / wintest.py
blobec2624bde5541ce0bafcf279e1a922a6ac48db44
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.putenv('PYTHONUNBUFFERED', '1')
17 self.parser = optparse.OptionParser("wintest")
19 def setvar(self, varname, value):
20 '''set a substitution variable'''
21 self.vars[varname] = value
23 def getvar(self, varname):
24 '''return a substitution variable'''
25 if not varname in self.vars:
26 return None
27 return self.vars[varname]
29 def setwinvars(self, vm, prefix='WIN'):
30 '''setup WIN_XX vars based on a vm name'''
31 for v in ['VM', 'HOSTNAME', 'USER', 'PASS', 'SNAPSHOT', 'REALM', 'DOMAIN', 'IP']:
32 vname = '%s_%s' % (vm, v)
33 if vname in self.vars:
34 self.setvar("%s_%s" % (prefix,v), self.substitute("${%s}" % vname))
35 else:
36 self.vars.pop("%s_%s" % (prefix,v), None)
38 if self.getvar("WIN_REALM"):
39 self.setvar("WIN_REALM", self.getvar("WIN_REALM").upper())
40 self.setvar("WIN_LCREALM", self.getvar("WIN_REALM").lower())
41 dnsdomain = self.getvar("WIN_REALM")
42 self.setvar("WIN_BASEDN", "DC=" + dnsdomain.replace(".", ",DC="))
44 def info(self, msg):
45 '''print some information'''
46 if not self.list_mode:
47 print(self.substitute(msg))
49 def load_config(self, fname):
50 '''load the config file'''
51 f = open(fname)
52 for line in f:
53 line = line.strip()
54 if len(line) == 0 or line[0] == '#':
55 continue
56 colon = line.find(':')
57 if colon == -1:
58 raise RuntimeError("Invalid config line '%s'" % line)
59 varname = line[0:colon].strip()
60 value = line[colon+1:].strip()
61 self.setvar(varname, value)
63 def list_steps_mode(self):
64 '''put wintest in step listing mode'''
65 self.list_mode = True
67 def set_skip(self, skiplist):
68 '''set a list of tests to skip'''
69 self.skiplist = skiplist.split(',')
71 def set_vms(self, vms):
72 '''set a list of VMs to test'''
73 if vms is not None:
74 self.vms = vms.split(',')
76 def skip(self, step):
77 '''return True if we should skip a step'''
78 if self.list_mode:
79 print("\t%s" % step)
80 return True
81 return step in self.skiplist
83 def substitute(self, text):
84 """Substitute strings of the form ${NAME} in text, replacing
85 with substitutions from vars.
86 """
87 if isinstance(text, list):
88 ret = text[:]
89 for i in range(len(ret)):
90 ret[i] = self.substitute(ret[i])
91 return ret
93 """We may have objects such as pexpect.EOF that are not strings"""
94 if not isinstance(text, str):
95 return text
96 while True:
97 var_start = text.find("${")
98 if var_start == -1:
99 return text
100 var_end = text.find("}", var_start)
101 if var_end == -1:
102 return text
103 var_name = text[var_start+2:var_end]
104 if not var_name in self.vars:
105 raise RuntimeError("Unknown substitution variable ${%s}" % var_name)
106 text = text.replace("${%s}" % var_name, self.vars[var_name])
107 return text
109 def have_var(self, varname):
110 '''see if a variable has been set'''
111 return varname in self.vars
113 def have_vm(self, vmname):
114 '''see if a VM should be used'''
115 if not self.have_var(vmname + '_VM'):
116 return False
117 if self.vms is None:
118 return True
119 return vmname in self.vms
121 def putenv(self, key, value):
122 '''putenv with substitution'''
123 os.putenv(key, self.substitute(value))
125 def chdir(self, dir):
126 '''chdir with substitution'''
127 os.chdir(self.substitute(dir))
129 def del_files(self, dirs):
130 '''delete all files in the given directory'''
131 for d in dirs:
132 self.run_cmd("find %s -type f | xargs rm -f" % d)
134 def write_file(self, filename, text, mode='w'):
135 '''write to a file'''
136 f = open(self.substitute(filename), mode=mode)
137 f.write(self.substitute(text))
138 f.close()
140 def run_cmd(self, cmd, dir=".", show=None, output=False, checkfail=True):
141 '''run a command'''
142 cmd = self.substitute(cmd)
143 if isinstance(cmd, list):
144 self.info('$ ' + " ".join(cmd))
145 else:
146 self.info('$ ' + cmd)
147 if output:
148 return subprocess.Popen([cmd], shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=dir).communicate()[0]
149 if isinstance(cmd, list):
150 shell=False
151 else:
152 shell=True
153 if checkfail:
154 return subprocess.check_call(cmd, shell=shell, cwd=dir)
155 else:
156 return subprocess.call(cmd, shell=shell, cwd=dir)
159 def run_child(self, cmd, dir="."):
160 '''create a child and return the Popen handle to it'''
161 cwd = os.getcwd()
162 cmd = self.substitute(cmd)
163 if isinstance(cmd, list):
164 self.info('$ ' + " ".join(cmd))
165 else:
166 self.info('$ ' + cmd)
167 if isinstance(cmd, list):
168 shell=False
169 else:
170 shell=True
171 os.chdir(dir)
172 ret = subprocess.Popen(cmd, shell=shell, stderr=subprocess.STDOUT)
173 os.chdir(cwd)
174 return ret
176 def cmd_output(self, cmd):
177 '''return output from and command'''
178 cmd = self.substitute(cmd)
179 return self.run_cmd(cmd, output=True)
181 def cmd_contains(self, cmd, contains, nomatch=False, ordered=False, regex=False,
182 casefold=True):
183 '''check that command output contains the listed strings'''
185 if isinstance(contains, str):
186 contains = [contains]
188 out = self.cmd_output(cmd)
189 self.info(out)
190 for c in self.substitute(contains):
191 if regex:
192 if casefold:
193 c = c.upper()
194 out = out.upper()
195 m = re.search(c, out)
196 if m is None:
197 start = -1
198 end = -1
199 else:
200 start = m.start()
201 end = m.end()
202 elif casefold:
203 start = out.upper().find(c.upper())
204 end = start + len(c)
205 else:
206 start = out.find(c)
207 end = start + len(c)
208 if nomatch:
209 if start != -1:
210 raise RuntimeError("Expected to not see %s in %s" % (c, cmd))
211 else:
212 if start == -1:
213 raise RuntimeError("Expected to see %s in %s" % (c, cmd))
214 if ordered and start != -1:
215 out = out[end:]
217 def retry_cmd(self, cmd, contains, retries=30, delay=2, wait_for_fail=False,
218 ordered=False, regex=False, casefold=True):
219 '''retry a command a number of times'''
220 while retries > 0:
221 try:
222 self.cmd_contains(cmd, contains, nomatch=wait_for_fail,
223 ordered=ordered, regex=regex, casefold=casefold)
224 return
225 except:
226 time.sleep(delay)
227 retries -= 1
228 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
229 raise RuntimeError("Failed to find %s" % contains)
231 def pexpect_spawn(self, cmd, timeout=60, crlf=True, casefold=True):
232 '''wrapper around pexpect spawn'''
233 cmd = self.substitute(cmd)
234 self.info("$ " + cmd)
235 ret = pexpect.spawn(cmd, logfile=sys.stdout, timeout=timeout)
237 def sendline_sub(line):
238 line = self.substitute(line)
239 if crlf:
240 line = line.replace('\n', '\r\n') + '\r'
241 return ret.old_sendline(line)
243 def expect_sub(line, timeout=ret.timeout, casefold=casefold):
244 line = self.substitute(line)
245 if casefold:
246 if isinstance(line, list):
247 for i in range(len(line)):
248 if isinstance(line[i], str):
249 line[i] = '(?i)' + line[i]
250 elif isinstance(line, str):
251 line = '(?i)' + line
252 return ret.old_expect(line, timeout=timeout)
254 ret.old_sendline = ret.sendline
255 ret.sendline = sendline_sub
256 ret.old_expect = ret.expect
257 ret.expect = expect_sub
259 return ret
261 def get_nameserver(self):
262 '''Get the current nameserver from /etc/resolv.conf'''
263 child = self.pexpect_spawn('cat /etc/resolv.conf', crlf=False)
264 i = child.expect(['Generated by wintest', 'nameserver'])
265 if i == 0:
266 child.expect('your original resolv.conf')
267 child.expect('nameserver')
268 child.expect('\d+.\d+.\d+.\d+')
269 return child.after
271 def vm_poweroff(self, vmname, checkfail=True):
272 '''power off a VM'''
273 self.setvar('VMNAME', vmname)
274 self.run_cmd("${VM_POWEROFF}", checkfail=checkfail)
276 def vm_reset(self, vmname):
277 '''reset a VM'''
278 self.setvar('VMNAME', vmname)
279 self.run_cmd("${VM_RESET}")
281 def vm_restore(self, vmname, snapshot):
282 '''restore a VM'''
283 self.setvar('VMNAME', vmname)
284 self.setvar('SNAPSHOT', snapshot)
285 self.run_cmd("${VM_RESTORE}")
287 def ping_wait(self, hostname):
288 '''wait for a hostname to come up on the network'''
289 hostname = self.substitute(hostname)
290 loops=10
291 while loops > 0:
292 try:
293 self.run_cmd("ping -c 1 -w 10 %s" % hostname)
294 break
295 except:
296 loops = loops - 1
297 if loops == 0:
298 raise RuntimeError("Failed to ping %s" % hostname)
299 self.info("Host %s is up" % hostname)
301 def port_wait(self, hostname, port, retries=200, delay=3, wait_for_fail=False):
302 '''wait for a host to come up on the network'''
303 self.retry_cmd("nc -v -z -w 1 %s %u" % (hostname, port), ['succeeded'],
304 retries=retries, delay=delay, wait_for_fail=wait_for_fail)
306 def run_net_time(self, child):
307 '''run net time on windows'''
308 child.sendline("net time \\\\${HOSTNAME} /set")
309 child.expect("Do you want to set the local computer")
310 child.sendline("Y")
311 child.expect("The command completed successfully")
313 def run_date_time(self, child, time_tuple=None):
314 '''run date and time on windows'''
315 if time_tuple is None:
316 time_tuple = time.localtime()
317 child.sendline("date")
318 child.expect("Enter the new date:")
319 i = child.expect(["dd-mm-yy", "mm-dd-yy"])
320 if i == 0:
321 child.sendline(time.strftime("%d-%m-%y", time_tuple))
322 else:
323 child.sendline(time.strftime("%m-%d-%y", time_tuple))
324 child.expect("C:")
325 child.sendline("time")
326 child.expect("Enter the new time:")
327 child.sendline(time.strftime("%H:%M:%S", time_tuple))
328 child.expect("C:")
330 def get_ipconfig(self, child):
331 '''get the IP configuration of the child'''
332 child.sendline("ipconfig /all")
333 child.expect('Ethernet adapter ')
334 child.expect("[\w\s]+")
335 self.setvar("WIN_NIC", child.after)
336 child.expect(['IPv4 Address', 'IP Address'])
337 child.expect('\d+.\d+.\d+.\d+')
338 self.setvar('WIN_IPV4_ADDRESS', child.after)
339 child.expect('Subnet Mask')
340 child.expect('\d+.\d+.\d+.\d+')
341 self.setvar('WIN_SUBNET_MASK', child.after)
342 child.expect('Default Gateway')
343 child.expect('\d+.\d+.\d+.\d+')
344 self.setvar('WIN_DEFAULT_GATEWAY', child.after)
345 child.expect("C:")
347 def get_is_dc(self, child):
348 '''check if a windows machine is a domain controller'''
349 child.sendline("dcdiag")
350 i = child.expect(["is not a Directory Server",
351 "is not recognized as an internal or external command",
352 "Home Server = ",
353 "passed test Replications"])
354 if i == 0:
355 return False
356 if i == 1 or i == 3:
357 child.expect("C:")
358 child.sendline("net config Workstation")
359 child.expect("Workstation domain")
360 child.expect('[\S]+')
361 domain = child.after
362 i = child.expect(["Workstation Domain DNS Name", "Logon domain"])
363 '''If we get the Logon domain first, we are not in an AD domain'''
364 if i == 1:
365 return False
366 if domain.upper() == self.getvar("WIN_DOMAIN").upper():
367 return True
369 child.expect('[\S]+')
370 hostname = child.after
371 if hostname.upper() == self.getvar("WIN_HOSTNAME").upper():
372 return True
374 def run_tlntadmn(self, child):
375 '''remove the annoying telnet restrictions'''
376 child.sendline('tlntadmn config maxconn=1024')
377 child.expect("The settings were successfully updated")
378 child.expect("C:")
380 def disable_firewall(self, child):
381 '''remove the annoying firewall'''
382 child.sendline('netsh advfirewall set allprofiles state off')
383 i = child.expect(["Ok", "The following command was not found: advfirewall set allprofiles state off"])
384 child.expect("C:")
385 if i == 1:
386 child.sendline('netsh firewall set opmode mode = DISABLE profile = ALL')
387 i = child.expect(["Ok", "The following command was not found"])
388 if i != 0:
389 self.info("Firewall disable failed - ignoring")
390 child.expect("C:")
392 def set_dns(self, child):
393 child.sendline('netsh interface ip set dns "${WIN_NIC}" static ${INTERFACE_IP} primary')
394 i = child.expect(['C:', pexpect.EOF, pexpect.TIMEOUT], timeout=5)
395 if i > 0:
396 return True
397 else:
398 return False
400 def set_ip(self, child):
401 """fix the IP address to the same value it had when we
402 connected, but don't use DHCP, and force the DNS server to our
403 DNS server. This allows DNS updates to run"""
404 self.get_ipconfig(child)
405 if self.getvar("WIN_IPV4_ADDRESS") != self.getvar("WIN_IP"):
406 raise RuntimeError("ipconfig address %s != nmblookup address %s" % (self.getvar("WIN_IPV4_ADDRESS"),
407 self.getvar("WIN_IP")))
408 child.sendline('netsh')
409 child.expect('netsh>')
410 child.sendline('offline')
411 child.expect('netsh>')
412 child.sendline('routing ip add persistentroute dest=0.0.0.0 mask=0.0.0.0 name="${WIN_NIC}" nhop=${WIN_DEFAULT_GATEWAY}')
413 child.expect('netsh>')
414 child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1 store=persistent')
415 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)
416 if i == 0:
417 child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1')
418 child.expect('netsh>')
419 child.sendline('commit')
420 child.sendline('online')
421 child.sendline('exit')
423 child.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=5)
424 return True
427 def resolve_ip(self, hostname, retries=60, delay=5):
428 '''resolve an IP given a hostname, assuming NBT'''
429 while retries > 0:
430 child = self.pexpect_spawn("bin/nmblookup %s" % hostname)
431 i = child.expect(['\d+.\d+.\d+.\d+', "Lookup failed"])
432 if i == 0:
433 return child.after
434 retries -= 1
435 time.sleep(delay)
436 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
437 raise RuntimeError("Failed to resolve IP of %s" % hostname)
440 def open_telnet(self, hostname, username, password, retries=60, delay=5, set_time=False, set_ip=False,
441 disable_firewall=True, run_tlntadmn=True):
442 '''open a telnet connection to a windows server, return the pexpect child'''
443 set_route = False
444 set_dns = False
445 if self.getvar('WIN_IP'):
446 ip = self.getvar('WIN_IP')
447 else:
448 ip = self.resolve_ip(hostname)
449 self.setvar('WIN_IP', ip)
450 while retries > 0:
451 child = self.pexpect_spawn("telnet " + ip + " -l '" + username + "'")
452 i = child.expect(["Welcome to Microsoft Telnet Service",
453 "Denying new connections due to the limit on number of connections",
454 "No more connections are allowed to telnet server",
455 "Unable to connect to remote host",
456 "No route to host",
457 "Connection refused",
458 pexpect.EOF])
459 if i != 0:
460 child.close()
461 time.sleep(delay)
462 retries -= 1
463 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
464 continue
465 child.expect("password:")
466 child.sendline(password)
467 i = child.expect(["C:",
468 "Denying new connections due to the limit on number of connections",
469 "No more connections are allowed to telnet server",
470 "Unable to connect to remote host",
471 "No route to host",
472 "Connection refused",
473 pexpect.EOF])
474 if i != 0:
475 child.close()
476 time.sleep(delay)
477 retries -= 1
478 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
479 continue
480 if set_dns:
481 set_dns = False
482 if self.set_dns(child):
483 continue;
484 if set_route:
485 child.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
486 child.expect("C:")
487 set_route = False
488 if set_time:
489 self.run_date_time(child, None)
490 set_time = False
491 if run_tlntadmn:
492 self.run_tlntadmn(child)
493 run_tlntadmn = False
494 if disable_firewall:
495 self.disable_firewall(child)
496 disable_firewall = False
497 if set_ip:
498 set_ip = False
499 if self.set_ip(child):
500 set_route = True
501 set_dns = True
502 continue
503 return child
504 raise RuntimeError("Failed to connect with telnet")
506 def kinit(self, username, password):
507 '''use kinit to setup a credentials cache'''
508 self.run_cmd("kdestroy")
509 self.putenv('KRB5CCNAME', "${PREFIX}/ccache.test")
510 username = self.substitute(username)
511 s = username.split('@')
512 if len(s) > 0:
513 s[1] = s[1].upper()
514 username = '@'.join(s)
515 child = self.pexpect_spawn('kinit ' + username)
516 child.expect("Password")
517 child.sendline(password)
518 child.expect(pexpect.EOF)
519 child.close()
520 if child.exitstatus != 0:
521 raise RuntimeError("kinit failed with status %d" % child.exitstatus)
523 def get_domains(self):
524 '''return a dictionary of DNS domains and IPs for named.conf'''
525 ret = {}
526 for v in self.vars:
527 if v[-6:] == "_REALM":
528 base = v[:-6]
529 if base + '_IP' in self.vars:
530 ret[self.vars[base + '_REALM']] = self.vars[base + '_IP']
531 return ret
533 def wait_reboot(self, retries=3):
534 '''wait for a VM to reboot'''
536 # first wait for it to shutdown
537 self.port_wait("${WIN_IP}", 139, wait_for_fail=True, delay=6)
539 # now wait for it to come back. If it fails to come back
540 # then try resetting it
541 while retries > 0:
542 try:
543 self.port_wait("${WIN_IP}", 139)
544 return
545 except:
546 retries -= 1
547 self.vm_reset("${WIN_VM}")
548 self.info("retrying reboot (retries=%u)" % retries)
549 raise RuntimeError(self.substitute("VM ${WIN_VM} failed to reboot"))
551 def get_vms(self):
552 '''return a dictionary of all the configured VM names'''
553 ret = []
554 for v in self.vars:
555 if v[-3:] == "_VM":
556 ret.append(self.vars[v])
557 return ret
559 def setup(self, testname, subdir):
560 '''setup for main tests, parsing command line'''
561 self.parser.add_option("--conf", type='string', default='', help='config file')
562 self.parser.add_option("--skip", type='string', default='', help='list of steps to skip (comma separated)')
563 self.parser.add_option("--vms", type='string', default=None, help='list of VMs to use (comma separated)')
564 self.parser.add_option("--list", action='store_true', default=False, help='list the available steps')
565 self.parser.add_option("--rebase", action='store_true', default=False, help='do a git pull --rebase')
566 self.parser.add_option("--clean", action='store_true', default=False, help='clean the tree')
567 self.parser.add_option("--prefix", type='string', default=None, help='override install prefix')
568 self.parser.add_option("--sourcetree", type='string', default=None, help='override sourcetree location')
569 self.parser.add_option("--nocleanup", action='store_true', default=False, help='disable cleanup code')
571 self.opts, self.args = self.parser.parse_args()
573 if not self.opts.conf:
574 print("Please specify a config file with --conf")
575 sys.exit(1)
577 # we don't need fsync safety in these tests
578 self.putenv('TDB_NO_FSYNC', '1')
580 self.load_config(self.opts.conf)
582 self.set_skip(self.opts.skip)
583 self.set_vms(self.opts.vms)
585 if self.opts.list:
586 self.list_steps_mode()
588 if self.opts.prefix:
589 self.setvar('PREFIX', self.opts.prefix)
591 if self.opts.sourcetree:
592 self.setvar('SOURCETREE', self.opts.sourcetree)
594 if self.opts.rebase:
595 self.info('rebasing')
596 self.chdir('${SOURCETREE}')
597 self.run_cmd('git pull --rebase')
599 if self.opts.clean:
600 self.info('cleaning')
601 self.chdir('${SOURCETREE}/' + subdir)
602 self.run_cmd('make clean')