diff --git a/tests/cli-test-xml/compare/virt-install-kvm-i686-uefi.xml b/tests/cli-test-xml/compare/virt-install-kvm-i686-uefi.xml index 7611430a..f58f39d2 100644 --- a/tests/cli-test-xml/compare/virt-install-kvm-i686-uefi.xml +++ b/tests/cli-test-xml/compare/virt-install-kvm-i686-uefi.xml @@ -6,7 +6,8 @@ hvm /usr/share/edk2/ovmf-ia32/OVMF_CODE.fd - + /TESTSUITE_KERNEL_PATH + /TESTSUITE_INITRD_PATH @@ -62,7 +63,7 @@ hvm /usr/share/edk2/ovmf-ia32/OVMF_CODE.fd - + diff --git a/tests/clitest.py b/tests/clitest.py index 1148c96f..7ee7093a 100644 --- a/tests/clitest.py +++ b/tests/clitest.py @@ -909,7 +909,7 @@ c.add_compare("--disk none --location %(ISO-NO-OS)s,kernel=frib.img,initrd=/frob c.add_compare("--disk %(EXISTIMG1)s --location %(ISOTREE)s --nonetworks", "location-iso", prerun_check=missing_isoinfo) # Using --location iso mounting c.add_compare("--disk %(EXISTIMG1)s --cdrom %(ISOLABEL)s", "cdrom-centos-label") # Using --cdrom with centos CD label, should use virtio etc. c.add_compare("--disk %(EXISTIMG1)s --install bootdev=network --os-variant rhel5.4", "kvm-rhel5") # RHEL5 defaults -c.add_compare("--disk %(EXISTIMG1)s --install kernel=./foo,initrd=./bar,kernel_args='foo bar' --os-variant rhel6.4", "kvm-rhel6") # RHEL6 defaults +c.add_compare("--disk %(EXISTIMG1)s --install kernel=%(ISO-WIN7)s,initrd=%(ISOLABEL)s,kernel_args='foo bar' --os-variant rhel6.4", "kvm-rhel6") # RHEL6 defaults. ISO paths are just to point at existing files c.add_compare("--disk %(EXISTIMG1)s --location https://example.com --install kernel_args='test overwrite',kernel_args_overwrite=yes --os-variant rhel7.0", "kvm-rhel7", prerun_check=has_old_osinfo) # RHEL7 defaults c.add_compare("--connect " + utils.URIs.kvm_nodomcaps + " --disk %(EXISTIMG1)s --pxe --os-variant rhel7.0", "kvm-cpu-default-fallback", prerun_check=has_old_osinfo) # No domcaps, so mode=host-model isn't safe, so we fallback to host-model-only c.add_compare("--connect " + utils.URIs.kvm_nodomcaps + " --cpu host-copy --disk none --pxe", "kvm-hostcopy-fallback") # No domcaps so need to use capabilities for CPU host-copy @@ -917,7 +917,7 @@ c.add_compare("--disk %(EXISTIMG1)s --pxe --os-variant centos7.0", "kvm-centos7" c.add_compare("--disk %(EXISTIMG1)s --pxe --os-variant centos7.0", "kvm-centos7", prerun_check=has_old_osinfo) # Centos 7 defaults c.add_compare("--disk %(EXISTIMG1)s --cdrom %(EXISTIMG2)s --os-variant win10", "kvm-win10", prerun_check=has_old_osinfo) # win10 defaults c.add_compare("--os-variant win7 --cdrom %(EXISTIMG2)s --boot loader_type=pflash,loader=CODE.fd,nvram_template=VARS.fd --disk %(EXISTIMG1)s", "win7-uefi", prerun_check=has_old_osinfo) # no HYPER-V with UEFI -c.add_compare("--arch i686 --boot uefi --pxe --disk none", "kvm-i686-uefi") # i686 uefi +c.add_compare("--arch i686 --boot uefi --install kernel=http://example.com/httpkernel,initrd=ftp://example.com/ftpinitrd --disk none", "kvm-i686-uefi") # i686 uefi. piggy back it for --install testing too c.add_compare("--machine q35 --cdrom %(EXISTIMG2)s --disk %(EXISTIMG1)s", "q35-defaults") # proper q35 disk defaults c.add_compare("--disk size=1 --os-variant openbsd4.9", "openbsd-defaults") # triggers net fallback scenario c.add_compare("--connect " + utils.URIs.kvm_remote + " --import --disk %(EXISTIMG1)s --os-variant fedora21 --pm suspend_to_disk=yes", "f21-kvm-remote", prerun_check=has_old_osinfo) diff --git a/virtinst/installer.py b/virtinst/installer.py index 2f5087fd..547336ee 100644 --- a/virtinst/installer.py +++ b/virtinst/installer.py @@ -51,8 +51,6 @@ class Installer(object): self.autostart = False self._install_bootdev = install_bootdev - self._install_kernel = None - self._install_initrd = None self._install_kernel_args = install_kernel_args self._install_cdrom_device_added = False self._unattended_install_cdrom_device = None @@ -61,18 +59,17 @@ class Installer(object): self._unattended_data = None self._treemedia = None + self._treemedia_bootconfig = None self._cdrom = None if cdrom: cdrom = InstallerTreeMedia.validate_path(self.conn, cdrom) self._cdrom = cdrom self._install_bootdev = "cdrom" - elif location or location_kernel or location_initrd: + elif (location or location_kernel or location_initrd or + install_kernel or install_initrd): self._treemedia = InstallerTreeMedia(self.conn, location, - location_kernel, location_initrd) - elif install_kernel or install_initrd: - self._install_kernel = os.path.realpath(install_kernel) - self._install_initrd = os.path.realpath(install_initrd) - self._install_bootdev = None + location_kernel, location_initrd, + install_kernel, install_initrd) ################### @@ -162,6 +159,26 @@ class Installer(object): not guest.os.kernel and not any([d.boot.order for d in guest.devices.get_all()])) + def _alter_treemedia_bootconfig(self, guest): + if not self._treemedia: + return + + kernel, initrd, kernel_args = self._treemedia_bootconfig + if kernel_args: + self.extra_args.append(kernel_args) + + if kernel: + guest.os.kernel = (self.conn.in_testsuite() and + "/TESTSUITE_KERNEL_PATH" or kernel) + if initrd: + guest.os.initrd = (self.conn.in_testsuite() and + "/TESTSUITE_INITRD_PATH" or initrd) + + if self._install_kernel_args: + guest.os.kernel_args = self._install_kernel_args + elif self.extra_args: + guest.os.kernel_args = " ".join(self.extra_args) + def _alter_bootconfig(self, guest): """ Generate the portion of the guest xml that determines boot devices @@ -170,18 +187,7 @@ class Installer(object): :param guest: Guest instance we are installing """ guest.on_reboot = "destroy" - - if self._install_kernel: - guest.os.kernel = (self.conn.in_testsuite() and - "/TESTSUITE_KERNEL_PATH" or self._install_kernel) - if self._install_initrd: - guest.os.initrd = (self.conn.in_testsuite() and - "/TESTSUITE_INITRD_PATH" or self._install_initrd) - - if self._install_kernel_args: - guest.os.kernel_args = self._install_kernel_args - elif self.extra_args: - guest.os.kernel_args = " ".join(self.extra_args) + self._alter_treemedia_bootconfig(guest) bootdev = self._install_bootdev if bootdev and self._can_set_guest_bootorder(guest): @@ -248,12 +254,8 @@ class Installer(object): unattended_script = self._prepare_unattended_script(guest, meter) if self._treemedia: - k, i, a = self._treemedia.prepare(guest, meter, + self._treemedia_bootconfig = self._treemedia.prepare(guest, meter, unattended_script) - self._install_kernel = k - self._install_initrd = i - if a: - self.extra_args.append(a) elif unattended_script: self._prepare_unattended_data(guest, unattended_script) @@ -346,8 +348,6 @@ class Installer(object): return False return bool(self._cdrom or self._install_bootdev or - self._install_kernel or - self._install_initrd or self._treemedia) def detect_distro(self, guest): diff --git a/virtinst/installertreemedia.py b/virtinst/installertreemedia.py index ff85debf..1e57a7aa 100644 --- a/virtinst/installertreemedia.py +++ b/virtinst/installertreemedia.py @@ -19,7 +19,8 @@ from .osdict import OSDB # Enum of the various install media types we can have (MEDIA_DIR, MEDIA_ISO, - MEDIA_URL) = range(1, 4) + MEDIA_URL, + MEDIA_KERNEL) = range(1, 5) def _is_url(url): @@ -98,11 +99,15 @@ class InstallerTreeMedia(object): return system_scratchdir # pragma: no cover - def __init__(self, conn, location, location_kernel, location_initrd): + def __init__(self, conn, location, location_kernel, location_initrd, + install_kernel, install_initrd): self.conn = conn self.location = location self._location_kernel = location_kernel self._location_initrd = location_initrd + self._install_kernel = install_kernel + self._install_initrd = install_initrd + self.initrd_injections = [] if location_kernel or location_initrd: @@ -119,16 +124,21 @@ class InstallerTreeMedia(object): self._tmpfiles = [] self._tmpvols = [] - self._media_type = MEDIA_ISO - if (not self.conn.is_remote() and - os.path.exists(self.location) and - os.path.isdir(self.location)): + if self._install_kernel or self._install_initrd: + self._media_type = MEDIA_KERNEL + elif (not self.conn.is_remote() and + os.path.exists(self.location) and + os.path.isdir(self.location)): self.location = os.path.abspath(self.location) self._media_type = MEDIA_DIR elif _is_url(self.location): self._media_type = MEDIA_URL + else: + self._media_type = MEDIA_ISO - if self.conn.is_remote() and not self._media_type == MEDIA_URL: + if (self.conn.is_remote() and + not self._media_type == MEDIA_URL and + not self._media_type == MEDIA_KERNEL): raise ValueError(_("Cannot access install tree on remote " "connection: %s") % self.location) @@ -146,32 +156,44 @@ class InstallerTreeMedia(object): if not self._cached_fetcher: scratchdir = InstallerTreeMedia.make_scratchdir(guest) - self._cached_fetcher = urlfetcher.fetcherForURI( - self.location, scratchdir, meter) + if self._media_type == MEDIA_KERNEL: + self._cached_fetcher = urlfetcher.DirectFetcher( + None, scratchdir, meter) + else: + self._cached_fetcher = urlfetcher.fetcherForURI( + self.location, scratchdir, meter) self._cached_fetcher.meter = meter return self._cached_fetcher def _get_cached_data(self, guest, fetcher): - if not self._cached_data: - has_location_kernel = bool( - self._location_kernel and self._location_initrd) + if self._cached_data: + return self._cached_data + + store = None + os_variant = None + os_media = None + kernel_paths = [] + has_location_kernel = bool( + self._location_kernel and self._location_initrd) + + if self._media_type == MEDIA_KERNEL: + kernel_paths = [ + (self._install_kernel, self._install_initrd)] + else: store = urldetect.getDistroStore(guest, fetcher, skip_error=has_location_kernel) - os_variant = None - os_media = None - kernel_paths = [] - if store: - kernel_paths = store.get_kernel_paths() - os_variant = store.get_osdict_info() - os_media = store.get_os_media() - if has_location_kernel: - kernel_paths = [ - (self._location_kernel, self._location_initrd)] + if store: + kernel_paths = store.get_kernel_paths() + os_variant = store.get_osdict_info() + os_media = store.get_os_media() + if has_location_kernel: + kernel_paths = [ + (self._location_kernel, self._location_initrd)] - self._cached_data = _LocationData(os_variant, kernel_paths, - os_media) + self._cached_data = _LocationData(os_variant, kernel_paths, + os_media) return self._cached_data def _prepare_kernel_url(self, guest, cache, fetcher): diff --git a/virtinst/urlfetcher.py b/virtinst/urlfetcher.py index 6a5a0bfc..169d4f69 100644 --- a/virtinst/urlfetcher.py +++ b/virtinst/urlfetcher.py @@ -119,12 +119,15 @@ class _URLFetcher(object): return self.location return os.path.join(self.location, filename) - def _grabURL(self, filename, fileobj): + def _grabURL(self, filename, fileobj, fullurl=None): """ Download the filename from self.location, and write contents to fileobj """ - url = self._make_full_url(filename) + if fullurl: + url = fullurl + else: + url = self._make_full_url(filename) try: urlobj, size = self._grabber(url) @@ -203,7 +206,7 @@ class _URLFetcher(object): logging.debug("hasFile(%s) returning %s", url, ret) return ret - def acquireFile(self, filename): + def acquireFile(self, filename, fullurl=None): """ Grab the passed filename from self.location and save it to a temporary file, returning the temp filename @@ -217,7 +220,7 @@ class _URLFetcher(object): dir=self.scratchdir, prefix=prefix, delete=False) fn = fileobj.name - self._grabURL(filename, fileobj) + self._grabURL(filename, fileobj, fullurl=fullurl) logging.debug("Saved file to %s", fn) return fn except: # noqa @@ -401,15 +404,33 @@ class _ISOURLFetcher(_URLFetcher): return url.encode("ascii") in self._cache_file_list -def fetcherForURI(uri, *args, **kwargs): +class DirectFetcher(_URLFetcher): + def _make_full_url(self, filename): + return filename + + def acquireFile(self, filename, fullurl=None): + fullurl = filename + filename = os.path.basename(filename) + fetcher = fetcherForURI(fullurl, self.scratchdir, self.meter, direct=True) + return fetcher.acquireFile(filename, fullurl) # pylint: disable=protected-access + + def _hasFile(self, url): + return True + + def _grabber(self, url): + raise RuntimeError( # pragma: no cover + "DirectFetcher shouldn't be used for file access.") + + +def fetcherForURI(uri, scratchdir, meter, direct=False): if uri.startswith("http://") or uri.startswith("https://"): fclass = _HTTPURLFetcher elif uri.startswith("ftp://"): fclass = _FTPURLFetcher - elif os.path.isdir(uri): + elif direct or os.path.isdir(uri): # Pointing to a local tree fclass = _LocalURLFetcher else: # Pointing to a path (e.g. iso), or a block device (e.g. /dev/cdrom) fclass = _ISOURLFetcher - return fclass(uri, *args, **kwargs) + return fclass(uri, scratchdir, meter)