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