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