예제 #1
0
    def _check_connectivity(self):
        """Check if winexe works on the Windows VM"""

        retries = self.sysprep_params['connection_retries'].value
        timeout = [5]
        for i in xrange(1, retries - 1):
            timeout.insert(0, timeout[0] * 2)

        # If the connection_retries parameter is set to 0 disable the
        # connectivity check
        if retries == 0:
            return True

        for i in xrange(retries):
            (stdout, stderr, rc) = self.vm.rexec('cmd /C',
                                                 fatal=False,
                                                 debug=True)
            if rc == 0:
                return True

            log = tempfile.NamedTemporaryFile(delete=False)
            try:
                log.file.write("STDOUT:\n%s\n" % stdout)
                log.file.write("STDERR:\n%s\n" % stderr)
            finally:
                log.close()
            self.out.info("failed! See: `%s' for the full output" % log.name)
            if i < retries - 1:
                wait = timeout.pop()
                self.out.info("retrying in %d seconds ..." % wait, False)
                time.sleep(wait)

        raise FatalError(
            "Connection to the Windows VM failed after %d retries" % retries)
예제 #2
0
    def umount(self, lazy=False):
        """umount the previously mounted image file system"""

        assert self.mount_local_support, \
            "MOUNT LOCAL not supported for this build of libguestfs"

        assert self._mount_thread is not None, "Image is not mounted"

        try:
            # Maybe the image was umounted externally
            if not self._mount_thread.is_alive():
                self._mount_thread = None
                return True

            try:
                args = (['-l'] if lazy else []) + [self._mount_thread.mpoint]
                get_command('umount')(*args)
            except:
                return False

            # Wait for a little while. If the image is umounted,
            # mount_local_run should have terminated
            self._mount_thread.join(5)

            if self._mount_thread.is_alive():
                raise FatalError('Unable to join the mount thread')

            self._mount_thread = None
            return True
        finally:
            self.os.umount()
예제 #3
0
    def _update_dword(self, keyname, valuename, data, default, valid):
        """Updates a registry key dword value to data.

            default: The default value is the key does not exist
            valid: a range of valid values

        Returns:
            The old value of the field
        """

        if data not in valid:
            raise ValueError("Valid values for parameter are %r" % (valid, ))

        hivename, keyname = keyname.split('/', 1)
        with self.open_hive(hivename, write=True) as hive:
            key = traverse(hive, keyname)

            value = None
            for v in hive.node_values(key):
                if hive.value_key(v) == valuename:
                    value = v
                    break

            old = default
            if value is not None:
                old = hive.value_dword(value)
            elif old is None:
                raise FatalError("Value: `%s' of key: `%s/%s' is missing." %
                                 (valuename, hivename, keyname))

            if old != data:
                hive.node_set_value(key, reg_dword(valuename, data))
                hive.commit(None)

        return old
예제 #4
0
    def get_setup_state(self):
        """Returns the stage of Windows Setup the image is in.
        The method will return an int with one of the following values:
            0 => IMAGE_STATE_COMPLETE
            1 => IMAGE_STATE_GENERALIZE_RESEAL_TO_OOBE
            2 => IMAGE_STATE_GENERALIZE_RESEAL_TO_AUDIT
            3 => IMAGE_STATE_SPECIALIZE_RESEAL_TO_OOBE
            4 => IMAGE_STATE_SPECIALIZE_RESEAL_TO_AUDIT
        For more information check here:
            http://technet.microsoft.com/en-us/library/hh824815.aspx
        """

        with self.open_hive('SOFTWARE') as hive:
            # Navigate to:
            #   SOFTWARE/Microsoft/Windows/CurrentVersion/Setup/State
            state = hive.root()
            for child in ('Microsoft', 'Windows', 'CurrentVersion', 'Setup',
                          'State'):
                state = hive.node_get_child(state, child)

            image_state = hive.node_get_value(state, 'ImageState')
            vtype, value = hive.value_value(image_state)
            assert vtype == 1L, \
                "ImageState field type (=%d) is not REG_SZ" % vtype

            value = value.decode('utf-16le')

            ret = 0
            for known_state in WINDOWS_SETUP_STATES:
                if value == known_state + '\x00':  # REG_SZ is null-terminated
                    return ret
                ret += 1

        raise FatalError("Unknown Windows Setup State: %s" % value)
예제 #5
0
    def _shrink(self):
        """Shrink the last file system. Please make sure the file system is
        defragged.
        """

        # Shrink the volume as much as possible and then give 100MB back.
        # From ntfsresize:
        # Practically the smallest shrunken size generally is at around
        # "used space" + (20-200 MB). Please also take into account that
        # Windows might need about 50-100 MB free space left to boot safely.
        cmd = (r'cmd /Q /V:ON /C "SET SCRIPT=%TEMP%\QUERYMAX_%RANDOM%.TXT & ' +
               r'ECHO SELECT DISK 0 > %SCRIPT% & ' +
               'ECHO SELECT PARTITION %d >> %%SCRIPT%% & ' % self.last_part_num
               + r'ECHO SHRINK NOERR >> %SCRIPT% & ' +
               r'ECHO EXTEND SIZE=100 NOERR >> %SCRIPT% & ' +
               r'ECHO EXIT >> %SCRIPT% & ' + r'DISKPART /S %SCRIPT% & ' +
               r'IF NOT !ERRORLEVEL! EQU 0 EXIT /B 1 & ' + r'DEL /Q %SCRIPT%"')

        stdout, _, rc = self.vm.rexec(cmd, fatal=False)

        if rc != 0:
            raise FatalError(
                "Shrinking failed. Please make sure the media is defragged.")

        for line in stdout.splitlines():
            line = line.strip()
            if not len(line):
                continue
            self.out.info(" %s" % line)

        self.shrinked = True
예제 #6
0
    def __init__(self, device, output, **kwargs):
        """Create a new Image instance"""

        self.device = device
        self.out = output
        self.format = kwargs['format'] if 'format' in kwargs else 'raw'

        self.meta = kwargs['meta'] if 'meta' in kwargs else {}
        self.sysprep_params = \
            kwargs['sysprep_params'] if 'sysprep_params' in kwargs else {}

        self.progress_bar = None
        self.guestfs_device = None
        self.size = 0

        self.g = guestfs.GuestFS()
        self.guestfs_enabled = False
        self.guestfs_version = self.g.version()

        # This is needed if the image format is not raw
        self.nbd = QemuNBD(device)

        if self.nbd.qemu_nbd is None and self.format != 'raw':
            raise FatalError("qemu-nbd command is missing, only raw input "
                             "media are supported")

        # Check If MOUNT LOCAL is supported for this guestfs build
        self.mount_local_support = hasattr(self.g, "mount_local")
        if self.mount_local_support:
            self._mount_thread = None
예제 #7
0
    def _last_partition(self):
        """Return the last partition of the image disk"""
        def is_extended(partition):
            """Returns True if the partition is an extended partition"""
            return self.g.part_get_mbr_id(self.guestfs_device,
                                          partition['part_num']) in (0x5, 0xf)

        def is_logical(partition):
            """Returns True if the partition is a logical partition"""
            return self.meta['PARTITION_TABLE'] == 'msdos' and \
                partition['part_num'] > 4

        if self.meta['PARTITION_TABLE'] not in 'msdos' 'gpt':
            msg = "Unsupported partition table: %s. Only msdos and gpt " \
                "partition tables are supported" % self.meta['PARTITION_TABLE']
            raise FatalError(msg)

        partitions = self.g.part_list(self.guestfs_device)
        last_partition = partitions[-1]

        if is_logical(last_partition):
            # The disk contains extended and logical partitions....
            extended = [p for p in partitions if is_extended(p)][0]
            last_primary = [p for p in partitions if p['part_num'] <= 4][-1]

            # check if extended is the last primary partition
            if last_primary['part_num'] > extended['part_num']:
                last_partition = last_primary

        return last_partition
예제 #8
0
 def handler(signum, frame):
     """Signal handler"""
     self.process.terminate()
     time.sleep(1)
     if self.isalive():
         self.process.kill()
     self.process.wait()
     if fatal:
         raise FatalError("Stopping the VM timed-out")
예제 #9
0
    def rexec(self, command, **kwargs):
        """Remote execute a command on the windows VM

        The following optional flags are allowed:

        * fatal: If True, a FatalError is thrown if the command fails

        * debug: If True, WinEXE is executed in the highest debug level

        * uninstall: If True, the winexesvc.exe service will be uninstalled
          after the execution of the command.
        """

        fatal = kwargs['fatal'] if 'fatal' in kwargs else True
        debug = kwargs['debug'] if 'debug' in kwargs else False
        uninstall = kwargs['uninstall'] if 'uninstall' in kwargs else False

        winexe = WinEXE(self.admin.name, 'localhost', password=self.password)
        winexe.no_pass()

        if debug:
            winexe.debug(9)

        if uninstall:
            winexe.uninstall()

        try:
            (stdout, stderr, rc) = winexe.run(command)
        except WinexeTimeout:
            raise FatalError("Command: `%s' timeout out." % command)

        if rc != 0 and fatal:
            log = tempfile.NamedTemporaryFile(delete=False)
            try:
                log.file.write("STDOUT:\n%s\n" % stdout)
                log.file.write("STDERR:\n%s\n" % stderr)
            finally:
                fname = log.name
                log.close()

            raise FatalError("Command: `%s' failed (rc=%d). See: %s" %
                             (command, rc, fname))

        return (stdout, stderr, rc)
예제 #10
0
    def _get_syslinux_base_dir(self):
        """Find the installation directory we need to use to when updating
        syslinux
        """
        cfg = None

        for path in self.syslinux.search_paths:
            if self.image.g.is_file(path):
                cfg = path
                break

        if not cfg:
            # Maybe we should fail here
            self.out.warn("Unable to find syslinux configuration file!")
            return "/boot"

        kernel_regexp = re.compile(r'\s*kernel\s+(.+)', re.IGNORECASE)
        initrd_regexp = re.compile(r'\s*initrd\s+(.+)', re.IGNORECASE)
        append_regexp = re.compile(
            r'\s*[Aa][Pp][Pp][Ee][Nn][Dd]\s+.*\binitrd=([^\s]+)')

        kernel = None
        initrd = None

        for line in self.image.g.cat(cfg).splitlines():
            kernel_match = kernel_regexp.match(line)
            if kernel_match:
                kernel = kernel_match.group(1).strip()
                continue

            initrd_match = initrd_regexp.match(line)
            if initrd_match:
                initrd = initrd_match.group(1).strip()
                continue

            append_match = append_regexp.match(line)
            if append_match:
                initrd = append_match.group(1)

        if kernel and kernel[0] != '/':
            relative_path = kernel
        elif initrd and initrd[0] != '/':
            relative_path = initrd
        else:
            # The config does not contain relative paths. Use the directory of
            # the config.
            return os.path.dirname(cfg)

        for d in self.syslinux.search_dirs:
            if self.image.g.is_file(d + relative_path):
                return d

        raise FatalError("Unable to find the working directory of extlinux")
예제 #11
0
    def enable_guestfs(self):
        """Enable the guestfs handler"""

        if self.guestfs_enabled:
            self.out.warn("Guestfs is already enabled")
            return

        # Before version 1.18.4 the behavior of kill_subprocess was different
        # and you need to reset the guestfs handler to relaunch a previously
        # shut down QEMU backend
        if self.check_guestfs_version(1, 18, 4) < 0:
            self.g = guestfs.GuestFS()

        self.g.add_drive_opts(self.device, readonly=0)

        # Before version 1.17.14 the recovery process, which is a fork of the
        # original process that called libguestfs, did not close its inherited
        # file descriptors. This can cause problems especially if the parent
        # process has opened pipes. Since the recovery process is an optional
        # feature of libguestfs, it's better to disable it.
        if self.check_guestfs_version(1, 17, 14) >= 0:
            self.out.info("Enabling recovery process ...", False)
            self.g.set_recovery_proc(1)
            self.out.success('done')
        else:
            self.g.set_recovery_proc(0)

        # self.g.set_trace(1)
        # self.g.set_verbose(1)

        self.out.info('Launching helper VM (may take a while) ...', False)
        # self.progressbar = self.out.Progress(100, "Launching helper VM",
        #                                     "percent")
        # eh = self.g.set_event_callback(self.progress_callback,
        #                               guestfs.EVENT_PROGRESS)
        try:
            self.g.launch()
        except RuntimeError as e:
            raise FatalError(
                "Launching libguestfs's helper VM failed!\nReason: %s.\n\n"
                "Please run `libguestfs-test-tool' for more info." % str(e))

        self.guestfs_enabled = True
        # self.g.delete_event_callback(eh)
        # self.progressbar.success('done')
        # self.progressbar = None

        if self.check_guestfs_version(1, 18, 4) < 0:
            self.g.inspect_os()  # some calls need this

        self.out.success('done')
예제 #12
0
    def _install_viostor_driver(self, dirname):
        """Quick and dirty installation of the VirtIO SCSI controller driver.
        It is done to make the image boot from the VirtIO disk.

        http://rwmj.wordpress.com/2010/04/30/
            tip-install-a-device-driver-in-a-windows-vm/
        """

        drivers_path = "%s/system32/drivers" % self.systemroot

        try:
            drivers_path = self.image.g.case_sensitive_path(drivers_path)
        except RuntimeError as err:
            raise FatalError("Unable to browse to directory: %s. Reason: %s" %
                             (drivers_path, str(err)))
        viostor = dirname + os.sep + 'viostor.sys'
        try:
            self.image.g.upload(viostor, drivers_path + '/viostor.sys')
        except RuntimeError as err:
            raise FatalError("Unable to upload file %s to %s. Reason: %s" %
                             (viostor, drivers_path, str(err)))

        self.registry.add_viostor()
예제 #13
0
    def _dir_to_disk(self):
        """Create a disk out of a directory."""
        if self.source == '/':
            bundle = BundleVolume(self.out, self.meta)
            image = '%s/%s.raw' % (self.tmp, uuid.uuid4().hex)

            def check_unlink(path):
                """Unlinks file if exists"""
                if os.path.exists(path):
                    os.unlink(path)

            self._add_cleanup(check_unlink, image)
            bundle.create_image(image)
            return image
        raise FatalError("Using a directory as media source is supported")
예제 #14
0
    def wait_on_serial(self, timeout):
        """Wait until the random token appears on the VM's serial port"""

        self._ntokens += 1

        for _ in xrange(timeout):
            time.sleep(1)
            with open(self.serial) as f:
                current = 0
                for line in f:
                    if line.startswith(RANDOM_TOKEN):
                        current += 1
                        if current == self._ntokens:
                            return True
            if not self.isalive():
                (_, stderr, rc) = self.wait()
                raise FatalError("Windows VM died unexpectedly!\n\n"
                                 "(rc=%d)\n%s" % (rc, stderr))

        return False
예제 #15
0
    def file(self):
        """Convert the source media into a file."""

        if self._file is not None:
            return self._file

        self.out.info("Examining source media `%s' ..." % self.source, False)
        mode = os.stat(self.source).st_mode
        if stat.S_ISDIR(mode):
            self.out.success('looks like a directory')
            self._file = self._dir_to_disk()
        elif stat.S_ISREG(mode):
            self.out.success('looks like an image file')
            self._file = self.source
        elif not stat.S_ISBLK(mode):
            raise FatalError("Invalid media source. Only block devices, "
                             "regular files and directories are supported.")
        else:
            self.out.success('looks like a block device')
            self._file = self.source

        return self._file
예제 #16
0
    def open_hive(self, hive, write=False):
        """Returns a context manager for opening a hive file of the image for
        reading or writing.
        """
        systemroot = self.image.g.inspect_get_windows_systemroot(self.root)
        path = "%s/system32/config/%s" % (systemroot, hive)
        try:
            path = self.image.g.case_sensitive_path(path)
        except RuntimeError as err:
            raise FatalError("Unable to retrieve file: %s. Reason: %s" %
                             (hive, str(err)))

        # OpenHive class needs this since 'self' gets overwritten
        g = self.image.g

        class OpenHive(object):
            """The OpenHive context manager"""
            def __enter__(self):
                # pylint: disable=attribute-defined-outside-init
                localfd, self.localpath = tempfile.mkstemp()
                try:
                    os.close(localfd)
                    g.download(path, self.localpath)

                    hive = hivex.Hivex(self.localpath, write=write)
                except:
                    os.unlink(self.localpath)
                    raise

                return hive

            def __exit__(self, exc_type, exc_value, traceback):
                try:
                    if write:
                        g.upload(self.localpath, path)
                finally:
                    os.unlink(self.localpath)

        return OpenHive()
예제 #17
0
def image_creator(options, out):
    """snf-mkimage main function"""

    if os.geteuid() != 0:
        raise FatalError("You must run %s as root" %
                         os.path.basename(sys.argv[0]))

    # Check if the authentication info is valid. The earlier the better
    if options.token is not None and options.url is not None:
        try:
            account = Kamaki.create_account(options.url, options.token)
            if account is None:
                raise FatalError("The authentication token and/or URL you "
                                 "provided is not valid!")
            else:
                kamaki = Kamaki(account, out)
        except ClientError as e:
            raise FatalError("Astakos client: %d %s" % (e.status, e.message))
    elif options.cloud:
        avail_clouds = Kamaki.get_clouds()
        if options.cloud not in avail_clouds.keys():
            raise FatalError(
                "Cloud: `%s' does not exist.\n\nAvailable clouds:\n\n\t%s\n" %
                (options.cloud, "\n\t".join(avail_clouds.keys())))
        try:
            account = Kamaki.get_account(options.cloud)
            if account is None:
                raise FatalError("Cloud: `%s' exists but is not valid!" %
                                 options.cloud)
            else:
                kamaki = Kamaki(account, out)
        except ClientError as e:
            raise FatalError("Astakos client: %d %s" % (e.status, e.message))

    if options.upload and not options.force:
        if kamaki.object_exists(options.container, options.upload):
            raise FatalError("Remote storage service object: `%s' exists "
                             "(use --force to overwrite it)." % options.upload)
        if kamaki.object_exists(options.container,
                                "%s.md5sum" % options.upload):
            raise FatalError("Remote storage service object: `%s.md5sum' "
                             "exists (use --force to overwrite it)." %
                             options.upload)

    if options.register and not options.force:
        if kamaki.object_exists(options.container, "%s.meta" % options.upload):
            raise FatalError("Remote storage service object `%s.meta' exists "
                             "(use --force to overwrite it)." % options.upload)

    disk = Disk(options.source, out, options.tmp)

    # pylint: disable=unused-argument
    def signal_handler(signum, frame):
        disk.cleanup()

    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    try:
        # There is no need to snapshot the media if it was created by the Disk
        # instance as a temporary object.
        device = disk.file if not options.snapshot else disk.snapshot()
        image = disk.get_image(device, sysprep_params=options.sysprep_params)

        if image.is_unsupported() and not options.allow_unsupported:
            raise FatalError(
                "The media seems to be unsupported.\n\n" +
                textwrap.fill("To create an image from an unsupported media, "
                              "you'll need to use the`--allow-unsupported' "
                              "command line option. Using this is highly "
                              "discouraged, since the resulting image will "
                              "not be cleared out of sensitive data and will "
                              "not get customized during the deployment."))

        if len(options.host_run) != 0 and not image.mount_local_support:
            raise FatalError("Running scripts against the guest media is not "
                             "supported for this build of libguestfs.")

        if len(options.host_run) != 0:
            for script in options.host_run:
                if not os.path.isfile(script):
                    raise FatalError("File: `%s' does not exist." % script)
                if not os.access(script, os.X_OK):
                    raise FatalError("File: `%s' is not executable." % script)

        for name in options.disabled_syspreps:
            sysprep = image.os.get_sysprep_by_name(name)
            if sysprep is not None:
                image.os.disable_sysprep(sysprep)
            else:
                out.warn("Sysprep: `%s' does not exist. Can't disable it." %
                         name)

        for name in options.enabled_syspreps:
            sysprep = image.os.get_sysprep_by_name(name)
            if sysprep is not None:
                image.os.enable_sysprep(sysprep)
            else:
                out.warn("Sysprep: `%s' does not exist. Can't enable it." %
                         name)
        if image.is_unsupported():
            image.meta['EXCLUDE_ALL_TASKS'] = "yes"

        # Add command line metadata to the collected ones...
        image.meta.update(options.metadata)

        if options.print_syspreps:
            image.os.print_syspreps()
            out.info()

        if options.print_sysprep_params:
            image.os.print_sysprep_params()
            out.info()

        if options.print_metadata:
            image.os.print_metadata()
            out.info()

        if options.outfile is None and not options.upload:
            return 0

        if options.virtio is not None and \
                hasattr(image.os, 'install_virtio_drivers'):
            image.os.install_virtio_drivers()

        if len(options.host_run) != 0:
            # Export image metadata as environment variables to make them
            # visible by the scripts
            for key, value in image.meta.items():
                os.environ["SNF_IMAGE_CREATOR_METADATA_%s" % key] = str(value)

            out.info("Running scripts on the input media:")
            mpoint = tempfile.mkdtemp()
            try:
                image.mount(mpoint)
                if not image.is_mounted():
                    raise FatalError("Mounting the media on the host failed.")
                try:
                    size = len(options.host_run)
                    cnt = 1
                    for script in options.host_run:
                        script = os.path.abspath(script)
                        out.info(("(%d/%d)" % (cnt, size)).ljust(7), False)
                        out.info("Running `%s'" % script)
                        ret = subprocess.Popen([script], cwd=mpoint).wait()
                        if ret != 0:
                            raise FatalError("Script: `%s' failed (rc=%d)" %
                                             (script, ret))
                        cnt += 1
                finally:
                    while not image.umount():
                        out.warn("Unable to umount the media. Retrying ...")
                        time.sleep(1)
                    out.info()
            finally:
                os.rmdir(mpoint)

        if options.sysprep:
            image.os.do_sysprep()

        checksum = image.md5()

        image_meta = {}
        for k, v in image.meta.items():
            image_meta[str(k)] = str(v)

        metastring = json.dumps(
            {
                'properties': image_meta,
                'disk-format': 'diskdump'
            },
            ensure_ascii=False)

        img_properties = json.dumps(image_meta, ensure_ascii=False)

        if options.outfile is not None:
            if os.path.realpath(options.outfile) == '/dev/null':
                out.warn('Not dumping file to /dev/null')
            else:
                image.dump(options.outfile)

                out.info('Dumping metadata file ...', False)
                with open('%s.%s' % (options.outfile, 'meta'), 'w') as f:
                    f.write(metastring)
                out.success('done')

                out.info('Dumping md5sum file ...', False)
                with open('%s.%s' % (options.outfile, 'md5sum'), 'w') as f:
                    f.write('%s %s\n' %
                            (checksum, os.path.basename(options.outfile)))
                out.success('done')

                out.info('Dumping variant file ...', False)
                with open('%s.%s' % (options.outfile, 'variant'), 'w') as f:
                    f.write(
                        to_shell(IMG_ID=options.outfile,
                                 IMG_FORMAT="diskdump",
                                 IMG_PROPERTIES=img_properties))
                out.success('done')

        out.info()
        try:
            if options.upload:
                out.info("Uploading image to the storage service:")
                with image.raw_device() as raw:
                    with open(raw, 'rb') as f:
                        remote = kamaki.upload(
                            f, image.size, options.upload, options.container,
                            None, "(1/3)  Calculating block hashes",
                            "(2/3)  Uploading missing blocks")

                out.info("(3/3)  Uploading md5sum file ...", False)
                md5sumstr = '%s %s\n' % (checksum,
                                         os.path.basename(options.upload))
                kamaki.upload(StringIO.StringIO(md5sumstr),
                              size=len(md5sumstr),
                              remote_path="%s.%s" % (options.upload, 'md5sum'),
                              container=options.container,
                              content_type="text/plain")
                out.success('done')
                out.info()

            if options.register:
                img_type = 'public' if options.public else 'private'
                out.info(
                    'Registering %s image with the compute service ...' %
                    img_type, False)
                result = kamaki.register(options.register, remote, image.meta,
                                         options.public)
                out.success('done')
                out.info("Uploading metadata file ...", False)
                metastring = unicode(
                    json.dumps(result, ensure_ascii=False, indent=4))
                kamaki.upload(StringIO.StringIO(metastring.encode('utf8')),
                              size=len(metastring),
                              remote_path="%s.%s" % (options.upload, 'meta'),
                              container=options.container,
                              content_type="application/json")
                out.success('done')
                if options.public:
                    out.info("Sharing md5sum file ...", False)
                    kamaki.share("%s.md5sum" % options.upload)
                    out.success('done')
                    out.info("Sharing metadata file ...", False)
                    kamaki.share("%s.meta" % options.upload)
                    out.success('done')
                out.result(json.dumps(result, indent=4, ensure_ascii=False))
                out.info()
        except ClientError as e:
            raise FatalError("Service client: %d %s" % (e.status, e.message))

    finally:
        out.info('cleaning up ...')
        disk.cleanup()

    out.success("snf-image-creator exited without errors")

    return 0
예제 #18
0
    def __init__(self, disk, params, admin):
        """Create VM instance"""

        self.disk = disk
        self.params = params
        self.admin = admin
        self.interface = 'virtio'

        # expected number of token occurrences in serial port
        self._ntokens = 0

        kvm, needed_args = get_kvm_binary()
        if kvm is None:
            raise FatalError("Can't find the kvm binary")

        self.kvm = [kvm] + list(needed_args)

        def random_mac():
            """creates a random mac address"""
            mac = [0x00, 0x16, 0x3e,
                   random.randint(0x00, 0x7f),
                   random.randint(0x00, 0xff),
                   random.randint(0x00, 0xff)]

            return ':'.join(['%02x' % x for x in mac])

        self.mac = random_mac()

        def random_password():
            """Creates a random password"""

            # I borrowed this from Synnefo
            pool = lowercase + uppercase + digits
            lowerset = set(lowercase)
            upperset = set(uppercase)
            digitset = set(digits)
            length = 10

            password = ''.join(random.choice(pool) for i in range(length - 2))

            # Make sure the password is compliant
            chars = set(password)
            if not chars & lowerset:
                password += random.choice(lowercase)
            if not chars & upperset:
                password += random.choice(uppercase)
            if not chars & digitset:
                password += random.choice(digits)

            # Pad if necessary to reach required length
            password += ''.join(random.choice(pool) for i in
                                range(length - len(password)))

            return password

        self.password = random_password()

        # Use Ganeti's VNC port range for a random vnc port
        self.display = random.randint(11000, 14999) - 5900

        self.serial = None
        self.process = None
예제 #19
0
    def _boot_virtio_vm(self):
        """Boot the media and install the VirtIO drivers"""

        old_windows = self.check_version(6, 1) <= 0
        self.image.disable_guestfs()
        try:
            timeout = self.sysprep_params['boot_timeout'].value
            shutdown_timeout = self.sysprep_params['shutdown_timeout'].value
            virtio_timeout = self.sysprep_params['virtio_timeout'].value
            self.out.info("Starting Windows VM ...", False)
            booted = False
            try:
                if old_windows:
                    self.vm.start()
                else:
                    self.vm.interface = 'ide'
                    self.vm.start(extra_disk=('/dev/null', 'virtio'))
                    self.vm.interface = 'virtio'

                self.out.success("started (console on VNC display: %d)" %
                                 self.vm.display)
                self.out.info("Waiting for Windows to boot ...", False)
                if not self.vm.wait_on_serial(timeout):
                    raise FatalError("Windows VM booting timed out!")
                self.out.success('done')
                booted = True
                self.out.info("Installing new drivers ...", False)
                if not self.vm.wait_on_serial(virtio_timeout):
                    raise FatalError("Windows VirtIO installation timed out!")
                self.out.success('done')
                self.out.info('Shutting down ...', False)
                (_, stderr, rc) = self.vm.wait(shutdown_timeout)
                if rc != 0 or "terminating on signal" in stderr:
                    raise FatalError("Windows VM died unexpectedly!\n\n"
                                     "(rc=%d)\n%s" % (rc, stderr))
                self.out.success('done')
            finally:
                self.vm.stop(shutdown_timeout if booted else 1, fatal=False)
        finally:
            self.image.enable_guestfs()

        with self.mount(readonly=True, silent=True):
            self.virtio_state = self.compute_virtio_state()
            viostor_service_found = self.registry.check_viostor_service()

        if not (len(self.virtio_state['viostor']) and viostor_service_found):
            raise FatalError("viostor was not successfully installed")

        if self.check_version(6, 1) > 0:
            # Hopefully restart in safe mode. Newer windows will not boot from
            # a viostor device unless we initially start them in safe mode
            try:
                self.out.info('Rebooting Windows VM in safe mode ...', False)
                self.vm.start()
                (_, stderr, rc) = self.vm.wait(timeout + shutdown_timeout)
                if rc != 0 or "terminating on signal" in stderr:
                    raise FatalError("Windows VM died unexpectedly!\n\n"
                                     "(rc=%d)\n%s" % (rc, stderr))
                self.out.success('done')
            finally:
                self.vm.stop(1, fatal=True)
예제 #20
0
 def handler(signum, frame):
     """Signal handler"""
     raise FatalError("VM wait timed-out.")
예제 #21
0
    def __init__(self, image, **kwargs):
        super(Windows, self).__init__(image, **kwargs)

        # 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.product_name = self.image.g.inspect_get_product_name(self.root)
        self.systemroot = self.image.g.inspect_get_windows_systemroot(
            self.root)

        self.registry = Registry(self.image)

        with self.mount(readonly=True, silent=True):
            self.out.info("Checking media state ...", False)

            # Enumerate the windows users
            (self.usernames, self.active_users,
             self.admins) = self.registry.enum_users()
            assert ADMIN_RID in self.usernames, "Administrator account missing"

            self.virtio_state = self.compute_virtio_state()

            self.arch = self.image.g.inspect_get_arch(self.root)
            if self.arch == 'x86_64':
                self.arch = 'amd64'
            elif self.arch == 'i386':
                self.arch = 'x86'
            major = int(self.image.g.inspect_get_major_version(self.root))
            minor = int(self.image.g.inspect_get_minor_version(self.root))
            self.nt_version = (major, minor)

            # The get_setup_state() command does not work for old windows
            if self.nt_version[0] >= 6:
                # If the image is already sysprepped, we cannot further
                # customize it.
                self.sysprepped = self.registry.get_setup_state() > 0
            else:
                # Fallback to NO although we done know
                # TODO: Add support for detecting the setup state on XP
                self.sysprepped = False

            self.out.success("done")

        # If the image is sysprepped no driver mappings will be present.
        self.systemdrive = None
        for drive, root in self.image.g.inspect_get_drive_mappings(self.root):
            if root == self.root:
                self.systemdrive = drive

        active_admins = [u for u in self.admins if u in self.active_users]
        if ADMIN_RID in self.active_users or len(active_admins) == 0:
            admin = ADMIN_RID
        else:
            active_admins.sort()
            admin = active_admins[0]

        self.vm = VM(
            self.image.device, self.sysprep_params,
            namedtuple('User', 'rid name')(admin, self.usernames[admin]))
예제 #22
0
    def run(self, src, dest, slabel='source', dlabel='destination'):
        """Run the actual command"""
        cmd = []
        cmd.append('rsync')
        cmd.extend(self._options)
        for i in self._exclude:
            cmd.extend(['--exclude', i])

        self._out.info("Calculating total number of %s files ..." % slabel,
                       False)

        # If you don't specify a destination, rsync will list the source files.
        dry_run = subprocess.Popen(cmd + [src],
                                   shell=False,
                                   stdout=subprocess.PIPE,
                                   bufsize=0)
        try:
            total = 0
            for _ in iter(dry_run.stdout.readline, b''):
                total += 1
        finally:
            dry_run.communicate()
            if dry_run.returncode != 0:
                raise FatalError("rsync failed")

        self._out.success("%d" % total)

        progress = self._out.Progress(total, "Copying files to %s" % dlabel)
        run = subprocess.Popen(cmd + [src, dest],
                               shell=False,
                               stdout=subprocess.PIPE,
                               bufsize=0)
        try:
            t = time.time()
            i = 0
            for _ in iter(run.stdout.readline, b''):
                i += 1
                current = time.time()
                if current - t > 0.1:
                    t = current
                    progress.goto(i)

            progress.success('done')

        finally:

            def handler(signum, frame):  # pylint: disable=unused-argument
                """Signal handler"""
                run.terminate()
                time.sleep(1)
                run.poll()
                if run.returncode is None:
                    run.kill()
                run.wait()

            signal.signal(signal.SIGALRM, handler)
            signal.alarm(2)
            run.communicate()
            signal.alarm(0)
            if run.returncode != 0:
                raise FatalError("rsync failed")
예제 #23
0
    def do_sysprep(self):
        """Prepare system for image creation."""

        self.out.info('Preparing system for image creation:')

        # Check if winexe is installed
        if not WinEXE.is_installed():
            raise FatalError(
                "Winexe not found! In order to be able to customize a Windows "
                "image you need to have Winexe installed.")

        if self.sysprepped:
            raise FatalError(
                "Microsoft's System Preparation Tool has ran on the media. "
                "Further image customization is not possible.")

        if len(self.virtio_state['viostor']) == 0:
            raise FatalError(
                "The media has no VirtIO SCSI controller driver installed. "
                "Further image customization is not possible.")

        if len(self.virtio_state['netkvm']) == 0:
            raise FatalError(
                "The media has no VirtIO Ethernet Adapter driver installed. "
                "Further image customization is not possible.")

        timeout = self.sysprep_params['boot_timeout'].value
        shutdown_timeout = self.sysprep_params['shutdown_timeout'].value

        self.out.info("Preparing media for boot ...", False)

        with self.mount(readonly=False, silent=True):

            if not self.registry.reset_account(self.vm.admin.rid):
                self._add_cleanup('sysprep', self.registry.reset_account,
                                  self.vm.admin.rid, False)

            old = self.registry.update_uac(0)
            if old != 0:
                self._add_cleanup('sysprep', self.registry.update_uac, old)

            old = self.registry.update_uac_remote_setting(1)
            if old != 1:
                self._add_cleanup('sysprep',
                                  self.registry.update_uac_remote_setting, old)

            def if_not_sysprepped(task, *args):
                """Only perform this if the image is not sysprepped"""
                if not self.sysprepped:
                    task(*args)

            # The next 2 registry values get completely removed by Microsoft
            # Sysprep. They should not be reverted if Sysprep gets executed.
            old = self.registry.update_noautoupdate(1)
            if old != 1:
                self._add_cleanup('sysprep', if_not_sysprepped,
                                  self.registry.update_noautoupdate, old)

            old = self.registry.update_auoptions(1)
            if old != 1:
                self._add_cleanup('sysprep', if_not_sysprepped,
                                  self.registry.update_auoptions, old)

            # disable the firewalls
            self._add_cleanup('sysprep', self.registry.update_firewalls,
                              *self.registry.update_firewalls(0, 0, 0))

            v_val = self.registry.reset_passwd(self.vm.admin.rid)

            self._add_boot_scripts()

            # Delete the pagefile. It will be recreated when the system boots
            try:
                pagefile = "%s/pagefile.sys" % self.systemroot
                self.image.g.rm_rf(self.image.g.case_sensitive_path(pagefile))
            except RuntimeError:
                pass

        self.out.success('done')

        self.image.disable_guestfs()
        booted = False
        try:
            self.out.info("Starting windows VM ...", False)
            self.vm.start()
            try:
                self.out.success("started (console on VNC display: %d)" %
                                 self.vm.display)

                self.out.info("Waiting for OS to boot ...", False)
                if not self.vm.wait_on_serial(timeout):
                    raise FatalError("Windows VM booting timed out!")
                self.out.success('done')
                booted = True

                # Since the password is reset when logging in, sleep a little
                # bit before checking the connectivity, to avoid race
                # conditions
                time.sleep(5)

                self.out.info("Checking connectivity to the VM ...", False)
                self._check_connectivity()
                # self.out.success('done')

                # self.out.info("Disabling automatic logon ...", False)
                self._disable_autologon()
                self.out.success('done')

                self._exec_sysprep_tasks()

                self.out.info("Waiting for windows to shut down ...", False)
                (_, stderr, rc) = self.vm.wait(shutdown_timeout)
                if rc != 0 or "terminating on signal" in stderr:
                    raise FatalError("Windows VM died unexpectedly!\n\n"
                                     "(rc=%d)\n%s" % (rc, stderr))
                self.out.success("done")
            finally:
                # if the VM is not already dead here, a Fatal Error will have
                # already been raised. There is no reason to make the command
                # fatal.
                self.vm.stop(shutdown_timeout if booted else 1, fatal=False)
        finally:
            self.image.enable_guestfs()

            self.out.info("Reverting media boot preparations ...", False)
            with self.mount(readonly=False, silent=True, fatal=False):

                if not self.ismounted:
                    self.out.warn("The boot changes cannot be reverted. "
                                  "The snapshot may be in a corrupted state.")
                else:
                    if not self.sysprepped:
                        # Reset the old password
                        self.registry.reset_passwd(self.vm.admin.rid, v_val)

                    self._cleanup('sysprep')
                    self.out.success("done")

        self.image.shrink(silent=True)
예제 #24
0
    def install_virtio_drivers(self, upgrade=True):
        """Install new VirtIO drivers on the input media. If upgrade is True,
        then the old drivers found in the media will be removed.
        """

        dirname = self.sysprep_params['virtio'].value
        if not dirname:
            raise FatalError('No directory hosting the VirtIO drivers defined')

        self.out.info('Installing VirtIO drivers:')

        valid_drvs = self._fetch_virtio_drivers(dirname)
        if not len(valid_drvs):
            self.out.warn('No suitable driver found to install!')
            return

        remove = []
        certs = []
        install = []
        add = []
        # Check which drivers we need to install, which to add to the database
        # and which to remove.
        for dtype in valid_drvs:
            versions = [v['DriverVer'] for k, v in valid_drvs[dtype].items()]
            certs.extend([
                v['CatalogFile'] for k, v in valid_drvs[dtype].items()
                if 'CatalogFile' in v
            ])
            installed = [(k, v['DriverVer'])
                         for k, v in self.virtio_state[dtype].items()]
            found = [d[0] for d in installed if d[1] in versions]
            not_found = [d[0] for d in installed if d[1] not in versions]

            for drvr in found:
                details = self.virtio_state[dtype][drvr]
                self.out.warn(
                    '%s driver with version %s is already installed!' %
                    (dtype, details['DriverVer']))
            if upgrade:
                remove.extend(not_found)

            if dtype == 'viostor':
                install.extend([d for d in valid_drvs[dtype]])
            else:
                add.extend([d for d in valid_drvs[dtype]])

        try:
            self._update_driver_database('virtio',
                                         upload=dirname,
                                         certs=certs,
                                         add=add,
                                         install=install,
                                         remove=remove)
        finally:
            with self.mount(readonly=False, silent=True, fatal=False):
                if not self.ismounted:
                    self.out.warn("The boot changes cannot be reverted. "
                                  "The image may be in a corrupted state.")
                else:
                    self._cleanup('virtio')

        self.out.success("VirtIO drivers were successfully installed")
        self.out.info()
예제 #25
0
def create_image(session, answers):
    """Create an image using the information collected by the wizard"""
    image = session['image']

    with_progress = OutputWthProgress()
    image.out.append(with_progress)
    try:
        image.out.clear()

        if 'virtio' in answers and image.os.sysprep_params['virtio'].value:
            image.os.install_virtio_drivers()

        # Sysprep
        image.os.do_sysprep()
        metadata = image.os.meta

        update_background_title(session)

        metadata['DESCRIPTION'] = answers['ImageDescription']

        # MD5
        session['checksum'] = image.md5()

        image.out.info()
        try:
            image.out.info("Uploading image to the cloud:")
            account = Kamaki.get_account(answers['Cloud'])
            assert account, "Cloud: %s is not valid" % answers['Cloud']
            kamaki = Kamaki(account, image.out)

            name = "%s-%s.diskdump" % (answers['ImageName'],
                                       time.strftime("%Y%m%d%H%M"))
            with image.raw_device() as raw:
                with open(raw, 'rb') as device:
                    remote = kamaki.upload(device, image.size, name, CONTAINER,
                                           None,
                                           "(1/3)  Calculating block hashes",
                                           "(2/3)  Uploading image blocks")

            image.out.info("(3/3)  Uploading md5sum file ...", False)
            md5sumstr = '%s %s\n' % (session['checksum'], name)
            kamaki.upload(StringIO.StringIO(md5sumstr),
                          size=len(md5sumstr),
                          remote_path="%s.%s" % (name, 'md5sum'),
                          container=CONTAINER,
                          content_type="text/plain")
            image.out.success('done')
            image.out.info()

            image.out.info(
                'Registering %s image with the cloud ...' %
                answers['RegistrationType'].lower(), False)
            result = kamaki.register(answers['ImageName'], remote, metadata,
                                     answers['RegistrationType'] == "Public")
            image.out.success('done')
            image.out.info("Uploading metadata file ...", False)
            metastring = unicode(json.dumps(result,
                                            ensure_ascii=False)).encode('utf8')
            kamaki.upload(StringIO.StringIO(metastring),
                          size=len(metastring),
                          remote_path="%s.%s" % (name, 'meta'),
                          container=CONTAINER,
                          content_type="application/json")
            image.out.success('done')

            if answers['RegistrationType'] == "Public":
                image.out.info("Sharing md5sum file ...", False)
                kamaki.share("%s.md5sum" % name)
                image.out.success('done')
                image.out.info("Sharing metadata file ...", False)
                kamaki.share("%s.meta" % name)
                image.out.success('done')

            image.out.info()

        except ClientError as error:
            raise FatalError("Storage service client: %d %s" %
                             (error.status, error.message))
    finally:
        image.out.remove(with_progress)

    text = "The %s image was successfully uploaded to the storage service " \
           "and registered with the compute service of %s. Would you like " \
           "to keep a local copy?" % \
           (answers['RegistrationType'].lower(), answers['Cloud'])

    if session['dialog'].yesno(text, width=PAGE_WIDTH) == session['dialog'].OK:
        extract_image(session)