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
])
141 def have_var(self
, varname
):
142 '''see if a variable has been set'''
143 return varname
in self
.vars
145 def have_vm(self
, vmname
):
146 '''see if a VM should be used'''
147 if not self
.have_var(vmname
+ '_VM'):
151 return vmname
in self
.vms
153 def putenv(self
, key
, value
):
154 '''putenv with substitution'''
155 os
.environ
[key
] = self
.substitute(value
)
157 def chdir(self
, dir):
158 '''chdir with substitution'''
159 os
.chdir(self
.substitute(dir))
161 def del_files(self
, dirs
):
162 '''delete all files in the given directory'''
164 self
.run_cmd("find %s -type f | xargs rm -f" % d
)
166 def write_file(self
, filename
, text
, mode
='w'):
167 '''write to a file'''
168 f
= open(self
.substitute(filename
), mode
=mode
)
169 f
.write(self
.substitute(text
))
172 def run_cmd(self
, cmd
, dir=".", show
=None, output
=False, checkfail
=True):
174 cmd
= self
.substitute(cmd
)
175 if isinstance(cmd
, list):
176 self
.info('$ ' + " ".join(cmd
))
178 self
.info('$ ' + cmd
)
180 return subprocess
.Popen([cmd
], shell
=True, stdout
=subprocess
.PIPE
, stderr
=subprocess
.STDOUT
, cwd
=dir).communicate()[0]
181 if isinstance(cmd
, list):
186 return subprocess
.check_call(cmd
, shell
=shell
, cwd
=dir)
188 return subprocess
.call(cmd
, shell
=shell
, cwd
=dir)
190 def run_child(self
, cmd
, dir="."):
191 '''create a child and return the Popen handle to it'''
193 cmd
= self
.substitute(cmd
)
194 if isinstance(cmd
, list):
195 self
.info('$ ' + " ".join(cmd
))
197 self
.info('$ ' + cmd
)
198 if isinstance(cmd
, list):
203 ret
= subprocess
.Popen(cmd
, shell
=shell
, stderr
=subprocess
.STDOUT
)
207 def cmd_output(self
, cmd
):
208 '''return output from and command'''
209 cmd
= self
.substitute(cmd
)
210 return self
.run_cmd(cmd
, output
=True)
212 def cmd_contains(self
, cmd
, contains
, nomatch
=False, ordered
=False, regex
=False,
214 '''check that command output contains the listed strings'''
216 if isinstance(contains
, str):
217 contains
= [contains
]
219 out
= self
.cmd_output(cmd
)
221 for c
in self
.substitute(contains
):
226 m
= re
.search(c
, out
)
234 start
= out
.upper().find(c
.upper())
241 raise RuntimeError("Expected to not see %s in %s" % (c
, cmd
))
244 raise RuntimeError("Expected to see %s in %s" % (c
, cmd
))
245 if ordered
and start
!= -1:
248 def retry_cmd(self
, cmd
, contains
, retries
=30, delay
=2, wait_for_fail
=False,
249 ordered
=False, regex
=False, casefold
=True):
250 '''retry a command a number of times'''
253 self
.cmd_contains(cmd
, contains
, nomatch
=wait_for_fail
,
254 ordered
=ordered
, regex
=regex
, casefold
=casefold
)
259 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
260 raise RuntimeError("Failed to find %s" % contains
)
262 def pexpect_spawn(self
, cmd
, timeout
=60, crlf
=True, casefold
=True):
263 '''wrapper around pexpect spawn'''
264 cmd
= self
.substitute(cmd
)
265 self
.info("$ " + cmd
)
266 ret
= pexpect
.spawn(cmd
, logfile
=sys
.stdout
, timeout
=timeout
)
268 def sendline_sub(line
):
269 line
= self
.substitute(line
)
271 line
= line
.replace('\n', '\r\n') + '\r'
272 return ret
.old_sendline(line
)
274 def expect_sub(line
, timeout
=ret
.timeout
, casefold
=casefold
):
275 line
= self
.substitute(line
)
277 if isinstance(line
, list):
278 for i
in range(len(line
)):
279 if isinstance(line
[i
], str):
280 line
[i
] = '(?i)' + line
[i
]
281 elif isinstance(line
, str):
283 return ret
.old_expect(line
, timeout
=timeout
)
285 ret
.old_sendline
= ret
.sendline
286 ret
.sendline
= sendline_sub
287 ret
.old_expect
= ret
.expect
288 ret
.expect
= expect_sub
292 def get_nameserver(self
):
293 '''Get the current nameserver from /etc/resolv.conf'''
294 child
= self
.pexpect_spawn('cat /etc/resolv.conf', crlf
=False)
295 i
= child
.expect(['Generated by wintest', 'nameserver'])
297 child
.expect('your original resolv.conf')
298 child
.expect('nameserver')
299 child
.expect('\d+.\d+.\d+.\d+')
302 def rndc_cmd(self
, cmd
, checkfail
=True):
303 '''run a rndc command'''
304 self
.run_cmd("${RNDC} -c ${PREFIX}/etc/rndc.conf %s" % cmd
, checkfail
=checkfail
)
306 def named_supports_gssapi_keytab(self
):
307 '''see if named supports tkey-gssapi-keytab'''
308 self
.write_file("${PREFIX}/named.conf.test",
309 'options { tkey-gssapi-keytab "test"; };')
311 self
.run_cmd("${NAMED_CHECKCONF} ${PREFIX}/named.conf.test")
312 except subprocess
.CalledProcessError
:
316 def set_nameserver(self
, nameserver
):
317 '''set the nameserver in resolv.conf'''
318 self
.write_file("/etc/resolv.conf.wintest", '''
319 # Generated by wintest, the Samba v Windows automated testing system
322 # your original resolv.conf appears below:
323 ''' % self
.substitute(nameserver
))
324 child
= self
.pexpect_spawn("cat /etc/resolv.conf", crlf
=False)
325 i
= child
.expect(['your original resolv.conf appears below:', pexpect
.EOF
])
327 child
.expect(pexpect
.EOF
)
328 contents
= child
.before
.lstrip().replace('\r', '')
329 self
.write_file('/etc/resolv.conf.wintest', contents
, mode
='a')
330 self
.write_file('/etc/resolv.conf.wintest-bak', contents
)
331 self
.run_cmd("mv -f /etc/resolv.conf.wintest /etc/resolv.conf")
332 self
.resolv_conf_backup
= '/etc/resolv.conf.wintest-bak'
334 def configure_bind(self
, kerberos_support
=False, include
=None):
335 self
.chdir('${PREFIX}')
337 if self
.getvar('NAMED_INTERFACE_IPV6'):
338 ipv6_listen
= 'listen-on-v6 port 53 { ${NAMED_INTERFACE_IPV6}; };'
341 self
.setvar('BIND_LISTEN_IPV6', ipv6_listen
)
343 if not kerberos_support
:
344 self
.setvar("NAMED_TKEY_OPTION", "")
345 elif self
.getvar('NAMESERVER_BACKEND') != 'SAMBA_INTERNAL':
346 if self
.named_supports_gssapi_keytab():
347 self
.setvar("NAMED_TKEY_OPTION",
348 'tkey-gssapi-keytab "${PREFIX}/bind-dns/dns.keytab";')
350 self
.info("LCREALM=${LCREALM}")
351 self
.setvar("NAMED_TKEY_OPTION",
352 '''tkey-gssapi-credential "DNS/${LCREALM}";
353 tkey-domain "${LCREALM}";
355 self
.putenv('KEYTAB_FILE', '${PREFIX}/bind-dns/dns.keytab')
356 self
.putenv('KRB5_KTNAME', '${PREFIX}/bind-dns/dns.keytab')
358 self
.setvar("NAMED_TKEY_OPTION", "")
360 if include
and self
.getvar('NAMESERVER_BACKEND') != 'SAMBA_INTERNAL':
361 self
.setvar("NAMED_INCLUDE", 'include "%s";' % include
)
363 self
.setvar("NAMED_INCLUDE", '')
365 self
.run_cmd("mkdir -p ${PREFIX}/etc")
367 self
.write_file("etc/named.conf", '''
369 listen-on port 53 { ${NAMED_INTERFACE_IP}; };
371 directory "${PREFIX}/var/named";
372 dump-file "${PREFIX}/var/named/data/cache_dump.db";
373 pid-file "${PREFIX}/var/named/named.pid";
374 statistics-file "${PREFIX}/var/named/data/named_stats.txt";
375 memstatistics-file "${PREFIX}/var/named/data/named_mem_stats.txt";
376 allow-query { any; };
391 secret "lA/cTrno03mt5Ju17ybEYw==";
395 inet ${NAMED_INTERFACE_IP} port 953
396 allow { any; } keys { "rndc-key"; };
402 if self
.getvar('NAMESERVER_BACKEND') == 'SAMBA_INTERNAL':
403 self
.write_file('etc/named.conf',
412 ''' % (self
.getvar('LCREALM'), self
.getvar('INTERFACE_IP')),
415 # add forwarding for the windows domains
416 domains
= self
.get_domains()
419 self
.write_file('etc/named.conf',
428 ''' % (d
, domains
[d
]),
431 self
.write_file("etc/rndc.conf", '''
435 secret "lA/cTrno03mt5Ju17ybEYw==";
439 default-key "rndc-key";
440 default-server ${NAMED_INTERFACE_IP};
446 '''Stop our private BIND from listening and operating'''
447 self
.rndc_cmd("stop", checkfail
=False)
448 self
.port_wait("${NAMED_INTERFACE_IP}", 53, wait_for_fail
=True)
450 self
.run_cmd("rm -rf var/named")
452 def start_bind(self
):
453 '''restart the test environment version of bind'''
454 self
.info("Restarting bind9")
455 self
.chdir('${PREFIX}')
457 self
.set_nameserver(self
.getvar('NAMED_INTERFACE_IP'))
459 self
.run_cmd("mkdir -p var/named/data")
460 self
.run_cmd("chown -R ${BIND_USER} var/named")
462 self
.bind_child
= self
.run_child("${BIND9} -u ${BIND_USER} -n 1 -c ${PREFIX}/etc/named.conf -g")
464 self
.port_wait("${NAMED_INTERFACE_IP}", 53)
465 self
.rndc_cmd("flush")
467 def restart_bind(self
, kerberos_support
=False, include
=None):
468 self
.configure_bind(kerberos_support
=kerberos_support
, include
=include
)
472 def restore_resolv_conf(self
):
473 '''restore the /etc/resolv.conf after testing is complete'''
474 if getattr(self
, 'resolv_conf_backup', False):
475 self
.info("restoring /etc/resolv.conf")
476 self
.run_cmd("mv -f %s /etc/resolv.conf" % self
.resolv_conf_backup
)
478 def vm_poweroff(self
, vmname
, checkfail
=True):
480 self
.setvar('VMNAME', vmname
)
481 self
.run_cmd("${VM_POWEROFF}", checkfail
=checkfail
)
483 def vm_reset(self
, vmname
):
485 self
.setvar('VMNAME', vmname
)
486 self
.run_cmd("${VM_RESET}")
488 def vm_restore(self
, vmname
, snapshot
):
490 self
.setvar('VMNAME', vmname
)
491 self
.setvar('SNAPSHOT', snapshot
)
492 self
.run_cmd("${VM_RESTORE}")
494 def ping_wait(self
, hostname
):
495 '''wait for a hostname to come up on the network'''
496 hostname
= self
.substitute(hostname
)
500 self
.run_cmd("ping -c 1 -w 10 %s" % hostname
)
505 raise RuntimeError("Failed to ping %s" % hostname
)
506 self
.info("Host %s is up" % hostname
)
508 def port_wait(self
, hostname
, port
, retries
=200, delay
=3, wait_for_fail
=False):
509 '''wait for a host to come up on the network'''
512 child
= self
.pexpect_spawn("nc -v -z -w 1 %s %u" % (hostname
, port
), crlf
=False, timeout
=1)
513 child
.expect([pexpect
.EOF
, pexpect
.TIMEOUT
])
517 # wait for timeout or fail
518 if i
is None or i
> 0:
526 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
528 raise RuntimeError("gave up waiting for %s:%d" % (hostname
, port
))
530 def run_net_time(self
, child
):
531 '''run net time on windows'''
532 child
.sendline("net time \\\\${HOSTNAME} /set")
533 child
.expect("Do you want to set the local computer")
535 child
.expect("The command completed successfully")
537 def run_date_time(self
, child
, time_tuple
=None):
538 '''run date and time on windows'''
539 if time_tuple
is None:
540 time_tuple
= time
.localtime()
541 child
.sendline("date")
542 child
.expect("Enter the new date:")
543 i
= child
.expect(["dd-mm-yy", "mm-dd-yy"])
545 child
.sendline(time
.strftime("%d-%m-%y", time_tuple
))
547 child
.sendline(time
.strftime("%m-%d-%y", time_tuple
))
549 child
.sendline("time")
550 child
.expect("Enter the new time:")
551 child
.sendline(time
.strftime("%H:%M:%S", time_tuple
))
554 def get_ipconfig(self
, child
):
555 '''get the IP configuration of the child'''
556 child
.sendline("ipconfig /all")
557 child
.expect('Ethernet adapter ')
558 child
.expect("[\w\s]+")
559 self
.setvar("WIN_NIC", child
.after
)
560 child
.expect(['IPv4 Address', 'IP Address'])
561 child
.expect('\d+.\d+.\d+.\d+')
562 self
.setvar('WIN_IPV4_ADDRESS', child
.after
)
563 child
.expect('Subnet Mask')
564 child
.expect('\d+.\d+.\d+.\d+')
565 self
.setvar('WIN_SUBNET_MASK', child
.after
)
566 child
.expect('Default Gateway')
567 i
= child
.expect(['\d+.\d+.\d+.\d+', "C:"])
569 self
.setvar('WIN_DEFAULT_GATEWAY', child
.after
)
572 def get_is_dc(self
, child
):
573 '''check if a windows machine is a domain controller'''
574 child
.sendline("dcdiag")
575 i
= child
.expect(["is not a [Directory Server|DC]",
576 "is not recognized as an internal or external command",
578 "passed test Replications"])
583 child
.sendline("net config Workstation")
584 child
.expect("Workstation domain")
585 child
.expect('[\S]+')
587 i
= child
.expect(["Workstation Domain DNS Name", "Logon domain"])
588 '''If we get the Logon domain first, we are not in an AD domain'''
591 if domain
.upper() == self
.getvar("WIN_DOMAIN").upper():
594 child
.expect('[\S]+')
595 hostname
= child
.after
596 if hostname
.upper() == self
.getvar("WIN_HOSTNAME").upper():
599 def set_noexpire(self
, child
, username
):
600 """Ensure this user's password does not expire"""
601 child
.sendline('wmic useraccount where name="%s" set PasswordExpires=FALSE' % username
)
602 child
.expect("update successful")
605 def run_tlntadmn(self
, child
):
606 '''remove the annoying telnet restrictions'''
607 child
.sendline('tlntadmn config maxconn=1024')
608 child
.expect(["The settings were successfully updated", "Access is denied"])
611 def disable_firewall(self
, child
):
612 '''remove the annoying firewall'''
613 child
.sendline('netsh advfirewall set allprofiles state off')
614 i
= child
.expect(["Ok", "The following command was not found: advfirewall set allprofiles state off", "The requested operation requires elevation", "Access is denied"])
617 child
.sendline('netsh firewall set opmode mode = DISABLE profile = ALL')
618 i
= child
.expect(["Ok", "The following command was not found", "Access is denied"])
620 self
.info("Firewall disable failed - ignoring")
623 def set_dns(self
, child
):
624 child
.sendline('netsh interface ip set dns "${WIN_NIC}" static ${NAMED_INTERFACE_IP} primary')
625 i
= child
.expect(['C:', pexpect
.EOF
, pexpect
.TIMEOUT
], timeout
=5)
631 def set_ip(self
, child
):
632 """fix the IP address to the same value it had when we
633 connected, but don't use DHCP, and force the DNS server to our
634 DNS server. This allows DNS updates to run"""
635 self
.get_ipconfig(child
)
636 if self
.getvar("WIN_IPV4_ADDRESS") != self
.getvar("WIN_IP"):
637 raise RuntimeError("ipconfig address %s != nmblookup address %s" % (self
.getvar("WIN_IPV4_ADDRESS"),
638 self
.getvar("WIN_IP")))
639 child
.sendline('netsh')
640 child
.expect('netsh>')
641 child
.sendline('offline')
642 child
.expect('netsh>')
643 child
.sendline('routing ip add persistentroute dest=0.0.0.0 mask=0.0.0.0 name="${WIN_NIC}" nhop=${WIN_DEFAULT_GATEWAY}')
644 child
.expect('netsh>')
645 child
.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1 store=persistent')
646 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)
648 child
.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1')
649 child
.expect('netsh>')
650 child
.sendline('commit')
651 child
.sendline('online')
652 child
.sendline('exit')
654 child
.expect([pexpect
.EOF
, pexpect
.TIMEOUT
], timeout
=5)
657 def resolve_ip(self
, hostname
, retries
=60, delay
=5):
658 '''resolve an IP given a hostname, assuming NBT'''
660 child
= self
.pexpect_spawn("bin/nmblookup %s" % hostname
)
663 i
= child
.expect(["querying", '\d+.\d+.\d+.\d+', hostname
, "Lookup failed"])
670 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
671 raise RuntimeError("Failed to resolve IP of %s" % hostname
)
673 def open_telnet(self
, hostname
, username
, password
, retries
=60, delay
=5, set_time
=False, set_ip
=False,
674 disable_firewall
=True, run_tlntadmn
=True, set_noexpire
=False):
675 '''open a telnet connection to a windows server, return the pexpect child'''
678 set_telnetclients
= True
680 if self
.getvar('WIN_IP'):
681 ip
= self
.getvar('WIN_IP')
683 ip
= self
.resolve_ip(hostname
)
684 self
.setvar('WIN_IP', ip
)
686 child
= self
.pexpect_spawn("telnet " + ip
+ " -l '" + username
+ "'")
687 i
= child
.expect(["Welcome to Microsoft Telnet Service",
688 "Denying new connections due to the limit on number of connections",
689 "No more connections are allowed to telnet server",
690 "Unable to connect to remote host",
692 "Connection refused",
698 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
700 child
.expect("password:")
701 child
.sendline(password
)
702 i
= child
.expect(["C:",
704 "Denying new connections due to the limit on number of connections",
705 "No more connections are allowed to telnet server",
706 "Unable to connect to remote host",
708 "Connection refused",
711 if set_telnetclients
:
712 self
.run_cmd('bin/net rpc group add TelnetClients -S $WIN_IP -U$WIN_USER%$WIN_PASS')
713 self
.run_cmd('bin/net rpc group addmem TelnetClients "authenticated users" -S $WIN_IP -U$WIN_USER%$WIN_PASS')
716 set_telnetclients
= False
717 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
720 raise RuntimeError("Failed to connect with telnet due to missing TelnetClients membership")
723 # This only works if it is installed and enabled, but not started. Not entirely likely, but possible
724 self
.run_cmd('bin/net rpc service start TlntSvr -S $WIN_IP -U$WIN_USER%$WIN_PASS')
728 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
735 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
739 if self
.set_dns(child
):
742 child
.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
746 self
.run_date_time(child
, None)
749 self
.run_tlntadmn(child
)
752 self
.set_noexpire(child
, username
)
755 self
.disable_firewall(child
)
756 disable_firewall
= False
759 if self
.set_ip(child
):
764 raise RuntimeError("Failed to connect with telnet")
766 def kinit(self
, username
, password
):
767 '''use kinit to setup a credentials cache'''
768 self
.run_cmd("kdestroy")
769 self
.putenv('KRB5CCNAME', "${PREFIX}/ccache.test")
770 username
= self
.substitute(username
)
771 s
= username
.split('@')
774 username
= '@'.join(s
)
775 child
= self
.pexpect_spawn('kinit ' + username
)
776 child
.expect("Password")
777 child
.sendline(password
)
778 child
.expect(pexpect
.EOF
)
780 if child
.exitstatus
!= 0:
781 raise RuntimeError("kinit failed with status %d" % child
.exitstatus
)
783 def get_domains(self
):
784 '''return a dictionary of DNS domains and IPs for named.conf'''
787 if v
[-6:] == "_REALM":
789 if base
+ '_IP' in self
.vars:
790 ret
[self
.vars[base
+ '_REALM']] = self
.vars[base
+ '_IP']
793 def wait_reboot(self
, retries
=3):
794 '''wait for a VM to reboot'''
796 # first wait for it to shutdown
797 self
.port_wait("${WIN_IP}", 139, wait_for_fail
=True, delay
=6)
799 # now wait for it to come back. If it fails to come back
800 # then try resetting it
803 self
.port_wait("${WIN_IP}", 139)
807 self
.vm_reset("${WIN_VM}")
808 self
.info("retrying reboot (retries=%u)" % retries
)
809 raise RuntimeError(self
.substitute("VM ${WIN_VM} failed to reboot"))
812 '''return a dictionary of all the configured VM names'''
816 ret
.append(self
.vars[v
])
819 def run_dcpromo_as_first_dc(self
, vm
, func_level
=None):
821 self
.info("Configuring a windows VM ${WIN_VM} at the first DC in the domain using dcpromo")
822 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_time
=True)
823 if self
.get_is_dc(child
):
826 if func_level
== '2008r2':
827 self
.setvar("FUNCTION_LEVEL_INT", str(4))
828 elif func_level
== '2003':
829 self
.setvar("FUNCTION_LEVEL_INT", str(1))
831 self
.setvar("FUNCTION_LEVEL_INT", str(0))
833 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_ip
=True, set_noexpire
=True)
835 """This server must therefore not yet be a directory server, so we must promote it"""
836 child
.sendline("copy /Y con answers.txt")
839 ; New forest promotion
840 ReplicaOrNewDomain=Domain
842 NewDomainDNSName=${WIN_REALM}
843 ForestLevel=${FUNCTION_LEVEL_INT}
844 DomainNetbiosName=${WIN_DOMAIN}
845 DomainLevel=${FUNCTION_LEVEL_INT}
848 CreateDNSDelegation=No
849 DatabasePath="C:\Windows\NTDS"
850 LogPath="C:\Windows\NTDS"
851 SYSVOLPath="C:\Windows\SYSVOL"
852 ; Set SafeModeAdminPassword to the correct value prior to using the unattend file
853 SafeModeAdminPassword=${WIN_PASS}
854 ; Run-time flags (optional)
855 RebootOnCompletion=No
858 child
.expect("copied.")
861 child
.sendline("dcpromo /answer:answers.txt")
862 i
= child
.expect(["You must restart this computer", "failed", "Active Directory Domain Services was not installed", "C:", pexpect
.TIMEOUT
], timeout
=240)
864 raise Exception("dcpromo failed")
866 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}")
868 child
.sendline("shutdown -r -t 0")
869 self
.port_wait("${WIN_IP}", 139, wait_for_fail
=True)
870 self
.port_wait("${WIN_IP}", 139)
872 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}")
873 # Check if we became a DC by now
874 if not self
.get_is_dc(child
):
875 raise Exception("dcpromo failed (and wasn't a DC even after rebooting)")
876 # Give DNS registration a kick
877 child
.sendline("ipconfig /registerdns")
879 self
.retry_cmd("host -t SRV _ldap._tcp.${WIN_REALM} ${WIN_IP}", ['has SRV record'], retries
=60, delay
=5)
881 def start_winvm(self
, vm
):
882 '''start a Windows VM'''
885 self
.info("Joining a windows box to the domain")
886 self
.vm_poweroff("${WIN_VM}", checkfail
=False)
887 self
.vm_restore("${WIN_VM}", "${WIN_SNAPSHOT}")
889 def run_winjoin(self
, vm
, domain
, username
="administrator", password
="${PASSWORD1}"):
890 '''join a windows box to a domain'''
891 child
= self
.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time
=True, set_ip
=True, set_noexpire
=True)
894 child
.sendline("ipconfig /flushdns")
896 child
.sendline("netdom join ${WIN_HOSTNAME} /Domain:%s /UserD:%s /PasswordD:%s" % (domain
, username
, password
))
897 i
= child
.expect(["The command completed successfully",
898 "The specified domain either does not exist or could not be contacted."], timeout
=120)
905 child
.sendline("shutdown /r -t 0")
907 child
= self
.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time
=True, set_ip
=True)
908 child
.sendline("ipconfig /registerdns")
909 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")
912 def test_remote_smbclient(self
, vm
, username
="${WIN_USER}", password
="${WIN_PASS}", args
=""):
913 '''test smbclient against remote server'''
915 self
.info('Testing smbclient')
916 self
.chdir('${PREFIX}')
917 smbclient
= self
.getvar("smbclient")
918 self
.cmd_contains("%s --version" % (smbclient
), ["${SAMBA_VERSION}"])
919 self
.retry_cmd('%s -L ${WIN_HOSTNAME} -U%s%%%s %s' % (smbclient
, username
, password
, args
), ["IPC"], retries
=60, delay
=5)
921 def test_net_use(self
, vm
, realm
, domain
, username
, password
):
923 self
.info('Testing net use against Samba3 member')
924 child
= self
.open_telnet("${WIN_HOSTNAME}", "%s\\%s" % (domain
, username
), password
)
925 child
.sendline("net use t: \\\\${HOSTNAME}.%s\\test" % realm
)
926 child
.expect("The command completed successfully")
928 def setup(self
, testname
, subdir
):
929 '''setup for main tests, parsing command line'''
930 self
.parser
.add_option("--conf", type='string', default
='', help='config file')
931 self
.parser
.add_option("--skip", type='string', default
='', help='list of steps to skip (comma separated)')
932 self
.parser
.add_option("--vms", type='string', default
=None, help='list of VMs to use (comma separated)')
933 self
.parser
.add_option("--list", action
='store_true', default
=False, help='list the available steps')
934 self
.parser
.add_option("--rebase", action
='store_true', default
=False, help='do a git pull --rebase')
935 self
.parser
.add_option("--clean", action
='store_true', default
=False, help='clean the tree')
936 self
.parser
.add_option("--prefix", type='string', default
=None, help='override install prefix')
937 self
.parser
.add_option("--sourcetree", type='string', default
=None, help='override sourcetree location')
938 self
.parser
.add_option("--nocleanup", action
='store_true', default
=False, help='disable cleanup code')
939 self
.parser
.add_option("--use-ntvfs", action
='store_true', default
=False, help='use NTVFS for the fileserver')
940 self
.parser
.add_option("--dns-backend", type="choice",
941 choices
=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
942 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), "
943 "BIND9_FLATFILE uses bind9 text database to store zone information, "
944 "BIND9_DLZ uses samba4 AD to store zone information, "
945 "NONE skips the DNS setup entirely (not recommended)",
946 default
="SAMBA_INTERNAL")
948 self
.opts
, self
.args
= self
.parser
.parse_args()
950 if not self
.opts
.conf
:
951 print("Please specify a config file with --conf")
954 # we don't need fsync safety in these tests
955 self
.putenv('TDB_NO_FSYNC', '1')
957 self
.load_config(self
.opts
.conf
)
959 nameserver
= self
.get_nameserver()
960 if nameserver
== self
.getvar('NAMED_INTERFACE_IP'):
961 raise RuntimeError("old /etc/resolv.conf must not contain %s as a nameserver, this will create loops with the generated dns configuration" % nameserver
)
962 self
.setvar('DNSSERVER', nameserver
)
964 self
.set_skip(self
.opts
.skip
)
965 self
.set_vms(self
.opts
.vms
)
968 self
.list_steps_mode()
971 self
.setvar('PREFIX', self
.opts
.prefix
)
973 if self
.opts
.sourcetree
:
974 self
.setvar('SOURCETREE', self
.opts
.sourcetree
)
977 self
.info('rebasing')
978 self
.chdir('${SOURCETREE}')
979 self
.run_cmd('git pull --rebase')
982 self
.info('cleaning')
983 self
.chdir('${SOURCETREE}/' + subdir
)
984 self
.run_cmd('make clean')
986 if self
.opts
.use_ntvfs
:
987 self
.setvar('USE_NTVFS', "--use-ntvfs")
989 self
.setvar('USE_NTVFS', "")
991 self
.setvar('NAMESERVER_BACKEND', self
.opts
.dns_backend
)
993 self
.setvar('DNS_FORWARDER', "--option=dns forwarder=%s" % nameserver
)