]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts
import 15.2.4
[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';
9f95a23c 7
1911f103
TL
8import { forkJoin, Observable, ReplaySubject } from 'rxjs';
9import { first, switchMap } from 'rxjs/operators';
11fdf7f2
TL
10
11import { PoolService } from '../../../shared/api/pool.service';
12import { RbdService } from '../../../shared/api/rbd.service';
13import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
9f95a23c 14import { Icons } from '../../../shared/enum/icons.enum';
11fdf7f2
TL
15import { CdFormGroup } from '../../../shared/forms/cd-form-group';
16import {
17 RbdConfigurationEntry,
18 RbdConfigurationSourceField
19} from '../../../shared/models/configuration';
20import { FinishedTask } from '../../../shared/models/finished-task';
9f95a23c 21import { ImageSpec } from '../../../shared/models/image-spec';
11fdf7f2
TL
22import { Permission } from '../../../shared/models/permissions';
23import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe';
24import { AuthStorageService } from '../../../shared/services/auth-storage.service';
25import { FormatterService } from '../../../shared/services/formatter.service';
26import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
9f95a23c 27import { Pool } from '../../pool/pool';
494da23a 28import { RbdImageFeature } from './rbd-feature.interface';
11fdf7f2
TL
29import { RbdFormCloneRequestModel } from './rbd-form-clone-request.model';
30import { RbdFormCopyRequestModel } from './rbd-form-copy-request.model';
31import { RbdFormCreateRequestModel } from './rbd-form-create-request.model';
32import { RbdFormEditRequestModel } from './rbd-form-edit-request.model';
33import { RbdFormMode } from './rbd-form-mode.enum';
34import { 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})
41export class RbdFormComponent implements OnInit {
42 poolPermission: Permission;
43 rbdForm: CdFormGroup;
11fdf7f2
TL
44 getDirtyConfigurationValues: (
45 includeLocalField?: boolean,
46 localField?: RbdConfigurationSourceField
47 ) => RbdConfigurationEntry[];
48
9f95a23c
TL
49 namespaces: Array<string> = [];
50 namespacesByPoolCache = {};
51 pools: Array<Pool> = null;
52 allPools: Array<Pool> = null;
11fdf7f2
TL
53 dataPools: Array<string> = null;
54 allDataPools: Array<string> = null;
494da23a
TL
55 features: { [key: string]: RbdImageFeature };
56 featuresList: RbdImageFeature[] = [];
11fdf7f2
TL
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;
1911f103 92 private rbdImage = new ReplaySubject(1);
11fdf7f2 93
9f95a23c
TL
94 icons = Icons;
95
11fdf7f2
TL
96 constructor(
97 private authStorageService: AuthStorageService,
98 private route: ActivatedRoute,
11fdf7f2
TL
99 private poolService: PoolService,
100 private rbdService: RbdService,
101 private formatter: FormatterService,
102 private taskWrapper: TaskWrapperService,
103 private dimlessBinaryPipe: DimlessBinaryPipe,
104 private i18n: I18n,
494da23a
TL
105 public actionLabels: ActionLabelsI18n,
106 public router: Router
11fdf7f2
TL
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,
494da23a
TL
133 allowDisable: true,
134 initDisabled: true
11fdf7f2
TL
135 },
136 journaling: {
137 desc: this.i18n('Journaling (requires exclusive-lock)'),
138 requires: 'exclusive-lock',
139 allowEnable: true,
494da23a
TL
140 allowDisable: true,
141 initDisabled: true
11fdf7f2
TL
142 },
143 'fast-diff': {
494da23a 144 desc: this.i18n('Fast diff (interlocked with object-map)'),
11fdf7f2
TL
145 requires: 'object-map',
146 allowEnable: true,
494da23a
TL
147 allowDisable: true,
148 interlockedWith: 'object-map',
149 initDisabled: true
11fdf7f2
TL
150 }
151 };
494da23a 152 this.featuresList = this.objToArray(this.features);
11fdf7f2 153 this.createForm();
494da23a
TL
154 }
155
156 objToArray(obj: { [key: string]: any }) {
157 return _.map(obj, (o, key) => Object.assign(o, { key: key }));
11fdf7f2
TL
158 }
159
160 createForm() {
11fdf7f2
TL
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 }),
9f95a23c 170 namespace: new FormControl(null),
11fdf7f2
TL
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),
494da23a 177 features: new CdFormGroup(
9f95a23c 178 this.featuresList.reduce((acc: object, e) => {
494da23a
TL
179 acc[e.key] = new FormControl({ value: false, disabled: !!e.initDisabled });
180 return acc;
181 }, {})
182 ),
11fdf7f2
TL
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();
9f95a23c 195 this.rbdForm.get('namespace').disable();
11fdf7f2
TL
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 }
9f95a23c
TL
229 enum Promisse {
230 RbdServiceGet = 'rbdService.get',
231 PoolServiceList = 'poolService.list'
232 }
233 const promisses = {};
11fdf7f2
TL
234 if (
235 this.mode === this.rbdFormMode.editing ||
236 this.mode === this.rbdFormMode.cloning ||
237 this.mode === this.rbdFormMode.copying
238 ) {
9f95a23c
TL
239 this.route.params.subscribe((params: { image_spec: string; snap: string }) => {
240 const imageSpec = ImageSpec.fromString(decodeURIComponent(params.image_spec));
11fdf7f2
TL
241 if (params.snap) {
242 this.snapName = decodeURIComponent(params.snap);
243 }
9f95a23c 244 promisses[Promisse.RbdServiceGet] = this.rbdService.get(imageSpec);
11fdf7f2
TL
245 });
246 } else {
494da23a 247 // New image
11fdf7f2
TL
248 this.rbdService.defaultFeatures().subscribe((defaultFeatures: Array<string>) => {
249 this.setFeatures(defaultFeatures);
250 });
251 }
252 if (this.mode !== this.rbdFormMode.editing && this.poolPermission.read) {
9f95a23c
TL
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);
11fdf7f2
TL
276 }
277 }
9f95a23c
TL
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
494da23a
TL
298 _.each(this.features, (feature) => {
299 this.rbdForm
300 .get('features')
301 .get(feature.key)
302 .valueChanges.subscribe((value) => this.featureFormUpdate(feature.key, value));
11fdf7f2
TL
303 });
304 }
305
9f95a23c
TL
306 onPoolChange(selectedPoolName: string) {
307 const newDataPools = this.allDataPools
308 ? this.allDataPools.filter((dataPool: any) => {
309 return dataPool.pool_name !== selectedPoolName;
310 })
311 : [];
11fdf7f2
TL
312 if (this.rbdForm.getValue('dataPool') === selectedPoolName) {
313 this.rbdForm.get('dataPool').setValue(null);
314 }
315 this.dataPools = newDataPools;
9f95a23c
TL
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);
11fdf7f2
TL
327 }
328
329 onUseDataPoolChange() {
330 if (!this.rbdForm.getValue('useDataPool')) {
331 this.rbdForm.get('dataPool').setValue(null);
332 this.onDataPoolChange(null);
333 }
334 }
335
9f95a23c
TL
336 onDataPoolChange(selectedDataPoolName: string) {
337 const newPools = this.allPools.filter((pool: Pool) => {
11fdf7f2
TL
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
494da23a
TL
398 protected getDependendChildFeatures(featureKey: string) {
399 return _.filter(this.features, (f) => f.requires === featureKey) || [];
400 }
401
9f95a23c 402 deepBoxCheck(key: string, checked: boolean) {
494da23a
TL
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);
11fdf7f2 412 }
494da23a
TL
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();
11fdf7f2
TL
423 }
424 }
425 });
426 }
427
9f95a23c 428 interlockCheck(key: string, checked: boolean) {
494da23a
TL
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.
e306af50 467 this.rbdForm.get('features').get(feature.interlockedWith).setValue(false);
494da23a
TL
468 }
469 }
470 }
471
9f95a23c 472 featureFormUpdate(key: string, checked: boolean) {
11fdf7f2
TL
473 if (checked) {
474 const required = this.features[key].requires;
475 if (required && !this.rbdForm.getValue(required)) {
494da23a 476 this.rbdForm.get(`features.${key}`).setValue(false);
11fdf7f2
TL
477 return;
478 }
479 }
480 this.deepBoxCheck(key, checked);
494da23a 481 this.interlockCheck(key, checked);
11fdf7f2
TL
482 }
483
484 setFeatures(features: Array<string>) {
485 const featuresControl = this.rbdForm.get('features');
486 _.forIn(this.features, (feature) => {
487 if (features.indexOf(feature.key) !== -1) {
488 featuresControl.get(feature.key).setValue(true);
489 }
494da23a 490 this.featureFormUpdate(feature.key, featuresControl.get(feature.key).value);
11fdf7f2
TL
491 });
492 }
493
494 setResponse(response: RbdFormResponseModel, snapName: string) {
495 this.response = response;
9f95a23c
TL
496 const imageSpec = new ImageSpec(
497 response.pool_name,
498 response.namespace,
499 response.name
500 ).toString();
11fdf7f2 501 if (this.mode === this.rbdFormMode.cloning) {
9f95a23c 502 this.rbdForm.get('parent').setValue(`${imageSpec}@${snapName}`);
11fdf7f2
TL
503 } else if (this.mode === this.rbdFormMode.copying) {
504 if (snapName) {
9f95a23c 505 this.rbdForm.get('parent').setValue(`${imageSpec}@${snapName}`);
11fdf7f2 506 } else {
9f95a23c 507 this.rbdForm.get('parent').setValue(`${imageSpec}`);
11fdf7f2
TL
508 }
509 } else if (response.parent) {
510 const parent = response.parent;
511 this.rbdForm
512 .get('parent')
513 .setValue(`${parent.pool_name}/${parent.image_name}@${parent.snap_name}`);
514 }
515 if (this.mode === this.rbdFormMode.editing) {
516 this.rbdForm.get('name').setValue(response.name);
517 }
518 this.rbdForm.get('pool').setValue(response.pool_name);
9f95a23c
TL
519 this.onPoolChange(response.pool_name);
520 this.rbdForm.get('namespace').setValue(response.namespace);
11fdf7f2
TL
521 if (response.data_pool) {
522 this.rbdForm.get('useDataPool').setValue(true);
523 this.rbdForm.get('dataPool').setValue(response.data_pool);
524 }
525 this.rbdForm.get('size').setValue(this.dimlessBinaryPipe.transform(response.size));
526 this.rbdForm.get('obj_size').setValue(this.dimlessBinaryPipe.transform(response.obj_size));
527 this.setFeatures(response.features_name);
528 this.rbdForm
529 .get('stripingUnit')
530 .setValue(this.dimlessBinaryPipe.transform(response.stripe_unit));
531 this.rbdForm.get('stripingCount').setValue(response.stripe_count);
532
533 /* Configuration */
534 this.initializeConfigData.emit({
535 initialData: this.response.configuration,
536 sourceType: RbdConfigurationSourceField.image
537 });
538 }
539
540 createRequest() {
541 const request = new RbdFormCreateRequestModel();
542 request.pool_name = this.rbdForm.getValue('pool');
9f95a23c 543 request.namespace = this.rbdForm.getValue('namespace');
11fdf7f2
TL
544 request.name = this.rbdForm.getValue('name');
545 request.size = this.formatter.toBytes(this.rbdForm.getValue('size'));
546 request.obj_size = this.formatter.toBytes(this.rbdForm.getValue('obj_size'));
547 _.forIn(this.features, (feature) => {
548 if (this.rbdForm.getValue(feature.key)) {
549 request.features.push(feature.key);
550 }
551 });
552
553 /* Striping */
554 request.stripe_unit = this.formatter.toBytes(this.rbdForm.getValue('stripingUnit'));
555 request.stripe_count = this.rbdForm.getValue('stripingCount');
556 request.data_pool = this.rbdForm.getValue('dataPool');
557
558 /* Configuration */
559 request.configuration = this.getDirtyConfigurationValues();
560
561 return request;
562 }
563
564 createAction(): Observable<any> {
565 const request = this.createRequest();
566 return this.taskWrapper.wrapTaskAroundCall({
567 task: new FinishedTask('rbd/create', {
568 pool_name: request.pool_name,
9f95a23c 569 namespace: request.namespace,
11fdf7f2
TL
570 image_name: request.name
571 }),
572 call: this.rbdService.create(request)
573 });
574 }
575
576 editRequest() {
577 const request = new RbdFormEditRequestModel();
578 request.name = this.rbdForm.getValue('name');
579 request.size = this.formatter.toBytes(this.rbdForm.getValue('size'));
580 _.forIn(this.features, (feature) => {
581 if (this.rbdForm.getValue(feature.key)) {
582 request.features.push(feature.key);
583 }
584 });
585
586 request.configuration = this.getDirtyConfigurationValues();
587
588 return request;
589 }
590
591 cloneRequest(): RbdFormCloneRequestModel {
592 const request = new RbdFormCloneRequestModel();
593 request.child_pool_name = this.rbdForm.getValue('pool');
9f95a23c 594 request.child_namespace = this.rbdForm.getValue('namespace');
11fdf7f2
TL
595 request.child_image_name = this.rbdForm.getValue('name');
596 request.obj_size = this.formatter.toBytes(this.rbdForm.getValue('obj_size'));
597 _.forIn(this.features, (feature) => {
598 if (this.rbdForm.getValue(feature.key)) {
599 request.features.push(feature.key);
600 }
601 });
602
603 /* Striping */
604 request.stripe_unit = this.formatter.toBytes(this.rbdForm.getValue('stripingUnit'));
605 request.stripe_count = this.rbdForm.getValue('stripingCount');
606 request.data_pool = this.rbdForm.getValue('dataPool');
607
608 /* Configuration */
609 request.configuration = this.getDirtyConfigurationValues(
610 true,
611 RbdConfigurationSourceField.image
612 );
613
614 return request;
615 }
616
617 editAction(): Observable<any> {
9f95a23c
TL
618 const imageSpec = new ImageSpec(
619 this.response.pool_name,
620 this.response.namespace,
621 this.response.name
622 );
11fdf7f2
TL
623 return this.taskWrapper.wrapTaskAroundCall({
624 task: new FinishedTask('rbd/edit', {
9f95a23c 625 image_spec: imageSpec.toString()
11fdf7f2 626 }),
9f95a23c 627 call: this.rbdService.update(imageSpec, this.editRequest())
11fdf7f2
TL
628 });
629 }
630
631 cloneAction(): Observable<any> {
632 const request = this.cloneRequest();
9f95a23c
TL
633 const imageSpec = new ImageSpec(
634 this.response.pool_name,
635 this.response.namespace,
636 this.response.name
637 );
11fdf7f2
TL
638 return this.taskWrapper.wrapTaskAroundCall({
639 task: new FinishedTask('rbd/clone', {
9f95a23c 640 parent_image_spec: imageSpec.toString(),
11fdf7f2
TL
641 parent_snap_name: this.snapName,
642 child_pool_name: request.child_pool_name,
9f95a23c 643 child_namespace: request.child_namespace,
11fdf7f2
TL
644 child_image_name: request.child_image_name
645 }),
9f95a23c 646 call: this.rbdService.cloneSnapshot(imageSpec, this.snapName, request)
11fdf7f2
TL
647 });
648 }
649
650 copyRequest(): RbdFormCopyRequestModel {
651 const request = new RbdFormCopyRequestModel();
652 if (this.snapName) {
653 request.snapshot_name = this.snapName;
654 }
655 request.dest_pool_name = this.rbdForm.getValue('pool');
9f95a23c 656 request.dest_namespace = this.rbdForm.getValue('namespace');
11fdf7f2
TL
657 request.dest_image_name = this.rbdForm.getValue('name');
658 request.obj_size = this.formatter.toBytes(this.rbdForm.getValue('obj_size'));
659 _.forIn(this.features, (feature) => {
660 if (this.rbdForm.getValue(feature.key)) {
661 request.features.push(feature.key);
662 }
663 });
664
665 /* Striping */
666 request.stripe_unit = this.formatter.toBytes(this.rbdForm.getValue('stripingUnit'));
667 request.stripe_count = this.rbdForm.getValue('stripingCount');
668 request.data_pool = this.rbdForm.getValue('dataPool');
669
670 /* Configuration */
671 request.configuration = this.getDirtyConfigurationValues(
672 true,
673 RbdConfigurationSourceField.image
674 );
675
676 return request;
677 }
678
679 copyAction(): Observable<any> {
680 const request = this.copyRequest();
9f95a23c
TL
681 const imageSpec = new ImageSpec(
682 this.response.pool_name,
683 this.response.namespace,
684 this.response.name
685 );
11fdf7f2
TL
686 return this.taskWrapper.wrapTaskAroundCall({
687 task: new FinishedTask('rbd/copy', {
9f95a23c 688 src_image_spec: imageSpec.toString(),
11fdf7f2 689 dest_pool_name: request.dest_pool_name,
9f95a23c 690 dest_namespace: request.dest_namespace,
11fdf7f2
TL
691 dest_image_name: request.dest_image_name
692 }),
9f95a23c 693 call: this.rbdService.copy(imageSpec, request)
11fdf7f2
TL
694 });
695 }
696
697 submit() {
eafe8130
TL
698 if (!this.mode) {
699 this.rbdImage.next('create');
11fdf7f2 700 }
eafe8130
TL
701 this.rbdImage
702 .pipe(
1911f103 703 first(),
eafe8130
TL
704 switchMap(() => {
705 if (this.mode === this.rbdFormMode.editing) {
706 return this.editAction();
707 } else if (this.mode === this.rbdFormMode.cloning) {
708 return this.cloneAction();
709 } else if (this.mode === this.rbdFormMode.copying) {
710 return this.copyAction();
711 } else {
712 return this.createAction();
713 }
714 })
715 )
716 .subscribe(
717 () => {},
718 () => this.rbdForm.setErrors({ cdSubmitButton: true }),
719 () => this.router.navigate(['/block/rbd'])
720 );
11fdf7f2
TL
721 }
722}