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