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