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