From c8d608e5654b0508a099e78b948b69bfbea07cfe Mon Sep 17 00:00:00 2001 From: astandrik Date: Thu, 26 Dec 2024 19:14:17 +0300 Subject: [PATCH 1/6] feat: set pdisks column width --- src/containers/Storage/PDisk/PDisk.scss | 13 +- src/containers/Storage/PDisk/PDisk.tsx | 16 +- .../columns/StorageNodesColumns.scss | 8 +- .../Storage/StorageNodes/columns/columns.tsx | 18 +- .../Storage/StorageNodes/getNodes.ts | 50 ++-- src/mocks/storage/nodes.ts | 224 ++++++++++++++++++ src/store/reducers/storage/types.ts | 1 + src/store/reducers/storage/utils.ts | 21 +- src/styles/mixins.scss | 20 ++ src/types/api/nodes.ts | 2 + 10 files changed, 320 insertions(+), 53 deletions(-) create mode 100644 src/mocks/storage/nodes.ts diff --git a/src/containers/Storage/PDisk/PDisk.scss b/src/containers/Storage/PDisk/PDisk.scss index d6aa887fe..12b5b6e9a 100644 --- a/src/containers/Storage/PDisk/PDisk.scss +++ b/src/containers/Storage/PDisk/PDisk.scss @@ -1,19 +1,16 @@ -.pdisk-storage { - --pdisk-vdisk-width: 3px; - --pdisk-gap-width: 2px; +@import '../../../styles/mixins.scss'; +.pdisk-storage { position: relative; display: flex; flex-direction: column; justify-content: flex-end; - width: calc( - var(--pdisk-max-slots, 1) * var(--pdisk-vdisk-width) + (var(--pdisk-max-slots, 1) - 1) * - var(--pdisk-gap-width) - ); - min-width: 120px; + width: var(--pdisk-width); + min-width: var(--pdisk-min-width); + @include calculate-storage-pdisk-variables(); &__content { position: relative; diff --git a/src/containers/Storage/PDisk/PDisk.tsx b/src/containers/Storage/PDisk/PDisk.tsx index e6cc14073..b37b5bbf4 100644 --- a/src/containers/Storage/PDisk/PDisk.tsx +++ b/src/containers/Storage/PDisk/PDisk.tsx @@ -16,8 +16,6 @@ import './PDisk.scss'; const b = cn('pdisk-storage'); -const PDISK_MAX_SLOTS_CSS_VAR = '--pdisk-max-slots'; - interface PDiskProps { data?: PreparedPDisk; vDisks?: PreparedVDisk[]; @@ -27,7 +25,6 @@ interface PDiskProps { className?: string; progressBarClassName?: string; viewContext?: StorageViewContext; - maximumSlotsPerDisk?: string; } export const PDisk = ({ @@ -39,7 +36,6 @@ export const PDisk = ({ className, progressBarClassName, viewContext, - maximumSlotsPerDisk, }: PDiskProps) => { const {NodeId, PDiskId} = data; const pDiskIdsDefined = valueIsDefined(NodeId) && valueIsDefined(PDiskId); @@ -77,17 +73,7 @@ export const PDisk = ({ } return ( -
+
{renderVDisks()} { return { name: NODES_COLUMNS_IDS.PDisks, header: NODES_COLUMNS_TITLES.PDisks, className: b('pdisks-column'), render: ({row}) => { + const pDiskStyles = { + [MAX_SLOTS_CSS_VAR]: row.MaximumSlotsPerDisk, + [MAX_DISKS_CSS_VAR]: row.MaximumDisksPerNode, + } as React.CSSProperties; + return ( -
+
{row.PDisks?.map((pDisk) => { const vDisks = row.VDisks?.filter( (vdisk) => vdisk.PDiskId === pDisk.PDiskId, @@ -45,12 +53,7 @@ const getPDisksColumn = ({viewContext}: GetStorageNodesColumnsParams): StorageNo return (
- +
); })} @@ -59,7 +62,6 @@ const getPDisksColumn = ({viewContext}: GetStorageNodesColumnsParams): StorageNo }, align: DataTable.CENTER, sortable: false, - width: 900, resizeable: false, }; }; diff --git a/src/containers/Storage/StorageNodes/getNodes.ts b/src/containers/Storage/StorageNodes/getNodes.ts index be9a0714e..fcb62b456 100644 --- a/src/containers/Storage/StorageNodes/getNodes.ts +++ b/src/containers/Storage/StorageNodes/getNodes.ts @@ -3,6 +3,7 @@ import { NODES_COLUMNS_TO_DATA_FIELDS, getNodesColumnSortField, } from '../../../components/nodesColumns/constants'; +import {generateNodes} from '../../../mocks/storage/nodes'; import type { PreparedStorageNode, PreparedStorageNodeFilters, @@ -12,7 +13,6 @@ import type {NodesRequestParams} from '../../../types/api/nodes'; import {prepareSortValue} from '../../../utils/filters'; import {getUptimeParamValue} from '../../../utils/nodes'; import {getRequiredDataFields} from '../../../utils/tableUtils/getRequiredDataFields'; - export const getStorageNodes: FetchData< PreparedStorageNode, PreparedStorageNodeFilters, @@ -38,28 +38,42 @@ export const getStorageNodes: FetchData< filterGroupBy, } = filters ?? {}; const {sortOrder, columnId} = sortParams ?? {}; - const sortField = getNodesColumnSortField(columnId); const sort = sortField ? prepareSortValue(sortField, sortOrder) : undefined; const dataFieldsRequired = getRequiredDataFields(columnsIds, NODES_COLUMNS_TO_DATA_FIELDS); - const response = await window.api.viewer.getNodes({ - type, - storage, - limit, - offset, - sort, - filter: searchValue, - uptime: getUptimeParamValue(nodesUptimeFilter), - with: visibleEntities, - database, - node_id: nodeId, - group_id: groupId, - filter_group: filterGroup, - filter_group_by: filterGroupBy, - fieldsRequired: dataFieldsRequired, - }); + let response; + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.get('mocks')) { + // Get mock configuration from URL parameters or use defaults + const pdisks = parseInt(urlParams.get('pdisks') || '10', 10); + const vdisksPerPDisk = parseInt(urlParams.get('vdisksPerPDisk') || '2', 10); + const totalNodes = parseInt(urlParams.get('totalNodes') || '50', 10); + response = generateNodes(totalNodes, { + maxVdisksPerPDisk: vdisksPerPDisk, + maxPdisks: pdisks, + offset, + limit, + }); + } else { + response = await window.api.viewer.getNodes({ + type, + storage, + limit, + offset, + sort, + filter: searchValue, + uptime: getUptimeParamValue(nodesUptimeFilter), + with: visibleEntities, + database, + node_id: nodeId, + group_id: groupId, + filter_group: filterGroup, + filter_group_by: filterGroupBy, + fieldsRequired: dataFieldsRequired, + }); + } const preparedResponse = prepareStorageNodesResponse(response); return { data: preparedResponse.nodes || [], diff --git a/src/mocks/storage/nodes.ts b/src/mocks/storage/nodes.ts new file mode 100644 index 000000000..751df1dbe --- /dev/null +++ b/src/mocks/storage/nodes.ts @@ -0,0 +1,224 @@ +import {EFlag} from '../../types/api/enums'; +import type { + TEndpoint, + TNodeInfo, + TNodesInfo, + TPoolStats, + TSystemStateInfo, +} from '../../types/api/nodes'; +import {TPDiskState} from '../../types/api/pdisk'; +import {EVDiskState} from '../../types/api/vdisk'; + +// Different disk sizes to simulate variety (in bytes) +const DISK_SIZES = [ + '68719476736', // 64 GB + '137438953472', // 128 GB + '274877906944', // 256 GB + '549755813888', // 512 GB + '1099511627776', // 1 TB +]; + +const getRandomDiskSize = () => DISK_SIZES[Math.floor(Math.random() * DISK_SIZES.length)]; + +const generatePoolStats = (count = 5): TPoolStats[] => { + const poolNames = ['System', 'User', 'Batch', 'IO', 'IC'] as const; + return poolNames.slice(0, count).map((Name) => ({ + Name, + Usage: Math.random() * 0.02, + Threads: Math.floor(Math.random() * 3) + 1, + })); +}; + +const generateEndpoints = (): TEndpoint[] => [ + {Name: 'ic', Address: ':19001'}, + {Name: 'http-mon', Address: ':8765'}, + {Name: 'grpcs', Address: ':2135'}, + {Name: 'grpc', Address: ':2136'}, +]; + +const generateSystemState = (nodeId: number): TSystemStateInfo => ({ + StartTime: '1734358137851', + ChangeTime: '1734358421375', + LoadAverage: [3.381347656, 2.489257813, 1.279296875], + NumberOfCpus: 8, + SystemState: EFlag.Green, + NodeId: nodeId, + Host: `localhost-${nodeId}`, + Version: 'main.95ce0df', + PoolStats: generatePoolStats(), + Endpoints: generateEndpoints(), + Roles: ['Bootstrapper', 'StateStorage', 'StateStorageBoard', 'SchemeBoard', 'Storage'], + MemoryLimit: '2147483648', + MaxDiskUsage: 0.002349853516, + Location: { + DataCenter: '1', + Rack: '1', + Unit: '1', + }, + TotalSessions: 0, + CoresUsed: 0.07583969556, + CoresTotal: 8, +}); + +const generatePDisk = (nodeId: number, pdiskId: number, totalSize = '68719476736') => ({ + PDiskId: pdiskId, + ChangeTime: '1734358142074', + Path: `/ydb_data/pdisk${pdiskId}l3ki78no.data`, + Guid: pdiskId.toString(), + Category: '0', + TotalSize: totalSize, + AvailableSize: (Number(totalSize) * 0.9).toString(), // 90% available by default + State: TPDiskState.Normal, + NodeId: nodeId, + Device: EFlag.Green, + Realtime: EFlag.Green, + SerialNumber: '', + SystemSize: '213909504', + LogUsedSize: '35651584', + LogTotalSize: '68486692864', + EnforcedDynamicSlotSize: '22817013760', +}); + +const generateVDisk = (nodeId: number, vdiskId: number, pdiskId: number) => ({ + VDiskId: { + GroupID: vdiskId, + GroupGeneration: 1, + Ring: 0, + Domain: 0, + VDisk: 0, + }, + ChangeTime: '1734358420919', + PDiskId: pdiskId, + VDiskSlotId: vdiskId, + Guid: '1', + Kind: '0', + NodeId: nodeId, + VDiskState: EVDiskState.OK, + DiskSpace: EFlag.Green, + SatisfactionRank: { + FreshRank: { + Flag: EFlag.Green, + }, + LevelRank: { + Flag: EFlag.Green, + }, + }, + Replicated: true, + ReplicationProgress: 1, + ReplicationSecondsRemaining: 0, + AllocatedSize: '0', + AvailableSize: '22817013760', + HasUnreadableBlobs: false, + IncarnationGuid: '11528832187803248876', + InstanceGuid: '14836434871903384493', + FrontQueues: EFlag.Green, + StoragePoolName: 'static', + ReadThroughput: '0', + WriteThroughput: '420', +}); + +interface NodeGeneratorOptions { + maxVdisksPerPDisk?: number; + maxPdisks?: number; +} + +const DEFAULT_OPTIONS: NodeGeneratorOptions = { + maxVdisksPerPDisk: 3, + maxPdisks: 4, +}; + +const generateNode = (nodeId: number, options: NodeGeneratorOptions = {}): TNodeInfo => { + const maxPdisks = options.maxPdisks ?? DEFAULT_OPTIONS.maxPdisks!; + const maxVdisksPerPDisk = options.maxVdisksPerPDisk ?? DEFAULT_OPTIONS.maxVdisksPerPDisk!; + + // Generate a random number of pdisks up to maxPdisks + const pdisksCount = Math.floor(Math.random() * maxPdisks) + 1; + + // For each pdisk, generate a random number of vdisks up to maxVdisksPerPDisk + const pdiskVdisksCounts = Array.from({length: pdisksCount}, () => + Math.floor(Math.random() * maxVdisksPerPDisk), + ); + const totalVdisks = pdiskVdisksCounts.reduce((sum: number, count: number) => sum + count, 0); + + return { + NodeId: nodeId, + UptimeSeconds: 284, + CpuUsage: 0.00947996, + DiskSpaceUsage: 0.234985, + SystemState: generateSystemState(nodeId), + PDisks: Array.from({length: pdisksCount}, (_, i) => + generatePDisk(nodeId, i + 1, getRandomDiskSize()), + ), + VDisks: Array.from({length: totalVdisks}, (_, i) => { + // Find which pdisk this vdisk belongs to based on the distribution + let pdiskIndex = 0; + let vdiskCount = pdiskVdisksCounts[0]; + while (i >= vdiskCount && pdiskIndex < pdisksCount - 1) { + pdiskIndex++; + vdiskCount += pdiskVdisksCounts[pdiskIndex]; + } + return generateVDisk(nodeId, i, pdiskIndex + 1); + }), + }; +}; + +interface GenerateNodesOptions extends NodeGeneratorOptions { + offset?: number; + limit?: number; +} + +// Keep a cache of generated nodes to maintain consistency between paginated requests +let cachedNodes: TNodeInfo[] | null = null; +let currentTotalNodes = 50; // Default number of nodes + +export const generateNodes = (count?: number, options: GenerateNodesOptions = {}): TNodesInfo => { + const totalNodes = count ?? currentTotalNodes; + const {offset = 0, limit = totalNodes, maxVdisksPerPDisk, maxPdisks} = options; + + // Reset cache if total nodes count changes + if (totalNodes !== currentTotalNodes) { + cachedNodes = null; + currentTotalNodes = totalNodes; + } + + // Generate or use cached nodes + if (!cachedNodes) { + cachedNodes = Array.from({length: totalNodes}, (_, i) => + generateNode(i + 1, {maxVdisksPerPDisk, maxPdisks}), + ); + } + + // Calculate MaximumSlotsPerDisk and MaximumDisksPerNode across all nodes + let maxSlotsPerDisk = 0; + let maxDisksPerNode = 0; + + cachedNodes.forEach((node) => { + // Count pdisks per node + if (node.PDisks) { + maxDisksPerNode = Math.max(maxDisksPerNode, node.PDisks.length); + } + + // Count vdisks per pdisk + if (node.VDisks) { + const pdiskVdiskCounts = new Map(); + node.VDisks.forEach((vdisk) => { + if (typeof vdisk.PDiskId === 'number') { + const count = (pdiskVdiskCounts.get(vdisk.PDiskId) || 0) + 1; + pdiskVdiskCounts.set(vdisk.PDiskId, count); + maxSlotsPerDisk = Math.max(maxSlotsPerDisk, count); + } + }); + } + }); + + // Get the requested slice of nodes + const paginatedNodes = cachedNodes.slice(offset, offset + limit); + + return { + TotalNodes: totalNodes.toString(), + FoundNodes: totalNodes.toString(), + Nodes: paginatedNodes, + MaximumSlotsPerDisk: maxSlotsPerDisk.toString(), + MaximumDisksPerNode: maxDisksPerNode.toString(), + }; +}; diff --git a/src/store/reducers/storage/types.ts b/src/store/reducers/storage/types.ts index 829405f28..b2c750fd9 100644 --- a/src/store/reducers/storage/types.ts +++ b/src/store/reducers/storage/types.ts @@ -36,6 +36,7 @@ export interface PreparedStorageNode extends PreparedNodeSystemState { Missing: number; MaximumSlotsPerDisk: string; + MaximumDisksPerNode: string; } export interface PreparedStorageGroupFilters { diff --git a/src/store/reducers/storage/utils.ts b/src/store/reducers/storage/utils.ts index e759800b3..27be314f7 100644 --- a/src/store/reducers/storage/utils.ts +++ b/src/store/reducers/storage/utils.ts @@ -191,6 +191,7 @@ const prepareStorageGroups = ( const prepareStorageNodeData = ( node: TNodeInfo, maximumSlotsPerDisk: string, + maximumDisksPerNode: string, ): PreparedStorageNode => { const missing = node.PDisks?.filter((pDisk) => { @@ -218,6 +219,7 @@ const prepareStorageNodeData = ( VDisks: vDisks, Missing: missing, MaximumSlotsPerDisk: maximumSlotsPerDisk, + MaximumDisksPerNode: maximumDisksPerNode, }; }; @@ -243,10 +245,22 @@ export const calculateMaximumSlotsPerDisk = ( ); }; +export const calculateMaximumDisksPerNode = ( + nodes: TNodeInfo[] | undefined, + providedMaximumDisksPerNode?: string, +) => { + if (providedMaximumDisksPerNode) { + return providedMaximumDisksPerNode; + } + + return String(Math.max(1, ...(nodes || []).map((node) => node.PDisks?.length || 0))); +}; + // ==== Prepare responses ==== export const prepareStorageNodesResponse = (data: TNodesInfo): PreparedStorageResponse => { - const {Nodes, TotalNodes, FoundNodes, NodeGroups, MaximumSlotsPerDisk} = data; + const {Nodes, TotalNodes, FoundNodes, NodeGroups, MaximumSlotsPerDisk, MaximumDisksPerNode} = + data; const tableGroups = NodeGroups?.map(({GroupName, NodeCount}) => { if (GroupName && NodeCount) { @@ -259,7 +273,10 @@ export const prepareStorageNodesResponse = (data: TNodesInfo): PreparedStorageRe }).filter((group): group is TableGroup => Boolean(group)); const maximumSlots = calculateMaximumSlotsPerDisk(Nodes, MaximumSlotsPerDisk); - const preparedNodes = Nodes?.map((node) => prepareStorageNodeData(node, maximumSlots)); + const maximumDisks = calculateMaximumDisksPerNode(Nodes, MaximumDisksPerNode); + const preparedNodes = Nodes?.map((node) => + prepareStorageNodeData(node, maximumSlots, maximumDisks), + ); return { nodes: preparedNodes, diff --git a/src/styles/mixins.scss b/src/styles/mixins.scss index 2b9014038..d689bea5f 100644 --- a/src/styles/mixins.scss +++ b/src/styles/mixins.scss @@ -395,3 +395,23 @@ background-color: unset; } } + +@mixin calculate-storage-pdisk-variables() { + --pdisk-vdisk-width: 3px; + --pdisk-gap-width: 2px; + --pdisk-min-width: 120px; + --pdisk-margin: 10px; + + --pdisk-width: max( + calc( + var(--maximum-slots, 1) * var(--pdisk-vdisk-width) + (var(--maximum-slots, 1) - 1) * + var(--pdisk-gap-width) + ), + var(--pdisk-min-width) + ); + + --pdisks-container-width: calc( + var(--maximum-disks, 1) * var(--pdisk-width) + (var(--maximum-disks, 1) - 1) * + var(--pdisk-margin) + ); +} diff --git a/src/types/api/nodes.ts b/src/types/api/nodes.ts index 716d974ab..8c01c5bdc 100644 --- a/src/types/api/nodes.ts +++ b/src/types/api/nodes.ts @@ -20,6 +20,8 @@ export interface TNodesInfo { FoundNodes: string; /** uint64 */ MaximumSlotsPerDisk?: string; + /** uint64 */ + MaximumDisksPerNode?: string; } export interface TNodeInfo { From 37e3543e75ab8403343137ae53b435c32a56ac4f Mon Sep 17 00:00:00 2001 From: astandrik Date: Thu, 26 Dec 2024 19:27:22 +0300 Subject: [PATCH 2/6] fix: width --- src/containers/Storage/Disks/Disks.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/containers/Storage/Disks/Disks.scss b/src/containers/Storage/Disks/Disks.scss index 75c8f9d99..f8efc362e 100644 --- a/src/containers/Storage/Disks/Disks.scss +++ b/src/containers/Storage/Disks/Disks.scss @@ -26,6 +26,7 @@ } &__pdisk-item { + width: 80px; min-width: 80px; } &__pdisk-progress-bar { From 4c9d0cfe226e6773bc6b57aed661e6e8120ec079 Mon Sep 17 00:00:00 2001 From: astandrik Date: Thu, 26 Dec 2024 19:56:33 +0300 Subject: [PATCH 3/6] fix: better code --- src/containers/Storage/Disks/Disks.scss | 1 - src/containers/Storage/PDisk/PDisk.scss | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/containers/Storage/Disks/Disks.scss b/src/containers/Storage/Disks/Disks.scss index f8efc362e..75c8f9d99 100644 --- a/src/containers/Storage/Disks/Disks.scss +++ b/src/containers/Storage/Disks/Disks.scss @@ -26,7 +26,6 @@ } &__pdisk-item { - width: 80px; min-width: 80px; } &__pdisk-progress-bar { diff --git a/src/containers/Storage/PDisk/PDisk.scss b/src/containers/Storage/PDisk/PDisk.scss index 12b5b6e9a..d615a278f 100644 --- a/src/containers/Storage/PDisk/PDisk.scss +++ b/src/containers/Storage/PDisk/PDisk.scss @@ -1,5 +1,3 @@ -@import '../../../styles/mixins.scss'; - .pdisk-storage { position: relative; @@ -10,7 +8,6 @@ width: var(--pdisk-width); min-width: var(--pdisk-min-width); - @include calculate-storage-pdisk-variables(); &__content { position: relative; From 78cf654dae04f4414b25d9b177b897492f95fdef Mon Sep 17 00:00:00 2001 From: astandrik Date: Thu, 26 Dec 2024 20:04:19 +0300 Subject: [PATCH 4/6] fix: better code --- .../Storage/StorageNodes/columns/StorageNodesColumns.scss | 2 +- src/styles/mixins.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/containers/Storage/StorageNodes/columns/StorageNodesColumns.scss b/src/containers/Storage/StorageNodes/columns/StorageNodesColumns.scss index e782dbaec..1ff35f2e2 100644 --- a/src/containers/Storage/StorageNodes/columns/StorageNodesColumns.scss +++ b/src/containers/Storage/StorageNodes/columns/StorageNodesColumns.scss @@ -12,7 +12,7 @@ width: var(--pdisks-container-width); height: 40px; - @include calculate-storage-pdisk-variables(); + @include calculate-storage-nodes-pdisk-variables(); } &__pdisks-item { diff --git a/src/styles/mixins.scss b/src/styles/mixins.scss index d689bea5f..b7883909c 100644 --- a/src/styles/mixins.scss +++ b/src/styles/mixins.scss @@ -396,7 +396,7 @@ } } -@mixin calculate-storage-pdisk-variables() { +@mixin calculate-storage-nodes-pdisk-variables() { --pdisk-vdisk-width: 3px; --pdisk-gap-width: 2px; --pdisk-min-width: 120px; From e8f359befdbbf205c1a66ec789900e1c772a66fb Mon Sep 17 00:00:00 2001 From: astandrik Date: Fri, 27 Dec 2024 11:30:15 +0300 Subject: [PATCH 5/6] fix: some refactoring --- .../calculateMaximumDisksPerNode.test.ts | 137 ++++++++++++++++++ src/store/reducers/storage/utils.ts | 41 ++++-- 2 files changed, 163 insertions(+), 15 deletions(-) create mode 100644 src/store/reducers/storage/__tests__/calculateMaximumDisksPerNode.test.ts diff --git a/src/store/reducers/storage/__tests__/calculateMaximumDisksPerNode.test.ts b/src/store/reducers/storage/__tests__/calculateMaximumDisksPerNode.test.ts new file mode 100644 index 000000000..30774f66b --- /dev/null +++ b/src/store/reducers/storage/__tests__/calculateMaximumDisksPerNode.test.ts @@ -0,0 +1,137 @@ +import type {TNodeInfo} from '../../../../types/api/nodes'; +import {TPDiskState} from '../../../../types/api/pdisk'; +import {calculateMaximumDisksPerNode} from '../utils'; + +describe('calculateMaximumDisksPerNode', () => { + it('should return providedMaximumDisksPerNode when it is provided', () => { + const nodes: TNodeInfo[] = []; + const providedMaximumDisksPerNode = '5'; + + expect(calculateMaximumDisksPerNode(nodes, providedMaximumDisksPerNode)).toBe('5'); + }); + + it('should return "1" for empty nodes array', () => { + const nodes: TNodeInfo[] = []; + + expect(calculateMaximumDisksPerNode(nodes)).toBe('1'); + }); + + it('should return "1" for undefined nodes', () => { + expect(calculateMaximumDisksPerNode(undefined)).toBe('1'); + }); + + it('should return "1" for nodes without PDisks', () => { + const nodes: TNodeInfo[] = [ + { + NodeId: 1, + SystemState: {}, + }, + ]; + + expect(calculateMaximumDisksPerNode(nodes)).toBe('1'); + }); + + it('should calculate maximum disks correctly for single node with multiple PDisks', () => { + const nodes: TNodeInfo[] = [ + { + NodeId: 1, + SystemState: {}, + PDisks: [ + { + PDiskId: 1, + State: TPDiskState.Normal, + }, + { + PDiskId: 2, + State: TPDiskState.Normal, + }, + { + PDiskId: 3, + State: TPDiskState.Normal, + }, + ], + }, + ]; + + expect(calculateMaximumDisksPerNode(nodes)).toBe('3'); + }); + + it('should calculate maximum disks across multiple nodes', () => { + const nodes: TNodeInfo[] = [ + { + NodeId: 1, + SystemState: {}, + PDisks: [ + { + PDiskId: 1, + State: TPDiskState.Normal, + }, + ], + }, + { + NodeId: 2, + SystemState: {}, + PDisks: [ + { + PDiskId: 2, + State: TPDiskState.Normal, + }, + { + PDiskId: 3, + State: TPDiskState.Normal, + }, + ], + }, + { + NodeId: 3, + SystemState: {}, + PDisks: [ + { + PDiskId: 4, + State: TPDiskState.Normal, + }, + { + PDiskId: 5, + State: TPDiskState.Normal, + }, + { + PDiskId: 6, + State: TPDiskState.Normal, + }, + { + PDiskId: 7, + State: TPDiskState.Normal, + }, + ], + }, + ]; + + expect(calculateMaximumDisksPerNode(nodes)).toBe('4'); + }); + + it('should handle nodes with empty PDisks array', () => { + const nodes: TNodeInfo[] = [ + { + NodeId: 1, + SystemState: {}, + PDisks: [], + }, + { + NodeId: 2, + SystemState: {}, + PDisks: [ + { + PDiskId: 1, + State: TPDiskState.Normal, + }, + { + PDiskId: 2, + State: TPDiskState.Normal, + }, + ], + }, + ]; + + expect(calculateMaximumDisksPerNode(nodes)).toBe('2'); + }); +}); diff --git a/src/store/reducers/storage/utils.ts b/src/store/reducers/storage/utils.ts index 27be314f7..09f365a76 100644 --- a/src/store/reducers/storage/utils.ts +++ b/src/store/reducers/storage/utils.ts @@ -223,37 +223,48 @@ const prepareStorageNodeData = ( }; }; +/** + * Calculates the maximum number of VDisk slots per PDisk across all nodes + * A slot represents a VDisk that can be allocated to a PDisk + */ export const calculateMaximumSlotsPerDisk = ( nodes: TNodeInfo[] | undefined, providedMaximumSlotsPerDisk?: string, -) => { +): string => { if (providedMaximumSlotsPerDisk) { return providedMaximumSlotsPerDisk; } - return String( - Math.max( - 1, - ...(nodes || []).flatMap((node) => - (node.PDisks || []).map( - (pDisk) => - (node.VDisks || []).filter((vDisk) => vDisk.PDiskId === pDisk.PDiskId) - .length || 0, - ), - ), - ), - ); + const safeNodes = nodes || []; + const slotsPerDiskCounts = safeNodes.flatMap((node) => { + const safePDisks = node.PDisks || []; + const safeVDisks = node.VDisks || []; + + return safePDisks.map((pDisk) => { + const vDisksOnPDisk = safeVDisks.filter((vDisk) => vDisk.PDiskId === pDisk.PDiskId); + return vDisksOnPDisk.length || 0; + }); + }); + + const maxSlots = Math.max(1, ...slotsPerDiskCounts); + return String(maxSlots); }; +/** + * Calculates the maximum number of PDisks per node across all nodes + */ export const calculateMaximumDisksPerNode = ( nodes: TNodeInfo[] | undefined, providedMaximumDisksPerNode?: string, -) => { +): string => { if (providedMaximumDisksPerNode) { return providedMaximumDisksPerNode; } - return String(Math.max(1, ...(nodes || []).map((node) => node.PDisks?.length || 0))); + const safeNodes = nodes || []; + const disksPerNodeCounts = safeNodes.map((node) => node.PDisks?.length || 0); + const maxDisks = Math.max(1, ...disksPerNodeCounts); + return String(maxDisks); }; // ==== Prepare responses ==== From d6897e699ca57bd833c216955118d1b2bfa38fc3 Mon Sep 17 00:00:00 2001 From: astandrik Date: Fri, 27 Dec 2024 11:55:12 +0300 Subject: [PATCH 6/6] fix: remove mocked data --- .../Storage/StorageNodes/getNodes.ts | 50 ++-- src/mocks/storage/nodes.ts | 224 ------------------ 2 files changed, 18 insertions(+), 256 deletions(-) delete mode 100644 src/mocks/storage/nodes.ts diff --git a/src/containers/Storage/StorageNodes/getNodes.ts b/src/containers/Storage/StorageNodes/getNodes.ts index fcb62b456..be9a0714e 100644 --- a/src/containers/Storage/StorageNodes/getNodes.ts +++ b/src/containers/Storage/StorageNodes/getNodes.ts @@ -3,7 +3,6 @@ import { NODES_COLUMNS_TO_DATA_FIELDS, getNodesColumnSortField, } from '../../../components/nodesColumns/constants'; -import {generateNodes} from '../../../mocks/storage/nodes'; import type { PreparedStorageNode, PreparedStorageNodeFilters, @@ -13,6 +12,7 @@ import type {NodesRequestParams} from '../../../types/api/nodes'; import {prepareSortValue} from '../../../utils/filters'; import {getUptimeParamValue} from '../../../utils/nodes'; import {getRequiredDataFields} from '../../../utils/tableUtils/getRequiredDataFields'; + export const getStorageNodes: FetchData< PreparedStorageNode, PreparedStorageNodeFilters, @@ -38,42 +38,28 @@ export const getStorageNodes: FetchData< filterGroupBy, } = filters ?? {}; const {sortOrder, columnId} = sortParams ?? {}; + const sortField = getNodesColumnSortField(columnId); const sort = sortField ? prepareSortValue(sortField, sortOrder) : undefined; const dataFieldsRequired = getRequiredDataFields(columnsIds, NODES_COLUMNS_TO_DATA_FIELDS); - let response; - const urlParams = new URLSearchParams(window.location.search); - if (urlParams.get('mocks')) { - // Get mock configuration from URL parameters or use defaults - const pdisks = parseInt(urlParams.get('pdisks') || '10', 10); - const vdisksPerPDisk = parseInt(urlParams.get('vdisksPerPDisk') || '2', 10); - const totalNodes = parseInt(urlParams.get('totalNodes') || '50', 10); - response = generateNodes(totalNodes, { - maxVdisksPerPDisk: vdisksPerPDisk, - maxPdisks: pdisks, - offset, - limit, - }); - } else { - response = await window.api.viewer.getNodes({ - type, - storage, - limit, - offset, - sort, - filter: searchValue, - uptime: getUptimeParamValue(nodesUptimeFilter), - with: visibleEntities, - database, - node_id: nodeId, - group_id: groupId, - filter_group: filterGroup, - filter_group_by: filterGroupBy, - fieldsRequired: dataFieldsRequired, - }); - } + const response = await window.api.viewer.getNodes({ + type, + storage, + limit, + offset, + sort, + filter: searchValue, + uptime: getUptimeParamValue(nodesUptimeFilter), + with: visibleEntities, + database, + node_id: nodeId, + group_id: groupId, + filter_group: filterGroup, + filter_group_by: filterGroupBy, + fieldsRequired: dataFieldsRequired, + }); const preparedResponse = prepareStorageNodesResponse(response); return { data: preparedResponse.nodes || [], diff --git a/src/mocks/storage/nodes.ts b/src/mocks/storage/nodes.ts deleted file mode 100644 index 751df1dbe..000000000 --- a/src/mocks/storage/nodes.ts +++ /dev/null @@ -1,224 +0,0 @@ -import {EFlag} from '../../types/api/enums'; -import type { - TEndpoint, - TNodeInfo, - TNodesInfo, - TPoolStats, - TSystemStateInfo, -} from '../../types/api/nodes'; -import {TPDiskState} from '../../types/api/pdisk'; -import {EVDiskState} from '../../types/api/vdisk'; - -// Different disk sizes to simulate variety (in bytes) -const DISK_SIZES = [ - '68719476736', // 64 GB - '137438953472', // 128 GB - '274877906944', // 256 GB - '549755813888', // 512 GB - '1099511627776', // 1 TB -]; - -const getRandomDiskSize = () => DISK_SIZES[Math.floor(Math.random() * DISK_SIZES.length)]; - -const generatePoolStats = (count = 5): TPoolStats[] => { - const poolNames = ['System', 'User', 'Batch', 'IO', 'IC'] as const; - return poolNames.slice(0, count).map((Name) => ({ - Name, - Usage: Math.random() * 0.02, - Threads: Math.floor(Math.random() * 3) + 1, - })); -}; - -const generateEndpoints = (): TEndpoint[] => [ - {Name: 'ic', Address: ':19001'}, - {Name: 'http-mon', Address: ':8765'}, - {Name: 'grpcs', Address: ':2135'}, - {Name: 'grpc', Address: ':2136'}, -]; - -const generateSystemState = (nodeId: number): TSystemStateInfo => ({ - StartTime: '1734358137851', - ChangeTime: '1734358421375', - LoadAverage: [3.381347656, 2.489257813, 1.279296875], - NumberOfCpus: 8, - SystemState: EFlag.Green, - NodeId: nodeId, - Host: `localhost-${nodeId}`, - Version: 'main.95ce0df', - PoolStats: generatePoolStats(), - Endpoints: generateEndpoints(), - Roles: ['Bootstrapper', 'StateStorage', 'StateStorageBoard', 'SchemeBoard', 'Storage'], - MemoryLimit: '2147483648', - MaxDiskUsage: 0.002349853516, - Location: { - DataCenter: '1', - Rack: '1', - Unit: '1', - }, - TotalSessions: 0, - CoresUsed: 0.07583969556, - CoresTotal: 8, -}); - -const generatePDisk = (nodeId: number, pdiskId: number, totalSize = '68719476736') => ({ - PDiskId: pdiskId, - ChangeTime: '1734358142074', - Path: `/ydb_data/pdisk${pdiskId}l3ki78no.data`, - Guid: pdiskId.toString(), - Category: '0', - TotalSize: totalSize, - AvailableSize: (Number(totalSize) * 0.9).toString(), // 90% available by default - State: TPDiskState.Normal, - NodeId: nodeId, - Device: EFlag.Green, - Realtime: EFlag.Green, - SerialNumber: '', - SystemSize: '213909504', - LogUsedSize: '35651584', - LogTotalSize: '68486692864', - EnforcedDynamicSlotSize: '22817013760', -}); - -const generateVDisk = (nodeId: number, vdiskId: number, pdiskId: number) => ({ - VDiskId: { - GroupID: vdiskId, - GroupGeneration: 1, - Ring: 0, - Domain: 0, - VDisk: 0, - }, - ChangeTime: '1734358420919', - PDiskId: pdiskId, - VDiskSlotId: vdiskId, - Guid: '1', - Kind: '0', - NodeId: nodeId, - VDiskState: EVDiskState.OK, - DiskSpace: EFlag.Green, - SatisfactionRank: { - FreshRank: { - Flag: EFlag.Green, - }, - LevelRank: { - Flag: EFlag.Green, - }, - }, - Replicated: true, - ReplicationProgress: 1, - ReplicationSecondsRemaining: 0, - AllocatedSize: '0', - AvailableSize: '22817013760', - HasUnreadableBlobs: false, - IncarnationGuid: '11528832187803248876', - InstanceGuid: '14836434871903384493', - FrontQueues: EFlag.Green, - StoragePoolName: 'static', - ReadThroughput: '0', - WriteThroughput: '420', -}); - -interface NodeGeneratorOptions { - maxVdisksPerPDisk?: number; - maxPdisks?: number; -} - -const DEFAULT_OPTIONS: NodeGeneratorOptions = { - maxVdisksPerPDisk: 3, - maxPdisks: 4, -}; - -const generateNode = (nodeId: number, options: NodeGeneratorOptions = {}): TNodeInfo => { - const maxPdisks = options.maxPdisks ?? DEFAULT_OPTIONS.maxPdisks!; - const maxVdisksPerPDisk = options.maxVdisksPerPDisk ?? DEFAULT_OPTIONS.maxVdisksPerPDisk!; - - // Generate a random number of pdisks up to maxPdisks - const pdisksCount = Math.floor(Math.random() * maxPdisks) + 1; - - // For each pdisk, generate a random number of vdisks up to maxVdisksPerPDisk - const pdiskVdisksCounts = Array.from({length: pdisksCount}, () => - Math.floor(Math.random() * maxVdisksPerPDisk), - ); - const totalVdisks = pdiskVdisksCounts.reduce((sum: number, count: number) => sum + count, 0); - - return { - NodeId: nodeId, - UptimeSeconds: 284, - CpuUsage: 0.00947996, - DiskSpaceUsage: 0.234985, - SystemState: generateSystemState(nodeId), - PDisks: Array.from({length: pdisksCount}, (_, i) => - generatePDisk(nodeId, i + 1, getRandomDiskSize()), - ), - VDisks: Array.from({length: totalVdisks}, (_, i) => { - // Find which pdisk this vdisk belongs to based on the distribution - let pdiskIndex = 0; - let vdiskCount = pdiskVdisksCounts[0]; - while (i >= vdiskCount && pdiskIndex < pdisksCount - 1) { - pdiskIndex++; - vdiskCount += pdiskVdisksCounts[pdiskIndex]; - } - return generateVDisk(nodeId, i, pdiskIndex + 1); - }), - }; -}; - -interface GenerateNodesOptions extends NodeGeneratorOptions { - offset?: number; - limit?: number; -} - -// Keep a cache of generated nodes to maintain consistency between paginated requests -let cachedNodes: TNodeInfo[] | null = null; -let currentTotalNodes = 50; // Default number of nodes - -export const generateNodes = (count?: number, options: GenerateNodesOptions = {}): TNodesInfo => { - const totalNodes = count ?? currentTotalNodes; - const {offset = 0, limit = totalNodes, maxVdisksPerPDisk, maxPdisks} = options; - - // Reset cache if total nodes count changes - if (totalNodes !== currentTotalNodes) { - cachedNodes = null; - currentTotalNodes = totalNodes; - } - - // Generate or use cached nodes - if (!cachedNodes) { - cachedNodes = Array.from({length: totalNodes}, (_, i) => - generateNode(i + 1, {maxVdisksPerPDisk, maxPdisks}), - ); - } - - // Calculate MaximumSlotsPerDisk and MaximumDisksPerNode across all nodes - let maxSlotsPerDisk = 0; - let maxDisksPerNode = 0; - - cachedNodes.forEach((node) => { - // Count pdisks per node - if (node.PDisks) { - maxDisksPerNode = Math.max(maxDisksPerNode, node.PDisks.length); - } - - // Count vdisks per pdisk - if (node.VDisks) { - const pdiskVdiskCounts = new Map(); - node.VDisks.forEach((vdisk) => { - if (typeof vdisk.PDiskId === 'number') { - const count = (pdiskVdiskCounts.get(vdisk.PDiskId) || 0) + 1; - pdiskVdiskCounts.set(vdisk.PDiskId, count); - maxSlotsPerDisk = Math.max(maxSlotsPerDisk, count); - } - }); - } - }); - - // Get the requested slice of nodes - const paginatedNodes = cachedNodes.slice(offset, offset + limit); - - return { - TotalNodes: totalNodes.toString(), - FoundNodes: totalNodes.toString(), - Nodes: paginatedNodes, - MaximumSlotsPerDisk: maxSlotsPerDisk.toString(), - MaximumDisksPerNode: maxDisksPerNode.toString(), - }; -};