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
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:
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
))
33 self
.vars.pop("%s_%s" % (prefix
,v
), None)
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'''
45 if len(line
) == 0 or line
[0] == '#':
47 colon
= line
.find(':')
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'''
58 def set_skip(self
, skiplist
):
59 '''set a list of tests to skip'''
60 self
.skiplist
= skiplist
.split(',')
63 '''return True if we should skip a step'''
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.
73 if isinstance(text
, list):
75 for i
in range(len(ret
)):
76 ret
[i
] = self
.substitute(ret
[i
])
79 """We may have objects such as pexpect.EOF that are not strings"""
80 if not isinstance(text
, str):
83 var_start
= text
.find("${")
86 var_end
= text
.find("}", var_start
)
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
])
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'''
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
))
119 def run_cmd(self
, cmd
, dir=".", show
=None, output
=False, checkfail
=True):
121 cmd
= self
.substitute(cmd
)
122 if isinstance(cmd
, list):
123 self
.info('$ ' + " ".join(cmd
))
125 self
.info('$ ' + cmd
)
127 return subprocess
.Popen([cmd
], shell
=True, stdout
=subprocess
.PIPE
, stderr
=subprocess
.STDOUT
, cwd
=dir).communicate()[0]
128 if isinstance(cmd
, list):
133 return subprocess
.check_call(cmd
, shell
=shell
, cwd
=dir)
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'''
141 cmd
= self
.substitute(cmd
)
142 if isinstance(cmd
, list):
143 self
.info('$ ' + " ".join(cmd
))
145 self
.info('$ ' + cmd
)
146 if isinstance(cmd
, list):
151 ret
= subprocess
.Popen(cmd
, shell
=shell
, stderr
=subprocess
.STDOUT
)
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,
162 '''check that command output contains the listed strings'''
164 if isinstance(contains
, str):
165 contains
= [contains
]
167 out
= self
.cmd_output(cmd
)
169 for c
in self
.substitute(contains
):
171 m
= re
.search(c
, out
)
179 start
= out
.upper().find(c
.upper())
186 raise RuntimeError("Expected to not see %s in %s" % (c
, cmd
))
189 raise RuntimeError("Expected to see %s in %s" % (c
, cmd
))
190 if ordered
and start
!= -1:
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'''
198 self
.cmd_contains(cmd
, contains
, nomatch
=wait_for_fail
,
199 ordered
=ordered
, regex
=regex
, casefold
=casefold
)
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
)
222 ret
.old_sendline
= ret
.sendline
223 ret
.sendline
= sendline_sub
224 ret
.old_expect
= ret
.expect
225 ret
.expect
= expect_sub
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'])
234 child
.expect('your original resolv.conf')
235 child
.expect('nameserver')
236 child
.expect('\d+.\d+.\d+.\d+')
239 def vm_poweroff(self
, vmname
, checkfail
=True):
241 self
.setvar('VMNAME', vmname
)
242 self
.run_cmd("${VM_POWEROFF}", checkfail
=checkfail
)
244 def vm_reset(self
, vmname
):
246 self
.setvar('VMNAME', vmname
)
247 self
.run_cmd("${VM_RESET}")
249 def vm_restore(self
, vmname
, snapshot
):
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
)
261 self
.run_cmd("ping -c 1 -w 10 %s" % hostname
)
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")
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"])
289 child
.sendline(time
.strftime("%d-%m-%y", time_tuple
))
291 child
.sendline(time
.strftime("%m-%d-%y", time_tuple
))
293 child
.sendline("time")
294 child
.expect("Enter the new time:")
295 child
.sendline(time
.strftime("%H:%M:%S", time_tuple
))
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
)
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")
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"])
327 child
.sendline('netsh firewall set opmode mode = DISABLE profile = ALL')
328 i
= child
.expect(["Ok", "The following command was not found"])
330 self
.info("Firewall disable failed - ignoring")
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)
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)
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)
368 def resolve_ip(self
, hostname
, retries
=60, delay
=5):
369 '''resolve an IP given a hostname, assuming NBT'''
371 child
= self
.pexpect_spawn("bin/nmblookup %s" % hostname
)
372 i
= child
.expect(['\d+.\d+.\d+.\d+', "Lookup failed"])
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'''
386 if self
.getvar('WIN_IP'):
387 ip
= self
.getvar('WIN_IP')
389 ip
= self
.resolve_ip(hostname
)
390 self
.setvar('WIN_IP', ip
)
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",
398 "Connection refused",
404 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
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",
413 "Connection refused",
419 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
423 if self
.set_dns(child
):
426 child
.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
430 self
.run_date_time(child
, None)
433 self
.run_tlntadmn(child
)
436 self
.disable_firewall(child
)
437 disable_firewall
= False
440 if self
.set_ip(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('@')
455 username
= '@'.join(s
)
456 child
= self
.pexpect_spawn('kinit ' + username
)
457 child
.expect("Password")
458 child
.sendline(password
)
459 child
.expect(pexpect
.EOF
)
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'''
468 if v
[-6:] == "_REALM":
470 if base
+ '_IP' in self
.vars:
471 ret
[self
.vars[base
+ '_REALM']] = self
.vars[base
+ '_IP']
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
484 self
.port_wait("${WIN_IP}", 139)
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"))