1 import { Component, OnInit } from '@angular/core';
2 import { UntypedFormControl, ValidatorFn, Validators } from '@angular/forms';
3 import { ActivatedRoute, Router } from '@angular/router';
5 import _ from 'lodash';
6 import { forkJoin, Observable, ReplaySubject } from 'rxjs';
7 import { first, switchMap } from 'rxjs/operators';
9 import { Pool } from '~/app/ceph/pool/pool';
10 import { PoolService } from '~/app/shared/api/pool.service';
11 import { RbdMirroringService } from '~/app/shared/api/rbd-mirroring.service';
12 import { RbdService } from '~/app/shared/api/rbd.service';
13 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
14 import { Icons } from '~/app/shared/enum/icons.enum';
15 import { CdForm } from '~/app/shared/forms/cd-form';
16 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
18 RbdConfigurationEntry,
19 RbdConfigurationSourceField
20 } from '~/app/shared/models/configuration';
21 import { FinishedTask } from '~/app/shared/models/finished-task';
22 import { ImageSpec } from '~/app/shared/models/image-spec';
23 import { Permission } from '~/app/shared/models/permissions';
24 import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
25 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
26 import { FormatterService } from '~/app/shared/services/formatter.service';
27 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
28 import { RBDImageFormat, RbdModel } from '../rbd-list/rbd-model';
29 import { RbdImageFeature } from './rbd-feature.interface';
30 import { RbdFormCloneRequestModel } from './rbd-form-clone-request.model';
31 import { RbdFormCopyRequestModel } from './rbd-form-copy-request.model';
32 import { RbdFormCreateRequestModel } from './rbd-form-create-request.model';
33 import { RbdFormEditRequestModel } from './rbd-form-edit-request.model';
34 import { RbdFormMode } from './rbd-form-mode.enum';
35 import { RbdFormResponseModel } from './rbd-form-response.model';
38 rbd: RbdFormResponseModel;
39 defaultFeatures: string[];
44 selector: 'cd-rbd-form',
45 templateUrl: './rbd-form.component.html',
46 styleUrls: ['./rbd-form.component.scss']
48 export class RbdFormComponent extends CdForm implements OnInit {
49 poolPermission: Permission;
51 getDirtyConfigurationValues: (
52 includeLocalField?: boolean,
53 localField?: RbdConfigurationSourceField
54 ) => RbdConfigurationEntry[];
56 namespaces: Array<string> = [];
57 namespacesByPoolCache = {};
58 pools: Array<Pool> = null;
59 allPools: Array<Pool> = null;
60 dataPools: Array<Pool> = null;
61 allDataPools: Array<Pool> = [];
62 features: { [key: string]: RbdImageFeature };
63 featuresList: RbdImageFeature[] = [];
64 initializeConfigData = new ReplaySubject<{
65 initialData: RbdConfigurationEntry[];
66 sourceType: RbdConfigurationSourceField;
70 peerConfigured = false;
72 advancedEnabled = false;
74 public rbdFormMode = RbdFormMode;
77 response: RbdFormResponseModel;
80 defaultObjectSize = '4 MiB';
82 mirroringOptions = ['journal', 'snapshot'];
83 poolMirrorMode: string;
87 objectSizes: Array<string> = [
104 defaultStripingUnit = '4 MiB';
106 defaultStripingCount = 1;
110 private rbdImage = new ReplaySubject(1);
111 private routerUrl: string;
116 private authStorageService: AuthStorageService,
117 private route: ActivatedRoute,
118 private poolService: PoolService,
119 private rbdService: RbdService,
120 private formatter: FormatterService,
121 private taskWrapper: TaskWrapperService,
122 private dimlessBinaryPipe: DimlessBinaryPipe,
123 public actionLabels: ActionLabelsI18n,
124 private router: Router,
125 private rbdMirroringService: RbdMirroringService
128 this.routerUrl = this.router.url;
129 this.poolPermission = this.authStorageService.getPermissions().pool;
130 this.resource = $localize`RBD`;
133 desc: $localize`Deep flatten`,
137 helperHtml: $localize`Feature can be disabled but can't be re-enabled later`
140 desc: $localize`Layering`,
144 helperHtml: $localize`Feature flag can't be manipulated after the image is created. Disabling this option will also disable the Protect and Clone actions on Snapshot`
147 desc: $localize`Exclusive lock`,
153 desc: $localize`Object map (requires exclusive-lock)`,
154 requires: 'exclusive-lock',
160 desc: $localize`Fast diff (interlocked with object-map)`,
161 requires: 'object-map',
164 interlockedWith: 'object-map',
168 this.featuresList = this.objToArray(this.features);
172 objToArray(obj: { [key: string]: any }) {
173 return _.map(obj, (o, key) => Object.assign(o, { key: key }));
177 this.rbdForm = new CdFormGroup(
179 parent: new UntypedFormControl(''),
180 name: new UntypedFormControl('', {
181 validators: [Validators.required, Validators.pattern(/^[^@/]+?$/)]
183 pool: new UntypedFormControl(null, {
184 validators: [Validators.required]
186 namespace: new UntypedFormControl(null),
187 useDataPool: new UntypedFormControl(false),
188 dataPool: new UntypedFormControl(null),
189 size: new UntypedFormControl(null, {
192 obj_size: new UntypedFormControl(this.defaultObjectSize),
193 features: new CdFormGroup(
194 this.featuresList.reduce((acc: object, e) => {
195 acc[e.key] = new UntypedFormControl({ value: false, disabled: !!e.initDisabled });
199 mirroring: new UntypedFormControl(''),
200 schedule: new UntypedFormControl('', {
201 validators: [Validators.pattern(/^([0-9]+)d|([0-9]+)h|([0-9]+)m$/)] // check schedule interval to be in format - 1d or 1h or 1m
203 mirroringMode: new UntypedFormControl(''),
204 stripingUnit: new UntypedFormControl(this.defaultStripingUnit),
205 stripingCount: new UntypedFormControl(this.defaultStripingCount, {
209 this.validateRbdForm(this.formatter)
214 this.rbdForm.get('parent').disable();
215 this.rbdForm.get('pool').disable();
216 this.rbdForm.get('namespace').disable();
217 this.rbdForm.get('useDataPool').disable();
218 this.rbdForm.get('dataPool').disable();
219 this.rbdForm.get('obj_size').disable();
220 this.rbdForm.get('stripingUnit').disable();
221 this.rbdForm.get('stripingCount').disable();
223 /* RBD Image Format v1 */
224 this.rbdImage.subscribe((image: RbdModel) => {
225 if (image.image_format === RBDImageFormat.V1) {
226 this.rbdForm.get('deep-flatten').disable();
227 this.rbdForm.get('layering').disable();
228 this.rbdForm.get('exclusive-lock').disable();
230 if (!this.rbdForm.get('deep-flatten').value) {
231 this.rbdForm.get('deep-flatten').disable();
233 this.rbdForm.get('layering').disable();
239 this.rbdForm.get('parent').disable();
240 this.rbdForm.get('size').disable();
244 this.rbdForm.get('parent').disable();
245 this.rbdForm.get('size').disable();
249 this.prepareFormForAction();
250 this.gatherNeededData().subscribe(this.handleExternalData.bind(this));
254 if (this.mirroring && this.rbdForm.get('mirroringMode').value === 'journal') {
255 this.rbdForm.get('exclusive-lock').setValue(true);
256 this.rbdForm.get('exclusive-lock').disable();
258 this.rbdForm.get('exclusive-lock').enable();
259 if (this.poolMirrorMode === 'pool') {
260 this.rbdForm.get('mirroringMode').setValue(this.mirroringOptions[0]);
266 this.mirroring = !this.mirroring;
267 this.setExclusiveLock();
268 this.checkPeersConfigured();
271 checkPeersConfigured(poolname?: string) {
272 var Poolname = poolname ? poolname : this.rbdForm.get('pool').value;
273 this.rbdMirroringService.getPeerForPool(Poolname).subscribe((resp: any) => {
274 if (resp.length > 0) {
275 this.peerConfigured = true;
280 setPoolMirrorMode() {
281 this.currentPoolName =
282 this.mode === this.rbdFormMode.editing
283 ? this.response?.pool_name
284 : this.rbdForm.getValue('pool');
285 if (this.currentPoolName) {
286 this.rbdMirroringService.refresh();
287 this.rbdMirroringService.subscribeSummary((data) => {
288 const pool = data.content_data.pools.find((o: any) => o.name === this.currentPoolName);
289 this.poolMirrorMode = pool.mirror_mode;
291 if (pool.mirror_mode === 'disabled') {
292 this.mirroring = false;
293 this.rbdForm.get('mirroring').setValue(this.mirroring);
294 this.rbdForm.get('mirroring').disable();
298 this.setExclusiveLock();
301 private prepareFormForAction() {
302 const url = this.routerUrl;
303 if (url.startsWith('/block/rbd/edit')) {
304 this.mode = this.rbdFormMode.editing;
305 this.action = this.actionLabels.EDIT;
306 this.disableForEdit();
307 } else if (url.startsWith('/block/rbd/clone')) {
308 this.mode = this.rbdFormMode.cloning;
309 this.disableForClone();
310 this.action = this.actionLabels.CLONE;
311 } else if (url.startsWith('/block/rbd/copy')) {
312 this.mode = this.rbdFormMode.copying;
313 this.action = this.actionLabels.COPY;
314 this.disableForCopy();
316 this.action = this.actionLabels.CREATE;
318 _.each(this.features, (feature) => {
322 .valueChanges.subscribe((value) => this.featureFormUpdate(feature.key, value));
326 private gatherNeededData(): Observable<object> {
329 // Mode is not set for creation
330 this.route.params.subscribe((params: { image_spec: string; snap: string }) => {
331 const imageSpec = ImageSpec.fromString(decodeURIComponent(params.image_spec));
333 this.snapName = decodeURIComponent(params.snap);
335 promises['rbd'] = this.rbdService.get(imageSpec);
336 this.checkPeersConfigured(imageSpec.poolName);
340 promises['defaultFeatures'] = this.rbdService.defaultFeatures();
342 if (this.mode !== this.rbdFormMode.editing && this.poolPermission.read) {
343 promises['pools'] = this.poolService.list([
347 'application_metadata'
350 return forkJoin(promises);
353 private handleExternalData(data: ExternalData) {
354 this.handlePoolData(data.pools);
355 this.setPoolMirrorMode();
357 if (data.defaultFeatures) {
358 // Fetched only during creation
359 this.setFeatures(data.defaultFeatures);
363 // Not fetched for creation
364 const resp = data.rbd;
365 this.setResponse(resp, this.snapName);
366 this.rbdImage.next(resp);
372 private handlePoolData(data: Pool[]) {
374 // Not fetched while editing
377 const pools: Pool[] = [];
378 const dataPools = [];
379 for (const pool of data) {
380 if (this.rbdService.isRBDPool(pool)) {
381 if (pool.type === 'replicated') {
383 dataPools.push(pool);
384 } else if (pool.type === 'erasure' && pool.flags_names.indexOf('ec_overwrites') !== -1) {
385 dataPools.push(pool);
390 this.allPools = pools;
391 this.dataPools = dataPools;
392 this.allDataPools = dataPools;
393 if (this.pools.length === 1) {
394 const poolName = this.pools[0].pool_name;
395 this.rbdForm.get('pool').setValue(poolName);
396 this.onPoolChange(poolName);
398 if (this.allDataPools.length <= 1) {
399 this.rbdForm.get('useDataPool').disable();
403 onPoolChange(selectedPoolName: string) {
404 const dataPoolControl = this.rbdForm.get('dataPool');
405 if (dataPoolControl.value === selectedPoolName) {
406 dataPoolControl.setValue(null);
408 this.dataPools = this.allDataPools
409 ? this.allDataPools.filter((dataPool: any) => {
410 return dataPool.pool_name !== selectedPoolName;
413 this.namespaces = null;
414 if (selectedPoolName in this.namespacesByPoolCache) {
415 this.namespaces = this.namespacesByPoolCache[selectedPoolName];
417 this.rbdService.listNamespaces(selectedPoolName).subscribe((namespaces: any[]) => {
418 namespaces = namespaces.map((namespace) => namespace.namespace);
419 this.namespacesByPoolCache[selectedPoolName] = namespaces;
420 this.namespaces = namespaces;
423 this.rbdForm.get('namespace').setValue(null);
426 onUseDataPoolChange() {
427 if (!this.rbdForm.getValue('useDataPool')) {
428 this.rbdForm.get('dataPool').setValue(null);
429 this.onDataPoolChange(null);
433 onDataPoolChange(selectedDataPoolName: string) {
434 const newPools = this.allPools.filter((pool: Pool) => {
435 return pool.pool_name !== selectedDataPoolName;
437 if (this.rbdForm.getValue('pool') === selectedDataPoolName) {
438 this.rbdForm.get('pool').setValue(null);
440 this.pools = newPools;
443 validateRbdForm(formatter: FormatterService): ValidatorFn {
444 return (formGroup: CdFormGroup) => {
446 const useDataPoolControl = formGroup.get('useDataPool');
447 const dataPoolControl = formGroup.get('dataPool');
448 let dataPoolControlErrors = null;
449 if (useDataPoolControl.value && dataPoolControl.value == null) {
450 dataPoolControlErrors = { required: true };
452 dataPoolControl.setErrors(dataPoolControlErrors);
454 const sizeControl = formGroup.get('size');
455 const objectSizeControl = formGroup.get('obj_size');
456 const objectSizeInBytes = formatter.toBytes(
457 objectSizeControl.value != null ? objectSizeControl.value : this.defaultObjectSize
459 const stripingCountControl = formGroup.get('stripingCount');
460 const stripingCount =
461 stripingCountControl.value != null ? stripingCountControl.value : this.defaultStripingCount;
462 let sizeControlErrors = null;
463 if (sizeControl.value === null) {
464 sizeControlErrors = { required: true };
466 const sizeInBytes = formatter.toBytes(sizeControl.value);
467 if (stripingCount * objectSizeInBytes > sizeInBytes) {
468 sizeControlErrors = { invalidSizeObject: true };
471 sizeControl.setErrors(sizeControlErrors);
473 const stripingUnitControl = formGroup.get('stripingUnit');
474 let stripingUnitControlErrors = null;
475 if (stripingUnitControl.value === null && stripingCountControl.value !== null) {
476 stripingUnitControlErrors = { required: true };
477 } else if (stripingUnitControl.value !== null) {
478 const stripingUnitInBytes = formatter.toBytes(stripingUnitControl.value);
479 if (stripingUnitInBytes > objectSizeInBytes) {
480 stripingUnitControlErrors = { invalidStripingUnit: true };
483 stripingUnitControl.setErrors(stripingUnitControlErrors);
485 let stripingCountControlErrors = null;
486 if (stripingCountControl.value === null && stripingUnitControl.value !== null) {
487 stripingCountControlErrors = { required: true };
488 } else if (stripingCount < 1) {
489 stripingCountControlErrors = { min: true };
491 stripingCountControl.setErrors(stripingCountControlErrors);
496 deepBoxCheck(key: string, checked: boolean) {
497 const childFeatures = this.getDependentChildFeatures(key);
498 childFeatures.forEach((feature) => {
499 const featureControl = this.rbdForm.get(feature.key);
501 featureControl.enable({ emitEvent: false });
503 featureControl.disable({ emitEvent: false });
504 featureControl.setValue(false, { emitEvent: false });
505 this.deepBoxCheck(feature.key, checked);
508 const featureFormGroup = this.rbdForm.get('features');
509 if (this.mode === this.rbdFormMode.editing && featureFormGroup.get(feature.key).enabled) {
510 if (this.response.features_name.indexOf(feature.key) !== -1 && !feature.allowDisable) {
511 featureFormGroup.get(feature.key).disable();
513 this.response.features_name.indexOf(feature.key) === -1 &&
516 featureFormGroup.get(feature.key).disable();
522 protected getDependentChildFeatures(featureKey: string) {
523 return _.filter(this.features, (f) => f.requires === featureKey) || [];
526 interlockCheck(key: string, checked: boolean) {
527 // Adds a compatibility layer for Ceph cluster where the feature interlock of features hasn't
528 // been implemented yet. It disables the feature interlock for images which only have one of
529 // both interlocked features (at the time of this writing: object-map and fast-diff) enabled.
530 const feature = this.featuresList.find((f) => f.key === key);
532 // Ignore `create` page
533 const hasInterlockedFeature = feature.interlockedWith != null;
534 const dependentInterlockedFeature = this.featuresList.find(
535 (f) => f.interlockedWith === feature.key
537 const isOriginFeatureEnabled = !!this.response.features_name.find((e) => e === feature.key); // in this case: fast-diff
538 if (hasInterlockedFeature) {
539 const isLinkedEnabled = !!this.response.features_name.find(
540 (e) => e === feature.interlockedWith
541 ); // depends: object-map
542 if (isOriginFeatureEnabled !== isLinkedEnabled) {
543 return; // Ignore incompatible setting because it's from a previous cluster version
545 } else if (dependentInterlockedFeature) {
546 const isOtherInterlockedFeatureEnabled = !!this.response.features_name.find(
547 (e) => e === dependentInterlockedFeature.key
549 if (isOtherInterlockedFeatureEnabled !== isOriginFeatureEnabled) {
550 return; // Ignore incompatible setting because it's from a previous cluster version
556 _.filter(this.features, (f) => f.interlockedWith === key).forEach((f) =>
557 this.rbdForm.get(f.key).setValue(true, { emitEvent: false })
560 if (feature.interlockedWith) {
561 // Don't skip emitting the event here, as it prevents `fast-diff` from
562 // becoming disabled when manually unchecked. This is because it
563 // triggers an update on `object-map` and if `object-map` doesn't emit,
564 // `fast-diff` will not be automatically disabled.
565 this.rbdForm.get('features').get(feature.interlockedWith).setValue(false);
570 featureFormUpdate(key: string, checked: boolean) {
572 const required = this.features[key].requires;
573 if (required && !this.rbdForm.getValue(required)) {
574 this.rbdForm.get(`features.${key}`).setValue(false);
578 this.deepBoxCheck(key, checked);
579 this.interlockCheck(key, checked);
582 setFeatures(features: Array<string>) {
583 const featuresControl = this.rbdForm.get('features');
584 _.forIn(this.features, (feature) => {
585 if (features.indexOf(feature.key) !== -1) {
586 featuresControl.get(feature.key).setValue(true);
588 this.featureFormUpdate(feature.key, featuresControl.get(feature.key).value);
592 setResponse(response: RbdFormResponseModel, snapName: string) {
593 this.response = response;
594 const imageSpec = new ImageSpec(
599 if (this.mode === this.rbdFormMode.cloning) {
600 this.rbdForm.get('parent').setValue(`${imageSpec}@${snapName}`);
601 } else if (this.mode === this.rbdFormMode.copying) {
603 this.rbdForm.get('parent').setValue(`${imageSpec}@${snapName}`);
605 this.rbdForm.get('parent').setValue(`${imageSpec}`);
607 } else if (response.parent) {
608 const parent = response.parent;
611 .setValue(`${parent.pool_name}/${parent.image_name}@${parent.snap_name}`);
613 if (this.mode === this.rbdFormMode.editing) {
614 this.rbdForm.get('name').setValue(response.name);
615 if (response?.mirror_mode === 'snapshot' || response.features_name.includes('journaling')) {
616 this.mirroring = true;
617 this.rbdForm.get('mirroring').setValue(this.mirroring);
618 this.rbdForm.get('mirroringMode').setValue(response?.mirror_mode);
619 this.rbdForm.get('schedule').setValue(response?.schedule_interval);
621 this.mirroring = false;
622 this.rbdForm.get('mirroring').setValue(this.mirroring);
624 this.setPoolMirrorMode();
626 this.rbdForm.get('pool').setValue(response.pool_name);
627 this.onPoolChange(response.pool_name);
628 this.rbdForm.get('namespace').setValue(response.namespace);
629 if (response.data_pool) {
630 this.rbdForm.get('useDataPool').setValue(true);
631 this.rbdForm.get('dataPool').setValue(response.data_pool);
633 this.rbdForm.get('size').setValue(this.dimlessBinaryPipe.transform(response.size));
634 this.rbdForm.get('obj_size').setValue(this.dimlessBinaryPipe.transform(response.obj_size));
635 this.setFeatures(response.features_name);
638 .setValue(this.dimlessBinaryPipe.transform(response.stripe_unit));
639 this.rbdForm.get('stripingCount').setValue(response.stripe_count);
641 this.initializeConfigData.next({
642 initialData: this.response.configuration,
643 sourceType: RbdConfigurationSourceField.image
648 const request = new RbdFormCreateRequestModel();
649 request.pool_name = this.rbdForm.getValue('pool');
650 request.namespace = this.rbdForm.getValue('namespace');
651 request.name = this.rbdForm.getValue('name');
652 request.schedule_interval = this.rbdForm.getValue('schedule');
653 request.size = this.formatter.toBytes(this.rbdForm.getValue('size'));
655 if (this.poolMirrorMode === 'image') {
656 request.mirror_mode = this.rbdForm.getValue('mirroringMode');
658 this.addObjectSizeAndStripingToRequest(request);
659 request.configuration = this.getDirtyConfigurationValues();
663 private addObjectSizeAndStripingToRequest(
664 request: RbdFormCreateRequestModel | RbdFormCloneRequestModel | RbdFormCopyRequestModel
666 request.obj_size = this.formatter.toBytes(this.rbdForm.getValue('obj_size'));
667 _.forIn(this.features, (feature) => {
668 if (this.rbdForm.getValue(feature.key)) {
669 request.features.push(feature.key);
673 if (this.mirroring && this.rbdForm.getValue('mirroringMode') === 'journal') {
674 request.features.push('journaling');
678 request.stripe_unit = this.formatter.toBytes(this.rbdForm.getValue('stripingUnit'));
679 request.stripe_count = this.rbdForm.getValue('stripingCount');
680 request.data_pool = this.rbdForm.getValue('dataPool');
683 createAction(): Observable<any> {
684 const request = this.createRequest();
685 return this.taskWrapper.wrapTaskAroundCall({
686 task: new FinishedTask('rbd/create', {
687 pool_name: request.pool_name,
688 namespace: request.namespace,
689 image_name: request.name,
690 schedule_interval: request.schedule_interval,
691 start_time: request.start_time
693 call: this.rbdService.create(request)
698 const request = new RbdFormEditRequestModel();
699 request.name = this.rbdForm.getValue('name');
700 request.schedule_interval = this.rbdForm.getValue('schedule');
701 request.name = this.rbdForm.getValue('name');
702 request.size = this.formatter.toBytes(this.rbdForm.getValue('size'));
703 _.forIn(this.features, (feature) => {
704 if (this.rbdForm.getValue(feature.key)) {
705 request.features.push(feature.key);
708 request.enable_mirror = this.rbdForm.getValue('mirroring');
709 if (request.enable_mirror) {
710 if (this.rbdForm.getValue('mirroringMode') === 'journal') {
711 request.features.push('journaling');
713 if (this.poolMirrorMode === 'image') {
714 request.mirror_mode = this.rbdForm.getValue('mirroringMode');
717 const index = request.features.indexOf('journaling', 0);
719 request.features.splice(index, 1);
722 request.configuration = this.getDirtyConfigurationValues();
726 cloneRequest(): RbdFormCloneRequestModel {
727 const request = new RbdFormCloneRequestModel();
728 request.child_pool_name = this.rbdForm.getValue('pool');
729 request.child_namespace = this.rbdForm.getValue('namespace');
730 request.child_image_name = this.rbdForm.getValue('name');
731 this.addObjectSizeAndStripingToRequest(request);
732 request.configuration = this.getDirtyConfigurationValues(
734 RbdConfigurationSourceField.image
739 editAction(): Observable<any> {
740 const imageSpec = new ImageSpec(
741 this.response.pool_name,
742 this.response.namespace,
745 return this.taskWrapper.wrapTaskAroundCall({
746 task: new FinishedTask('rbd/edit', {
747 image_spec: imageSpec.toString()
749 call: this.rbdService.update(imageSpec, this.editRequest())
753 cloneAction(): Observable<any> {
754 const request = this.cloneRequest();
755 const imageSpec = new ImageSpec(
756 this.response.pool_name,
757 this.response.namespace,
760 return this.taskWrapper.wrapTaskAroundCall({
761 task: new FinishedTask('rbd/clone', {
762 parent_image_spec: imageSpec.toString(),
763 parent_snap_name: this.snapName,
764 child_pool_name: request.child_pool_name,
765 child_namespace: request.child_namespace,
766 child_image_name: request.child_image_name
768 call: this.rbdService.cloneSnapshot(imageSpec, this.snapName, request)
772 copyRequest(): RbdFormCopyRequestModel {
773 const request = new RbdFormCopyRequestModel();
775 request.snapshot_name = this.snapName;
777 request.dest_pool_name = this.rbdForm.getValue('pool');
778 request.dest_namespace = this.rbdForm.getValue('namespace');
779 request.dest_image_name = this.rbdForm.getValue('name');
780 this.addObjectSizeAndStripingToRequest(request);
781 request.configuration = this.getDirtyConfigurationValues(
783 RbdConfigurationSourceField.image
788 copyAction(): Observable<any> {
789 const request = this.copyRequest();
790 const imageSpec = new ImageSpec(
791 this.response.pool_name,
792 this.response.namespace,
795 return this.taskWrapper.wrapTaskAroundCall({
796 task: new FinishedTask('rbd/copy', {
797 src_image_spec: imageSpec.toString(),
798 dest_pool_name: request.dest_pool_name,
799 dest_namespace: request.dest_namespace,
800 dest_image_name: request.dest_image_name
802 call: this.rbdService.copy(imageSpec, request)
808 this.rbdImage.next('create');
814 if (this.mode === this.rbdFormMode.editing) {
815 return this.editAction();
816 } else if (this.mode === this.rbdFormMode.cloning) {
817 return this.cloneAction();
818 } else if (this.mode === this.rbdFormMode.copying) {
819 return this.copyAction();
821 return this.createAction();
827 () => this.rbdForm.setErrors({ cdSubmitButton: true }),
828 () => this.router.navigate(['/block/rbd'])