Skip to content

Commit

Permalink
NAS-129643 / 24.10 / Pool tint in new enclosure (#10301)
Browse files Browse the repository at this point in the history
  • Loading branch information
undsoft authored Jul 10, 2024
1 parent 2a8d54e commit c9854bc
Show file tree
Hide file tree
Showing 32 changed files with 550 additions and 210 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ import { EnclosureStatus } from 'app/enums/enclosure-slot-status.enum';
import { DashboardEnclosure, DashboardEnclosureSlot } from 'app/interfaces/enclosure.interface';

export function addDisksToSlots(enclosures: DashboardEnclosure[], percentageToAdd: number): DashboardEnclosure[] {
const totalSlots = countSlots(enclosures);
let totalAddedDisks = -1;

return mapSlots(enclosures, ({ slot, index }) => {
if (index > totalSlots * percentageToAdd) {
return mapSlots(enclosures, ({ slot, enclosure, index }) => {
const slotsInEnclosure = countSlots(enclosure);
if (index > slotsInEnclosure * percentageToAdd) {
return slot;
}

return addDisk(slot, index);
totalAddedDisks = totalAddedDisks + 1;

return addDisk(slot, totalAddedDisks);
});
}

Expand Down Expand Up @@ -43,6 +46,5 @@ function generateDiskName(index: number): string {
index = Math.floor(index / base) - 1;
}

result = 'sd' + String.fromCharCode(offset + index) + result;
return result;
return 'sd' + String.fromCharCode(offset + index) + result;
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,73 @@
import { countSlotsBy, mapSlots } from 'app/core/testing/mock-enclosure/enclosure-templates/utils/slots.utils';
import { EnclosureDiskStatus } from 'app/enums/enclosure-slot-status.enum';
import { mapSlots } from 'app/core/testing/mock-enclosure/enclosure-templates/utils/slots.utils';
import { EnclosureDiskStatus, EnclosureElementType } from 'app/enums/enclosure-slot-status.enum';
import { VdevType } from 'app/enums/v-dev-type.enum';
import { DashboardEnclosure } from 'app/interfaces/enclosure.interface';
import { DashboardEnclosure, DashboardEnclosureSlot } from 'app/interfaces/enclosure.interface';

// TODO: Only creates single disk vdevs in the same pool. Improve.
// TODO: Messy.
export function addPoolsToDisks(enclosures: DashboardEnclosure[], percentageToAdd: number): DashboardEnclosure[] {
return mapSlots(enclosures, ({ slot, enclosure, index }) => {
const totalDisks = countSlotsBy([enclosure], ({ dev }) => Boolean(dev));
const disksPerVdev = 3;
const poolsToAdd = 40;

if (index >= totalDisks * percentageToAdd) {
return slot;
const slotsToUpdate: DashboardEnclosureSlot[] = [];
enclosures.forEach((enclosure) => {
const enclosureSlotsWithDisks = Object.values(enclosure.elements[EnclosureElementType.ArrayDeviceSlot])
.filter((slot) => Boolean(slot.dev));

const enclosureSlotsToUse = enclosureSlotsWithDisks.slice(
0,
Math.floor(enclosureSlotsWithDisks.length * percentageToAdd),
);

slotsToUpdate.push(...enclosureSlotsToUse);
});

const vdevs = groupDisksIntoVdevs(slotsToUpdate, disksPerVdev);

let i = 0;
return mapSlots(enclosures, ({ slot: originalSlot, enclosure }) => {
const updatedSlot = slotsToUpdate.find((slot) => slot.dev === originalSlot.dev);

if (!updatedSlot) {
return originalSlot;
}

const vdevIndex = Math.floor(i / disksPerVdev);
const vdev = vdevs[vdevIndex];
i = i + 1;

return {
...slot,
...originalSlot,
pool_info: {
pool_name: 'Test Pool',
pool_name: `pool-${vdevIndex % poolsToAdd + 1}`,
disk_status: EnclosureDiskStatus.Online,
disk_read_errors: 0,
disk_write_errors: 0,
disk_checksum_errors: 0,
vdev_name: 'stripe',
vdev_type: VdevType.Data,
vdev_disks: [{
enclosure_id: '5b0bd6d1a30714bf',
slot: slot.drive_bay_number,
dev: slot.dev,
}],
vdev_disks: vdev.map((disk) => ({
// TODO: Bug. Enclosure id should come from the disk.
enclosure_id: enclosure.id,
slot: disk.drive_bay_number,
dev: disk.dev,
})),
},
};
});
}

function groupDisksIntoVdevs(slots: DashboardEnclosureSlot[], disksPerVdev: number): DashboardEnclosureSlot[][] {
return slots.reduce((acc, slot, index) => {
const vdevIndex = Math.floor(index / disksPerVdev);
if (!acc[vdevIndex]) {
acc[vdevIndex] = [];
}

acc[vdevIndex].push(slot);
return acc;
}, [] as DashboardEnclosureSlot[][]);
}

export function randomizeDiskStatuses(enclosures: DashboardEnclosure[]): DashboardEnclosure[] {
const allStatuses = Object.values(EnclosureDiskStatus);
return mapSlots(enclosures, ({ slot, index }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { EnclosureElementType } from 'app/enums/enclosure-slot-status.enum';
import {
DashboardEnclosure,
DashboardEnclosureSlot,
DashboardEnclosureElements,
} from 'app/interfaces/enclosure.interface';
import { mapSlots, countSlots } from './slots.utils';

describe('mapSlots', () => {
it('should correctly map slots for each enclosure', () => {
const enclosures = [
{
elements: {
[EnclosureElementType.ArrayDeviceSlot]: {
1: { model: 'test1' } as DashboardEnclosureSlot,
2: { model: 'test2' } as DashboardEnclosureSlot,
},
} as DashboardEnclosureElements,
},
] as DashboardEnclosure[];

const result = mapSlots(enclosures, ({ slot }) => ({
...slot,
model: `mapped-${slot.model}`,
}));

expect(result).toEqual([
{
elements: {
[EnclosureElementType.ArrayDeviceSlot]: {
1: { model: 'mapped-test1' } as DashboardEnclosureSlot,
2: { model: 'mapped-test2' } as DashboardEnclosureSlot,
},
} as DashboardEnclosureElements,
},
]);
});
});

describe('countSlots', () => {
it('should return the correct number of slots', () => {
const enclosure = {
elements: {
[EnclosureElementType.ArrayDeviceSlot]: {
1: {} as DashboardEnclosureSlot,
2: {} as DashboardEnclosureSlot,
},
} as DashboardEnclosureElements,
} as DashboardEnclosure;
const result = countSlots(enclosure);
expect(result).toBe(2);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface SlotMappingFunction {

export function mapSlots(
enclosures: DashboardEnclosure[],
mappingFunction: ({ slot, enclosure, index }: SlotMappingFunction) => DashboardEnclosureSlot,
mappingFunction: ({ slot, index }: SlotMappingFunction) => DashboardEnclosureSlot,
): DashboardEnclosure[] {
return enclosures.map((enclosure) => {
let i = -1;
Expand All @@ -29,17 +29,6 @@ export function mapSlots(
});
}

export function countSlots(enclosures: DashboardEnclosure[]): number {
return enclosures.reduce((acc, enclosure) => {
return acc + Object.keys(enclosure.elements[EnclosureElementType.ArrayDeviceSlot]).length;
}, 0);
}

export function countSlotsBy(
enclosures: DashboardEnclosure[],
predicate: (slot: DashboardEnclosureSlot) => boolean,
): number {
return enclosures.reduce((acc, enclosure) => {
return acc + Object.values(enclosure.elements[EnclosureElementType.ArrayDeviceSlot]).filter(predicate).length;
}, 0);
export function countSlots(enclosure: DashboardEnclosure): number {
return Object.keys(enclosure.elements[EnclosureElementType.ArrayDeviceSlot]).length;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ export enum MockStorageScenario {

export const mockStorageScenarioLabels = new Map<MockStorageScenario, string>([
[MockStorageScenario.AllSlotsEmpty, 'All slots empty'],
[MockStorageScenario.FillSomeSlots, 'Add disks to some slots and add a pool to some disks.'],
[MockStorageScenario.FillAllSlots, 'Add disks to all slots and add a pool to all disks'],
[MockStorageScenario.FillSomeSlots, 'Add disks to some slots and add pools to some disks.'],
[MockStorageScenario.FillAllSlots, 'Add disks to all slots and add pools to all disks'],
[MockStorageScenario.DiskStatuses, 'Fill some slots and use all disk statuses'],
]);

/**
* @deprecated
*/
export enum MockDiskType {
'Hdd' = 'Hdd',
'Nvme' = 'Nvme',
Expand Down
4 changes: 2 additions & 2 deletions src/app/interfaces/enclosure-old.interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DiskPowerLevel } from 'app/enums/disk-power-level.enum';
import { DiskStandby } from 'app/enums/disk-standby.enum';
import { EnclosureElement, EnclosureSlotMetadata, EnclosureVdev } from 'app/interfaces/enclosure.interface';
import { EnclosureElement, EnclosureSlotMetadata, EnclosureVdevDisk } from 'app/interfaces/enclosure.interface';

/**
* @deprecated
Expand Down Expand Up @@ -73,7 +73,7 @@ export interface EnclosureOldPool {
disk_status: string;
vdev_name: string;
vdev_type: string;
vdev_disks: EnclosureVdev[];
vdev_disks: EnclosureVdevDisk[];
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/app/interfaces/enclosure.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export interface DashboardEnclosureSlot {
pool_info: EnclosureSlotPoolInfo | null;
}

export interface EnclosureVdev {
export interface EnclosureVdevDisk {
enclosure_id: string;
slot: number;
dev: string;
Expand All @@ -102,7 +102,7 @@ export interface EnclosureSlotPoolInfo {
disk_checksum_errors?: number;
vdev_name: string;
vdev_type: VdevType;
vdev_disks: EnclosureVdev[];
vdev_disks: EnclosureVdevDisk[];
}

export type DashboardEnclosureElements = Overwrite<EnclosureElements, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
<div class="container">
@if (selectedSlot().dev) {
<h1>{{ selectedSlot().dev }}</h1>
}
@if (selectedSlot().pool_info) {
<span>
<span class="title">{{ 'Topology' | translate }}:</span>
<span class="value">{{ selectedSlot().pool_info.vdev_name }}</span>
@if (selectedSlot().dev) {
<h1>{{ selectedSlot().dev }}</h1>
}
@if (selectedSlot().pool_info) {
<div>
<span class="title">{{ 'Topology' | translate }}:</span>
<span class="value">{{ selectedSlot().pool_info.vdev_name }}</span>
</div>
}
@if (selectedSlot().pool_info) {
<div>
<span class="title">{{ 'Category' | translate }}:</span>
<span class="value">
{{ selectedSlot().pool_info.vdev_type | mapValue: vdevTypeLabels | translate }}
</span>
}
@if (selectedSlot().pool_info) {
<span>
<span class="title">{{ 'Category' | translate }}:</span>
<span class="value">
{{ selectedSlot().pool_info.vdev_type | mapValue: vdevTypeLabels | translate }}
</span>
</span>
}
<span>
<span class="title">{{ 'Slot' | translate }}:</span>
<span class="value">{{ selectedSlot().drive_bay_number }}</span>
</span>
</div>
}
<div>
<span class="title">{{ 'Slot' | translate }}:</span>
<span class="value">{{ selectedSlot().drive_bay_number }}</span>
</div>
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
.container {
align-items: center;
color: white;
display: flex;
flex-direction: column;
height: 100%;
justify-content: center;
min-width: 105px;
:host {
text-align: center;
}

h1 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<ix-disk-topology-description
[selectedSlot]="selectedSlot()"
></ix-disk-topology-description>
} @else {
<h2>{{ 'Pools' | translate }}</h2>
}
</div>

Expand All @@ -26,8 +28,15 @@

<div class="right-column">
@if (selectedSlot()) {
<ix-vdev-disks-list
<ix-vdev-disks-legend
[selectedSlot]="selectedSlot()"
></ix-vdev-disks-list>
[poolColor]="poolColor()"
(diskClick)="onVdevDiskClicked($event)"
></ix-vdev-disks-legend>
} @else {
<ix-pools-legend
[slots]="selectedEnclosureSlots()"
[poolColors]="poolColors()"
></ix-pools-legend>
}
</div>
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
@import 'scss-imports/cssvars';

:host {
box-sizing: border-box;
column-gap: 5px;
display: flex;
height: 100%;
padding: 0 5px;
width: 100%;

@media (max-width: $breakpoint-md) {
Expand All @@ -15,8 +17,14 @@

.left-column,
.right-column {
align-items: center;
display: flex;
flex: 1 1 25%;
flex-direction: column;
justify-content: center;
max-width: 200px;
min-height: 100%;
min-width: 105px;

@media (max-width: $breakpoint-md) {
max-width: 100%;
Expand Down
Loading

0 comments on commit c9854bc

Please sign in to comment.