]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
cb80a2571c0ae73a2a474b555d0f2bdf54aa339c
[ceph.git] /
1 import { Component, OnInit, ViewChild } from '@angular/core';
2 import { AbstractControl, Validators } from '@angular/forms';
3 import { Router } from '@angular/router';
4
5 import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
6 import _ from 'lodash';
7 import { merge, Observable, Subject } from 'rxjs';
8 import { debounceTime, distinctUntilChanged, filter, map } from 'rxjs/operators';
9
10 import { CephServiceService } from '~/app/shared/api/ceph-service.service';
11 import { HostService } from '~/app/shared/api/host.service';
12 import { PoolService } from '~/app/shared/api/pool.service';
13 import { SelectMessages } from '~/app/shared/components/select/select-messages.model';
14 import { SelectOption } from '~/app/shared/components/select/select-option.model';
15 import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
16 import { CdForm } from '~/app/shared/forms/cd-form';
17 import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
18 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
19 import { CdValidators } from '~/app/shared/forms/cd-validators';
20 import { FinishedTask } from '~/app/shared/models/finished-task';
21 import { CephServiceSpec } from '~/app/shared/models/service.interface';
22 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
23
24 @Component({
25   selector: 'cd-service-form',
26   templateUrl: './service-form.component.html',
27   styleUrls: ['./service-form.component.scss']
28 })
29 export class ServiceFormComponent extends CdForm implements OnInit {
30   readonly RGW_SVC_ID_PATTERN = /^([^.]+)(\.([^.]+)\.([^.]+))?$/;
31   @ViewChild(NgbTypeahead, { static: false })
32   typeahead: NgbTypeahead;
33
34   serviceForm: CdFormGroup;
35   action: string;
36   resource: string;
37   serviceTypes: string[] = [];
38   hosts: any;
39   labels: string[];
40   labelClick = new Subject<string>();
41   labelFocus = new Subject<string>();
42   pools: Array<object>;
43   services: Array<CephServiceSpec> = [];
44
45   constructor(
46     public actionLabels: ActionLabelsI18n,
47     private cephServiceService: CephServiceService,
48     private formBuilder: CdFormBuilder,
49     private hostService: HostService,
50     private poolService: PoolService,
51     private router: Router,
52     private taskWrapperService: TaskWrapperService
53   ) {
54     super();
55     this.resource = $localize`service`;
56     this.hosts = {
57       options: [],
58       messages: new SelectMessages({
59         empty: $localize`There are no hosts.`,
60         filter: $localize`Filter hosts`
61       })
62     };
63     this.createForm();
64   }
65
66   createForm() {
67     this.serviceForm = this.formBuilder.group({
68       // Global
69       service_type: [null, [Validators.required]],
70       service_id: [
71         null,
72         [
73           CdValidators.requiredIf({
74             service_type: 'mds'
75           }),
76           CdValidators.requiredIf({
77             service_type: 'nfs'
78           }),
79           CdValidators.requiredIf({
80             service_type: 'iscsi'
81           }),
82           CdValidators.requiredIf({
83             service_type: 'ingress'
84           }),
85           CdValidators.composeIf(
86             {
87               service_type: 'rgw'
88             },
89             [
90               Validators.required,
91               CdValidators.custom('rgwPattern', (value: string) => {
92                 if (_.isEmpty(value)) {
93                   return false;
94                 }
95                 return !this.RGW_SVC_ID_PATTERN.test(value);
96               })
97             ]
98           )
99         ]
100       ],
101       placement: ['hosts'],
102       label: [
103         null,
104         [
105           CdValidators.requiredIf({
106             placement: 'label',
107             unmanaged: false
108           })
109         ]
110       ],
111       hosts: [[]],
112       count: [null, [CdValidators.number(false), Validators.min(1)]],
113       unmanaged: [false],
114       // NFS & iSCSI
115       pool: [
116         null,
117         [
118           CdValidators.requiredIf({
119             service_type: 'nfs',
120             unmanaged: false
121           }),
122           CdValidators.requiredIf({
123             service_type: 'iscsi',
124             unmanaged: false
125           })
126         ]
127       ],
128       // NFS
129       namespace: [null],
130       // RGW
131       rgw_frontend_port: [
132         null,
133         [CdValidators.number(false), Validators.min(1), Validators.max(65535)]
134       ],
135       // iSCSI
136       trusted_ip_list: [null],
137       api_port: [null, [CdValidators.number(false), Validators.min(1), Validators.max(65535)]],
138       api_user: [
139         null,
140         [
141           CdValidators.requiredIf({
142             service_type: 'iscsi',
143             unmanaged: false
144           })
145         ]
146       ],
147       api_password: [
148         null,
149         [
150           CdValidators.requiredIf({
151             service_type: 'iscsi',
152             unmanaged: false
153           })
154         ]
155       ],
156       // Ingress
157       backend_service: [
158         null,
159         [
160           CdValidators.requiredIf({
161             service_type: 'ingress',
162             unmanaged: false
163           })
164         ]
165       ],
166       virtual_ip: [
167         null,
168         [
169           CdValidators.requiredIf({
170             service_type: 'ingress',
171             unmanaged: false
172           })
173         ]
174       ],
175       frontend_port: [null, [CdValidators.number(false), Validators.min(1), Validators.max(65535)]],
176       monitor_port: [null, [CdValidators.number(false), Validators.min(1), Validators.max(65535)]],
177       virtual_interface_networks: [null],
178       // RGW, Ingress & iSCSI
179       ssl: [false],
180       ssl_cert: [
181         '',
182         [
183           CdValidators.composeIf(
184             {
185               service_type: 'rgw',
186               unmanaged: false,
187               ssl: true
188             },
189             [Validators.required, CdValidators.pemCert()]
190           ),
191           CdValidators.composeIf(
192             {
193               service_type: 'iscsi',
194               unmanaged: false,
195               ssl: true
196             },
197             [Validators.required, CdValidators.sslCert()]
198           )
199         ]
200       ],
201       ssl_key: [
202         '',
203         [
204           CdValidators.composeIf(
205             {
206               service_type: 'iscsi',
207               unmanaged: false,
208               ssl: true
209             },
210             [Validators.required, CdValidators.sslPrivKey()]
211           )
212         ]
213       ]
214     });
215   }
216
217   ngOnInit(): void {
218     this.action = this.actionLabels.CREATE;
219     this.cephServiceService.getKnownTypes().subscribe((resp: Array<string>) => {
220       // Remove service types:
221       // osd       - This is deployed a different way.
222       // container - This should only be used in the CLI.
223       this.serviceTypes = _.difference(resp, ['container', 'osd']).sort();
224     });
225     this.hostService.list().subscribe((resp: object[]) => {
226       const options: SelectOption[] = [];
227       _.forEach(resp, (host: object) => {
228         if (_.get(host, 'sources.orchestrator', false)) {
229           const option = new SelectOption(false, _.get(host, 'hostname'), '');
230           options.push(option);
231         }
232       });
233       this.hosts.options = [...options];
234     });
235     this.hostService.getLabels().subscribe((resp: string[]) => {
236       this.labels = resp;
237     });
238     this.poolService.getList().subscribe((resp: Array<object>) => {
239       this.pools = resp;
240     });
241     this.cephServiceService.list().subscribe((services: CephServiceSpec[]) => {
242       this.services = services.filter((service: any) => service.service_type === 'rgw');
243     });
244   }
245
246   goToListView() {
247     this.router.navigate(['/services']);
248   }
249
250   searchLabels = (text$: Observable<string>) => {
251     return merge(
252       text$.pipe(debounceTime(200), distinctUntilChanged()),
253       this.labelFocus,
254       this.labelClick.pipe(filter(() => !this.typeahead.isPopupOpen()))
255     ).pipe(
256       map((value) =>
257         this.labels
258           .filter((label: string) => label.toLowerCase().indexOf(value.toLowerCase()) > -1)
259           .slice(0, 10)
260       )
261     );
262   };
263
264   fileUpload(files: FileList, controlName: string) {
265     const file: File = files[0];
266     const reader = new FileReader();
267     reader.addEventListener('load', (event: ProgressEvent<FileReader>) => {
268       const control: AbstractControl = this.serviceForm.get(controlName);
269       control.setValue(event.target.result);
270       control.markAsDirty();
271       control.markAsTouched();
272       control.updateValueAndValidity();
273     });
274     reader.readAsText(file, 'utf8');
275   }
276
277   prePopulateId() {
278     const control: AbstractControl = this.serviceForm.get('service_id');
279     const backendService = this.serviceForm.getValue('backend_service');
280     // Set Id as read-only
281     control.reset({ value: backendService, disabled: true });
282   }
283
284   onSubmit() {
285     const self = this;
286     const values: object = this.serviceForm.value;
287     const serviceType: string = values['service_type'];
288     const serviceSpec: object = {
289       service_type: serviceType,
290       placement: {},
291       unmanaged: values['unmanaged']
292     };
293     let svcId: string;
294     if (serviceType === 'rgw') {
295       const svcIdMatch = values['service_id'].match(this.RGW_SVC_ID_PATTERN);
296       svcId = svcIdMatch[1];
297       if (svcIdMatch[3]) {
298         serviceSpec['rgw_realm'] = svcIdMatch[3];
299         serviceSpec['rgw_zone'] = svcIdMatch[4];
300       }
301     } else {
302       svcId = values['service_id'];
303     }
304     const serviceId: string = svcId;
305     let serviceName: string = serviceType;
306     if (_.isString(serviceId) && !_.isEmpty(serviceId)) {
307       serviceName = `${serviceType}.${serviceId}`;
308       serviceSpec['service_id'] = serviceId;
309     }
310     if (!values['unmanaged']) {
311       switch (values['placement']) {
312         case 'hosts':
313           if (values['hosts'].length > 0) {
314             serviceSpec['placement']['hosts'] = values['hosts'];
315           }
316           break;
317         case 'label':
318           serviceSpec['placement']['label'] = values['label'];
319           break;
320       }
321       if (_.isNumber(values['count']) && values['count'] > 0) {
322         serviceSpec['placement']['count'] = values['count'];
323       }
324       switch (serviceType) {
325         case 'nfs':
326           serviceSpec['pool'] = values['pool'];
327           if (_.isString(values['namespace']) && !_.isEmpty(values['namespace'])) {
328             serviceSpec['namespace'] = values['namespace'];
329           }
330           break;
331         case 'rgw':
332           if (_.isNumber(values['rgw_frontend_port']) && values['rgw_frontend_port'] > 0) {
333             serviceSpec['rgw_frontend_port'] = values['rgw_frontend_port'];
334           }
335           serviceSpec['ssl'] = values['ssl'];
336           if (values['ssl']) {
337             serviceSpec['rgw_frontend_ssl_certificate'] = values['ssl_cert'].trim();
338           }
339           break;
340         case 'iscsi':
341           serviceSpec['pool'] = values['pool'];
342           if (_.isString(values['trusted_ip_list']) && !_.isEmpty(values['trusted_ip_list'])) {
343             serviceSpec['trusted_ip_list'] = values['trusted_ip_list'].trim();
344           }
345           if (_.isNumber(values['api_port']) && values['api_port'] > 0) {
346             serviceSpec['api_port'] = values['api_port'];
347           }
348           serviceSpec['api_user'] = values['api_user'];
349           serviceSpec['api_password'] = values['api_password'];
350           serviceSpec['api_secure'] = values['ssl'];
351           if (values['ssl']) {
352             serviceSpec['ssl_cert'] = values['ssl_cert'].trim();
353             serviceSpec['ssl_key'] = values['ssl_key'].trim();
354           }
355           break;
356         case 'ingress':
357           serviceSpec['backend_service'] = values['backend_service'];
358           serviceSpec['service_id'] = values['backend_service'];
359           if (_.isString(values['virtual_ip']) && !_.isEmpty(values['virtual_ip'])) {
360             serviceSpec['virtual_ip'] = values['virtual_ip'].trim();
361           }
362           if (_.isNumber(values['frontend_port']) && values['frontend_port'] > 0) {
363             serviceSpec['frontend_port'] = values['frontend_port'];
364           }
365           if (_.isNumber(values['monitor_port']) && values['monitor_port'] > 0) {
366             serviceSpec['monitor_port'] = values['monitor_port'];
367           }
368           serviceSpec['ssl'] = values['ssl'];
369           if (values['ssl']) {
370             serviceSpec['ssl_cert'] = values['ssl_cert'].trim();
371             serviceSpec['ssl_key'] = values['ssl_key'].trim();
372           }
373           serviceSpec['virtual_interface_networks'] = values['virtual_interface_networks'];
374           break;
375       }
376     }
377     this.taskWrapperService
378       .wrapTaskAroundCall({
379         task: new FinishedTask(`service/${URLVerbs.CREATE}`, {
380           service_name: serviceName
381         }),
382         call: this.cephServiceService.create(serviceSpec)
383       })
384       .subscribe({
385         error() {
386           self.serviceForm.setErrors({ cdSubmitButton: true });
387         },
388         complete() {
389           self.goToListView();
390         }
391       });
392   }
393 }