summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts')
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts822
1 files changed, 822 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts
new file mode 100644
index 000000000..6704b41e6
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts
@@ -0,0 +1,822 @@
+import { Component, OnInit } from '@angular/core';
+import { FormArray, FormControl, Validators } from '@angular/forms';
+import { ActivatedRoute, Router } from '@angular/router';
+
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
+import _ from 'lodash';
+import { forkJoin } from 'rxjs';
+
+import { IscsiService } from '~/app/shared/api/iscsi.service';
+import { RbdService } from '~/app/shared/api/rbd.service';
+import { SelectMessages } from '~/app/shared/components/select/select-messages.model';
+import { SelectOption } from '~/app/shared/components/select/select-option.model';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { Icons } from '~/app/shared/enum/icons.enum';
+import { CdForm } from '~/app/shared/forms/cd-form';
+import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
+import { CdValidators } from '~/app/shared/forms/cd-validators';
+import { CdTableFetchDataContext } from '~/app/shared/models/cd-table-fetch-data-context';
+import { FinishedTask } from '~/app/shared/models/finished-task';
+import { ModalService } from '~/app/shared/services/modal.service';
+import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
+import { IscsiTargetImageSettingsModalComponent } from '../iscsi-target-image-settings-modal/iscsi-target-image-settings-modal.component';
+import { IscsiTargetIqnSettingsModalComponent } from '../iscsi-target-iqn-settings-modal/iscsi-target-iqn-settings-modal.component';
+
+@Component({
+ selector: 'cd-iscsi-target-form',
+ templateUrl: './iscsi-target-form.component.html',
+ styleUrls: ['./iscsi-target-form.component.scss']
+})
+export class IscsiTargetFormComponent extends CdForm implements OnInit {
+ cephIscsiConfigVersion: number;
+ targetForm: CdFormGroup;
+ modalRef: NgbModalRef;
+ api_version = 0;
+ minimum_gateways = 1;
+ target_default_controls: any;
+ target_controls_limits: any;
+ disk_default_controls: any;
+ disk_controls_limits: any;
+ backstores: string[];
+ default_backstore: string;
+ unsupported_rbd_features: any;
+ required_rbd_features: any;
+
+ icons = Icons;
+
+ isEdit = false;
+ target_iqn: string;
+
+ imagesAll: any[];
+ imagesSelections: SelectOption[];
+ portalsSelections: SelectOption[] = [];
+
+ imagesInitiatorSelections: SelectOption[][] = [];
+ groupDiskSelections: SelectOption[][] = [];
+ groupMembersSelections: SelectOption[][] = [];
+
+ imagesSettings: any = {};
+ messages = {
+ portals: new SelectMessages({ noOptions: $localize`There are no portals available.` }),
+ images: new SelectMessages({ noOptions: $localize`There are no images available.` }),
+ initiatorImage: new SelectMessages({
+ noOptions: $localize`There are no images available. Please make sure you add an image to the target.`
+ }),
+ groupInitiator: new SelectMessages({
+ noOptions: $localize`There are no initiators available. Please make sure you add an initiator to the target.`
+ })
+ };
+
+ IQN_REGEX = /^iqn\.(19|20)\d\d-(0[1-9]|1[0-2])\.\D{2,3}(\.[A-Za-z0-9-]+)+(:[A-Za-z0-9-\.]+)*$/;
+ USER_REGEX = /^[\w\.:@_-]{8,64}$/;
+ PASSWORD_REGEX = /^[\w@\-_\/]{12,16}$/;
+ action: string;
+ resource: string;
+
+ constructor(
+ private iscsiService: IscsiService,
+ private modalService: ModalService,
+ private rbdService: RbdService,
+ private router: Router,
+ private route: ActivatedRoute,
+ private taskWrapper: TaskWrapperService,
+ public actionLabels: ActionLabelsI18n
+ ) {
+ super();
+ this.resource = $localize`target`;
+ }
+
+ ngOnInit() {
+ const rbdListContext = new CdTableFetchDataContext(() => undefined);
+ /* limit -1 to specify all images */
+ rbdListContext.pageInfo.limit = -1;
+ const promises: any[] = [
+ this.iscsiService.listTargets(),
+ /* tslint:disable:no-empty */
+ this.rbdService.list(rbdListContext.toParams()),
+ this.iscsiService.portals(),
+ this.iscsiService.settings(),
+ this.iscsiService.version()
+ ];
+
+ if (this.router.url.startsWith('/block/iscsi/targets/edit')) {
+ this.isEdit = true;
+ this.route.params.subscribe((params: { target_iqn: string }) => {
+ this.target_iqn = decodeURIComponent(params.target_iqn);
+ promises.push(this.iscsiService.getTarget(this.target_iqn));
+ });
+ }
+ this.action = this.isEdit ? this.actionLabels.EDIT : this.actionLabels.CREATE;
+
+ forkJoin(promises).subscribe((data: any[]) => {
+ // iscsiService.listTargets
+ const usedImages = _(data[0])
+ .filter((target) => target.target_iqn !== this.target_iqn)
+ .flatMap((target) => target.disks)
+ .map((image) => `${image.pool}/${image.image}`)
+ .value();
+
+ // iscsiService.settings()
+ if ('api_version' in data[3]) {
+ this.api_version = data[3].api_version;
+ }
+ this.minimum_gateways = data[3].config.minimum_gateways;
+ this.target_default_controls = data[3].target_default_controls;
+ this.target_controls_limits = data[3].target_controls_limits;
+ this.disk_default_controls = data[3].disk_default_controls;
+ this.disk_controls_limits = data[3].disk_controls_limits;
+ this.backstores = data[3].backstores;
+ this.default_backstore = data[3].default_backstore;
+ this.unsupported_rbd_features = data[3].unsupported_rbd_features;
+ this.required_rbd_features = data[3].required_rbd_features;
+
+ // rbdService.list()
+ this.imagesAll = _(data[1])
+ .flatMap((pool) => pool.value)
+ .filter((image) => {
+ // Namespaces are not supported by ceph-iscsi
+ if (image.namespace) {
+ return false;
+ }
+ const imageId = `${image.pool_name}/${image.name}`;
+ if (usedImages.indexOf(imageId) !== -1) {
+ return false;
+ }
+ const validBackstores = this.getValidBackstores(image);
+ if (validBackstores.length === 0) {
+ return false;
+ }
+ return true;
+ })
+ .value();
+
+ this.imagesSelections = this.imagesAll.map(
+ (image) => new SelectOption(false, `${image.pool_name}/${image.name}`, '')
+ );
+
+ // iscsiService.portals()
+ const portals: SelectOption[] = [];
+ data[2].forEach((portal: Record<string, any>) => {
+ portal.ip_addresses.forEach((ip: string) => {
+ portals.push(new SelectOption(false, portal.name + ':' + ip, ''));
+ });
+ });
+ this.portalsSelections = [...portals];
+
+ // iscsiService.version()
+ this.cephIscsiConfigVersion = data[4]['ceph_iscsi_config_version'];
+
+ this.createForm();
+
+ // iscsiService.getTarget()
+ if (data[5]) {
+ this.resolveModel(data[5]);
+ }
+
+ this.loadingReady();
+ });
+ }
+
+ createForm() {
+ this.targetForm = new CdFormGroup({
+ target_iqn: new FormControl('iqn.2001-07.com.ceph:' + Date.now(), {
+ validators: [Validators.required, Validators.pattern(this.IQN_REGEX)]
+ }),
+ target_controls: new FormControl({}),
+ portals: new FormControl([], {
+ validators: [
+ CdValidators.custom('minGateways', (value: any[]) => {
+ const gateways = _.uniq(value.map((elem) => elem.split(':')[0]));
+ return gateways.length < Math.max(1, this.minimum_gateways);
+ })
+ ]
+ }),
+ disks: new FormControl([], {
+ validators: [
+ CdValidators.custom('dupLunId', (value: any[]) => {
+ const lunIds = this.getLunIds(value);
+ return lunIds.length !== _.uniq(lunIds).length;
+ }),
+ CdValidators.custom('dupWwn', (value: any[]) => {
+ const wwns = this.getWwns(value);
+ return wwns.length !== _.uniq(wwns).length;
+ })
+ ]
+ }),
+ initiators: new FormArray([]),
+ groups: new FormArray([]),
+ acl_enabled: new FormControl(false)
+ });
+ // Target level authentication was introduced in ceph-iscsi config v11
+ if (this.cephIscsiConfigVersion > 10) {
+ const authFormGroup = new CdFormGroup({
+ user: new FormControl(''),
+ password: new FormControl(''),
+ mutual_user: new FormControl(''),
+ mutual_password: new FormControl('')
+ });
+ this.setAuthValidator(authFormGroup);
+ this.targetForm.addControl('auth', authFormGroup);
+ }
+ }
+
+ resolveModel(res: Record<string, any>) {
+ this.targetForm.patchValue({
+ target_iqn: res.target_iqn,
+ target_controls: res.target_controls,
+ acl_enabled: res.acl_enabled
+ });
+ // Target level authentication was introduced in ceph-iscsi config v11
+ if (this.cephIscsiConfigVersion > 10) {
+ this.targetForm.patchValue({
+ auth: res.auth
+ });
+ }
+ const portals: any[] = [];
+ _.forEach(res.portals, (portal) => {
+ const id = `${portal.host}:${portal.ip}`;
+ portals.push(id);
+ });
+ this.targetForm.patchValue({
+ portals: portals
+ });
+
+ const disks: any[] = [];
+ _.forEach(res.disks, (disk) => {
+ const id = `${disk.pool}/${disk.image}`;
+ disks.push(id);
+ this.imagesSettings[id] = {
+ backstore: disk.backstore
+ };
+ this.imagesSettings[id][disk.backstore] = disk.controls;
+ if ('lun' in disk) {
+ this.imagesSettings[id]['lun'] = disk.lun;
+ }
+ if ('wwn' in disk) {
+ this.imagesSettings[id]['wwn'] = disk.wwn;
+ }
+
+ this.onImageSelection({ option: { name: id, selected: true } });
+ });
+ this.targetForm.patchValue({
+ disks: disks
+ });
+
+ _.forEach(res.clients, (client) => {
+ const initiator = this.addInitiator();
+ client.luns = _.map(client.luns, (lun) => `${lun.pool}/${lun.image}`);
+ initiator.patchValue(client);
+ // updatedInitiatorSelector()
+ });
+
+ (res.groups as any[]).forEach((group: any, group_index: number) => {
+ const fg = this.addGroup();
+ group.disks = _.map(group.disks, (disk) => `${disk.pool}/${disk.image}`);
+ fg.patchValue(group);
+ _.forEach(group.members, (member) => {
+ this.onGroupMemberSelection({ option: new SelectOption(true, member, '') }, group_index);
+ });
+ });
+ }
+
+ hasAdvancedSettings(settings: any) {
+ return Object.values(settings).length > 0;
+ }
+
+ // Portals
+ get portals() {
+ return this.targetForm.get('portals') as FormControl;
+ }
+
+ onPortalSelection() {
+ this.portals.setValue(this.portals.value);
+ }
+
+ removePortal(index: number, portal: string) {
+ this.portalsSelections.forEach((value) => {
+ if (value.name === portal) {
+ value.selected = false;
+ }
+ });
+
+ this.portals.value.splice(index, 1);
+ this.portals.setValue(this.portals.value);
+ return false;
+ }
+
+ // Images
+ get disks() {
+ return this.targetForm.get('disks') as FormControl;
+ }
+
+ removeImage(index: number, image: string) {
+ this.imagesSelections.forEach((value) => {
+ if (value.name === image) {
+ value.selected = false;
+ }
+ });
+ this.disks.value.splice(index, 1);
+ this.removeImageRefs(image);
+ this.targetForm.get('disks').updateValueAndValidity({ emitEvent: false });
+ return false;
+ }
+
+ removeImageRefs(name: string) {
+ this.initiators.controls.forEach((element) => {
+ const newImages = element.value.luns.filter((item: string) => item !== name);
+ element.get('luns').setValue(newImages);
+ });
+
+ this.groups.controls.forEach((element) => {
+ const newDisks = element.value.disks.filter((item: string) => item !== name);
+ element.get('disks').setValue(newDisks);
+ });
+
+ _.forEach(this.imagesInitiatorSelections, (selections, i) => {
+ this.imagesInitiatorSelections[i] = selections.filter((item: any) => item.name !== name);
+ });
+ _.forEach(this.groupDiskSelections, (selections, i) => {
+ this.groupDiskSelections[i] = selections.filter((item: any) => item.name !== name);
+ });
+ }
+
+ getDefaultBackstore(imageId: string) {
+ let result = this.default_backstore;
+ const image = this.getImageById(imageId);
+ if (!this.validFeatures(image, this.default_backstore)) {
+ this.backstores.forEach((backstore) => {
+ if (backstore !== this.default_backstore) {
+ if (this.validFeatures(image, backstore)) {
+ result = backstore;
+ }
+ }
+ });
+ }
+ return result;
+ }
+
+ isLunIdInUse(lunId: string, imageId: string) {
+ const images = this.disks.value.filter((currentImageId: string) => currentImageId !== imageId);
+ return this.getLunIds(images).includes(lunId);
+ }
+
+ getLunIds(images: object) {
+ return _.map(images, (image) => this.imagesSettings[image]['lun']);
+ }
+
+ nextLunId(imageId: string) {
+ const images = this.disks.value.filter((currentImageId: string) => currentImageId !== imageId);
+ const lunIdsInUse = this.getLunIds(images);
+ let lunIdCandidate = 0;
+ while (lunIdsInUse.includes(lunIdCandidate)) {
+ lunIdCandidate++;
+ }
+ return lunIdCandidate;
+ }
+
+ getWwns(images: object) {
+ const wwns = _.map(images, (image) => this.imagesSettings[image]['wwn']);
+ return wwns.filter((wwn) => _.isString(wwn) && wwn !== '');
+ }
+
+ onImageSelection($event: any) {
+ const option = $event.option;
+
+ if (option.selected) {
+ if (!this.imagesSettings[option.name]) {
+ const defaultBackstore = this.getDefaultBackstore(option.name);
+ this.imagesSettings[option.name] = {
+ backstore: defaultBackstore,
+ lun: this.nextLunId(option.name)
+ };
+ this.imagesSettings[option.name][defaultBackstore] = {};
+ } else if (this.isLunIdInUse(this.imagesSettings[option.name]['lun'], option.name)) {
+ // If the lun id is now in use, we have to generate a new one
+ this.imagesSettings[option.name]['lun'] = this.nextLunId(option.name);
+ }
+
+ _.forEach(this.imagesInitiatorSelections, (selections, i) => {
+ selections.push(new SelectOption(false, option.name, ''));
+ this.imagesInitiatorSelections[i] = [...selections];
+ });
+
+ _.forEach(this.groupDiskSelections, (selections, i) => {
+ selections.push(new SelectOption(false, option.name, ''));
+ this.groupDiskSelections[i] = [...selections];
+ });
+ } else {
+ this.removeImageRefs(option.name);
+ }
+ this.targetForm.get('disks').updateValueAndValidity({ emitEvent: false });
+ }
+
+ // Initiators
+ get initiators() {
+ return this.targetForm.get('initiators') as FormArray;
+ }
+
+ addInitiator() {
+ const fg = new CdFormGroup({
+ client_iqn: new FormControl('', {
+ validators: [
+ Validators.required,
+ CdValidators.custom('notUnique', (client_iqn: string) => {
+ const flattened = this.initiators.controls.reduce(function (accumulator, currentValue) {
+ return accumulator.concat(currentValue.value.client_iqn);
+ }, []);
+
+ return flattened.indexOf(client_iqn) !== flattened.lastIndexOf(client_iqn);
+ }),
+ Validators.pattern(this.IQN_REGEX)
+ ]
+ }),
+ auth: new CdFormGroup({
+ user: new FormControl(''),
+ password: new FormControl(''),
+ mutual_user: new FormControl(''),
+ mutual_password: new FormControl('')
+ }),
+ luns: new FormControl([]),
+ cdIsInGroup: new FormControl(false)
+ });
+
+ this.setAuthValidator(fg);
+
+ this.initiators.push(fg);
+
+ _.forEach(this.groupMembersSelections, (selections, i) => {
+ selections.push(new SelectOption(false, '', ''));
+ this.groupMembersSelections[i] = [...selections];
+ });
+
+ const disks = _.map(
+ this.targetForm.getValue('disks'),
+ (disk) => new SelectOption(false, disk, '')
+ );
+ this.imagesInitiatorSelections.push(disks);
+
+ return fg;
+ }
+
+ setAuthValidator(fg: CdFormGroup) {
+ CdValidators.validateIf(
+ fg.get('user'),
+ () => fg.getValue('password') || fg.getValue('mutual_user') || fg.getValue('mutual_password'),
+ [Validators.required],
+ [Validators.pattern(this.USER_REGEX)],
+ [fg.get('password'), fg.get('mutual_user'), fg.get('mutual_password')]
+ );
+
+ CdValidators.validateIf(
+ fg.get('password'),
+ () => fg.getValue('user') || fg.getValue('mutual_user') || fg.getValue('mutual_password'),
+ [Validators.required],
+ [Validators.pattern(this.PASSWORD_REGEX)],
+ [fg.get('user'), fg.get('mutual_user'), fg.get('mutual_password')]
+ );
+
+ CdValidators.validateIf(
+ fg.get('mutual_user'),
+ () => fg.getValue('mutual_password'),
+ [Validators.required],
+ [Validators.pattern(this.USER_REGEX)],
+ [fg.get('user'), fg.get('password'), fg.get('mutual_password')]
+ );
+
+ CdValidators.validateIf(
+ fg.get('mutual_password'),
+ () => fg.getValue('mutual_user'),
+ [Validators.required],
+ [Validators.pattern(this.PASSWORD_REGEX)],
+ [fg.get('user'), fg.get('password'), fg.get('mutual_user')]
+ );
+ }
+
+ removeInitiator(index: number) {
+ const removed = this.initiators.value[index];
+
+ this.initiators.removeAt(index);
+
+ _.forEach(this.groupMembersSelections, (selections, i) => {
+ selections.splice(index, 1);
+ this.groupMembersSelections[i] = [...selections];
+ });
+
+ this.groups.controls.forEach((element) => {
+ const newMembers = element.value.members.filter(
+ (item: string) => item !== removed.client_iqn
+ );
+ element.get('members').setValue(newMembers);
+ });
+
+ this.imagesInitiatorSelections.splice(index, 1);
+ }
+
+ updatedInitiatorSelector() {
+ // Validate all client_iqn
+ this.initiators.controls.forEach((control) => {
+ control.get('client_iqn').updateValueAndValidity({ emitEvent: false });
+ });
+
+ // Update Group Initiator Selector
+ _.forEach(this.groupMembersSelections, (group, group_index) => {
+ _.forEach(group, (elem, index) => {
+ const oldName = elem.name;
+ elem.name = this.initiators.controls[index].value.client_iqn;
+
+ this.groups.controls.forEach((element) => {
+ const members = element.value.members;
+ const i = members.indexOf(oldName);
+
+ if (i !== -1) {
+ members[i] = elem.name;
+ }
+ element.get('members').setValue(members);
+ });
+ });
+ this.groupMembersSelections[group_index] = [...this.groupMembersSelections[group_index]];
+ });
+ }
+
+ removeInitiatorImage(initiator: any, lun_index: number, initiator_index: number, image: string) {
+ const luns = initiator.getValue('luns');
+ luns.splice(lun_index, 1);
+ initiator.patchValue({ luns: luns });
+
+ this.imagesInitiatorSelections[initiator_index].forEach((value: Record<string, any>) => {
+ if (value.name === image) {
+ value.selected = false;
+ }
+ });
+
+ return false;
+ }
+
+ // Groups
+ get groups() {
+ return this.targetForm.get('groups') as FormArray;
+ }
+
+ addGroup() {
+ const fg = new CdFormGroup({
+ group_id: new FormControl('', { validators: [Validators.required] }),
+ members: new FormControl([]),
+ disks: new FormControl([])
+ });
+
+ this.groups.push(fg);
+
+ const disks = _.map(
+ this.targetForm.getValue('disks'),
+ (disk) => new SelectOption(false, disk, '')
+ );
+ this.groupDiskSelections.push(disks);
+
+ const initiators = _.map(
+ this.initiators.value,
+ (initiator) => new SelectOption(false, initiator.client_iqn, '', !initiator.cdIsInGroup)
+ );
+ this.groupMembersSelections.push(initiators);
+
+ return fg;
+ }
+
+ removeGroup(index: number) {
+ // Remove group and disk selections
+ this.groups.removeAt(index);
+
+ // Free initiator from group
+ const selectedMembers = this.groupMembersSelections[index].filter((value) => value.selected);
+ selectedMembers.forEach((selection) => {
+ selection.selected = false;
+ this.onGroupMemberSelection({ option: selection }, index);
+ });
+
+ this.groupMembersSelections.splice(index, 1);
+ this.groupDiskSelections.splice(index, 1);
+ }
+
+ onGroupMemberSelection($event: any, group_index: number) {
+ const option = $event.option;
+
+ let luns: string[] = [];
+ if (!option.selected) {
+ const selectedDisks = this.groupDiskSelections[group_index].filter((value) => value.selected);
+ luns = selectedDisks.map((value) => value.name);
+ }
+
+ this.initiators.controls.forEach((element, index) => {
+ if (element.value.client_iqn === option.name) {
+ element.patchValue({ luns: luns });
+ element.get('cdIsInGroup').setValue(option.selected);
+
+ // Members can only be at one group at a time, so when a member is selected
+ // in one group we need to disable its selection in other groups
+ _.forEach(this.groupMembersSelections, (group) => {
+ group[index].enabled = !option.selected;
+ });
+
+ this.imagesInitiatorSelections[index].forEach((image) => {
+ image.selected = luns.includes(image.name);
+ });
+ }
+ });
+ }
+
+ removeGroupInitiator(group: CdFormGroup, member_index: number, group_index: number) {
+ const name = group.getValue('members')[member_index];
+ group.getValue('members').splice(member_index, 1);
+
+ this.onGroupMemberSelection({ option: new SelectOption(false, name, '') }, group_index);
+ }
+
+ removeGroupDisk(group: CdFormGroup, disk_index: number, group_index: number) {
+ const name = group.getValue('disks')[disk_index];
+ group.getValue('disks').splice(disk_index, 1);
+
+ this.groupDiskSelections[group_index].forEach((value) => {
+ if (value.name === name) {
+ value.selected = false;
+ }
+ });
+ this.groupDiskSelections[group_index] = [...this.groupDiskSelections[group_index]];
+ }
+
+ submit() {
+ const formValue = _.cloneDeep(this.targetForm.value);
+
+ const request: Record<string, any> = {
+ target_iqn: this.targetForm.getValue('target_iqn'),
+ target_controls: this.targetForm.getValue('target_controls'),
+ acl_enabled: this.targetForm.getValue('acl_enabled'),
+ portals: [],
+ disks: [],
+ clients: [],
+ groups: []
+ };
+
+ // Target level authentication was introduced in ceph-iscsi config v11
+ if (this.cephIscsiConfigVersion > 10) {
+ const targetAuth: CdFormGroup = this.targetForm.get('auth') as CdFormGroup;
+ if (!targetAuth.getValue('user')) {
+ targetAuth.get('user').setValue('');
+ }
+ if (!targetAuth.getValue('password')) {
+ targetAuth.get('password').setValue('');
+ }
+ if (!targetAuth.getValue('mutual_user')) {
+ targetAuth.get('mutual_user').setValue('');
+ }
+ if (!targetAuth.getValue('mutual_password')) {
+ targetAuth.get('mutual_password').setValue('');
+ }
+ const acl_enabled = this.targetForm.getValue('acl_enabled');
+ request['auth'] = {
+ user: acl_enabled ? '' : targetAuth.getValue('user'),
+ password: acl_enabled ? '' : targetAuth.getValue('password'),
+ mutual_user: acl_enabled ? '' : targetAuth.getValue('mutual_user'),
+ mutual_password: acl_enabled ? '' : targetAuth.getValue('mutual_password')
+ };
+ }
+
+ // Disks
+ formValue.disks.forEach((disk: string) => {
+ const imageSplit = disk.split('/');
+ const backstore = this.imagesSettings[disk].backstore;
+ request.disks.push({
+ pool: imageSplit[0],
+ image: imageSplit[1],
+ backstore: backstore,
+ controls: this.imagesSettings[disk][backstore],
+ lun: this.imagesSettings[disk]['lun'],
+ wwn: this.imagesSettings[disk]['wwn']
+ });
+ });
+
+ // Portals
+ formValue.portals.forEach((portal: string) => {
+ const index = portal.indexOf(':');
+ request.portals.push({
+ host: portal.substring(0, index),
+ ip: portal.substring(index + 1)
+ });
+ });
+
+ // Clients
+ if (request.acl_enabled) {
+ formValue.initiators.forEach((initiator: Record<string, any>) => {
+ if (!initiator.auth.user) {
+ initiator.auth.user = '';
+ }
+ if (!initiator.auth.password) {
+ initiator.auth.password = '';
+ }
+ if (!initiator.auth.mutual_user) {
+ initiator.auth.mutual_user = '';
+ }
+ if (!initiator.auth.mutual_password) {
+ initiator.auth.mutual_password = '';
+ }
+ delete initiator.cdIsInGroup;
+
+ const newLuns: any[] = [];
+ initiator.luns.forEach((lun: string) => {
+ const imageSplit = lun.split('/');
+ newLuns.push({
+ pool: imageSplit[0],
+ image: imageSplit[1]
+ });
+ });
+
+ initiator.luns = newLuns;
+ });
+ request.clients = formValue.initiators;
+ }
+
+ // Groups
+ if (request.acl_enabled) {
+ formValue.groups.forEach((group: Record<string, any>) => {
+ const newDisks: any[] = [];
+ group.disks.forEach((disk: string) => {
+ const imageSplit = disk.split('/');
+ newDisks.push({
+ pool: imageSplit[0],
+ image: imageSplit[1]
+ });
+ });
+
+ group.disks = newDisks;
+ });
+ request.groups = formValue.groups;
+ }
+
+ let wrapTask;
+ if (this.isEdit) {
+ request['new_target_iqn'] = request.target_iqn;
+ request.target_iqn = this.target_iqn;
+ wrapTask = this.taskWrapper.wrapTaskAroundCall({
+ task: new FinishedTask('iscsi/target/edit', {
+ target_iqn: request.target_iqn
+ }),
+ call: this.iscsiService.updateTarget(this.target_iqn, request)
+ });
+ } else {
+ wrapTask = this.taskWrapper.wrapTaskAroundCall({
+ task: new FinishedTask('iscsi/target/create', {
+ target_iqn: request.target_iqn
+ }),
+ call: this.iscsiService.createTarget(request)
+ });
+ }
+
+ wrapTask.subscribe({
+ error: () => {
+ this.targetForm.setErrors({ cdSubmitButton: true });
+ },
+ complete: () => this.router.navigate(['/block/iscsi/targets'])
+ });
+ }
+
+ targetSettingsModal() {
+ const initialState = {
+ target_controls: this.targetForm.get('target_controls'),
+ target_default_controls: this.target_default_controls,
+ target_controls_limits: this.target_controls_limits
+ };
+
+ this.modalRef = this.modalService.show(IscsiTargetIqnSettingsModalComponent, initialState);
+ }
+
+ imageSettingsModal(image: string) {
+ const initialState = {
+ imagesSettings: this.imagesSettings,
+ image: image,
+ api_version: this.api_version,
+ disk_default_controls: this.disk_default_controls,
+ disk_controls_limits: this.disk_controls_limits,
+ backstores: this.getValidBackstores(this.getImageById(image)),
+ control: this.targetForm.get('disks')
+ };
+
+ this.modalRef = this.modalService.show(IscsiTargetImageSettingsModalComponent, initialState);
+ }
+
+ validFeatures(image: Record<string, any>, backstore: string) {
+ const imageFeatures = image.features;
+ const requiredFeatures = this.required_rbd_features[backstore];
+ const unsupportedFeatures = this.unsupported_rbd_features[backstore];
+ // tslint:disable-next-line:no-bitwise
+ const validRequiredFeatures = (imageFeatures & requiredFeatures) === requiredFeatures;
+ // tslint:disable-next-line:no-bitwise
+ const validSupportedFeatures = (imageFeatures & unsupportedFeatures) === 0;
+ return validRequiredFeatures && validSupportedFeatures;
+ }
+
+ getImageById(imageId: string) {
+ return this.imagesAll.find((image) => imageId === `${image.pool_name}/${image.name}`);
+ }
+
+ getValidBackstores(image: object) {
+ return this.backstores.filter((backstore) => this.validFeatures(image, backstore));
+ }
+}