]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
e993468c80d891368bd938e29637e1ae099d1b63
[ceph-ci.git] /
1 import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
2 import { ComponentFixture, TestBed } from '@angular/core/testing';
3 import { ReactiveFormsModule } from '@angular/forms';
4 import { ActivatedRoute } from '@angular/router';
5 import { RouterTestingModule } from '@angular/router/testing';
6
7 import { ToastrModule } from 'ngx-toastr';
8
9 import { LoadingPanelComponent } from '~/app/shared/components/loading-panel/loading-panel.component';
10 import { SelectOption } from '~/app/shared/components/select/select-option.model';
11 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
12 import { SharedModule } from '~/app/shared/shared.module';
13 import { ActivatedRouteStub } from '~/testing/activated-route-stub';
14 import { configureTestBed, FormHelper, IscsiHelper } from '~/testing/unit-test-helper';
15 import { IscsiTargetFormComponent } from './iscsi-target-form.component';
16
17 describe('IscsiTargetFormComponent', () => {
18   let component: IscsiTargetFormComponent;
19   let fixture: ComponentFixture<IscsiTargetFormComponent>;
20   let httpTesting: HttpTestingController;
21   let activatedRoute: ActivatedRouteStub;
22
23   const SETTINGS = {
24     config: { minimum_gateways: 2 },
25     disk_default_controls: {
26       'backstore:1': {
27         hw_max_sectors: 1024,
28         osd_op_timeout: 30
29       },
30       'backstore:2': {
31         qfull_timeout: 5
32       }
33     },
34     target_default_controls: {
35       cmdsn_depth: 128,
36       dataout_timeout: 20,
37       immediate_data: true
38     },
39     required_rbd_features: {
40       'backstore:1': 0,
41       'backstore:2': 0
42     },
43     unsupported_rbd_features: {
44       'backstore:1': 0,
45       'backstore:2': 0
46     },
47     backstores: ['backstore:1', 'backstore:2'],
48     default_backstore: 'backstore:1',
49     api_version: 1
50   };
51
52   const LIST_TARGET: any[] = [
53     {
54       target_iqn: 'iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw',
55       portals: [{ host: 'node1', ip: '192.168.100.201' }],
56       disks: [
57         {
58           pool: 'rbd',
59           image: 'disk_1',
60           controls: {},
61           backstore: 'backstore:1',
62           wwn: '64af6678-9694-4367-bacc-f8eb0baa'
63         }
64       ],
65       clients: [
66         {
67           client_iqn: 'iqn.1994-05.com.redhat:rh7-client',
68           luns: [{ pool: 'rbd', image: 'disk_1', lun: 0 }],
69           auth: {
70             user: 'myiscsiusername',
71             password: 'myiscsipassword',
72             mutual_user: null,
73             mutual_password: null
74           }
75         }
76       ],
77       groups: [],
78       target_controls: {}
79     }
80   ];
81
82   const PORTALS = [
83     { name: 'node1', ip_addresses: ['192.168.100.201', '10.0.2.15'] },
84     { name: 'node2', ip_addresses: ['192.168.100.202'] }
85   ];
86
87   const VERSION = {
88     ceph_iscsi_config_version: 11
89   };
90
91   const RBD_LIST: any[] = [
92     { value: [], pool_name: 'ganesha' },
93     {
94       value: [
95         {
96           size: 96636764160,
97           obj_size: 4194304,
98           num_objs: 23040,
99           order: 22,
100           block_name_prefix: 'rbd_data.148162fb31a8',
101           name: 'disk_1',
102           id: '148162fb31a8',
103           pool_name: 'rbd',
104           features: 61,
105           features_name: ['deep-flatten', 'exclusive-lock', 'fast-diff', 'layering', 'object-map'],
106           timestamp: '2019-01-18T10:44:26Z',
107           stripe_count: 1,
108           stripe_unit: 4194304,
109           data_pool: null,
110           parent: null,
111           snapshots: [],
112           total_disk_usage: 0,
113           disk_usage: 0
114         },
115         {
116           size: 119185342464,
117           obj_size: 4194304,
118           num_objs: 28416,
119           order: 22,
120           block_name_prefix: 'rbd_data.14b292cee6cb',
121           name: 'disk_2',
122           id: '14b292cee6cb',
123           pool_name: 'rbd',
124           features: 61,
125           features_name: ['deep-flatten', 'exclusive-lock', 'fast-diff', 'layering', 'object-map'],
126           timestamp: '2019-01-18T10:45:56Z',
127           stripe_count: 1,
128           stripe_unit: 4194304,
129           data_pool: null,
130           parent: null,
131           snapshots: [],
132           total_disk_usage: 0,
133           disk_usage: 0
134         }
135       ],
136       pool_name: 'rbd'
137     }
138   ];
139
140   configureTestBed(
141     {
142       declarations: [IscsiTargetFormComponent],
143       imports: [
144         SharedModule,
145         ReactiveFormsModule,
146         HttpClientTestingModule,
147         RouterTestingModule,
148         ToastrModule.forRoot()
149       ],
150       providers: [
151         {
152           provide: ActivatedRoute,
153           useValue: new ActivatedRouteStub({ target_iqn: undefined })
154         }
155       ]
156     },
157     [LoadingPanelComponent]
158   );
159
160   beforeEach(() => {
161     fixture = TestBed.createComponent(IscsiTargetFormComponent);
162     component = fixture.componentInstance;
163     httpTesting = TestBed.inject(HttpTestingController);
164     activatedRoute = <ActivatedRouteStub>TestBed.inject(ActivatedRoute);
165     fixture.detectChanges();
166
167     httpTesting.expectOne('ui-api/iscsi/settings').flush(SETTINGS);
168     httpTesting.expectOne('ui-api/iscsi/portals').flush(PORTALS);
169     httpTesting.expectOne('ui-api/iscsi/version').flush(VERSION);
170     httpTesting.expectOne('api/block/image?offset=0&limit=-1&search=&sort=+name').flush(RBD_LIST);
171     httpTesting.expectOne('api/iscsi/target').flush(LIST_TARGET);
172     httpTesting.verify();
173   });
174
175   it('should create', () => {
176     expect(component).toBeTruthy();
177   });
178
179   it('should only show images not used in other targets', () => {
180     expect(component.imagesAll).toEqual([RBD_LIST[1]['value'][1]]);
181     expect(component.imagesSelections).toEqual([
182       { description: '', name: 'rbd/disk_2', selected: false, enabled: true }
183     ]);
184   });
185
186   it('should generate portals selectOptions', () => {
187     expect(component.portalsSelections).toEqual([
188       { description: '', name: 'node1:192.168.100.201', selected: false, enabled: true },
189       { description: '', name: 'node1:10.0.2.15', selected: false, enabled: true },
190       { description: '', name: 'node2:192.168.100.202', selected: false, enabled: true }
191     ]);
192   });
193
194   it('should create the form', () => {
195     expect(component.targetForm.value).toEqual({
196       disks: [],
197       groups: [],
198       initiators: [],
199       acl_enabled: false,
200       auth: {
201         password: '',
202         user: '',
203         mutual_password: '',
204         mutual_user: ''
205       },
206       portals: [],
207       target_controls: {},
208       target_iqn: component.targetForm.value.target_iqn
209     });
210   });
211
212   it('should prepare data when selecting an image', () => {
213     expect(component.imagesSettings).toEqual({});
214     component.onImageSelection({ option: { name: 'rbd/disk_2', selected: true } });
215     expect(component.imagesSettings).toEqual({
216       'rbd/disk_2': {
217         lun: 0,
218         backstore: 'backstore:1',
219         'backstore:1': {}
220       }
221     });
222   });
223
224   it('should clean data when removing an image', () => {
225     component.onImageSelection({ option: { name: 'rbd/disk_2', selected: true } });
226     component.addGroup();
227     component.groups.controls[0].patchValue({
228       group_id: 'foo',
229       disks: ['rbd/disk_2']
230     });
231
232     expect(component.groups.controls[0].value).toEqual({
233       disks: ['rbd/disk_2'],
234       group_id: 'foo',
235       members: []
236     });
237
238     component.onImageSelection({ option: { name: 'rbd/disk_2', selected: false } });
239
240     expect(component.groups.controls[0].value).toEqual({ disks: [], group_id: 'foo', members: [] });
241     expect(component.imagesSettings).toEqual({
242       'rbd/disk_2': {
243         lun: 0,
244         backstore: 'backstore:1',
245         'backstore:1': {}
246       }
247     });
248   });
249
250   it('should validate authentication', () => {
251     const control = component.targetForm;
252     const formHelper = new FormHelper(control as CdFormGroup);
253     formHelper.expectValid('auth');
254     validateAuth(formHelper);
255   });
256
257   describe('should test initiators', () => {
258     beforeEach(() => {
259       component.onImageSelection({ option: { name: 'rbd/disk_2', selected: true } });
260       component.targetForm.patchValue({ disks: ['rbd/disk_2'], acl_enabled: true });
261       component.addGroup().patchValue({ name: 'group_1' });
262       component.addGroup().patchValue({ name: 'group_2' });
263
264       component.addInitiator();
265       component.initiators.controls[0].patchValue({
266         client_iqn: 'iqn.initiator'
267       });
268       component.updatedInitiatorSelector();
269     });
270
271     it('should prepare data when creating an initiator', () => {
272       expect(component.initiators.controls.length).toBe(1);
273       expect(component.initiators.controls[0].value).toEqual({
274         auth: { mutual_password: '', mutual_user: '', password: '', user: '' },
275         cdIsInGroup: false,
276         client_iqn: 'iqn.initiator',
277         luns: []
278       });
279       expect(component.imagesInitiatorSelections).toEqual([
280         [{ description: '', name: 'rbd/disk_2', selected: false, enabled: true }]
281       ]);
282       expect(component.groupMembersSelections).toEqual([
283         [{ description: '', name: 'iqn.initiator', selected: false, enabled: true }],
284         [{ description: '', name: 'iqn.initiator', selected: false, enabled: true }]
285       ]);
286     });
287
288     it('should update data when changing an initiator name', () => {
289       expect(component.groupMembersSelections).toEqual([
290         [{ description: '', name: 'iqn.initiator', selected: false, enabled: true }],
291         [{ description: '', name: 'iqn.initiator', selected: false, enabled: true }]
292       ]);
293
294       component.initiators.controls[0].patchValue({
295         client_iqn: 'iqn.initiator_new'
296       });
297       component.updatedInitiatorSelector();
298
299       expect(component.groupMembersSelections).toEqual([
300         [{ description: '', name: 'iqn.initiator_new', selected: false, enabled: true }],
301         [{ description: '', name: 'iqn.initiator_new', selected: false, enabled: true }]
302       ]);
303     });
304
305     it('should clean data when removing an initiator', () => {
306       component.groups.controls[0].patchValue({
307         group_id: 'foo',
308         members: ['iqn.initiator']
309       });
310
311       expect(component.groups.controls[0].value).toEqual({
312         disks: [],
313         group_id: 'foo',
314         members: ['iqn.initiator']
315       });
316
317       component.removeInitiator(0);
318
319       expect(component.groups.controls[0].value).toEqual({
320         disks: [],
321         group_id: 'foo',
322         members: []
323       });
324       expect(component.groupMembersSelections).toEqual([[], []]);
325       expect(component.imagesInitiatorSelections).toEqual([]);
326     });
327
328     it('should remove images in the initiator when added in a group', () => {
329       component.initiators.controls[0].patchValue({
330         luns: ['rbd/disk_2']
331       });
332       component.imagesInitiatorSelections[0] = [
333         {
334           description: '',
335           enabled: true,
336           name: 'rbd/disk_2',
337           selected: true
338         }
339       ];
340       expect(component.initiators.controls[0].value).toEqual({
341         auth: { mutual_password: '', mutual_user: '', password: '', user: '' },
342         cdIsInGroup: false,
343         client_iqn: 'iqn.initiator',
344         luns: ['rbd/disk_2']
345       });
346
347       component.groups.controls[0].patchValue({
348         group_id: 'foo',
349         members: ['iqn.initiator']
350       });
351       component.onGroupMemberSelection(
352         {
353           option: {
354             name: 'iqn.initiator',
355             selected: true
356           }
357         },
358         0
359       );
360
361       expect(component.initiators.controls[0].value).toEqual({
362         auth: { mutual_password: '', mutual_user: '', password: '', user: '' },
363         cdIsInGroup: true,
364         client_iqn: 'iqn.initiator',
365         luns: []
366       });
367       expect(component.imagesInitiatorSelections[0]).toEqual([
368         {
369           description: '',
370           enabled: true,
371           name: 'rbd/disk_2',
372           selected: false
373         }
374       ]);
375     });
376
377     it('should disabled the initiator when selected', () => {
378       expect(component.groupMembersSelections).toEqual([
379         [{ description: '', enabled: true, name: 'iqn.initiator', selected: false }],
380         [{ description: '', enabled: true, name: 'iqn.initiator', selected: false }]
381       ]);
382
383       component.groupMembersSelections[0][0].selected = true;
384       component.onGroupMemberSelection({ option: { name: 'iqn.initiator', selected: true } }, 0);
385
386       expect(component.groupMembersSelections).toEqual([
387         [{ description: '', enabled: false, name: 'iqn.initiator', selected: true }],
388         [{ description: '', enabled: false, name: 'iqn.initiator', selected: false }]
389       ]);
390     });
391
392     describe('should remove from group', () => {
393       beforeEach(() => {
394         component.onGroupMemberSelection(
395           { option: new SelectOption(true, 'iqn.initiator', '') },
396           0
397         );
398         component.groupDiskSelections[0][0].selected = true;
399         component.groups.controls[0].patchValue({
400           disks: ['rbd/disk_2'],
401           members: ['iqn.initiator']
402         });
403
404         expect(component.initiators.value[0].luns).toEqual([]);
405         expect(component.imagesInitiatorSelections[0]).toEqual([
406           { description: '', enabled: true, name: 'rbd/disk_2', selected: false }
407         ]);
408         expect(component.initiators.value[0].cdIsInGroup).toBe(true);
409       });
410
411       it('should update initiator images when deselecting', () => {
412         component.onGroupMemberSelection(
413           { option: new SelectOption(false, 'iqn.initiator', '') },
414           0
415         );
416
417         expect(component.initiators.value[0].luns).toEqual(['rbd/disk_2']);
418         expect(component.imagesInitiatorSelections[0]).toEqual([
419           { description: '', enabled: true, name: 'rbd/disk_2', selected: true }
420         ]);
421         expect(component.initiators.value[0].cdIsInGroup).toBe(false);
422       });
423
424       it('should update initiator when removing', () => {
425         component.removeGroupInitiator(component.groups.controls[0] as CdFormGroup, 0, 0);
426
427         expect(component.initiators.value[0].luns).toEqual(['rbd/disk_2']);
428         expect(component.imagesInitiatorSelections[0]).toEqual([
429           { description: '', enabled: true, name: 'rbd/disk_2', selected: true }
430         ]);
431         expect(component.initiators.value[0].cdIsInGroup).toBe(false);
432       });
433     });
434
435     it('should validate authentication', () => {
436       const control = component.initiators.controls[0];
437       const formHelper = new FormHelper(control as CdFormGroup);
438       formHelper.expectValid(control);
439       validateAuth(formHelper);
440     });
441   });
442
443   describe('should submit request', () => {
444     beforeEach(() => {
445       component.onImageSelection({ option: { name: 'rbd/disk_2', selected: true } });
446       component.targetForm.patchValue({ disks: ['rbd/disk_2'], acl_enabled: true });
447       component.portals.setValue(['node1:192.168.100.201', 'node2:192.168.100.202']);
448       component.addInitiator().patchValue({
449         client_iqn: 'iqn.initiator'
450       });
451       component.addGroup().patchValue({
452         group_id: 'foo',
453         members: ['iqn.initiator'],
454         disks: ['rbd/disk_2']
455       });
456     });
457
458     it('should call update', () => {
459       activatedRoute.setParams({ target_iqn: 'iqn.iscsi' });
460       component.isEdit = true;
461       component.target_iqn = 'iqn.iscsi';
462
463       component.submit();
464
465       const req = httpTesting.expectOne('api/iscsi/target/iqn.iscsi');
466       expect(req.request.method).toBe('PUT');
467       expect(req.request.body).toEqual({
468         clients: [
469           {
470             auth: { mutual_password: '', mutual_user: '', password: '', user: '' },
471             client_iqn: 'iqn.initiator',
472             luns: []
473           }
474         ],
475         disks: [
476           {
477             backstore: 'backstore:1',
478             controls: {},
479             image: 'disk_2',
480             pool: 'rbd',
481             lun: 0,
482             wwn: undefined
483           }
484         ],
485         groups: [
486           { disks: [{ image: 'disk_2', pool: 'rbd' }], group_id: 'foo', members: ['iqn.initiator'] }
487         ],
488         new_target_iqn: component.targetForm.value.target_iqn,
489         portals: [
490           { host: 'node1', ip: '192.168.100.201' },
491           { host: 'node2', ip: '192.168.100.202' }
492         ],
493         target_controls: {},
494         target_iqn: component.target_iqn,
495         acl_enabled: true,
496         auth: {
497           password: '',
498           user: '',
499           mutual_password: '',
500           mutual_user: ''
501         }
502       });
503     });
504
505     it('should call create', () => {
506       component.submit();
507
508       const req = httpTesting.expectOne('api/iscsi/target');
509       expect(req.request.method).toBe('POST');
510       expect(req.request.body).toEqual({
511         clients: [
512           {
513             auth: { mutual_password: '', mutual_user: '', password: '', user: '' },
514             client_iqn: 'iqn.initiator',
515             luns: []
516           }
517         ],
518         disks: [
519           {
520             backstore: 'backstore:1',
521             controls: {},
522             image: 'disk_2',
523             pool: 'rbd',
524             lun: 0,
525             wwn: undefined
526           }
527         ],
528         groups: [
529           {
530             disks: [{ image: 'disk_2', pool: 'rbd' }],
531             group_id: 'foo',
532             members: ['iqn.initiator']
533           }
534         ],
535         portals: [
536           { host: 'node1', ip: '192.168.100.201' },
537           { host: 'node2', ip: '192.168.100.202' }
538         ],
539         target_controls: {},
540         target_iqn: component.targetForm.value.target_iqn,
541         acl_enabled: true,
542         auth: {
543           password: '',
544           user: '',
545           mutual_password: '',
546           mutual_user: ''
547         }
548       });
549     });
550
551     it('should call create with acl_enabled disabled', () => {
552       component.targetForm.patchValue({ acl_enabled: false });
553       component.submit();
554
555       const req = httpTesting.expectOne('api/iscsi/target');
556       expect(req.request.method).toBe('POST');
557       expect(req.request.body).toEqual({
558         clients: [],
559         disks: [
560           {
561             backstore: 'backstore:1',
562             controls: {},
563             image: 'disk_2',
564             pool: 'rbd',
565             lun: 0,
566             wwn: undefined
567           }
568         ],
569         groups: [],
570         acl_enabled: false,
571         auth: {
572           password: '',
573           user: '',
574           mutual_password: '',
575           mutual_user: ''
576         },
577         portals: [
578           { host: 'node1', ip: '192.168.100.201' },
579           { host: 'node2', ip: '192.168.100.202' }
580         ],
581         target_controls: {},
582         target_iqn: component.targetForm.value.target_iqn
583       });
584     });
585   });
586
587   function validateAuth(formHelper: FormHelper) {
588     IscsiHelper.validateUser(formHelper, 'auth.user');
589     IscsiHelper.validatePassword(formHelper, 'auth.password');
590     IscsiHelper.validateUser(formHelper, 'auth.mutual_user');
591     IscsiHelper.validatePassword(formHelper, 'auth.mutual_password');
592   }
593 });