3 '''automated testing library for testing Samba against windows'''
15 '''testing of Samba against windows VMs'''
19 self
.list_mode
= False
21 os
.environ
['PYTHONUNBUFFERED'] = '1'
22 self
.parser
= optparse
.OptionParser("wintest")
24 def check_prerequesites(self
):
25 self
.info("Checking prerequesites")
26 self
.setvar('HOSTNAME', self
.cmd_output("hostname -s").strip())
28 raise Exception("You must run this script as root")
29 self
.run_cmd('ifconfig ${INTERFACE} ${INTERFACE_NET} up')
30 if self
.getvar('INTERFACE_IPV6'):
31 self
.run_cmd('ifconfig ${INTERFACE} inet6 del ${INTERFACE_IPV6}/64', checkfail
=False)
32 self
.run_cmd('ifconfig ${INTERFACE} inet6 add ${INTERFACE_IPV6}/64 up')
34 self
.run_cmd('ifconfig ${NAMED_INTERFACE} ${NAMED_INTERFACE_NET} up')
35 if self
.getvar('NAMED_INTERFACE_IPV6'):
36 self
.run_cmd('ifconfig ${NAMED_INTERFACE} inet6 del ${NAMED_INTERFACE_IPV6}/64', checkfail
=False)
37 self
.run_cmd('ifconfig ${NAMED_INTERFACE} inet6 add ${NAMED_INTERFACE_IPV6}/64 up')
40 '''Shut down any existing alive VMs, so they do not collide with what we are doing'''
41 self
.info('Shutting down any of our VMs already running')
44 self
.vm_poweroff(v
, checkfail
=False)
46 def setvar(self
, varname
, value
):
47 '''set a substitution variable'''
48 self
.vars[varname
] = value
50 def getvar(self
, varname
):
51 '''return a substitution variable'''
52 if varname
not in self
.vars:
54 return self
.vars[varname
]
56 def setwinvars(self
, vm
, prefix
='WIN'):
57 '''setup WIN_XX vars based on a vm name'''
58 for v
in ['VM', 'HOSTNAME', 'USER', 'PASS', 'SNAPSHOT', 'REALM', 'DOMAIN', 'IP']:
59 vname
= '%s_%s' % (vm
, v
)
60 if vname
in self
.vars:
61 self
.setvar("%s_%s" % (prefix
, v
), self
.substitute("${%s}" % vname
))
63 self
.vars.pop("%s_%s" % (prefix
, v
), None)
65 if self
.getvar("WIN_REALM"):
66 self
.setvar("WIN_REALM", self
.getvar("WIN_REALM").upper())
67 self
.setvar("WIN_LCREALM", self
.getvar("WIN_REALM").lower())
68 dnsdomain
= self
.getvar("WIN_REALM")
69 self
.setvar("WIN_BASEDN", "DC=" + dnsdomain
.replace(".", ",DC="))
70 if self
.getvar("WIN_USER") is None:
71 self
.setvar("WIN_USER", "administrator")
74 '''print some information'''
75 if not self
.list_mode
:
76 print(self
.substitute(msg
))
78 def load_config(self
, fname
):
79 '''load the config file'''
83 if len(line
) == 0 or line
[0] == '#':
85 colon
= line
.find(':')
87 raise RuntimeError("Invalid config line '%s'" % line
)
88 varname
= line
[0:colon
].strip()
89 value
= line
[colon
+ 1:].strip()
90 self
.setvar(varname
, value
)
92 def list_steps_mode(self
):
93 '''put wintest in step listing mode'''
96 def set_skip(self
, skiplist
):
97 '''set a list of tests to skip'''
98 self
.skiplist
= skiplist
.split(',')
100 def set_vms(self
, vms
):
101 '''set a list of VMs to test'''
104 for vm
in vms
.split(','):
108 def skip(self
, step
):
109 '''return True if we should skip a step'''
113 return step
in self
.skiplist
115 def substitute(self
, text
):
116 """Substitute strings of the form ${NAME} in text, replacing
117 with substitutions from vars.
119 if isinstance(text
, list):
121 for i
in range(len(ret
)):
122 ret
[i
] = self
.substitute(ret
[i
])
125 """We may have objects such as pexpect.EOF that are not strings"""
126 if not isinstance(text
, str):
129 var_start
= text
.find("${")
132 var_end
= text
.find("}", var_start
)
135 var_name
= text
[var_start
+ 2:var_end
]
136 if var_name
not in self
.vars:
137 raise RuntimeError("Unknown substitution variable ${%s}" % var_name
)
138 text
= text
.replace("${%s}" % var_name
, self
.vars[var_name
])
140 def have_var(self
, varname
):
141 '''see if a variable has been set'''
142 return varname
in self
.vars
144 def have_vm(self
, vmname
):
145 '''see if a VM should be used'''
146 if not self
.have_var(vmname
+ '_VM'):
150 return vmname
in self
.vms
152 def putenv(self
, key
, value
):
153 '''putenv with substitution'''
154 os
.environ
[key
] = self
.substitute(value
)
156 def chdir(self
, dir):
157 '''chdir with substitution'''
158 os
.chdir(self
.substitute(dir))
160 def del_files(self
, dirs
):
161 '''delete all files in the given directory'''
163 self
.run_cmd("find %s -type f | xargs rm -f" % d
)
165 def write_file(self
, filename
, text
, mode
='w'):
166 '''write to a file'''
167 f
= open(self
.substitute(filename
), mode
=mode
)
168 f
.write(self
.substitute(text
))
171 def run_cmd(self
, cmd
, dir=".", show
=None, output
=False, checkfail
=True):
173 cmd
= self
.substitute(cmd
)
174 if isinstance(cmd
, list):
175 self
.info('$ ' + " ".join(cmd
))
177 self
.info('$ ' + cmd
)
179 return subprocess
.Popen([cmd
], shell
=True, stdout
=subprocess
.PIPE
, stderr
=subprocess
.STDOUT
, cwd
=dir).communicate()[0]
180 if isinstance(cmd
, list):
185 return subprocess
.check_call(cmd
, shell
=shell
, cwd
=dir)
187 return subprocess
.call(cmd
, shell
=shell
, cwd
=dir)
189 def run_child(self
, cmd
, dir="."):
190 '''create a child and return the Popen handle to it'''
192 cmd
= self
.substitute(cmd
)
193 if isinstance(cmd
, list):
194 self
.info('$ ' + " ".join(cmd
))
196 self
.info('$ ' + cmd
)
197 if isinstance(cmd
, list):
202 ret
= subprocess
.Popen(cmd
, shell
=shell
, stderr
=subprocess
.STDOUT
)
206 def cmd_output(self
, cmd
):
207 '''return output from and command'''
208 cmd
= self
.substitute(cmd
)
209 return self
.run_cmd(cmd
, output
=True)
211 def cmd_contains(self
, cmd
, contains
, nomatch
=False, ordered
=False, regex
=False,
213 '''check that command output contains the listed strings'''
215 if isinstance(contains
, str):
216 contains
= [contains
]
218 out
= self
.cmd_output(cmd
)
220 for c
in self
.substitute(contains
):
225 m
= re
.search(c
, out
)
233 start
= out
.upper().find(c
.upper())
240 raise RuntimeError("Expected to not see %s in %s" % (c
, cmd
))
243 raise RuntimeError("Expected to see %s in %s" % (c
, cmd
))
244 if ordered
and start
!= -1:
247 def retry_cmd(self
, cmd
, contains
, retries
=30, delay
=2, wait_for_fail
=False,
248 ordered
=False, regex
=False, casefold
=True):
249 '''retry a command a number of times'''
252 self
.cmd_contains(cmd
, contains
, nomatch
=wait_for_fail
,
253 ordered
=ordered
, regex
=regex
, casefold
=casefold
)
258 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
259 raise RuntimeError("Failed to find %s" % contains
)
261 def pexpect_spawn(self
, cmd
, timeout
=60, crlf
=True, casefold
=True):
262 '''wrapper around pexpect spawn'''
263 cmd
= self
.substitute(cmd
)
264 self
.info("$ " + cmd
)
265 ret
= pexpect
.spawn(cmd
, logfile
=sys
.stdout
, timeout
=timeout
)
267 def sendline_sub(line
):
268 line
= self
.substitute(line
)
270 line
= line
.replace('\n', '\r\n') + '\r'
271 return ret
.old_sendline(line
)
273 def expect_sub(line
, timeout
=ret
.timeout
, casefold
=casefold
):
274 line
= self
.substitute(line
)
276 if isinstance(line
, list):
277 for i
in range(len(line
)):
278 if isinstance(line
[i
], str):
279 line
[i
] = '(?i)' + line
[i
]
280 elif isinstance(line
, str):
282 return ret
.old_expect(line
, timeout
=timeout
)
284 ret
.old_sendline
= ret
.sendline
285 ret
.sendline
= sendline_sub
286 ret
.old_expect
= ret
.expect
287 ret
.expect
= expect_sub
291 def get_nameserver(self
):
292 '''Get the current nameserver from /etc/resolv.conf'''
293 child
= self
.pexpect_spawn('cat /etc/resolv.conf', crlf
=False)
294 i
= child
.expect(['Generated by wintest', 'nameserver'])
296 child
.expect('your original resolv.conf')
297 child
.expect('nameserver')
298 child
.expect('\d+.\d+.\d+.\d+')
301 def rndc_cmd(self
, cmd
, checkfail
=True):
302 '''run a rndc command'''
303 self
.run_cmd("${RNDC} -c ${PREFIX}/etc/rndc.conf %s" % cmd
, checkfail
=checkfail
)
305 def named_supports_gssapi_keytab(self
):
306 '''see if named supports tkey-gssapi-keytab'''
307 self
.write_file("${PREFIX}/named.conf.test",
308 'options { tkey-gssapi-keytab "test"; };')
310 self
.run_cmd("${NAMED_CHECKCONF} ${PREFIX}/named.conf.test")
311 except subprocess
.CalledProcessError
:
315 def set_nameserver(self
, nameserver
):
316 '''set the nameserver in resolv.conf'''
317 self
.write_file("/etc/resolv.conf.wintest", '''
318 # Generated by wintest, the Samba v Windows automated testing system
321 # your original resolv.conf appears below:
322 ''' % self
.substitute(nameserver
))
323 child
= self
.pexpect_spawn("cat /etc/resolv.conf", crlf
=False)
324 i
= child
.expect(['your original resolv.conf appears below:', pexpect
.EOF
])
326 child
.expect(pexpect
.EOF
)
327 contents
= child
.before
.lstrip().replace('\r', '')
328 self
.write_file('/etc/resolv.conf.wintest', contents
, mode
='a')
329 self
.write_file('/etc/resolv.conf.wintest-bak', contents
)
330 self
.run_cmd("mv -f /etc/resolv.conf.wintest /etc/resolv.conf")
331 self
.resolv_conf_backup
= '/etc/resolv.conf.wintest-bak'
333 def configure_bind(self
, kerberos_support
=False, include
=None):
334 self
.chdir('${PREFIX}')
336 if self
.getvar('NAMED_INTERFACE_IPV6'):
337 ipv6_listen
= 'listen-on-v6 port 53 { ${NAMED_INTERFACE_IPV6}; };'
340 self
.setvar('BIND_LISTEN_IPV6', ipv6_listen
)
342 if not kerberos_support
:
343 self
.setvar("NAMED_TKEY_OPTION", "")
344 elif self
.getvar('NAMESERVER_BACKEND') != 'SAMBA_INTERNAL':
345 if self
.named_supports_gssapi_keytab():
346 self
.setvar("NAMED_TKEY_OPTION",
347 'tkey-gssapi-keytab "${PREFIX}/bind-dns/dns.keytab";')
349 self
.info("LCREALM=${LCREALM}")
350 self
.setvar("NAMED_TKEY_OPTION",
351 '''tkey-gssapi-credential "DNS/${LCREALM}";
352 tkey-domain "${LCREALM}";
354 self
.putenv('KEYTAB_FILE', '${PREFIX}/bind-dns/dns.keytab')
355 self
.putenv('KRB5_KTNAME', '${PREFIX}/bind-dns/dns.keytab')
357 self
.setvar("NAMED_TKEY_OPTION", "")
359 if include
and self
.getvar('NAMESERVER_BACKEND') != 'SAMBA_INTERNAL':
360 self
.setvar("NAMED_INCLUDE", 'include "%s";' % include
)
362 self
.setvar("NAMED_INCLUDE", '')
364 self
.run_cmd("mkdir -p ${PREFIX}/etc")
366 self
.write_file("etc/named.conf", '''
368 listen-on port 53 { ${NAMED_INTERFACE_IP}; };
370 directory "${PREFIX}/var/named";
371 dump-file "${PREFIX}/var/named/data/cache_dump.db";
372 pid-file "${PREFIX}/var/named/named.pid";
373 statistics-file "${PREFIX}/var/named/data/named_stats.txt";
374 memstatistics-file "${PREFIX}/var/named/data/named_mem_stats.txt";
375 allow-query { any; };
390 secret "lA/cTrno03mt5Ju17ybEYw==";
394 inet ${NAMED_INTERFACE_IP} port 953
395 allow { any; } keys { "rndc-key"; };
401 if self
.getvar('NAMESERVER_BACKEND') == 'SAMBA_INTERNAL':
402 self
.write_file('etc/named.conf',
411 ''' % (self
.getvar('LCREALM'), self
.getvar('INTERFACE_IP')),
414 # add forwarding for the windows domains
415 domains
= self
.get_domains()
418 self
.write_file('etc/named.conf',
427 ''' % (d
, domains
[d
]),
430 self
.write_file("etc/rndc.conf", '''
434 secret "lA/cTrno03mt5Ju17ybEYw==";
438 default-key "rndc-key";
439 default-server ${NAMED_INTERFACE_IP};
445 '''Stop our private BIND from listening and operating'''
446 self
.rndc_cmd("stop", checkfail
=False)
447 self
.port_wait("${NAMED_INTERFACE_IP}", 53, wait_for_fail
=True)
449 self
.run_cmd("rm -rf var/named")
451 def start_bind(self
):
452 '''restart the test environment version of bind'''
453 self
.info("Restarting bind9")
454 self
.chdir('${PREFIX}')
456 self
.set_nameserver(self
.getvar('NAMED_INTERFACE_IP'))
458 self
.run_cmd("mkdir -p var/named/data")
459 self
.run_cmd("chown -R ${BIND_USER} var/named")
461 self
.bind_child
= self
.run_child("${BIND9} -u ${BIND_USER} -n 1 -c ${PREFIX}/etc/named.conf -g")
463 self
.port_wait("${NAMED_INTERFACE_IP}", 53)
464 self
.rndc_cmd("flush")
466 def restart_bind(self
, kerberos_support
=False, include
=None):
467 self
.configure_bind(kerberos_support
=kerberos_support
, include
=include
)
471 def restore_resolv_conf(self
):
472 '''restore the /etc/resolv.conf after testing is complete'''
473 if getattr(self
, 'resolv_conf_backup', False):
474 self
.info("restoring /etc/resolv.conf")
475 self
.run_cmd("mv -f %s /etc/resolv.conf" % self
.resolv_conf_backup
)
477 def vm_poweroff(self
, vmname
, checkfail
=True):
479 self
.setvar('VMNAME', vmname
)
480 self
.run_cmd("${VM_POWEROFF}", checkfail
=checkfail
)
482 def vm_reset(self
, vmname
):
484 self
.setvar('VMNAME', vmname
)
485 self
.run_cmd("${VM_RESET}")
487 def vm_restore(self
, vmname
, snapshot
):
489 self
.setvar('VMNAME', vmname
)
490 self
.setvar('SNAPSHOT', snapshot
)
491 self
.run_cmd("${VM_RESTORE}")
493 def ping_wait(self
, hostname
):
494 '''wait for a hostname to come up on the network'''
495 hostname
= self
.substitute(hostname
)
499 self
.run_cmd("ping -c 1 -w 10 %s" % hostname
)
504 raise RuntimeError("Failed to ping %s" % hostname
)
505 self
.info("Host %s is up" % hostname
)
507 def port_wait(self
, hostname
, port
, retries
=200, delay
=3, wait_for_fail
=False):
508 '''wait for a host to come up on the network'''
511 child
= self
.pexpect_spawn("nc -v -z -w 1 %s %u" % (hostname
, port
), crlf
=False, timeout
=1)
512 child
.expect([pexpect
.EOF
, pexpect
.TIMEOUT
])
516 # wait for timeout or fail
517 if i
is None or i
> 0:
525 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
527 raise RuntimeError("gave up waiting for %s:%d" % (hostname
, port
))
529 def run_net_time(self
, child
):
530 '''run net time on windows'''
531 child
.sendline("net time \\\\${HOSTNAME} /set")
532 child
.expect("Do you want to set the local computer")
534 child
.expect("The command completed successfully")
536 def run_date_time(self
, child
, time_tuple
=None):
537 '''run date and time on windows'''
538 if time_tuple
is None:
539 time_tuple
= time
.localtime()
540 child
.sendline("date")
541 child
.expect("Enter the new date:")
542 i
= child
.expect(["dd-mm-yy", "mm-dd-yy"])
544 child
.sendline(time
.strftime("%d-%m-%y", time_tuple
))
546 child
.sendline(time
.strftime("%m-%d-%y", time_tuple
))
548 child
.sendline("time")
549 child
.expect("Enter the new time:")
550 child
.sendline(time
.strftime("%H:%M:%S", time_tuple
))
553 def get_ipconfig(self
, child
):
554 '''get the IP configuration of the child'''
555 child
.sendline("ipconfig /all")
556 child
.expect('Ethernet adapter ')
557 child
.expect("[\w\s]+")
558 self
.setvar("WIN_NIC", child
.after
)
559 child
.expect(['IPv4 Address', 'IP Address'])
560 child
.expect('\d+.\d+.\d+.\d+')
561 self
.setvar('WIN_IPV4_ADDRESS', child
.after
)
562 child
.expect('Subnet Mask')
563 child
.expect('\d+.\d+.\d+.\d+')
564 self
.setvar('WIN_SUBNET_MASK', child
.after
)
565 child
.expect('Default Gateway')
566 i
= child
.expect(['\d+.\d+.\d+.\d+', "C:"])
568 self
.setvar('WIN_DEFAULT_GATEWAY', child
.after
)
571 def get_is_dc(self
, child
):
572 '''check if a windows machine is a domain controller'''
573 child
.sendline("dcdiag")
574 i
= child
.expect(["is not a [Directory Server|DC]",
575 "is not recognized as an internal or external command",
577 "passed test Replications"])
582 child
.sendline("net config Workstation")
583 child
.expect("Workstation domain")
584 child
.expect('[\S]+')
586 i
= child
.expect(["Workstation Domain DNS Name", "Logon domain"])
587 '''If we get the Logon domain first, we are not in an AD domain'''
590 if domain
.upper() == self
.getvar("WIN_DOMAIN").upper():
593 child
.expect('[\S]+')
594 hostname
= child
.after
595 if hostname
.upper() == self
.getvar("WIN_HOSTNAME").upper():
598 def set_noexpire(self
, child
, username
):
599 """Ensure this user's password does not expire"""
600 child
.sendline('wmic useraccount where name="%s" set PasswordExpires=FALSE' % username
)
601 child
.expect("update successful")
604 def run_tlntadmn(self
, child
):
605 '''remove the annoying telnet restrictions'''
606 child
.sendline('tlntadmn config maxconn=1024')
607 child
.expect(["The settings were successfully updated", "Access is denied"])
610 def disable_firewall(self
, child
):
611 '''remove the annoying firewall'''
612 child
.sendline('netsh advfirewall set allprofiles state off')
613 i
= child
.expect(["Ok", "The following command was not found: advfirewall set allprofiles state off", "The requested operation requires elevation", "Access is denied"])
616 child
.sendline('netsh firewall set opmode mode = DISABLE profile = ALL')
617 i
= child
.expect(["Ok", "The following command was not found", "Access is denied"])
619 self
.info("Firewall disable failed - ignoring")
622 def set_dns(self
, child
):
623 child
.sendline('netsh interface ip set dns "${WIN_NIC}" static ${NAMED_INTERFACE_IP} primary')
624 i
= child
.expect(['C:', pexpect
.EOF
, pexpect
.TIMEOUT
], timeout
=5)
630 def set_ip(self
, child
):
631 """fix the IP address to the same value it had when we
632 connected, but don't use DHCP, and force the DNS server to our
633 DNS server. This allows DNS updates to run"""
634 self
.get_ipconfig(child
)
635 if self
.getvar("WIN_IPV4_ADDRESS") != self
.getvar("WIN_IP"):
636 raise RuntimeError("ipconfig address %s != nmblookup address %s" % (self
.getvar("WIN_IPV4_ADDRESS"),
637 self
.getvar("WIN_IP")))
638 child
.sendline('netsh')
639 child
.expect('netsh>')
640 child
.sendline('offline')
641 child
.expect('netsh>')
642 child
.sendline('routing ip add persistentroute dest=0.0.0.0 mask=0.0.0.0 name="${WIN_NIC}" nhop=${WIN_DEFAULT_GATEWAY}')
643 child
.expect('netsh>')
644 child
.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1 store=persistent')
645 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)
647 child
.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1')
648 child
.expect('netsh>')
649 child
.sendline('commit')
650 child
.sendline('online')
651 child
.sendline('exit')
653 child
.expect([pexpect
.EOF
, pexpect
.TIMEOUT
], timeout
=5)
656 def resolve_ip(self
, hostname
, retries
=60, delay
=5):
657 '''resolve an IP given a hostname, assuming NBT'''
659 child
= self
.pexpect_spawn("bin/nmblookup %s" % hostname
)
662 i
= child
.expect(["querying", '\d+.\d+.\d+.\d+', hostname
, "Lookup failed"])
669 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
670 raise RuntimeError("Failed to resolve IP of %s" % hostname
)
672 def open_telnet(self
, hostname
, username
, password
, retries
=60, delay
=5, set_time
=False, set_ip
=False,
673 disable_firewall
=True, run_tlntadmn
=True, set_noexpire
=False):
674 '''open a telnet connection to a windows server, return the pexpect child'''
677 set_telnetclients
= True
679 if self
.getvar('WIN_IP'):
680 ip
= self
.getvar('WIN_IP')
682 ip
= self
.resolve_ip(hostname
)
683 self
.setvar('WIN_IP', ip
)
685 child
= self
.pexpect_spawn("telnet " + ip
+ " -l '" + username
+ "'")
686 i
= child
.expect(["Welcome to Microsoft Telnet Service",
687 "Denying new connections due to the limit on number of connections",
688 "No more connections are allowed to telnet server",
689 "Unable to connect to remote host",
691 "Connection refused",
697 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
699 child
.expect("password:")
700 child
.sendline(password
)
701 i
= child
.expect(["C:",
703 "Denying new connections due to the limit on number of connections",
704 "No more connections are allowed to telnet server",
705 "Unable to connect to remote host",
707 "Connection refused",
710 if set_telnetclients
:
711 self
.run_cmd('bin/net rpc group add TelnetClients -S $WIN_IP -U$WIN_USER%$WIN_PASS')
712 self
.run_cmd('bin/net rpc group addmem TelnetClients "authenticated users" -S $WIN_IP -U$WIN_USER%$WIN_PASS')
715 set_telnetclients
= False
716 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
719 raise RuntimeError("Failed to connect with telnet due to missing TelnetClients membership")
722 # This only works if it is installed and enabled, but not started. Not entirely likely, but possible
723 self
.run_cmd('bin/net rpc service start TlntSvr -S $WIN_IP -U$WIN_USER%$WIN_PASS')
727 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
734 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
738 if self
.set_dns(child
):
741 child
.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
745 self
.run_date_time(child
, None)
748 self
.run_tlntadmn(child
)
751 self
.set_noexpire(child
, username
)
754 self
.disable_firewall(child
)
755 disable_firewall
= False
758 if self
.set_ip(child
):
763 raise RuntimeError("Failed to connect with telnet")
765 def kinit(self
, username
, password
):
766 '''use kinit to setup a credentials cache'''
767 self
.run_cmd("kdestroy")
768 self
.putenv('KRB5CCNAME', "${PREFIX}/ccache.test")
769 username
= self
.substitute(username
)
770 s
= username
.split('@')
773 username
= '@'.join(s
)
774 child
= self
.pexpect_spawn('kinit ' + username
)
775 child
.expect("Password")
776 child
.sendline(password
)
777 child
.expect(pexpect
.EOF
)
779 if child
.exitstatus
!= 0:
780 raise RuntimeError("kinit failed with status %d" % child
.exitstatus
)
782 def get_domains(self
):
783 '''return a dictionary of DNS domains and IPs for named.conf'''
786 if v
[-6:] == "_REALM":
788 if base
+ '_IP' in self
.vars:
789 ret
[self
.vars[base
+ '_REALM']] = self
.vars[base
+ '_IP']
792 def wait_reboot(self
, retries
=3):
793 '''wait for a VM to reboot'''
795 # first wait for it to shutdown
796 self
.port_wait("${WIN_IP}", 139, wait_for_fail
=True, delay
=6)
798 # now wait for it to come back. If it fails to come back
799 # then try resetting it
802 self
.port_wait("${WIN_IP}", 139)
806 self
.vm_reset("${WIN_VM}")
807 self
.info("retrying reboot (retries=%u)" % retries
)
808 raise RuntimeError(self
.substitute("VM ${WIN_VM} failed to reboot"))
811 '''return a dictionary of all the configured VM names'''
815 ret
.append(self
.vars[v
])
818 def run_dcpromo_as_first_dc(self
, vm
, func_level
=None):
820 self
.info("Configuring a windows VM ${WIN_VM} at the first DC in the domain using dcpromo")
821 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_time
=True)
822 if self
.get_is_dc(child
):
825 if func_level
== '2008r2':
826 self
.setvar("FUNCTION_LEVEL_INT", str(4))
827 elif func_level
== '2003':
828 self
.setvar("FUNCTION_LEVEL_INT", str(1))
830 self
.setvar("FUNCTION_LEVEL_INT", str(0))
832 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_ip
=True, set_noexpire
=True)
834 """This server must therefore not yet be a directory server, so we must promote it"""
835 child
.sendline("copy /Y con answers.txt")
838 ; New forest promotion
839 ReplicaOrNewDomain=Domain
841 NewDomainDNSName=${WIN_REALM}
842 ForestLevel=${FUNCTION_LEVEL_INT}
843 DomainNetbiosName=${WIN_DOMAIN}
844 DomainLevel=${FUNCTION_LEVEL_INT}
847 CreateDNSDelegation=No
848 DatabasePath="C:\Windows\NTDS"
849 LogPath="C:\Windows\NTDS"
850 SYSVOLPath="C:\Windows\SYSVOL"
851 ; Set SafeModeAdminPassword to the correct value prior to using the unattend file
852 SafeModeAdminPassword=${WIN_PASS}
853 ; Run-time flags (optional)
854 RebootOnCompletion=No
857 child
.expect("copied.")
860 child
.sendline("dcpromo /answer:answers.txt")
861 i
= child
.expect(["You must restart this computer", "failed", "Active Directory Domain Services was not installed", "C:", pexpect
.TIMEOUT
], timeout
=240)
863 raise Exception("dcpromo failed")
865 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}")
867 child
.sendline("shutdown -r -t 0")
868 self
.port_wait("${WIN_IP}", 139, wait_for_fail
=True)
869 self
.port_wait("${WIN_IP}", 139)
871 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}")
872 # Check if we became a DC by now
873 if not self
.get_is_dc(child
):
874 raise Exception("dcpromo failed (and wasn't a DC even after rebooting)")
875 # Give DNS registration a kick
876 child
.sendline("ipconfig /registerdns")
878 self
.retry_cmd("host -t SRV _ldap._tcp.${WIN_REALM} ${WIN_IP}", ['has SRV record'], retries
=60, delay
=5)
880 def start_winvm(self
, vm
):
881 '''start a Windows VM'''
884 self
.info("Joining a windows box to the domain")
885 self
.vm_poweroff("${WIN_VM}", checkfail
=False)
886 self
.vm_restore("${WIN_VM}", "${WIN_SNAPSHOT}")
888 def run_winjoin(self
, vm
, domain
, username
="administrator", password
="${PASSWORD1}"):
889 '''join a windows box to a domain'''
890 child
= self
.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time
=True, set_ip
=True, set_noexpire
=True)
893 child
.sendline("ipconfig /flushdns")
895 child
.sendline("netdom join ${WIN_HOSTNAME} /Domain:%s /UserD:%s /PasswordD:%s" % (domain
, username
, password
))
896 i
= child
.expect(["The command completed successfully",
897 "The specified domain either does not exist or could not be contacted."], timeout
=120)
904 child
.sendline("shutdown /r -t 0")
906 child
= self
.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time
=True, set_ip
=True)
907 child
.sendline("ipconfig /registerdns")
908 child
.expect("Registration of the DNS resource records for all adapters of this computer has been initiated. Any errors will be reported in the Event Viewer")
911 def test_remote_smbclient(self
, vm
, username
="${WIN_USER}", password
="${WIN_PASS}", args
=""):
912 '''test smbclient against remote server'''
914 self
.info('Testing smbclient')
915 self
.chdir('${PREFIX}')
916 smbclient
= self
.getvar("smbclient")
917 self
.cmd_contains("%s --version" % (smbclient
), ["${SAMBA_VERSION}"])
918 self
.retry_cmd('%s -L ${WIN_HOSTNAME} -U%s%%%s %s' % (smbclient
, username
, password
, args
), ["IPC"], retries
=60, delay
=5)
920 def test_net_use(self
, vm
, realm
, domain
, username
, password
):
922 self
.info('Testing net use against Samba3 member')
923 child
= self
.open_telnet("${WIN_HOSTNAME}", "%s\\%s" % (domain
, username
), password
)
924 child
.sendline("net use t: \\\\${HOSTNAME}.%s\\test" % realm
)
925 child
.expect("The command completed successfully")
927 def setup(self
, testname
, subdir
):
928 '''setup for main tests, parsing command line'''
929 self
.parser
.add_option("--conf", type='string', default
='', help='config file')
930 self
.parser
.add_option("--skip", type='string', default
='', help='list of steps to skip (comma separated)')
931 self
.parser
.add_option("--vms", type='string', default
=None, help='list of VMs to use (comma separated)')
932 self
.parser
.add_option("--list", action
='store_true', default
=False, help='list the available steps')
933 self
.parser
.add_option("--rebase", action
='store_true', default
=False, help='do a git pull --rebase')
934 self
.parser
.add_option("--clean", action
='store_true', default
=False, help='clean the tree')
935 self
.parser
.add_option("--prefix", type='string', default
=None, help='override install prefix')
936 self
.parser
.add_option("--sourcetree", type='string', default
=None, help='override sourcetree location')
937 self
.parser
.add_option("--nocleanup", action
='store_true', default
=False, help='disable cleanup code')
938 self
.parser
.add_option("--use-ntvfs", action
='store_true', default
=False, help='use NTVFS for the fileserver')
939 self
.parser
.add_option("--dns-backend", type="choice",
940 choices
=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
941 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
942 "BIND9_FLATFILE uses bind9 text database to store zone information, "
943 "BIND9_DLZ uses samba4 AD to store zone information, "
944 "NONE skips the DNS setup entirely (not recommended)",
945 default
="SAMBA_INTERNAL")
947 self
.opts
, self
.args
= self
.parser
.parse_args()
949 if not self
.opts
.conf
:
950 print("Please specify a config file with --conf")
953 # we don't need fsync safety in these tests
954 self
.putenv('TDB_NO_FSYNC', '1')
956 self
.load_config(self
.opts
.conf
)
958 nameserver
= self
.get_nameserver()
959 if nameserver
== self
.getvar('NAMED_INTERFACE_IP'):
960 raise RuntimeError("old /etc/resolv.conf must not contain %s as a nameserver, this will create loops with the generated dns configuration" % nameserver
)
961 self
.setvar('DNSSERVER', nameserver
)
963 self
.set_skip(self
.opts
.skip
)
964 self
.set_vms(self
.opts
.vms
)
967 self
.list_steps_mode()
970 self
.setvar('PREFIX', self
.opts
.prefix
)
972 if self
.opts
.sourcetree
:
973 self
.setvar('SOURCETREE', self
.opts
.sourcetree
)
976 self
.info('rebasing')
977 self
.chdir('${SOURCETREE}')
978 self
.run_cmd('git pull --rebase')
981 self
.info('cleaning')
982 self
.chdir('${SOURCETREE}/' + subdir
)
983 self
.run_cmd('make clean')
985 if self
.opts
.use_ntvfs
:
986 self
.setvar('USE_NTVFS', "--use-ntvfs")
988 self
.setvar('USE_NTVFS', "")
990 self
.setvar('NAMESERVER_BACKEND', self
.opts
.dns_backend
)
992 self
.setvar('DNS_FORWARDER', "--option=dns forwarder=%s" % nameserver
)