Skip to content

Commit b90201b

Browse files
committed
Merge branch 'feature/localvolumestatus' into q/130.0
2 parents f4aefba + 0446b52 commit b90201b

File tree

2 files changed

+170
-1
lines changed

2 files changed

+170
-1
lines changed

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

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@ import Metalk8sLocalVolumeProvider, {
44
HardwareDiskType,
55
VolumeType,
66
} from './Metalk8sLocalVolumeProvider';
7+
import * as NodeVolumesUtils from '../NodeVolumesUtils';
78

89
jest.mock('../k8s/api', () => ({
910
updateApiServerConfig: jest.fn(),
1011
}));
1112

13+
jest.mock('../NodeVolumesUtils', () => ({
14+
computeVolumeGlobalStatus: jest.fn(),
15+
}));
16+
1217
const MOCK_GROUP = 'storage.metalk8s.scality.com';
1318
const MOCK_VERSION = 'v1alpha1';
1419
const MOCK_PLURAL = 'volumes';
@@ -33,6 +38,8 @@ describe('Metalk8sLocalVolumeProvider', () => {
3338
} as unknown as CoreV1Api;
3439

3540
beforeEach(() => {
41+
jest.clearAllMocks();
42+
3643
(updateApiServerConfig as jest.Mock).mockReturnValue({
3744
coreV1: mockCoreV1Api,
3845
customObjects: mockCustomObjectsApi,
@@ -111,6 +118,11 @@ describe('Metalk8sLocalVolumeProvider', () => {
111118
},
112119
});
113120

121+
(NodeVolumesUtils.computeVolumeGlobalStatus as jest.Mock)
122+
.mockReturnValueOnce('Ready')
123+
.mockReturnValueOnce('Ready')
124+
.mockReturnValueOnce('Ready');
125+
114126
const volumes = await provider.listLocalPersistentVolumes('test-node');
115127

116128
expect(volumes).toHaveLength(3);
@@ -134,6 +146,119 @@ describe('Metalk8sLocalVolumeProvider', () => {
134146
});
135147
});
136148

149+
it('should compute and include volume status for each volume', async () => {
150+
(mockCoreV1Api.listNode as jest.Mock).mockResolvedValue({
151+
body: {
152+
items: [
153+
{
154+
metadata: { name: 'test-node' },
155+
status: {
156+
addresses: [
157+
{ type: 'Hostname', address: 'test-node' },
158+
{ type: 'InternalIP', address: '192.168.1.100' },
159+
],
160+
},
161+
},
162+
],
163+
},
164+
});
165+
166+
(mockCoreV1Api.listPersistentVolume as jest.Mock).mockResolvedValue({
167+
body: {
168+
items: [
169+
{
170+
metadata: { name: 'test-volume-1' },
171+
spec: {},
172+
},
173+
{
174+
metadata: { name: 'test-volume-2' },
175+
spec: {},
176+
},
177+
],
178+
},
179+
});
180+
181+
const mockVolumeStatus1 = {
182+
deviceName: 'sda',
183+
conditions: [
184+
{
185+
type: 'Ready',
186+
status: 'True',
187+
},
188+
],
189+
};
190+
191+
const mockVolumeStatus2 = {
192+
deviceName: 'sdb',
193+
conditions: [
194+
{
195+
type: 'Ready',
196+
status: 'False',
197+
reason: 'Failed',
198+
},
199+
],
200+
};
201+
202+
(
203+
mockCustomObjectsApi.listClusterCustomObject as jest.Mock
204+
).mockResolvedValue({
205+
body: {
206+
items: [
207+
{
208+
metadata: { name: 'test-volume-1' },
209+
spec: {
210+
nodeName: 'test-node',
211+
rawBlockDevice: { devicePath: '/dev/sda' },
212+
},
213+
status: mockVolumeStatus1,
214+
},
215+
{
216+
metadata: { name: 'test-volume-2' },
217+
spec: {
218+
nodeName: 'test-node',
219+
rawBlockDevice: { devicePath: '/dev/sdb' },
220+
},
221+
status: mockVolumeStatus2,
222+
},
223+
],
224+
},
225+
});
226+
227+
(NodeVolumesUtils.computeVolumeGlobalStatus as jest.Mock)
228+
.mockReturnValueOnce('Ready')
229+
.mockReturnValueOnce('Failed');
230+
231+
const volumes = await provider.listLocalPersistentVolumes('test-node');
232+
233+
expect(NodeVolumesUtils.computeVolumeGlobalStatus).toHaveBeenCalledTimes(
234+
2,
235+
);
236+
expect(NodeVolumesUtils.computeVolumeGlobalStatus).toHaveBeenCalledWith(
237+
'test-volume-1',
238+
mockVolumeStatus1,
239+
);
240+
expect(NodeVolumesUtils.computeVolumeGlobalStatus).toHaveBeenCalledWith(
241+
'test-volume-2',
242+
mockVolumeStatus2,
243+
);
244+
245+
expect(volumes).toHaveLength(2);
246+
expect(volumes[0]).toMatchObject({
247+
IP: '192.168.1.100',
248+
devicePath: '/dev/sda',
249+
nodeName: 'test-node',
250+
volumeType: VolumeType.Hardware,
251+
volumeStatus: 'Ready',
252+
});
253+
expect(volumes[1]).toMatchObject({
254+
IP: '192.168.1.100',
255+
devicePath: '/dev/sdb',
256+
nodeName: 'test-node',
257+
volumeType: VolumeType.Hardware,
258+
volumeStatus: 'Failed',
259+
});
260+
});
261+
137262
it('should raise an error if the node cannot be found', async () => {
138263
(mockCoreV1Api.listNode as jest.Mock).mockResolvedValue({
139264
body: { items: [] },
@@ -250,7 +375,40 @@ describe('Metalk8sLocalVolumeProvider', () => {
250375
body: { status: { conditions: [{ type: 'Ready', status: 'True' }] } },
251376
});
252377
(mockCoreV1Api.readPersistentVolume as jest.Mock).mockResolvedValue({
253-
status: { phase: 'Bound' },
378+
body: {
379+
metadata: { name: 'test-volume' },
380+
status: { phase: 'Bound' },
381+
},
382+
});
383+
//E
384+
const result = await provider.isVolumeProvisioned({
385+
IP: '192.168.1.100',
386+
devicePath: '/dev/sda',
387+
volumeType: VolumeType.Hardware,
388+
nodeName: 'test-node',
389+
volumeName: 'test-volume',
390+
});
391+
//V
392+
expect(result).toMatchObject({
393+
IP: '192.168.1.100',
394+
devicePath: '/dev/sda',
395+
volumeType: VolumeType.Hardware,
396+
nodeName: 'test-node',
397+
});
398+
});
399+
400+
it('should return volume with status if the volume is provisioned and has volumeStatus', async () => {
401+
//S
402+
(
403+
mockCustomObjectsApi.getClusterCustomObject as jest.Mock
404+
).mockResolvedValue({
405+
body: { status: { conditions: [{ type: 'Ready', status: 'True' }] } },
406+
});
407+
(mockCoreV1Api.readPersistentVolume as jest.Mock).mockResolvedValue({
408+
body: {
409+
metadata: { name: 'test-volume' },
410+
status: { phase: 'Bound' },
411+
},
254412
});
255413
//E
256414
const result = await provider.isVolumeProvisioned({
@@ -259,13 +417,15 @@ describe('Metalk8sLocalVolumeProvider', () => {
259417
volumeType: VolumeType.Hardware,
260418
nodeName: 'test-node',
261419
volumeName: 'test-volume',
420+
volumeStatus: 'Ready',
262421
});
263422
//V
264423
expect(result).toMatchObject({
265424
IP: '192.168.1.100',
266425
devicePath: '/dev/sda',
267426
volumeType: VolumeType.Hardware,
268427
nodeName: 'test-node',
428+
volumeStatus: 'Ready',
269429
});
270430
});
271431

ui/src/services/k8s/Metalk8sLocalVolumeProvider.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Metalk8sV1alpha1VolumeClient,
55
Result,
66
} from './Metalk8sVolumeClient.generated';
7+
import { computeVolumeGlobalStatus, VolumeStatus } from '../NodeVolumesUtils';
78

89
function isError<T>(result: Result<T>): result is { error: any } {
910
return (result as { error: any }).error !== undefined;
@@ -30,6 +31,7 @@ type LocalVolumeInfo = {
3031
devicePath: string;
3132
nodeName: string;
3233
volumeType: VolumeType;
34+
volumeStatus?: VolumeStatus;
3335
};
3436

3537
export type LocalPersistentVolume = V1PersistentVolume & LocalVolumeInfo;
@@ -76,6 +78,11 @@ export default class Metalk8sLocalVolumeProvider {
7678
(p) => p.metadata.name === item.metadata['name'],
7779
);
7880

81+
const volumeStatus = computeVolumeGlobalStatus(
82+
item.metadata['name'],
83+
item.status,
84+
);
85+
7986
return [
8087
...acc,
8188
{
@@ -88,6 +95,7 @@ export default class Metalk8sLocalVolumeProvider {
8895
volumeType: item.spec.rawBlockDevice
8996
? VolumeType.Hardware
9097
: VolumeType.Virtual,
98+
volumeStatus,
9199
},
92100
];
93101
}, [] as LocalPersistentVolume[]);
@@ -180,6 +188,7 @@ export default class Metalk8sLocalVolumeProvider {
180188
devicePath: localVolume.devicePath,
181189
nodeName: localVolume.nodeName,
182190
volumeType: localVolume.volumeType,
191+
volumeStatus: localVolume.volumeStatus,
183192
};
184193
}
185194

0 commit comments

Comments
 (0)