]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
aed3493fc4d091aad343f6f4246c5f7c910866bb
[ceph.git] /
1 import { Component, EventEmitter, OnInit, Output } from '@angular/core';
2 import { Validators } from '@angular/forms';
3
4 import { I18n } from '@ngx-translate/i18n-polyfill';
5 import { BsModalRef } from 'ngx-bootstrap/modal';
6
7 import { ErasureCodeProfileService } from '../../../shared/api/erasure-code-profile.service';
8 import { CrushNodeSelectionClass } from '../../../shared/classes/crush.node.selection.class';
9 import { ActionLabelsI18n } from '../../../shared/constants/app.constants';
10 import { CdFormBuilder } from '../../../shared/forms/cd-form-builder';
11 import { CdFormGroup } from '../../../shared/forms/cd-form-group';
12 import { CdValidators } from '../../../shared/forms/cd-validators';
13 import { CrushNode } from '../../../shared/models/crush-node';
14 import { ErasureCodeProfile } from '../../../shared/models/erasure-code-profile';
15 import { FinishedTask } from '../../../shared/models/finished-task';
16 import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
17
18 @Component({
19   selector: 'cd-erasure-code-profile-form-modal',
20   templateUrl: './erasure-code-profile-form-modal.component.html',
21   styleUrls: ['./erasure-code-profile-form-modal.component.scss']
22 })
23 export class ErasureCodeProfileFormModalComponent extends CrushNodeSelectionClass
24   implements OnInit {
25   @Output()
26   submitAction = new EventEmitter();
27
28   tooltips = this.ecpService.formTooltips;
29   PLUGIN = {
30     LRC: 'lrc', // Locally Repairable Erasure Code
31     SHEC: 'shec', // Shingled Erasure Code
32     JERASURE: 'jerasure', // default
33     ISA: 'isa' // Intel Storage Acceleration
34   };
35   plugin = this.PLUGIN.JERASURE;
36
37   form: CdFormGroup;
38   plugins: string[];
39   names: string[];
40   techniques: string[];
41   action: string;
42   resource: string;
43
44   constructor(
45     private formBuilder: CdFormBuilder,
46     public bsModalRef: BsModalRef,
47     private taskWrapper: TaskWrapperService,
48     private ecpService: ErasureCodeProfileService,
49     private i18n: I18n,
50     public actionLabels: ActionLabelsI18n
51   ) {
52     super();
53     this.action = this.actionLabels.CREATE;
54     this.resource = this.i18n('EC Profile');
55     this.createForm();
56     this.setJerasureDefaults();
57   }
58
59   createForm() {
60     this.form = this.formBuilder.group({
61       name: [
62         null,
63         [
64           Validators.required,
65           Validators.pattern('[A-Za-z0-9_-]+'),
66           CdValidators.custom(
67             'uniqueName',
68             (value: string) => this.names && this.names.indexOf(value) !== -1
69           )
70         ]
71       ],
72       plugin: [this.PLUGIN.JERASURE, [Validators.required]],
73       k: [1], // Will be replaced by plugin defaults
74       m: [1], // Will be replaced by plugin defaults
75       crushFailureDomain: ['host'],
76       crushRoot: ['default'], // default for all - is a list possible???
77       crushDeviceClass: [''], // set none to empty at submit - get list from configs?
78       directory: [''],
79       // Only for 'jerasure' and 'isa' use
80       technique: ['reed_sol_van'],
81       // Only for 'jerasure' use
82       packetSize: [2048, [Validators.min(1)]],
83       // Only for 'lrc' use
84       l: [1, [Validators.required, Validators.min(1)]],
85       crushLocality: [''], // set to none at the end (same list as for failure domains)
86       // Only for 'shec' use
87       c: [1, [Validators.required, Validators.min(1)]]
88     });
89     this.form.get('plugin').valueChanges.subscribe((plugin) => this.onPluginChange(plugin));
90   }
91
92   onPluginChange(plugin: string) {
93     this.plugin = plugin;
94     if (plugin === this.PLUGIN.JERASURE) {
95       this.setJerasureDefaults();
96     } else if (plugin === this.PLUGIN.LRC) {
97       this.setLrcDefaults();
98     } else if (plugin === this.PLUGIN.ISA) {
99       this.setIsaDefaults();
100     } else if (plugin === this.PLUGIN.SHEC) {
101       this.setShecDefaults();
102     }
103   }
104
105   private setNumberValidators(name: string, required: boolean) {
106     const validators = [Validators.min(1)];
107     if (required) {
108       validators.push(Validators.required);
109     }
110     this.form.get(name).setValidators(validators);
111   }
112
113   private setKMValidators(required: boolean) {
114     ['k', 'm'].forEach((name) => this.setNumberValidators(name, required));
115   }
116
117   private setJerasureDefaults() {
118     this.requiredControls = ['k', 'm'];
119     this.setDefaults({
120       k: 4,
121       m: 2
122     });
123     this.setKMValidators(true);
124     this.techniques = [
125       'reed_sol_van',
126       'reed_sol_r6_op',
127       'cauchy_orig',
128       'cauchy_good',
129       'liberation',
130       'blaum_roth',
131       'liber8tion'
132     ];
133   }
134
135   private setLrcDefaults() {
136     this.requiredControls = ['k', 'm', 'l'];
137     this.setKMValidators(true);
138     this.setNumberValidators('l', true);
139     this.setDefaults({
140       k: 4,
141       m: 2,
142       l: 3
143     });
144   }
145
146   private setIsaDefaults() {
147     this.requiredControls = [];
148     this.setKMValidators(false);
149     this.setDefaults({
150       k: 7,
151       m: 3
152     });
153     this.techniques = ['reed_sol_van', 'cauchy'];
154   }
155
156   private setShecDefaults() {
157     this.requiredControls = [];
158     this.setKMValidators(false);
159     this.setDefaults({
160       k: 4,
161       m: 3,
162       c: 2
163     });
164   }
165
166   private setDefaults(defaults: object) {
167     Object.keys(defaults).forEach((controlName) => {
168       if (this.form.get(controlName).pristine) {
169         this.form.silentSet(controlName, defaults[controlName]);
170       }
171     });
172   }
173
174   ngOnInit() {
175     this.ecpService
176       .getInfo()
177       .subscribe(
178         ({
179           plugins,
180           names,
181           directory,
182           nodes
183         }: {
184           plugins: string[];
185           names: string[];
186           directory: string;
187           nodes: CrushNode[];
188         }) => {
189           this.initCrushNodeSelection(
190             nodes,
191             this.form.get('crushRoot'),
192             this.form.get('crushFailureDomain'),
193             this.form.get('crushDeviceClass')
194           );
195           this.plugins = plugins;
196           this.names = names;
197           this.form.silentSet('directory', directory);
198         }
199       );
200   }
201
202   onSubmit() {
203     if (this.form.invalid) {
204       this.form.setErrors({ cdSubmitButton: true });
205       return;
206     }
207     const profile = this.createJson();
208     this.taskWrapper
209       .wrapTaskAroundCall({
210         task: new FinishedTask('ecp/create', { name: profile.name }),
211         call: this.ecpService.create(profile)
212       })
213       .subscribe(
214         undefined,
215         () => {
216           this.form.setErrors({ cdSubmitButton: true });
217         },
218         () => {
219           this.bsModalRef.hide();
220           this.submitAction.emit(profile);
221         }
222       );
223   }
224
225   private createJson() {
226     const pluginControls = {
227       technique: [this.PLUGIN.ISA, this.PLUGIN.JERASURE],
228       packetSize: [this.PLUGIN.JERASURE],
229       l: [this.PLUGIN.LRC],
230       crushLocality: [this.PLUGIN.LRC],
231       c: [this.PLUGIN.SHEC]
232     };
233     const ecp = new ErasureCodeProfile();
234     const plugin = this.form.getValue('plugin');
235     Object.keys(this.form.controls)
236       .filter((name) => {
237         const pluginControl = pluginControls[name];
238         const value = this.form.getValue(name);
239         const usable = (pluginControl && pluginControl.includes(plugin)) || !pluginControl;
240         return usable && value && value !== '';
241       })
242       .forEach((name) => {
243         this.extendJson(name, ecp);
244       });
245     return ecp;
246   }
247
248   private extendJson(name: string, ecp: ErasureCodeProfile) {
249     const differentApiAttributes = {
250       crushFailureDomain: 'crush-failure-domain',
251       crushRoot: 'crush-root',
252       crushDeviceClass: 'crush-device-class',
253       packetSize: 'packetsize',
254       crushLocality: 'crush-locality'
255     };
256     const value = this.form.getValue(name);
257     ecp[differentApiAttributes[name] || name] = name === 'crushRoot' ? value.name : value;
258   }
259 }