1 import { Component, OnInit } from '@angular/core';
2 import { FormControl, 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`,
139 desc: $localize`Layering`,
145 desc: $localize`Exclusive lock`,
151 desc: $localize`Object map (requires exclusive-lock)`,
152 requires: 'exclusive-lock',
158 desc: $localize`Fast diff (interlocked with object-map)`,
159 requires: 'object-map',
162 interlockedWith: 'object-map',
166 this.featuresList = this.objToArray(this.features);
170 objToArray(obj: { [key: string]: any }) {
171 return _.map(obj, (o, key) => Object.assign(o, { key: key }));
175 this.rbdForm = new CdFormGroup(
177 parent: new FormControl(''),
178 name: new FormControl('', {
179 validators: [Validators.required, Validators.pattern(/^[^@/]+?$/)]
181 pool: new FormControl(null, {
182 validators: [Validators.required]
184 namespace: new FormControl(null),
185 useDataPool: new FormControl(false),
186 dataPool: new FormControl(null),
187 size: new FormControl(null, {
190 obj_size: new FormControl(this.defaultObjectSize),
191 features: new CdFormGroup(
192 this.featuresList.reduce((acc: object, e) => {
193 acc[e.key] = new FormControl({ value: false, disabled: !!e.initDisabled });
197 mirroring: new FormControl(''),
198 schedule: new FormControl('', {
199 validators: [Validators.pattern(/^([0-9]+)d|([0-9]+)h|([0-9]+)m$/)] // check schedule interval to be in format - 1d or 1h or 1m
201 mirroringMode: new FormControl(''),
202 stripingUnit: new FormControl(this.defaultStripingUnit),
203 stripingCount: new FormControl(this.defaultStripingCount, {
207 this.validateRbdForm(this.formatter)
212 this.rbdForm.get('parent').disable();
213 this.rbdForm.get('pool').disable();
214 this.rbdForm.get('namespace').disable();
215 this.rbdForm.get('useDataPool').disable();
216 this.rbdForm.get('dataPool').disable();
217 this.rbdForm.get('obj_size').disable();
218 this.rbdForm.get('stripingUnit').disable();
219 this.rbdForm.get('stripingCount').disable();
221 /* RBD Image Format v1 */
222 this.rbdImage.subscribe((image: RbdModel) => {
223 if (image.image_format === RBDImageFormat.V1) {
224 this.rbdForm.get('deep-flatten').disable();
225 this.rbdForm.get('layering').disable();
226 this.rbdForm.get('exclusive-lock').disable();
232 this.rbdForm.get('parent').disable();
233 this.rbdForm.get('size').disable();
237 this.rbdForm.get('parent').disable();
238 this.rbdForm.get('size').disable();
242 this.prepareFormForAction();
243 this.gatherNeededData().subscribe(this.handleExternalData.bind(this));
247 if (this.mirroring && this.rbdForm.get('mirroringMode').value === 'journal') {
248 this.rbdForm.get('exclusive-lock').setValue(true);
249 this.rbdForm.get('exclusive-lock').disable();
251 this.rbdForm.get('exclusive-lock').enable();
252 if (this.poolMirrorMode === 'pool') {
253 this.rbdForm.get('mirroringMode').setValue(this.mirroringOptions[0]);
259 this.mirroring = !this.mirroring;
260 this.setExclusiveLock();
261 this.checkPeersConfigured();
264 checkPeersConfigured(poolname?: string) {
265 var Poolname = poolname ? poolname : this.rbdForm.get('pool').value;
266 this.rbdMirroringService.getPeerForPool(Poolname).subscribe((resp: any) => {
267 if (resp.length > 0) {
268 this.peerConfigured = true;
273 setPoolMirrorMode() {
274 this.currentPoolName =
275 this.mode === this.rbdFormMode.editing
276 ? this.response?.pool_name
277 : this.rbdForm.getValue('pool');
278 if (this.currentPoolName) {
279 this.rbdMirroringService.refresh();
280 this.rbdMirroringService.subscribeSummary((data) => {
281 const pool = data.content_data.pools.find((o: any) => o.name === this.currentPoolName);
282 this.poolMirrorMode = pool.mirror_mode;
284 if (pool.mirror_mode === 'disabled') {
285 this.mirroring = false;
286 this.rbdForm.get('mirroring').setValue(this.mirroring);
287 this.rbdForm.get('mirroring').disable();
291 this.setExclusiveLock();
294 private prepareFormForAction() {
295 const url = this.routerUrl;
296 if (url.startsWith('/block/rbd/edit')) {
297 this.mode = this.rbdFormMode.editing;
298 this.action = this.actionLabels.EDIT;
299 this.disableForEdit();
300 } else if (url.startsWith('/block/rbd/clone')) {
301 this.mode = this.rbdFormMode.cloning;
302 this.disableForClone();
303 this.action = this.actionLabels.CLONE;
304 } else if (url.startsWith('/block/rbd/copy')) {
305 this.mode = this.rbdFormMode.copying;
306 this.action = this.actionLabels.COPY;
307 this.disableForCopy();
309 this.action = this.actionLabels.CREATE;
311 _.each(this.features, (feature) => {
315 .valueChanges.subscribe((value) => this.featureFormUpdate(feature.key, value));
319 private gatherNeededData(): Observable<object> {
322 // Mode is not set for creation
323 this.route.params.subscribe((params: { image_spec: string; snap: string }) => {
324 const imageSpec = ImageSpec.fromString(decodeURIComponent(params.image_spec));
326 this.snapName = decodeURIComponent(params.snap);
328 promises['rbd'] = this.rbdService.get(imageSpec);
329 this.checkPeersConfigured(imageSpec.poolName);
333 promises['defaultFeatures'] = this.rbdService.defaultFeatures();
335 if (this.mode !== this.rbdFormMode.editing && this.poolPermission.read) {
336 promises['pools'] = this.poolService.list([
340 'application_metadata'
343 return forkJoin(promises);
346 private handleExternalData(data: ExternalData) {
347 this.handlePoolData(data.pools);
348 this.setPoolMirrorMode();
350 if (data.defaultFeatures) {
351 // Fetched only during creation
352 this.setFeatures(data.defaultFeatures);
356 // Not fetched for creation
357 const resp = data.rbd;
358 this.setResponse(resp, this.snapName);
359 this.rbdImage.next(resp);
365 private handlePoolData(data: Pool[]) {
367 // Not fetched while editing
370 const pools: Pool[] = [];
371 const dataPools = [];
372 for (const pool of data) {
373 if (this.rbdService.isRBDPool(pool)) {
374 if (pool.type === 'replicated') {
376 dataPools.push(pool);
377 } else if (pool.type === 'erasure' && pool.flags_names.indexOf('ec_overwrites') !== -1) {
378 dataPools.push(pool);
383 this.allPools = pools;
384 this.dataPools = dataPools;
385 this.allDataPools = dataPools;
386 if (this.pools.length === 1) {
387 const poolName = this.pools[0].pool_name;
388 this.rbdForm.get('pool').setValue(poolName);
389 this.onPoolChange(poolName);
391 if (this.allDataPools.length <= 1) {
392 this.rbdForm.get('useDataPool').disable();
396 onPoolChange(selectedPoolName: string) {
397 const dataPoolControl = this.rbdForm.get('dataPool');
398 if (dataPoolControl.value === selectedPoolName) {
399 dataPoolControl.setValue(null);
401 this.dataPools = this.allDataPools
402 ? this.allDataPools.filter((dataPool: any) => {
403 return dataPool.pool_name !== selectedPoolName;
406 this.namespaces = null;
407 if (selectedPoolName in this.namespacesByPoolCache) {
408 this.namespaces = this.namespacesByPoolCache[selectedPoolName];
410 this.rbdService.listNamespaces(selectedPoolName).subscribe((namespaces: any[]) => {
411 namespaces = namespaces.map((namespace) => namespace.namespace);
412 this.namespacesByPoolCache[selectedPoolName] = namespaces;
413 this.namespaces = namespaces;
416 this.rbdForm.get('namespace').setValue(null);
419 onUseDataPoolChange() {
420 if (!this.rbdForm.getValue('useDataPool')) {
421 this.rbdForm.get('dataPool').setValue(null);
422 this.onDataPoolChange(null);
426 onDataPoolChange(selectedDataPoolName: string) {
427 const newPools = this.allPools.filter((pool: Pool) => {
428 return pool.pool_name !== selectedDataPoolName;
430 if (this.rbdForm.getValue('pool') === selectedDataPoolName) {
431 this.rbdForm.get('pool').setValue(null);
433 this.pools = newPools;
436 validateRbdForm(formatter: FormatterService): ValidatorFn {
437 return (formGroup: CdFormGroup) => {
439 const useDataPoolControl = formGroup.get('useDataPool');
440 const dataPoolControl = formGroup.get('dataPool');
441 let dataPoolControlErrors = null;
442 if (useDataPoolControl.value && dataPoolControl.value == null) {
443 dataPoolControlErrors = { required: true };
445 dataPoolControl.setErrors(dataPoolControlErrors);
447 const sizeControl = formGroup.get('size');
448 const objectSizeControl = formGroup.get('obj_size');
449 const objectSizeInBytes = formatter.toBytes(
450 objectSizeControl.value != null ? objectSizeControl.value : this.defaultObjectSize
452 const stripingCountControl = formGroup.get('stripingCount');
453 const stripingCount =
454 stripingCountControl.value != null ? stripingCountControl.value : this.defaultStripingCount;
455 let sizeControlErrors = null;
456 if (sizeControl.value === null) {
457 sizeControlErrors = { required: true };
459 const sizeInBytes = formatter.toBytes(sizeControl.value);
460 if (stripingCount * objectSizeInBytes > sizeInBytes) {
461 sizeControlErrors = { invalidSizeObject: true };
464 sizeControl.setErrors(sizeControlErrors);
466 const stripingUnitControl = formGroup.get('stripingUnit');
467 let stripingUnitControlErrors = null;
468 if (stripingUnitControl.value === null && stripingCountControl.value !== null) {
469 stripingUnitControlErrors = { required: true };
470 } else if (stripingUnitControl.value !== null) {
471 const stripingUnitInBytes = formatter.toBytes(stripingUnitControl.value);
472 if (stripingUnitInBytes > objectSizeInBytes) {
473 stripingUnitControlErrors = { invalidStripingUnit: true };
476 stripingUnitControl.setErrors(stripingUnitControlErrors);
478 let stripingCountControlErrors = null;
479 if (stripingCountControl.value === null && stripingUnitControl.value !== null) {
480 stripingCountControlErrors = { required: true };
481 } else if (stripingCount < 1) {
482 stripingCountControlErrors = { min: true };
484 stripingCountControl.setErrors(stripingCountControlErrors);
489 deepBoxCheck(key: string, checked: boolean) {
490 const childFeatures = this.getDependentChildFeatures(key);
491 childFeatures.forEach((feature) => {
492 const featureControl = this.rbdForm.get(feature.key);
494 featureControl.enable({ emitEvent: false });
496 featureControl.disable({ emitEvent: false });
497 featureControl.setValue(false, { emitEvent: false });
498 this.deepBoxCheck(feature.key, checked);
501 const featureFormGroup = this.rbdForm.get('features');
502 if (this.mode === this.rbdFormMode.editing && featureFormGroup.get(feature.key).enabled) {
503 if (this.response.features_name.indexOf(feature.key) !== -1 && !feature.allowDisable) {
504 featureFormGroup.get(feature.key).disable();
506 this.response.features_name.indexOf(feature.key) === -1 &&
509 featureFormGroup.get(feature.key).disable();
515 protected getDependentChildFeatures(featureKey: string) {
516 return _.filter(this.features, (f) => f.requires === featureKey) || [];
519 interlockCheck(key: string, checked: boolean) {
520 // Adds a compatibility layer for Ceph cluster where the feature interlock of features hasn't
521 // been implemented yet. It disables the feature interlock for images which only have one of
522 // both interlocked features (at the time of this writing: object-map and fast-diff) enabled.
523 const feature = this.featuresList.find((f) => f.key === key);
525 // Ignore `create` page
526 const hasInterlockedFeature = feature.interlockedWith != null;
527 const dependentInterlockedFeature = this.featuresList.find(
528 (f) => f.interlockedWith === feature.key
530 const isOriginFeatureEnabled = !!this.response.features_name.find((e) => e === feature.key); // in this case: fast-diff
531 if (hasInterlockedFeature) {
532 const isLinkedEnabled = !!this.response.features_name.find(
533 (e) => e === feature.interlockedWith
534 ); // depends: object-map
535 if (isOriginFeatureEnabled !== isLinkedEnabled) {
536 return; // Ignore incompatible setting because it's from a previous cluster version
538 } else if (dependentInterlockedFeature) {
539 const isOtherInterlockedFeatureEnabled = !!this.response.features_name.find(
540 (e) => e === dependentInterlockedFeature.key
542 if (isOtherInterlockedFeatureEnabled !== isOriginFeatureEnabled) {
543 return; // Ignore incompatible setting because it's from a previous cluster version
549 _.filter(this.features, (f) => f.interlockedWith === key).forEach((f) =>
550 this.rbdForm.get(f.key).setValue(true, { emitEvent: false })
553 if (feature.interlockedWith) {
554 // Don't skip emitting the event here, as it prevents `fast-diff` from
555 // becoming disabled when manually unchecked. This is because it
556 // triggers an update on `object-map` and if `object-map` doesn't emit,
557 // `fast-diff` will not be automatically disabled.
558 this.rbdForm.get('features').get(feature.interlockedWith).setValue(false);
563 featureFormUpdate(key: string, checked: boolean) {
565 const required = this.features[key].requires;
566 if (required && !this.rbdForm.getValue(required)) {
567 this.rbdForm.get(`features.${key}`).setValue(false);
571 this.deepBoxCheck(key, checked);
572 this.interlockCheck(key, checked);
575 setFeatures(features: Array<string>) {
576 const featuresControl = this.rbdForm.get('features');
577 _.forIn(this.features, (feature) => {
578 if (features.indexOf(feature.key) !== -1) {
579 featuresControl.get(feature.key).setValue(true);
581 this.featureFormUpdate(feature.key, featuresControl.get(feature.key).value);
585 setResponse(response: RbdFormResponseModel, snapName: string) {
586 this.response = response;
587 const imageSpec = new ImageSpec(
592 if (this.mode === this.rbdFormMode.cloning) {
593 this.rbdForm.get('parent').setValue(`${imageSpec}@${snapName}`);
594 } else if (this.mode === this.rbdFormMode.copying) {
596 this.rbdForm.get('parent').setValue(`${imageSpec}@${snapName}`);
598 this.rbdForm.get('parent').setValue(`${imageSpec}`);
600 } else if (response.parent) {
601 const parent = response.parent;
604 .setValue(`${parent.pool_name}/${parent.image_name}@${parent.snap_name}`);
606 if (this.mode === this.rbdFormMode.editing) {
607 this.rbdForm.get('name').setValue(response.name);
608 if (response?.mirror_mode === 'snapshot' || response.features_name.includes('journaling')) {
609 this.mirroring = true;
610 this.rbdForm.get('mirroring').setValue(this.mirroring);
611 this.rbdForm.get('mirroringMode').setValue(response?.mirror_mode);
612 this.rbdForm.get('schedule').setValue(response?.schedule_interval);
614 this.mirroring = false;
615 this.rbdForm.get('mirroring').setValue(this.mirroring);
617 this.setPoolMirrorMode();
619 this.rbdForm.get('pool').setValue(response.pool_name);
620 this.onPoolChange(response.pool_name);
621 this.rbdForm.get('namespace').setValue(response.namespace);
622 if (response.data_pool) {
623 this.rbdForm.get('useDataPool').setValue(true);
624 this.rbdForm.get('dataPool').setValue(response.data_pool);
626 this.rbdForm.get('size').setValue(this.dimlessBinaryPipe.transform(response.size));
627 this.rbdForm.get('obj_size').setValue(this.dimlessBinaryPipe.transform(response.obj_size));
628 this.setFeatures(response.features_name);
631 .setValue(this.dimlessBinaryPipe.transform(response.stripe_unit));
632 this.rbdForm.get('stripingCount').setValue(response.stripe_count);
634 this.initializeConfigData.next({
635 initialData: this.response.configuration,
636 sourceType: RbdConfigurationSourceField.image
641 const request = new RbdFormCreateRequestModel();
642 request.pool_name = this.rbdForm.getValue('pool');
643 request.namespace = this.rbdForm.getValue('namespace');
644 request.name = this.rbdForm.getValue('name');
645 request.schedule_interval = this.rbdForm.getValue('schedule');
646 request.size = this.formatter.toBytes(this.rbdForm.getValue('size'));
648 if (this.poolMirrorMode === 'image') {
649 request.mirror_mode = this.rbdForm.getValue('mirroringMode');
651 this.addObjectSizeAndStripingToRequest(request);
652 request.configuration = this.getDirtyConfigurationValues();
656 private addObjectSizeAndStripingToRequest(
657 request: RbdFormCreateRequestModel | RbdFormCloneRequestModel | RbdFormCopyRequestModel
659 request.obj_size = this.formatter.toBytes(this.rbdForm.getValue('obj_size'));
660 _.forIn(this.features, (feature) => {
661 if (this.rbdForm.getValue(feature.key)) {
662 request.features.push(feature.key);
666 if (this.mirroring && this.rbdForm.getValue('mirroringMode') === 'journal') {
667 request.features.push('journaling');
671 request.stripe_unit = this.formatter.toBytes(this.rbdForm.getValue('stripingUnit'));
672 request.stripe_count = this.rbdForm.getValue('stripingCount');
673 request.data_pool = this.rbdForm.getValue('dataPool');
676 createAction(): Observable<any> {
677 const request = this.createRequest();
678 return this.taskWrapper.wrapTaskAroundCall({
679 task: new FinishedTask('rbd/create', {
680 pool_name: request.pool_name,
681 namespace: request.namespace,
682 image_name: request.name,
683 schedule_interval: request.schedule_interval,
684 start_time: request.start_time
686 call: this.rbdService.create(request)
691 const request = new RbdFormEditRequestModel();
692 request.name = this.rbdForm.getValue('name');
693 request.schedule_interval = this.rbdForm.getValue('schedule');
694 request.name = this.rbdForm.getValue('name');
695 request.size = this.formatter.toBytes(this.rbdForm.getValue('size'));
696 _.forIn(this.features, (feature) => {
697 if (this.rbdForm.getValue(feature.key)) {
698 request.features.push(feature.key);
701 request.enable_mirror = this.rbdForm.getValue('mirroring');
702 if (request.enable_mirror) {
703 if (this.rbdForm.getValue('mirroringMode') === 'journal') {
704 request.features.push('journaling');
706 if (this.poolMirrorMode === 'image') {
707 request.mirror_mode = this.rbdForm.getValue('mirroringMode');
710 const index = request.features.indexOf('journaling', 0);
712 request.features.splice(index, 1);
715 request.configuration = this.getDirtyConfigurationValues();
719 cloneRequest(): RbdFormCloneRequestModel {
720 const request = new RbdFormCloneRequestModel();
721 request.child_pool_name = this.rbdForm.getValue('pool');
722 request.child_namespace = this.rbdForm.getValue('namespace');
723 request.child_image_name = this.rbdForm.getValue('name');
724 this.addObjectSizeAndStripingToRequest(request);
725 request.configuration = this.getDirtyConfigurationValues(
727 RbdConfigurationSourceField.image
732 editAction(): Observable<any> {
733 const imageSpec = new ImageSpec(
734 this.response.pool_name,
735 this.response.namespace,
738 return this.taskWrapper.wrapTaskAroundCall({
739 task: new FinishedTask('rbd/edit', {
740 image_spec: imageSpec.toString()
742 call: this.rbdService.update(imageSpec, this.editRequest())
746 cloneAction(): Observable<any> {
747 const request = this.cloneRequest();
748 const imageSpec = new ImageSpec(
749 this.response.pool_name,
750 this.response.namespace,
753 return this.taskWrapper.wrapTaskAroundCall({
754 task: new FinishedTask('rbd/clone', {
755 parent_image_spec: imageSpec.toString(),
756 parent_snap_name: this.snapName,
757 child_pool_name: request.child_pool_name,
758 child_namespace: request.child_namespace,
759 child_image_name: request.child_image_name
761 call: this.rbdService.cloneSnapshot(imageSpec, this.snapName, request)
765 copyRequest(): RbdFormCopyRequestModel {
766 const request = new RbdFormCopyRequestModel();
768 request.snapshot_name = this.snapName;
770 request.dest_pool_name = this.rbdForm.getValue('pool');
771 request.dest_namespace = this.rbdForm.getValue('namespace');
772 request.dest_image_name = this.rbdForm.getValue('name');
773 this.addObjectSizeAndStripingToRequest(request);
774 request.configuration = this.getDirtyConfigurationValues(
776 RbdConfigurationSourceField.image
781 copyAction(): Observable<any> {
782 const request = this.copyRequest();
783 const imageSpec = new ImageSpec(
784 this.response.pool_name,
785 this.response.namespace,
788 return this.taskWrapper.wrapTaskAroundCall({
789 task: new FinishedTask('rbd/copy', {
790 src_image_spec: imageSpec.toString(),
791 dest_pool_name: request.dest_pool_name,
792 dest_namespace: request.dest_namespace,
793 dest_image_name: request.dest_image_name
795 call: this.rbdService.copy(imageSpec, request)
801 this.rbdImage.next('create');
807 if (this.mode === this.rbdFormMode.editing) {
808 return this.editAction();
809 } else if (this.mode === this.rbdFormMode.cloning) {
810 return this.cloneAction();
811 } else if (this.mode === this.rbdFormMode.copying) {
812 return this.copyAction();
814 return this.createAction();
820 () => this.rbdForm.setErrors({ cdSubmitButton: true }),
821 () => this.router.navigate(['/block/rbd'])