diff --git a/libs/ui-lib-tests/cypress/fixtures/storage/host-inventory.ts b/libs/ui-lib-tests/cypress/fixtures/storage/host-inventory.ts
index b68597a3fe..15b38170a6 100644
--- a/libs/ui-lib-tests/cypress/fixtures/storage/host-inventory.ts
+++ b/libs/ui-lib-tests/cypress/fixtures/storage/host-inventory.ts
@@ -238,4 +238,141 @@ const createHostInventory = (id: number, memory: number, diskSpace: number) => {
};
};
-export default createHostInventory;
+const disksWithHolders = [
+ {
+ drive_type: 'RAID',
+ id: '/dev/md0',
+ installation_eligibility: { eligible: true },
+ name: 'md0',
+ path: '/dev/md0',
+ size_bytes: 21455962112,
+ },
+ {
+ drive_type: 'LVM',
+ id: '/dev/dm-0',
+ installation_eligibility: { eligible: true },
+ name: 'dm-0',
+ path: '/dev/dm-0',
+ size_bytes: 15032385536,
+ },
+ {
+ by_id: '/dev/disk/by-id/wwn-0x60000000000000000e00000000010001',
+ drive_type: 'Multipath',
+ has_uuid: true,
+ id: '/dev/disk/by-id/wwn-0x60000000000000000e00000000010001',
+ installation_eligibility: { eligible: true },
+ name: 'dm-1',
+ path: '/dev/dm-1',
+ size_bytes: 10737418240,
+ },
+ {
+ drive_type: 'LVM',
+ id: '/dev/dm-2',
+ installation_eligibility: { eligible: true },
+ name: 'dm-2',
+ path: '/dev/dm-2',
+ size_bytes: 4294967296,
+ },
+ {
+ by_path:
+ '/dev/disk/by-path/ip-192.168.122.41:3260-iscsi-iqn.2015-06.com.example.test:target1-lun-1',
+ drive_type: 'iSCSI',
+ has_uuid: true,
+ hctl: '8:0:0:1',
+ holders: 'dm-1',
+ id: '/dev/disk/by-path/ip-192.168.122.41:3260-iscsi-iqn.2015-06.com.example.test:target1-lun-1',
+ installation_eligibility: { eligible: true },
+ model: 'VIRTUAL-DISK',
+ name: 'sda',
+ path: '/dev/sda',
+ serial: '60000000000000000e00000000010001',
+ size_bytes: 10737418240,
+ vendor: 'IET',
+ wwn: '0x60000000000000000e00000000010001',
+ },
+ {
+ by_path: '/dev/disk/by-path/ip-192.168.123.49:3260',
+ drive_type: 'iSCSI',
+ has_uuid: true,
+ hctl: '9:0:0:1',
+ holders: 'dm-1',
+ id: '/dev/disk/by-path/ip-192.168.123.49:3260',
+ installation_eligibility: { eligible: true },
+ model: 'VIRTUAL-DISK',
+ name: 'sdb',
+ path: '/dev/sdb',
+ serial: '60000000000000000e00000000010001',
+ size_bytes: 10737418240,
+ vendor: 'IET',
+ wwn: '0x60000000000000000e00000000010001',
+ },
+ {
+ bootable: true,
+ by_path: '/dev/disk/by-path/pci-0000:00:05.0-ata-3',
+ drive_type: 'ODD',
+ has_uuid: true,
+ hctl: '4:0:0:0',
+ id: '/dev/disk/by-path/pci-0000:00:05.0-ata-3',
+ installation_eligibility: { eligible: true },
+ is_installation_media: true,
+ model: 'QEMU_DVD-ROM',
+ name: 'sr0',
+ path: '/dev/sr0',
+ removable: true,
+ serial: 'QM00009',
+ size_bytes: 1135607808,
+ smart: 'SMART support is: Unavailable - device lacks SMART capability.\n',
+ vendor: 'QEMU',
+ },
+ {
+ by_path: '/dev/disk/by-path/pci-0000:00:07.0',
+ drive_type: 'HDD',
+ holders: 'dm-0,dm-2',
+ id: '/dev/disk/by-path/pci-0000:00:07.0',
+ installation_eligibility: { eligible: true },
+ name: 'vda',
+ path: '/dev/vda',
+ size_bytes: 10737418240,
+ vendor: '0x1af4',
+ },
+ {
+ by_path: '/dev/disk/by-path/pci-0000:00:08.0',
+ drive_type: 'HDD',
+ holders: 'dm-0,dm-2',
+ id: '/dev/disk/by-path/pci-0000:00:08.0',
+ installation_eligibility: { eligible: true },
+ name: 'vdb',
+ path: '/dev/vdb',
+ size_bytes: 10737418240,
+ vendor: '0x1af4',
+ },
+ {
+ by_path: '/dev/disk/by-path/pci-0000:00:09.0',
+ drive_type: 'HDD',
+ holders: 'md0',
+ id: '/dev/disk/by-path/pci-0000:00:09.0',
+ installation_eligibility: { eligible: true },
+ name: 'vdc',
+ path: '/dev/vdc',
+ size_bytes: 10737418240,
+ vendor: '0x1af4',
+ },
+ {
+ by_path: '/dev/disk/by-path/pci-0000:00:0a.0',
+ drive_type: 'HDD',
+ holders: 'md0',
+ id: '/dev/disk/by-path/pci-0000:00:0a.0',
+ installation_eligibility: { eligible: true },
+ name: 'vdd',
+ path: '/dev/vdd',
+ size_bytes: 10737418240,
+ vendor: '0x1af4',
+ },
+];
+
+const createHostInventoryWithDiskHolders = (id: number, memory: number, diskSpace: number) => ({
+ ...createHostInventory(id, memory, diskSpace),
+ disks: disksWithHolders,
+});
+
+export { createHostInventory, createHostInventoryWithDiskHolders };
diff --git a/libs/ui-lib-tests/cypress/fixtures/storage/index.ts b/libs/ui-lib-tests/cypress/fixtures/storage/index.ts
index 083304428c..49a7f69840 100644
--- a/libs/ui-lib-tests/cypress/fixtures/storage/index.ts
+++ b/libs/ui-lib-tests/cypress/fixtures/storage/index.ts
@@ -1,5 +1,6 @@
import storageCluster from './storage-cluster';
-import storageHosts from './storage-hosts';
+import { createMultinodeFixtureMapping } from '../create-mn';
+import { storageHosts, hostsWithDiskHolders } from './storage-hosts';
const createStorageFixtureMapping = {
clusters: {
@@ -10,4 +11,13 @@ const createStorageFixtureMapping = {
},
};
-export { createStorageFixtureMapping };
+const createDiskHoldersFixtureMapping = {
+ clusters: {
+ default: createMultinodeFixtureMapping.clusters.READY_TO_INSTALL,
+ },
+ hosts: {
+ default: hostsWithDiskHolders,
+ },
+};
+
+export { createStorageFixtureMapping, createDiskHoldersFixtureMapping };
diff --git a/libs/ui-lib-tests/cypress/fixtures/storage/storage-hosts.ts b/libs/ui-lib-tests/cypress/fixtures/storage/storage-hosts.ts
index a42ffecefa..de34eae383 100644
--- a/libs/ui-lib-tests/cypress/fixtures/storage/storage-hosts.ts
+++ b/libs/ui-lib-tests/cypress/fixtures/storage/storage-hosts.ts
@@ -1,5 +1,5 @@
import { fakeClusterId } from '../cluster/base-cluster';
-import createHostInventory from './host-inventory';
+import { createHostInventory, createHostInventoryWithDiskHolders } from './host-inventory';
const operatorValidations = [
{
@@ -118,6 +118,14 @@ const workerMemory = 7179869184; // TODO
const masterDisk = 17797418240; // 17.80 GB
const workerDisk = 10476748240; // 10.48 GB
+const installationDiskIds = [
+ '/dev/disk/by-path/pci-0000:00:08.0', // LVM
+ '/dev/disk/by-path/pci-0000:00:09.0', // raid
+ '/dev/disk/by-path/ip-192.168.123.49:3260', //multipath
+ '/dev/disk/by-path/pci-0000:00:05.0-ata-3', // sr0
+ '/dev/disk/by-path/pci-0000:00:05.0-ata-3', // sr0
+];
+
const hosts = [
{
checked_in_at: '2022-08-16T15:02:43.543Z',
@@ -368,4 +376,17 @@ const hosts = [
},
];
-export default hosts;
+const hostsWithDiskHolders = hosts.map((host, index) => ({
+ ...host,
+ inventory: JSON.stringify(
+ createHostInventoryWithDiskHolders(
+ index,
+ host['role'] === 'master' ? masterMemory : workerMemory,
+ host['role'] === 'master' ? masterDisk : workerDisk,
+ ),
+ ),
+ installation_disk_id: installationDiskIds[index],
+ skip_formatting_disks: false,
+}));
+
+export { hosts as storageHosts, hostsWithDiskHolders };
diff --git a/libs/ui-lib-tests/cypress/integration/storage/storage-step-disk-holders.cy.ts b/libs/ui-lib-tests/cypress/integration/storage/storage-step-disk-holders.cy.ts
new file mode 100644
index 0000000000..46c56a2dfb
--- /dev/null
+++ b/libs/ui-lib-tests/cypress/integration/storage/storage-step-disk-holders.cy.ts
@@ -0,0 +1,92 @@
+import { commonActions } from '../../views/common';
+import { ValidateDiskHoldersParams, hostsTableSection } from '../../views/hostsTableSection';
+import { StorageForm } from '../../views/forms/Storage/StorageForm';
+
+describe(`Assisted Installer Storage Step`, () => {
+ let storageForm;
+ const disks = [
+ { name: 'vda' },
+ { name: 'vdb' },
+ { name: 'dm-1' },
+ { name: 'sda', indented: true },
+ { name: 'sdb', indented: true },
+ { name: 'md0' },
+ { name: 'vdc', indented: true },
+ { name: 'vdd', indented: true },
+ { name: 'sr0' },
+ ] as ValidateDiskHoldersParams;
+
+ const warningTexts = [
+ 'LVM logical volumes were found on the installation disk vdb selected for host storage-test-odf-master-1 and will be deleted during installation.',
+ 'The installation disk vdc selected for host storage-test-odf-master-2, is part of a software RAID that will be deleted during the installation.',
+ 'The installation disk sdb selected for host storage-test-odf-master-3 is managed by multipath. We strongly recommend using the multipath device dm-1 to improve reliability.',
+ ];
+
+ const setTestStartSignal = (activeSignal: string) => {
+ cy.setTestEnvironment({
+ activeSignal,
+ activeScenario: 'AI_DISK_HOLDERS_CLUSTER',
+ });
+ };
+
+ before(() => {
+ setTestStartSignal('READY_TO_INSTALL');
+ });
+
+ describe(`Host storage`, () => {
+ beforeEach(() => {
+ setTestStartSignal('READY_TO_INSTALL');
+ commonActions.visitClusterDetailsPage();
+ commonActions.startAtWizardStep('Storage');
+
+ storageForm = new StorageForm();
+ });
+
+ it('Should display the correct alerts', () => {
+ storageForm.diskLimitationAlert.title.should(
+ 'contain.text',
+ 'Warning alert:Installation disk limitations',
+ );
+
+ storageForm.diskLimitationAlert.description.find('li').then(($res) => {
+ expect($res).to.have.length(3);
+ warningTexts.forEach((text, index) => expect($res[index]).to.contain(text));
+ });
+
+ storageForm.diskFormattingAlert.title.should(
+ 'contain.text',
+ 'Warning alert:All bootable disks, except for read-only disks, will be formatted during installation. Make sure to back up any critical data before proceeding.',
+ );
+ });
+
+ it('Should display the correct warning for LVM', () => {
+ disks[1].warning = true;
+
+ hostsTableSection.getHostDisksExpander(0).click();
+ hostsTableSection.validateGroupingByDiskHolders(disks, warningTexts[0]);
+ });
+
+ it('Should display the correct warning for RAID', () => {
+ disks[1].warning = false;
+ disks[6].warning = true;
+
+ hostsTableSection.getHostDisksExpander(1).click();
+ hostsTableSection.validateGroupingByDiskHolders(disks, warningTexts[1]);
+ });
+
+ it('Should display the correct warning for multipath', () => {
+ disks[6].warning = false;
+ disks[4].warning = true;
+
+ hostsTableSection.getHostDisksExpander(2).click();
+ hostsTableSection.validateGroupingByDiskHolders(disks, warningTexts[2]);
+ });
+
+ it('Should display no warnings', () => {
+ disks[6].warning = false;
+
+ hostsTableSection.getHostDisksExpander(2).click();
+ hostsTableSection.validateGroupingByDiskHolders(disks);
+ });
+ });
+});
diff --git a/libs/ui-lib-tests/cypress/integration/use-cases/create-cluster/with-mce-operator.cy.ts b/libs/ui-lib-tests/cypress/integration/use-cases/create-cluster/with-mce-operator.cy.ts
index e7a862a883..1acaf10ca8 100644
--- a/libs/ui-lib-tests/cypress/integration/use-cases/create-cluster/with-mce-operator.cy.ts
+++ b/libs/ui-lib-tests/cypress/integration/use-cases/create-cluster/with-mce-operator.cy.ts
@@ -1,5 +1,5 @@
import { commonActions } from '../../../views/common';
-import OperatorsForm from '../../../views/forms/OperatorsForm';
+import OperatorsForm from '../../../views/forms/Operators/OperatorsForm';
describe(`Create cluster with mce operator enabled`, () => {
const setTestStartSignal = (activeSignal: string) => {
diff --git a/libs/ui-lib-tests/cypress/support/interceptors.ts b/libs/ui-lib-tests/cypress/support/interceptors.ts
index 3ea02b2672..3891009198 100644
--- a/libs/ui-lib-tests/cypress/support/interceptors.ts
+++ b/libs/ui-lib-tests/cypress/support/interceptors.ts
@@ -67,6 +67,9 @@ const getScenarioFixtureMapping = () => {
case 'AI_STORAGE_CLUSTER':
fixtureMapping = fixtures.createStorageFixtureMapping;
break;
+ case 'AI_DISK_HOLDERS_CLUSTER':
+ fixtureMapping = fixtures.createDiskHoldersFixtureMapping;
+ break;
case 'AI_CREATE_STATIC_IP':
fixtureMapping = fixtures.createStaticIpFixtureMapping;
break;
@@ -154,6 +157,11 @@ const setScenarioEnvVars = (activeScenario) => {
Cypress.env('NUM_MASTERS', 3);
Cypress.env('NUM_WORKERS', 2);
break;
+ case 'AI_DISK_HOLDERS_CLUSTER':
+ Cypress.env('CLUSTER_NAME', 'ai-e2e-disk-holders');
+ Cypress.env('NUM_MASTERS', 3);
+ Cypress.env('NUM_WORKERS', 2);
+ break;
case 'AI_CREATE_STATIC_IP':
Cypress.env('CLUSTER_NAME', 'ai-e2e-static-ip');
break;
diff --git a/libs/ui-lib-tests/cypress/support/variables/index.ts b/libs/ui-lib-tests/cypress/support/variables/index.ts
index 275b182c04..0ad71a0312 100644
--- a/libs/ui-lib-tests/cypress/support/variables/index.ts
+++ b/libs/ui-lib-tests/cypress/support/variables/index.ts
@@ -2,9 +2,7 @@ import './common';
import './cluster-list';
import './cluster-details';
import './host-discovery';
-import './installation';
import './misc';
import './networking';
import './review-create';
import './test-constants';
-import './storage-step';
diff --git a/libs/ui-lib-tests/cypress/support/variables/installation.ts b/libs/ui-lib-tests/cypress/support/variables/installation.ts
deleted file mode 100644
index 34ef378ee1..0000000000
--- a/libs/ui-lib-tests/cypress/support/variables/installation.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-// Installation
-Cypress.env('hostnameDataTestId', `[data-testid=host-name]`);
-Cypress.env('roleDataLabel', `td[data-label="Role"][data-testid="host-role"]`);
-Cypress.env('odfUsageDataLabel', `td[data-label="ODF Usage"]`);
-Cypress.env('cpuCoresDataLabel', `td[data-label='CPU Cores']`);
-Cypress.env('memoryDataLabel', `td[data-label='Memory']`);
-Cypress.env('totalStorageDataLabel', `td[data-label='Total storage']`);
-Cypress.env('diskNumberDataLabel', `td[data-label='Number of disks']`);
-Cypress.env(
- 'clusterDetailButtonDownloadKubeconfigId',
- '#cluster-detail-button-download-kubeconfig',
-);
-Cypress.env(
- 'clusterDetailClusterCredsTshootHintOpen',
- 'cluster-detail-cluster-creds-troubleshooting-hint-open',
-);
-Cypress.env('clusterProgressStatusValueId', '#cluster-progress-status-value');
-Cypress.env('operatorsProgressItem', `[data-testid=operators-progress-item]`);
-Cypress.env('skipFormattingDataLabel', `td[data-label='Format?']`);
diff --git a/libs/ui-lib-tests/cypress/support/variables/storage-step.ts b/libs/ui-lib-tests/cypress/support/variables/storage-step.ts
deleted file mode 100644
index 02de7143a1..0000000000
--- a/libs/ui-lib-tests/cypress/support/variables/storage-step.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-// Storage Step
-Cypress.env('skipFormattingWarningTitle', 'There might be issues with the boot order');
-Cypress.env(
- 'skipFormattingWarningDesc',
- 'You have opted out of formatting bootable disks on some hosts. To ensure the hosts reboot into the expected installation disk, manual user intervention might be required during OpenShift installation.',
-);
-Cypress.env('warningIconFillColor', '#f0ab00');
diff --git a/libs/ui-lib-tests/cypress/support/variables/test-constants.ts b/libs/ui-lib-tests/cypress/support/variables/test-constants.ts
index bee5916706..ea4e13d572 100644
--- a/libs/ui-lib-tests/cypress/support/variables/test-constants.ts
+++ b/libs/ui-lib-tests/cypress/support/variables/test-constants.ts
@@ -1,42 +1,11 @@
// timeouts
-Cypress.env('DEFAULT_API_REQUEST_TIMEOUT', 20 * 1000);
-Cypress.env('DEFAULT_CREATE_CLUSTER_BUTTON_SHOW_TIMEOUT', 60 * 1000);
-Cypress.env('DEFAULT_SAVE_BUTTON_TIMEOUT', 30 * 1000);
Cypress.env('HOST_REGISTRATION_TIMEOUT', 11 * 1000);
Cypress.env('HOST_DISCOVERY_TIMEOUT', 11 * 1000);
Cypress.env('HOST_READY_TIMEOUT', 11 * 1000);
-Cypress.env('VALIDATE_CHANGES_TIMEOUT', 10 * 1000);
Cypress.env('START_INSTALLATION_TIMEOUT', 2.5 * 60 * 1000);
-Cypress.env('INSTALL_PREPARATION_TIMEOUT', 5 * 60 * 1000);
Cypress.env('GENERATE_ISO_TIMEOUT', 2 * 60 * 1000);
-Cypress.env('FILE_DOWNLOAD_TIMEOUT', 60 * 1000);
-Cypress.env('ISO_DOWNLOAD_TIMEOUT', 60 * 60 * 1000);
-Cypress.env('CLUSTER_CREATION_TIMEOUT', 9000000);
-Cypress.env('CLUSTER_REGISTRATION_TIMEOUT', 20 * 60 * 1000);
-Cypress.env('DAY2_HOST_INSTALLATION_TIMEOUT', 10 * 60 * 1000);
Cypress.env('HOST_STATUS_INSUFFICIENT_TIMEOUT', 300000);
-Cypress.env('NETWORK_LATENCY_ALERT_MESSAGE_TIMEOUT', 300000);
-Cypress.env('WAIT_FOR_PROGRESS_STATUS_INSTALLED', 1800000);
-Cypress.env('KUBECONFIG_DOWNLOAD_TIMEOUT', 300000);
-Cypress.env('WAIT_FOR_HEADER_TIMEOUT', 120000);
-Cypress.env('WAIT_FOR_CONSOLE_TIMEOUT', 2900000);
Cypress.env('DNS_RESOLUTION_ALERT_MESSAGE_TIMEOUT', 900000);
// Deployment
Cypress.env('NUM_MASTERS', parseInt(Cypress.env('NUM_MASTERS')));
Cypress.env('NUM_WORKERS', parseInt(Cypress.env('NUM_WORKERS')));
-Cypress.env('MASTER_HOST_ROW_MAX_INDEX', Number(Cypress.env('NUM_MASTERS')) * 2 - 2);
-Cypress.env(
- 'WORKER_HOST_ROW_MAX_INDEX',
- (Number(Cypress.env('NUM_MASTERS')) + Number(Cypress.env('NUM_WORKERS'))) * 2 - 2,
-);
-Cypress.env('DISCOVERY_IMAGE_GLOB_PATTERN', 'discovery_image_*.iso');
-Cypress.env('DISCOVERY_IMAGE_PATH', '/var/lib/libvirt/images/0/cluster-discovery.iso');
-Cypress.env('DEFAULT_CLUSTER_NAME', 'ocp-edge-cluster-0');
-Cypress.env('OPENSHIFT_CONF', '/etc/NetworkManager/dnsmasq.d/openshift.conf');
-Cypress.env('HYPERVISOR_IP', '192.168.123.1');
-Cypress.env('BAREMETAL_QE3', 'r640-u09.qe3.kni.lab.eng.bos.redhat.com');
-Cypress.env('HOST_ROLE_MASTER_LABEL', 'Control plane node');
-Cypress.env('HOST_ROLE_WORKER_LABEL', 'Worker');
-Cypress.env('VMWARE_ENV', false);
-Cypress.env('VMWARE_SNO', false);
-Cypress.env('IS_BAREMETAL', false);
diff --git a/libs/ui-lib-tests/cypress/views/forms/OperatorsForm.ts b/libs/ui-lib-tests/cypress/views/forms/Operators/OperatorsForm.ts
similarity index 100%
rename from libs/ui-lib-tests/cypress/views/forms/OperatorsForm.ts
rename to libs/ui-lib-tests/cypress/views/forms/Operators/OperatorsForm.ts
diff --git a/libs/ui-lib-tests/cypress/views/forms/Storage/StorageForm.ts b/libs/ui-lib-tests/cypress/views/forms/Storage/StorageForm.ts
new file mode 100644
index 0000000000..448a377eed
--- /dev/null
+++ b/libs/ui-lib-tests/cypress/views/forms/Storage/StorageForm.ts
@@ -0,0 +1,18 @@
+import { Alert } from '../../reusableComponents/Alert';
+
+export class StorageForm {
+ static readonly alias = `@${StorageForm.name}`;
+ static readonly selector = '.pf-c-wizard__main-body';
+
+ constructor() {
+ cy.findWithinOrGet(StorageForm.selector).as(StorageForm.name);
+ }
+
+ get diskLimitationAlert() {
+ return new Alert(StorageForm.alias, '[data-testid="diskLimitationsAlert"]');
+ }
+
+ get diskFormattingAlert() {
+ return new Alert(StorageForm.alias, '[data-testid="alert-format-bootable-disks"]');
+ }
+}
diff --git a/libs/ui-lib-tests/cypress/views/hostsTableSection.ts b/libs/ui-lib-tests/cypress/views/hostsTableSection.ts
index b0f50e93a8..6d0056ec51 100644
--- a/libs/ui-lib-tests/cypress/views/hostsTableSection.ts
+++ b/libs/ui-lib-tests/cypress/views/hostsTableSection.ts
@@ -1,10 +1,12 @@
+export type ValidateDiskHoldersParams = { name: string; indented?: boolean; warning?: boolean }[];
+
export const hostsTableSection = {
validateHostNames: (
numMasters: number = Cypress.env('NUM_MASTERS'),
numWorkers: number = Cypress.env('NUM_WORKERS'),
hostNames = Cypress.env('requestedHostnames'),
) => {
- cy.get(Cypress.env('hostnameDataTestId'))
+ cy.get('[data-testid=host-name]')
.should('have.length', numMasters + numWorkers)
.each((hostName, idx) => {
expect(hostName).to.contain(hostNames[idx]);
@@ -14,14 +16,14 @@ export const hostsTableSection = {
numMasters: number = Cypress.env('NUM_MASTERS'),
numWorkers: number = Cypress.env('NUM_WORKERS'),
) => {
- cy.get(Cypress.env('roleDataLabel'))
+ cy.get('td[data-testid="host-role"]')
.should('have.length', numMasters + numWorkers)
.each((hostRole, idx) => {
const isMaster = idx <= numMasters - 1;
if (isMaster) {
- expect(hostRole).to.contain(Cypress.env('HOST_ROLE_MASTER_LABEL'));
+ expect(hostRole).to.contain('Control plane node');
} else {
- expect(hostRole).to.contain(Cypress.env('HOST_ROLE_WORKER_LABEL'));
+ expect(hostRole).to.contain('Worker');
}
});
},
@@ -29,7 +31,7 @@ export const hostsTableSection = {
numMasters: number = Cypress.env('NUM_MASTERS'),
numWorkers: number = Cypress.env('NUM_WORKERS'),
) => {
- cy.get(Cypress.env('cpuCoresDataLabel'))
+ cy.get('td[data-label="CPU Cores"]')
.should('have.length', numMasters + numWorkers)
.each((hostCpuCores, idx) => {
const isMaster = idx <= numMasters - 1;
@@ -44,7 +46,7 @@ export const hostsTableSection = {
numMasters: number = Cypress.env('NUM_MASTERS'),
numWorkers: number = Cypress.env('NUM_WORKERS'),
) => {
- cy.get(Cypress.env('memoryDataLabel'))
+ cy.get('td[data-label="Memory"]')
.should('have.length', numMasters + numWorkers)
.each((hostMemory, idx) => {
const isMaster = idx <= numMasters - 1;
@@ -59,7 +61,7 @@ export const hostsTableSection = {
numMasters: number = Cypress.env('NUM_MASTERS'),
numWorkers: number = Cypress.env('NUM_WORKERS'),
) => {
- cy.get(Cypress.env('totalStorageDataLabel'))
+ cy.get('td[data-label="Total storage"]')
.should('have.length', numMasters + numWorkers)
.each((hostDisk, idx) => {
const isMaster = idx <= numMasters - 1;
@@ -101,4 +103,22 @@ export const hostsTableSection = {
);
});
},
+ validateGroupingByDiskHolders: (disks: ValidateDiskHoldersParams, message?: string) => {
+ cy.get('td[data-testid="disk-name"]').then(($diskNames) => {
+ disks.forEach((disk, index) => {
+ cy.wrap($diskNames).eq(index).should('contain.text', disk.name);
+
+ if (disk.indented) {
+ cy.wrap($diskNames).eq(index).find('span').should('exist');
+ } else {
+ cy.wrap($diskNames).eq(index).find('span').should('not.exist');
+ }
+
+ if (disk.warning && message) {
+ cy.wrap($diskNames).eq(index).find('svg').click();
+ cy.get('[data-testid="disk-limitations-popover"').should('contain.text', message);
+ }
+ });
+ });
+ },
};
diff --git a/libs/ui-lib-tests/cypress/views/installationPage.ts b/libs/ui-lib-tests/cypress/views/installationPage.ts
deleted file mode 100644
index 4157d1a904..0000000000
--- a/libs/ui-lib-tests/cypress/views/installationPage.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-export const installationPage = {
- validateInstallConfigWarning: (msg) => {
- cy.get(`.pf-m-warning:contains(${msg})`);
- },
- waitForDownloadKubeconfigToBeEnabled: (timeout = 600000) => {
- cy.get(Cypress.env('clusterDetailButtonDownloadKubeconfigId'), {
- timeout: timeout,
- }).should('be.enabled');
- },
- downloadKubeconfigAndSetKubeconfigEnv: (
- kubeconfigFile,
- timeout = Cypress.env('KUBECONFIG_DOWNLOAD_TIMEOUT'),
- ) => {
- cy.get(Cypress.env('clusterDetailButtonDownloadKubeconfigId')).should('be.visible').click();
- cy.readFile(kubeconfigFile, { timeout: timeout })
- .should('have.length.gt', 50)
- .then((kubeconfig) => {
- Cypress.env('kubeconfig', kubeconfig);
- });
- },
- copyAllFiles: () => {
- installationPage.downloadKubeconfigAndSetKubeconfigEnv(
- Cypress.env('kubeconfigFile'),
- Cypress.env('KUBECONFIG_DOWNLOAD_TIMEOUT'),
- );
- cy.runCopyCmd(Cypress.env('kubeconfigFile'), '~/clusterconfigs/auth/kubeconfig');
- cy.runCopyCmd(Cypress.env('kubeconfigFile'), '~/kubeconfig');
- cy.runCopyCmd(
- Cypress.env('kubeconfigFile'),
- `${Cypress.env(
- 'BASE_REPO_DIR_REMOTE',
- )}/linchpin-workspace/hooks/ansible/ocp-edge-setup/kubeconfig`,
- );
- cy.setKubeAdminPassword(Cypress.env('API_BASE_URL'), Cypress.env('clusterId'), true);
- cy.get('@kubeadmin-password').then((kubeadminPassword) => {
- cy.writeFile(Cypress.env('kubeadminPasswordFilePath'), kubeadminPassword);
- });
- cy.runCopyCmd(
- Cypress.env('kubeadminPasswordFilePath'),
- '~/clusterconfigs/auth/kubeadmin-password',
- );
- cy.runCopyCmd(Cypress.env('kubeadminPasswordFilePath'), '~/kubeadmin-password');
- cy.runCopyCmd(
- Cypress.env('kubeadminPasswordFilePath'),
- `${Cypress.env(
- 'BASE_REPO_DIR_REMOTE',
- )}/linchpin-workspace/hooks/ansible/ocp-edge-setup/kubeadmin-password`,
- );
- cy.setInstallConfig(Cypress.env('API_BASE_URL'), Cypress.env('clusterId'), true);
- cy.get('@install-config').then((installConfig) => {
- cy.writeFile(Cypress.env('installConfigFilePath'), installConfig);
- });
- cy.runCopyCmd(Cypress.env('installConfigFilePath'), '~/install-config.yaml');
- cy.runCopyCmd(
- Cypress.env('installConfigFilePath'),
- `${Cypress.env(
- 'BASE_REPO_DIR_REMOTE',
- )}/linchpin-workspace/hooks/ansible/ocp-edge-setup/install-config.yaml`,
- );
- },
- waitForConsoleTroubleShootingHintToBeVisible: (
- timeout = Cypress.env('WAIT_FOR_CONSOLE_TIMEOUT'),
- ) => {
- cy.newByDataTestId(Cypress.env('clusterDetailClusterCredsTshootHintOpen'), timeout)
- .scrollIntoView()
- .should('be.visible');
- },
- progressStatusShouldContain: (
- status = 'Installed',
- timeout = Cypress.env('WAIT_FOR_PROGRESS_STATUS_INSTALLED'),
- ) => {
- cy.get(Cypress.env('clusterProgressStatusValueId')).scrollIntoView().should('be.visible');
- cy.get(Cypress.env('clusterProgressStatusValueId'), {
- timeout: timeout,
- }).should('contain.text', status);
- },
- operatorsPopover: {
- open: () => {
- cy.get(Cypress.env('operatorsProgressItem')).click();
- },
- validateListItemContents: (msg) => {
- cy.get('.pf-c-popover__body').within(() => {
- cy.get('li').should('contain.text', msg);
- });
- },
- close: () => {
- cy.get('.pf-c-popover__content > .pf-c-button > svg').should('be.visible').click();
- },
- },
-};
diff --git a/libs/ui-lib-tests/cypress/views/reusableComponents/Alert.ts b/libs/ui-lib-tests/cypress/views/reusableComponents/Alert.ts
new file mode 100644
index 0000000000..c3c53e637b
--- /dev/null
+++ b/libs/ui-lib-tests/cypress/views/reusableComponents/Alert.ts
@@ -0,0 +1,20 @@
+export class Alert {
+ static readonly alias = `@${Alert.name}`;
+ static readonly selector = '.pf-c-alert';
+
+ constructor(parentAlias: string, id: string = Alert.selector) {
+ cy.findWithinOrGet(id, parentAlias).as(Alert.name);
+ }
+
+ get body() {
+ return cy.get(Alert.alias);
+ }
+
+ get title() {
+ return this.body.find('.pf-c-alert__title');
+ }
+
+ get description() {
+ return this.body.find('.pf-c-alert__description');
+ }
+}
diff --git a/libs/ui-lib-tests/cypress/views/storagePage.ts b/libs/ui-lib-tests/cypress/views/storagePage.ts
index 7e14a365e9..311b6ae47f 100644
--- a/libs/ui-lib-tests/cypress/views/storagePage.ts
+++ b/libs/ui-lib-tests/cypress/views/storagePage.ts
@@ -3,7 +3,7 @@ export const storagePage = {
numMasters: number = Cypress.env('NUM_MASTERS'),
numWorkers: number = Cypress.env('NUM_WORKERS'),
) => {
- cy.get(Cypress.env('odfUsageDataLabel'))
+ cy.get('td[data-label="ODF Usage"]')
.should('have.length', numMasters + numWorkers)
.each((hostRole, idx) => {
const isMaster = idx <= numMasters - 1;
@@ -18,7 +18,7 @@ export const storagePage = {
numMasters: number = Cypress.env('NUM_MASTERS'),
numWorkers: number = Cypress.env('NUM_WORKERS'),
) => {
- cy.get(Cypress.env('diskNumberDataLabel'))
+ cy.get('td[data-label="Number of disks"]')
.should('have.length', numMasters + numWorkers)
.each((hostDisk) => {
expect(hostDisk).to.contain('3');
@@ -28,7 +28,7 @@ export const storagePage = {
return cy.get(`input[id="select-formatted-${hostId}-${indexSelect}"]`);
},
validateSkipFormattingDisks: (hostId: string, numDisks: number) => {
- cy.get(Cypress.env('skipFormattingDataLabel')).should('have.length', numDisks);
+ cy.get("td[data-label='Format?']").should('have.length', numDisks);
//Checking if checkboxes are checked/unchecked
storagePage.getSkipFormattingCheckbox(hostId, 0).should('not.be.checked');
storagePage.getSkipFormattingCheckbox(hostId, 1).should('be.checked');
@@ -39,17 +39,20 @@ export const storagePage = {
storagePage.getSkipFormattingCheckbox(hostId, 2).should('be.disabled');
},
validateSkipFormattingWarning: () => {
- cy.get('.pf-c-alert__title').should('contain.text', Cypress.env('skipFormattingWarningTitle'));
+ cy.get('.pf-c-alert__title').should(
+ 'contain.text',
+ 'There might be issues with the boot order',
+ );
cy.get('.pf-c-alert__description').should(
'contain.text',
- Cypress.env('skipFormattingWarningDesc'),
+ 'You have opted out of formatting bootable disks on some hosts. To ensure the hosts reboot into the expected installation disk, manual user intervention might be required during OpenShift installation.',
);
},
validateSkipFormattingIcon: (diskId: string) => {
//If a disk is skip formatting validate that warning icon is shown
cy.get(`[data-testid="disk-row-${diskId}"] [data-testid="disk-name"]`).within(
(/* $diskRow */) => {
- cy.get('[role="img"]').should('have.attr', 'fill', Cypress.env('warningIconFillColor'));
+ cy.get('[role="img"]').should('have.attr', 'fill', '#f0ab00');
},
);
},
diff --git a/libs/ui-lib/lib/common/components/storage/DisksTable.tsx b/libs/ui-lib/lib/common/components/storage/DisksTable.tsx
index a0abb9758c..a9376d5c42 100644
--- a/libs/ui-lib/lib/common/components/storage/DisksTable.tsx
+++ b/libs/ui-lib/lib/common/components/storage/DisksTable.tsx
@@ -1,5 +1,12 @@
import React from 'react';
-import { TextContent, Text, TextVariants, Popover } from '@patternfly/react-core';
+import {
+ TextContent,
+ Text,
+ TextVariants,
+ Popover,
+ Alert,
+ AlertVariant,
+} from '@patternfly/react-core';
import {
Table,
TableHeader,
@@ -55,6 +62,80 @@ const SkipFormattingDisk = () => (
);
+const getDiskLimitation = (
+ diskName: Disk['name'],
+ hostName: Host['requestedHostname'],
+ holder: Disk,
+) => {
+ if (holder.driveType) {
+ switch (holder.driveType) {
+ case 'LVM':
+ return `LVM logical volumes were found on the installation disk ${
+ diskName as string
+ } selected for host ${hostName as string} and will be deleted during installation.`;
+ case 'RAID':
+ return `The installation disk ${diskName as string} selected for host ${
+ hostName as string
+ }, is part of a software RAID that will be deleted during the installation.`;
+ case 'Multipath':
+ return `The installation disk ${diskName as string} selected for host ${
+ hostName as string
+ } is managed by multipath. We strongly recommend using the multipath device ${
+ holder.name as string
+ } to improve reliability.`;
+ }
+ }
+};
+
+const DiskName = ({
+ disk,
+ disks,
+ host,
+ installationDiskId,
+}: {
+ disk: Disk;
+ disks: Disk[];
+ host: Host;
+ installationDiskId?: string;
+}) => {
+ const isIndented = disk.holders?.split(',').length === 1;
+ let diskLimitations = null;
+
+ if (disk.id === installationDiskId) {
+ const parentDisk = disks.find((d) => disk.holders?.split(',').includes(d.name as string));
+ if (parentDisk) {
+ diskLimitations = getDiskLimitation(disk.name, host.requestedHostname, parentDisk);
+ }
+ }
+
+ return (
+ <>
+ {isIndented && }
+ {isInDiskSkipFormattingList(host, disk.id) && (
+ } minWidth="20rem" maxWidth="30rem">
+
+
+ )}
+ {' '}
+ {disk.bootable ? `${disk.name || ''} (bootable)` : disk.name}
+ {diskLimitations && (
+ <>
+ {' '}
+ }
+ minWidth="20rem"
+ maxWidth="30rem"
+ data-testid="disk-limitations-popover"
+ >
+
+
+ >
+ )}
+ >
+ );
+};
+
const DisksTable = ({
canEditDisks,
host,
@@ -67,21 +148,25 @@ const DisksTable = ({
const isEditable = !!canEditDisks?.(host);
const diskColumnTitles = diskColumns(isEditable);
- const rows: IRow[] = [...disks]
- .sort((diskA, diskB) => diskA.name?.localeCompare(diskB.name || '') || 0)
+ const rows: IRow[] = disks
+ .filter((disk) => disk.driveType !== 'LVM')
+ .sort((a, b) => (a.name && a.name.localeCompare(b.name as string)) || 0)
+ .sort((a, b) => {
+ const aVal = (a.holders || a.name) as string;
+ const bVal = (b.holders || b.name) as string;
+
+ return aVal?.localeCompare(bVal) || 0;
+ })
.map((disk, index) => ({
cells: [
{
title: (
- <>
- {isInDiskSkipFormattingList(host, disk.id) && (
- } minWidth="20rem" maxWidth="30rem">
-
-
- )}
- {' '}
- {disk.bootable ? `${disk.name || ''} (bootable)` : disk.name}
- >
+
),
props: { 'data-testid': 'disk-name' },
},
@@ -135,4 +220,4 @@ const DisksTable = ({
);
};
-export default DisksTable;
+export { DisksTable, getDiskLimitation };
diff --git a/libs/ui-lib/lib/common/components/storage/StorageDetail.tsx b/libs/ui-lib/lib/common/components/storage/StorageDetail.tsx
index 9ac1e4f689..3c4521402c 100644
--- a/libs/ui-lib/lib/common/components/storage/StorageDetail.tsx
+++ b/libs/ui-lib/lib/common/components/storage/StorageDetail.tsx
@@ -3,7 +3,7 @@ import { Grid, GridItem } from '@patternfly/react-core';
import { getInventory, Host } from '../../index';
import { OnDiskRoleType } from '../hosts/DiskRole';
import { DiskFormattingType } from '../hosts/FormatDiskCheckbox';
-import DisksTable from './DisksTable';
+import { DisksTable } from './DisksTable';
import SectionTitle from '../ui/SectionTitle';
type StorageDetailProps = {
diff --git a/libs/ui-lib/lib/ocm/components/hosts/OdfDisksManualFormattingHint.tsx b/libs/ui-lib/lib/ocm/components/hosts/OdfDisksManualFormattingHint.tsx
index 8daf655f61..a27dc1e2bf 100644
--- a/libs/ui-lib/lib/ocm/components/hosts/OdfDisksManualFormattingHint.tsx
+++ b/libs/ui-lib/lib/ocm/components/hosts/OdfDisksManualFormattingHint.tsx
@@ -1,15 +1,5 @@
import React from 'react';
-import { Alert, AlertVariant, Text, TextContent, TextVariants } from '@patternfly/react-core';
-
-const Hint = () => (
-
-
- All non-installation disks will be used for local storage and must be formatted before the
- storage operator's installation. To view and format available disks, expand each host row in
- the table.
-
-
-);
+import { Alert, AlertVariant } from '@patternfly/react-core';
const OdfDisksManualFormattingHint = () => {
return (
@@ -18,7 +8,9 @@ const OdfDisksManualFormattingHint = () => {
isInline
title="Make sure you format all non-installation disks"
>
-
+ All non-installation disks will be used for local storage and must be formatted before the
+ storage operator's installation. To view and format available disks, expand each host row in
+ the table.
);
};
diff --git a/libs/ui-lib/lib/ocm/components/hosts/StorageAlerts.tsx b/libs/ui-lib/lib/ocm/components/hosts/StorageAlerts.tsx
index ec7cde4f8f..3686f62d9d 100644
--- a/libs/ui-lib/lib/ocm/components/hosts/StorageAlerts.tsx
+++ b/libs/ui-lib/lib/ocm/components/hosts/StorageAlerts.tsx
@@ -1,13 +1,16 @@
import * as React from 'react';
-import { Stack, StackItem } from '@patternfly/react-core';
+import { Alert, AlertVariant, List, ListItem, Stack, StackItem } from '@patternfly/react-core';
import {
Cluster,
FormatDiskWarning,
+ getInventory,
hasEnabledOperators,
+ Host,
OPERATOR_NAME_ODF,
} from '../../../common';
import { isAddHostsCluster, isSomeDisksSkipFormatting } from '../clusters/utils';
import OdfDisksManualFormattingHint from './OdfDisksManualFormattingHint';
+import { getDiskLimitation } from '../../../common/components/storage/DisksTable';
const StorageAlerts = ({ cluster }: { cluster: Cluster }) => {
const showFormattingHint =
@@ -15,8 +18,54 @@ const StorageAlerts = ({ cluster }: { cluster: Cluster }) => {
!isAddHostsCluster(cluster);
const someDisksAreSkipFormatting = isSomeDisksSkipFormatting(cluster);
+ const diskLimitations = [...(cluster.hosts as Host[])]
+ ?.sort(
+ (a, b) =>
+ (a.requestedHostname && a.requestedHostname.localeCompare(b.requestedHostname as string)) ||
+ 0,
+ )
+ ?.map((host) => {
+ const disks = getInventory(host).disks || [];
+ const installationDisk = disks.find((disk) => disk.id === host.installationDiskId);
+ if (installationDisk) {
+ const holder = disks.find((d) =>
+ installationDisk.holders?.split(',').includes(d.name as string),
+ );
+
+ if (holder) {
+ return getDiskLimitation(installationDisk.name, host.requestedHostname, holder);
+ }
+ }
+ })
+ .filter(Boolean);
+
return (
+ {!!diskLimitations.length && (
+
+ {diskLimitations?.length === 1 ? (
+
+ ) : (
+
+
+ {diskLimitations.map((warning, index) => (
+ {warning}
+ ))}
+
+
+ )}
+
+ )}
{showFormattingHint && (