Revision 8bae613f 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