Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix [#370 #369 #368]: Remove conflicting LVM objects #373

Merged
merged 2 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions vanilla_installer/core/disks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
import subprocess
import json

class Diskutils:
@staticmethod
Expand All @@ -12,6 +13,36 @@ def pretty_size(size: int) -> str:
return f"{round(size / 1024, 2)} KB"
else:
return f"{size} B"

@staticmethod
def separate_device_and_partn(part_dev: str) -> tuple[str, str|None]:
info_json = subprocess.check_output(
"lsblk --json -o NAME,PKNAME,PARTN " + part_dev, shell=True
).decode("utf-8")
info_multiple = json.loads(info_json)["blockdevices"]

if len(info_multiple) > 1:
raise ValueError(f'{part_dev} returned more than one device')
info = info_multiple[0]

if info["partn"] == None:
# part_dev is actually a device, not a partition
return "/dev/" + info["name"], None

return "/dev/" + info["pkname"], str(info["partn"])

@staticmethod
def fetch_lvm_pvs() -> list[list[str]]:
output_json = subprocess.check_output(
"sudo pvs --reportformat=json", shell=True
).decode("utf-8")
output_pvs = json.loads(output_json)["report"][0]["pv"]
pv_with_vgs = []
for pv_output in output_pvs:
pv_name = pv_output["pv_name"]
vg_name = pv_output["vg_name"] if pv_output["vg_name"] != "" else None
pv_with_vgs.append([pv_name, vg_name])
return pv_with_vgs

class Disk:
def __init__(self, disk: str):
Expand Down
58 changes: 56 additions & 2 deletions vanilla_installer/defaults/disk.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,16 +518,27 @@ def set_btn_apply_sensitive(self, val):
def partition_recipe(self):
recipe = {}

pv_list = Diskutils.fetch_lvm_pvs()

for __, info in self.__partition_selector.selected_partitions.items():
# Partition can be None if user didn't configure swap
if not isinstance(info["partition"], Partition):
continue

pv_to_remove = None
vg_to_remove = None
for pv, vg in pv_list:
if pv == info["partition"].partition:
pv_to_remove = pv
vg_to_remove = vg

recipe[info["partition"].partition] = {
"fs": info["fstype"],
"mp": info["mountpoint"],
"pretty_size": info["partition"].pretty_size,
"size": info["partition"].size,
"existing_pv": pv_to_remove,
"existing_vg": vg_to_remove
}

return recipe
Expand Down Expand Up @@ -557,7 +568,6 @@ def __init__(self, window, partition_recipe, **kwargs):
entry.set_title(partition_recipe[partition]["disk"])
entry.set_subtitle(_("Entire disk will be used."))
else:
self.set_default_size(self.default_width, 650)
if partition == "disk":
continue
entry.set_title(partition)
Expand All @@ -567,7 +577,39 @@ def __init__(self, window, partition_recipe, **kwargs):
partition_recipe[partition]["mp"],
)
)
self.group_partitions.add(entry)
self.group_partitions.add(entry)

if "auto" in partition_recipe:
for vg in partition_recipe["auto"]["vgs_to_remove"]:
entry = Adw.ActionRow()
entry.set_title("LVM volume group: " + vg)
entry.set_subtitle(_("Volume group will be removed."))
self.group_partitions.add(entry)
for pv in partition_recipe["auto"]["pvs_to_remove"]:
entry = Adw.ActionRow()
entry.set_title("LVM physical volume: " + pv)
entry.set_subtitle(_("Physical volume will be removed."))
self.group_partitions.add(entry)
else:
vgs_to_remove = []
for partition, values in partition_recipe.items():
if partition == "disk":
continue
pv = values["existing_pv"]
vg = values["existing_vg"]
if pv is None:
continue
if vg is not None and vg not in vgs_to_remove:
vgs_to_remove.append(values["existing_vg"])
entry = Adw.ActionRow()
entry.set_title("LVM physical volume: " + pv)
entry.set_subtitle(_("Physical volume will be removed."))
self.group_partitions.add(entry)
for vg in vgs_to_remove:
entry = Adw.ActionRow()
entry.set_title("LVM volume group: " + vg)
entry.set_subtitle(_("Volume group will be removed."))
self.group_partitions.add(entry)

def __on_btn_cancel_clicked(self, widget):
self.destroy()
Expand Down Expand Up @@ -626,11 +668,23 @@ def __on_modal_close_request(self, *args):
self.btn_next.set_sensitive(self.__partition_recipe is not None)

def __on_auto_clicked(self, button):
pvs_to_remove = []
vgs_to_remove = []
for pv, vg in Diskutils.fetch_lvm_pvs():
pv_disk, _ = Diskutils.separate_device_and_partn(pv)
if pv_disk != self.__selected_disks[0].disk:
continue
pvs_to_remove.append(pv)
if vg is not None and vg not in vgs_to_remove:
vgs_to_remove.append(vg)

self.__partition_recipe = {
"auto": {
"disk": self.__selected_disks[0].disk,
"pretty_size": self.__selected_disks[0].pretty_size,
"size": self.__selected_disks[0].size,
"vgs_to_remove": vgs_to_remove,
"pvs_to_remove": pvs_to_remove,
}
}
modal = VanillaDefaultDiskConfirmModal(self.__window, self.__partition_recipe)
Expand Down
32 changes: 17 additions & 15 deletions vanilla_installer/gtk/dialog-disk-confirm.ui
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<property name="modal">true</property>
<property name="deletable">false</property>
<property name="default-width">500</property>
<property name="default-height">450</property>
<property name="default-height">650</property>
<child>
<object class="GtkBox">
<property name="orientation">vertical</property>
Expand All @@ -25,25 +25,27 @@
<property name="title" translatable="yes">Confirm Changes</property>
<property name="description" translatable="yes">The following changes will be made to your disk. Please review them carefully.</property>
<child>
<object class="AdwPreferencesPage">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="spacing">20</property>
<child>
<object class="AdwPreferencesGroup" id="group_partitions"></object>
</child>
<child>
<object class="AdwPreferencesGroup">
<object class="AdwPreferencesPage">
<child>
<object class="GtkButton" id="btn_apply">
<property name="label" translatable="yes">Confirm Changes</property>
<property name="valign">center</property>
<property name="halign">center</property>
<style>
<class name="destructive-action"/>
<class name="pill"/>
</style>
</object>
<object class="AdwPreferencesGroup" id="group_partitions"></object>
</child>
</object>
</child>
<child>
<object class="GtkButton" id="btn_apply">
<property name="label" translatable="yes">Confirm Changes</property>
<property name="valign">center</property>
<property name="halign">center</property>
<style>
<class name="destructive-action"/>
<class name="pill"/>
</style>
</object>
</child>
</object>
</child>
</object>
Expand Down
52 changes: 36 additions & 16 deletions vanilla_installer/utils/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from typing import Any, Union

from vanilla_installer.core.system import Systeminfo
from vanilla_installer.core.disks import Diskutils

logger = logging.getLogger("Installer::Processor")

Expand Down Expand Up @@ -241,12 +242,20 @@ def merge_postinstall_steps(self):
class Processor:
@staticmethod
def __gen_auto_partition_steps(
disk: str, encrypt: bool, root_size: int, password: str | None = None
disk: str, encrypt: bool, root_size: int,
existing_pvs: list[str] | None, existing_vgs: list[str] | None,
password: str | None = None,
):
setup_steps = []
mountpoints = []
post_install_steps = []

# Before we do anything, we need to remove conflicting LVM objects
for vg in existing_vgs:
setup_steps.append([disk, "vgremove", [vg]])
for pv in existing_pvs:
setup_steps.append([disk, "pvremove", [pv]])

setup_steps.append([disk, "label", ["gpt"]])
# Boot
setup_steps.append([disk, "mkpart", ["vos-boot", "ext4", 1, 1025]])
Expand Down Expand Up @@ -322,14 +331,30 @@ def __gen_manual_partition_steps(
mountpoints = []
post_install_steps = []

# Before we do anything, we need to remove conflicting LVM objects
vgs_to_remove = []
pvs_to_remove = []
for part, values in disk_final.items():
pv = values["existing_pv"]
vg = values["existing_vg"]
if pv is None:
continue
disk, _ = Diskutils.separate_device_and_partn(pv)
pvs_to_remove.append([pv, disk])
if vg is not None and vg not in vgs_to_remove:
vgs_to_remove.append([vg, disk])

for vg, disk in vgs_to_remove:
setup_steps.append([disk, "vgremove", [vg]])
for pv, disk in pvs_to_remove:
setup_steps.append([disk, "pvremove", [pv]])


# Since manual partitioning uses GParted to handle partitions (for now),
# we don't need to create any partitions or label disks (for now).
# But we still need to format partitions.
for part, values in disk_final.items():
disk_regex = r"^/dev/[a-zA-Z]+([0-9]+[a-z][0-9]+)?"
part_regex = r".*[a-z]([0-9]+)"
part_disk = re.match(disk_regex, part, re.MULTILINE)[0]
part_number = re.sub(part_regex, r"\1", part)
part_disk, part_number = Diskutils.separate_device_and_partn(part)

def setup_partition(
part_name: str, encrypt: bool = False, password: str = None
Expand All @@ -346,14 +371,9 @@ def setup_partition(
mountpoints.append([part, values["mp"]])

if values["mp"] == "/":
part_prefix = (
f"{part_disk}p"
if re.match(r"[0-9]", part_disk[-1])
else f"{part_disk}"
)
setup_steps.append([part_disk, "pvcreate", [part_prefix + part_number]])
setup_steps.append([part_disk, "pvcreate", [part]])
setup_steps.append(
[part_disk, "vgcreate", ["vos-root", [part_prefix + part_number]]]
[part_disk, "vgcreate", ["vos-root", [part]]]
)
setup_steps.append(
[part_disk, "lvcreate", ["init", "vos-root", "linear", 512]]
Expand Down Expand Up @@ -466,7 +486,9 @@ def gen_install_recipe(log_path, finals, sys_recipe):
if "disk" in final.keys():
if "auto" in final["disk"].keys():
part_info = Processor.__gen_auto_partition_steps(
final["disk"]["auto"]["disk"], encrypt, root_size, password
final["disk"]["auto"]["disk"], encrypt, root_size,
final["disk"]["auto"]["pvs_to_remove"], final["disk"]["auto"]["vgs_to_remove"],
password,
)
else:
part_info = Processor.__gen_manual_partition_steps(
Expand Down Expand Up @@ -498,9 +520,7 @@ def gen_install_recipe(log_path, finals, sys_recipe):
root_b_part,
var_part,
) = Processor.__find_partitions(recipe)
boot_disk = re.match(
r"^/dev/[a-zA-Z]+([0-9]+[a-z][0-9]+)?", boot_part, re.MULTILINE
)[0]
boot_disk, _ = Diskutils.separate_device_and_partn(boot_part)

# Create SystemD units to setup mountpoints
extra_target = "cryptsetup" if encrypt else ""
Expand Down