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