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', '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))
34 self.vars.pop("%s_%s" % (prefix,v), None)
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'''
46 if len(line) == 0 or line[0] == '#':
48 colon = line.find(':')
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'''
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(',')
68 '''return True if we should skip a step'''
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.
78 if isinstance(text, list):
80 for i in range(len(ret)):
81 ret[i] = self.substitute(ret[i])
84 """We may have objects such as pexpect.EOF that are not strings"""
85 if not isinstance(text, str):
88 var_start = text.find("${")
91 var_end = text.find("}", var_start)
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])
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'):
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'''
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))
131 def run_cmd(self, cmd, dir=".", show=None, output=False, checkfail=True):
133 cmd = self.substitute(cmd)
134 if isinstance(cmd, list):
135 self.info('$ ' + " ".join(cmd))
137 self.info('$ ' + cmd)
139 return subprocess.Popen([cmd], shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, cwd=dir).communicate()[0]
140 if isinstance(cmd, list):
145 return subprocess.check_call(cmd, shell=shell, cwd=dir)
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'''
153 cmd = self.substitute(cmd)
154 if isinstance(cmd, list):
155 self.info('$ ' + " ".join(cmd))
157 self.info('$ ' + cmd)
158 if isinstance(cmd, list):
163 ret = subprocess.Popen(cmd, shell=shell, stderr=subprocess.STDOUT)
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,
174 '''check that command output contains the listed strings'''
176 if isinstance(contains, str):
177 contains = [contains]
179 out = self.cmd_output(cmd)
181 for c in self.substitute(contains):
183 m = re.search(c, out)
191 start = out.upper().find(c.upper())
198 raise RuntimeError("Expected to not see %s in %s" % (c, cmd))
201 raise RuntimeError("Expected to see %s in %s" % (c, cmd))
202 if ordered and start != -1:
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'''
210 self.cmd_contains(cmd, contains, nomatch=wait_for_fail,
211 ordered=ordered, regex=regex, casefold=casefold)
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):
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).replace('\n', '\r\n')
227 return ret.old_sendline(line + '\r')
229 def expect_sub(line, timeout=ret.timeout):
230 line = self.substitute(line)
231 return ret.old_expect(line, timeout=timeout)
234 ret.old_sendline = ret.sendline
235 ret.sendline = sendline_sub
236 ret.old_expect = ret.expect
237 ret.expect = expect_sub
241 def get_nameserver(self):
242 '''Get the current nameserver from /etc/resolv.conf'''
243 child = self.pexpect_spawn('cat /etc/resolv.conf', crlf=False)
244 i = child.expect(['Generated by wintest', 'nameserver'])
246 child.expect('your original resolv.conf')
247 child.expect('nameserver')
248 child.expect('\d+.\d+.\d+.\d+')
251 def vm_poweroff(self, vmname, checkfail=True):
253 self.setvar('VMNAME', vmname)
254 self.run_cmd("${VM_POWEROFF}", checkfail=checkfail)
256 def vm_reset(self, vmname):
258 self.setvar('VMNAME', vmname)
259 self.run_cmd("${VM_RESET}")
261 def vm_restore(self, vmname, snapshot):
263 self.setvar('VMNAME', vmname)
264 self.setvar('SNAPSHOT', snapshot)
265 self.run_cmd("${VM_RESTORE}")
267 def ping_wait(self, hostname):
268 '''wait for a hostname to come up on the network'''
269 hostname = self.substitute(hostname)
273 self.run_cmd("ping -c 1 -w 10 %s" % hostname)
278 raise RuntimeError("Failed to ping %s" % hostname)
279 self.info("Host %s is up" % hostname)
281 def port_wait(self, hostname, port, retries=200, delay=3, wait_for_fail=False):
282 '''wait for a host to come up on the network'''
283 self.retry_cmd("nc -v -z -w 1 %s %u" % (hostname, port), ['succeeded'],
284 retries=retries, delay=delay, wait_for_fail=wait_for_fail)
286 def run_net_time(self, child):
287 '''run net time on windows'''
288 child.sendline("net time \\\\${HOSTNAME} /set")
289 child.expect("Do you want to set the local computer")
291 child.expect("The command completed successfully")
293 def run_date_time(self, child, time_tuple=None):
294 '''run date and time on windows'''
295 if time_tuple is None:
296 time_tuple = time.localtime()
297 child.sendline("date")
298 child.expect("Enter the new date:")
299 i = child.expect(["dd-mm-yy", "mm-dd-yy"])
301 child.sendline(time.strftime("%d-%m-%y", time_tuple))
303 child.sendline(time.strftime("%m-%d-%y", time_tuple))
305 child.sendline("time")
306 child.expect("Enter the new time:")
307 child.sendline(time.strftime("%H:%M:%S", time_tuple))
310 def get_ipconfig(self, child):
311 '''get the IP configuration of the child'''
312 child.sendline("ipconfig /all")
313 child.expect('Ethernet adapter ')
314 child.expect("[\w\s]+")
315 self.setvar("WIN_NIC", child.after)
316 child.expect(['IPv4 Address', 'IP Address'])
317 child.expect('\d+.\d+.\d+.\d+')
318 self.setvar('WIN_IPV4_ADDRESS', child.after)
319 child.expect('Subnet Mask')
320 child.expect('\d+.\d+.\d+.\d+')
321 self.setvar('WIN_SUBNET_MASK', child.after)
322 child.expect('Default Gateway')
323 child.expect('\d+.\d+.\d+.\d+')
324 self.setvar('WIN_DEFAULT_GATEWAY', child.after)
327 def run_tlntadmn(self, child):
328 '''remove the annoying telnet restrictions'''
329 child.sendline('tlntadmn config maxconn=1024')
330 child.expect("The settings were successfully updated")
333 def disable_firewall(self, child):
334 '''remove the annoying firewall'''
335 child.sendline('netsh advfirewall set allprofiles state off')
336 i = child.expect(["Ok", "The following command was not found: advfirewall set allprofiles state off"])
339 child.sendline('netsh firewall set opmode mode = DISABLE profile = ALL')
340 i = child.expect(["Ok", "The following command was not found"])
342 self.info("Firewall disable failed - ignoring")
345 def set_dns(self, child):
346 child.sendline('netsh interface ip set dns "${WIN_NIC}" static ${INTERFACE_IP} primary')
347 i = child.expect(['C:', pexpect.EOF, pexpect.TIMEOUT], timeout=5)
353 def set_ip(self, child):
354 """fix the IP address to the same value it had when we
355 connected, but don't use DHCP, and force the DNS server to our
356 DNS server. This allows DNS updates to run"""
357 self.get_ipconfig(child)
358 if self.getvar("WIN_IPV4_ADDRESS") != self.getvar("WIN_IP"):
359 raise RuntimeError("ipconfig address %s != nmblookup address %s" % (self.getvar("WIN_IPV4_ADDRESS"),
360 self.getvar("WIN_IP")))
361 child.sendline('netsh')
362 child.expect('netsh>')
363 child.sendline('offline')
364 child.expect('netsh>')
365 child.sendline('routing ip add persistentroute dest=0.0.0.0 mask=0.0.0.0 name="${WIN_NIC}" nhop=${WIN_DEFAULT_GATEWAY}')
366 child.expect('netsh>')
367 child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1 store=persistent')
368 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)
370 child.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1')
371 child.expect('netsh>')
372 child.sendline('commit')
373 child.sendline('online')
374 child.sendline('exit')
376 child.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=5)
380 def resolve_ip(self, hostname, retries=60, delay=5):
381 '''resolve an IP given a hostname, assuming NBT'''
383 child = self.pexpect_spawn("bin/nmblookup %s" % hostname)
384 i = child.expect(['\d+.\d+.\d+.\d+', "Lookup failed"])
389 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
390 raise RuntimeError("Failed to resolve IP of %s" % hostname)
393 def open_telnet(self, hostname, username, password, retries=60, delay=5, set_time=False, set_ip=False,
394 disable_firewall=True, run_tlntadmn=True):
395 '''open a telnet connection to a windows server, return the pexpect child'''
398 if self.getvar('WIN_IP'):
399 ip = self.getvar('WIN_IP')
401 ip = self.resolve_ip(hostname)
402 self.setvar('WIN_IP', ip)
404 child = self.pexpect_spawn("telnet " + ip + " -l '" + username + "'")
405 i = child.expect(["Welcome to Microsoft Telnet Service",
406 "Denying new connections due to the limit on number of connections",
407 "No more connections are allowed to telnet server",
408 "Unable to connect to remote host",
410 "Connection refused",
416 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
418 child.expect("password:")
419 child.sendline(password)
420 i = child.expect(["C:",
421 "Denying new connections due to the limit on number of connections",
422 "No more connections are allowed to telnet server",
423 "Unable to connect to remote host",
425 "Connection refused",
431 self.info("retrying (retries=%u delay=%u)" % (retries, delay))
435 if self.set_dns(child):
438 child.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
442 self.run_date_time(child, None)
445 self.run_tlntadmn(child)
448 self.disable_firewall(child)
449 disable_firewall = False
452 if self.set_ip(child):
457 raise RuntimeError("Failed to connect with telnet")
459 def kinit(self, username, password):
460 '''use kinit to setup a credentials cache'''
461 self.run_cmd("kdestroy")
462 self.putenv('KRB5CCNAME', "${PREFIX}/ccache.test")
463 username = self.substitute(username)
464 s = username.split('@')
467 username = '@'.join(s)
468 child = self.pexpect_spawn('kinit ' + username)
469 child.expect("Password")
470 child.sendline(password)
471 child.expect(pexpect.EOF)
473 if child.exitstatus != 0:
474 raise RuntimeError("kinit failed with status %d" % child.exitstatus)
476 def get_domains(self):
477 '''return a dictionary of DNS domains and IPs for named.conf'''
480 if v[-6:] == "_REALM":
482 if base + '_IP' in self.vars:
483 ret[self.vars[base + '_REALM']] = self.vars[base + '_IP']
486 def wait_reboot(self, retries=3):
487 '''wait for a VM to reboot'''
489 # first wait for it to shutdown
490 self.port_wait("${WIN_IP}", 139, wait_for_fail=True, delay=6)
492 # now wait for it to come back. If it fails to come back
493 # then try resetting it
496 self.port_wait("${WIN_IP}", 139)
500 self.vm_reset("${WIN_VM}")
501 self.info("retrying reboot (retries=%u)" % retries)
502 raise RuntimeError(self.substitute("VM ${WIN_VM} failed to reboot"))