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