]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | import { Component, EventEmitter, OnInit } from '@angular/core'; |
2 | import { FormControl, Validators } from '@angular/forms'; | |
3 | import { ActivatedRoute, Router } from '@angular/router'; | |
4 | ||
5 | import { I18n } from '@ngx-translate/i18n-polyfill'; | |
6 | import * as _ from 'lodash'; | |
7 | import { BsModalService } from 'ngx-bootstrap/modal'; | |
8 | import { forkJoin, Subscription } from 'rxjs'; | |
9 | ||
10 | import { ErasureCodeProfileService } from '../../../shared/api/erasure-code-profile.service'; | |
11 | import { PoolService } from '../../../shared/api/pool.service'; | |
12 | import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; | |
13 | import { ActionLabelsI18n, URLVerbs } from '../../../shared/constants/app.constants'; | |
14 | import { CdFormGroup } from '../../../shared/forms/cd-form-group'; | |
15 | import { CdValidators } from '../../../shared/forms/cd-validators'; | |
16 | import { | |
17 | RbdConfigurationEntry, | |
18 | RbdConfigurationSourceField | |
19 | } from '../../../shared/models/configuration'; | |
20 | import { CrushRule } from '../../../shared/models/crush-rule'; | |
21 | import { CrushStep } from '../../../shared/models/crush-step'; | |
22 | import { ErasureCodeProfile } from '../../../shared/models/erasure-code-profile'; | |
23 | import { FinishedTask } from '../../../shared/models/finished-task'; | |
24 | import { Permission } from '../../../shared/models/permissions'; | |
25 | import { PoolFormInfo } from '../../../shared/models/pool-form-info'; | |
26 | import { DimlessBinaryPipe } from '../../../shared/pipes/dimless-binary.pipe'; | |
27 | import { AuthStorageService } from '../../../shared/services/auth-storage.service'; | |
28 | import { FormatterService } from '../../../shared/services/formatter.service'; | |
29 | import { TaskWrapperService } from '../../../shared/services/task-wrapper.service'; | |
30 | import { ErasureCodeProfileFormComponent } from '../erasure-code-profile-form/erasure-code-profile-form.component'; | |
31 | import { Pool } from '../pool'; | |
32 | import { PoolFormData } from './pool-form-data'; | |
33 | ||
34 | interface FormFieldDescription { | |
35 | externalFieldName: string; | |
36 | formControlName: string; | |
37 | attr?: string; | |
38 | replaceFn?: Function; | |
39 | editable?: boolean; | |
40 | resetValue?: any; | |
41 | } | |
42 | ||
43 | @Component({ | |
44 | selector: 'cd-pool-form', | |
45 | templateUrl: './pool-form.component.html', | |
46 | styleUrls: ['./pool-form.component.scss'] | |
47 | }) | |
48 | export class PoolFormComponent implements OnInit { | |
49 | permission: Permission; | |
50 | form: CdFormGroup; | |
51 | ecProfiles: ErasureCodeProfile[]; | |
52 | info: PoolFormInfo; | |
53 | routeParamsSubscribe: any; | |
54 | editing = false; | |
55 | data = new PoolFormData(this.i18n); | |
56 | externalPgChange = false; | |
57 | private modalSubscription: Subscription; | |
58 | current = { | |
59 | rules: [] | |
60 | }; | |
61 | initializeConfigData = new EventEmitter<{ | |
62 | initialData: RbdConfigurationEntry[]; | |
63 | sourceType: RbdConfigurationSourceField; | |
64 | }>(); | |
65 | currentConfigurationValues: { [configKey: string]: any } = {}; | |
66 | action: string; | |
67 | resource: string; | |
68 | ||
69 | constructor( | |
70 | private dimlessBinaryPipe: DimlessBinaryPipe, | |
71 | private route: ActivatedRoute, | |
72 | private router: Router, | |
73 | private modalService: BsModalService, | |
74 | private poolService: PoolService, | |
75 | private authStorageService: AuthStorageService, | |
76 | private formatter: FormatterService, | |
77 | private bsModalService: BsModalService, | |
78 | private taskWrapper: TaskWrapperService, | |
79 | private ecpService: ErasureCodeProfileService, | |
80 | private i18n: I18n, | |
81 | public actionLabels: ActionLabelsI18n | |
82 | ) { | |
83 | this.editing = this.router.url.startsWith(`/pool/${URLVerbs.EDIT}`); | |
84 | this.action = this.editing ? this.actionLabels.EDIT : this.actionLabels.CREATE; | |
85 | this.resource = this.i18n('pool'); | |
86 | this.authenticate(); | |
87 | this.createForm(); | |
88 | } | |
89 | ||
90 | authenticate() { | |
91 | this.permission = this.authStorageService.getPermissions().pool; | |
92 | if ( | |
93 | !this.permission.read || | |
94 | ((!this.permission.update && this.editing) || (!this.permission.create && !this.editing)) | |
95 | ) { | |
96 | this.router.navigate(['/404']); | |
97 | } | |
98 | } | |
99 | ||
100 | private createForm() { | |
101 | const compressionForm = new CdFormGroup({ | |
102 | mode: new FormControl('none'), | |
103 | algorithm: new FormControl(''), | |
104 | minBlobSize: new FormControl('', { | |
105 | updateOn: 'blur' | |
106 | }), | |
107 | maxBlobSize: new FormControl('', { | |
108 | updateOn: 'blur' | |
109 | }), | |
110 | ratio: new FormControl('', { | |
111 | updateOn: 'blur' | |
112 | }) | |
113 | }); | |
114 | ||
115 | this.form = new CdFormGroup( | |
116 | { | |
117 | name: new FormControl('', { | |
eafe8130 | 118 | validators: [Validators.pattern(/^[.A-Za-z0-9_/-]+$/), Validators.required] |
11fdf7f2 TL |
119 | }), |
120 | poolType: new FormControl('', { | |
121 | validators: [Validators.required] | |
122 | }), | |
123 | crushRule: new FormControl(null, { | |
124 | validators: [ | |
125 | CdValidators.custom( | |
126 | 'tooFewOsds', | |
127 | (rule) => this.info && rule && this.info.osd_count < rule.min_size | |
128 | ) | |
129 | ] | |
130 | }), | |
131 | size: new FormControl('', { | |
132 | updateOn: 'blur' | |
133 | }), | |
134 | erasureProfile: new FormControl(null), | |
135 | pgNum: new FormControl('', { | |
136 | validators: [Validators.required, Validators.min(1)] | |
137 | }), | |
138 | ecOverwrites: new FormControl(false), | |
139 | compression: compressionForm | |
140 | }, | |
141 | [ | |
142 | CdValidators.custom('form', () => null), | |
143 | CdValidators.custom('rbdPool', () => { | |
144 | return ( | |
145 | this.form && | |
146 | this.form.getValue('name').includes('/') && | |
147 | this.data && | |
148 | this.data.applications.selected.indexOf('rbd') !== -1 | |
149 | ); | |
150 | }) | |
151 | ] | |
152 | ); | |
153 | } | |
154 | ||
155 | ngOnInit() { | |
156 | forkJoin(this.poolService.getInfo(), this.ecpService.list()).subscribe( | |
157 | (data: [PoolFormInfo, ErasureCodeProfile[]]) => { | |
158 | this.initInfo(data[0]); | |
159 | this.initEcp(data[1]); | |
160 | if (this.editing) { | |
161 | this.initEditMode(); | |
162 | } | |
163 | this.listenToChanges(); | |
164 | this.setComplexValidators(); | |
165 | } | |
166 | ); | |
167 | } | |
168 | ||
169 | private initInfo(info: PoolFormInfo) { | |
170 | this.form.silentSet('algorithm', info.bluestore_compression_algorithm); | |
171 | info.compression_modes.push('unset'); | |
172 | this.info = info; | |
173 | } | |
174 | ||
175 | private initEcp(ecProfiles: ErasureCodeProfile[]) { | |
176 | const control = this.form.get('erasureProfile'); | |
177 | if (ecProfiles.length <= 1) { | |
178 | control.disable(); | |
179 | } | |
180 | if (ecProfiles.length === 1) { | |
181 | control.setValue(ecProfiles[0]); | |
182 | } else if (ecProfiles.length > 1 && control.disabled) { | |
183 | control.enable(); | |
184 | } | |
185 | this.ecProfiles = ecProfiles; | |
186 | } | |
187 | ||
188 | private initEditMode() { | |
189 | this.disableForEdit(); | |
190 | this.routeParamsSubscribe = this.route.params.subscribe((param: { name: string }) => | |
191 | this.poolService.get(param.name).subscribe((pool: Pool) => { | |
192 | this.data.pool = pool; | |
193 | this.initEditFormData(pool); | |
194 | }) | |
195 | ); | |
196 | } | |
197 | ||
198 | private disableForEdit() { | |
199 | ['poolType', 'crushRule', 'size', 'erasureProfile', 'ecOverwrites'].forEach((controlName) => | |
200 | this.form.get(controlName).disable() | |
201 | ); | |
202 | } | |
203 | ||
204 | private initEditFormData(pool: Pool) { | |
205 | this.initializeConfigData.emit({ | |
206 | initialData: pool.configuration, | |
207 | sourceType: RbdConfigurationSourceField.pool | |
208 | }); | |
209 | ||
210 | const dataMap = { | |
211 | name: pool.pool_name, | |
212 | poolType: pool.type, | |
213 | crushRule: this.info['crush_rules_' + pool.type].find( | |
214 | (rule: CrushRule) => rule.rule_name === pool.crush_rule | |
215 | ), | |
216 | size: pool.size, | |
217 | erasureProfile: this.ecProfiles.find((ecp) => ecp.name === pool.erasure_code_profile), | |
218 | pgNum: pool.pg_num, | |
219 | ecOverwrites: pool.flags_names.includes('ec_overwrites'), | |
220 | mode: pool.options.compression_mode, | |
221 | algorithm: pool.options.compression_algorithm, | |
222 | minBlobSize: this.dimlessBinaryPipe.transform(pool.options.compression_min_blob_size), | |
223 | maxBlobSize: this.dimlessBinaryPipe.transform(pool.options.compression_max_blob_size), | |
224 | ratio: pool.options.compression_required_ratio | |
225 | }; | |
226 | ||
227 | Object.keys(dataMap).forEach((controlName: string) => { | |
228 | const value = dataMap[controlName]; | |
229 | if (!_.isUndefined(value) && value !== '') { | |
230 | this.form.silentSet(controlName, value); | |
231 | } | |
232 | }); | |
eafe8130 | 233 | this.data.pgs = this.form.getValue('pgNum'); |
11fdf7f2 TL |
234 | this.data.applications.selected = pool.application_metadata; |
235 | } | |
236 | ||
237 | private listenToChanges() { | |
238 | this.listenToChangesDuringAddEdit(); | |
239 | if (!this.editing) { | |
240 | this.listenToChangesDuringAdd(); | |
241 | } | |
242 | } | |
243 | ||
244 | private listenToChangesDuringAddEdit() { | |
245 | this.form.get('pgNum').valueChanges.subscribe((pgs) => { | |
246 | const change = pgs - this.data.pgs; | |
247 | if (Math.abs(change) !== 1 || pgs === 2) { | |
248 | this.data.pgs = pgs; | |
249 | return; | |
250 | } | |
251 | this.doPgPowerJump(change as 1 | -1); | |
252 | }); | |
253 | } | |
254 | ||
255 | private doPgPowerJump(jump: 1 | -1) { | |
256 | const power = this.calculatePgPower() + jump; | |
257 | this.setPgs(jump === -1 ? Math.round(power) : Math.floor(power)); | |
258 | } | |
259 | ||
260 | private calculatePgPower(pgs = this.form.getValue('pgNum')): number { | |
261 | return Math.log(pgs) / Math.log(2); | |
262 | } | |
263 | ||
264 | private setPgs(power: number) { | |
265 | const pgs = Math.pow(2, power < 0 ? 0 : power); // Set size the nearest accurate size. | |
266 | this.data.pgs = pgs; | |
267 | this.form.silentSet('pgNum', pgs); | |
268 | } | |
269 | ||
270 | private listenToChangesDuringAdd() { | |
271 | this.form.get('poolType').valueChanges.subscribe((poolType) => { | |
272 | this.form.get('size').updateValueAndValidity(); | |
273 | this.rulesChange(); | |
274 | if (poolType === 'replicated') { | |
275 | this.replicatedRuleChange(); | |
276 | } | |
277 | this.pgCalc(); | |
278 | }); | |
279 | this.form.get('crushRule').valueChanges.subscribe(() => { | |
280 | if (this.form.getValue('poolType') === 'replicated') { | |
281 | this.replicatedRuleChange(); | |
282 | } | |
283 | this.pgCalc(); | |
284 | }); | |
285 | this.form.get('size').valueChanges.subscribe(() => { | |
286 | this.pgCalc(); | |
287 | }); | |
288 | this.form.get('erasureProfile').valueChanges.subscribe(() => { | |
289 | this.pgCalc(); | |
290 | }); | |
291 | this.form.get('mode').valueChanges.subscribe(() => { | |
292 | ['minBlobSize', 'maxBlobSize', 'ratio'].forEach((name) => { | |
293 | this.form.get(name).updateValueAndValidity({ emitEvent: false }); | |
294 | }); | |
295 | }); | |
296 | this.form.get('minBlobSize').valueChanges.subscribe(() => { | |
297 | this.form.get('maxBlobSize').updateValueAndValidity({ emitEvent: false }); | |
298 | }); | |
299 | this.form.get('maxBlobSize').valueChanges.subscribe(() => { | |
300 | this.form.get('minBlobSize').updateValueAndValidity({ emitEvent: false }); | |
301 | }); | |
302 | } | |
303 | ||
304 | private rulesChange() { | |
305 | const poolType = this.form.getValue('poolType'); | |
306 | if (!poolType || !this.info) { | |
307 | this.current.rules = []; | |
308 | return; | |
309 | } | |
310 | const rules = this.info['crush_rules_' + poolType] || []; | |
311 | const control = this.form.get('crushRule'); | |
312 | if (rules.length === 1) { | |
313 | control.setValue(rules[0]); | |
314 | control.disable(); | |
315 | } else { | |
316 | control.setValue(null); | |
317 | control.enable(); | |
318 | } | |
319 | this.current.rules = rules; | |
320 | } | |
321 | ||
322 | private replicatedRuleChange() { | |
323 | if (this.form.getValue('poolType') !== 'replicated') { | |
324 | return; | |
325 | } | |
326 | const control = this.form.get('size'); | |
327 | let size = this.form.getValue('size') || 3; | |
328 | const min = this.getMinSize(); | |
329 | const max = this.getMaxSize(); | |
330 | if (size < min) { | |
331 | size = min; | |
332 | } else if (size > max) { | |
333 | size = max; | |
334 | } | |
335 | if (size !== control.value) { | |
336 | this.form.silentSet('size', size); | |
337 | } | |
338 | } | |
339 | ||
340 | getMinSize(): number { | |
341 | if (!this.info || this.info.osd_count < 1) { | |
342 | return; | |
343 | } | |
344 | const rule = this.form.getValue('crushRule'); | |
345 | if (rule) { | |
346 | return rule.min_size; | |
347 | } | |
348 | return 1; | |
349 | } | |
350 | ||
351 | getMaxSize(): number { | |
352 | if (!this.info || this.info.osd_count < 1) { | |
353 | return; | |
354 | } | |
355 | const osds: number = this.info.osd_count; | |
356 | if (this.form.getValue('crushRule')) { | |
357 | const max: number = this.form.get('crushRule').value.max_size; | |
358 | if (max < osds) { | |
359 | return max; | |
360 | } | |
361 | } | |
362 | return osds; | |
363 | } | |
364 | ||
365 | private pgCalc() { | |
366 | const poolType = this.form.getValue('poolType'); | |
367 | if (!this.info || this.form.get('pgNum').dirty || !poolType) { | |
368 | return; | |
369 | } | |
370 | const pgMax = this.info.osd_count * 100; | |
371 | const pgs = | |
372 | poolType === 'replicated' ? this.replicatedPgCalc(pgMax) : this.erasurePgCalc(pgMax); | |
373 | if (!pgs) { | |
374 | return; | |
375 | } | |
376 | const oldValue = this.data.pgs; | |
377 | this.alignPgs(pgs); | |
378 | const newValue = this.data.pgs; | |
379 | if (!this.externalPgChange) { | |
380 | this.externalPgChange = oldValue !== newValue; | |
381 | } | |
382 | } | |
383 | ||
384 | private replicatedPgCalc(pgs): number { | |
385 | const sizeControl = this.form.get('size'); | |
386 | const size = sizeControl.value; | |
387 | if (sizeControl.valid && size > 0) { | |
388 | return pgs / size; | |
389 | } | |
390 | } | |
391 | ||
392 | private erasurePgCalc(pgs): number { | |
393 | const ecpControl = this.form.get('erasureProfile'); | |
394 | const ecp = ecpControl.value; | |
395 | if ((ecpControl.valid || ecpControl.disabled) && ecp) { | |
396 | return pgs / (ecp.k + ecp.m); | |
397 | } | |
398 | } | |
399 | ||
400 | private alignPgs(pgs = this.form.getValue('pgNum')) { | |
401 | this.setPgs(Math.round(this.calculatePgPower(pgs < 1 ? 1 : pgs))); | |
402 | } | |
403 | ||
404 | private setComplexValidators() { | |
405 | if (this.editing) { | |
11fdf7f2 TL |
406 | this.form |
407 | .get('name') | |
408 | .setValidators([ | |
409 | this.form.get('name').validator, | |
410 | CdValidators.custom( | |
411 | 'uniqueName', | |
412 | (name) => | |
413 | this.data.pool && | |
414 | this.info && | |
415 | this.info.pool_names.indexOf(name) !== -1 && | |
416 | this.info.pool_names.indexOf(name) !== | |
417 | this.info.pool_names.indexOf(this.data.pool.pool_name) | |
418 | ) | |
419 | ]); | |
420 | } else { | |
421 | CdValidators.validateIf( | |
422 | this.form.get('size'), | |
423 | () => this.form.get('poolType').value === 'replicated', | |
424 | [ | |
425 | CdValidators.custom( | |
426 | 'min', | |
427 | (value) => this.form.getValue('size') && value < this.getMinSize() | |
428 | ), | |
429 | CdValidators.custom( | |
430 | 'max', | |
431 | (value) => this.form.getValue('size') && this.getMaxSize() < value | |
432 | ) | |
433 | ] | |
434 | ); | |
435 | this.form | |
436 | .get('name') | |
437 | .setValidators([ | |
438 | this.form.get('name').validator, | |
439 | CdValidators.custom( | |
440 | 'uniqueName', | |
441 | (name) => this.info && this.info.pool_names.indexOf(name) !== -1 | |
442 | ) | |
443 | ]); | |
444 | } | |
445 | this.setCompressionValidators(); | |
446 | } | |
447 | ||
448 | private setCompressionValidators() { | |
449 | CdValidators.validateIf(this.form.get('minBlobSize'), () => this.hasCompressionEnabled(), [ | |
450 | Validators.min(0), | |
451 | CdValidators.custom('maximum', (size) => | |
452 | this.oddBlobSize(size, this.form.getValue('maxBlobSize')) | |
453 | ) | |
454 | ]); | |
455 | CdValidators.validateIf(this.form.get('maxBlobSize'), () => this.hasCompressionEnabled(), [ | |
456 | Validators.min(0), | |
457 | CdValidators.custom('minimum', (size) => | |
458 | this.oddBlobSize(this.form.getValue('minBlobSize'), size) | |
459 | ) | |
460 | ]); | |
461 | CdValidators.validateIf(this.form.get('ratio'), () => this.hasCompressionEnabled(), [ | |
462 | Validators.min(0), | |
463 | Validators.max(1) | |
464 | ]); | |
465 | } | |
466 | ||
467 | private oddBlobSize(minimum, maximum) { | |
468 | minimum = this.formatter.toBytes(minimum); | |
469 | maximum = this.formatter.toBytes(maximum); | |
470 | return Boolean(minimum && maximum && minimum >= maximum); | |
471 | } | |
472 | ||
473 | hasCompressionEnabled() { | |
474 | return this.form.getValue('mode') && this.form.get('mode').value.toLowerCase() !== 'none'; | |
475 | } | |
476 | ||
477 | describeCrushStep(step: CrushStep) { | |
478 | return [ | |
479 | step.op.replace('_', ' '), | |
480 | step.item_name || '', | |
481 | step.type ? step.num + ' type ' + step.type : '' | |
482 | ].join(' '); | |
483 | } | |
484 | ||
485 | addErasureCodeProfile() { | |
486 | this.modalSubscription = this.modalService.onHide.subscribe(() => this.reloadECPs()); | |
487 | this.bsModalService.show(ErasureCodeProfileFormComponent); | |
488 | } | |
489 | ||
490 | private reloadECPs() { | |
491 | this.ecpService.list().subscribe((profiles: ErasureCodeProfile[]) => this.initEcp(profiles)); | |
492 | this.modalSubscription.unsubscribe(); | |
493 | } | |
494 | ||
495 | deleteErasureCodeProfile() { | |
496 | const ecp = this.form.getValue('erasureProfile'); | |
497 | if (!ecp) { | |
498 | return; | |
499 | } | |
500 | const name = ecp.name; | |
501 | this.modalSubscription = this.modalService.onHide.subscribe(() => this.reloadECPs()); | |
502 | this.modalService.show(CriticalConfirmationModalComponent, { | |
503 | initialState: { | |
504 | itemDescription: this.i18n('erasure code profile'), | |
eafe8130 | 505 | itemNames: [name], |
11fdf7f2 TL |
506 | submitActionObservable: () => |
507 | this.taskWrapper.wrapTaskAroundCall({ | |
508 | task: new FinishedTask('ecp/delete', { name: name }), | |
509 | call: this.ecpService.delete(name) | |
510 | }) | |
511 | } | |
512 | }); | |
513 | } | |
514 | ||
515 | submit() { | |
516 | if (this.form.invalid) { | |
517 | this.form.setErrors({ cdSubmitButton: true }); | |
518 | return; | |
519 | } | |
520 | ||
521 | const pool = { pool: this.form.getValue('name') }; | |
522 | ||
523 | this.assignFormFields(pool, [ | |
524 | { externalFieldName: 'pool_type', formControlName: 'poolType' }, | |
525 | { externalFieldName: 'pg_num', formControlName: 'pgNum', editable: true }, | |
526 | this.form.getValue('poolType') === 'replicated' | |
527 | ? { externalFieldName: 'size', formControlName: 'size' } | |
528 | : { | |
529 | externalFieldName: 'erasure_code_profile', | |
530 | formControlName: 'erasureProfile', | |
531 | attr: 'name' | |
532 | }, | |
533 | { externalFieldName: 'rule_name', formControlName: 'crushRule', attr: 'rule_name' } | |
534 | ]); | |
535 | ||
536 | if (this.info.is_all_bluestore) { | |
537 | this.assignFormField(pool, { | |
538 | externalFieldName: 'flags', | |
539 | formControlName: 'ecOverwrites', | |
540 | replaceFn: () => ['ec_overwrites'] | |
541 | }); | |
542 | ||
543 | if (this.form.getValue('mode') !== 'none') { | |
544 | this.assignFormFields(pool, [ | |
545 | { | |
546 | externalFieldName: 'compression_mode', | |
547 | formControlName: 'mode', | |
548 | editable: true, | |
549 | replaceFn: (value) => this.hasCompressionEnabled() && value | |
550 | }, | |
551 | { | |
552 | externalFieldName: 'compression_algorithm', | |
553 | formControlName: 'algorithm', | |
554 | editable: true | |
555 | }, | |
556 | { | |
557 | externalFieldName: 'compression_min_blob_size', | |
558 | formControlName: 'minBlobSize', | |
559 | replaceFn: this.formatter.toBytes, | |
560 | editable: true, | |
561 | resetValue: 0 | |
562 | }, | |
563 | { | |
564 | externalFieldName: 'compression_max_blob_size', | |
565 | formControlName: 'maxBlobSize', | |
566 | replaceFn: this.formatter.toBytes, | |
567 | editable: true, | |
568 | resetValue: 0 | |
569 | }, | |
570 | { | |
571 | externalFieldName: 'compression_required_ratio', | |
572 | formControlName: 'ratio', | |
573 | editable: true, | |
574 | resetValue: 0 | |
575 | } | |
576 | ]); | |
577 | } else if (this.editing) { | |
578 | this.assignFormFields(pool, [ | |
579 | { | |
580 | externalFieldName: 'compression_mode', | |
581 | formControlName: 'mode', | |
582 | editable: true, | |
583 | replaceFn: () => 'unset' | |
584 | }, | |
585 | { | |
586 | externalFieldName: 'srcpool', | |
587 | formControlName: 'name', | |
588 | editable: true, | |
589 | replaceFn: () => this.data.pool.pool_name | |
590 | } | |
591 | ]); | |
592 | } | |
593 | } | |
594 | ||
595 | const apps = this.data.applications.selected; | |
596 | if (apps.length > 0 || this.editing) { | |
597 | pool['application_metadata'] = apps; | |
598 | } | |
599 | ||
600 | // Only collect configuration data for replicated pools, as QoS cannot be configured on EC | |
601 | // pools. EC data pools inherit their settings from the corresponding replicated metadata pool. | |
602 | if ( | |
603 | this.form.get('poolType').value === 'replicated' && | |
604 | !_.isEmpty(this.currentConfigurationValues) | |
605 | ) { | |
606 | pool['configuration'] = this.currentConfigurationValues; | |
607 | } | |
608 | ||
609 | this.triggerApiTask(pool); | |
610 | } | |
611 | ||
612 | /** | |
613 | * Retrieves the values for the given form field descriptions and assigns the values to the given | |
614 | * object. This method differentiates between `add` and `edit` mode and acts differently on one or | |
615 | * the other. | |
616 | */ | |
617 | private assignFormFields(pool: object, formFieldDescription: FormFieldDescription[]): void { | |
618 | formFieldDescription.forEach((item) => this.assignFormField(pool, item)); | |
619 | } | |
620 | ||
621 | /** | |
622 | * Retrieves the value for the given form field description and assigns the values to the given | |
623 | * object. This method differentiates between `add` and `edit` mode and acts differently on one or | |
624 | * the other. | |
625 | */ | |
626 | private assignFormField( | |
627 | pool: object, | |
628 | { | |
629 | externalFieldName, | |
630 | formControlName, | |
631 | attr, | |
632 | replaceFn, | |
633 | editable, | |
634 | resetValue | |
635 | }: FormFieldDescription | |
636 | ): void { | |
637 | if (this.editing && (!editable || this.form.get(formControlName).pristine)) { | |
638 | return; | |
639 | } | |
640 | const value = this.form.getValue(formControlName); | |
641 | let apiValue = replaceFn ? replaceFn(value) : attr ? _.get(value, attr) : value; | |
642 | if (!value || !apiValue) { | |
643 | if (editable && !_.isUndefined(resetValue)) { | |
644 | apiValue = resetValue; | |
645 | } else { | |
646 | return; | |
647 | } | |
648 | } | |
649 | pool[externalFieldName] = apiValue; | |
650 | } | |
651 | ||
652 | private triggerApiTask(pool) { | |
653 | this.taskWrapper | |
654 | .wrapTaskAroundCall({ | |
655 | task: new FinishedTask('pool/' + (this.editing ? URLVerbs.EDIT : URLVerbs.CREATE), { | |
656 | pool_name: pool.hasOwnProperty('srcpool') ? pool.srcpool : pool.pool | |
657 | }), | |
658 | call: this.poolService[this.editing ? URLVerbs.UPDATE : URLVerbs.CREATE](pool) | |
659 | }) | |
660 | .subscribe( | |
661 | undefined, | |
662 | (resp) => { | |
663 | if (_.isObject(resp.error) && resp.error.code === '34') { | |
664 | this.form.get('pgNum').setErrors({ '34': true }); | |
665 | } | |
666 | this.form.setErrors({ cdSubmitButton: true }); | |
667 | }, | |
668 | () => this.router.navigate(['/pool']) | |
669 | ); | |
670 | } | |
671 | ||
672 | appSelection() { | |
673 | this.form.updateValueAndValidity({ emitEvent: false, onlySelf: true }); | |
674 | } | |
675 | } |