]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts
1dc1df0dd3aba8da08c4025e85018572d3cd0df2
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / block / rbd-form / rbd-form.component.ts
1 import { Component, OnInit } from '@angular/core';
2 import { FormControl, ValidatorFn, Validators } from '@angular/forms';
3 import { ActivatedRoute, Router } from '@angular/router';
4
5 import _ from 'lodash';
6 import { forkJoin, Observable, ReplaySubject } from 'rxjs';
7 import { first, switchMap } from 'rxjs/operators';
8
9 import { Pool } from '~/app/ceph/pool/pool';
10 import { PoolService } from '~/app/shared/api/pool.service';
11 import { RbdService } from '~/app/shared/api/rbd.service';
12 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
13 import { Icons } from '~/app/shared/enum/icons.enum';
14 import { CdForm } from '~/app/shared/forms/cd-form';
15 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
16 import {
17 RbdConfigurationEntry,
18 RbdConfigurationSourceField
19 } from '~/app/shared/models/configuration';
20 import { FinishedTask } from '~/app/shared/models/finished-task';
21 import { ImageSpec } from '~/app/shared/models/image-spec';
22 import { Permission } from '~/app/shared/models/permissions';
23 import { DimlessBinaryPipe } from '~/app/shared/pipes/dimless-binary.pipe';
24 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
25 import { FormatterService } from '~/app/shared/services/formatter.service';
26 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
27 import { RBDImageFormat, RbdModel } from '../rbd-list/rbd-model';
28 import { RbdImageFeature } from './rbd-feature.interface';
29 import { RbdFormCloneRequestModel } from './rbd-form-clone-request.model';
30 import { RbdFormCopyRequestModel } from './rbd-form-copy-request.model';
31 import { RbdFormCreateRequestModel } from './rbd-form-create-request.model';
32 import { RbdFormEditRequestModel } from './rbd-form-edit-request.model';
33 import { RbdFormMode } from './rbd-form-mode.enum';
34 import { RbdFormResponseModel } from './rbd-form-response.model';
35
36 class ExternalData {
37 rbd: RbdFormResponseModel;
38 defaultFeatures: string[];
39 pools: Pool[];
40 }
41
42 @Component({
43 selector: 'cd-rbd-form',
44 templateUrl: './rbd-form.component.html',
45 styleUrls: ['./rbd-form.component.scss']
46 })
47 export class RbdFormComponent extends CdForm implements OnInit {
48 poolPermission: Permission;
49 rbdForm: CdFormGroup;
50 getDirtyConfigurationValues: (
51 includeLocalField?: boolean,
52 localField?: RbdConfigurationSourceField
53 ) => RbdConfigurationEntry[];
54
55 namespaces: Array<string> = [];
56 namespacesByPoolCache = {};
57 pools: Array<Pool> = null;
58 allPools: Array<Pool> = null;
59 dataPools: Array<Pool> = null;
60 allDataPools: Array<Pool> = [];
61 features: { [key: string]: RbdImageFeature };
62 featuresList: RbdImageFeature[] = [];
63 initializeConfigData = new ReplaySubject<{
64 initialData: RbdConfigurationEntry[];
65 sourceType: RbdConfigurationSourceField;
66 }>(1);
67
68 pool: string;
69
70 advancedEnabled = false;
71
72 public rbdFormMode = RbdFormMode;
73 mode: RbdFormMode;
74
75 response: RbdFormResponseModel;
76 snapName: string;
77
78 defaultObjectSize = '4 MiB';
79
80 objectSizes: Array<string> = [
81 '4 KiB',
82 '8 KiB',
83 '16 KiB',
84 '32 KiB',
85 '64 KiB',
86 '128 KiB',
87 '256 KiB',
88 '512 KiB',
89 '1 MiB',
90 '2 MiB',
91 '4 MiB',
92 '8 MiB',
93 '16 MiB',
94 '32 MiB'
95 ];
96 action: string;
97 resource: string;
98 private rbdImage = new ReplaySubject(1);
99 private routerUrl: string;
100
101 icons = Icons;
102
103 constructor(
104 private authStorageService: AuthStorageService,
105 private route: ActivatedRoute,
106 private poolService: PoolService,
107 private rbdService: RbdService,
108 private formatter: FormatterService,
109 private taskWrapper: TaskWrapperService,
110 private dimlessBinaryPipe: DimlessBinaryPipe,
111 public actionLabels: ActionLabelsI18n,
112 private router: Router
113 ) {
114 super();
115 this.routerUrl = this.router.url;
116 this.poolPermission = this.authStorageService.getPermissions().pool;
117 this.resource = $localize`RBD`;
118 this.features = {
119 'deep-flatten': {
120 desc: $localize`Deep flatten`,
121 requires: null,
122 allowEnable: false,
123 allowDisable: true
124 },
125 layering: {
126 desc: $localize`Layering`,
127 requires: null,
128 allowEnable: false,
129 allowDisable: false
130 },
131 'exclusive-lock': {
132 desc: $localize`Exclusive lock`,
133 requires: null,
134 allowEnable: true,
135 allowDisable: true
136 },
137 'object-map': {
138 desc: $localize`Object map (requires exclusive-lock)`,
139 requires: 'exclusive-lock',
140 allowEnable: true,
141 allowDisable: true,
142 initDisabled: true
143 },
144 journaling: {
145 desc: $localize`Journaling (requires exclusive-lock)`,
146 requires: 'exclusive-lock',
147 allowEnable: true,
148 allowDisable: true,
149 initDisabled: true
150 },
151 'fast-diff': {
152 desc: $localize`Fast diff (interlocked with object-map)`,
153 requires: 'object-map',
154 allowEnable: true,
155 allowDisable: true,
156 interlockedWith: 'object-map',
157 initDisabled: true
158 }
159 };
160 this.featuresList = this.objToArray(this.features);
161 this.createForm();
162 }
163
164 objToArray(obj: { [key: string]: any }) {
165 return _.map(obj, (o, key) => Object.assign(o, { key: key }));
166 }
167
168 createForm() {
169 this.rbdForm = new CdFormGroup(
170 {
171 parent: new FormControl(''),
172 name: new FormControl('', {
173 validators: [Validators.required, Validators.pattern(/^[^@/]+?$/)]
174 }),
175 pool: new FormControl(null, {
176 validators: [Validators.required]
177 }),
178 namespace: new FormControl(null),
179 useDataPool: new FormControl(false),
180 dataPool: new FormControl(null),
181 size: new FormControl(null, {
182 updateOn: 'blur'
183 }),
184 obj_size: new FormControl(this.defaultObjectSize),
185 features: new CdFormGroup(
186 this.featuresList.reduce((acc: object, e) => {
187 acc[e.key] = new FormControl({ value: false, disabled: !!e.initDisabled });
188 return acc;
189 }, {})
190 ),
191 stripingUnit: new FormControl(null),
192 stripingCount: new FormControl(null, {
193 updateOn: 'blur'
194 })
195 },
196 this.validateRbdForm(this.formatter)
197 );
198 }
199
200 disableForEdit() {
201 this.rbdForm.get('parent').disable();
202 this.rbdForm.get('pool').disable();
203 this.rbdForm.get('namespace').disable();
204 this.rbdForm.get('useDataPool').disable();
205 this.rbdForm.get('dataPool').disable();
206 this.rbdForm.get('obj_size').disable();
207 this.rbdForm.get('stripingUnit').disable();
208 this.rbdForm.get('stripingCount').disable();
209
210 /* RBD Image Format v1 */
211 this.rbdImage.subscribe((image: RbdModel) => {
212 if (image.image_format === RBDImageFormat.V1) {
213 this.rbdForm.get('deep-flatten').disable();
214 this.rbdForm.get('layering').disable();
215 this.rbdForm.get('exclusive-lock').disable();
216 }
217 });
218 }
219
220 disableForClone() {
221 this.rbdForm.get('parent').disable();
222 this.rbdForm.get('size').disable();
223 }
224
225 disableForCopy() {
226 this.rbdForm.get('parent').disable();
227 this.rbdForm.get('size').disable();
228 }
229
230 ngOnInit() {
231 this.prepareFormForAction();
232 this.gatherNeededData().subscribe(this.handleExternalData.bind(this));
233 }
234
235 private prepareFormForAction() {
236 const url = this.routerUrl;
237 if (url.startsWith('/block/rbd/edit')) {
238 this.mode = this.rbdFormMode.editing;
239 this.action = this.actionLabels.EDIT;
240 this.disableForEdit();
241 } else if (url.startsWith('/block/rbd/clone')) {
242 this.mode = this.rbdFormMode.cloning;
243 this.disableForClone();
244 this.action = this.actionLabels.CLONE;
245 } else if (url.startsWith('/block/rbd/copy')) {
246 this.mode = this.rbdFormMode.copying;
247 this.action = this.actionLabels.COPY;
248 this.disableForCopy();
249 } else {
250 this.action = this.actionLabels.CREATE;
251 }
252 _.each(this.features, (feature) => {
253 this.rbdForm
254 .get('features')
255 .get(feature.key)
256 .valueChanges.subscribe((value) => this.featureFormUpdate(feature.key, value));
257 });
258 }
259
260 private gatherNeededData(): Observable<object> {
261 const promises = {};
262 if (this.mode) {
263 // Mode is not set for creation
264 this.route.params.subscribe((params: { image_spec: string; snap: string }) => {
265 const imageSpec = ImageSpec.fromString(decodeURIComponent(params.image_spec));
266 if (params.snap) {
267 this.snapName = decodeURIComponent(params.snap);
268 }
269 promises['rbd'] = this.rbdService.get(imageSpec);
270 });
271 } else {
272 // New image
273 promises['defaultFeatures'] = this.rbdService.defaultFeatures();
274 }
275 if (this.mode !== this.rbdFormMode.editing && this.poolPermission.read) {
276 promises['pools'] = this.poolService.list([
277 'pool_name',
278 'type',
279 'flags_names',
280 'application_metadata'
281 ]);
282 }
283 return forkJoin(promises);
284 }
285
286 private handleExternalData(data: ExternalData) {
287 this.handlePoolData(data.pools);
288
289 if (data.defaultFeatures) {
290 // Fetched only during creation
291 this.setFeatures(data.defaultFeatures);
292 }
293
294 if (data.rbd) {
295 // Not fetched for creation
296 const resp = data.rbd;
297 this.setResponse(resp, this.snapName);
298 this.rbdImage.next(resp);
299 }
300
301 this.loadingReady();
302 }
303
304 private handlePoolData(data: Pool[]) {
305 if (!data) {
306 // Not fetched while editing
307 return;
308 }
309 const pools: Pool[] = [];
310 const dataPools = [];
311 for (const pool of data) {
312 if (this.rbdService.isRBDPool(pool)) {
313 if (pool.type === 'replicated') {
314 pools.push(pool);
315 dataPools.push(pool);
316 } else if (pool.type === 'erasure' && pool.flags_names.indexOf('ec_overwrites') !== -1) {
317 dataPools.push(pool);
318 }
319 }
320 }
321 this.pools = pools;
322 this.allPools = pools;
323 this.dataPools = dataPools;
324 this.allDataPools = dataPools;
325 if (this.pools.length === 1) {
326 const poolName = this.pools[0].pool_name;
327 this.rbdForm.get('pool').setValue(poolName);
328 this.onPoolChange(poolName);
329 }
330 if (this.allDataPools.length <= 1) {
331 this.rbdForm.get('useDataPool').disable();
332 }
333 }
334
335 onPoolChange(selectedPoolName: string) {
336 const dataPoolControl = this.rbdForm.get('dataPool');
337 if (dataPoolControl.value === selectedPoolName) {
338 dataPoolControl.setValue(null);
339 }
340 this.dataPools = this.allDataPools
341 ? this.allDataPools.filter((dataPool: any) => {
342 return dataPool.pool_name !== selectedPoolName;
343 })
344 : [];
345 this.namespaces = null;
346 if (selectedPoolName in this.namespacesByPoolCache) {
347 this.namespaces = this.namespacesByPoolCache[selectedPoolName];
348 } else {
349 this.rbdService.listNamespaces(selectedPoolName).subscribe((namespaces: any[]) => {
350 namespaces = namespaces.map((namespace) => namespace.namespace);
351 this.namespacesByPoolCache[selectedPoolName] = namespaces;
352 this.namespaces = namespaces;
353 });
354 }
355 this.rbdForm.get('namespace').setValue(null);
356 }
357
358 onUseDataPoolChange() {
359 if (!this.rbdForm.getValue('useDataPool')) {
360 this.rbdForm.get('dataPool').setValue(null);
361 this.onDataPoolChange(null);
362 }
363 }
364
365 onDataPoolChange(selectedDataPoolName: string) {
366 const newPools = this.allPools.filter((pool: Pool) => {
367 return pool.pool_name !== selectedDataPoolName;
368 });
369 if (this.rbdForm.getValue('pool') === selectedDataPoolName) {
370 this.rbdForm.get('pool').setValue(null);
371 }
372 this.pools = newPools;
373 }
374
375 validateRbdForm(formatter: FormatterService): ValidatorFn {
376 return (formGroup: CdFormGroup) => {
377 // Data Pool
378 const useDataPoolControl = formGroup.get('useDataPool');
379 const dataPoolControl = formGroup.get('dataPool');
380 let dataPoolControlErrors = null;
381 if (useDataPoolControl.value && dataPoolControl.value == null) {
382 dataPoolControlErrors = { required: true };
383 }
384 dataPoolControl.setErrors(dataPoolControlErrors);
385 // Size
386 const sizeControl = formGroup.get('size');
387 const objectSizeControl = formGroup.get('obj_size');
388 const objectSizeInBytes = formatter.toBytes(
389 objectSizeControl.value != null ? objectSizeControl.value : this.defaultObjectSize
390 );
391 const stripingCountControl = formGroup.get('stripingCount');
392 const stripingCount = stripingCountControl.value != null ? stripingCountControl.value : 1;
393 let sizeControlErrors = null;
394 if (sizeControl.value === null) {
395 sizeControlErrors = { required: true };
396 } else {
397 const sizeInBytes = formatter.toBytes(sizeControl.value);
398 if (stripingCount * objectSizeInBytes > sizeInBytes) {
399 sizeControlErrors = { invalidSizeObject: true };
400 }
401 }
402 sizeControl.setErrors(sizeControlErrors);
403 // Striping Unit
404 const stripingUnitControl = formGroup.get('stripingUnit');
405 let stripingUnitControlErrors = null;
406 if (stripingUnitControl.value === null && stripingCountControl.value !== null) {
407 stripingUnitControlErrors = { required: true };
408 } else if (stripingUnitControl.value !== null) {
409 const stripingUnitInBytes = formatter.toBytes(stripingUnitControl.value);
410 if (stripingUnitInBytes > objectSizeInBytes) {
411 stripingUnitControlErrors = { invalidStripingUnit: true };
412 }
413 }
414 stripingUnitControl.setErrors(stripingUnitControlErrors);
415 // Striping Count
416 let stripingCountControlErrors = null;
417 if (stripingCountControl.value === null && stripingUnitControl.value !== null) {
418 stripingCountControlErrors = { required: true };
419 } else if (stripingCount < 1) {
420 stripingCountControlErrors = { min: true };
421 }
422 stripingCountControl.setErrors(stripingCountControlErrors);
423 return null;
424 };
425 }
426
427 deepBoxCheck(key: string, checked: boolean) {
428 const childFeatures = this.getDependentChildFeatures(key);
429 childFeatures.forEach((feature) => {
430 const featureControl = this.rbdForm.get(feature.key);
431 if (checked) {
432 featureControl.enable({ emitEvent: false });
433 } else {
434 featureControl.disable({ emitEvent: false });
435 featureControl.setValue(false, { emitEvent: false });
436 this.deepBoxCheck(feature.key, checked);
437 }
438
439 const featureFormGroup = this.rbdForm.get('features');
440 if (this.mode === this.rbdFormMode.editing && featureFormGroup.get(feature.key).enabled) {
441 if (this.response.features_name.indexOf(feature.key) !== -1 && !feature.allowDisable) {
442 featureFormGroup.get(feature.key).disable();
443 } else if (
444 this.response.features_name.indexOf(feature.key) === -1 &&
445 !feature.allowEnable
446 ) {
447 featureFormGroup.get(feature.key).disable();
448 }
449 }
450 });
451 }
452
453 protected getDependentChildFeatures(featureKey: string) {
454 return _.filter(this.features, (f) => f.requires === featureKey) || [];
455 }
456
457 interlockCheck(key: string, checked: boolean) {
458 // Adds a compatibility layer for Ceph cluster where the feature interlock of features hasn't
459 // been implemented yet. It disables the feature interlock for images which only have one of
460 // both interlocked features (at the time of this writing: object-map and fast-diff) enabled.
461 const feature = this.featuresList.find((f) => f.key === key);
462 if (this.response) {
463 // Ignore `create` page
464 const hasInterlockedFeature = feature.interlockedWith != null;
465 const dependentInterlockedFeature = this.featuresList.find(
466 (f) => f.interlockedWith === feature.key
467 );
468 const isOriginFeatureEnabled = !!this.response.features_name.find((e) => e === feature.key); // in this case: fast-diff
469 if (hasInterlockedFeature) {
470 const isLinkedEnabled = !!this.response.features_name.find(
471 (e) => e === feature.interlockedWith
472 ); // depends: object-map
473 if (isOriginFeatureEnabled !== isLinkedEnabled) {
474 return; // Ignore incompatible setting because it's from a previous cluster version
475 }
476 } else if (dependentInterlockedFeature) {
477 const isOtherInterlockedFeatureEnabled = !!this.response.features_name.find(
478 (e) => e === dependentInterlockedFeature.key
479 );
480 if (isOtherInterlockedFeatureEnabled !== isOriginFeatureEnabled) {
481 return; // Ignore incompatible setting because it's from a previous cluster version
482 }
483 }
484 }
485
486 if (checked) {
487 _.filter(this.features, (f) => f.interlockedWith === key).forEach((f) =>
488 this.rbdForm.get(f.key).setValue(true, { emitEvent: false })
489 );
490 } else {
491 if (feature.interlockedWith) {
492 // Don't skip emitting the event here, as it prevents `fast-diff` from
493 // becoming disabled when manually unchecked. This is because it
494 // triggers an update on `object-map` and if `object-map` doesn't emit,
495 // `fast-diff` will not be automatically disabled.
496 this.rbdForm.get('features').get(feature.interlockedWith).setValue(false);
497 }
498 }
499 }
500
501 featureFormUpdate(key: string, checked: boolean) {
502 if (checked) {
503 const required = this.features[key].requires;
504 if (required && !this.rbdForm.getValue(required)) {
505 this.rbdForm.get(`features.${key}`).setValue(false);
506 return;
507 }
508 }
509 this.deepBoxCheck(key, checked);
510 this.interlockCheck(key, checked);
511 }
512
513 setFeatures(features: Array<string>) {
514 const featuresControl = this.rbdForm.get('features');
515 _.forIn(this.features, (feature) => {
516 if (features.indexOf(feature.key) !== -1) {
517 featuresControl.get(feature.key).setValue(true);
518 }
519 this.featureFormUpdate(feature.key, featuresControl.get(feature.key).value);
520 });
521 }
522
523 setResponse(response: RbdFormResponseModel, snapName: string) {
524 this.response = response;
525 const imageSpec = new ImageSpec(
526 response.pool_name,
527 response.namespace,
528 response.name
529 ).toString();
530 if (this.mode === this.rbdFormMode.cloning) {
531 this.rbdForm.get('parent').setValue(`${imageSpec}@${snapName}`);
532 } else if (this.mode === this.rbdFormMode.copying) {
533 if (snapName) {
534 this.rbdForm.get('parent').setValue(`${imageSpec}@${snapName}`);
535 } else {
536 this.rbdForm.get('parent').setValue(`${imageSpec}`);
537 }
538 } else if (response.parent) {
539 const parent = response.parent;
540 this.rbdForm
541 .get('parent')
542 .setValue(`${parent.pool_name}/${parent.image_name}@${parent.snap_name}`);
543 }
544 if (this.mode === this.rbdFormMode.editing) {
545 this.rbdForm.get('name').setValue(response.name);
546 }
547 this.rbdForm.get('pool').setValue(response.pool_name);
548 this.onPoolChange(response.pool_name);
549 this.rbdForm.get('namespace').setValue(response.namespace);
550 if (response.data_pool) {
551 this.rbdForm.get('useDataPool').setValue(true);
552 this.rbdForm.get('dataPool').setValue(response.data_pool);
553 }
554 this.rbdForm.get('size').setValue(this.dimlessBinaryPipe.transform(response.size));
555 this.rbdForm.get('obj_size').setValue(this.dimlessBinaryPipe.transform(response.obj_size));
556 this.setFeatures(response.features_name);
557 this.rbdForm
558 .get('stripingUnit')
559 .setValue(this.dimlessBinaryPipe.transform(response.stripe_unit));
560 this.rbdForm.get('stripingCount').setValue(response.stripe_count);
561 /* Configuration */
562 this.initializeConfigData.next({
563 initialData: this.response.configuration,
564 sourceType: RbdConfigurationSourceField.image
565 });
566 }
567
568 createRequest() {
569 const request = new RbdFormCreateRequestModel();
570 request.pool_name = this.rbdForm.getValue('pool');
571 request.namespace = this.rbdForm.getValue('namespace');
572 request.name = this.rbdForm.getValue('name');
573 request.size = this.formatter.toBytes(this.rbdForm.getValue('size'));
574 this.addObjectSizeAndStripingToRequest(request);
575 request.configuration = this.getDirtyConfigurationValues();
576 return request;
577 }
578
579 private addObjectSizeAndStripingToRequest(
580 request: RbdFormCreateRequestModel | RbdFormCloneRequestModel | RbdFormCopyRequestModel
581 ) {
582 request.obj_size = this.formatter.toBytes(this.rbdForm.getValue('obj_size'));
583 _.forIn(this.features, (feature) => {
584 if (this.rbdForm.getValue(feature.key)) {
585 request.features.push(feature.key);
586 }
587 });
588
589 /* Striping */
590 request.stripe_unit = this.formatter.toBytes(this.rbdForm.getValue('stripingUnit'));
591 request.stripe_count = this.rbdForm.getValue('stripingCount');
592 request.data_pool = this.rbdForm.getValue('dataPool');
593 }
594
595 createAction(): Observable<any> {
596 const request = this.createRequest();
597 return this.taskWrapper.wrapTaskAroundCall({
598 task: new FinishedTask('rbd/create', {
599 pool_name: request.pool_name,
600 namespace: request.namespace,
601 image_name: request.name
602 }),
603 call: this.rbdService.create(request)
604 });
605 }
606
607 editRequest() {
608 const request = new RbdFormEditRequestModel();
609 request.name = this.rbdForm.getValue('name');
610 request.size = this.formatter.toBytes(this.rbdForm.getValue('size'));
611 _.forIn(this.features, (feature) => {
612 if (this.rbdForm.getValue(feature.key)) {
613 request.features.push(feature.key);
614 }
615 });
616 request.configuration = this.getDirtyConfigurationValues();
617 return request;
618 }
619
620 cloneRequest(): RbdFormCloneRequestModel {
621 const request = new RbdFormCloneRequestModel();
622 request.child_pool_name = this.rbdForm.getValue('pool');
623 request.child_namespace = this.rbdForm.getValue('namespace');
624 request.child_image_name = this.rbdForm.getValue('name');
625 this.addObjectSizeAndStripingToRequest(request);
626 request.configuration = this.getDirtyConfigurationValues(
627 true,
628 RbdConfigurationSourceField.image
629 );
630 return request;
631 }
632
633 editAction(): Observable<any> {
634 const imageSpec = new ImageSpec(
635 this.response.pool_name,
636 this.response.namespace,
637 this.response.name
638 );
639 return this.taskWrapper.wrapTaskAroundCall({
640 task: new FinishedTask('rbd/edit', {
641 image_spec: imageSpec.toString()
642 }),
643 call: this.rbdService.update(imageSpec, this.editRequest())
644 });
645 }
646
647 cloneAction(): Observable<any> {
648 const request = this.cloneRequest();
649 const imageSpec = new ImageSpec(
650 this.response.pool_name,
651 this.response.namespace,
652 this.response.name
653 );
654 return this.taskWrapper.wrapTaskAroundCall({
655 task: new FinishedTask('rbd/clone', {
656 parent_image_spec: imageSpec.toString(),
657 parent_snap_name: this.snapName,
658 child_pool_name: request.child_pool_name,
659 child_namespace: request.child_namespace,
660 child_image_name: request.child_image_name
661 }),
662 call: this.rbdService.cloneSnapshot(imageSpec, this.snapName, request)
663 });
664 }
665
666 copyRequest(): RbdFormCopyRequestModel {
667 const request = new RbdFormCopyRequestModel();
668 if (this.snapName) {
669 request.snapshot_name = this.snapName;
670 }
671 request.dest_pool_name = this.rbdForm.getValue('pool');
672 request.dest_namespace = this.rbdForm.getValue('namespace');
673 request.dest_image_name = this.rbdForm.getValue('name');
674 this.addObjectSizeAndStripingToRequest(request);
675 request.configuration = this.getDirtyConfigurationValues(
676 true,
677 RbdConfigurationSourceField.image
678 );
679 return request;
680 }
681
682 copyAction(): Observable<any> {
683 const request = this.copyRequest();
684 const imageSpec = new ImageSpec(
685 this.response.pool_name,
686 this.response.namespace,
687 this.response.name
688 );
689 return this.taskWrapper.wrapTaskAroundCall({
690 task: new FinishedTask('rbd/copy', {
691 src_image_spec: imageSpec.toString(),
692 dest_pool_name: request.dest_pool_name,
693 dest_namespace: request.dest_namespace,
694 dest_image_name: request.dest_image_name
695 }),
696 call: this.rbdService.copy(imageSpec, request)
697 });
698 }
699
700 submit() {
701 if (!this.mode) {
702 this.rbdImage.next('create');
703 }
704 this.rbdImage
705 .pipe(
706 first(),
707 switchMap(() => {
708 if (this.mode === this.rbdFormMode.editing) {
709 return this.editAction();
710 } else if (this.mode === this.rbdFormMode.cloning) {
711 return this.cloneAction();
712 } else if (this.mode === this.rbdFormMode.copying) {
713 return this.copyAction();
714 } else {
715 return this.createAction();
716 }
717 })
718 )
719 .subscribe(
720 () => undefined,
721 () => this.rbdForm.setErrors({ cdSubmitButton: true }),
722 () => this.router.navigate(['/block/rbd'])
723 );
724 }
725 }