def blkid(self): """Return the blkid output of each device in devices {"<device>": {"UUID": "<UUID">", "TYPE": "<TYPE>"} blkid is used to get the filesystem and UUID of a block device """ if self._blkid: return self._blkid self.debug_action(action="GET BLKID DATA") _blkid = {} blkid_lines = run("blkid") def blkid_val(line, prop): """ Return the blkid property value if present else none """ word = next( (elem for elem in line.split(" ") if f"{prop}=" in elem), None) if word: return word.split("=")[1].replace('"', "") return None for line in blkid_lines: split = line.split(" ") if not split[0]: continue path = split[0].replace(":", "") uuid = blkid_val(line, "UUID") type_ = blkid_val(line, "TYPE") _blkid[path] = {"UUID": uuid, "TYPE": type_} self._blkid = _blkid for device in _blkid: debug(f"{device}: {_blkid[device]}") self.debug_action(end=True) return _blkid
def lvm_lvs(self): """Return a dict of of LVM logical volumes on the given devices {"<device mapper path>": { name: "<name>", devices: [<partitions/PVs>] }} """ if self._lvm_lvs: return self._lvm_lvs self.debug_action(action="FIND LVM LV's") lvs = {} for lvm_pv in self.lvm_pvs: pv_lv_lines = grep(f"pvdisplay -m {lvm_pv}", "Logical volume") # pv_lv_lines looks like this: [' Logical volume\t/dev/vg_rhel610/lv_root'] for pv_lv in pv_lv_lines: # example's name is /dev/vg_rhel610/lv_root name = pv_lv.strip().split("\t")[1] dm_path = run(f"lvdisplay -C -o lv_dm_path {name}")[1].strip() if dm_path not in lvs: # First time encountering this LV lvs[dm_path] = {"name": name, "devices": [lvm_pv]} else: # This LV was in a prior device, just add this device to it lvs[dm_path]["devices"].append(lvm_pv) self._lvm_lvs = lvs debug(f"lvs: {list(lvs)}") self.debug_action(end=True) return lvs
def get_vmdk_thick_size(file_path): """ Return the 'thick' size of a VMDK file in bytes as an integer - requires qemu-utils """ qemu_img_lines = run(f"qemu-img info {file_path}") vsize_line = next(line for line in qemu_img_lines if "virtual size" in line) size_bytes = int(vsize_line.split(" ")[-2].replace("(", "")) return size_bytes
def repair_partitions(self): """ Repair a given partition using the appropriate tool""" if is_mounted(self.ROOT_MOUNT): error("ERROR: Cannot repair partitions when they are mounted", exit=True) for partition in self.data_volumes: filesystem = self.blkid[partition]["TYPE"] if filesystem == "xfs": print(f" > Repairing XFS partition {partition}") run(f"xfs_repair {partition}") elif "ext" in filesystem: print(f" > Repairing {filesystem} partition {partition}") repair_cmd = f"fsck.{filesystem} -y {partition}" run(repair_cmd) else: print( f" ! Cannot repair {partition} - unsupported filesystem: {filesystem}" )
def lvm_pvs(self): """ Return a list of physical volumes (partitions) from LVM that match given devices """ if self._lvm_pvs: return self._lvm_pvs self.debug_action(action="FIND LVM PV's") pvs = [] pvs_lines = run("pvs") for line in pvs_lines: partition = line.strip().split(" ")[0] if "/dev/" not in partition: continue if partition in self.fdisk_partitions: pvs.append(partition) self._lvm_pvs = pvs self.debug_action(end=True) return pvs
def fdisk_partitions(self): """ return list of partitions on devices """ if self._fdisk_partitions: return self._fdisk_partitions self.debug_action(action="FIND FDISK PARTITIONS") partitions = [] if not self.devices: error( "ERROR: Cannot list partitions when devices are not specified", exit=True) for device in self.devices: fdisk = run(f"fdisk -l {device}") partition_lines = (line for line in fdisk if line.startswith(device)) for partition_line in partition_lines: partitions.append(partition_line.split(" ")[0]) self._fdisk_partitions = partitions debug(f"fdisk_partitions: {partitions}") self.debug_action(end=True) return partitions
def boot_mode(self): """ Return either "UEFI" or "BIOS" - Determine how this device boots """ if self._boot_mode: return self._boot_mode self.debug_action(action="FIND BOOT MODE") # Get the disk of the boot partition, ex /dev/vdb for /dev/vdb1 drive = "".join( [char for char in self.boot_volume if not char.isdigit()]) # Read fdisk's Disklabel for the disk fdisk = run(f"fdisk -l {drive}") disk_type_line = next( (line for line in fdisk if "Disklabel type" in line), None) if disk_type_line is None: error( f"Error: Failed to determine boot mode of {self.boot_volume}", exit=True) disk_type = disk_type_line.split(" ")[-1] _boot_mode = "UEFI" if (disk_type == "gpt") else "BIOS" self._boot_mode = _boot_mode self.debug_action(end=True) return _boot_mode
def add_virtio_drivers(self, force=False): """ Install VirtIO drivers to mounted system """ if not self.was_root_mounted: error("ERROR: You must mount the volumes before you can add virtio drivers", exit=True) self.debug_action(action="ADD VIRTIO DRIVERS") ls_boot_lines = run(f"ls {self.ROOT_MOUNT}/boot") initram_lines = [ line for line in ls_boot_lines if line.startswith("initramfs-") and line.endswith(".img") and "dump" not in line ] for filename in initram_lines: if "rescue" in filename or "kdump" in filename: debug(f"Skipping rescue/kdump file: {filename}") continue kernel_version = filename.replace("initramfs-", "").replace(".img", "") debug("Running lsinitrd to check for virtio drivers") lsinitrd = self.chroot_run(f"lsinitrd /boot/{filename}") virtio_line = next((line for line in lsinitrd if "virtio" in line.lower()), None) if virtio_line is not None: print(f"{filename} already has virtio drivers") if force: print("force=true, reinstalling") else: continue print(f"Adding virtio drivers to {filename}") drivers = "virtio_blk virtio_net virtio_scsi virtio_balloon" cmd = f'dracut --add-drivers "{drivers}" -f /boot/{filename} {kernel_version}' # Python+chroot causes dracut space delimiter to break - use a script file script_file = f"{self.ROOT_MOUNT}/virtio.sh" debug(f"writing script file: {script_file}") debug(f"script file contents: {cmd}") set_file_contents(script_file, cmd) self.chroot_run("bash /virtio.sh") debug(f"deleting script file: {script_file}") os.remove(script_file) self.debug_action(end=True)
def download_thread(url, file_path): """ Start a thread to download the file """ run(f"wget --quiet --no-check-certificate {url} -O {file_path}")
def chroot_run(self, cmd): """ Run a command in the chroot """ if not is_mounted(self.ROOT_MOUNT): error("ERROR: Root volume not mounted", exit=True) return run(f"chroot {self.ROOT_MOUNT} {cmd}")