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