Ejemplo n.º 1
0
 def loop_context_test(self):
     """Test the LoopDev context manager (requires loop)"""
     with tempfile.NamedTemporaryFile(prefix="lorax.test.disk.") as disk_img:
         mksparse(disk_img.name, 42 * 1024**2)
         with LoopDev(disk_img.name) as loop_dev:
             self.assertTrue(loop_dev is not None)
             self.assertEqual(loop_dev[5:], get_loop_name(disk_img.name))
Ejemplo n.º 2
0
def mkfakediskimg(disk_img):
    """Create a fake partitioned disk image

    :param disk_img: Full path to a partitioned disk image
    :type disk_img: str
    :returns: True if it was successful, False if something went wrong

    Include /boot, swap, and / partitions with fake kernel and /etc/passwd
    """
    try:
        mksparse(disk_img, 42 * 1024**2)
        # Make a /boot, / and swap partitions on it
        dev = parted.getDevice(disk_img)
        disk = parted.freshDisk(dev, "gpt")

        # (start, length, flags, name)
        for start, length, flags, name in [
                  (  1024**2,    1024**2, None, "boot"),
                  (2*1024**2,  2*1024**2, parted.PARTITION_SWAP, "swap"),
                  (4*1024**2, 38*1024**2, None, "root")]:
            geo = parted.Geometry(device=dev, start=start//dev.sectorSize, length=length//dev.sectorSize)
            part = parted.Partition(disk=disk, type=parted.PARTITION_NORMAL, geometry=geo)
            part.getPedPartition().set_name(name)
            disk.addPartition(partition=part)
            if flags:
                part.setFlag(flags)
        disk.commit()
        os.sync()
    except parted.PartedException:
        return False

    # Mount the disk's partitions
    loop_devs = kpartx_disk_img(disk_img)

    try:
        # Format the partitions
        runcmd(["mkfs.ext4", "/dev/mapper/" + loop_devs[0][0]])
        runcmd(["mkswap", "/dev/mapper/" + loop_devs[1][0]])
        runcmd(["mkfs.ext4", "/dev/mapper/" + loop_devs[2][0]])

        # Mount the boot partition and make a fake kernel and initrd
        boot_mnt = mount("/dev/mapper/" + loop_devs[0][0])
        try:
            mkfakebootdir(boot_mnt)
        finally:
            umount(boot_mnt)

        # Mount the / partition and make a fake / filesystem with /etc/passwd
        root_mnt = mount("/dev/mapper/" + loop_devs[2][0])
        try:
            mkfakerootdir(root_mnt)
        finally:
            umount(root_mnt)
    except Exception:
        return False
    finally:
        # Remove the disk's mounted partitions
        runcmd(["kpartx", "-d", "-s", disk_img])

    return True
Ejemplo n.º 3
0
 def dmdev_test(self):
     """Test the DMDev context manager (requires device-mapper support)"""
     with tempfile.NamedTemporaryFile(prefix="lorax.test.disk.") as disk_img:
         mksparse(disk_img.name, 42 * 1024**2)
         with LoopDev(disk_img.name) as loop_dev:
             self.assertTrue(loop_dev  is not None)
             with DMDev(loop_dev, 42 * 1024**2) as dm_name:
                 self.assertTrue(dm_name is not None)
Ejemplo n.º 4
0
 def mount_test(self):
     """Test the Mount context manager (requires loop)"""
     with tempfile.NamedTemporaryFile(prefix="lorax.test.disk.") as disk_img:
         mksparse(disk_img.name, 42 * 1024**2)
         runcmd(["mkfs.ext4", "-L", "Anaconda", "-b", "4096", "-m", "0", disk_img.name])
         with LoopDev(disk_img.name) as loopdev:
             self.assertTrue(loopdev is not None)
             with Mount(loopdev) as mnt:
                 self.assertTrue(mnt is not None)
Ejemplo n.º 5
0
 def loop_test(self):
     """Test the loop_* functions (requires loop support)"""
     with tempfile.NamedTemporaryFile(prefix="lorax.test.disk.") as disk_img:
         mksparse(disk_img.name, 42 * 1024**2)
         loop_dev = loop_attach(disk_img.name)
         try:
             self.assertTrue(loop_dev is not None)
             self.assertEqual(loop_dev[5:], get_loop_name(disk_img.name))
         finally:
             loop_detach(loop_dev)
Ejemplo n.º 6
0
 def dm_test(self):
     """Test the dm_* functions (requires device-mapper support)"""
     with tempfile.NamedTemporaryFile(prefix="lorax.test.disk.") as disk_img:
         mksparse(disk_img.name, 42 * 1024**2)
         with LoopDev(disk_img.name) as loop_dev:
             self.assertTrue(loop_dev  is not None)
             dm_name = dm_attach(loop_dev, 42 * 1024**2)
             try:
                 self.assertTrue(dm_name is not None)
             finally:
                 dm_detach(dm_name)
Ejemplo n.º 7
0
    def make_squashfs_test(self):
        """Test making a squashfs image"""
        with tempfile.TemporaryDirectory(prefix="lorax.test.") as work_dir:
            with tempfile.NamedTemporaryFile(
                    prefix="lorax.test.disk.") as disk_img:
                # Make a small ext4 disk image
                mksparse(disk_img.name, 42 * 1024**2)
                runcmd([
                    "mkfs.ext4", "-L", "Anaconda", "-b", "4096", "-m", "0",
                    disk_img.name
                ])
                opts = DataHolder(compression="xz", arch="x86_64")
                make_squashfs(opts, disk_img.name, work_dir)

                # Make sure it made an install.img
                self.assertTrue(
                    os.path.exists(joinpaths(work_dir, "images/install.img")))

                # Make sure it looks like a squashfs filesystem
                file_details = get_file_magic(
                    joinpaths(work_dir, "images/install.img"))
                self.assertTrue("Squashfs" in file_details)
Ejemplo n.º 8
0
def novirt_install(opts, disk_img, disk_size, cancel_func=None):
    """
    Use Anaconda to install to a disk image

    :param opts: options passed to livemedia-creator
    :type opts: argparse options
    :param str disk_img: The full path to the disk image to be created
    :param int disk_size: The size of the disk_img in MiB
    :param cancel_func: Function that returns True to cancel build
    :type cancel_func: function

    This method runs anaconda to create the image and then based on the opts
    passed creates a qemu disk image or tarfile.
    """
    dirinstall_path = ROOT_PATH

    # Clean up /tmp/ from previous runs to prevent stale info from being used
    for path in ["/tmp/yum.repos.d/", "/tmp/yum.cache/"]:
        if os.path.isdir(path):
            shutil.rmtree(path)

    args = ["--kickstart", opts.ks[0], "--cmdline", "--loglevel", "debug"]
    if opts.anaconda_args:
        for arg in opts.anaconda_args:
            args += arg.split(" ", 1)
    if opts.proxy:
        args += ["--proxy", opts.proxy]
    if opts.armplatform:
        args += ["--armplatform", opts.armplatform]

    if opts.make_iso or opts.make_fsimage or opts.make_pxe_live:
        # Make a blank fs image
        args += ["--dirinstall"]

        mkext4img(None,
                  disk_img,
                  label=opts.fs_label,
                  size=disk_size * 1024**2)
        if not os.path.isdir(dirinstall_path):
            os.mkdir(dirinstall_path)
        mount(disk_img, opts="loop", mnt=dirinstall_path)
    elif opts.make_tar or opts.make_oci:
        # Install under dirinstall_path, make sure it starts clean
        if os.path.exists(dirinstall_path):
            shutil.rmtree(dirinstall_path)

        if opts.make_oci:
            # OCI installs under /rootfs/
            dirinstall_path = joinpaths(dirinstall_path, "rootfs")
            args += ["--dirinstall", dirinstall_path]
        else:
            args += ["--dirinstall"]

        os.makedirs(dirinstall_path)
    else:
        args += ["--image", disk_img]

        # Create the sparse image
        mksparse(disk_img, disk_size * 1024**2)

    log_monitor = LogMonitor(timeout=opts.timeout)
    args += ["--remotelog", "%s:%s" % (log_monitor.host, log_monitor.port)]
    cancel_funcs = [log_monitor.server.log_check]
    if cancel_func is not None:
        cancel_funcs.append(cancel_func)

    # Make sure anaconda has the right product and release
    log.info("Running anaconda.")
    try:
        unshare_args = [
            "--pid", "--kill-child", "--mount", "--propagation", "unchanged",
            "anaconda"
        ] + args
        for line in execReadlines(
                "unshare",
                unshare_args,
                reset_lang=False,
                env_add={
                    "ANACONDA_PRODUCTNAME": opts.project,
                    "ANACONDA_PRODUCTVERSION": opts.releasever
                },
                callback=lambda p: not novirt_cancel_check(cancel_funcs, p)):
            log.info(line)

        # Make sure the new filesystem is correctly labeled
        setfiles_args = [
            "-e", "/proc", "-e", "/sys",
            "/etc/selinux/targeted/contexts/files/file_contexts", "/"
        ]

        if "--dirinstall" in args:
            # setfiles may not be available, warn instead of fail
            try:
                execWithRedirect("setfiles",
                                 setfiles_args,
                                 root=dirinstall_path)
            except (subprocess.CalledProcessError, OSError) as e:
                log.warning("Running setfiles on install tree failed: %s",
                            str(e))
        else:
            with PartitionMount(disk_img) as img_mount:
                if img_mount and img_mount.mount_dir:
                    try:
                        execWithRedirect("setfiles",
                                         setfiles_args,
                                         root=img_mount.mount_dir)
                    except (subprocess.CalledProcessError, OSError) as e:
                        log.warning(
                            "Running setfiles on install tree failed: %s",
                            str(e))

                    # For image installs, run fstrim to discard unused blocks. This way
                    # unused blocks do not need to be allocated for sparse image types
                    execWithRedirect("fstrim", [img_mount.mount_dir])

    except (subprocess.CalledProcessError, OSError) as e:
        log.error("Running anaconda failed: %s", e)
        raise InstallError("novirt_install failed")
    finally:
        log_monitor.shutdown()

        # Move the anaconda logs over to a log directory
        log_dir = os.path.abspath(os.path.dirname(opts.logfile))
        log_anaconda = joinpaths(log_dir, "anaconda")
        if not os.path.isdir(log_anaconda):
            os.mkdir(log_anaconda)
        for l in glob.glob("/tmp/*log") + glob.glob("/tmp/anaconda-tb-*"):
            shutil.copy2(l, log_anaconda)
            os.unlink(l)

        # Make sure any leftover anaconda mounts have been cleaned up
        if not anaconda_cleanup(dirinstall_path):
            raise InstallError(
                "novirt_install cleanup of anaconda mounts failed.")

        if not opts.make_iso and not opts.make_fsimage and not opts.make_pxe_live:
            dm_name = os.path.splitext(os.path.basename(disk_img))[0]

            # Remove device-mapper for partitions and disk
            log.debug("Removing device-mapper setup on %s", dm_name)
            for d in sorted(glob.glob("/dev/mapper/" + dm_name + "*"),
                            reverse=True):
                dm_detach(d)

            log.debug("Removing loop device for %s", disk_img)
            loop_detach("/dev/" + get_loop_name(disk_img))

    # qemu disk image is used by bare qcow2 images and by Vagrant
    if opts.image_type:
        log.info("Converting %s to %s", disk_img, opts.image_type)
        qemu_args = []
        for arg in opts.qemu_args:
            qemu_args += arg.split(" ", 1)

        # convert the image to the selected format
        if "-O" not in qemu_args:
            qemu_args.extend(["-O", opts.image_type])
        qemu_img = tempfile.mktemp(prefix="lmc-disk-", suffix=".img")
        execWithRedirect("qemu-img",
                         ["convert"] + qemu_args + [disk_img, qemu_img],
                         raise_err=True)
        if not opts.make_vagrant:
            execWithRedirect("mv", ["-f", qemu_img, disk_img], raise_err=True)
        else:
            # Take the new qcow2 image and package it up for Vagrant
            compress_args = []
            for arg in opts.compress_args:
                compress_args += arg.split(" ", 1)

            vagrant_dir = tempfile.mkdtemp(prefix="lmc-tmpdir-")
            metadata_path = joinpaths(vagrant_dir, "metadata.json")
            execWithRedirect(
                "mv", ["-f", qemu_img,
                       joinpaths(vagrant_dir, "box.img")],
                raise_err=True)
            if opts.vagrant_metadata:
                shutil.copy2(opts.vagrant_metadata, metadata_path)
            else:
                create_vagrant_metadata(metadata_path)
            update_vagrant_metadata(metadata_path, disk_size)
            if opts.vagrantfile:
                shutil.copy2(opts.vagrantfile,
                             joinpaths(vagrant_dir, "vagrantfile"))

            log.info("Creating Vagrant image")
            rc = mktar(vagrant_dir,
                       disk_img,
                       opts.compression,
                       compress_args,
                       selinux=False)
            if rc:
                raise InstallError("novirt_install mktar failed: rc=%s" % rc)
            shutil.rmtree(vagrant_dir)
    elif opts.make_tar:
        compress_args = []
        for arg in opts.compress_args:
            compress_args += arg.split(" ", 1)

        rc = mktar(dirinstall_path, disk_img, opts.compression, compress_args)
        shutil.rmtree(dirinstall_path)

        if rc:
            raise InstallError("novirt_install mktar failed: rc=%s" % rc)
    elif opts.make_oci:
        # An OCI image places the filesystem under /rootfs/ and adds the json files at the top
        # And then creates a tar of the whole thing.
        compress_args = []
        for arg in opts.compress_args:
            compress_args += arg.split(" ", 1)

        shutil.copy2(opts.oci_config, ROOT_PATH)
        shutil.copy2(opts.oci_runtime, ROOT_PATH)
        rc = mktar(ROOT_PATH, disk_img, opts.compression, compress_args)

        if rc:
            raise InstallError("novirt_install mktar failed: rc=%s" % rc)
    else:
        # For raw disk images, use fallocate to deallocate unused space
        execWithRedirect("fallocate", ["--dig-holes", disk_img],
                         raise_err=True)
Ejemplo n.º 9
0
    def __init__(self,
                 opts,
                 iso,
                 ks_paths,
                 disk_img,
                 img_size=2048,
                 kernel_args=None,
                 memory=1024,
                 vcpus=None,
                 vnc=None,
                 arch=None,
                 cancel_func=None,
                 virtio_host="127.0.0.1",
                 virtio_port=6080,
                 image_type=None,
                 boot_uefi=False,
                 ovmf_path=None):
        """
        Start the installation

        :param iso: Information about the iso to use for the installation
        :type iso: IsoMountpoint
        :param list ks_paths: Paths to kickstart files. All are injected, the
           first one is the one executed.
        :param str disk_img: Path to a disk image, created it it doesn't exist
        :param int img_size: The image size, in MiB, to create if it doesn't exist
        :param str kernel_args: Extra kernel arguments to pass on the kernel cmdline
        :param int memory: Amount of RAM to assign to the virt, in MiB
        :param int vcpus: Number of virtual cpus
        :param str vnc: Arguments to pass to qemu -display
        :param str arch: Optional architecture to use in the virt
        :param cancel_func: Function that returns True if the installation fails
        :type cancel_func: function
        :param str virtio_host: Hostname to connect virtio log to
        :param int virtio_port: Port to connect virtio log to
        :param str image_type: Type of qemu-img disk to create, or None.
        :param bool boot_uefi: Use OVMF to boot the VM in UEFI mode
        :param str ovmf_path: Path to the OVMF firmware
        """
        # Lookup qemu-system- for arch if passed, or try to guess using host arch
        qemu_cmd = [
            self.QEMU_CMDS.get(arch or os.uname().machine,
                               "qemu-system-" + os.uname().machine)
        ]
        if not os.path.exists("/usr/bin/" + qemu_cmd[0]):
            raise InstallError("%s does not exist, cannot run qemu" %
                               qemu_cmd[0])

        qemu_cmd += ["-no-user-config"]
        qemu_cmd += ["-m", str(memory)]
        if vcpus:
            qemu_cmd += ["-smp", str(vcpus)]

        if not opts.no_kvm and os.path.exists("/dev/kvm"):
            qemu_cmd += ["--machine", "accel=kvm"]

        # Copy the initrd from the iso, create a cpio archive of the kickstart files
        # and append it to the temporary initrd.
        qemu_initrd = append_initrd(iso.initrd, ks_paths)
        qemu_cmd += ["-kernel", iso.kernel]
        qemu_cmd += ["-initrd", qemu_initrd]

        # Add the disk and cdrom
        if not os.path.isfile(disk_img):
            mksparse(disk_img, img_size * 1024**2)
        drive_args = "file=%s" % disk_img
        drive_args += ",cache=unsafe,discard=unmap"
        if image_type:
            drive_args += ",format=%s" % image_type
        else:
            drive_args += ",format=raw"
        qemu_cmd += ["-drive", drive_args]

        drive_args = "file=%s,media=cdrom,readonly=on" % iso.iso_path
        qemu_cmd += ["-drive", drive_args]

        # Setup the cmdline args
        # ======================
        cmdline_args = "ks=file:/%s" % os.path.basename(ks_paths[0])
        cmdline_args += " inst.stage2=hd:LABEL=%s" % udev_escape(iso.label)
        if opts.proxy:
            cmdline_args += " inst.proxy=%s" % opts.proxy
        if kernel_args:
            cmdline_args += " " + kernel_args
        cmdline_args += " inst.text inst.cmdline"

        qemu_cmd += ["-append", cmdline_args]

        if not opts.vnc:
            vnc_port = find_free_port()
            if vnc_port == -1:
                raise InstallError("No free VNC ports")
            display_args = "vnc=127.0.0.1:%d" % (vnc_port - 5900)
        else:
            display_args = opts.vnc
        log.info("qemu %s", display_args)
        qemu_cmd += ["-nographic", "-display", display_args]

        # Setup the virtio log port
        qemu_cmd += ["-device", "virtio-serial-pci,id=virtio-serial0"]
        qemu_cmd += [
            "-device",
            "virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0"
            ",id=channel0,name=org.fedoraproject.anaconda.log.0"
        ]
        qemu_cmd += [
            "-chardev",
            "socket,id=charchannel0,host=%s,port=%s" %
            (virtio_host, virtio_port)
        ]

        # PAss through rng from host
        if opts.with_rng != "none":
            qemu_cmd += [
                "-object",
                "rng-random,id=virtio-rng0,filename=%s" % opts.with_rng
            ]
            qemu_cmd += [
                "-device",
                "virtio-rng-pci,rng=virtio-rng0,id=rng0,bus=pci.0,addr=0x9"
            ]

        if boot_uefi and ovmf_path:
            qemu_cmd += [
                "-drive",
                "file=%s/OVMF_CODE.fd,if=pflash,format=raw,unit=0,readonly=on"
                % ovmf_path
            ]

            # Make a copy of the OVMF_VARS.fd for this run
            ovmf_vars = tempfile.mktemp(prefix="lmc-OVMF_VARS-", suffix=".fd")
            shutil.copy2(joinpaths(ovmf_path, "/OVMF_VARS.fd"), ovmf_vars)

            qemu_cmd += [
                "-drive",
                "file=%s,if=pflash,format=raw,unit=1" % ovmf_vars
            ]

        log.info("Running qemu")
        log.debug(qemu_cmd)
        try:
            execWithRedirect(qemu_cmd[0],
                             qemu_cmd[1:],
                             reset_lang=False,
                             raise_err=True,
                             callback=lambda p: not cancel_func())
        except subprocess.CalledProcessError as e:
            log.error("Running qemu failed:")
            log.error("cmd: %s", " ".join(e.cmd))
            log.error("output: %s", e.output or "")
            raise InstallError("QEMUInstall failed")
        except (OSError, KeyboardInterrupt) as e:
            log.error("Running qemu failed: %s", str(e))
            raise InstallError("QEMUInstall failed")
        finally:
            os.unlink(qemu_initrd)
            if boot_uefi and ovmf_path:
                os.unlink(ovmf_vars)

        if cancel_func():
            log.error("Installation error detected. See logfile for details.")
            raise InstallError("QEMUInstall failed")
        else:
            log.info("Installation finished without errors.")
Ejemplo n.º 10
0
 def mksparse_test(self):
     """Test mksparse function"""
     with tempfile.NamedTemporaryFile(
             prefix="lorax.test.disk.") as disk_img:
         mksparse(disk_img.name, 42 * 1024**2)
         self.assertEqual(os.stat(disk_img.name).st_size, 42 * 1024**2)