Skip to content

Commit 364f6e1

Browse files
committed
ARTESCA-14896: Add attachHardwareVolume in Metalk8sLocalVolumeProvider
1 parent 41caeb5 commit 364f6e1

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

ui/src/services/k8s/Metalk8sLocalVolumeProvider.test.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,4 +221,94 @@ describe('Metalk8sLocalVolumeProvider', () => {
221221
);
222222
});
223223
});
224+
225+
describe('attachHardwareVolumes', () => {
226+
it('should attach hardware volumes', async () => {
227+
//S
228+
(mockCoreV1Api.listNode as jest.Mock).mockResolvedValue({
229+
body: {
230+
apiVersion: 'v1',
231+
kind: 'NodeList',
232+
items: [
233+
{
234+
metadata: {
235+
name: 'test-node',
236+
},
237+
status: {
238+
addresses: [{ type: 'InternalIP', address: '192.168.1.100' }],
239+
},
240+
},
241+
],
242+
},
243+
});
244+
(
245+
mockVolumeClient.createMetalk8sV1alpha1Volume as jest.Mock
246+
).mockResolvedValue({
247+
metadata: {
248+
name: 'storage-data-192.168.1.100-/dev/sda',
249+
},
250+
});
251+
//E
252+
const result = await provider.attachHardwareVolumes([
253+
{
254+
IP: '192.168.1.100',
255+
devicePath: '/dev/sda',
256+
hardwareDiskType: 'NVMe',
257+
},
258+
]);
259+
//V
260+
expect(
261+
mockVolumeClient.createMetalk8sV1alpha1Volume,
262+
).toHaveBeenCalledWith({
263+
apiVersion: 'storage.metalk8s.scality.com/v1alpha1',
264+
kind: 'Volume',
265+
metadata: {
266+
name: 'storage-data-192.168.1.100-/dev/sda',
267+
labels: {
268+
'xcore.scality.com/volume-type': 'data',
269+
},
270+
},
271+
spec: {
272+
nodeName: 'test-node',
273+
rawBlockDevice: { devicePath: '/dev/sda' },
274+
storageClassName: 'ssd-ext4',
275+
},
276+
});
277+
expect(result).toEqual(['storage-data-192.168.1.100-/dev/sda']);
278+
});
279+
280+
it('should raise an error if volume creation fails', async () => {
281+
//S
282+
(
283+
mockVolumeClient.createMetalk8sV1alpha1Volume as jest.Mock
284+
).mockRejectedValue(new Error('Failed to create volume'));
285+
//E+V
286+
await expect(
287+
provider.attachHardwareVolumes([
288+
{
289+
IP: '192.168.1.100',
290+
devicePath: '/dev/sda',
291+
hardwareDiskType: 'NVMe',
292+
},
293+
]),
294+
).rejects.toThrow('Failed to create volume');
295+
});
296+
297+
it('should raise an error if node retrieval fails', async () => {
298+
//S
299+
(mockCoreV1Api.listNode as jest.Mock).mockRejectedValue(
300+
new Error('Failed to fetch nodes'),
301+
);
302+
//E+V
303+
await expect(
304+
provider.attachHardwareVolumes([
305+
{
306+
IP: '192.168.1.100',
307+
devicePath: '/dev/sda',
308+
hardwareDiskType: 'NVMe',
309+
},
310+
]),
311+
).rejects.toThrow('Failed to fetch nodes');
312+
});
313+
});
224314
});

ui/src/services/k8s/Metalk8sLocalVolumeProvider.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ export type LocalPersistentVolume = V1PersistentVolume & {
2121
volumeType: VolumeType;
2222
};
2323

24+
export type HardwareVolume = {
25+
IP: string;
26+
devicePath: string;
27+
hardwareDiskType: string;
28+
};
29+
2430
export default class Metalk8sLocalVolumeProvider {
2531
volumeClient: Metalk8sV1alpha1VolumeClient;
2632
k8sClient: CoreV1Api;
@@ -100,4 +106,55 @@ export default class Metalk8sLocalVolumeProvider {
100106
}
101107
}
102108
};
109+
110+
public attachHardwareVolumes = async (
111+
hardwareVolumes: HardwareVolume[],
112+
): Promise<string[]> => {
113+
const nodes = await this.k8sClient.listNode();
114+
if (isError(nodes)) {
115+
throw new Error(`Failed to fetch nodes: ${nodes.error}`);
116+
}
117+
118+
const volumeNames: string[] = [];
119+
120+
for (const hardwareVolume of hardwareVolumes) {
121+
const { IP, devicePath, hardwareDiskType } = hardwareVolume;
122+
123+
const nodeName = nodes.body.items.find((node) =>
124+
node.status.addresses.find(
125+
(address) => address.type === 'InternalIP' && address.address === IP,
126+
),
127+
)?.metadata.name;
128+
if (!nodeName) {
129+
throw new Error(`Failed to find node for IP ${IP}`);
130+
}
131+
// The map between hardwareDisk Type and StorageClassName
132+
// NVMe => SSD
133+
// the rest=> HDD
134+
const storageClassName =
135+
hardwareDiskType === 'NVMe' ? 'ssd-ext4' : 'hdd-ext4';
136+
137+
const volume = await this.volumeClient.createMetalk8sV1alpha1Volume({
138+
apiVersion: 'storage.metalk8s.scality.com/v1alpha1',
139+
kind: 'Volume',
140+
metadata: {
141+
// It will be changed to Disk Serial Number in the future.
142+
name: `storage-data-${IP}-${devicePath}`,
143+
labels: {
144+
'xcore.scality.com/volume-type': 'data',
145+
},
146+
},
147+
spec: {
148+
nodeName,
149+
rawBlockDevice: { devicePath },
150+
storageClassName,
151+
},
152+
});
153+
if (isError(volume)) {
154+
throw new Error(`Failed to create MetalK8s volume: ${volume.error}`);
155+
}
156+
volumeNames.push(volume.metadata['name']);
157+
}
158+
return volumeNames;
159+
};
103160
}

0 commit comments

Comments
 (0)