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}/private/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}/private/dns.keytab')
352 self
.putenv('KRB5_KTNAME', '${PREFIX}/private/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
682 if self
.getvar('WIN_IP'):
683 ip
= self
.getvar('WIN_IP')
685 ip
= self
.resolve_ip(hostname
)
686 self
.setvar('WIN_IP', ip
)
688 child
= self
.pexpect_spawn("telnet " + ip
+ " -l '" + username
+ "'")
689 i
= child
.expect(["Welcome to Microsoft Telnet Service",
690 "Denying new connections due to the limit on number of connections",
691 "No more connections are allowed to telnet server",
692 "Unable to connect to remote host",
694 "Connection refused",
700 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
702 child
.expect("password:")
703 child
.sendline(password
)
704 i
= child
.expect(["C:",
706 "Denying new connections due to the limit on number of connections",
707 "No more connections are allowed to telnet server",
708 "Unable to connect to remote host",
710 "Connection refused",
713 if set_telnetclients
:
714 self
.run_cmd('bin/net rpc group addmem TelnetClients "authenticated users" -S $WIN_IP -U$WIN_USER%$WIN_PASS')
717 set_telnetclients
= False
718 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
721 raise RuntimeError("Failed to connect with telnet due to missing TelnetClients membership")
727 self
.info("retrying (retries=%u delay=%u)" % (retries
, delay
))
731 if self
.set_dns(child
):
734 child
.sendline('route add 0.0.0.0 mask 0.0.0.0 ${WIN_DEFAULT_GATEWAY}')
738 self
.run_date_time(child
, None)
741 self
.run_tlntadmn(child
)
744 self
.set_noexpire(child
, username
)
747 self
.disable_firewall(child
)
748 disable_firewall
= False
751 if self
.set_ip(child
):
756 raise RuntimeError("Failed to connect with telnet")
758 def kinit(self
, username
, password
):
759 '''use kinit to setup a credentials cache'''
760 self
.run_cmd("kdestroy")
761 self
.putenv('KRB5CCNAME', "${PREFIX}/ccache.test")
762 username
= self
.substitute(username
)
763 s
= username
.split('@')
766 username
= '@'.join(s
)
767 child
= self
.pexpect_spawn('kinit ' + username
)
768 child
.expect("Password")
769 child
.sendline(password
)
770 child
.expect(pexpect
.EOF
)
772 if child
.exitstatus
!= 0:
773 raise RuntimeError("kinit failed with status %d" % child
.exitstatus
)
775 def get_domains(self
):
776 '''return a dictionary of DNS domains and IPs for named.conf'''
779 if v
[-6:] == "_REALM":
781 if base
+ '_IP' in self
.vars:
782 ret
[self
.vars[base
+ '_REALM']] = self
.vars[base
+ '_IP']
785 def wait_reboot(self
, retries
=3):
786 '''wait for a VM to reboot'''
788 # first wait for it to shutdown
789 self
.port_wait("${WIN_IP}", 139, wait_for_fail
=True, delay
=6)
791 # now wait for it to come back. If it fails to come back
792 # then try resetting it
795 self
.port_wait("${WIN_IP}", 139)
799 self
.vm_reset("${WIN_VM}")
800 self
.info("retrying reboot (retries=%u)" % retries
)
801 raise RuntimeError(self
.substitute("VM ${WIN_VM} failed to reboot"))
804 '''return a dictionary of all the configured VM names'''
808 ret
.append(self
.vars[v
])
812 def run_dcpromo_as_first_dc(self
, vm
, func_level
=None):
814 self
.info("Configuring a windows VM ${WIN_VM} at the first DC in the domain using dcpromo")
815 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_time
=True)
816 if self
.get_is_dc(child
):
819 if func_level
== '2008r2':
820 self
.setvar("FUNCTION_LEVEL_INT", str(4))
821 elif func_level
== '2003':
822 self
.setvar("FUNCTION_LEVEL_INT", str(1))
824 self
.setvar("FUNCTION_LEVEL_INT", str(0))
826 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}", set_ip
=True, set_noexpire
=True)
828 """This server must therefore not yet be a directory server, so we must promote it"""
829 child
.sendline("copy /Y con answers.txt")
832 ; New forest promotion
833 ReplicaOrNewDomain=Domain
835 NewDomainDNSName=${WIN_REALM}
836 ForestLevel=${FUNCTION_LEVEL_INT}
837 DomainNetbiosName=${WIN_DOMAIN}
838 DomainLevel=${FUNCTION_LEVEL_INT}
841 CreateDNSDelegation=No
842 DatabasePath="C:\Windows\NTDS"
843 LogPath="C:\Windows\NTDS"
844 SYSVOLPath="C:\Windows\SYSVOL"
845 ; Set SafeModeAdminPassword to the correct value prior to using the unattend file
846 SafeModeAdminPassword=${WIN_PASS}
847 ; Run-time flags (optional)
848 RebootOnCompletion=No
851 child
.expect("copied.")
854 child
.sendline("dcpromo /answer:answers.txt")
855 i
= child
.expect(["You must restart this computer", "failed", "Active Directory Domain Services was not installed", "C:", pexpect
.TIMEOUT
], timeout
=240)
857 raise Exception("dcpromo failed")
859 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}")
861 child
.sendline("shutdown -r -t 0")
862 self
.port_wait("${WIN_IP}", 139, wait_for_fail
=True)
863 self
.port_wait("${WIN_IP}", 139)
865 child
= self
.open_telnet("${WIN_HOSTNAME}", "administrator", "${WIN_PASS}")
866 # Check if we became a DC by now
867 if not self
.get_is_dc(child
):
868 raise Exception("dcpromo failed (and wasn't a DC even after rebooting)")
869 # Give DNS registration a kick
870 child
.sendline("ipconfig /registerdns")
872 self
.retry_cmd("host -t SRV _ldap._tcp.${WIN_REALM} ${WIN_IP}", ['has SRV record'], retries
=60, delay
=5 )
875 def start_winvm(self
, vm
):
876 '''start a Windows VM'''
879 self
.info("Joining a windows box to the domain")
880 self
.vm_poweroff("${WIN_VM}", checkfail
=False)
881 self
.vm_restore("${WIN_VM}", "${WIN_SNAPSHOT}")
883 def run_winjoin(self
, vm
, domain
, username
="administrator", password
="${PASSWORD1}"):
884 '''join a windows box to a domain'''
885 child
= self
.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time
=True, set_ip
=True, set_noexpire
=True)
888 child
.sendline("ipconfig /flushdns")
890 child
.sendline("netdom join ${WIN_HOSTNAME} /Domain:%s /UserD:%s /PasswordD:%s" % (domain
, username
, password
))
891 i
= child
.expect(["The command completed successfully",
892 "The specified domain either does not exist or could not be contacted."], timeout
=120)
899 child
.sendline("shutdown /r -t 0")
901 child
= self
.open_telnet("${WIN_HOSTNAME}", "${WIN_USER}", "${WIN_PASS}", set_time
=True, set_ip
=True)
902 child
.sendline("ipconfig /registerdns")
903 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")
907 def test_remote_smbclient(self
, vm
, username
="${WIN_USER}", password
="${WIN_PASS}", args
=""):
908 '''test smbclient against remote server'''
910 self
.info('Testing smbclient')
911 self
.chdir('${PREFIX}')
912 smbclient
= self
.getvar("smbclient")
913 self
.cmd_contains("%s --version" % (smbclient
), ["${SAMBA_VERSION}"])
914 self
.retry_cmd('%s -L ${WIN_HOSTNAME} -U%s%%%s %s' % (smbclient
, username
, password
, args
), ["IPC"], retries
=60, delay
=5)
916 def test_net_use(self
, vm
, realm
, domain
, username
, password
):
918 self
.info('Testing net use against Samba3 member')
919 child
= self
.open_telnet("${WIN_HOSTNAME}", "%s\\%s" % (domain
, username
), password
)
920 child
.sendline("net use t: \\\\${HOSTNAME}.%s\\test" % realm
)
921 child
.expect("The command completed successfully")
924 def setup(self
, testname
, subdir
):
925 '''setup for main tests, parsing command line'''
926 self
.parser
.add_option("--conf", type='string', default
='', help='config file')
927 self
.parser
.add_option("--skip", type='string', default
='', help='list of steps to skip (comma separated)')
928 self
.parser
.add_option("--vms", type='string', default
=None, help='list of VMs to use (comma separated)')
929 self
.parser
.add_option("--list", action
='store_true', default
=False, help='list the available steps')
930 self
.parser
.add_option("--rebase", action
='store_true', default
=False, help='do a git pull --rebase')
931 self
.parser
.add_option("--clean", action
='store_true', default
=False, help='clean the tree')
932 self
.parser
.add_option("--prefix", type='string', default
=None, help='override install prefix')
933 self
.parser
.add_option("--sourcetree", type='string', default
=None, help='override sourcetree location')
934 self
.parser
.add_option("--nocleanup", action
='store_true', default
=False, help='disable cleanup code')
935 self
.parser
.add_option("--use-ntvfs", action
='store_true', default
=False, help='use NTVFS for the fileserver')
936 self
.parser
.add_option("--dns-backend", type="choice",
937 choices
=["SAMBA_INTERNAL", "BIND9_FLATFILE", "BIND9_DLZ", "NONE"],
938 help="The DNS server backend. SAMBA_INTERNAL is the builtin name server (default), " \
939 "BIND9_FLATFILE uses bind9 text database to store zone information, " \
940 "BIND9_DLZ uses samba4 AD to store zone information, " \
941 "NONE skips the DNS setup entirely (not recommended)",
942 default
="SAMBA_INTERNAL")
944 self
.opts
, self
.args
= self
.parser
.parse_args()
946 if not self
.opts
.conf
:
947 print("Please specify a config file with --conf")
950 # we don't need fsync safety in these tests
951 self
.putenv('TDB_NO_FSYNC', '1')
953 self
.load_config(self
.opts
.conf
)
955 nameserver
= self
.get_nameserver()
956 if nameserver
== self
.getvar('NAMED_INTERFACE_IP'):
957 raise RuntimeError("old /etc/resolv.conf must not contain %s as a nameserver, this will create loops with the generated dns configuration" % nameserver
)
958 self
.setvar('DNSSERVER', nameserver
)
960 self
.set_skip(self
.opts
.skip
)
961 self
.set_vms(self
.opts
.vms
)
964 self
.list_steps_mode()
967 self
.setvar('PREFIX', self
.opts
.prefix
)
969 if self
.opts
.sourcetree
:
970 self
.setvar('SOURCETREE', self
.opts
.sourcetree
)
973 self
.info('rebasing')
974 self
.chdir('${SOURCETREE}')
975 self
.run_cmd('git pull --rebase')
978 self
.info('cleaning')
979 self
.chdir('${SOURCETREE}/' + subdir
)
980 self
.run_cmd('make clean')
982 if self
.opts
.use_ntvfs
:
983 self
.setvar('USE_NTVFS', "--use-ntvfs")
985 self
.setvar('USE_NTVFS', "")
987 self
.setvar('NAMESERVER_BACKEND', self
.opts
.dns_backend
)
989 self
.setvar('DNS_FORWARDER', "--option=dns forwarder=%s" % nameserver
)