]> 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
6fa48af6ec4a2b7bb6ec66bcef5d5c2f72ec5e9f
[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 declarations: [IscsiTargetFormComponent],
146 imports: [
147 SharedModule,
148 ReactiveFormsModule,
149 HttpClientTestingModule,
150 RouterTestingModule,
151 ToastrModule.forRoot()
152 ],
153 providers: [
154 i18nProviders,
155 {
156 provide: ActivatedRoute,
157 useValue: new ActivatedRouteStub({ target_iqn: undefined })
158 }
159 ]
160 });
161
162 beforeEach(() => {
163 fixture = TestBed.createComponent(IscsiTargetFormComponent);
164 component = fixture.componentInstance;
165 httpTesting = TestBed.get(HttpTestingController);
166 activatedRoute = TestBed.get(ActivatedRoute);
167 fixture.detectChanges();
168
169 httpTesting.expectOne('ui-api/iscsi/settings').flush(SETTINGS);
170 httpTesting.expectOne('ui-api/iscsi/portals').flush(PORTALS);
171 httpTesting.expectOne('ui-api/iscsi/version').flush(VERSION);
172 httpTesting.expectOne('api/block/image').flush(RBD_LIST);
173 httpTesting.expectOne('api/iscsi/target').flush(LIST_TARGET);
174 httpTesting.verify();
175 });
176
177 it('should create', () => {
178 expect(component).toBeTruthy();
179 });
180
181 it('should only show images not used in other targets', () => {
182 expect(component.imagesAll).toEqual([RBD_LIST[1]['value'][1]]);
183 expect(component.imagesSelections).toEqual([
184 { description: '', name: 'rbd/disk_2', selected: false, enabled: true }
185 ]);
186 });
187
188 it('should generate portals selectOptions', () => {
189 expect(component.portalsSelections).toEqual([
190 { description: '', name: 'node1:192.168.100.201', selected: false, enabled: true },
191 { description: '', name: 'node1:10.0.2.15', selected: false, enabled: true },
192 { description: '', name: 'node2:192.168.100.202', selected: false, enabled: true }
193 ]);
194 });
195
196 it('should create the form', () => {
197 expect(component.targetForm.value).toEqual({
198 disks: [],
199 groups: [],
200 initiators: [],
201 acl_enabled: false,
202 auth: {
203 password: '',
204 user: '',
205 mutual_password: '',
206 mutual_user: ''
207 },
208 portals: [],
209 target_controls: {},
210 target_iqn: component.targetForm.value.target_iqn
211 });
212 });
213
214 it('should prepare data when selecting an image', () => {
215 expect(component.imagesSettings).toEqual({});
216 component.onImageSelection({ option: { name: 'rbd/disk_2', selected: true } });
217 expect(component.imagesSettings).toEqual({
218 'rbd/disk_2': {
219 lun: 0,
220 backstore: 'backstore:1',
221 'backstore:1': {}
222 }
223 });
224 });
225
226 it('should clean data when removing an image', () => {
227 component.onImageSelection({ option: { name: 'rbd/disk_2', selected: true } });
228 component.addGroup();
229 component.groups.controls[0].patchValue({
230 group_id: 'foo',
231 disks: ['rbd/disk_2']
232 });
233
234 expect(component.groups.controls[0].value).toEqual({
235 disks: ['rbd/disk_2'],
236 group_id: 'foo',
237 members: []
238 });
239
240 component.onImageSelection({ option: { name: 'rbd/disk_2', selected: false } });
241
242 expect(component.groups.controls[0].value).toEqual({ disks: [], group_id: 'foo', members: [] });
243 expect(component.imagesSettings).toEqual({
244 'rbd/disk_2': {
245 lun: 0,
246 backstore: 'backstore:1',
247 'backstore:1': {}
248 }
249 });
250 });
251
252 it('should validate authentication', () => {
253 const control = component.targetForm;
254 const formHelper = new FormHelper(control as CdFormGroup);
255 formHelper.expectValid('auth');
256 validateAuth(formHelper);
257 });
258
259 describe('should test initiators', () => {
260 beforeEach(() => {
261 component.onImageSelection({ option: { name: 'rbd/disk_2', selected: true } });
262 component.targetForm.patchValue({ disks: ['rbd/disk_2'], acl_enabled: true });
263 component.addGroup().patchValue({ name: 'group_1' });
264 component.addGroup().patchValue({ name: 'group_2' });
265
266 component.addInitiator();
267 component.initiators.controls[0].patchValue({
268 client_iqn: 'iqn.initiator'
269 });
270 component.updatedInitiatorSelector();
271 });
272
273 it('should prepare data when creating an initiator', () => {
274 expect(component.initiators.controls.length).toBe(1);
275 expect(component.initiators.controls[0].value).toEqual({
276 auth: { mutual_password: '', mutual_user: '', password: '', user: '' },
277 cdIsInGroup: false,
278 client_iqn: 'iqn.initiator',
279 luns: []
280 });
281 expect(component.imagesInitiatorSelections).toEqual([
282 [{ description: '', name: 'rbd/disk_2', selected: false, enabled: true }]
283 ]);
284 expect(component.groupMembersSelections).toEqual([
285 [{ description: '', name: 'iqn.initiator', selected: false, enabled: true }],
286 [{ description: '', name: 'iqn.initiator', selected: false, enabled: true }]
287 ]);
288 });
289
290 it('should update data when changing an initiator name', () => {
291 expect(component.groupMembersSelections).toEqual([
292 [{ description: '', name: 'iqn.initiator', selected: false, enabled: true }],
293 [{ description: '', name: 'iqn.initiator', selected: false, enabled: true }]
294 ]);
295
296 component.initiators.controls[0].patchValue({
297 client_iqn: 'iqn.initiator_new'
298 });
299 component.updatedInitiatorSelector();
300
301 expect(component.groupMembersSelections).toEqual([
302 [{ description: '', name: 'iqn.initiator_new', selected: false, enabled: true }],
303 [{ description: '', name: 'iqn.initiator_new', selected: false, enabled: true }]
304 ]);
305 });
306
307 it('should clean data when removing an initiator', () => {
308 component.groups.controls[0].patchValue({
309 group_id: 'foo',
310 members: ['iqn.initiator']
311 });
312
313 expect(component.groups.controls[0].value).toEqual({
314 disks: [],
315 group_id: 'foo',
316 members: ['iqn.initiator']
317 });
318
319 component.removeInitiator(0);
320
321 expect(component.groups.controls[0].value).toEqual({
322 disks: [],
323 group_id: 'foo',
324 members: []
325 });
326 expect(component.groupMembersSelections).toEqual([[], []]);
327 expect(component.imagesInitiatorSelections).toEqual([]);
328 });
329
330 it('should remove images in the initiator when added in a group', () => {
331 component.initiators.controls[0].patchValue({
332 luns: ['rbd/disk_2']
333 });
334 expect(component.initiators.controls[0].value).toEqual({
335 auth: { mutual_password: '', mutual_user: '', password: '', user: '' },
336 cdIsInGroup: false,
337 client_iqn: 'iqn.initiator',
338 luns: ['rbd/disk_2']
339 });
340
341 component.addGroup();
342 component.groups.controls[0].patchValue({
343 group_id: 'foo',
344 members: ['iqn.initiator']
345 });
346 component.onGroupMemberSelection({
347 option: {
348 name: 'iqn.initiator',
349 selected: true
350 }
351 });
352
353 expect(component.initiators.controls[0].value).toEqual({
354 auth: { mutual_password: '', mutual_user: '', password: '', user: '' },
355 cdIsInGroup: true,
356 client_iqn: 'iqn.initiator',
357 luns: []
358 });
359 });
360
361 it('should disabled the initiator when selected', () => {
362 expect(component.groupMembersSelections).toEqual([
363 [{ description: '', enabled: true, name: 'iqn.initiator', selected: false }],
364 [{ description: '', enabled: true, name: 'iqn.initiator', selected: false }]
365 ]);
366
367 component.groupMembersSelections[0][0].selected = true;
368 component.onGroupMemberSelection({ option: { name: 'iqn.initiator', selected: true } });
369
370 expect(component.groupMembersSelections).toEqual([
371 [{ description: '', enabled: false, name: 'iqn.initiator', selected: true }],
372 [{ description: '', enabled: false, name: 'iqn.initiator', selected: false }]
373 ]);
374 });
375
376 it('should validate authentication', () => {
377 const control = component.initiators.controls[0];
378 const formHelper = new FormHelper(control as CdFormGroup);
379 formHelper.expectValid(control);
380 validateAuth(formHelper);
381 });
382 });
383
384 describe('should submit request', () => {
385 beforeEach(() => {
386 component.onImageSelection({ option: { name: 'rbd/disk_2', selected: true } });
387 component.targetForm.patchValue({ disks: ['rbd/disk_2'], acl_enabled: true });
388 component.portals.setValue(['node1:192.168.100.201', 'node2:192.168.100.202']);
389 component.addInitiator().patchValue({
390 client_iqn: 'iqn.initiator'
391 });
392 component.addGroup().patchValue({
393 group_id: 'foo',
394 members: ['iqn.initiator'],
395 disks: ['rbd/disk_2']
396 });
397 });
398
399 it('should call update', () => {
400 activatedRoute.setParams({ target_iqn: 'iqn.iscsi' });
401 component.isEdit = true;
402 component.target_iqn = 'iqn.iscsi';
403
404 component.submit();
405
406 const req = httpTesting.expectOne('api/iscsi/target/iqn.iscsi');
407 expect(req.request.method).toBe('PUT');
408 expect(req.request.body).toEqual({
409 clients: [
410 {
411 auth: { mutual_password: '', mutual_user: '', password: '', user: '' },
412 client_iqn: 'iqn.initiator',
413 luns: []
414 }
415 ],
416 disks: [
417 {
418 backstore: 'backstore:1',
419 controls: {},
420 image: 'disk_2',
421 pool: 'rbd',
422 lun: 0,
423 wwn: undefined
424 }
425 ],
426 groups: [
427 { disks: [{ image: 'disk_2', pool: 'rbd' }], group_id: 'foo', members: ['iqn.initiator'] }
428 ],
429 new_target_iqn: component.targetForm.value.target_iqn,
430 portals: [
431 { host: 'node1', ip: '192.168.100.201' },
432 { host: 'node2', ip: '192.168.100.202' }
433 ],
434 target_controls: {},
435 target_iqn: component.target_iqn,
436 acl_enabled: true,
437 auth: {
438 password: '',
439 user: '',
440 mutual_password: '',
441 mutual_user: ''
442 }
443 });
444 });
445
446 it('should call create', () => {
447 component.submit();
448
449 const req = httpTesting.expectOne('api/iscsi/target');
450 expect(req.request.method).toBe('POST');
451 expect(req.request.body).toEqual({
452 clients: [
453 {
454 auth: { mutual_password: '', mutual_user: '', password: '', user: '' },
455 client_iqn: 'iqn.initiator',
456 luns: []
457 }
458 ],
459 disks: [
460 {
461 backstore: 'backstore:1',
462 controls: {},
463 image: 'disk_2',
464 pool: 'rbd',
465 lun: 0,
466 wwn: undefined
467 }
468 ],
469 groups: [
470 {
471 disks: [{ image: 'disk_2', pool: 'rbd' }],
472 group_id: 'foo',
473 members: ['iqn.initiator']
474 }
475 ],
476 portals: [
477 { host: 'node1', ip: '192.168.100.201' },
478 { host: 'node2', ip: '192.168.100.202' }
479 ],
480 target_controls: {},
481 target_iqn: component.targetForm.value.target_iqn,
482 acl_enabled: true,
483 auth: {
484 password: '',
485 user: '',
486 mutual_password: '',
487 mutual_user: ''
488 }
489 });
490 });
491
492 it('should call create with acl_enabled disabled', () => {
493 component.targetForm.patchValue({ acl_enabled: false });
494 component.submit();
495
496 const req = httpTesting.expectOne('api/iscsi/target');
497 expect(req.request.method).toBe('POST');
498 expect(req.request.body).toEqual({
499 clients: [],
500 disks: [
501 {
502 backstore: 'backstore:1',
503 controls: {},
504 image: 'disk_2',
505 pool: 'rbd',
506 lun: 0,
507 wwn: undefined
508 }
509 ],
510 groups: [],
511 acl_enabled: false,
512 auth: {
513 password: '',
514 user: '',
515 mutual_password: '',
516 mutual_user: ''
517 },
518 portals: [
519 { host: 'node1', ip: '192.168.100.201' },
520 { host: 'node2', ip: '192.168.100.202' }
521 ],
522 target_controls: {},
523 target_iqn: component.targetForm.value.target_iqn
524 });
525 });
526 });
527
528 function validateAuth(formHelper: FormHelper) {
529 IscsiHelper.validateUser(formHelper, 'auth.user');
530 IscsiHelper.validatePassword(formHelper, 'auth.password');
531 IscsiHelper.validateUser(formHelper, 'auth.mutual_user');
532 IscsiHelper.validatePassword(formHelper, 'auth.mutual_password');
533 }
534 });