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