Revision 0f089865 image_creator/os_type/windows.py
b/image_creator/os_type/windows.py | ||
---|---|---|
43 | 43 |
import hivex |
44 | 44 |
import tempfile |
45 | 45 |
import os |
46 |
import signal |
|
46 | 47 |
import time |
47 | 48 |
import random |
48 | 49 |
import string |
49 | 50 |
import subprocess |
50 | 51 |
import struct |
51 | 52 |
|
52 |
kvm = get_command('kvm') |
|
53 |
|
|
54 | 53 |
BOOT_TIMEOUT = 300 |
54 |
SHUTDOWN_TIMEOUT = 120 |
|
55 |
CONNECTION_RETRIES = 5 |
|
55 | 56 |
|
56 | 57 |
# For more info see: http://technet.microsoft.com/en-us/library/jj612867.aspx |
57 | 58 |
KMS_CLIENT_SETUP_KEYS = { |
... | ... | |
314 | 315 |
self.out.output("Starting windows VM ...", False) |
315 | 316 |
monitorfd, monitor = tempfile.mkstemp() |
316 | 317 |
os.close(monitorfd) |
317 |
vm, display = self._create_vm(monitor) |
|
318 |
self.out.success("started (console on vnc display: %d)." % display) |
|
318 |
vm = _VM(self.image.device, monitor) |
|
319 |
self.out.success("started (console on vnc display: %d)." % |
|
320 |
vm.display) |
|
319 | 321 |
|
320 | 322 |
self.out.output("Waiting for OS to boot ...", False) |
321 | 323 |
self._wait_vm_boot(vm, monitor, token) |
322 | 324 |
self.out.success('done') |
323 | 325 |
|
326 |
self.out.output("Checking connectivity to the VM ...", False) |
|
327 |
self._check_connectivity() |
|
328 |
self.out.success('done') |
|
329 |
|
|
324 | 330 |
self.out.output("Disabling automatic logon ...", False) |
325 | 331 |
self._disable_autologon() |
326 | 332 |
self.out.success('done') |
327 | 333 |
|
328 |
self.out.output('Preparing system from image creation:')
|
|
334 |
self.out.output('Preparing system for image creation:')
|
|
329 | 335 |
|
330 | 336 |
tasks = self.list_syspreps() |
331 | 337 |
enabled = filter(lambda x: x.enabled, tasks) |
... | ... | |
363 | 369 |
self.out.success("done") |
364 | 370 |
|
365 | 371 |
self.out.output("Waiting for windows to shut down ...", False) |
366 |
vm.wait() |
|
372 |
vm.wait(SHUTDOWN_TIMEOUT)
|
|
367 | 373 |
self.out.success("done") |
368 | 374 |
finally: |
369 | 375 |
if monitor is not None: |
370 | 376 |
os.unlink(monitor) |
371 | 377 |
|
372 |
if vm is not None: |
|
373 |
self._destroy_vm(vm) |
|
374 |
|
|
375 |
self.out.output("Relaunching helper VM (may take a while) ...", |
|
376 |
False) |
|
377 |
self.g.launch() |
|
378 |
self.out.success('done') |
|
379 |
|
|
380 |
self.mount(readonly=False) |
|
381 | 378 |
try: |
382 |
if disabled_uac:
|
|
383 |
self._update_uac_remote_setting(0)
|
|
384 |
|
|
385 |
self._update_firewalls(*firewall_states)
|
|
379 |
if vm is not None:
|
|
380 |
self.out.output("Destroying windows VM ...", False)
|
|
381 |
vm.destroy() |
|
382 |
self.out.success("done")
|
|
386 | 383 |
finally: |
387 |
self.umount() |
|
388 |
|
|
389 |
def _create_vm(self, monitor): |
|
390 |
"""Create a VM with the image attached as the disk |
|
391 |
|
|
392 |
monitor: a file to be used to monitor when the OS is up |
|
393 |
""" |
|
394 |
|
|
395 |
def random_mac(): |
|
396 |
mac = [0x00, 0x16, 0x3e, |
|
397 |
random.randint(0x00, 0x7f), |
|
398 |
random.randint(0x00, 0xff), |
|
399 |
random.randint(0x00, 0xff)] |
|
384 |
self.out.output("Relaunching helper VM (may take a while) ...", |
|
385 |
False) |
|
386 |
self.g.launch() |
|
387 |
self.out.success('done') |
|
400 | 388 |
|
401 |
return ':'.join(map(lambda x: "%02x" % x, mac)) |
|
402 |
|
|
403 |
# Use ganeti's VNC port range for a random vnc port |
|
404 |
vnc_port = random.randint(11000, 14999) |
|
405 |
display = vnc_port - 5900 |
|
406 |
|
|
407 |
vm = kvm( |
|
408 |
'-smp', '1', '-m', '1024', '-drive', |
|
409 |
'file=%s,format=raw,cache=unsafe,if=virtio' % self.image.device, |
|
410 |
'-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0', |
|
411 |
'-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(), |
|
412 |
'-vnc', ':%d' % display, '-serial', 'file:%s' % monitor, _bg=True) |
|
389 |
self.mount(readonly=False) |
|
390 |
try: |
|
391 |
if disabled_uac: |
|
392 |
self._update_uac_remote_setting(0) |
|
413 | 393 |
|
414 |
return vm, display |
|
415 |
|
|
416 |
def _destroy_vm(self, vm): |
|
417 |
"""Destroy a VM previously created by _create_vm""" |
|
418 |
if vm.process.alive: |
|
419 |
vm.terminate() |
|
394 |
self._update_firewalls(*firewall_states) |
|
395 |
finally: |
|
396 |
self.umount() |
|
420 | 397 |
|
421 | 398 |
def _shutdown(self): |
422 | 399 |
"""Shuts down the windows VM""" |
... | ... | |
431 | 408 |
for line in f: |
432 | 409 |
if line.startswith(msg): |
433 | 410 |
return True |
434 |
if not vm.process.alive:
|
|
411 |
if not vm.isalive():
|
|
435 | 412 |
raise FatalError("Windows VM died unexpectedly!") |
436 |
raise FatalError("Windows booting timed out!") |
|
413 |
|
|
414 |
raise FatalError("Windows VM booting timed out!") |
|
437 | 415 |
|
438 | 416 |
def _disable_autologon(self): |
439 | 417 |
"""Disable automatic logon on the windows image""" |
... | ... | |
682 | 660 |
# Filter out the guest account |
683 | 661 |
return filter(lambda x: x != "Guest", users) |
684 | 662 |
|
663 |
def _check_connectivity(self): |
|
664 |
"""Check if winexe works on the Windows VM""" |
|
665 |
|
|
666 |
passwd = self.sysprep_params['password'] |
|
667 |
winexe = WinEXE('Administrator', passwd, 'localhost') |
|
668 |
winexe.uninstall().debug(9) |
|
669 |
|
|
670 |
for i in range(CONNECTION_RETRIES): |
|
671 |
(stdout, stderr, rc) = winexe.run('cmd /C') |
|
672 |
if rc == 0: |
|
673 |
return True |
|
674 |
log = tempfile.NamedTemporaryFile(delete=False) |
|
675 |
try: |
|
676 |
log.file.write(stdout) |
|
677 |
finally: |
|
678 |
log.close() |
|
679 |
self.out.output("failed! See: `%' for the full output" % log.name) |
|
680 |
if i < CONNECTION_RETRIES - 1: |
|
681 |
self.out.output("Retrying ...", False) |
|
682 |
raise FatalError("Connection to the VM failed after %d retries" % |
|
683 |
CONNECTION_RETRIES) |
|
684 |
|
|
685 | 685 |
def _guest_exec(self, command, fatal=True): |
686 | 686 |
"""Execute a command on a windows VM""" |
687 | 687 |
|
... | ... | |
704 | 704 |
|
705 | 705 |
return (stdout, stderr, rc) |
706 | 706 |
|
707 |
|
|
708 |
class _VM(object): |
|
709 |
"""Windows Virtual Machine""" |
|
710 |
def __init__(self, disk, serial): |
|
711 |
"""Create _VM instance |
|
712 |
|
|
713 |
disk: VM's hard disk |
|
714 |
serial: File to save the output of the serial port |
|
715 |
""" |
|
716 |
|
|
717 |
self.disk = disk |
|
718 |
self.serial = serial |
|
719 |
|
|
720 |
def random_mac(): |
|
721 |
mac = [0x00, 0x16, 0x3e, |
|
722 |
random.randint(0x00, 0x7f), |
|
723 |
random.randint(0x00, 0xff), |
|
724 |
random.randint(0x00, 0xff)] |
|
725 |
|
|
726 |
return ':'.join(map(lambda x: "%02x" % x, mac)) |
|
727 |
|
|
728 |
# Use ganeti's VNC port range for a random vnc port |
|
729 |
self.display = random.randint(11000, 14999) - 5900 |
|
730 |
|
|
731 |
args = [ |
|
732 |
'kvm', '-smp', '1', '-m', '1024', '-drive', |
|
733 |
'file=%s,format=raw,cache=unsafe,if=virtio' % self.disk, |
|
734 |
'-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0', |
|
735 |
'-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(), |
|
736 |
'-vnc', ':%d' % self.display, '-serial', 'file:%s' % self.serial, |
|
737 |
'-monitor', 'stdio'] |
|
738 |
|
|
739 |
self.process = subprocess.Popen(args, stdin=subprocess.PIPE, |
|
740 |
stdout=subprocess.PIPE) |
|
741 |
|
|
742 |
def isalive(self): |
|
743 |
"""Check if the VM is still alive""" |
|
744 |
return self.process.poll() is None |
|
745 |
|
|
746 |
def destroy(self): |
|
747 |
"""Destroy the VM""" |
|
748 |
|
|
749 |
if not self.isalive(): |
|
750 |
return |
|
751 |
|
|
752 |
def handler(signum, frame): |
|
753 |
self.process.terminate() |
|
754 |
time.sleep(1) |
|
755 |
if self.isalive(): |
|
756 |
self.process.kill() |
|
757 |
self.process.wait() |
|
758 |
self.out.output("timed-out") |
|
759 |
raise FatalError("VM destroy timed-out") |
|
760 |
|
|
761 |
signal.signal(signal.SIGALRM, handler) |
|
762 |
|
|
763 |
signal.alarm(SHUTDOWN_TIMEOUT) |
|
764 |
self.process.communicate(input="system_powerdown\n") |
|
765 |
signal.alarm(0) |
|
766 |
|
|
767 |
def wait(self, timeout=0): |
|
768 |
"""Wait for the VM to terminate""" |
|
769 |
|
|
770 |
def handler(signum, frame): |
|
771 |
self.destroy() |
|
772 |
raise FatalError("VM wait timed-out.") |
|
773 |
|
|
774 |
signal.signal(signal.SIGALRM, handler) |
|
775 |
|
|
776 |
signal.alarm(timeout) |
|
777 |
stdout, stderr = self.process.communicate() |
|
778 |
signal.alarm(0) |
|
779 |
|
|
780 |
return (stdout, stderr, self.process.poll()) |
|
781 |
|
|
707 | 782 |
# vim: set sta sts=4 shiftwidth=4 sw=4 et ai : |
Also available in: Unified diff