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