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