1 import { Component, EventEmitter, OnInit, Output } from '@angular/core';
2 import { Validators } from '@angular/forms';
4 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
6 import { ErasureCodeProfileService } from '../../../shared/api/erasure-code-profile.service';
7 import { CrushNodeSelectionClass } from '../../../shared/classes/crush.node.selection.class';
8 import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
9 import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
10 import { CdFormGroup } from '../../../shared/forms/cd-form-group';
11 import { CdValidators } from '../../../shared/forms/cd-validators';
12 import { CrushNode } from '../../../shared/models/crush-node';
13 import { ErasureCodeProfile } from '../../../shared/models/erasure-code-profile';
14 import { FinishedTask } from '../../../shared/models/finished-task';
15 import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
18 selector: 'cd-erasure-code-profile-form-modal',
19 templateUrl: './erasure-code-profile-form-modal.component.html',
20 styleUrls: ['./erasure-code-profile-form-modal.component.scss']
22 export class ErasureCodeProfileFormModalComponent extends CrushNodeSelectionClass
25 submitAction = new EventEmitter();
27 tooltips = this.ecpService.formTooltips;
29 LRC: 'lrc', // Locally Repairable Erasure Code
30 SHEC: 'shec', // Shingled Erasure Code
31 JERASURE: 'jerasure', // default
32 ISA: 'isa' // Intel Storage Acceleration
34 plugin = this.PLUGIN.JERASURE;
46 private formBuilder: CdFormBuilder,
47 public activeModal: NgbActiveModal,
48 private taskWrapper: TaskWrapperService,
49 private ecpService: ErasureCodeProfileService,
50 public actionLabels: ActionLabelsI18n
53 this.action = this.actionLabels.CREATE;
54 this.resource = $localize`EC Profile`;
56 this.setJerasureDefaults();
60 this.form = this.formBuilder.group({
65 Validators.pattern('[A-Za-z0-9_-]+'),
68 (value: string) => this.names && this.names.indexOf(value) !== -1
72 plugin: [this.PLUGIN.JERASURE, [Validators.required]],
74 4, // Will be overwritten with plugin defaults
78 CdValidators.custom('max', () => this.baseValueValidation(true)),
79 CdValidators.custom('unequal', (v: number) => this.lrcDataValidation(v)),
80 CdValidators.custom('kLowerM', (v: number) => this.shecDataValidation(v))
84 2, // Will be overwritten with plugin defaults
88 CdValidators.custom('max', () => this.baseValueValidation())
91 crushFailureDomain: '', // Will be preselected
92 crushRoot: null, // Will be preselected
93 crushDeviceClass: '', // Will be preselected
95 // Only for 'jerasure' and 'isa' use
96 technique: 'reed_sol_van',
97 // Only for 'jerasure' use
98 packetSize: [2048, [Validators.min(1)]],
101 3, // Will be overwritten with plugin defaults
105 CdValidators.custom('unequal', (v: number) => this.lrcLocalityValidation(v))
108 crushLocality: '', // set to none at the end (same list as for failure domains)
109 // Only for 'shec' use
111 2, // Will be overwritten with plugin defaults
115 CdValidators.custom('cGreaterM', (v: number) => this.shecDurabilityValidation(v))
119 this.form.get('k').valueChanges.subscribe(() => this.updateValidityOnChange(['m', 'l']));
120 this.form.get('m').valueChanges.subscribe(() => this.updateValidityOnChange(['k', 'l', 'c']));
121 this.form.get('l').valueChanges.subscribe(() => this.updateValidityOnChange(['k', 'm']));
122 this.form.get('plugin').valueChanges.subscribe((plugin) => this.onPluginChange(plugin));
125 private baseValueValidation(dataChunk: boolean = false): boolean {
126 return this.validValidation(() => {
128 this.getKMSum() > this.deviceCount &&
129 this.form.getValue('k') > this.form.getValue('m') === dataChunk
134 private validValidation(fn: () => boolean, plugin?: string): boolean {
135 if (!this.form || plugin ? this.plugin !== plugin : false) {
141 private getKMSum(): number {
142 return this.form.getValue('k') + this.form.getValue('m');
145 private lrcDataValidation(k: number): boolean {
146 return this.validValidation(() => {
147 const m = this.form.getValue('m');
148 const l = this.form.getValue('l');
150 this.lrcMultiK = k / (km / l);
151 return k % (km / l) !== 0;
155 private shecDataValidation(k: number): boolean {
156 return this.validValidation(() => {
157 const m = this.form.getValue('m');
162 private lrcLocalityValidation(l: number) {
163 return this.validValidation(() => {
164 const value = this.getKMSum();
165 this.lrcGroups = l > 0 ? value / l : 0;
166 return l > 0 && value % l !== 0;
170 private shecDurabilityValidation(c: number): boolean {
171 return this.validValidation(() => {
172 const m = this.form.getValue('m');
177 private updateValidityOnChange(names: string[]) {
178 names.forEach((name) => this.form.get(name).updateValueAndValidity({ emitEvent: false }));
181 private onPluginChange(plugin: string) {
182 this.plugin = plugin;
183 if (plugin === this.PLUGIN.JERASURE) {
184 this.setJerasureDefaults();
185 } else if (plugin === this.PLUGIN.LRC) {
186 this.setLrcDefaults();
187 } else if (plugin === this.PLUGIN.ISA) {
188 this.setIsaDefaults();
189 } else if (plugin === this.PLUGIN.SHEC) {
190 this.setShecDefaults();
192 this.updateValidityOnChange(['m']); // Triggers k, m, c and l
195 private setJerasureDefaults() {
211 private setLrcDefaults() {
219 private setIsaDefaults() {
221 * Actually k and m are not required - but they will be set to the default values in case
222 * if they are not set, therefore it's fine to mark them as required in order to get
223 * strange values that weren't set.
229 this.techniques = ['reed_sol_van', 'cauchy'];
232 private setShecDefaults() {
234 * Actually k, c and m are not required - but they will be set to the default values in case
235 * if they are not set, therefore it's fine to mark them as required in order to get
236 * strange values that weren't set.
245 private setDefaults(defaults: object) {
246 Object.keys(defaults).forEach((controlName) => {
247 const control = this.form.get(controlName);
248 const value = control.value;
249 let overwrite = control.pristine;
251 * As k, m, c and l are now set touched and dirty on the beginning, plugin change will
252 * overwrite their values as we can't determine if the user has changed anything.
253 * k and m can have two default values where as l and c can only have one,
254 * so there is no need to overwrite them.
256 if ('k' === controlName) {
257 overwrite = [4, 7].includes(value);
258 } else if ('m' === controlName) {
259 overwrite = [2, 3].includes(value);
262 this.form.get(controlName).setValue(defaults[controlName]);
282 this.initCrushNodeSelection(
284 this.form.get('crushRoot'),
285 this.form.get('crushFailureDomain'),
286 this.form.get('crushDeviceClass')
288 this.plugins = plugins;
290 this.form.silentSet('directory', directory);
291 this.preValidateNumericInputFields();
297 * This allows k, m, l and c to be validated instantly on change, before the
298 * fields got changed before by the user.
300 private preValidateNumericInputFields() {
301 const kml = ['k', 'm', 'l', 'c'].map((name) => this.form.get(name));
302 kml.forEach((control) => {
303 control.markAsTouched();
304 control.markAsDirty();
306 kml[1].updateValueAndValidity(); // Update validity of k, m, c and l
310 if (this.form.invalid) {
311 this.form.setErrors({ cdSubmitButton: true });
314 const profile = this.createJson();
316 .wrapTaskAroundCall({
317 task: new FinishedTask('ecp/create', { name: profile.name }),
318 call: this.ecpService.create(profile)
322 this.form.setErrors({ cdSubmitButton: true });
325 this.activeModal.close();
326 this.submitAction.emit(profile);
331 private createJson() {
332 const pluginControls = {
333 technique: [this.PLUGIN.ISA, this.PLUGIN.JERASURE],
334 packetSize: [this.PLUGIN.JERASURE],
335 l: [this.PLUGIN.LRC],
336 crushLocality: [this.PLUGIN.LRC],
337 c: [this.PLUGIN.SHEC]
339 const ecp = new ErasureCodeProfile();
340 const plugin = this.form.getValue('plugin');
341 Object.keys(this.form.controls)
343 const pluginControl = pluginControls[name];
344 const value = this.form.getValue(name);
345 const usable = (pluginControl && pluginControl.includes(plugin)) || !pluginControl;
346 return usable && value && value !== '';
349 this.extendJson(name, ecp);
354 private extendJson(name: string, ecp: ErasureCodeProfile) {
355 const differentApiAttributes = {
356 crushFailureDomain: 'crush-failure-domain',
357 crushRoot: 'crush-root',
358 crushDeviceClass: 'crush-device-class',
359 packetSize: 'packetsize',
360 crushLocality: 'crush-locality'
362 const value = this.form.getValue(name);
363 ecp[differentApiAttributes[name] || name] = name === 'crushRoot' ? value.name : value;