summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts')
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts815
1 files changed, 815 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts
new file mode 100644
index 000000000..f6c6af579
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts
@@ -0,0 +1,815 @@
+import { Component, OnInit } from '@angular/core';
+import { FormControl, ValidatorFn, Validators } from '@angular/forms';
+import { ActivatedRoute, Router } from '@angular/router';
+
+import _ from 'lodash';
+import { forkJoin, Observable, ReplaySubject } from 'rxjs';
+import { first, switchMap } from 'rxjs/operators';
+
+import { Pool } from '~/app/ceph/pool/pool';
+import { PoolService } from '~/app/shared/api/pool.service';
+import { RbdMirroringService } from '~/app/shared/api/rbd-mirroring.service';
+import { RbdService } from '~/app/shared/api/rbd.service';
+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 {
+ RbdConfigurationEntry,
+ RbdConfigurationSourceField
+} from '~/app/shared/models/configuration';
+import { FinishedTask } from '~/app/shared/models/finished-task';
+import { ImageSpec } from '~/app/shared/models/image-spec';
+import { Permission } from '~/app/shared/models/permissions';
+import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
+import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
+import { FormatterService } from '~/app/shared/services/formatter.service';
+import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
+import { RBDImageFormat, RbdModel } from '../rbd-list/rbd-model';
+import { RbdImageFeature } from './rbd-feature.interface';
+import { RbdFormCloneRequestModel } from './rbd-form-clone-request.model';
+import { RbdFormCopyRequestModel } from './rbd-form-copy-request.model';
+import { RbdFormCreateRequestModel } from './rbd-form-create-request.model';
+import { RbdFormEditRequestModel } from './rbd-form-edit-request.model';
+import { RbdFormMode } from './rbd-form-mode.enum';
+import { RbdFormResponseModel } from './rbd-form-response.model';
+
+class ExternalData {
+ rbd: RbdFormResponseModel;
+ defaultFeatures: string[];
+ pools: Pool[];
+}
+
+@Component({
+ selector: 'cd-rbd-form',
+ templateUrl: './rbd-form.component.html',
+ styleUrls: ['./rbd-form.component.scss']
+})
+export class RbdFormComponent extends CdForm implements OnInit {
+ poolPermission: Permission;
+ rbdForm: CdFormGroup;
+ getDirtyConfigurationValues: (
+ includeLocalField?: boolean,
+ localField?: RbdConfigurationSourceField
+ ) => RbdConfigurationEntry[];
+
+ namespaces: Array<string> = [];
+ namespacesByPoolCache = {};
+ pools: Array<Pool> = null;
+ allPools: Array<Pool> = null;
+ dataPools: Array<Pool> = null;
+ allDataPools: Array<Pool> = [];
+ features: { [key: string]: RbdImageFeature };
+ featuresList: RbdImageFeature[] = [];
+ initializeConfigData = new ReplaySubject<{
+ initialData: RbdConfigurationEntry[];
+ sourceType: RbdConfigurationSourceField;
+ }>(1);
+
+ pool: string;
+
+ advancedEnabled = false;
+
+ public rbdFormMode = RbdFormMode;
+ mode: RbdFormMode;
+
+ response: RbdFormResponseModel;
+ snapName: string;
+
+ defaultObjectSize = '4 MiB';
+
+ mirroringOptions = ['journal', 'snapshot'];
+ poolMirrorMode: string;
+ mirroring = false;
+ currentPoolName = '';
+
+ objectSizes: Array<string> = [
+ '4 KiB',
+ '8 KiB',
+ '16 KiB',
+ '32 KiB',
+ '64 KiB',
+ '128 KiB',
+ '256 KiB',
+ '512 KiB',
+ '1 MiB',
+ '2 MiB',
+ '4 MiB',
+ '8 MiB',
+ '16 MiB',
+ '32 MiB'
+ ];
+
+ defaultStripingUnit = '4 MiB';
+
+ defaultStripingCount = 1;
+
+ action: string;
+ resource: string;
+ private rbdImage = new ReplaySubject(1);
+ private routerUrl: string;
+
+ icons = Icons;
+
+ constructor(
+ private authStorageService: AuthStorageService,
+ private route: ActivatedRoute,
+ private poolService: PoolService,
+ private rbdService: RbdService,
+ private formatter: FormatterService,
+ private taskWrapper: TaskWrapperService,
+ private dimlessBinaryPipe: DimlessBinaryPipe,
+ public actionLabels: ActionLabelsI18n,
+ private router: Router,
+ private rbdMirroringService: RbdMirroringService
+ ) {
+ super();
+ this.routerUrl = this.router.url;
+ this.poolPermission = this.authStorageService.getPermissions().pool;
+ this.resource = $localize`RBD`;
+ this.features = {
+ 'deep-flatten': {
+ desc: $localize`Deep flatten`,
+ requires: null,
+ allowEnable: false,
+ allowDisable: true
+ },
+ layering: {
+ desc: $localize`Layering`,
+ requires: null,
+ allowEnable: false,
+ allowDisable: false
+ },
+ 'exclusive-lock': {
+ desc: $localize`Exclusive lock`,
+ requires: null,
+ allowEnable: true,
+ allowDisable: true
+ },
+ 'object-map': {
+ desc: $localize`Object map (requires exclusive-lock)`,
+ requires: 'exclusive-lock',
+ allowEnable: true,
+ allowDisable: true,
+ initDisabled: true
+ },
+ 'fast-diff': {
+ desc: $localize`Fast diff (interlocked with object-map)`,
+ requires: 'object-map',
+ allowEnable: true,
+ allowDisable: true,
+ interlockedWith: 'object-map',
+ initDisabled: true
+ }
+ };
+ this.featuresList = this.objToArray(this.features);
+ this.createForm();
+ }
+
+ objToArray(obj: { [key: string]: any }) {
+ return _.map(obj, (o, key) => Object.assign(o, { key: key }));
+ }
+
+ createForm() {
+ this.rbdForm = new CdFormGroup(
+ {
+ parent: new FormControl(''),
+ name: new FormControl('', {
+ validators: [Validators.required, Validators.pattern(/^[^@/]+?$/)]
+ }),
+ pool: new FormControl(null, {
+ validators: [Validators.required]
+ }),
+ namespace: new FormControl(null),
+ useDataPool: new FormControl(false),
+ dataPool: new FormControl(null),
+ size: new FormControl(null, {
+ updateOn: 'blur'
+ }),
+ obj_size: new FormControl(this.defaultObjectSize),
+ features: new CdFormGroup(
+ this.featuresList.reduce((acc: object, e) => {
+ acc[e.key] = new FormControl({ value: false, disabled: !!e.initDisabled });
+ return acc;
+ }, {})
+ ),
+ mirroring: new FormControl(false),
+ schedule: new FormControl('', {
+ validators: [Validators.pattern(/^([0-9]+)d|([0-9]+)h|([0-9]+)m$/)] // check schedule interval to be in format - 1d or 1h or 1m
+ }),
+ mirroringMode: new FormControl(this.mirroringOptions[0]),
+ stripingUnit: new FormControl(this.defaultStripingUnit),
+ stripingCount: new FormControl(this.defaultStripingCount, {
+ updateOn: 'blur'
+ })
+ },
+ this.validateRbdForm(this.formatter)
+ );
+ }
+
+ disableForEdit() {
+ this.rbdForm.get('parent').disable();
+ this.rbdForm.get('pool').disable();
+ this.rbdForm.get('namespace').disable();
+ this.rbdForm.get('useDataPool').disable();
+ this.rbdForm.get('dataPool').disable();
+ this.rbdForm.get('obj_size').disable();
+ this.rbdForm.get('stripingUnit').disable();
+ this.rbdForm.get('stripingCount').disable();
+
+ /* RBD Image Format v1 */
+ this.rbdImage.subscribe((image: RbdModel) => {
+ if (image.image_format === RBDImageFormat.V1) {
+ this.rbdForm.get('deep-flatten').disable();
+ this.rbdForm.get('layering').disable();
+ this.rbdForm.get('exclusive-lock').disable();
+ }
+ });
+ }
+
+ disableForClone() {
+ this.rbdForm.get('parent').disable();
+ this.rbdForm.get('size').disable();
+ }
+
+ disableForCopy() {
+ this.rbdForm.get('parent').disable();
+ this.rbdForm.get('size').disable();
+ }
+
+ ngOnInit() {
+ this.prepareFormForAction();
+ this.gatherNeededData().subscribe(this.handleExternalData.bind(this));
+ }
+
+ setExclusiveLock() {
+ if (this.mirroring && this.rbdForm.get('mirroringMode').value === 'journal') {
+ this.rbdForm.get('exclusive-lock').setValue(true);
+ this.rbdForm.get('exclusive-lock').disable();
+ } else {
+ this.rbdForm.get('exclusive-lock').enable();
+ if (this.poolMirrorMode === 'pool') {
+ this.rbdForm.get('mirroringMode').setValue(this.mirroringOptions[0]);
+ }
+ }
+ }
+
+ setMirrorMode() {
+ this.mirroring = !this.mirroring;
+ this.setExclusiveLock();
+ }
+
+ setPoolMirrorMode() {
+ this.currentPoolName =
+ this.mode === this.rbdFormMode.editing
+ ? this.response?.pool_name
+ : this.rbdForm.getValue('pool');
+ if (this.currentPoolName) {
+ this.rbdMirroringService.refresh();
+ this.rbdMirroringService.subscribeSummary((data) => {
+ const pool = data.content_data.pools.find((o: any) => o.name === this.currentPoolName);
+ this.poolMirrorMode = pool.mirror_mode;
+
+ if (pool.mirror_mode === 'disabled') {
+ this.mirroring = false;
+ this.rbdForm.get('mirroring').setValue(this.mirroring);
+ this.rbdForm.get('mirroring').disable();
+ } else if (this.mode !== this.rbdFormMode.editing) {
+ this.rbdForm.get('mirroring').enable();
+ this.mirroring = true;
+ this.rbdForm.get('mirroring').setValue(this.mirroring);
+ }
+ });
+ }
+ this.setExclusiveLock();
+ }
+
+ private prepareFormForAction() {
+ const url = this.routerUrl;
+ if (url.startsWith('/block/rbd/edit')) {
+ this.mode = this.rbdFormMode.editing;
+ this.action = this.actionLabels.EDIT;
+ this.disableForEdit();
+ } else if (url.startsWith('/block/rbd/clone')) {
+ this.mode = this.rbdFormMode.cloning;
+ this.disableForClone();
+ this.action = this.actionLabels.CLONE;
+ } else if (url.startsWith('/block/rbd/copy')) {
+ this.mode = this.rbdFormMode.copying;
+ this.action = this.actionLabels.COPY;
+ this.disableForCopy();
+ } else {
+ this.action = this.actionLabels.CREATE;
+ }
+ _.each(this.features, (feature) => {
+ this.rbdForm
+ .get('features')
+ .get(feature.key)
+ .valueChanges.subscribe((value) => this.featureFormUpdate(feature.key, value));
+ });
+ }
+
+ private gatherNeededData(): Observable<object> {
+ const promises = {};
+ if (this.mode) {
+ // Mode is not set for creation
+ this.route.params.subscribe((params: { image_spec: string; snap: string }) => {
+ const imageSpec = ImageSpec.fromString(decodeURIComponent(params.image_spec));
+ if (params.snap) {
+ this.snapName = decodeURIComponent(params.snap);
+ }
+ promises['rbd'] = this.rbdService.get(imageSpec);
+ });
+ } else {
+ // New image
+ promises['defaultFeatures'] = this.rbdService.defaultFeatures();
+ }
+ if (this.mode !== this.rbdFormMode.editing && this.poolPermission.read) {
+ promises['pools'] = this.poolService.list([
+ 'pool_name',
+ 'type',
+ 'flags_names',
+ 'application_metadata'
+ ]);
+ }
+ return forkJoin(promises);
+ }
+
+ private handleExternalData(data: ExternalData) {
+ this.handlePoolData(data.pools);
+ this.setPoolMirrorMode();
+
+ if (data.defaultFeatures) {
+ // Fetched only during creation
+ this.setFeatures(data.defaultFeatures);
+ }
+
+ if (data.rbd) {
+ // Not fetched for creation
+ const resp = data.rbd;
+ this.setResponse(resp, this.snapName);
+ this.rbdImage.next(resp);
+ }
+
+ this.loadingReady();
+ }
+
+ private handlePoolData(data: Pool[]) {
+ if (!data) {
+ // Not fetched while editing
+ return;
+ }
+ const pools: Pool[] = [];
+ const dataPools = [];
+ for (const pool of data) {
+ if (this.rbdService.isRBDPool(pool)) {
+ if (pool.type === 'replicated') {
+ pools.push(pool);
+ dataPools.push(pool);
+ } else if (pool.type === 'erasure' && pool.flags_names.indexOf('ec_overwrites') !== -1) {
+ dataPools.push(pool);
+ }
+ }
+ }
+ this.pools = pools;
+ this.allPools = pools;
+ this.dataPools = dataPools;
+ this.allDataPools = dataPools;
+ if (this.pools.length === 1) {
+ const poolName = this.pools[0].pool_name;
+ this.rbdForm.get('pool').setValue(poolName);
+ this.onPoolChange(poolName);
+ }
+ if (this.allDataPools.length <= 1) {
+ this.rbdForm.get('useDataPool').disable();
+ }
+ }
+
+ onPoolChange(selectedPoolName: string) {
+ const dataPoolControl = this.rbdForm.get('dataPool');
+ if (dataPoolControl.value === selectedPoolName) {
+ dataPoolControl.setValue(null);
+ }
+ this.dataPools = this.allDataPools
+ ? this.allDataPools.filter((dataPool: any) => {
+ return dataPool.pool_name !== selectedPoolName;
+ })
+ : [];
+ this.namespaces = null;
+ if (selectedPoolName in this.namespacesByPoolCache) {
+ this.namespaces = this.namespacesByPoolCache[selectedPoolName];
+ } else {
+ this.rbdService.listNamespaces(selectedPoolName).subscribe((namespaces: any[]) => {
+ namespaces = namespaces.map((namespace) => namespace.namespace);
+ this.namespacesByPoolCache[selectedPoolName] = namespaces;
+ this.namespaces = namespaces;
+ });
+ }
+ this.rbdForm.get('namespace').setValue(null);
+ }
+
+ onUseDataPoolChange() {
+ if (!this.rbdForm.getValue('useDataPool')) {
+ this.rbdForm.get('dataPool').setValue(null);
+ this.onDataPoolChange(null);
+ }
+ }
+
+ onDataPoolChange(selectedDataPoolName: string) {
+ const newPools = this.allPools.filter((pool: Pool) => {
+ return pool.pool_name !== selectedDataPoolName;
+ });
+ if (this.rbdForm.getValue('pool') === selectedDataPoolName) {
+ this.rbdForm.get('pool').setValue(null);
+ }
+ this.pools = newPools;
+ }
+
+ validateRbdForm(formatter: FormatterService): ValidatorFn {
+ return (formGroup: CdFormGroup) => {
+ // Data Pool
+ const useDataPoolControl = formGroup.get('useDataPool');
+ const dataPoolControl = formGroup.get('dataPool');
+ let dataPoolControlErrors = null;
+ if (useDataPoolControl.value && dataPoolControl.value == null) {
+ dataPoolControlErrors = { required: true };
+ }
+ dataPoolControl.setErrors(dataPoolControlErrors);
+ // Size
+ const sizeControl = formGroup.get('size');
+ const objectSizeControl = formGroup.get('obj_size');
+ const objectSizeInBytes = formatter.toBytes(
+ objectSizeControl.value != null ? objectSizeControl.value : this.defaultObjectSize
+ );
+ const stripingCountControl = formGroup.get('stripingCount');
+ const stripingCount =
+ stripingCountControl.value != null ? stripingCountControl.value : this.defaultStripingCount;
+ let sizeControlErrors = null;
+ if (sizeControl.value === null) {
+ sizeControlErrors = { required: true };
+ } else {
+ const sizeInBytes = formatter.toBytes(sizeControl.value);
+ if (stripingCount * objectSizeInBytes > sizeInBytes) {
+ sizeControlErrors = { invalidSizeObject: true };
+ }
+ }
+ sizeControl.setErrors(sizeControlErrors);
+ // Striping Unit
+ const stripingUnitControl = formGroup.get('stripingUnit');
+ let stripingUnitControlErrors = null;
+ if (stripingUnitControl.value === null && stripingCountControl.value !== null) {
+ stripingUnitControlErrors = { required: true };
+ } else if (stripingUnitControl.value !== null) {
+ const stripingUnitInBytes = formatter.toBytes(stripingUnitControl.value);
+ if (stripingUnitInBytes > objectSizeInBytes) {
+ stripingUnitControlErrors = { invalidStripingUnit: true };
+ }
+ }
+ stripingUnitControl.setErrors(stripingUnitControlErrors);
+ // Striping Count
+ let stripingCountControlErrors = null;
+ if (stripingCountControl.value === null && stripingUnitControl.value !== null) {
+ stripingCountControlErrors = { required: true };
+ } else if (stripingCount < 1) {
+ stripingCountControlErrors = { min: true };
+ }
+ stripingCountControl.setErrors(stripingCountControlErrors);
+ return null;
+ };
+ }
+
+ deepBoxCheck(key: string, checked: boolean) {
+ const childFeatures = this.getDependentChildFeatures(key);
+ childFeatures.forEach((feature) => {
+ const featureControl = this.rbdForm.get(feature.key);
+ if (checked) {
+ featureControl.enable({ emitEvent: false });
+ } else {
+ featureControl.disable({ emitEvent: false });
+ featureControl.setValue(false, { emitEvent: false });
+ this.deepBoxCheck(feature.key, checked);
+ }
+
+ const featureFormGroup = this.rbdForm.get('features');
+ if (this.mode === this.rbdFormMode.editing && featureFormGroup.get(feature.key).enabled) {
+ if (this.response.features_name.indexOf(feature.key) !== -1 && !feature.allowDisable) {
+ featureFormGroup.get(feature.key).disable();
+ } else if (
+ this.response.features_name.indexOf(feature.key) === -1 &&
+ !feature.allowEnable
+ ) {
+ featureFormGroup.get(feature.key).disable();
+ }
+ }
+ });
+ }
+
+ protected getDependentChildFeatures(featureKey: string) {
+ return _.filter(this.features, (f) => f.requires === featureKey) || [];
+ }
+
+ interlockCheck(key: string, checked: boolean) {
+ // Adds a compatibility layer for Ceph cluster where the feature interlock of features hasn't
+ // been implemented yet. It disables the feature interlock for images which only have one of
+ // both interlocked features (at the time of this writing: object-map and fast-diff) enabled.
+ const feature = this.featuresList.find((f) => f.key === key);
+ if (this.response) {
+ // Ignore `create` page
+ const hasInterlockedFeature = feature.interlockedWith != null;
+ const dependentInterlockedFeature = this.featuresList.find(
+ (f) => f.interlockedWith === feature.key
+ );
+ const isOriginFeatureEnabled = !!this.response.features_name.find((e) => e === feature.key); // in this case: fast-diff
+ if (hasInterlockedFeature) {
+ const isLinkedEnabled = !!this.response.features_name.find(
+ (e) => e === feature.interlockedWith
+ ); // depends: object-map
+ if (isOriginFeatureEnabled !== isLinkedEnabled) {
+ return; // Ignore incompatible setting because it's from a previous cluster version
+ }
+ } else if (dependentInterlockedFeature) {
+ const isOtherInterlockedFeatureEnabled = !!this.response.features_name.find(
+ (e) => e === dependentInterlockedFeature.key
+ );
+ if (isOtherInterlockedFeatureEnabled !== isOriginFeatureEnabled) {
+ return; // Ignore incompatible setting because it's from a previous cluster version
+ }
+ }
+ }
+
+ if (checked) {
+ _.filter(this.features, (f) => f.interlockedWith === key).forEach((f) =>
+ this.rbdForm.get(f.key).setValue(true, { emitEvent: false })
+ );
+ } else {
+ if (feature.interlockedWith) {
+ // Don't skip emitting the event here, as it prevents `fast-diff` from
+ // becoming disabled when manually unchecked. This is because it
+ // triggers an update on `object-map` and if `object-map` doesn't emit,
+ // `fast-diff` will not be automatically disabled.
+ this.rbdForm.get('features').get(feature.interlockedWith).setValue(false);
+ }
+ }
+ }
+
+ featureFormUpdate(key: string, checked: boolean) {
+ if (checked) {
+ const required = this.features[key].requires;
+ if (required && !this.rbdForm.getValue(required)) {
+ this.rbdForm.get(`features.${key}`).setValue(false);
+ return;
+ }
+ }
+ this.deepBoxCheck(key, checked);
+ this.interlockCheck(key, checked);
+ }
+
+ setFeatures(features: Array<string>) {
+ const featuresControl = this.rbdForm.get('features');
+ _.forIn(this.features, (feature) => {
+ if (features.indexOf(feature.key) !== -1) {
+ featuresControl.get(feature.key).setValue(true);
+ }
+ this.featureFormUpdate(feature.key, featuresControl.get(feature.key).value);
+ });
+ }
+
+ setResponse(response: RbdFormResponseModel, snapName: string) {
+ this.response = response;
+ const imageSpec = new ImageSpec(
+ response.pool_name,
+ response.namespace,
+ response.name
+ ).toString();
+ if (this.mode === this.rbdFormMode.cloning) {
+ this.rbdForm.get('parent').setValue(`${imageSpec}@${snapName}`);
+ } else if (this.mode === this.rbdFormMode.copying) {
+ if (snapName) {
+ this.rbdForm.get('parent').setValue(`${imageSpec}@${snapName}`);
+ } else {
+ this.rbdForm.get('parent').setValue(`${imageSpec}`);
+ }
+ } else if (response.parent) {
+ const parent = response.parent;
+ this.rbdForm
+ .get('parent')
+ .setValue(`${parent.pool_name}/${parent.image_name}@${parent.snap_name}`);
+ }
+ if (this.mode === this.rbdFormMode.editing) {
+ this.rbdForm.get('name').setValue(response.name);
+ if (response?.mirror_mode === 'snapshot' || response.features_name.includes('journaling')) {
+ this.mirroring = true;
+ this.rbdForm.get('mirroring').setValue(this.mirroring);
+ this.rbdForm.get('mirroringMode').setValue(response?.mirror_mode);
+ this.rbdForm.get('schedule').setValue(response?.schedule_interval);
+ } else {
+ this.mirroring = false;
+ this.rbdForm.get('mirroring').setValue(this.mirroring);
+ }
+ this.setPoolMirrorMode();
+ }
+ this.rbdForm.get('pool').setValue(response.pool_name);
+ this.onPoolChange(response.pool_name);
+ this.rbdForm.get('namespace').setValue(response.namespace);
+ if (response.data_pool) {
+ this.rbdForm.get('useDataPool').setValue(true);
+ this.rbdForm.get('dataPool').setValue(response.data_pool);
+ }
+ this.rbdForm.get('size').setValue(this.dimlessBinaryPipe.transform(response.size));
+ this.rbdForm.get('obj_size').setValue(this.dimlessBinaryPipe.transform(response.obj_size));
+ this.setFeatures(response.features_name);
+ this.rbdForm
+ .get('stripingUnit')
+ .setValue(this.dimlessBinaryPipe.transform(response.stripe_unit));
+ this.rbdForm.get('stripingCount').setValue(response.stripe_count);
+ /* Configuration */
+ this.initializeConfigData.next({
+ initialData: this.response.configuration,
+ sourceType: RbdConfigurationSourceField.image
+ });
+ }
+
+ createRequest() {
+ const request = new RbdFormCreateRequestModel();
+ request.pool_name = this.rbdForm.getValue('pool');
+ request.namespace = this.rbdForm.getValue('namespace');
+ request.name = this.rbdForm.getValue('name');
+ request.schedule_interval = this.rbdForm.getValue('schedule');
+ request.size = this.formatter.toBytes(this.rbdForm.getValue('size'));
+ if (this.poolMirrorMode === 'image') {
+ request.mirror_mode = this.rbdForm.getValue('mirroringMode');
+ }
+ this.addObjectSizeAndStripingToRequest(request);
+ request.configuration = this.getDirtyConfigurationValues();
+ return request;
+ }
+
+ private addObjectSizeAndStripingToRequest(
+ request: RbdFormCreateRequestModel | RbdFormCloneRequestModel | RbdFormCopyRequestModel
+ ) {
+ request.obj_size = this.formatter.toBytes(this.rbdForm.getValue('obj_size'));
+ _.forIn(this.features, (feature) => {
+ if (this.rbdForm.getValue(feature.key)) {
+ request.features.push(feature.key);
+ }
+ });
+
+ if (this.mirroring && this.rbdForm.getValue('mirroringMode') === 'journal') {
+ request.features.push('journaling');
+ }
+
+ /* Striping */
+ request.stripe_unit = this.formatter.toBytes(this.rbdForm.getValue('stripingUnit'));
+ request.stripe_count = this.rbdForm.getValue('stripingCount');
+ request.data_pool = this.rbdForm.getValue('dataPool');
+ }
+
+ createAction(): Observable<any> {
+ const request = this.createRequest();
+ return this.taskWrapper.wrapTaskAroundCall({
+ task: new FinishedTask('rbd/create', {
+ pool_name: request.pool_name,
+ namespace: request.namespace,
+ image_name: request.name,
+ schedule_interval: request.schedule_interval,
+ start_time: request.start_time
+ }),
+ call: this.rbdService.create(request)
+ });
+ }
+
+ editRequest() {
+ const request = new RbdFormEditRequestModel();
+ request.name = this.rbdForm.getValue('name');
+ request.schedule_interval = this.rbdForm.getValue('schedule');
+ request.name = this.rbdForm.getValue('name');
+ request.size = this.formatter.toBytes(this.rbdForm.getValue('size'));
+ _.forIn(this.features, (feature) => {
+ if (this.rbdForm.getValue(feature.key)) {
+ request.features.push(feature.key);
+ }
+ });
+ request.enable_mirror = this.rbdForm.getValue('mirroring');
+ if (request.enable_mirror) {
+ if (this.rbdForm.getValue('mirroringMode') === 'journal') {
+ request.features.push('journaling');
+ }
+ if (this.poolMirrorMode === 'image') {
+ request.mirror_mode = this.rbdForm.getValue('mirroringMode');
+ }
+ } else {
+ const index = request.features.indexOf('journaling', 0);
+ if (index > -1) {
+ request.features.splice(index, 1);
+ }
+ }
+ request.configuration = this.getDirtyConfigurationValues();
+ return request;
+ }
+
+ cloneRequest(): RbdFormCloneRequestModel {
+ const request = new RbdFormCloneRequestModel();
+ request.child_pool_name = this.rbdForm.getValue('pool');
+ request.child_namespace = this.rbdForm.getValue('namespace');
+ request.child_image_name = this.rbdForm.getValue('name');
+ this.addObjectSizeAndStripingToRequest(request);
+ request.configuration = this.getDirtyConfigurationValues(
+ true,
+ RbdConfigurationSourceField.image
+ );
+ return request;
+ }
+
+ editAction(): Observable<any> {
+ const imageSpec = new ImageSpec(
+ this.response.pool_name,
+ this.response.namespace,
+ this.response.name
+ );
+ return this.taskWrapper.wrapTaskAroundCall({
+ task: new FinishedTask('rbd/edit', {
+ image_spec: imageSpec.toString()
+ }),
+ call: this.rbdService.update(imageSpec, this.editRequest())
+ });
+ }
+
+ cloneAction(): Observable<any> {
+ const request = this.cloneRequest();
+ const imageSpec = new ImageSpec(
+ this.response.pool_name,
+ this.response.namespace,
+ this.response.name
+ );
+ return this.taskWrapper.wrapTaskAroundCall({
+ task: new FinishedTask('rbd/clone', {
+ parent_image_spec: imageSpec.toString(),
+ parent_snap_name: this.snapName,
+ child_pool_name: request.child_pool_name,
+ child_namespace: request.child_namespace,
+ child_image_name: request.child_image_name
+ }),
+ call: this.rbdService.cloneSnapshot(imageSpec, this.snapName, request)
+ });
+ }
+
+ copyRequest(): RbdFormCopyRequestModel {
+ const request = new RbdFormCopyRequestModel();
+ if (this.snapName) {
+ request.snapshot_name = this.snapName;
+ }
+ request.dest_pool_name = this.rbdForm.getValue('pool');
+ request.dest_namespace = this.rbdForm.getValue('namespace');
+ request.dest_image_name = this.rbdForm.getValue('name');
+ this.addObjectSizeAndStripingToRequest(request);
+ request.configuration = this.getDirtyConfigurationValues(
+ true,
+ RbdConfigurationSourceField.image
+ );
+ return request;
+ }
+
+ copyAction(): Observable<any> {
+ const request = this.copyRequest();
+ const imageSpec = new ImageSpec(
+ this.response.pool_name,
+ this.response.namespace,
+ this.response.name
+ );
+ return this.taskWrapper.wrapTaskAroundCall({
+ task: new FinishedTask('rbd/copy', {
+ src_image_spec: imageSpec.toString(),
+ dest_pool_name: request.dest_pool_name,
+ dest_namespace: request.dest_namespace,
+ dest_image_name: request.dest_image_name
+ }),
+ call: this.rbdService.copy(imageSpec, request)
+ });
+ }
+
+ submit() {
+ if (!this.mode) {
+ this.rbdImage.next('create');
+ }
+ this.rbdImage
+ .pipe(
+ first(),
+ switchMap(() => {
+ if (this.mode === this.rbdFormMode.editing) {
+ return this.editAction();
+ } else if (this.mode === this.rbdFormMode.cloning) {
+ return this.cloneAction();
+ } else if (this.mode === this.rbdFormMode.copying) {
+ return this.copyAction();
+ } else {
+ return this.createAction();
+ }
+ })
+ )
+ .subscribe(
+ () => undefined,
+ () => this.rbdForm.setErrors({ cdSubmitButton: true }),
+ () => this.router.navigate(['/block/rbd'])
+ );
+ }
+}