]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts
import 15.2.4
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / block / iscsi-target-form / iscsi-target-form.component.ts
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 }