]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
c5bead7dc38869e4b47f2ca624a9c823afdde9d1
[ceph-ci.git] /
1 import { Component, OnInit } from '@angular/core';
2 import { FormArray, FormControl, Validators } from '@angular/forms';
3 import { ActivatedRoute, Router } from '@angular/router';
4
5 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
6 import { I18n } from '@ngx-translate/i18n-polyfill';
7 import * as _ from 'lodash';
8 import { forkJoin } from 'rxjs';
9
10 import { IscsiService } from '../../../shared/api/iscsi.service';
11 import { RbdService } from '../../../shared/api/rbd.service';
12 import { SelectMessages } from '../../../shared/components/select/select-messages.model';
13 import { SelectOption } from '../../../shared/components/select/select-option.model';
14 import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
15 import { Icons } from '../../../shared/enum/icons.enum';
16 import { CdForm } from '../../../shared/forms/cd-form';
17 import { CdFormGroup } from '../../../shared/forms/cd-form-group';
18 import { CdValidators } from '../../../shared/forms/cd-validators';
19 import { FinishedTask } from '../../../shared/models/finished-task';
20 import { ModalService } from '../../../shared/services/modal.service';
21 import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
22 import { IscsiTargetImageSettingsModalComponent } from '../iscsi-target-image-settings-modal/iscsi-target-image-settings-modal.component';
23 import { IscsiTargetIqnSettingsModalComponent } from '../iscsi-target-iqn-settings-modal/iscsi-target-iqn-settings-modal.component';
24
25 @Component({
26   selector: 'cd-iscsi-target-form',
27   templateUrl: './iscsi-target-form.component.html',
28   styleUrls: ['./iscsi-target-form.component.scss']
29 })
30 export class IscsiTargetFormComponent extends CdForm implements OnInit {
31   cephIscsiConfigVersion: number;
32   targetForm: CdFormGroup;
33   modalRef: NgbModalRef;
34   api_version = 0;
35   minimum_gateways = 1;
36   target_default_controls: any;
37   target_controls_limits: any;
38   disk_default_controls: any;
39   disk_controls_limits: any;
40   backstores: string[];
41   default_backstore: string;
42   unsupported_rbd_features: any;
43   required_rbd_features: any;
44
45   icons = Icons;
46
47   isEdit = false;
48   target_iqn: string;
49
50   imagesAll: any[];
51   imagesSelections: SelectOption[];
52   portalsSelections: SelectOption[] = [];
53
54   imagesInitiatorSelections: SelectOption[][] = [];
55   groupDiskSelections: SelectOption[][] = [];
56   groupMembersSelections: SelectOption[][] = [];
57
58   imagesSettings: any = {};
59   messages = {
60     portals: new SelectMessages(
61       { noOptions: this.i18n('There are no portals available.') },
62       this.i18n
63     ),
64     images: new SelectMessages(
65       { noOptions: this.i18n('There are no images available.') },
66       this.i18n
67     ),
68     initiatorImage: new SelectMessages(
69       {
70         noOptions: this.i18n(
71           'There are no images available. Please make sure you add an image to the target.'
72         )
73       },
74       this.i18n
75     ),
76     groupInitiator: new SelectMessages(
77       {
78         noOptions: this.i18n(
79           'There are no initiators available. Please make sure you add an initiator to the target.'
80         )
81       },
82       this.i18n
83     )
84   };
85
86   IQN_REGEX = /^iqn\.(19|20)\d\d-(0[1-9]|1[0-2])\.\D{2,3}(\.[A-Za-z0-9-]+)+(:[A-Za-z0-9-\.]+)*$/;
87   USER_REGEX = /^[\w\.:@_-]{8,64}$/;
88   PASSWORD_REGEX = /^[\w@\-_\/]{12,16}$/;
89   action: string;
90   resource: string;
91
92   constructor(
93     private iscsiService: IscsiService,
94     private modalService: ModalService,
95     private rbdService: RbdService,
96     private router: Router,
97     private route: ActivatedRoute,
98     private i18n: I18n,
99     private taskWrapper: TaskWrapperService,
100     public actionLabels: ActionLabelsI18n
101   ) {
102     super();
103     this.resource = this.i18n('target');
104   }
105
106   ngOnInit() {
107     const promises: any[] = [
108       this.iscsiService.listTargets(),
109       this.rbdService.list(),
110       this.iscsiService.portals(),
111       this.iscsiService.settings(),
112       this.iscsiService.version()
113     ];
114
115     if (this.router.url.startsWith('/block/iscsi/targets/edit')) {
116       this.isEdit = true;
117       this.route.params.subscribe((params: { target_iqn: string }) => {
118         this.target_iqn = decodeURIComponent(params.target_iqn);
119         promises.push(this.iscsiService.getTarget(this.target_iqn));
120       });
121     }
122     this.action = this.isEdit ? this.actionLabels.EDIT : this.actionLabels.CREATE;
123
124     forkJoin(promises).subscribe((data: any[]) => {
125       // iscsiService.listTargets
126       const usedImages = _(data[0])
127         .filter((target) => target.target_iqn !== this.target_iqn)
128         .flatMap((target) => target.disks)
129         .map((image) => `${image.pool}/${image.image}`)
130         .value();
131
132       // iscsiService.settings()
133       if ('api_version' in data[3]) {
134         this.api_version = data[3].api_version;
135       }
136       this.minimum_gateways = data[3].config.minimum_gateways;
137       this.target_default_controls = data[3].target_default_controls;
138       this.target_controls_limits = data[3].target_controls_limits;
139       this.disk_default_controls = data[3].disk_default_controls;
140       this.disk_controls_limits = data[3].disk_controls_limits;
141       this.backstores = data[3].backstores;
142       this.default_backstore = data[3].default_backstore;
143       this.unsupported_rbd_features = data[3].unsupported_rbd_features;
144       this.required_rbd_features = data[3].required_rbd_features;
145
146       // rbdService.list()
147       this.imagesAll = _(data[1])
148         .flatMap((pool) => pool.value)
149         .filter((image) => {
150           // Namespaces are not supported by ceph-iscsi
151           if (image.namespace) {
152             return false;
153           }
154           const imageId = `${image.pool_name}/${image.name}`;
155           if (usedImages.indexOf(imageId) !== -1) {
156             return false;
157           }
158           const validBackstores = this.getValidBackstores(image);
159           if (validBackstores.length === 0) {
160             return false;
161           }
162           return true;
163         })
164         .value();
165
166       this.imagesSelections = this.imagesAll.map(
167         (image) => new SelectOption(false, `${image.pool_name}/${image.name}`, '')
168       );
169
170       // iscsiService.portals()
171       const portals: SelectOption[] = [];
172       data[2].forEach((portal: Record<string, any>) => {
173         portal.ip_addresses.forEach((ip: string) => {
174           portals.push(new SelectOption(false, portal.name + ':' + ip, ''));
175         });
176       });
177       this.portalsSelections = [...portals];
178
179       // iscsiService.version()
180       this.cephIscsiConfigVersion = data[4]['ceph_iscsi_config_version'];
181
182       this.createForm();
183
184       // iscsiService.getTarget()
185       if (data[5]) {
186         this.resolveModel(data[5]);
187       }
188
189       this.loadingReady();
190     });
191   }
192
193   createForm() {
194     this.targetForm = new CdFormGroup({
195       target_iqn: new FormControl('iqn.2001-07.com.ceph:' + Date.now(), {
196         validators: [Validators.required, Validators.pattern(this.IQN_REGEX)]
197       }),
198       target_controls: new FormControl({}),
199       portals: new FormControl([], {
200         validators: [
201           CdValidators.custom('minGateways', (value: any[]) => {
202             const gateways = _.uniq(value.map((elem) => elem.split(':')[0]));
203             return gateways.length < Math.max(1, this.minimum_gateways);
204           })
205         ]
206       }),
207       disks: new FormControl([], {
208         validators: [
209           CdValidators.custom('dupLunId', (value: any[]) => {
210             const lunIds = this.getLunIds(value);
211             return lunIds.length !== _.uniq(lunIds).length;
212           }),
213           CdValidators.custom('dupWwn', (value: any[]) => {
214             const wwns = this.getWwns(value);
215             return wwns.length !== _.uniq(wwns).length;
216           })
217         ]
218       }),
219       initiators: new FormArray([]),
220       groups: new FormArray([]),
221       acl_enabled: new FormControl(false)
222     });
223     // Target level authentication was introduced in ceph-iscsi config v11
224     if (this.cephIscsiConfigVersion > 10) {
225       const authFormGroup = new CdFormGroup({
226         user: new FormControl(''),
227         password: new FormControl(''),
228         mutual_user: new FormControl(''),
229         mutual_password: new FormControl('')
230       });
231       this.setAuthValidator(authFormGroup);
232       this.targetForm.addControl('auth', authFormGroup);
233     }
234   }
235
236   resolveModel(res: Record<string, any>) {
237     this.targetForm.patchValue({
238       target_iqn: res.target_iqn,
239       target_controls: res.target_controls,
240       acl_enabled: res.acl_enabled
241     });
242     // Target level authentication was introduced in ceph-iscsi config v11
243     if (this.cephIscsiConfigVersion > 10) {
244       this.targetForm.patchValue({
245         auth: res.auth
246       });
247     }
248     const portals: any[] = [];
249     _.forEach(res.portals, (portal) => {
250       const id = `${portal.host}:${portal.ip}`;
251       portals.push(id);
252     });
253     this.targetForm.patchValue({
254       portals: portals
255     });
256
257     const disks: any[] = [];
258     _.forEach(res.disks, (disk) => {
259       const id = `${disk.pool}/${disk.image}`;
260       disks.push(id);
261       this.imagesSettings[id] = {
262         backstore: disk.backstore
263       };
264       this.imagesSettings[id][disk.backstore] = disk.controls;
265       if ('lun' in disk) {
266         this.imagesSettings[id]['lun'] = disk.lun;
267       }
268       if ('wwn' in disk) {
269         this.imagesSettings[id]['wwn'] = disk.wwn;
270       }
271
272       this.onImageSelection({ option: { name: id, selected: true } });
273     });
274     this.targetForm.patchValue({
275       disks: disks
276     });
277
278     _.forEach(res.clients, (client) => {
279       const initiator = this.addInitiator();
280       client.luns = _.map(client.luns, (lun) => `${lun.pool}/${lun.image}`);
281       initiator.patchValue(client);
282       // updatedInitiatorSelector()
283     });
284
285     _.forEach(res.groups, (group) => {
286       const fg = this.addGroup();
287       group.disks = _.map(group.disks, (disk) => `${disk.pool}/${disk.image}`);
288       fg.patchValue(group);
289       _.forEach(group.members, (member) => {
290         this.onGroupMemberSelection({ option: new SelectOption(true, member, '') });
291       });
292     });
293   }
294
295   hasAdvancedSettings(settings: any) {
296     return Object.values(settings).length > 0;
297   }
298
299   // Portals
300   get portals() {
301     return this.targetForm.get('portals') as FormControl;
302   }
303
304   onPortalSelection() {
305     this.portals.setValue(this.portals.value);
306   }
307
308   removePortal(index: number, portal: string) {
309     this.portalsSelections.forEach((value) => {
310       if (value.name === portal) {
311         value.selected = false;
312       }
313     });
314
315     this.portals.value.splice(index, 1);
316     this.portals.setValue(this.portals.value);
317     return false;
318   }
319
320   // Images
321   get disks() {
322     return this.targetForm.get('disks') as FormControl;
323   }
324
325   removeImage(index: number, image: string) {
326     this.imagesSelections.forEach((value) => {
327       if (value.name === image) {
328         value.selected = false;
329       }
330     });
331     this.disks.value.splice(index, 1);
332     this.removeImageRefs(image);
333     this.targetForm.get('disks').updateValueAndValidity({ emitEvent: false });
334     return false;
335   }
336
337   removeImageRefs(name: string) {
338     this.initiators.controls.forEach((element) => {
339       const newImages = element.value.luns.filter((item: string) => item !== name);
340       element.get('luns').setValue(newImages);
341     });
342
343     this.groups.controls.forEach((element) => {
344       const newDisks = element.value.disks.filter((item: string) => item !== name);
345       element.get('disks').setValue(newDisks);
346     });
347
348     _.forEach(this.imagesInitiatorSelections, (selections, i) => {
349       this.imagesInitiatorSelections[i] = selections.filter((item: any) => item.name !== name);
350     });
351     _.forEach(this.groupDiskSelections, (selections, i) => {
352       this.groupDiskSelections[i] = selections.filter((item: any) => item.name !== name);
353     });
354   }
355
356   getDefaultBackstore(imageId: string) {
357     let result = this.default_backstore;
358     const image = this.getImageById(imageId);
359     if (!this.validFeatures(image, this.default_backstore)) {
360       this.backstores.forEach((backstore) => {
361         if (backstore !== this.default_backstore) {
362           if (this.validFeatures(image, backstore)) {
363             result = backstore;
364           }
365         }
366       });
367     }
368     return result;
369   }
370
371   isLunIdInUse(lunId: string, imageId: string) {
372     const images = this.disks.value.filter((currentImageId: string) => currentImageId !== imageId);
373     return this.getLunIds(images).includes(lunId);
374   }
375
376   getLunIds(images: object) {
377     return _.map(images, (image) => this.imagesSettings[image]['lun']);
378   }
379
380   nextLunId(imageId: string) {
381     const images = this.disks.value.filter((currentImageId: string) => currentImageId !== imageId);
382     const lunIdsInUse = this.getLunIds(images);
383     let lunIdCandidate = 0;
384     while (lunIdsInUse.includes(lunIdCandidate)) {
385       lunIdCandidate++;
386     }
387     return lunIdCandidate;
388   }
389
390   getWwns(images: object) {
391     const wwns = _.map(images, (image) => this.imagesSettings[image]['wwn']);
392     return wwns.filter((wwn) => _.isString(wwn) && wwn !== '');
393   }
394
395   onImageSelection($event: any) {
396     const option = $event.option;
397
398     if (option.selected) {
399       if (!this.imagesSettings[option.name]) {
400         const defaultBackstore = this.getDefaultBackstore(option.name);
401         this.imagesSettings[option.name] = {
402           backstore: defaultBackstore,
403           lun: this.nextLunId(option.name)
404         };
405         this.imagesSettings[option.name][defaultBackstore] = {};
406       } else if (this.isLunIdInUse(this.imagesSettings[option.name]['lun'], option.name)) {
407         // If the lun id is now in use, we have to generate a new one
408         this.imagesSettings[option.name]['lun'] = this.nextLunId(option.name);
409       }
410
411       _.forEach(this.imagesInitiatorSelections, (selections, i) => {
412         selections.push(new SelectOption(false, option.name, ''));
413         this.imagesInitiatorSelections[i] = [...selections];
414       });
415
416       _.forEach(this.groupDiskSelections, (selections, i) => {
417         selections.push(new SelectOption(false, option.name, ''));
418         this.groupDiskSelections[i] = [...selections];
419       });
420     } else {
421       this.removeImageRefs(option.name);
422     }
423     this.targetForm.get('disks').updateValueAndValidity({ emitEvent: false });
424   }
425
426   // Initiators
427   get initiators() {
428     return this.targetForm.get('initiators') as FormArray;
429   }
430
431   addInitiator() {
432     const fg = new CdFormGroup({
433       client_iqn: new FormControl('', {
434         validators: [
435           Validators.required,
436           CdValidators.custom('notUnique', (client_iqn: string) => {
437             const flattened = this.initiators.controls.reduce(function (accumulator, currentValue) {
438               return accumulator.concat(currentValue.value.client_iqn);
439             }, []);
440
441             return flattened.indexOf(client_iqn) !== flattened.lastIndexOf(client_iqn);
442           }),
443           Validators.pattern(this.IQN_REGEX)
444         ]
445       }),
446       auth: new CdFormGroup({
447         user: new FormControl(''),
448         password: new FormControl(''),
449         mutual_user: new FormControl(''),
450         mutual_password: new FormControl('')
451       }),
452       luns: new FormControl([]),
453       cdIsInGroup: new FormControl(false)
454     });
455
456     this.setAuthValidator(fg);
457
458     this.initiators.push(fg);
459
460     _.forEach(this.groupMembersSelections, (selections, i) => {
461       selections.push(new SelectOption(false, '', ''));
462       this.groupMembersSelections[i] = [...selections];
463     });
464
465     const disks = _.map(
466       this.targetForm.getValue('disks'),
467       (disk) => new SelectOption(false, disk, '')
468     );
469     this.imagesInitiatorSelections.push(disks);
470
471     return fg;
472   }
473
474   setAuthValidator(fg: CdFormGroup) {
475     CdValidators.validateIf(
476       fg.get('user'),
477       () => fg.getValue('password') || fg.getValue('mutual_user') || fg.getValue('mutual_password'),
478       [Validators.required],
479       [Validators.pattern(this.USER_REGEX)],
480       [fg.get('password'), fg.get('mutual_user'), fg.get('mutual_password')]
481     );
482
483     CdValidators.validateIf(
484       fg.get('password'),
485       () => fg.getValue('user') || fg.getValue('mutual_user') || fg.getValue('mutual_password'),
486       [Validators.required],
487       [Validators.pattern(this.PASSWORD_REGEX)],
488       [fg.get('user'), fg.get('mutual_user'), fg.get('mutual_password')]
489     );
490
491     CdValidators.validateIf(
492       fg.get('mutual_user'),
493       () => fg.getValue('mutual_password'),
494       [Validators.required],
495       [Validators.pattern(this.USER_REGEX)],
496       [fg.get('user'), fg.get('password'), fg.get('mutual_password')]
497     );
498
499     CdValidators.validateIf(
500       fg.get('mutual_password'),
501       () => fg.getValue('mutual_user'),
502       [Validators.required],
503       [Validators.pattern(this.PASSWORD_REGEX)],
504       [fg.get('user'), fg.get('password'), fg.get('mutual_user')]
505     );
506   }
507
508   removeInitiator(index: number) {
509     const removed = this.initiators.value[index];
510
511     this.initiators.removeAt(index);
512
513     _.forEach(this.groupMembersSelections, (selections, i) => {
514       selections.splice(index, 1);
515       this.groupMembersSelections[i] = [...selections];
516     });
517
518     this.groups.controls.forEach((element) => {
519       const newMembers = element.value.members.filter(
520         (item: string) => item !== removed.client_iqn
521       );
522       element.get('members').setValue(newMembers);
523     });
524
525     this.imagesInitiatorSelections.splice(index, 1);
526   }
527
528   updatedInitiatorSelector() {
529     // Validate all client_iqn
530     this.initiators.controls.forEach((control) => {
531       control.get('client_iqn').updateValueAndValidity({ emitEvent: false });
532     });
533
534     // Update Group Initiator Selector
535     _.forEach(this.groupMembersSelections, (group, group_index) => {
536       _.forEach(group, (elem, index) => {
537         const oldName = elem.name;
538         elem.name = this.initiators.controls[index].value.client_iqn;
539
540         this.groups.controls.forEach((element) => {
541           const members = element.value.members;
542           const i = members.indexOf(oldName);
543
544           if (i !== -1) {
545             members[i] = elem.name;
546           }
547           element.get('members').setValue(members);
548         });
549       });
550       this.groupMembersSelections[group_index] = [...this.groupMembersSelections[group_index]];
551     });
552   }
553
554   removeInitiatorImage(initiator: any, lun_index: number, initiator_index: number, image: string) {
555     const luns = initiator.getValue('luns');
556     luns.splice(lun_index, 1);
557     initiator.patchValue({ luns: luns });
558
559     this.imagesInitiatorSelections[initiator_index].forEach((value: Record<string, any>) => {
560       if (value.name === image) {
561         value.selected = false;
562       }
563     });
564
565     return false;
566   }
567
568   // Groups
569   get groups() {
570     return this.targetForm.get('groups') as FormArray;
571   }
572
573   addGroup() {
574     const fg = new CdFormGroup({
575       group_id: new FormControl('', { validators: [Validators.required] }),
576       members: new FormControl([]),
577       disks: new FormControl([])
578     });
579
580     this.groups.push(fg);
581
582     const disks = _.map(
583       this.targetForm.getValue('disks'),
584       (disk) => new SelectOption(false, disk, '')
585     );
586     this.groupDiskSelections.push(disks);
587
588     const initiators = _.map(
589       this.initiators.value,
590       (initiator) => new SelectOption(false, initiator.client_iqn, '', !initiator.cdIsInGroup)
591     );
592     this.groupMembersSelections.push(initiators);
593
594     return fg;
595   }
596
597   removeGroup(index: number) {
598     this.groups.removeAt(index);
599     this.groupDiskSelections.splice(index, 1);
600   }
601
602   onGroupMemberSelection($event: any) {
603     const option = $event.option;
604
605     let initiator_index: number;
606     this.initiators.controls.forEach((element, index) => {
607       if (element.value.client_iqn === option.name) {
608         element.patchValue({ luns: [] });
609         element.get('cdIsInGroup').setValue(option.selected);
610         initiator_index = index;
611       }
612     });
613
614     // Members can only be at one group at a time, so when a member is selected
615     // in one group we need to disable its selection in other groups
616     _.forEach(this.groupMembersSelections, (group) => {
617       group[initiator_index].enabled = !option.selected;
618     });
619   }
620
621   removeGroupInitiator(group: CdFormGroup, member_index: number, group_index: number) {
622     const name = group.getValue('members')[member_index];
623     group.getValue('members').splice(member_index, 1);
624
625     this.groupMembersSelections[group_index].forEach((value) => {
626       if (value.name === name) {
627         value.selected = false;
628       }
629     });
630     this.groupMembersSelections[group_index] = [...this.groupMembersSelections[group_index]];
631
632     this.onGroupMemberSelection({ option: new SelectOption(false, name, '') });
633   }
634
635   removeGroupDisk(group: CdFormGroup, disk_index: number, group_index: number) {
636     const name = group.getValue('disks')[disk_index];
637     group.getValue('disks').splice(disk_index, 1);
638
639     this.groupDiskSelections[group_index].forEach((value) => {
640       if (value.name === name) {
641         value.selected = false;
642       }
643     });
644     this.groupDiskSelections[group_index] = [...this.groupDiskSelections[group_index]];
645   }
646
647   submit() {
648     const formValue = _.cloneDeep(this.targetForm.value);
649
650     const request: Record<string, any> = {
651       target_iqn: this.targetForm.getValue('target_iqn'),
652       target_controls: this.targetForm.getValue('target_controls'),
653       acl_enabled: this.targetForm.getValue('acl_enabled'),
654       portals: [],
655       disks: [],
656       clients: [],
657       groups: []
658     };
659
660     // Target level authentication was introduced in ceph-iscsi config v11
661     if (this.cephIscsiConfigVersion > 10) {
662       const targetAuth: CdFormGroup = this.targetForm.get('auth') as CdFormGroup;
663       if (!targetAuth.getValue('user')) {
664         targetAuth.get('user').setValue('');
665       }
666       if (!targetAuth.getValue('password')) {
667         targetAuth.get('password').setValue('');
668       }
669       if (!targetAuth.getValue('mutual_user')) {
670         targetAuth.get('mutual_user').setValue('');
671       }
672       if (!targetAuth.getValue('mutual_password')) {
673         targetAuth.get('mutual_password').setValue('');
674       }
675       const acl_enabled = this.targetForm.getValue('acl_enabled');
676       request['auth'] = {
677         user: acl_enabled ? '' : targetAuth.getValue('user'),
678         password: acl_enabled ? '' : targetAuth.getValue('password'),
679         mutual_user: acl_enabled ? '' : targetAuth.getValue('mutual_user'),
680         mutual_password: acl_enabled ? '' : targetAuth.getValue('mutual_password')
681       };
682     }
683
684     // Disks
685     formValue.disks.forEach((disk: string) => {
686       const imageSplit = disk.split('/');
687       const backstore = this.imagesSettings[disk].backstore;
688       request.disks.push({
689         pool: imageSplit[0],
690         image: imageSplit[1],
691         backstore: backstore,
692         controls: this.imagesSettings[disk][backstore],
693         lun: this.imagesSettings[disk]['lun'],
694         wwn: this.imagesSettings[disk]['wwn']
695       });
696     });
697
698     // Portals
699     formValue.portals.forEach((portal: string) => {
700       const index = portal.indexOf(':');
701       request.portals.push({
702         host: portal.substring(0, index),
703         ip: portal.substring(index + 1)
704       });
705     });
706
707     // Clients
708     if (request.acl_enabled) {
709       formValue.initiators.forEach((initiator: Record<string, any>) => {
710         if (!initiator.auth.user) {
711           initiator.auth.user = '';
712         }
713         if (!initiator.auth.password) {
714           initiator.auth.password = '';
715         }
716         if (!initiator.auth.mutual_user) {
717           initiator.auth.mutual_user = '';
718         }
719         if (!initiator.auth.mutual_password) {
720           initiator.auth.mutual_password = '';
721         }
722         delete initiator.cdIsInGroup;
723
724         const newLuns: any[] = [];
725         initiator.luns.forEach((lun: string) => {
726           const imageSplit = lun.split('/');
727           newLuns.push({
728             pool: imageSplit[0],
729             image: imageSplit[1]
730           });
731         });
732
733         initiator.luns = newLuns;
734       });
735       request.clients = formValue.initiators;
736     }
737
738     // Groups
739     if (request.acl_enabled) {
740       formValue.groups.forEach((group: Record<string, any>) => {
741         const newDisks: any[] = [];
742         group.disks.forEach((disk: string) => {
743           const imageSplit = disk.split('/');
744           newDisks.push({
745             pool: imageSplit[0],
746             image: imageSplit[1]
747           });
748         });
749
750         group.disks = newDisks;
751       });
752       request.groups = formValue.groups;
753     }
754
755     let wrapTask;
756     if (this.isEdit) {
757       request['new_target_iqn'] = request.target_iqn;
758       request.target_iqn = this.target_iqn;
759       wrapTask = this.taskWrapper.wrapTaskAroundCall({
760         task: new FinishedTask('iscsi/target/edit', {
761           target_iqn: request.target_iqn
762         }),
763         call: this.iscsiService.updateTarget(this.target_iqn, request)
764       });
765     } else {
766       wrapTask = this.taskWrapper.wrapTaskAroundCall({
767         task: new FinishedTask('iscsi/target/create', {
768           target_iqn: request.target_iqn
769         }),
770         call: this.iscsiService.createTarget(request)
771       });
772     }
773
774     wrapTask.subscribe({
775       error: () => {
776         this.targetForm.setErrors({ cdSubmitButton: true });
777       },
778       complete: () => this.router.navigate(['/block/iscsi/targets'])
779     });
780   }
781
782   targetSettingsModal() {
783     const initialState = {
784       target_controls: this.targetForm.get('target_controls'),
785       target_default_controls: this.target_default_controls,
786       target_controls_limits: this.target_controls_limits
787     };
788
789     this.modalRef = this.modalService.show(IscsiTargetIqnSettingsModalComponent, initialState);
790   }
791
792   imageSettingsModal(image: string) {
793     const initialState = {
794       imagesSettings: this.imagesSettings,
795       image: image,
796       api_version: this.api_version,
797       disk_default_controls: this.disk_default_controls,
798       disk_controls_limits: this.disk_controls_limits,
799       backstores: this.getValidBackstores(this.getImageById(image)),
800       control: this.targetForm.get('disks')
801     };
802
803     this.modalRef = this.modalService.show(IscsiTargetImageSettingsModalComponent, initialState);
804   }
805
806   validFeatures(image: Record<string, any>, backstore: string) {
807     const imageFeatures = image.features;
808     const requiredFeatures = this.required_rbd_features[backstore];
809     const unsupportedFeatures = this.unsupported_rbd_features[backstore];
810     // tslint:disable-next-line:no-bitwise
811     const validRequiredFeatures = (imageFeatures & requiredFeatures) === requiredFeatures;
812     // tslint:disable-next-line:no-bitwise
813     const validSupportedFeatures = (imageFeatures & unsupportedFeatures) === 0;
814     return validRequiredFeatures && validSupportedFeatures;
815   }
816
817   getImageById(imageId: string) {
818     return this.imagesAll.find((image) => imageId === `${image.pool_name}/${image.name}`);
819   }
820
821   getValidBackstores(image: object) {
822     return this.backstores.filter((backstore) => this.validFeatures(image, backstore));
823   }
824 }