3 '''automated testing library for testing Samba against windows'''
5 import pexpect, subprocess
6 import sys, os, time, re
9 '''testing of Samba against windows VMs'''
13 self.list_mode = False
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:
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', '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))
34 self.vars.pop("%s_%s" % (prefix,v), None)
36 if self.getvar("WIN_REALM"):
37 self.setvar("WIN_REALM", self.getvar("WIN_REALM").upper())
38 self.setvar("WIN_LCREALM", self.getvar("WIN_REALM").lower())
39 dnsdomain = self.getvar("WIN_REALM")
40 self.setvar("WIN_BASEDN", "DC=" + dnsdomain.replace(".", ",DC="))
43 '''print some information'''
44 if not self.list_mode:
45 print(self.substitute(msg))
47 def load_config(self, fname):
48 '''load the config file'''
52 if len(line) == 0 or line[0] == '#':
54 colon = line.find(':')
56 raise RuntimeError("Invalid config line '%s'" % line)
57 varname = line[0:colon].strip()
58 value = line[colon+1:].strip()
59 self.setvar(varname, value)
61 def list_steps_mode(self):
62 '''put wintest in step listing mode'''
65 def set_skip(self, skiplist):
66 '''set a list of tests to skip'''
67 self.skiplist = skiplist.split(',')
69 def set_vms(self, vms):
70 '''set a list of VMs to test'''
71 self.vms = vms.split(',')
74 '''return True if we should skip a step'''
78 return step in self.skiplist
80 def substitute(self, text):
81 """Substitute strings of the form ${NAME} in text, replacing
82 with substitutions from vars.
84 if isinstance(text, list):
86 for i in range(len(ret)):
87 ret[i] = self.substitute(ret[i])
90 """We may have objects such as pexpect.EOF that are not strings"""
91 if not isinstance(text, str):
94 var_start = text.find("${")
97 var_end = text.find("}", var_start)
100 var_name = text[var_start+2:var_end]
101 if not var_name in self.vars:
102 raise RuntimeError("Unknown substitution variable ${%s}" % var_name)
103 text = text.replace("${%s}" % var_name, self.vars[var_name])
106 def have_var(self, varname):
107 '''see if a variable has been set'''
108 return varname in self.vars
110 def have_vm(self, vmname):
111 '''see if a VM should be used'''
112 if not self.have_var(vmname + '_VM'):
116 return vmname in self.vms
118 def putenv(self, key, value):
119 '''putenv with substitution'''
120 os.putenv(key, self.substitute(value))
122 def chdir(self, dir):
123 '''chdir with substitution'''
124 os.chdir(self.substitute(dir))
126 def del_files(self, dirs):
127 '''delete all files in the given directory'''
129 self.run_cmd("find %s -type f | xargs rm -f" % d)
131 def write_file(self, filename, text, mode='w'):
132 '''write to a file'''
133 f = open(self.substitute(filename), mode=mode)
134 f.write(self.substitute(text))
137 def run_cmd(self, cmd, dir=".", show=None, output=False, checkfail=True):
139 cmd = self.substitute(cmd)
140 if isinstance(cmd, list):
141 self.info('$ ' + " ".join(cmd))
143 self.info('$ ' + cmd)
145 return subprocess.Popen([cmd], shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=dir).communicate()[0]
146 if isinstance(cmd, list):
151 return subprocess.check_call(cmd, shell=shell, cwd=dir)
153 return subprocess.call(cmd, shell=shell, cwd=dir)
156 def run_child(self, cmd, dir="."):
157 '''create a child and return the Popen handle to it'''
159 cmd = self.substitute(cmd)
160 if isinstance(cmd, list):
161 self.info('$ ' + " ".join(cmd))
163 self.info('$ ' + cmd)
164 if isinstance(cmd, list):
169 ret = subprocess.Popen(cmd, shell=shell, stderr=subprocess.STDOUT)
173 def cmd_output(self, cmd):
174 '''return output from and command'''
175 cmd = self.substitute(cmd)
176 return self.run_cmd(cmd, output=True)
178 def cmd_contains(self, cmd, contains, nomatch=False, ordered=False, regex=False,
180 '''check that command output contains the listed strings'''
182 if isinstance(contains, str):
183 contains = [contains]
185 out = self.cmd_output(cmd)
187 for c in self.substitute(contains):
189 m = re.search(c, out)
197 start = out.upper().find(c.upper())
204 raise RuntimeError("Expected to not see %s in %s" % (c, cmd))
207 raise RuntimeError("Expected to see %s in %s" % (c, cmd))
208 if ordered and start != -1:
211 def retry_cmd(self, cmd, contains, retries=30, delay=2, wait_for_fail=False,
212 ordered=False, regex=False, casefold=False):
213 '''retry a command a number of times'''
216 self.cmd_contains(cmd, contains, nomatch=wait_for_fail,
217 ordered=ordered, regex=regex, casefold=casefold)
222 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
223 raise RuntimeError("Failed to find %s" % contains)
225 def pexpect_spawn(self, cmd, timeout=60, crlf=True, casefold=True):
226 '''wrapper around pexpect spawn'''
227 cmd = self.substitute(cmd)
228 self.info("$ " + cmd)
229 ret = pexpect.spawn(cmd, logfile=sys.stdout, timeout=timeout)
231 def sendline_sub(line):
232 line = self.substitute(line)
234 line = line.replace('\n', '\r\n') + '\r'
235 return ret.old_sendline(line)
237 def expect_sub(line, timeout=ret.timeout, casefold=casefold):
238 line = self.substitute(line)
240 if isinstance(line, list):
241 for i in range(len(line)):
242 if isinstance(line[i], str):
243 line[i] = '(?i)' + line[i]
244 elif isinstance(line, str):
246 return ret.old_expect(line, timeout=timeout)
248 ret.old_sendline = ret.sendline
249 ret.sendline = sendline_sub
250 ret.old_expect = ret.expect
251 ret.expect = expect_sub
255 def get_nameserver(self):
256 '''Get the current nameserver from /etc/resolv.conf'''
257 child = self.pexpect_spawn('cat /etc/resolv.conf', crlf=False)
258 i = child.expect(['Generated by wintest', 'nameserver'])
260 child.expect('your original resolv.conf')
261 child.expect('nameserver')
262 child.expect('\d+.\d+.\d+.\d+')
265 def vm_poweroff(self, vmname, checkfail=True):
267 self.setvar('VMNAME', vmname)
268 self.run_cmd("${VM_POWEROFF}", checkfail=checkfail)
270 def vm_reset(self, vmname):
272 self.setvar('VMNAME', vmname)
273 self.run_cmd("${VM_RESET}")
275 def vm_restore(self, vmname, snapshot):
277 self.setvar('VMNAME', vmname)
278 self.setvar('SNAPSHOT', snapshot)
279 self.run_cmd("${VM_RESTORE}")
281 def ping_wait(self, hostname):
282 '''wait for a hostname to come up on the network'''
283 hostname = self.substitute(hostname)
287 self.run_cmd("ping -c 1 -w 10 %s" % hostname)
292 raise RuntimeError("Failed to ping %s" % hostname)
293 self.info("Host %s is up" % hostname)
295 def port_wait(self, hostname, port, retries=200, delay=3, wait_for_fail=False):
296 '''wait for a host to come up on the network'''
297 self.retry_cmd("nc -v -z -w 1 %s %u" % (hostname, port), ['succeeded'],
298 retries=retries, delay=delay, wait_for_fail=wait_for_fail)
300 def run_net_time(self, child):
301 '''run net time on windows'''
302 child.sendline("net time \\\\${HOSTNAME} /set")
303 child.expect("Do you want to set the local computer")
305 child.expect("The command completed successfully")
307 def run_date_time(self, child, time_tuple=None):
308 '''run date and time on windows'''
309 if time_tuple is None:
310 time_tuple = time.localtime()
311 child.sendline("date")
312 child.expect("Enter the new date:")
313 i = child.expect(["dd-mm-yy", "mm-dd-yy"])
315 child.sendline(time.strftime("%d-%m-%y", time_tuple))
317 child.sendline(time.strftime("%m-%d-%y", time_tuple))
319 child.sendline("time")
320 child.expect("Enter the new time:")
321 child.sendline(time.strftime("%H:%M:%S", time_tuple))
324 def get_ipconfig(self, child):
325 '''get the IP configuration of the child'''
326 child.sendline("ipconfig /all")
327 child.expect('Ethernet adapter ')
328 child.expect("[\w\s]+")
329 self.setvar("WIN_NIC", child.after)
330 child.expect(['IPv4 Address', 'IP Address'])
331 child.expect('\d+.\d+.\d+.\d+')
332 self.setvar('WIN_IPV4_ADDRESS', child.after)
333 child.expect('Subnet Mask')
334 child.expect('\d+.\d+.\d+.\d+')
335 self.setvar('WIN_SUBNET_MASK', child.after)
336 child.expect('Default Gateway')
337 child.expect('\d+.\d+.\d+.\d+')
338 self.setvar('WIN_DEFAULT_GATEWAY', child.after)
341 def get_is_dc(self, child):
342 child.sendline("dcdiag")
343 i = child.expect(["is not a Directory Server", "Home Server = "])
346 child.expect('[\S]+')
347 hostname = child.after
348 if hostname.upper() == self.getvar("WIN_HOSTNAME").upper:
351 def run_tlntadmn(self, child):
352 '''remove the annoying telnet restrictions'''
353 child.sendline('tlntadmn config maxconn=1024')
354 child.expect("The settings were successfully updated")
357 def disable_firewall(self, child):
358 '''remove the annoying firewall'''
359 child.sendline('netsh advfirewall set allprofiles state off')
360 i = child.expect(["Ok", "The following command was not found: advfirewall set allprofiles state off"])
363 child.sendline('netsh firewall set opmode mode = DISABLE profile = ALL')
364 i = child.expect(["Ok", "The following command was not found"])
366 self.info("Firewall disable failed - ignoring")
369 def set_dns(self, child):
370 child.sendline('netsh interface ip set dns "${WIN_NIC}" static ${INTERFACE_IP} primary')
371 i = child.expect(['C:', pexpect.EOF, pexpect.TIMEOUT], timeout=5)
377 def set_ip(self, child):
378 """fix the IP address to the same value it had when we
379 connected, but don't use DHCP, and force the DNS server to our
380 DNS server. This allows DNS updates to run"""
381 self.get_ipconfig(child)
382 if self.getvar("WIN_IPV4_ADDRESS") != self.getvar("WIN_IP"):
383 raise RuntimeError("ipconfig address %s != nmblookup address %s" % (self.getvar("WIN_IPV4_ADDRESS"),
384 self.getvar("WIN_IP")))
385 child.sendline('netsh')
386 child.expect('netsh>')
387 child.sendline('offline')
388 child.expect('netsh>')
389 child.sendline('routing ip add persistentroute dest=0.0.0.0 mask=0.0.0.0 name="${WIN_NIC}" nhop=${WIN_DEFAULT_GATEWAY}')
390 child.expect('netsh>')
391 child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1 store=persistent')
392 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)
394 child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1')
395 child.expect('netsh>')
396 child.sendline('commit')
397 child.sendline('online')
398 child.sendline('exit')
400 child.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=5)
404 def resolve_ip(self, hostname, retries=60, delay=5):
405 '''resolve an IP given a hostname, assuming NBT'''
407 child = self.pexpect_spawn("bin/nmblookup %s" % hostname)
408 i = child.expect(['\d+.\d+.\d+.\d+', "Lookup failed"])
413 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
414 raise RuntimeError("Failed to resolve IP of %s" % hostname)
417 def open_telnet(self, hostname, username, password, retries=60, delay=5, set_time=False, set_ip=False,
418 disable_firewall=True, run_tlntadmn=True):
419 '''open a telnet connection to a windows server, return the pexpect child'''
422 if self.getvar('WIN_IP'):
423 ip = self.getvar('WIN_IP')
425 ip = self.resolve_ip(hostname)
426 self.setvar('WIN_IP', ip)
428 child = self.pexpect_spawn("telnet " + ip + " -l '" + username + "'")
429 i = child.expect(["Welcome to Microsoft Telnet Service",
430 "Denying new connections due to the limit on number of connections",
431 "No more connections are allowed to telnet server",
432 "Unable to connect to remote host",
434 "Connection refused",
440 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
442 child.expect("password:")
443 child.sendline(password)
444 i = child.expect(["C:",
445 "Denying new connections due to the limit on number of connections",
446 "No more connections are allowed to telnet server",
447 "Unable to connect to remote host",
449 "Connection refused",
455 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
459 if self.set_dns(child):
462 child.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
466 self.run_date_time(child, None)
469 self.run_tlntadmn(child)
472 self.disable_firewall(child)
473 disable_firewall = False
476 if self.set_ip(child):
481 raise RuntimeError("Failed to connect with telnet")
483 def kinit(self, username, password):
484 '''use kinit to setup a credentials cache'''
485 self.run_cmd("kdestroy")
486 self.putenv('KRB5CCNAME', "${PREFIX}/ccache.test")
487 username = self.substitute(username)
488 s = username.split('@')
491 username = '@'.join(s)
492 child = self.pexpect_spawn('kinit ' + username)
493 child.expect("Password")
494 child.sendline(password)
495 child.expect(pexpect.EOF)
497 if child.exitstatus != 0:
498 raise RuntimeError("kinit failed with status %d" % child.exitstatus)
500 def get_domains(self):
501 '''return a dictionary of DNS domains and IPs for named.conf'''
504 if v[-6:] == "_REALM":
506 if base + '_IP' in self.vars:
507 ret[self.vars[base + '_REALM']] = self.vars[base + '_IP']
510 def wait_reboot(self, retries=3):
511 '''wait for a VM to reboot'''
513 # first wait for it to shutdown
514 self.port_wait("${WIN_IP}", 139, wait_for_fail=True, delay=6)
516 # now wait for it to come back. If it fails to come back
517 # then try resetting it
520 self.port_wait("${WIN_IP}", 139)
524 self.vm_reset("${WIN_VM}")
525 self.info("retrying reboot (retries=%u)" % retries)
526 raise RuntimeError(self.substitute("VM ${WIN_VM} failed to reboot"))
529 '''return a dictionary of all the configured VM names'''
533 ret.append(self.vars[v])