Use forceall in e2fsck if available
[snf-image-creator] / image_creator / os_type / windows.py
index 58d98c8..96efdab 100644 (file)
@@ -37,7 +37,7 @@
 Windows OSs."""
 
 from image_creator.os_type import OSBase, sysprep, add_sysprep_param
 Windows OSs."""
 
 from image_creator.os_type import OSBase, sysprep, add_sysprep_param
-from image_creator.util import FatalError, check_guestfs_version
+from image_creator.util import FatalError, get_kvm_binary
 from image_creator.winexe import WinEXE, WinexeTimeout
 
 import hivex
 from image_creator.winexe import WinEXE, WinexeTimeout
 
 import hivex
@@ -50,10 +50,6 @@ import string
 import subprocess
 import struct
 
 import subprocess
 import struct
 
-BOOT_TIMEOUT = 300
-SHUTDOWN_TIMEOUT = 120
-CONNECTION_RETRIES = 5
-
 # For more info see: http://technet.microsoft.com/en-us/library/jj612867.aspx
 KMS_CLIENT_SETUP_KEYS = {
     "Windows 8 Professional": "NG4HW-VH26C-733KW-K6F98-J8CK4",
 # For more info see: http://technet.microsoft.com/en-us/library/jj612867.aspx
 KMS_CLIENT_SETUP_KEYS = {
     "Windows 8 Professional": "NG4HW-VH26C-733KW-K6F98-J8CK4",
@@ -104,29 +100,52 @@ KMS_CLIENT_SETUP_KEYS = {
     "Windows Server 2008 for Itanium-Based Systems":
     "4DWFP-JF3DJ-B7DTH-78FJB-PDRHK"}
 
     "Windows Server 2008 for Itanium-Based Systems":
     "4DWFP-JF3DJ-B7DTH-78FJB-PDRHK"}
 
+_POSINT = lambda x: type(x) == int and x >= 0
+
 
 class Windows(OSBase):
     """OS class for Windows"""
 
 class Windows(OSBase):
     """OS class for Windows"""
-
+    @add_sysprep_param(
+        'shutdown_timeout', int, 120, "Shutdown Timeout (seconds)", _POSINT)
+    @add_sysprep_param(
+        'boot_timeout', int, 300, "Boot Timeout (seconds)", _POSINT)
+    @add_sysprep_param(
+        'connection_retries', int, 5, "Connection Retries", _POSINT)
     @add_sysprep_param('password', str, None, 'Image Administrator Password')
     def __init__(self, image, **kargs):
         super(Windows, self).__init__(image, **kargs)
 
     @add_sysprep_param('password', str, None, 'Image Administrator Password')
     def __init__(self, image, **kargs):
         super(Windows, self).__init__(image, **kargs)
 
-        device = self.g.part_to_dev(self.root)
-
-        self.last_part_num = self.g.part_list(device)[-1]['part_num']
+        # The commit with the following message was added in
+        # libguestfs 1.17.18 and was backported in version 1.16.11:
+        #
+        # When a Windows guest doesn't have a HKLM\SYSTEM\MountedDevices node,
+        # inspection fails.  However inspection should not completely fail just
+        # because we cannot get the drive letter mapping from a guest.
+        #
+        # Since Microsoft Sysprep removes the aforementioned key, image
+        # creation for windows can only be supported if the installed guestfs
+        # version is 1.17.18 or higher
+        if self.image.check_guestfs_version(1, 17, 18) < 0 and \
+                (self.image.check_guestfs_version(1, 17, 0) >= 0 or
+                 self.image.check_guestfs_version(1, 16, 11) < 0):
+            raise FatalError(
+                'For windows support libguestfs 1.16.11 or above is required')
+
+        device = self.image.g.part_to_dev(self.root)
+
+        self.last_part_num = self.image.g.part_list(device)[-1]['part_num']
         self.last_drive = None
         self.system_drive = None
 
         self.last_drive = None
         self.system_drive = None
 
-        for drive, partition in self.g.inspect_get_drive_mappings(self.root):
-            if partition == "%s%d" % (device, self.last_part_num):
+        for drive, part in self.image.g.inspect_get_drive_mappings(self.root):
+            if part == "%s%d" % (device, self.last_part_num):
                 self.last_drive = drive
                 self.last_drive = drive
-            if partition == self.root:
+            if part == self.root:
                 self.system_drive = drive
 
         assert self.system_drive
 
                 self.system_drive = drive
 
         assert self.system_drive
 
-        self.product_name = self.g.inspect_get_product_name(self.root)
+        self.product_name = self.image.g.inspect_get_product_name(self.root)
         self.syspreped = False
 
     @sysprep('Disabling IPv6 privacy extensions')
         self.syspreped = False
 
     @sysprep('Disabling IPv6 privacy extensions')
@@ -191,7 +210,7 @@ class Windows(OSBase):
         """Install the appropriate KMS client setup key to the image to convert
         it to a KMS client. Computers that are running volume licensing
         editions of Windows 8, Windows Server 2012, Windows 7, Windows Server
         """Install the appropriate KMS client setup key to the image to convert
         it to a KMS client. Computers that are running volume licensing
         editions of Windows 8, Windows Server 2012, Windows 7, Windows Server
-        2008 R2, Windows Vista, and Windows Server 2008 are, by default, KMS
+        2008 R2, Windows Vista, and Windows Server 2008 are by default KMS
         clients with no additional configuration needed.
         """
         try:
         clients with no additional configuration needed.
         """
         try:
@@ -261,8 +280,12 @@ class Windows(OSBase):
             r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
             r'DEL /Q %SCRIPT%"')
 
             r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' +
             r'DEL /Q %SCRIPT%"')
 
-        stdout, stderr, rc = self._guest_exec(cmd)
+        stdout, stderr, rc = self._guest_exec(cmd, False)
 
 
+        if rc != 0:
+            FatalError("Shrinking failed. Please make sure the media is "
+                       "defraged with a command like this: "
+                       "`Defrag.exe /U /X /W'")
         for line in stdout.splitlines():
             if line.find('shrunk') >= 0:
                 self.out.output(line)
         for line in stdout.splitlines():
             if line.find('shrunk') >= 0:
                 self.out.output(line)
@@ -276,7 +299,7 @@ class Windows(OSBase):
         txt = "System preparation parameter: `%s' is needed but missing!"
         for name, param in self.needed_sysprep_params.items():
             if name not in self.sysprep_params:
         txt = "System preparation parameter: `%s' is needed but missing!"
         for name, param in self.needed_sysprep_params.items():
             if name not in self.sysprep_params:
-                raise FatalError(txt % param)
+                raise FatalError(txt % name)
 
         self.mount(readonly=False)
         try:
 
         self.mount(readonly=False)
         try:
@@ -287,23 +310,17 @@ class Windows(OSBase):
             firewall_states = self._update_firewalls(0, 0, 0)
 
             # Delete the pagefile. It will be recreated when the system boots
             firewall_states = self._update_firewalls(0, 0, 0)
 
             # Delete the pagefile. It will be recreated when the system boots
-            systemroot = self.g.inspect_get_windows_systemroot(self.root)
-            pagefile = "%s/pagefile.sys" % systemroot
-            self.g.rm_rf(self.g.case_sensitive_path(pagefile))
+            systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
+            try:
+                pagefile = "%s/pagefile.sys" % systemroot
+                self.image.g.rm_rf(self.image.g.case_sensitive_path(pagefile))
+            except RuntimeError:
+                pass
 
         finally:
             self.umount()
 
 
         finally:
             self.umount()
 
-        self.out.output("Shutting down helper VM ...", False)
-        self.g.sync()
-        # guestfs_shutdown which is the prefered way to shutdown the backend
-        # process was introduced in version 1.19.16
-        if check_guestfs_version(self.g, 1, 19, 16) >= 0:
-            self.g.shutdown()
-        else:
-            self.g.kill_subprocess()
-
-        self.out.success('done')
+        self.image.disable_guestfs()
 
         vm = None
         monitor = None
 
         vm = None
         monitor = None
@@ -311,7 +328,7 @@ class Windows(OSBase):
             self.out.output("Starting windows VM ...", False)
             monitorfd, monitor = tempfile.mkstemp()
             os.close(monitorfd)
             self.out.output("Starting windows VM ...", False)
             monitorfd, monitor = tempfile.mkstemp()
             os.close(monitorfd)
-            vm = _VM(self.image.device, monitor)
+            vm = _VM(self.image.device, monitor, self.sysprep_params)
             self.out.success("started (console on vnc display: %d)." %
                              vm.display)
 
             self.out.success("started (console on vnc display: %d)." %
                              vm.display)
 
@@ -362,7 +379,7 @@ class Windows(OSBase):
             self.out.success("done")
 
             self.out.output("Waiting for windows to shut down ...", False)
             self.out.success("done")
 
             self.out.output("Waiting for windows to shut down ...", False)
-            vm.wait(SHUTDOWN_TIMEOUT)
+            vm.wait(self.sysprep_params['shutdown_timeout'])
             self.out.success("done")
         finally:
             if monitor is not None:
             self.out.success("done")
         finally:
             if monitor is not None:
@@ -374,10 +391,7 @@ class Windows(OSBase):
                     vm.destroy()
                     self.out.success("done")
             finally:
                     vm.destroy()
                     self.out.success("done")
             finally:
-                self.out.output("Relaunching helper VM (may take a while) ...",
-                                False)
-                self.g.launch()
-                self.out.success('done')
+                self.image.enable_guestfs()
 
                 self.mount(readonly=False)
                 try:
 
                 self.mount(readonly=False)
                 try:
@@ -395,7 +409,7 @@ class Windows(OSBase):
     def _wait_vm_boot(self, vm, fname, msg):
         """Wait until a message appears on a file or the vm process dies"""
 
     def _wait_vm_boot(self, vm, fname, msg):
         """Wait until a message appears on a file or the vm process dies"""
 
-        for _ in range(BOOT_TIMEOUT):
+        for _ in range(self.sysprep_params['boot_timeout']):
             time.sleep(1)
             with open(fname) as f:
                 for line in f:
             time.sleep(1)
             with open(fname) as f:
                 for line in f:
@@ -419,10 +433,10 @@ class Windows(OSBase):
     def _registry_file_path(self, regfile):
         """Retrieves the case sensitive path to a registry file"""
 
     def _registry_file_path(self, regfile):
         """Retrieves the case sensitive path to a registry file"""
 
-        systemroot = self.g.inspect_get_windows_systemroot(self.root)
+        systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
         path = "%s/system32/config/%s" % (systemroot, regfile)
         try:
         path = "%s/system32/config/%s" % (systemroot, regfile)
         try:
-            path = self.g.case_sensitive_path(path)
+            path = self.image.g.case_sensitive_path(path)
         except RuntimeError as error:
             raise FatalError("Unable to retrieve registry file: %s. Reason: %s"
                              % (regfile, str(error)))
         except RuntimeError as error:
             raise FatalError("Unable to retrieve registry file: %s. Reason: %s"
                              % (regfile, str(error)))
@@ -439,7 +453,7 @@ class Windows(OSBase):
         softwarefd, software = tempfile.mkstemp()
         try:
             os.close(softwarefd)
         softwarefd, software = tempfile.mkstemp()
         try:
             os.close(softwarefd)
-            self.g.download(path, software)
+            self.image.g.download(path, software)
 
             h = hivex.Hivex(software, write=True)
 
 
             h = hivex.Hivex(software, write=True)
 
@@ -504,7 +518,7 @@ class Windows(OSBase):
 
             h.commit(None)
 
 
             h.commit(None)
 
-            self.g.upload(software, path)
+            self.image.g.upload(software, path)
         finally:
             os.unlink(software)
 
         finally:
             os.unlink(software)
 
@@ -530,7 +544,7 @@ class Windows(OSBase):
         systemfd, system = tempfile.mkstemp()
         try:
             os.close(systemfd)
         systemfd, system = tempfile.mkstemp()
         try:
             os.close(systemfd)
-            self.g.download(path, system)
+            self.image.g.download(path, system)
 
             h = hivex.Hivex(system, write=True)
 
 
             h = hivex.Hivex(system, write=True)
 
@@ -562,7 +576,7 @@ class Windows(OSBase):
                            'value': struct.pack("<I", new_values.pop(0))})
 
             h.commit(None)
                            'value': struct.pack("<I", new_values.pop(0))})
 
             h.commit(None)
-            self.g.upload(system, path)
+            self.image.g.upload(system, path)
 
         finally:
             os.unlink(system)
 
         finally:
             os.unlink(system)
@@ -591,7 +605,7 @@ class Windows(OSBase):
         softwarefd, software = tempfile.mkstemp()
         try:
             os.close(softwarefd)
         softwarefd, software = tempfile.mkstemp()
         try:
             os.close(softwarefd)
-            self.g.download(path, software)
+            self.image.g.download(path, software)
 
             h = hivex.Hivex(software, write=True)
 
 
             h = hivex.Hivex(software, write=True)
 
@@ -618,7 +632,7 @@ class Windows(OSBase):
             h.node_set_value(key, new_value)
             h.commit(None)
 
             h.node_set_value(key, new_value)
             h.commit(None)
 
-            self.g.upload(software, path)
+            self.image.g.upload(software, path)
 
         finally:
             os.unlink(software)
 
         finally:
             os.unlink(software)
@@ -635,7 +649,7 @@ class Windows(OSBase):
         samfd, sam = tempfile.mkstemp()
         try:
             os.close(samfd)
         samfd, sam = tempfile.mkstemp()
         try:
             os.close(samfd)
-            self.g.download(self._registry_file_path('SAM'), sam)
+            self.image.g.download(self._registry_file_path('SAM'), sam)
 
             h = hivex.Hivex(sam)
 
 
             h = hivex.Hivex(sam)
 
@@ -685,11 +699,17 @@ class Windows(OSBase):
     def _check_connectivity(self):
         """Check if winexe works on the Windows VM"""
 
     def _check_connectivity(self):
         """Check if winexe works on the Windows VM"""
 
+        retries = self.sysprep_params['connection_retries']
+        # If the connection_retries parameter is set to 0 disable the
+        # connectivity check
+        if retries == 0:
+            return True
+
         passwd = self.sysprep_params['password']
         winexe = WinEXE('Administrator', passwd, 'localhost')
         winexe.uninstall().debug(9)
 
         passwd = self.sysprep_params['password']
         winexe = WinEXE('Administrator', passwd, 'localhost')
         winexe.uninstall().debug(9)
 
-        for i in range(CONNECTION_RETRIES):
+        for i in range(retries):
             (stdout, stderr, rc) = winexe.run('cmd /C')
             if rc == 0:
                 return True
             (stdout, stderr, rc) = winexe.run('cmd /C')
             if rc == 0:
                 return True
@@ -699,10 +719,11 @@ class Windows(OSBase):
             finally:
                 log.close()
             self.out.output("failed! See: `%s' for the full output" % log.name)
             finally:
                 log.close()
             self.out.output("failed! See: `%s' for the full output" % log.name)
-            if i < CONNECTION_RETRIES - 1:
-                self.out.output("Retrying ...", False)
-        raise FatalError("Connection to the VM failed after %d retries" %
-                         CONNECTION_RETRIES)
+            if i < retries - 1:
+                self.out.output("retrying ...", False)
+
+        raise FatalError("Connection to the Windows VM failed after %d retries"
+                         % retries)
 
     def _guest_exec(self, command, fatal=True):
         """Execute a command on a windows VM"""
 
     def _guest_exec(self, command, fatal=True):
         """Execute a command on a windows VM"""
@@ -729,7 +750,7 @@ class Windows(OSBase):
 
 class _VM(object):
     """Windows Virtual Machine"""
 
 class _VM(object):
     """Windows Virtual Machine"""
-    def __init__(self, disk, serial):
+    def __init__(self, disk, serial, params):
         """Create _VM instance
 
             disk: VM's hard disk
         """Create _VM instance
 
             disk: VM's hard disk
@@ -738,6 +759,7 @@ class _VM(object):
 
         self.disk = disk
         self.serial = serial
 
         self.disk = disk
         self.serial = serial
+        self.params = params
 
         def random_mac():
             """creates a random mac address"""
 
         def random_mac():
             """creates a random mac address"""
@@ -751,13 +773,21 @@ class _VM(object):
         # Use ganeti's VNC port range for a random vnc port
         self.display = random.randint(11000, 14999) - 5900
 
         # Use ganeti's VNC port range for a random vnc port
         self.display = random.randint(11000, 14999) - 5900
 
-        args = [
-            'kvm', '-smp', '1', '-m', '1024', '-drive',
+        kvm, needed_args = get_kvm_binary()
+
+        if kvm is None:
+            FatalError("Can't find the kvm binary")
+
+        args = [kvm]
+        args.extend(needed_args)
+
+        args.extend([
+            '-smp', '1', '-m', '1024', '-drive',
             'file=%s,format=raw,cache=unsafe,if=virtio' % self.disk,
             '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
             '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(),
             '-vnc', ':%d' % self.display, '-serial', 'file:%s' % self.serial,
             'file=%s,format=raw,cache=unsafe,if=virtio' % self.disk,
             '-netdev', 'type=user,hostfwd=tcp::445-:445,id=netdev0',
             '-device', 'virtio-net-pci,mac=%s,netdev=netdev0' % random_mac(),
             '-vnc', ':%d' % self.display, '-serial', 'file:%s' % self.serial,
-            '-monitor', 'stdio']
+            '-monitor', 'stdio'])
 
         self.process = subprocess.Popen(args, stdin=subprocess.PIPE,
                                         stdout=subprocess.PIPE)
 
         self.process = subprocess.Popen(args, stdin=subprocess.PIPE,
                                         stdout=subprocess.PIPE)
@@ -782,7 +812,7 @@ class _VM(object):
 
         signal.signal(signal.SIGALRM, handler)
 
 
         signal.signal(signal.SIGALRM, handler)
 
-        signal.alarm(SHUTDOWN_TIMEOUT)
+        signal.alarm(self.params['shutdown_timeout'])
         self.process.communicate(input="system_powerdown\n")
         signal.alarm(0)
 
         self.process.communicate(input="system_powerdown\n")
         signal.alarm(0)