diff --git a/ui/webui/src/components/storage/InstallationScenario.jsx b/ui/webui/src/components/storage/InstallationScenario.jsx
index 89fd7c6f128..1a6a0a4b43f 100644
--- a/ui/webui/src/components/storage/InstallationScenario.jsx
+++ b/ui/webui/src/components/storage/InstallationScenario.jsx
@@ -84,12 +84,12 @@ const checkUseFreeSpace = ({ diskFreeSpace, diskTotalSpace, requiredSize }) => {
return availability;
};
-const checkMountPointMapping = ({ hasPartitions }) => {
+const checkMountPointMapping = ({ hasFilesystems }) => {
const availability = new AvailabilityState();
- if (!hasPartitions) {
+ if (!hasFilesystems) {
availability.available = false;
- availability.reason = _("No existing partitions on the selected disks.");
+ availability.reason = _("No usable devices on the selected disks.");
} else {
availability.available = true;
}
@@ -163,7 +163,7 @@ const InstallationScenarioSelector = ({ deviceData, selectedDisks, idPrefix, sto
const [requiredSize, setRequiredSize] = useState();
const [diskTotalSpace, setDiskTotalSpace] = useState();
const [diskFreeSpace, setDiskFreeSpace] = useState();
- const [hasPartitions, setHasPartitions] = useState();
+ const [hasFilesystems, setHasFilesystems] = useState();
useEffect(() => {
const updateSizes = async () => {
@@ -187,22 +187,22 @@ const InstallationScenarioSelector = ({ deviceData, selectedDisks, idPrefix, sto
}, []);
useEffect(() => {
- const hasPartitions = selectedDisks.some(device => deviceData[device]?.children.v.some(child => deviceData[child]?.type.v === "partition"));
+ const hasFilesystems = selectedDisks.some(device => deviceData[device]?.children.v.some(child => deviceData[child]?.formatData.mountable.v || deviceData[child]?.formatData.type.v === "luks"));
- setHasPartitions(hasPartitions);
+ setHasFilesystems(hasFilesystems);
}, [selectedDisks, deviceData]);
useEffect(() => {
let selectedScenarioId = "";
let availableScenarioExists = false;
- if ([diskTotalSpace, diskFreeSpace, hasPartitions, requiredSize].some(itm => itm === undefined)) {
+ if ([diskTotalSpace, diskFreeSpace, hasFilesystems, requiredSize].some(itm => itm === undefined)) {
return;
}
const newAvailability = {};
for (const scenario of scenarios) {
- const availability = scenario.check({ diskTotalSpace, diskFreeSpace, hasPartitions, requiredSize });
+ const availability = scenario.check({ diskTotalSpace, diskFreeSpace, hasFilesystems, requiredSize });
newAvailability[scenario.id] = availability;
if (availability.available) {
availableScenarioExists = true;
@@ -219,7 +219,7 @@ const InstallationScenarioSelector = ({ deviceData, selectedDisks, idPrefix, sto
setSelectedScenario(selectedScenarioId);
setScenarioAvailability(newAvailability);
setIsFormValid(availableScenarioExists);
- }, [deviceData, hasPartitions, requiredSize, diskFreeSpace, diskTotalSpace, setIsFormValid, storageScenarioId]);
+ }, [deviceData, hasFilesystems, requiredSize, diskFreeSpace, diskTotalSpace, setIsFormValid, storageScenarioId]);
useEffect(() => {
const applyScenario = async (scenarioId) => {
diff --git a/ui/webui/src/components/storage/MountPointMapping.jsx b/ui/webui/src/components/storage/MountPointMapping.jsx
index 9acce5984c0..b6b8212aaf6 100644
--- a/ui/webui/src/components/storage/MountPointMapping.jsx
+++ b/ui/webui/src/components/storage/MountPointMapping.jsx
@@ -53,11 +53,6 @@ import "./MountPointMapping.scss";
const _ = cockpit.gettext;
-const requiredMountPointOptions = [
- { value: "/boot", name: "boot" },
- { value: "/", name: "root" },
-];
-
const getDeviceChildren = ({ deviceData, device }) => {
const children = [];
const deviceChildren = deviceData[device]?.children?.v || [];
@@ -73,11 +68,12 @@ const getDeviceChildren = ({ deviceData, device }) => {
return children;
};
-const getInitialRequests = (partitioningData) => {
- const bootOriginalRequest = partitioningData.requests.find(r => r["mount-point"] === "/boot");
- const rootOriginalRequest = partitioningData.requests.find(r => r["mount-point"] === "/");
+const getInitialRequests = (usablePartitioningRequests, requiredMountPoints) => {
+ const bootOriginalRequest = usablePartitioningRequests.find(r => r["mount-point"] === "/boot");
+ const rootOriginalRequest = usablePartitioningRequests.find(r => r["mount-point"] === "/");
+ const bootEfiOriginalRequest = usablePartitioningRequests.find(r => r["mount-point"] === "/boot/efi");
- const requests = requiredMountPointOptions.map((mountPoint, idx) => {
+ const requests = requiredMountPoints.map((mountPoint, idx) => {
const request = ({ "mount-point": mountPoint.value, reformat: mountPoint.name === "root" });
if (mountPoint.name === "boot" && bootOriginalRequest) {
@@ -88,10 +84,14 @@ const getInitialRequests = (partitioningData) => {
return { ...rootOriginalRequest, ...request };
}
+ if (mountPoint.name === "boot-efi" && bootEfiOriginalRequest) {
+ return { ...bootEfiOriginalRequest, ...request };
+ }
+
return request;
});
- const extraRequests = partitioningData.requests.filter(r => r["mount-point"] && r["mount-point"] !== "/" && r["mount-point"] !== "/boot" && r["format-type"] !== "biosboot") || [];
+ const extraRequests = usablePartitioningRequests.filter(r => r["mount-point"] && r["mount-point"] !== "/" && r["mount-point"] !== "/boot" && r["mount-point"] !== "/boot/efi" && r["format-type"] !== "biosboot") || [];
return [...requests, ...extraRequests].map((request, idx) => ({ ...request, "request-id": idx + 1 }));
};
@@ -119,6 +119,23 @@ const isReformatValid = (deviceData, request, requests) => {
});
};
+const isDeviceMountPointInvalid = (deviceData, request) => {
+ const device = request["device-spec"];
+
+ if (!device || !request["mount-point"]) {
+ return [false, ""];
+ }
+
+ // /boot/efi must be on EFI System Partition
+ if (request["mount-point"] === "/boot/efi") {
+ if (deviceData[device].formatData.type.v !== "efi") {
+ return [true, _("/boot/efi must be on a EFI System Partition device")];
+ }
+ }
+
+ return [false, ""];
+};
+
const getLockedLUKSDevices = (requests, deviceData) => {
const devs = requests?.map(r => r["device-spec"]) || [];
@@ -233,6 +250,7 @@ const DeviceColumnSelect = ({ deviceData, devices, idPrefix, lockedLUKSDevices,
const DeviceColumn = ({ deviceData, devices, idPrefix, handleRequestChange, lockedLUKSDevices, request, requests }) => {
const device = request["device-spec"];
const duplicatedDevice = isDuplicateRequestField(requests, "device-spec", device);
+ const [deviceInvalid, errorMessage] = isDeviceMountPointInvalid(deviceData, request);
return (
@@ -250,6 +268,12 @@ const DeviceColumn = ({ deviceData, devices, idPrefix, handleRequestChange, lock
{_("Duplicate device.")}
}
+ {deviceInvalid &&
+
+
+ {errorMessage}
+
+ }
);
};
@@ -314,6 +338,7 @@ const MountPointRowRemove = ({ request, setRequests }) => {
const RequestsTable = ({
allDevices,
deviceData,
+ requiredMountPoints,
handleRequestChange,
idPrefix,
lockedLUKSDevices,
@@ -322,7 +347,7 @@ const RequestsTable = ({
}) => {
const columnClassName = idPrefix + "__column";
const getRequestRow = (request) => {
- const isRequiredMountPoint = !!requiredMountPointOptions.find(val => val.value === request["mount-point"]);
+ const isRequiredMountPoint = !!requiredMountPoints.find(val => val.value === request["mount-point"]);
const duplicatedMountPoint = isDuplicateRequestField(requests, "mount-point", request["mount-point"]);
const rowId = idPrefix + "-row-" + request["request-id"];
@@ -392,19 +417,19 @@ const RequestsTable = ({
);
};
-const MountPointMappingContent = ({ deviceData, partitioningData, dispatch, idPrefix, setIsFormValid, onAddErrorNotification }) => {
+const MountPointMappingContent = ({ deviceData, partitioningData, usablePartitioningRequests, requiredMountPoints, dispatch, idPrefix, setIsFormValid, onAddErrorNotification }) => {
const [skipUnlock, setSkipUnlock] = useState(false);
- const [requests, setRequests] = useState(getInitialRequests(partitioningData));
+ const [requests, setRequests] = useState(getInitialRequests(usablePartitioningRequests, requiredMountPoints));
const [updateRequestCnt, setUpdateRequestCnt] = useState(0);
const currentUpdateRequestCnt = useRef(0);
const allDevices = useMemo(() => {
- return partitioningData.requests?.map(r => r["device-spec"]) || [];
- }, [partitioningData.requests]);
+ return usablePartitioningRequests?.map(r => r["device-spec"]) || [];
+ }, [usablePartitioningRequests]);
const lockedLUKSDevices = useMemo(
- () => getLockedLUKSDevices(partitioningData.requests, deviceData),
- [deviceData, partitioningData.requests]
+ () => getLockedLUKSDevices(usablePartitioningRequests, deviceData),
+ [deviceData, usablePartitioningRequests]
);
const handlePartitioningRequestsChange = useCallback(_requests => {
@@ -426,12 +451,12 @@ const MountPointMappingContent = ({ deviceData, partitioningData, dispatch, idPr
setManualPartitioningRequests({
partitioning: partitioningData.path,
- requests: requestsToDbus(partitioningData.requests)
+ requests: requestsToDbus(usablePartitioningRequests)
}).catch(ex => {
onAddErrorNotification(ex);
setIsFormValid(false);
});
- }, [partitioningData.path, onAddErrorNotification, partitioningData.requests, setIsFormValid]);
+ }, [partitioningData.path, onAddErrorNotification, usablePartitioningRequests, setIsFormValid]);
/* When requests change apply directly to the backend */
useEffect(() => {
@@ -515,6 +540,7 @@ const MountPointMappingContent = ({ deviceData, partitioningData, dispatch, idPr
{
const [usedPartitioning, setUsedPartitioning] = useState(partitioningData?.path);
+ const requiredMountPoints = useMemo(() => {
+ const mounts = [
+ { value: "/boot", name: "boot" },
+ { value: "/", name: "root" },
+ ];
+
+ cockpit.spawn(["ls", "/sys/firmware/efi"], { err: "ignore" })
+ .then(() => { mounts.push({ value: "/boot/efi", name: "boot-efi" }) });
+
+ return mounts;
+ }, []);
+
+ const isUsableDevice = (devSpec, deviceData) => {
+ const device = deviceData[devSpec];
+ if (device === undefined || device.formatData === undefined) {
+ return false;
+ }
+
+ // luks is allowed -- we need to be able to unlock it
+ if (device.formatData.type.v === "luks") {
+ return true;
+ }
+
+ // only swap and mountable filesystems should be shown in the mount point assignment
+ if (device.formatData.type.v === "swap" || device.formatData.mountable.v === true) {
+ return true;
+ }
+
+ return false;
+ };
+
+ const usablePartitioningRequests = useMemo(() => {
+ return partitioningData.requests?.filter(r => isUsableDevice(r["device-spec"], deviceData)) || [];
+ }, [partitioningData.requests, deviceData]);
useEffect(() => {
if (!reusePartitioning || partitioningData?.method !== "MANUAL") {
@@ -571,7 +631,9 @@ export const MountPointMapping = ({ deviceData, diskSelection, partitioningData,
idPrefix={idPrefix}
onAddErrorNotification={onAddErrorNotification}
partitioningData={partitioningData}
+ usablePartitioningRequests={usablePartitioningRequests}
setIsFormValid={setIsFormValid}
+ requiredMountPoints={requiredMountPoints}
/>
);
diff --git a/ui/webui/test/anacondalib.py b/ui/webui/test/anacondalib.py
index afefaf8e1c9..f15afa425d1 100644
--- a/ui/webui/test/anacondalib.py
+++ b/ui/webui/test/anacondalib.py
@@ -31,8 +31,13 @@
class VirtInstallMachineCase(MachineCase):
+ efi = False
MachineCase.machine_class = VirtInstallMachine
+ @classmethod
+ def setUpClass(cls):
+ VirtInstallMachine.efi = cls.efi
+
def setUp(self):
# FIXME: running this in destructive tests fails because the SSH session closes before this is run
if self.is_nondestructive():
diff --git a/ui/webui/test/check-storage b/ui/webui/test/check-storage
index 0cdac2f0dda..a79929223d8 100755
--- a/ui/webui/test/check-storage
+++ b/ui/webui/test/check-storage
@@ -26,6 +26,8 @@ from storagelib import StorageHelpers # pylint: disable=import-error
@nondestructive
class TestStorage(anacondalib.VirtInstallMachineCase, StorageHelpers):
+ efi = False
+
def set_valid_password(self, password="abcdefgh"):
s = Storage(self.browser, self.machine)
@@ -250,6 +252,7 @@ class TestStorage(anacondalib.VirtInstallMachineCase, StorageHelpers):
# TODO add next back test keeping the choice
@nondestructive
class TestStorageExtraDisks(anacondalib.VirtInstallMachineCase, StorageHelpers):
+ efi = False
def testLocalDisksSyncNew(self):
b = self.browser
@@ -292,7 +295,7 @@ class TestStorageExtraDisks(anacondalib.VirtInstallMachineCase, StorageHelpers):
for disk in disks:
s.check_disk_selected(disk)
-class TestStorageMountPoints(anacondalib.VirtInstallMachineCase):
+class TestUtils():
def table_row(self, row):
return f"#mount-point-mapping-table-row-{row}";
@@ -305,8 +308,8 @@ class TestStorageMountPoints(anacondalib.VirtInstallMachineCase):
return True
- def select_mountpoint(self, i , s, disks):
- self.browser.wait(lambda : self.disks_loaded(s, disks))
+ def select_mountpoint(self, b, i , s, disks):
+ b.wait(lambda : self.disks_loaded(s, disks))
for disk in disks:
current_selection = s.get_disk_selected(disk)
@@ -316,92 +319,89 @@ class TestStorageMountPoints(anacondalib.VirtInstallMachineCase):
s.set_partitioning("mount-point-mapping")
i.next(next_page=i.steps.CUSTOM_MOUNT_POINT)
- def select_row_mountpoint(self, row, mountpoint):
- b = self.browser
+ def select_row_mountpoint(self, b, row, mountpoint):
b.set_input_text(f"{self.table_row(row)} td[data-label='Mount point'] input", mountpoint)
- def select_row_device(self, row, device):
- b = self.browser
+ def select_row_device(self, b, row, device):
selector = f"{self.table_row(row)} .pf-v5-c-select__toggle"
- self.browser.click(f"{selector}:not([disabled]):not([aria-disabled=true])")
+ b.click(f"{selector}:not([disabled]):not([aria-disabled=true])")
select_entry = f"{selector} + ul button[data-value='{device}']"
- self.browser.click(select_entry)
- self.browser.wait_in_text(f"{selector} .pf-v5-c-select__toggle-text", device)
+ b.click(select_entry)
+ b.wait_in_text(f"{selector} .pf-v5-c-select__toggle-text", device)
- def toggle_row_device(self, row):
- b = self.browser
+ def toggle_row_device(self, b, row):
b.click(f"{self.table_row(row)}-device-select-toggle")
- def check_row_device(self, row, device):
- self.browser.wait_text(f"{self.table_row(row)} .pf-v5-c-select__toggle-text", device)
+ def check_row_device(self, b, row, device):
+ b.wait_text(f"{self.table_row(row)} .pf-v5-c-select__toggle-text", device)
- def check_row_mountpoint(self, row, mountpoint, isRequired=True):
+ def check_row_mountpoint(self, b, row, mountpoint, isRequired=True):
if isRequired:
- self.browser.wait_text(f"{self.table_row(row)}-mountpoint", mountpoint)
+ b.wait_text(f"{self.table_row(row)}-mountpoint", mountpoint)
else:
- self.browser.wait_val(f"{self.table_row(row)}-mountpoint", mountpoint)
+ b.wait_val(f"{self.table_row(row)}-mountpoint", mountpoint)
- def check_format_type(self, row, format_type):
- self.toggle_row_device(row)
- self.browser.wait_in_text(f"{self.table_row(row)} ul li button.pf-m-selected", format_type)
- self.toggle_row_device(row)
+ def check_format_type(self, b, row, format_type):
+ self.toggle_row_device(b, row)
+ b.wait_in_text(f"{self.table_row(row)} ul li button.pf-m-selected", format_type)
+ self.toggle_row_device(b, row)
- def check_device_available(self, row, device, available=True):
- self.toggle_row_device(row)
+ def check_device_available(self, b, row, device, available=True):
+ self.toggle_row_device(b, row)
if available:
- self.browser.wait_visible(f"{self.table_row(row)} ul li button:contains({device})")
+ b.wait_visible(f"{self.table_row(row)} ul li button:contains({device})")
else:
- self.browser.wait_not_present(f"{self.table_row(row)} ul li button:contains({device})")
- self.toggle_row_device(row)
+ b.wait_not_present(f"{self.table_row(row)} ul li button:contains({device})")
+ self.toggle_row_device(b, row)
- def unlock_device(self, passphrase, xfail=None):
+ def unlock_device(self, b, passphrase, xfail=None):
# FIXME: https://github.com/patternfly/patternfly-react/issues/9512
- self.browser.wait_visible("#unlock-device-dialog.pf-v5-c-modal-box")
- self.browser.set_input_text("#unlock-device-dialog-luks-password", passphrase)
- self.browser.click("#unlock-device-dialog-submit-btn")
+ b.wait_visible("#unlock-device-dialog.pf-v5-c-modal-box")
+ b.set_input_text("#unlock-device-dialog-luks-password", passphrase)
+ b.click("#unlock-device-dialog-submit-btn")
if xfail:
- self.browser.wait_in_text("#unlock-device-dialog .pf-v5-c-alert", xfail)
- self.browser.click("#unlock-device-dialog-cancel-btn")
- self.browser.wait_not_present("#unlock-device-dialog.pf-v5-c-modal-box")
+ b.wait_in_text("#unlock-device-dialog .pf-v5-c-alert", xfail)
+ b.click("#unlock-device-dialog-cancel-btn")
+ b.wait_not_present("#unlock-device-dialog.pf-v5-c-modal-box")
- def select_reformat(self, row, selected=True):
- self.browser.set_checked(f"{self.table_row(row)} td[data-label='Reformat'] input", selected)
+ def select_reformat(self, b, row, selected=True):
+ b.set_checked(f"{self.table_row(row)} td[data-label='Reformat'] input", selected)
- def remove_row(self, row):
- self.browser.click(f"{self.table_row(row)} button[aria-label='Remove']")
+ def remove_row(self, b, row):
+ b.click(f"{self.table_row(row)} button[aria-label='Remove']")
- def check_reformat(self, row, checked):
+ def check_reformat(self, b, row, checked):
checked_selector = "input:checked" if checked else "input:not(:checked)"
- self.browser.wait_visible(f"{self.table_row(row)} td[data-label='Reformat'] {checked_selector}")
+ b.wait_visible(f"{self.table_row(row)} td[data-label='Reformat'] {checked_selector}")
- def check_select_disabled(self, row):
- self.browser.wait_visible(f"{self.table_row(row)} td[data-label='Device'] .pf-v5-c-select__toggle.pf-m-disabled")
+ def check_select_disabled(self, b, row):
+ b.wait_visible(f"{self.table_row(row)} td[data-label='Device'] .pf-v5-c-select__toggle.pf-m-disabled")
- def check_reformat_disabled(self, row):
- self.browser.wait_visible(f"{self.table_row(row)} td[data-label='Reformat'] .pf-v5-c-check__input:disabled")
+ def check_reformat_disabled(self, b, row):
+ b.wait_visible(f"{self.table_row(row)} td[data-label='Reformat'] .pf-v5-c-check__input:disabled")
- def add_mount(self):
- self.browser.click("button:contains('Add mount')")
+ def add_mount(self, b):
+ b.click("button:contains('Add mount')")
- def unlock_all_encrypted(self):
- self.browser.click("button:contains('Unlock')")
+ def unlock_all_encrypted(self, b):
+ b.click("button:contains('Unlock')")
- def unlock_all_encrypted_skip(self):
- self.browser.click("button:contains('Skip')")
+ def unlock_all_encrypted_skip(self, b):
+ b.click("button:contains('Skip')")
- def assert_inline_error(self, text):
- self.browser.wait_in_text(".pf-v5-c-alert.pf-m-inline.pf-m-danger", text)
-
-
- def wait_mount_point_table_column_helper(self, row, column, text=None, present=True):
- b = self.browser
+ def assert_inline_error(self, b, text):
+ b.wait_in_text(".pf-v5-c-alert.pf-m-inline.pf-m-danger", text)
+ def wait_mount_point_table_column_helper(self, b, row, column, text=None, present=True):
if present:
b.wait_in_text(f"#mount-point-mapping-table-row-{row}-{column} .pf-v5-c-helper-text__item.pf-m-error", text)
else:
b.wait_not_present(f"#mount-point-mapping-table-row-{row}-{column} .pf-v5-c-helper-text__item.pf-m-error")
+class TestStorageMountPoints(anacondalib.VirtInstallMachineCase, TestUtils):
+ efi = False
+
@nondestructive
def testBasic(self):
b = self.browser
@@ -423,31 +423,31 @@ class TestStorageMountPoints(anacondalib.VirtInstallMachineCase):
i.next()
s.rescan_disks()
- self.select_mountpoint(i, s, [(dev, True)])
+ self.select_mountpoint(b, i, s, [(dev, True)])
# verify gathered requests
# root partition is not auto-mapped
- self.check_row_mountpoint(1, "/boot")
- self.check_row_device(1, "Select a device")
- self.check_reformat(1, False)
- self.select_row_device(1, f"{dev}2")
- self.check_format_type(1, "ext4")
-
- self.check_row_mountpoint(2, "/")
- self.check_row_device(2, "Select a device")
- self.check_reformat(2, True)
- self.select_row_device(2, f"btrfstest")
- self.check_format_type(2, "btrfs")
-
- self.add_mount()
- self.select_row_device(3, f"{dev}4")
- self.check_reformat(3, False)
- self.select_row_mountpoint(3, "/home")
- self.check_format_type(3, "xfs")
+ self.check_row_mountpoint(b, 1, "/boot")
+ self.check_row_device(b, 1, "Select a device")
+ self.check_reformat(b, 1, False)
+ self.select_row_device(b, 1, f"{dev}2")
+ self.check_format_type(b, 1, "ext4")
+
+ self.check_row_mountpoint(b, 2, "/")
+ self.check_row_device(b, 2, "Select a device")
+ self.check_reformat(b, 2, True)
+ self.select_row_device(b, 2, f"btrfstest")
+ self.check_format_type(b, 2, "btrfs")
+
+ self.add_mount(b)
+ self.select_row_device(b, 3, f"{dev}4")
+ self.check_reformat(b, 3, False)
+ self.select_row_mountpoint(b, 3, "/home")
+ self.check_format_type(b, 3, "xfs")
# Toggle reformat option
- self.select_reformat(1)
- self.check_reformat(1, True)
+ self.select_reformat(b, 1)
+ self.check_reformat(b, 1, True)
b.assert_pixels(
"#app",
@@ -472,11 +472,11 @@ class TestStorageMountPoints(anacondalib.VirtInstallMachineCase):
m.execute(f"sgdisk --new=0:0:0 {disk}")
s.rescan_disks()
- self.select_mountpoint(i, s, [(dev, True)])
- self.check_row_device(1, "Select a device")
- self.check_row_device(2, "Select a device")
- self.select_row_device(1, f"{dev}2")
- self.select_row_device(2, f"btrfstest")
+ self.select_mountpoint(b, i, s, [(dev, True)])
+ self.check_row_device(b, 1, "Select a device")
+ self.check_row_device(b, 2, "Select a device")
+ self.select_row_device(b, 1, f"{dev}2")
+ self.select_row_device(b, 2, f"btrfstest")
i.next()
new_applied_partitioning = s.dbus_get_applied_partitioning()
@@ -501,23 +501,23 @@ class TestStorageMountPoints(anacondalib.VirtInstallMachineCase):
i.next()
s.rescan_disks()
- self.select_mountpoint(i, s, [(dev, True)])
+ self.select_mountpoint(b, i, s, [(dev, True)])
# verify gathered requests
- self.select_row_device(1, f"{dev}2")
- self.check_format_type(1, "ext4")
- self.check_row_mountpoint(1, "/boot")
- self.check_reformat(1, False)
+ self.select_row_device(b, 1, f"{dev}2")
+ self.check_format_type(b, 1, "ext4")
+ self.check_row_mountpoint(b, 1, "/boot")
+ self.check_reformat(b, 1, False)
- self.check_row_mountpoint(2, "/")
- self.check_row_device(2, "Select a device")
- self.check_reformat(2, True)
+ self.check_row_mountpoint(b, 2, "/")
+ self.check_row_device(b, 2, "Select a device")
+ self.check_reformat(b, 2, True)
- self.add_mount()
- self.select_row_device(3, f"{dev}4")
- self.check_format_type(3, "ext4")
- self.select_row_mountpoint(3, "/home")
- self.check_reformat(3, False)
+ self.add_mount(b)
+ self.select_row_device(b, 3, f"{dev}4")
+ self.check_format_type(b, 3, "ext4")
+ self.select_row_mountpoint(b, 3, "/home")
+ self.check_reformat(b, 3, False)
i.check_next_disabled()
@@ -546,35 +546,35 @@ class TestStorageMountPoints(anacondalib.VirtInstallMachineCase):
i.next()
s.rescan_disks()
- self.select_mountpoint(i, s, [(dev1, False), (dev2, True)])
+ self.select_mountpoint(b, i, s, [(dev1, False), (dev2, True)])
- self.check_device_available(1, "vda2", False)
- self.check_device_available(1, "vdb1")
+ self.check_device_available(b, 1, "vda2", False)
+ self.check_device_available(b, 1, "vdb1")
# Go back and change the disk selection. The partitioning should be re-created
i.back()
- self.browser.click("#installation-method-disk-selector-clear")
- self.select_mountpoint(i, s, [(dev1, True), (dev2, True)])
+ b.click("#installation-method-disk-selector-clear")
+ self.select_mountpoint(b, i, s, [(dev1, True), (dev2, True)])
- self.check_device_available(1, "vda2", True)
- self.check_device_available(1, "vdb1")
+ self.check_device_available(b, 1, "vda2", True)
+ self.check_device_available(b, 1, "vdb1")
- self.select_row_device(1, f"{dev1}2")
- self.check_format_type(1, "xfs")
- self.check_row_mountpoint(1, "/boot")
- self.check_reformat(1, False)
+ self.select_row_device(b, 1, f"{dev1}2")
+ self.check_format_type(b, 1, "xfs")
+ self.check_row_mountpoint(b, 1, "/boot")
+ self.check_reformat(b, 1, False)
- self.select_row_device(2, f"{dev1}3")
- self.check_format_type(2, "xfs")
- self.check_row_mountpoint(2, "/")
- self.check_reformat(2, True)
+ self.select_row_device(b, 2, f"{dev1}3")
+ self.check_format_type(b, 2, "xfs")
+ self.check_row_mountpoint(b, 2, "/")
+ self.check_reformat(b, 2, True)
- self.add_mount()
- self.select_row_device(3, f"{dev2}1")
- self.check_format_type(3, "xfs")
- self.select_row_mountpoint(3, "/home")
- self.check_reformat(3, False)
+ self.add_mount(b)
+ self.select_row_device(b, 3, f"{dev2}1")
+ self.check_format_type(b, 3, "xfs")
+ self.select_row_mountpoint(b, 3, "/home")
+ self.check_reformat(b, 3, False)
i.next()
@@ -622,40 +622,40 @@ class TestStorageMountPoints(anacondalib.VirtInstallMachineCase):
i.next()
s.rescan_disks()
- self.select_mountpoint(i, s, [(dev1, True)])
+ self.select_mountpoint(b, i, s, [(dev1, True)])
- self.unlock_all_encrypted()
- self.unlock_device("1234", "Some LUKS devices were not unlocked")
+ self.unlock_all_encrypted(b)
+ self.unlock_device(b, "1234", "Some LUKS devices were not unlocked")
- self.unlock_all_encrypted_skip()
+ self.unlock_all_encrypted_skip(b)
b.wait_not_present("button:contains(Unlock)")
i.back()
i.next(next_page=i.steps.CUSTOM_MOUNT_POINT)
- self.unlock_all_encrypted()
- self.unlock_device("einszweidrei", "Some LUKS devices were not unlocked")
+ self.unlock_all_encrypted(b)
+ self.unlock_device(b, "einszweidrei", "Some LUKS devices were not unlocked")
- self.unlock_all_encrypted_skip()
+ self.unlock_all_encrypted_skip(b)
b.wait_not_present("button:contains(Unlock)")
i.back()
i.next(next_page=i.steps.CUSTOM_MOUNT_POINT)
- self.unlock_all_encrypted()
- self.unlock_device("einszweidreivier")
+ self.unlock_all_encrypted(b)
+ self.unlock_device(b, "einszweidreivier")
b.wait_not_present("#mount-point-mapping-table tbody tr:nth-child(4) td[data-label='Format type'] #unlock-luks-btn")
- self.check_row_mountpoint(1, "/boot")
- self.select_row_device(1, f"{dev1}2")
+ self.check_row_mountpoint(b, 1, "/boot")
+ self.select_row_device(b, 1, f"{dev1}2")
- self.check_row_mountpoint(2, "/")
+ self.check_row_mountpoint(b, 2, "/")
selector = "#mount-point-mapping-table-row-2 .pf-v5-c-select__toggle"
- self.browser.click(f"{selector}:not([disabled]):not([aria-disabled=true])")
+ b.click(f"{selector}:not([disabled]):not([aria-disabled=true])")
select_entry = f"{selector} + ul li:nth-of-type(3) button"
- self.browser.click(select_entry)
- self.browser.wait_in_text(f"{selector} .pf-v5-c-select__toggle-text", "luks")
- self.check_format_type(2, "xfs")
+ b.click(select_entry)
+ b.wait_in_text(f"{selector} .pf-v5-c-select__toggle-text", "luks")
+ self.check_format_type(b, 2, "xfs")
i.next()
@@ -692,34 +692,34 @@ class TestStorageMountPoints(anacondalib.VirtInstallMachineCase):
i.next()
s.rescan_disks()
- self.select_mountpoint(i, s, [(dev, True)])
+ self.select_mountpoint(b, i, s, [(dev, True)])
# btrfs snapshots should not be available
- self.check_device_available(1, "snapshot1", False)
+ self.check_device_available(b, 1, "snapshot1", False)
# verify gathered requests
# root partition is not auto-mapped
- self.check_row_mountpoint(1, "/boot")
- self.check_row_device(1, "Select a device")
- self.check_reformat(1, False)
- self.select_row_device(1, f"{dev}2")
- self.check_format_type(1, "ext4")
-
- self.check_row_mountpoint(2, "/")
- self.check_row_device(2, "Select a device")
- self.check_reformat(2, True)
- self.select_row_device(2, "root")
- self.check_format_type(2, "btrfs")
-
- self.add_mount()
- self.select_row_device(3, "home")
- self.check_reformat(3, False)
- self.select_row_mountpoint(3, "/home")
- self.check_format_type(3, "btrfs")
+ self.check_row_mountpoint(b, 1, "/boot")
+ self.check_row_device(b, 1, "Select a device")
+ self.check_reformat(b, 1, False)
+ self.select_row_device(b, 1, f"{dev}2")
+ self.check_format_type(b, 1, "ext4")
+
+ self.check_row_mountpoint(b, 2, "/")
+ self.check_row_device(b, 2, "Select a device")
+ self.check_reformat(b, 2, True)
+ self.select_row_device(b, 2, "root")
+ self.check_format_type(b, 2, "btrfs")
+
+ self.add_mount(b)
+ self.select_row_device(b, 3, "home")
+ self.check_reformat(b, 3, False)
+ self.select_row_mountpoint(b, 3, "/home")
+ self.check_format_type(b, 3, "btrfs")
# Toggle reformat option
- self.select_reformat(1)
- self.check_reformat(1, True)
+ self.select_reformat(b, 1)
+ self.check_reformat(b, 1, True)
i.next()
@@ -746,48 +746,48 @@ class TestStorageMountPoints(anacondalib.VirtInstallMachineCase):
rmdir {tmp_mount}
""")
s.rescan_disks()
- self.select_mountpoint(i, s, [(dev, True)])
-
- self.select_row_device(1, f"{dev}2")
- self.select_row_device(2, "root")
- self.add_mount()
- self.select_row_device(3, "home")
- self.select_row_mountpoint(3, "/home")
- self.add_mount()
- self.select_row_device(4, "home/Movies")
- self.select_row_mountpoint(4, "/home/Movies")
- self.add_mount()
- self.select_row_device(5, "home/Movies/Good_Movies")
- self.select_row_mountpoint(5, "/home/Movies/Good_Movies")
- self.add_mount()
- self.select_row_device(6, "home/Movies/Bad_Movies")
- self.select_row_mountpoint(6, "/home/Movies/Bad_Movies")
+ self.select_mountpoint(b, i, s, [(dev, True)])
+
+ self.select_row_device(b, 1, f"{dev}2")
+ self.select_row_device(b, 2, "root")
+ self.add_mount(b)
+ self.select_row_device(b, 3, "home")
+ self.select_row_mountpoint(b, 3, "/home")
+ self.add_mount(b)
+ self.select_row_device(b, 4, "home/Movies")
+ self.select_row_mountpoint(b, 4, "/home/Movies")
+ self.add_mount(b)
+ self.select_row_device(b, 5, "home/Movies/Good_Movies")
+ self.select_row_mountpoint(b, 5, "/home/Movies/Good_Movies")
+ self.add_mount(b)
+ self.select_row_device(b, 6, "home/Movies/Bad_Movies")
+ self.select_row_mountpoint(b, 6, "/home/Movies/Bad_Movies")
# No error when no devices are reformatted
for row in range(3, 6):
- self.wait_mount_point_table_column_helper(row, "format", present=False)
+ self.wait_mount_point_table_column_helper(b, row, "format", present=False)
# When parent is re-formatted all child devices must be reformatted
- self.select_row_device(4, "home/Movies")
- self.select_reformat(4)
- self.wait_mount_point_table_column_helper(4, "format", text="Mismatch")
- self.select_reformat(5)
- self.select_reformat(6)
- self.wait_mount_point_table_column_helper(4, "format", present=False)
+ self.select_row_device(b, 4, "home/Movies")
+ self.select_reformat(b, 4)
+ self.wait_mount_point_table_column_helper(b, 4, "format", text="Mismatch")
+ self.select_reformat(b, 5)
+ self.select_reformat(b, 6)
+ self.wait_mount_point_table_column_helper(b, 4, "format", present=False)
# Check also that the rules apply to children deeper in the device tree
- self.select_reformat(3)
- self.wait_mount_point_table_column_helper(3, "format", present=False)
- self.select_reformat(6, False)
- self.wait_mount_point_table_column_helper(3, "format", text="Mismatch")
+ self.select_reformat(b, 3)
+ self.wait_mount_point_table_column_helper(b, 3, "format", present=False)
+ self.select_reformat(b, 6, False)
+ self.wait_mount_point_table_column_helper(b, 3, "format", text="Mismatch")
# When parent is re-formmated all child devices should be
# * either also reformatted if selected
# * either not selected (not part of the mountpoint assignment table)
- self.remove_row(5)
- self.remove_row(5)
- self.wait_mount_point_table_column_helper(3, "format", present=False)
- self.wait_mount_point_table_column_helper(4, "format", present=False)
+ self.remove_row(b, 5)
+ self.remove_row(b, 5)
+ self.wait_mount_point_table_column_helper(b, 3, "format", present=False)
+ self.wait_mount_point_table_column_helper(b, 4, "format", present=False)
i.check_next_disabled(False)
@@ -823,37 +823,37 @@ class TestStorageMountPoints(anacondalib.VirtInstallMachineCase):
i.next()
s.rescan_disks()
- self.select_mountpoint(i, s, [(dev, True)])
+ self.select_mountpoint(b, i, s, [(dev, True)])
# verify gathered requests
# root partition is not auto-mapped
- self.check_row_mountpoint(1, "/boot")
- self.check_row_device(1, "Select a device")
- self.check_reformat(1, False)
- self.select_row_device(1, f"{dev}2")
- self.check_format_type(1, "ext4")
-
- self.check_row_mountpoint(2, "/")
- self.check_row_device(2, "Select a device")
- self.check_reformat(2, True)
- self.select_row_device(2, f"{vgname}-root")
- self.check_format_type(2, "ext4")
-
- self.add_mount()
- self.select_row_device(3, f"{vgname}-home")
- self.check_reformat(3, False)
- self.select_row_mountpoint(3, "/home")
- self.check_format_type(3, "ext4")
-
- self.add_mount()
- self.select_row_device(4, f"{vgname}-swap")
- self.check_reformat(4, False)
- self.check_row_mountpoint(4, "swap")
- self.check_format_type(4, "swap")
+ self.check_row_mountpoint(b, 1, "/boot")
+ self.check_row_device(b, 1, "Select a device")
+ self.check_reformat(b, 1, False)
+ self.select_row_device(b, 1, f"{dev}2")
+ self.check_format_type(b, 1, "ext4")
+
+ self.check_row_mountpoint(b, 2, "/")
+ self.check_row_device(b, 2, "Select a device")
+ self.check_reformat(b, 2, True)
+ self.select_row_device(b, 2, f"{vgname}-root")
+ self.check_format_type(b, 2, "ext4")
+
+ self.add_mount(b)
+ self.select_row_device(b, 3, f"{vgname}-home")
+ self.check_reformat(b, 3, False)
+ self.select_row_mountpoint(b, 3, "/home")
+ self.check_format_type(b, 3, "ext4")
+
+ self.add_mount(b)
+ self.select_row_device(b, 4, f"{vgname}-swap")
+ self.check_reformat(b, 4, False)
+ self.check_row_mountpoint(b, 4, "swap")
+ self.check_format_type(b, 4, "swap")
# Toggle reformat option
- self.select_reformat(1)
- self.check_reformat(1, True)
+ self.select_reformat(b, 1)
+ self.check_reformat(b, 1, True)
i.next()
@@ -869,10 +869,10 @@ class TestStorageMountPoints(anacondalib.VirtInstallMachineCase):
i.back(previous_page=i.steps.CUSTOM_MOUNT_POINT)
# remove the /home row and check that row 3 is now swap
- self.remove_row(3)
+ self.remove_row(b, 3)
- self.check_row_mountpoint(3, "swap")
- self.check_row_device(3, f"{vgname}-swap")
+ self.check_row_mountpoint(b, 3, "swap")
+ self.check_row_device(b, 3, f"{vgname}-swap")
i.next()
@@ -884,5 +884,84 @@ class TestStorageMountPoints(anacondalib.VirtInstallMachineCase):
r.check_disk_row(disk, 2, f"{vgname}-root, 6.01 GB: format as ext4, /")
r.check_disk_row(disk, 3, f"{vgname}-swap, 902 MB: mount, swap")
+ @nondestructive
+ def testUnusableFormats(self):
+ b = self.browser
+ m = self.machine
+ i = Installer(b, m)
+ s = Storage(b, m)
+
+ self.addCleanup(m.execute, "wipefs --all /dev/vda")
+
+ disk = "/dev/vda"
+ dev = "vda"
+ s.partition_disk(disk, [("1GB", "ext4"), ("1GB", None), ("1GB", "lvmpv")])
+ s.udevadm_settle()
+
+ i.open()
+ i.next()
+ s.rescan_disks()
+
+ self.select_mountpoint(b, i, s, [(dev, True)])
+
+ # unformatted and unmountable devices should not be available
+ self.check_device_available(b, 1, f"{dev}2", False)
+ self.check_device_available(b, 1, f"{dev}3", False)
+
+
+class TestStorageMountPointsEFI(anacondalib.VirtInstallMachineCase, TestUtils):
+ efi = True
+
+ def testBasic(self):
+ b = self.browser
+ m = self.machine
+ i = Installer(b, m)
+ s = Storage(b, m)
+ r = Review(b)
+
+ self.addCleanup(m.execute, "wipefs --all /dev/vda")
+
+ disk = "/dev/vda"
+ dev = "vda"
+ s.partition_disk(disk, [("500MiB", "efi"), ("1GB", "ext4"), ("", "xfs")])
+
+ s.udevadm_settle()
+
+ i.open()
+ i.next()
+ s.rescan_disks()
+
+ self.select_mountpoint(b, i, s, [(dev, True)])
+
+ # verify gathered requests
+ # root partition is not auto-mapped
+ self.check_row_mountpoint(b, 1, "/boot")
+ self.check_row_device(b, 1, "Select a device")
+ self.check_reformat(b, 1, False)
+ self.select_row_device(b, 1, f"{dev}2")
+ self.check_format_type(b, 1, "ext4")
+
+ self.check_row_mountpoint(b, 2, "/")
+ self.check_row_device(b, 2, "Select a device")
+ self.check_reformat(b, 2, True)
+ self.select_row_device(b, 2, f"{dev}3")
+ self.check_format_type(b, 2, "xfs")
+
+ self.check_row_mountpoint(b, 3, "/boot/efi")
+ self.check_row_device(b, 3, "Select a device")
+ self.check_reformat(b, 3, False)
+ self.select_row_device(b, 3, f"{dev}1")
+ self.check_format_type(b, 3, "EFI System Partition")
+
+ i.next()
+
+ # verify review screen
+ r.check_disk(dev, "16.1 GB vda (0x1af4)")
+
+ r.check_disk_row(dev, 1, "vda1, 524 MB: mount, /boot/efi")
+ r.check_disk_row(dev, 2, "vda2, 1.07 GB: mount, /boot")
+ r.check_disk_row(dev, 3, "vda3, 14.5 GB: format as xfs, /")
+
+
if __name__ == '__main__':
test_main()
diff --git a/ui/webui/test/helpers/storage.py b/ui/webui/test/helpers/storage.py
index f9ad15d5f35..a8febabab8a 100644
--- a/ui/webui/test/helpers/storage.py
+++ b/ui/webui/test/helpers/storage.py
@@ -292,16 +292,25 @@ def partition_disk(self, disk, partitions_params):
if params[1] == "biosboot":
sgdisk.append("--typecode=0:ef02")
+ if params[1] == "efi":
+ sgdisk.append("--typecode=0:ef00")
sgdisk.append(disk)
command += f"\n{' '.join(sgdisk)}"
if params[1] not in ("biosboot", None):
- mkfs = [f"mkfs.{params[1]}"]
+ if params[1] == "lvmpv":
+ mkfs = ["pvcreate"]
+ else:
+ if params[1] == "efi":
+ fs = "vfat"
+ else:
+ fs = params[1]
+ mkfs = [f"mkfs.{fs}"]
# force flag
- if params[1] in ["xfs", "btrfs"]:
+ if params[1] in ["xfs", "btrfs", "lvmpv"]:
mkfs.append("-f")
elif params[1] in ["ext4", "etx3", "ext2", "ntfs"]:
mkfs.append("-F")
diff --git a/ui/webui/test/machine_install.py b/ui/webui/test/machine_install.py
index 1f38781004a..61165c37c39 100755
--- a/ui/webui/test/machine_install.py
+++ b/ui/webui/test/machine_install.py
@@ -43,6 +43,8 @@
class VirtInstallMachine(VirtMachine):
+ efi = False
+
def _execute(self, cmd):
return subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=True)
@@ -125,12 +127,18 @@ def start(self):
location = f"{iso_path}"
extra_args = f"inst.ks=file:/{os.path.basename(self.payload_ks_path)}"
+ if self.efi:
+ boot_arg = "--boot uefi "
+ else:
+ boot_arg = ""
+
try:
self._execute(
"virt-install "
"--wait "
"--connect qemu:///session "
"--quiet "
+ f"{boot_arg} "
f"--name {self.label} "
f"--os-variant=detect=on "
"--memory 2048 "
@@ -173,7 +181,7 @@ def start(self):
def kill(self):
self._execute(f"virsh -q -c qemu:///session destroy {self.label} || true")
self._execute(
- f"virsh -q -c qemu:///session undefine "
+ f"virsh -q -c qemu:///session undefine --nvram " # tell undefine to also delete the EFI NVRAM device
f"--remove-all-storage {self.label} || true"
)
os.remove(self.payload_ks_path)
@@ -204,3 +212,7 @@ def is_live(self):
def get_volume_id(self, iso_path):
return subprocess.check_output(fr"isoinfo -d -i {iso_path} | grep -oP 'Volume id: \K.*'", shell=True).decode(sys.stdout.encoding).strip()
+
+
+class VirtInstallEFIMachine(VirtInstallMachine):
+ efi = True
diff --git a/ui/webui/test/reference b/ui/webui/test/reference
index 905b97a37f8..8e686c8c532 160000
--- a/ui/webui/test/reference
+++ b/ui/webui/test/reference
@@ -1 +1 @@
-Subproject commit 905b97a37f82ce6f9ea338629fbd22da776bebf4
+Subproject commit 8e686c8c532df3e346d4939dac0065abd780b025
diff --git a/ui/webui/test/webui_testvm.py b/ui/webui/test/webui_testvm.py
index c002d81adb6..1b9adad0ef3 100755
--- a/ui/webui/test/webui_testvm.py
+++ b/ui/webui/test/webui_testvm.py
@@ -19,7 +19,7 @@
import subprocess
import argparse
-from machine_install import VirtInstallMachine
+from machine_install import VirtInstallMachine, VirtInstallEFIMachine
def cmd_cli():
@@ -27,9 +27,13 @@ def cmd_cli():
parser.add_argument("image", help="Image name")
parser.add_argument("--rsync", help="Rsync development files over on startup", action='store_true')
parser.add_argument("--host", help="Hostname to rsync", default='test-updates')
+ parser.add_argument("--efi", help="Start the VM with an EFI firmware", action='store_true')
args = parser.parse_args()
- machine = VirtInstallMachine(image=args.image)
+ if args.efi:
+ machine = VirtInstallEFIMachine(image=args.image)
+ else:
+ machine = VirtInstallMachine(image=args.image)
try:
machine.start()