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))
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
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)
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)
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)
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)
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)
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)
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.")
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)