]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.spec.ts
update source to Ceph Pacific 16.2.2
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / block / iscsi-target-form / iscsi-target-form.component.spec.ts
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 });