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