3 '''automated testing library for testing Samba against windows'''
5 import pexpect
, subprocess
7 import sys
, os
, time
, re
10 '''testing of Samba against windows VMs'''
14 self
.list_mode
= False
16 os
.environ
['PYTHONUNBUFFERED'] = '1'
17 self
.parser
= optparse
.OptionParser("wintest")
19 def check_prerequesites(self
):
20 self
.info("Checking prerequesites")
21 self
.setvar('HOSTNAME', self
.cmd_output("hostname -s").strip())
23 raise Exception("You must run this script as root")
24 self
.run_cmd('ifconfig ${INTERFACE} ${INTERFACE_NET} up')
25 if self
.getvar('INTERFACE_IPV6'):
26 self
.run_cmd('ifconfig ${INTERFACE} inet6 del ${INTERFACE_IPV6}/64', checkfail
=False)
27 self
.run_cmd('ifconfig ${INTERFACE} inet6 add ${INTERFACE_IPV6}/64 up')
29 self
.run_cmd('ifconfig ${NAMED_INTERFACE} ${NAMED_INTERFACE_NET} up')
30 if self
.getvar('NAMED_INTERFACE_IPV6'):
31 self
.run_cmd('ifconfig ${NAMED_INTERFACE} inet6 del ${NAMED_INTERFACE_IPV6}/64', checkfail
=False)
32 self
.run_cmd('ifconfig ${NAMED_INTERFACE} inet6 add ${NAMED_INTERFACE_IPV6}/64 up')
35 '''Shut down any existing alive VMs, so they do not collide with what we are doing'''
36 self
.info('Shutting down any of our VMs already running')
39 self
.vm_poweroff(v
, checkfail
=False)
41 def setvar(self
, varname
, value
):
42 '''set a substitution variable'''
43 self
.vars[varname
] = value
45 def getvar(self
, varname
):
46 '''return a substitution variable'''
47 if not varname
in self
.vars:
49 return self
.vars[varname
]
51 def setwinvars(self
, vm
, prefix
='WIN'):
52 '''setup WIN_XX vars based on a vm name'''
53 for v
in ['VM', 'HOSTNAME', 'USER', 'PASS', 'SNAPSHOT', 'REALM', 'DOMAIN', 'IP']:
54 vname
= '%s_%s' % (vm
, v
)
55 if vname
in self
.vars:
56 self
.setvar("%s_%s" % (prefix
,v
), self
.substitute("${%s}" % vname
))
58 self
.vars.pop("%s_%s" % (prefix
,v
), None)
60 if self
.getvar("WIN_REALM"):
61 self
.setvar("WIN_REALM", self
.getvar("WIN_REALM").upper())
62 self
.setvar("WIN_LCREALM", self
.getvar("WIN_REALM").lower())
63 dnsdomain
= self
.getvar("WIN_REALM")
64 self
.setvar("WIN_BASEDN", "DC=" + dnsdomain
.replace(".", ",DC="))
65 if self
.getvar("WIN_USER") is None:
66 self
.setvar("WIN_USER", "administrator")
69 '''print some information'''
70 if not self
.list_mode
:
71 print(self
.substitute(msg
))
73 def load_config(self
, fname
):
74 '''load the config file'''
78 if len(line
) == 0 or line
[0] == '#':
80 colon
= line
.find(':')
82 raise RuntimeError("Invalid config line '%s'" % line
)
83 varname
= line
[0:colon
].strip()
84 value
= line
[colon
+1:].strip()
85 self
.setvar(varname
, value
)
87 def list_steps_mode(self
):
88 '''put wintest in step listing mode'''
91 def set_skip(self
, skiplist
):
92 '''set a list of tests to skip'''
93 self
.skiplist
= skiplist
.split(',')
95 def set_vms(self
, vms
):
96 '''set a list of VMs to test'''
99 for vm
in vms
.split(','):
103 def skip(self
, step
):
104 '''return True if we should skip a step'''
108 return step
in self
.skiplist
110 def substitute(self
, text
):
111 """Substitute strings of the form ${NAME} in text, replacing
112 with substitutions from vars.
114 if isinstance(text
, list):
116 for i
in range(len(ret
)):
117 ret
[i
] = self
.substitute(ret
[i
])
120 """We may have objects such as pexpect.EOF that are not strings"""
121 if not isinstance(text
, str):
124 var_start
= text
.find("${")
127 var_end
= text
.find("}", var_start
)
130 var_name
= text
[var_start
+2:var_end
]
131 if not var_name
in self
.vars:
132 raise RuntimeError("Unknown substitution variable ${%s}" % var_name
)
133 text
= text
.replace("${%s}" % var_name
, self
.vars[var_name
])
136 def have_var(self
, varname
):
137 '''see if a variable has been set'''
138 return varname
in self
.vars
140 def have_vm(self
, vmname
):
141 '''see if a VM should be used'''
142 if not self
.have_var(vmname
+ '_VM'):
146 return vmname
in self
.vms
148 def putenv(self
, key
, value
):
149 '''putenv with substitution'''
150 os
.environ
[key
] = self
.substitute(value
)
152 def chdir(self
, dir):
153 '''chdir with substitution'''
154 os
.chdir(self
.substitute(dir))
156 def del_files(self
, dirs
):
157 '''delete all files in the given directory'''
159 self
.run_cmd("find %s -type f | xargs rm -f" % d
)
161 def write_file(self
, filename
, text
, mode
='w'):
162 '''write to a file'''
163 f
= open(self
.substitute(filename
), mode
=mode
)
164 f
.write(self
.substitute(text
))
167 def run_cmd(self
, cmd
, dir=".", show
=None, output
=False, checkfail
=True):
169 cmd
= self
.substitute(cmd
)
170 if isinstance(cmd
, list):
171 self
.info('$ ' + " ".join(cmd
))
173 self
.info('$ ' + cmd
)
175 return subprocess
.Popen([cmd
], shell
=True, stdout
=subprocess
.PIPE
, stderr
=subprocess
.STDOUT
, cwd
=dir).communicate()[0]
176 if isinstance(cmd
, list):
181 return subprocess
.check_call(cmd
, shell
=shell
, cwd
=dir)
183 return subprocess
.call(cmd
, shell
=shell
, cwd
=dir)
186 def run_child(self
, cmd
, dir="."):
187 '''create a child and return the Popen handle to it'''
189 cmd
= self
.substitute(cmd
)
190 if isinstance(cmd
, list):
191 self
.info('$ ' + " ".join(cmd
))
193 self
.info('$ ' + cmd
)
194 if isinstance(cmd
, list):
199 ret
= subprocess
.Popen(cmd
, shell
=shell
, stderr
=subprocess
.STDOUT
)
203 def cmd_output(self
, cmd
):
204 '''return output from and command'''
205 cmd
= self
.substitute(cmd
)
206 return self
.run_cmd(cmd
, output
=True)
208 def cmd_contains(self
, cmd
, contains
, nomatch
=False, ordered
=False, regex
=False,
210 '''check that command output contains the listed strings'''
212 if isinstance(contains
, str):
213 contains
= [contains
]
215 out
= self
.cmd_output(cmd
)
217 for c
in self
.substitute(contains
):
222 m
= re
.search(c
, out
)
230 start
= out
.upper().find(c
.upper())
237 raise RuntimeError("Expected to not see %s in %s" % (c
, cmd
))
240 raise RuntimeError("Expected to see %s in %s" % (c
, cmd
))
241 if ordered
and start
!= -1:
244 def retry_cmd(self
, cmd
, contains
, retries
=30, delay
=2, wait_for_fail
=False,
245 ordered
=False, regex
=False, casefold
=True):
246 '''retry a command a number of times'''
249 self
.cmd_contains(cmd
, contains
, nomatch
=wait_for_fail
,
250 ordered
=ordered
, regex
=regex
, casefold
=casefold
)
255 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
256 raise RuntimeError("Failed to find %s" % contains
)
258 def pexpect_spawn(self
, cmd
, timeout
=60, crlf
=True, casefold
=True):
259 '''wrapper around pexpect spawn'''
260 cmd
= self
.substitute(cmd
)
261 self
.info("$ " + cmd
)
262 ret
= pexpect
.spawn(cmd
, logfile
=sys
.stdout
, timeout
=timeout
)
264 def sendline_sub(line
):
265 line
= self
.substitute(line
)
267 line
= line
.replace('\n', '\r\n') + '\r'
268 return ret
.old_sendline(line
)
270 def expect_sub(line
, timeout
=ret
.timeout
, casefold
=casefold
):
271 line
= self
.substitute(line
)
273 if isinstance(line
, list):
274 for i
in range(len(line
)):
275 if isinstance(line
[i
], str):
276 line
[i
] = '(?i)' + line
[i
]
277 elif isinstance(line
, str):
279 return ret
.old_expect(line
, timeout
=timeout
)
281 ret
.old_sendline
= ret
.sendline
282 ret
.sendline
= sendline_sub
283 ret
.old_expect
= ret
.expect
284 ret
.expect
= expect_sub
288 def get_nameserver(self
):
289 '''Get the current nameserver from /etc/resolv.conf'''
290 child
= self
.pexpect_spawn('cat /etc/resolv.conf', crlf
=False)
291 i
= child
.expect(['Generated by wintest', 'nameserver'])
293 child
.expect('your original resolv.conf')
294 child
.expect('nameserver')
295 child
.expect('\d+.\d+.\d+.\d+')
298 def rndc_cmd(self
, cmd
, checkfail
=True):
299 '''run a rndc command'''
300 self
.run_cmd("${RNDC} -c ${PREFIX}/etc/rndc.conf %s" % cmd
, checkfail
=checkfail
)
302 def named_supports_gssapi_keytab(self
):
303 '''see if named supports tkey-gssapi-keytab'''
304 self
.write_file("${PREFIX}/named.conf.test",
305 'options { tkey-gssapi-keytab "test"; };')
307 self
.run_cmd("${NAMED_CHECKCONF} ${PREFIX}/named.conf.test")
308 except subprocess
.CalledProcessError
:
312 def set_nameserver(self
, nameserver
):
313 '''set the nameserver in resolv.conf'''
314 self
.write_file("/etc/resolv.conf.wintest", '''
315 # Generated by wintest, the Samba v Windows automated testing system
318 # your original resolv.conf appears below:
319 ''' % self
.substitute(nameserver
))
320 child
= self
.pexpect_spawn("cat /etc/resolv.conf", crlf
=False)
321 i
= child
.expect(['your original resolv.conf appears below:', pexpect
.EOF
])
323 child
.expect(pexpect
.EOF
)
324 contents
= child
.before
.lstrip().replace('\r', '')
325 self
.write_file('/etc/resolv.conf.wintest', contents
, mode
='a')
326 self
.write_file('/etc/resolv.conf.wintest-bak', contents
)
327 self
.run_cmd("mv -f /etc/resolv.conf.wintest /etc/resolv.conf")
328 self
.resolv_conf_backup
= '/etc/resolv.conf.wintest-bak';
330 def configure_bind(self
, kerberos_support
=False, include
=None):
331 self
.chdir('${PREFIX}')
333 if self
.getvar('NAMED_INTERFACE_IPV6'):
334 ipv6_listen
= 'listen-on-v6 port 53 { ${NAMED_INTERFACE_IPV6}; };'
337 self
.setvar('BIND_LISTEN_IPV6', ipv6_listen
)
339 if not kerberos_support
:
340 self
.setvar("NAMED_TKEY_OPTION", "")
341 elif self
.getvar('NAMESERVER_BACKEND') != 'SAMBA_INTERNAL':
342 if self
.named_supports_gssapi_keytab():
343 self
.setvar("NAMED_TKEY_OPTION",
344 'tkey-gssapi-keytab "${PREFIX}/bind-dns/dns.keytab";')
346 self
.info("LCREALM=${LCREALM}")
347 self
.setvar("NAMED_TKEY_OPTION",
348 '''tkey-gssapi-credential "DNS/${LCREALM}";
349 tkey-domain "${LCREALM}";
351 self
.putenv('KEYTAB_FILE', '${PREFIX}/bind-dns/dns.keytab')
352 self
.putenv('KRB5_KTNAME', '${PREFIX}/bind-dns/dns.keytab')
354 self
.setvar("NAMED_TKEY_OPTION", "")
356 if include
and self
.getvar('NAMESERVER_BACKEND') != 'SAMBA_INTERNAL':
357 self
.setvar("NAMED_INCLUDE", 'include "%s";' % include
)
359 self
.setvar("NAMED_INCLUDE", '')
361 self
.run_cmd("mkdir -p ${PREFIX}/etc")
363 self
.write_file("etc/named.conf", '''
365 listen-on port 53 { ${NAMED_INTERFACE_IP}; };
367 directory "${PREFIX}/var/named";
368 dump-file "${PREFIX}/var/named/data/cache_dump.db";
369 pid-file "${PREFIX}/var/named/named.pid";
370 statistics-file "${PREFIX}/var/named/data/named_stats.txt";
371 memstatistics-file "${PREFIX}/var/named/data/named_mem_stats.txt";
372 allow-query { any; };
387 secret "lA/cTrno03mt5Ju17ybEYw==";
391 inet ${NAMED_INTERFACE_IP} port 953
392 allow { any; } keys { "rndc-key"; };
398 if self
.getvar('NAMESERVER_BACKEND') == 'SAMBA_INTERNAL':
399 self
.write_file('etc/named.conf',
408 ''' % (self
.getvar('LCREALM'), self
.getvar('INTERFACE_IP')),
412 # add forwarding for the windows domains
413 domains
= self
.get_domains()
416 self
.write_file('etc/named.conf',
425 ''' % (d
, domains
[d
]),
429 self
.write_file("etc/rndc.conf", '''
433 secret "lA/cTrno03mt5Ju17ybEYw==";
437 default-key "rndc-key";
438 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")
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
)
479 def vm_poweroff(self
, vmname
, checkfail
=True):
481 self
.setvar('VMNAME', vmname
)
482 self
.run_cmd("${VM_POWEROFF}", checkfail
=checkfail
)
484 def vm_reset(self
, vmname
):
486 self
.setvar('VMNAME', vmname
)
487 self
.run_cmd("${VM_RESET}")
489 def vm_restore(self
, vmname
, snapshot
):
491 self
.setvar('VMNAME', vmname
)
492 self
.setvar('SNAPSHOT', snapshot
)
493 self
.run_cmd("${VM_RESTORE}")
495 def ping_wait(self
, hostname
):
496 '''wait for a hostname to come up on the network'''
497 hostname
= self
.substitute(hostname
)
501 self
.run_cmd("ping -c 1 -w 10 %s" % hostname
)
506 raise RuntimeError("Failed to ping %s" % hostname
)
507 self
.info("Host %s is up" % hostname
)
509 def port_wait(self
, hostname
, port
, retries
=200, delay
=3, wait_for_fail
=False):
510 '''wait for a host to come up on the network'''
513 child
= self
.pexpect_spawn("nc -v -z -w 1 %s %u" % (hostname
, port
), crlf
=False, timeout
=1)
514 child
.expect([pexpect
.EOF
, pexpect
.TIMEOUT
])
518 #wait for timeout or fail
519 if i
== None or i
> 0:
527 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
529 raise RuntimeError("gave up waiting for %s:%d" % (hostname
, port
))
531 def run_net_time(self
, child
):
532 '''run net time on windows'''
533 child
.sendline("net time \\\\${HOSTNAME} /set")
534 child
.expect("Do you want to set the local computer")
536 child
.expect("The command completed successfully")
538 def run_date_time(self
, child
, time_tuple
=None):
539 '''run date and time on windows'''
540 if time_tuple
is None:
541 time_tuple
= time
.localtime()
542 child
.sendline("date")
543 child
.expect("Enter the new date:")
544 i
= child
.expect(["dd-mm-yy", "mm-dd-yy"])
546 child
.sendline(time
.strftime("%d-%m-%y", time_tuple
))
548 child
.sendline(time
.strftime("%m-%d-%y", time_tuple
))
550 child
.sendline("time")
551 child
.expect("Enter the new time:")
552 child
.sendline(time
.strftime("%H:%M:%S", time_tuple
))
555 def get_ipconfig(self
, child
):
556 '''get the IP configuration of the child'''
557 child
.sendline("ipconfig /all")
558 child
.expect('Ethernet adapter ')
559 child
.expect("[\w\s]+")
560 self
.setvar("WIN_NIC", child
.after
)
561 child
.expect(['IPv4 Address', 'IP Address'])
562 child
.expect('\d+.\d+.\d+.\d+')
563 self
.setvar('WIN_IPV4_ADDRESS', child
.after
)
564 child
.expect('Subnet Mask')
565 child
.expect('\d+.\d+.\d+.\d+')
566 self
.setvar('WIN_SUBNET_MASK', child
.after
)
567 child
.expect('Default Gateway')
568 i
= child
.expect(['\d+.\d+.\d+.\d+', "C:"])
570 self
.setvar('WIN_DEFAULT_GATEWAY', child
.after
)
573 def get_is_dc(self
, child
):
574 '''check if a windows machine is a domain controller'''
575 child
.sendline("dcdiag")
576 i
= child
.expect(["is not a [Directory Server|DC]",
577 "is not recognized as an internal or external command",
579 "passed test Replications"])
584 child
.sendline("net config Workstation")
585 child
.expect("Workstation domain")
586 child
.expect('[\S]+')
588 i
= child
.expect(["Workstation Domain DNS Name", "Logon domain"])
589 '''If we get the Logon domain first, we are not in an AD domain'''
592 if domain
.upper() == self
.getvar("WIN_DOMAIN").upper():
595 child
.expect('[\S]+')
596 hostname
= child
.after
597 if hostname
.upper() == self
.getvar("WIN_HOSTNAME").upper():
600 def set_noexpire(self
, child
, username
):
601 """Ensure this user's password does not expire"""
602 child
.sendline('wmic useraccount where name="%s" set PasswordExpires=FALSE' % username
)
603 child
.expect("update successful")
606 def run_tlntadmn(self
, child
):
607 '''remove the annoying telnet restrictions'''
608 child
.sendline('tlntadmn config maxconn=1024')
609 child
.expect(["The settings were successfully updated", "Access is denied"])
612 def disable_firewall(self
, child
):
613 '''remove the annoying firewall'''
614 child
.sendline('netsh advfirewall set allprofiles state off')
615 i
= child
.expect(["Ok", "The following command was not found: advfirewall set allprofiles state off", "The requested operation requires elevation", "Access is denied"])
618 child
.sendline('netsh firewall set opmode mode = DISABLE profile = ALL')
619 i
= child
.expect(["Ok", "The following command was not found", "Access is denied"])
621 self
.info("Firewall disable failed - ignoring")
624 def set_dns(self
, child
):
625 child
.sendline('netsh interface ip set dns "${WIN_NIC}" static ${NAMED_INTERFACE_IP} primary')
626 i
= child
.expect(['C:', pexpect
.EOF
, pexpect
.TIMEOUT
], timeout
=5)
632 def set_ip(self
, child
):
633 """fix the IP address to the same value it had when we
634 connected, but don't use DHCP, and force the DNS server to our
635 DNS server. This allows DNS updates to run"""
636 self
.get_ipconfig(child
)
637 if self
.getvar("WIN_IPV4_ADDRESS") != self
.getvar("WIN_IP"):
638 raise RuntimeError("ipconfig address %s != nmblookup address %s" % (self
.getvar("WIN_IPV4_ADDRESS"),
639 self
.getvar("WIN_IP")))
640 child
.sendline('netsh')
641 child
.expect('netsh>')
642 child
.sendline('offline')
643 child
.expect('netsh>')
644 child
.sendline('routing ip add persistentroute dest=0.0.0.0 mask=0.0.0.0 name="${WIN_NIC}" nhop=${WIN_DEFAULT_GATEWAY}')
645 child
.expect('netsh>')
646 child
.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1 store=persistent')
647 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)
649 child
.sendline('interface ip set address "${WIN_NIC}" static ${WIN_IPV4_ADDRESS} ${WIN_SUBNET_MASK} ${WIN_DEFAULT_GATEWAY} 1')
650 child
.expect('netsh>')
651 child
.sendline('commit')
652 child
.sendline('online')
653 child
.sendline('exit')
655 child
.expect([pexpect
.EOF
, pexpect
.TIMEOUT
], timeout
=5)
659 def resolve_ip(self
, hostname
, retries
=60, delay
=5):
660 '''resolve an IP given a hostname, assuming NBT'''
662 child
= self
.pexpect_spawn("bin/nmblookup %s" % hostname
)
665 i
= child
.expect(["querying", '\d+.\d+.\d+.\d+', hostname
, "Lookup failed"])
672 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
673 raise RuntimeError("Failed to resolve IP of %s" % hostname
)
676 def open_telnet(self
, hostname
, username
, password
, retries
=60, delay
=5, set_time
=False, set_ip
=False,
677 disable_firewall
=True, run_tlntadmn
=True, set_noexpire
=False):
678 '''open a telnet connection to a windows server, return the pexpect child'''
681 set_telnetclients
= True
683 if self
.getvar('WIN_IP'):
684 ip
= self
.getvar('WIN_IP')
686 ip
= self
.resolve_ip(hostname
)
687 self
.setvar('WIN_IP', ip
)
689 child
= self
.pexpect_spawn("telnet " + ip
+ " -l '" + username
+ "'")
690 i
= child
.expect(["Welcome to Microsoft Telnet Service",
691 "Denying new connections due to the limit on number of connections",
692 "No more connections are allowed to telnet server",
693 "Unable to connect to remote host",
695 "Connection refused",
701 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
703 child
.expect("password:")
704 child
.sendline(password
)
705 i
= child
.expect(["C:",
707 "Denying new connections due to the limit on number of connections",
708 "No more connections are allowed to telnet server",
709 "Unable to connect to remote host",
711 "Connection refused",
714 if set_telnetclients
:
715 self
.run_cmd('bin/net rpc group add TelnetClients -S $WIN_IP -U$WIN_USER%$WIN_PASS')
716 self
.run_cmd('bin/net rpc group addmem TelnetClients "authenticated users" -S $WIN_IP -U$WIN_USER%$WIN_PASS')
719 set_telnetclients
= False
720 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
723 raise RuntimeError("Failed to connect with telnet due to missing TelnetClients membership")
726 # This only works if it is installed and enabled, but not started. Not entirely likely, but possible
727 self
.run_cmd('bin/net rpc service start TlntSvr -S $WIN_IP -U$WIN_USER%$WIN_PASS')
731 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
738 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
742 if self
.set_dns(child
):
745 child
.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
749 self
.run_date_time(child
, None)
752 self
.run_tlntadmn(child
)
755 self
.set_noexpire(child
, username
)
758 self
.disable_firewall(child
)
759 disable_firewall
= False
762 if self
.set_ip(child
):
767 raise RuntimeError("Failed to connect with telnet")
769 def kinit(self
, username
, password
):
770 '''use kinit to setup a credentials cache'''
771 self
.run_cmd("kdestroy")
772 self
.putenv('KRB5CCNAME', "${PREFIX}/ccache.test")
773 username
= self
.substitute(username
)
774 s
= username
.split('@')
777 username
= '@'.join(s
)
778 child
= self
.pexpect_spawn('kinit ' + username
)
779 child
.expect("Password")
780 child
.sendline(password
)
781 child
.expect(pexpect
.EOF
)
783 if child
.exitstatus
!= 0:
784 raise RuntimeError("kinit failed with status %d" % child
.exitstatus
)
786 def get_domains(self
):
787 '''return a dictionary of DNS domains and IPs for named.conf'''
790 if v
[-6:] == "_REALM":
792 if base
+ '_IP' in self
.vars:
793 ret
[self
.vars[base
+ '_REALM']] = self
.vars[base
+ '_IP']
796 def wait_reboot(self
, retries
=3):
797 '''wait for a VM to reboot'''
799 # first wait for it to shutdown
800 self
.port_wait("${WIN_IP}", 139, wait_for_fail
=True, delay
=6)
802 # now wait for it to come back. If it fails to come back
803 # then try resetting it
806 self
.port_wait("${WIN_IP}", 139)
810 self
.vm_reset("${WIN_VM}")
811 self
.info("retrying reboot (retries=%u)" % retries
)
812 raise RuntimeError(self
.substitute("VM ${WIN_VM} failed to reboot"))
815 '''return a dictionary of all the configured VM names'''
819 ret
.append(self
.vars[v
])
823 def run_dcpromo_as_first_dc(self
, vm
, func_level
=None):
825 self
.info("Configuring a windows VM ${WIN_VM} at the first DC in the domain using dcpromo")
826 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_time
=True)
827 if self
.get_is_dc(child
):
830 if func_level
== '2008r2':
831 self
.setvar("FUNCTION_LEVEL_INT", str(4))
832 elif func_level
== '2003':
833 self
.setvar("FUNCTION_LEVEL_INT", str(1))
835 self
.setvar("FUNCTION_LEVEL_INT", str(0))
837 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_ip
=True, set_noexpire
=True)
839 """This server must therefore not yet be a directory server, so we must promote it"""
840 child
.sendline("copy /Y con answers.txt")
843 ; New forest promotion
844 ReplicaOrNewDomain=Domain
846 NewDomainDNSName=${WIN_REALM}
847 ForestLevel=${FUNCTION_LEVEL_INT}
848 DomainNetbiosName=${WIN_DOMAIN}
849 DomainLevel=${FUNCTION_LEVEL_INT}
852 CreateDNSDelegation=No
853 DatabasePath="C:\Windows\NTDS"
854 LogPath="C:\Windows\NTDS"
855 SYSVOLPath="C:\Windows\SYSVOL"
856 ; Set SafeModeAdminPassword to the correct value prior to using the unattend file
857 SafeModeAdminPassword=${WIN_PASS}
858 ; Run-time flags (optional)
859 RebootOnCompletion=No
862 child
.expect("copied.")
865 child
.sendline("dcpromo /answer:answers.txt")
866 i
= child
.expect(["You must restart this computer", "failed", "Active Directory Domain Services was not installed", "C:", pexpect
.TIMEOUT
], timeout
=240)
868 raise Exception("dcpromo failed")
870 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}")
872 child
.sendline("shutdown -r -t 0")
873 self
.port_wait("${WIN_IP}", 139, wait_for_fail
=True)
874 self
.port_wait("${WIN_IP}", 139)
876 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}")
877 # Check if we became a DC by now
878 if not self
.get_is_dc(child
):
879 raise Exception("dcpromo failed (and wasn't a DC even after rebooting)")
880 # Give DNS registration a kick
881 child
.sendline("ipconfig /registerdns")
883 self
.retry_cmd("host -t SRV _ldap._tcp.${WIN_REALM} ${WIN_IP}", ['has SRV record'], retries
=60, delay
=5 )
886 def start_winvm(self
, vm
):
887 '''start a Windows VM'''
890 self
.info("Joining a windows box to the domain")
891 self
.vm_poweroff("${WIN_VM}", checkfail
=False)
892 self
.vm_restore("${WIN_VM}", "${WIN_SNAPSHOT}")
894 def run_winjoin(self
, vm
, domain
, username
="administrator", password
="${PASSWORD1}"):
895 '''join a windows box to a domain'''
896 child
= self
.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time
=True, set_ip
=True, set_noexpire
=True)
899 child
.sendline("ipconfig /flushdns")
901 child
.sendline("netdom join ${WIN_HOSTNAME} /Domain:%s /UserD:%s /PasswordD:%s" % (domain
, username
, password
))
902 i
= child
.expect(["The command completed successfully",
903 "The specified domain either does not exist or could not be contacted."], timeout
=120)
910 child
.sendline("shutdown /r -t 0")
912 child
= self
.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time
=True, set_ip
=True)
913 child
.sendline("ipconfig /registerdns")
914 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")
918 def test_remote_smbclient(self
, vm
, username
="${WIN_USER}", password
="${WIN_PASS}", args
=""):
919 '''test smbclient against remote server'''
921 self
.info('Testing smbclient')
922 self
.chdir('${PREFIX}')
923 smbclient
= self
.getvar("smbclient")
924 self
.cmd_contains("%s --version" % (smbclient
), ["${SAMBA_VERSION}"])
925 self
.retry_cmd('%s -L ${WIN_HOSTNAME} -U%s%%%s %s' % (smbclient
, username
, password
, args
), ["IPC"], retries
=60, delay
=5)
927 def test_net_use(self
, vm
, realm
, domain
, username
, password
):
929 self
.info('Testing net use against Samba3 member')
930 child
= self
.open_telnet("${WIN_HOSTNAME}", "%s\\%s" % (domain
, username
), password
)
931 child
.sendline("net use t: \\\\${HOSTNAME}.%s\\test" % realm
)
932 child
.expect("The command completed successfully")
935 def setup(self
, testname
, subdir
):
936 '''setup for main tests, parsing command line'''
937 self
.parser
.add_option("--conf", type='string', default
='', help='config file')
938 self
.parser
.add_option("--skip", type='string', default
='', help='list of steps to skip (comma separated)')
939 self
.parser
.add_option("--vms", type='string', default
=None, help='list of VMs to use (comma separated)')
940 self
.parser
.add_option("--list", action
='store_true', default
=False, help='list the available steps')
941 self
.parser
.add_option("--rebase", action
='store_true', default
=False, help='do a git pull --rebase')
942 self
.parser
.add_option("--clean", action
='store_true', default
=False, help='clean the tree')
943 self
.parser
.add_option("--prefix", type='string', default
=None, help='override install prefix')
944 self
.parser
.add_option("--sourcetree", type='string', default
=None, help='override sourcetree location')
945 self
.parser
.add_option("--nocleanup", action
='store_true', default
=False, help='disable cleanup code')
946 self
.parser
.add_option("--use-ntvfs", action
='store_true', default
=False, help='use NTVFS for the fileserver')
947 self
.parser
.add_option("--dns-backend", type="choice",
948 choices
=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
949 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), " \
950 "BIND9_FLATFILE uses bind9 text database to store zone information, " \
951 "BIND9_DLZ uses samba4 AD to store zone information, " \
952 "NONE skips the DNS setup entirely (not recommended)",
953 default
="SAMBA_INTERNAL")
955 self
.opts
, self
.args
= self
.parser
.parse_args()
957 if not self
.opts
.conf
:
958 print("Please specify a config file with --conf")
961 # we don't need fsync safety in these tests
962 self
.putenv('TDB_NO_FSYNC', '1')
964 self
.load_config(self
.opts
.conf
)
966 nameserver
= self
.get_nameserver()
967 if nameserver
== self
.getvar('NAMED_INTERFACE_IP'):
968 raise RuntimeError("old /etc/resolv.conf must not contain %s as a nameserver, this will create loops with the generated dns configuration" % nameserver
)
969 self
.setvar('DNSSERVER', nameserver
)
971 self
.set_skip(self
.opts
.skip
)
972 self
.set_vms(self
.opts
.vms
)
975 self
.list_steps_mode()
978 self
.setvar('PREFIX', self
.opts
.prefix
)
980 if self
.opts
.sourcetree
:
981 self
.setvar('SOURCETREE', self
.opts
.sourcetree
)
984 self
.info('rebasing')
985 self
.chdir('${SOURCETREE}')
986 self
.run_cmd('git pull --rebase')
989 self
.info('cleaning')
990 self
.chdir('${SOURCETREE}/' + subdir
)
991 self
.run_cmd('make clean')
993 if self
.opts
.use_ntvfs
:
994 self
.setvar('USE_NTVFS', "--use-ntvfs")
996 self
.setvar('USE_NTVFS', "")
998 self
.setvar('NAMESERVER_BACKEND', self
.opts
.dns_backend
)
1000 self
.setvar('DNS_FORWARDER', "--option=dns forwarder=%s" % nameserver
)