diff --git a/ChangeLog b/ChangeLog index 852b0eeda8a73ff59d8137b1f953112e63da57be..89f87d94ce8418f7aaea135a04ef23849a0ad15f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2014-09-12, v0.7rc2 + * Support all QEMU supported disk image formats as input media + * Detect if a Windows input media is sysprepped + * Support VirtIO driver installation in Windows + * Do a major code cleanup + * Fix bugs + 2014-06-10, v0.6.2 * Add support for Ubuntu 14.04 * Fix a bug in Windows image creation diff --git a/image_creator/dialog_menu.py b/image_creator/dialog_menu.py index 1322cb2933b978f5e46a822c704b37afa4fe6a75..0731cb4700cf7ec50d566ae21e0efb073ecab918 100644 --- a/image_creator/dialog_menu.py +++ b/image_creator/dialog_menu.py @@ -704,11 +704,12 @@ def virtio(session): (code, choice) = d.menu( "In this menu you can see details about the installed VirtIO " - "drivers on the input media. Press <OK> to see more information " + "drivers on the input media. Press <Info> to see more information " "about a specific installed driver or <Update> to install one or " "more new drivers.", height=16, width=WIDTH, choices=choices, - menu_height=len(choices), cancel="Back", title="VirtIO Drivers", - extra_button=1, extra_label="Update", default_item=default_item) + ok_label="Info", menu_height=len(choices), cancel="Back", + title="VirtIO Drivers", extra_button=1, extra_label="Update", + default_item=default_item) if code in (d.DIALOG_CANCEL, d.DIALOG_ESC): return True @@ -832,13 +833,12 @@ def sysprep(session): sysprep_help = "%s\n%s\n\n" % (help_title, '=' * len(help_title)) for task in syspreps: - name, descr = image.os.sysprep_info(task) - display_name = name.replace('-', ' ').capitalize() - sysprep_help += "%s\n" % display_name - sysprep_help += "%s\n" % ('-' * len(display_name)) + name, descr, display = image.os.sysprep_info(task) + sysprep_help += "%s\n" % display + sysprep_help += "%s\n" % ('-' * len(display)) sysprep_help += "%s\n\n" % wrapper.fill(" ".join(descr.split())) enabled = 1 if image.os.sysprep_enabled(task) else 0 - choices.append((str(index + 1), display_name, enabled)) + choices.append((str(index + 1), display, enabled)) index += 1 (code, tags) = d.checklist( diff --git a/image_creator/disk.py b/image_creator/disk.py index 0b02d0504006dcb36afa4f064536dc57452ba015..0f1fdc46bd616d0a52a8ca2a317ec8a61bb9d514 100644 --- a/image_creator/disk.py +++ b/image_creator/disk.py @@ -156,10 +156,13 @@ class Disk(object): self.out.warn("Snapshotting ignored for host bundling mode.") return self.file + # Examine media file + mode = os.stat(self.file).st_mode + self.out.output("Snapshotting media source ...", False) # Create a qcow2 snapshot for image files - if not stat.S_ISBLK(os.stat(self.file).st_mode): + if not stat.S_ISBLK(mode): snapshot = create_snapshot(self.file, self.tmp) self._add_cleanup(os.unlink, snapshot) self.out.success('done') diff --git a/image_creator/os_type/__init__.py b/image_creator/os_type/__init__.py index fa2fc92716676008cc2b51efcaaa773b14b4436d..2af3d044edcebf596b98a9c02c567f09c5980cdc 100644 --- a/image_creator/os_type/__init__.py +++ b/image_creator/os_type/__init__.py @@ -292,12 +292,14 @@ class OSBase(object): """Returns information about a sysprep object""" assert hasattr(obj, '_sysprep'), "Object is not a sysprep" - SysprepInfo = namedtuple("SysprepInfo", "name description") + SysprepInfo = namedtuple("SysprepInfo", "name description display") name = obj.__name__.replace('_', '-')[1:] description = textwrap.dedent(obj.__doc__) + display = getattr(obj, '_sysprep_display', + name.replace('-', ' ').capitalize()) - return SysprepInfo(name, description) + return SysprepInfo(name, description, display) def get_sysprep_by_name(self, name): """Returns the sysprep object with the given name""" diff --git a/image_creator/os_type/windows/__init__.py b/image_creator/os_type/windows/__init__.py index 002260e4700b7f98ed4b5a286436ed663e31d95e..f1ab16919b7cc49f142ba75ce6a674add58d84f3 100644 --- a/image_creator/os_type/windows/__init__.py +++ b/image_creator/os_type/windows/__init__.py @@ -240,7 +240,7 @@ class Windows(OSBase): @add_sysprep_param('virtio', 'dir', "", DESCR['virtio'], check=virtio_dir_check, hidden=True) @add_sysprep_param( - 'virtio_timeout', 'posint', 300, DESCR['virtio_timeout']) + 'virtio_timeout', 'posint', 900, DESCR['virtio_timeout']) def __init__(self, image, **kargs): super(Windows, self).__init__(image, **kargs) @@ -313,20 +313,21 @@ class Windows(OSBase): self.image.device, self.sysprep_params, namedtuple('User', 'rid name')(admin, self.usernames[admin])) - @sysprep('Disabling IPv6 privacy extensions') + @sysprep('Disabling IPv6 privacy extensions', + display="Disable IPv6 privacy extensions") def _disable_ipv6_privacy_extensions(self): """Disable IPv6 privacy extensions""" self.vm.rexec('netsh interface ipv6 set global ' 'randomizeidentifiers=disabled store=persistent') - @sysprep('Disabling Teredo interface') + @sysprep('Disabling Teredo interface', display="Disable Teredo") def _disable_teredo(self): """Disable Teredo interface""" self.vm.rexec('netsh interface teredo set state disabled') - @sysprep('Disabling ISATAP Adapters') + @sysprep('Disabling ISATAP Adapters', display="Disable ISATAP") def _disable_isatap(self): """Disable ISATAP Adapters""" @@ -338,7 +339,7 @@ class Windows(OSBase): self.vm.rexec('netsh firewall set icmpsetting 8') - @sysprep('Setting the system clock to UTC') + @sysprep('Setting the system clock to UTC', display="UTC") def _utc(self): """Set the hardware clock to UTC""" @@ -354,7 +355,8 @@ class Windows(OSBase): "cmd /q /c for /f \"tokens=*\" %l in ('wevtutil el') do " "wevtutil cl \"%l\"") - @sysprep('Executing Sysprep on the image (may take more that 10 min)') + @sysprep('Executing Sysprep on the image (may take more that 10 min)', + display="Microsoft Sysprep") def _microsoft_sysprep(self): """Run the Microsoft System Preparation Tool. This will remove system-specific data and will make the image ready to be deployed. @@ -365,7 +367,8 @@ class Windows(OSBase): r'/quiet /generalize /oobe /shutdown', uninstall=True) self.sysprepped = True - @sysprep('Converting the image into a KMS client', enabled=False) + @sysprep('Converting the image into a KMS client', enabled=False, + display="KMS client setup") def _kms_client_setup(self): """Install the appropriate KMS client setup key to the image to convert it to a KMS client. Computers that are running volume licensing @@ -701,7 +704,7 @@ class Windows(OSBase): """Check if winexe works on the Windows VM""" retries = self.sysprep_params['connection_retries'].value - timeout = [2] + timeout = [5] for i in xrange(1, retries - 1): timeout.insert(0, timeout[0] * 2) @@ -724,8 +727,9 @@ class Windows(OSBase): log.close() self.out.output("failed! See: `%s' for the full output" % log.name) if i < retries - 1: - self.out.output("retrying ...", False) - time.sleep(timeout.pop()) + wait = timeout.pop() + self.out.output("retrying in %d seconds ..." % wait, False) + time.sleep(wait) raise FatalError("Connection to the Windows VM failed after %d retries" % retries) diff --git a/image_creator/os_type/windows/vm.py b/image_creator/os_type/windows/vm.py index 766e570a44abb86f00f94fa6e6b8c6f546d2bade..4501166ca963c20dc160791bb5c284e42e326f5b 100644 --- a/image_creator/os_type/windows/vm.py +++ b/image_creator/os_type/windows/vm.py @@ -23,6 +23,7 @@ import signal import tempfile import os import time +import errno from string import lowercase, uppercase, digits from image_creator.util import FatalError, get_kvm_binary @@ -168,8 +169,9 @@ class VM(object): if self.serial is not None: try: os.unlink(self.serial) - except FileNotFoundError: - pass + except OSError as e: + if errno.errorcode[e.errno] != 'ENOENT': # File not found + raise def wait_on_serial(self, timeout): """Wait until the random token appears on the VM's serial port""" diff --git a/image_creator/util.py b/image_creator/util.py index dfae024be68350b58352b8c00e491ef9905e75d7..c38e5a4f943c4ed78ade70eec465ca04a1478198 100644 --- a/image_creator/util.py +++ b/image_creator/util.py @@ -25,9 +25,6 @@ import os import re import json import tempfile -from sh import qemu_img -from sh import qemu_nbd -from sh import modprobe class FatalError(Exception): @@ -35,8 +32,25 @@ class FatalError(Exception): pass +def get_command(command): + """Return a file system binary command""" + def find_sbin_command(command, exception): + search_paths = ['/usr/local/sbin', '/usr/sbin', '/sbin'] + for fullpath in map(lambda x: "%s/%s" % (x, command), search_paths): + if os.path.exists(fullpath) and os.access(fullpath, os.X_OK): + return sh.Command(fullpath) + raise exception + + try: + return sh.__getattr__(command) + except sh.CommandNotFound as e: + return find_sbin_command(command, e) + + def image_info(image): """Returns information about an image file""" + + qemu_img = get_command('qemu-img') info = qemu_img('info', '--output', 'json', image) return json.loads(str(info)) @@ -44,6 +58,7 @@ def image_info(image): def create_snapshot(source, target_dir): """Returns a qcow2 snapshot of an image file""" + qemu_img = get_command('qemu-img') snapfd, snap = tempfile.mkstemp(prefix='snapshot-', dir=target_dir) os.close(snapfd) qemu_img('create', '-f', 'qcow2', '-o', @@ -51,21 +66,6 @@ def create_snapshot(source, target_dir): return snap -def get_command(command): - """Return a file system binary command""" - def find_sbin_command(command, exception): - search_paths = ['/usr/local/sbin', '/usr/sbin', '/sbin'] - for fullpath in map(lambda x: "%s/%s" % (x, command), search_paths): - if os.path.exists(fullpath) and os.access(fullpath, os.X_OK): - return sh.Command(fullpath) - raise exception - - try: - return sh.__getattr__(command) - except sh.CommandNotFound as e: - return find_sbin_command(command, e) - - def get_kvm_binary(): """Returns the path to the kvm binary and some extra arguments if needed""" @@ -131,6 +131,8 @@ class QemuNBD(object): self.image = image self.device = None self.pattern = re.compile('^nbd\d+$') + self.modprobe = get_command('modprobe') + self.qemu_nbd = get_command('qemu-nbd') def _list_devices(self): """Returns all the NBD block devices""" @@ -141,7 +143,7 @@ class QemuNBD(object): devs = self._list_devices() if len(devs) == 0: # Is nbd module loaded? - modprobe('nbd', 'max_part=16') + self.modprobe('nbd', 'max_part=16') # Wait a second for /dev to be populated time.sleep(1) devs = self._list_devices() @@ -166,7 +168,7 @@ class QemuNBD(object): args.append('-r') args.append(self.image) - qemu_nbd(*args) + self.qemu_nbd(*args) self.device = device return device @@ -174,7 +176,7 @@ class QemuNBD(object): """Disconnect the image from the connected device""" assert self.device is not None, "No device connected" - qemu_nbd('-d', self.device) + self.qemu_nbd('-d', self.device) self.device = None # vim: set sta sts=4 shiftwidth=4 sw=4 et ai : diff --git a/image_creator/version.py b/image_creator/version.py index 32e9e76dad5a3b74c7d435d02dc16455c30f0afb..c3fc276ffd32ea11b0ab818a049feb6874b81a4b 100644 --- a/image_creator/version.py +++ b/image_creator/version.py @@ -1,8 +1,8 @@ -__version__ = "0.7rc1" +__version__ = "0.7rc2" __version_vcs_info__ = { 'branch': 'release-0.7', - 'revid': '96d50cb', - 'revno': 555} + 'revid': '9813162', + 'revno': 564} __version_user_email__ = "skalkoto@grnet.gr" __version_user_name__ = "Nikos Skalkotos" diff --git a/version b/version index 4e27a3c163016ce02f4e739a9b71918cdc55ad1d..22e9674151c70675fea07f7ed4dbed62fabfdc4a 100644 --- a/version +++ b/version @@ -1 +1 @@ -0.7rc1 +0.7rc2