]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
6c33024d417f1e98e68efdb601dae147b505fa2b
[ceph-ci.git] /
1 import { HttpParams } from '@angular/common/http';
2 import { Component, Input, OnInit, ViewChild } from '@angular/core';
3 import { AbstractControl, UntypedFormControl, Validators } from '@angular/forms';
4 import { ActivatedRoute, Router } from '@angular/router';
5
6 import { NgbActiveModal, NgbModalRef, NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
7 import _ from 'lodash';
8 import { forkJoin, merge, Observable, Subject, Subscription } from 'rxjs';
9 import { debounceTime, distinctUntilChanged, filter, map } from 'rxjs/operators';
10 import { Pool } from '~/app/ceph/pool/pool';
11 import { CreateRgwServiceEntitiesComponent } from '~/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component';
12 import { RgwRealm, RgwZonegroup, RgwZone } from '~/app/ceph/rgw/models/rgw-multisite';
13
14 import { CephServiceService } from '~/app/shared/api/ceph-service.service';
15 import { HostService } from '~/app/shared/api/host.service';
16 import { PoolService } from '~/app/shared/api/pool.service';
17 import { RbdService } from '~/app/shared/api/rbd.service';
18 import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
19 import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
20 import { RgwZoneService } from '~/app/shared/api/rgw-zone.service';
21 import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
22 import { SelectMessages } from '~/app/shared/components/select/select-messages.model';
23 import { SelectOption } from '~/app/shared/components/select/select-option.model';
24 import {
25   ActionLabelsI18n,
26   TimerServiceInterval,
27   URLVerbs
28 } from '~/app/shared/constants/app.constants';
29 import { CdForm } from '~/app/shared/forms/cd-form';
30 import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
31 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
32 import { CdValidators } from '~/app/shared/forms/cd-validators';
33 import { FinishedTask } from '~/app/shared/models/finished-task';
34 import { CephServiceSpec } from '~/app/shared/models/service.interface';
35 import { ModalService } from '~/app/shared/services/modal.service';
36 import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
37 import { TimerService } from '~/app/shared/services/timer.service';
38
39 @Component({
40   selector: 'cd-service-form',
41   templateUrl: './service-form.component.html',
42   styleUrls: ['./service-form.component.scss']
43 })
44 export class ServiceFormComponent extends CdForm implements OnInit {
45   public sub = new Subscription();
46
47   readonly MDS_SVC_ID_PATTERN = /^[a-zA-Z_.-][a-zA-Z0-9_.-]*$/;
48   readonly SNMP_DESTINATION_PATTERN = /^[^\:]+:[0-9]/;
49   readonly SNMP_ENGINE_ID_PATTERN = /^[0-9A-Fa-f]{10,64}/g;
50   readonly INGRESS_SUPPORTED_SERVICE_TYPES = ['rgw', 'nfs'];
51   readonly SMB_CONFIG_URI_PATTERN = /^(http:|https:|rados:|rados:mon-config-key:)/;
52   @ViewChild(NgbTypeahead, { static: false })
53   typeahead: NgbTypeahead;
54
55   @Input() hiddenServices: string[] = [];
56
57   @Input() editing = false;
58
59   @Input() serviceName: string;
60
61   @Input() serviceType: string;
62
63   serviceForm: CdFormGroup;
64   action: string;
65   resource: string;
66   serviceTypes: string[] = [];
67   serviceIds: string[] = [];
68   hosts: any;
69   labels: string[];
70   labelClick = new Subject<string>();
71   labelFocus = new Subject<string>();
72   pools: Array<Pool>;
73   rbdPools: Array<Pool>;
74   services: Array<CephServiceSpec> = [];
75   pageURL: string;
76   serviceList: CephServiceSpec[];
77   multisiteInfo: object[] = [];
78   defaultRealmId = '';
79   defaultZonegroupId = '';
80   defaultZoneId = '';
81   realmList: RgwRealm[] = [];
82   zonegroupList: RgwZonegroup[] = [];
83   zoneList: RgwZone[] = [];
84   bsModalRef: NgbModalRef;
85   defaultZonegroup: RgwZonegroup;
86   showRealmCreationForm = false;
87   defaultsInfo: { defaultRealmName: string; defaultZonegroupName: string; defaultZoneName: string };
88   realmNames: string[];
89   zonegroupNames: string[];
90   zoneNames: string[];
91   smbFeaturesList = ['domain'];
92
93   constructor(
94     public actionLabels: ActionLabelsI18n,
95     private cephServiceService: CephServiceService,
96     private formBuilder: CdFormBuilder,
97     private hostService: HostService,
98     private poolService: PoolService,
99     private rbdService: RbdService,
100     private router: Router,
101     private taskWrapperService: TaskWrapperService,
102     public timerService: TimerService,
103     public timerServiceVariable: TimerServiceInterval,
104     public rgwRealmService: RgwRealmService,
105     public rgwZonegroupService: RgwZonegroupService,
106     public rgwZoneService: RgwZoneService,
107     public rgwMultisiteService: RgwMultisiteService,
108     private route: ActivatedRoute,
109     public activeModal: NgbActiveModal,
110     public modalService: ModalService
111   ) {
112     super();
113     this.resource = $localize`service`;
114     this.hosts = {
115       options: [],
116       messages: new SelectMessages({
117         empty: $localize`There are no hosts.`,
118         filter: $localize`Filter hosts`
119       })
120     };
121     this.createForm();
122   }
123
124   createForm() {
125     this.serviceForm = this.formBuilder.group({
126       // Global
127       service_type: [null, [Validators.required]],
128       service_id: [
129         null,
130         [
131           CdValidators.composeIf(
132             {
133               service_type: 'mds'
134             },
135             [
136               Validators.required,
137               CdValidators.custom('mdsPattern', (value: string) => {
138                 if (_.isEmpty(value)) {
139                   return false;
140                 }
141                 return !this.MDS_SVC_ID_PATTERN.test(value);
142               })
143             ]
144           ),
145           CdValidators.requiredIf({
146             service_type: 'nfs'
147           }),
148           CdValidators.requiredIf({
149             service_type: 'iscsi'
150           }),
151           CdValidators.requiredIf({
152             service_type: 'nvmeof'
153           }),
154           CdValidators.requiredIf({
155             service_type: 'ingress'
156           }),
157           CdValidators.requiredIf({
158             service_type: 'smb'
159           }),
160           CdValidators.composeIf(
161             {
162               service_type: 'rgw'
163             },
164             [Validators.required]
165           ),
166           CdValidators.custom('uniqueName', (service_id: string) => {
167             return this.serviceIds && this.serviceIds.includes(service_id);
168           })
169         ]
170       ],
171       placement: ['hosts'],
172       label: [
173         null,
174         [
175           CdValidators.requiredIf({
176             placement: 'label',
177             unmanaged: false
178           })
179         ]
180       ],
181       hosts: [[]],
182       count: [null, [CdValidators.number(false)]],
183       unmanaged: [false],
184       // iSCSI
185       // NVMe/TCP
186       pool: [
187         null,
188         [
189           CdValidators.requiredIf({
190             service_type: 'iscsi'
191           }),
192           CdValidators.requiredIf({
193             service_type: 'nvmeof'
194           })
195         ]
196       ],
197       group: [
198         'default',
199         CdValidators.requiredIf({
200           service_type: 'nvmeof'
201         })
202       ],
203       // RGW
204       rgw_frontend_port: [null, [CdValidators.number(false)]],
205       realm_name: [null],
206       zonegroup_name: [null],
207       zone_name: [null],
208       // iSCSI
209       trusted_ip_list: [null],
210       api_port: [null, [CdValidators.number(false)]],
211       api_user: [
212         null,
213         [
214           CdValidators.requiredIf({
215             service_type: 'iscsi',
216             unmanaged: false
217           })
218         ]
219       ],
220       api_password: [
221         null,
222         [
223           CdValidators.requiredIf({
224             service_type: 'iscsi',
225             unmanaged: false
226           })
227         ]
228       ],
229       // smb
230       cluster_id: [
231         null,
232         [
233           CdValidators.requiredIf({
234             service_type: 'smb'
235           })
236         ]
237       ],
238       features: new CdFormGroup(
239         this.smbFeaturesList.reduce((acc: object, e) => {
240           acc[e] = new UntypedFormControl(false);
241           return acc;
242         }, {})
243       ),
244       config_uri: [
245         null,
246         [
247           CdValidators.composeIf(
248             {
249               service_type: 'smb'
250             },
251             [
252               Validators.required,
253               CdValidators.custom('configUriPattern', (value: string) => {
254                 if (_.isEmpty(value)) {
255                   return false;
256                 }
257                 return !this.SMB_CONFIG_URI_PATTERN.test(value);
258               })
259             ]
260           )
261         ]
262       ],
263       custom_dns: [null],
264       join_sources: [null],
265       user_sources: [null],
266       include_ceph_users: [null],
267       // Ingress
268       backend_service: [
269         null,
270         [
271           CdValidators.requiredIf({
272             service_type: 'ingress'
273           })
274         ]
275       ],
276       virtual_ip: [
277         null,
278         [
279           CdValidators.requiredIf({
280             service_type: 'ingress'
281           })
282         ]
283       ],
284       frontend_port: [
285         null,
286         [
287           CdValidators.number(false),
288           CdValidators.requiredIf({
289             service_type: 'ingress'
290           })
291         ]
292       ],
293       monitor_port: [
294         null,
295         [
296           CdValidators.number(false),
297           CdValidators.requiredIf({
298             service_type: 'ingress'
299           })
300         ]
301       ],
302       virtual_interface_networks: [null],
303       // RGW, Ingress & iSCSI
304       ssl: [false],
305       ssl_cert: [
306         '',
307         [
308           CdValidators.composeIf(
309             {
310               service_type: 'rgw',
311               unmanaged: false,
312               ssl: true
313             },
314             [Validators.required, CdValidators.pemCert()]
315           ),
316           CdValidators.composeIf(
317             {
318               service_type: 'iscsi',
319               unmanaged: false,
320               ssl: true
321             },
322             [Validators.required, CdValidators.sslCert()]
323           ),
324           CdValidators.composeIf(
325             {
326               service_type: 'ingress',
327               unmanaged: false,
328               ssl: true
329             },
330             [Validators.required, CdValidators.pemCert()]
331           )
332         ]
333       ],
334       ssl_key: [
335         '',
336         [
337           CdValidators.composeIf(
338             {
339               service_type: 'iscsi',
340               unmanaged: false,
341               ssl: true
342             },
343             [Validators.required, CdValidators.sslPrivKey()]
344           )
345         ]
346       ],
347       // snmp-gateway
348       snmp_version: [
349         null,
350         [
351           CdValidators.requiredIf({
352             service_type: 'snmp-gateway'
353           })
354         ]
355       ],
356       snmp_destination: [
357         null,
358         {
359           validators: [
360             CdValidators.requiredIf({
361               service_type: 'snmp-gateway'
362             }),
363             CdValidators.custom('snmpDestinationPattern', (value: string) => {
364               if (_.isEmpty(value)) {
365                 return false;
366               }
367               return !this.SNMP_DESTINATION_PATTERN.test(value);
368             })
369           ]
370         }
371       ],
372       engine_id: [
373         null,
374         [
375           CdValidators.requiredIf({
376             service_type: 'snmp-gateway'
377           }),
378           CdValidators.custom('snmpEngineIdPattern', (value: string) => {
379             if (_.isEmpty(value)) {
380               return false;
381             }
382             return !this.SNMP_ENGINE_ID_PATTERN.test(value);
383           })
384         ]
385       ],
386       auth_protocol: [
387         'SHA',
388         [
389           CdValidators.requiredIf({
390             service_type: 'snmp-gateway'
391           })
392         ]
393       ],
394       privacy_protocol: [null],
395       snmp_community: [
396         null,
397         [
398           CdValidators.requiredIf({
399             snmp_version: 'V2c'
400           })
401         ]
402       ],
403       snmp_v3_auth_username: [
404         null,
405         [
406           CdValidators.requiredIf({
407             service_type: 'snmp-gateway'
408           })
409         ]
410       ],
411       snmp_v3_auth_password: [
412         null,
413         [
414           CdValidators.requiredIf({
415             service_type: 'snmp-gateway'
416           })
417         ]
418       ],
419       snmp_v3_priv_password: [
420         null,
421         [
422           CdValidators.requiredIf({
423             privacy_protocol: { op: '!empty' }
424           })
425         ]
426       ],
427       grafana_port: [null, [CdValidators.number(false)]],
428       grafana_admin_password: [null]
429     });
430   }
431
432   resolveRoute() {
433     if (this.router.url.includes('services/(modal:create')) {
434       this.pageURL = 'services';
435       this.route.params.subscribe((params: { type: string }) => {
436         if (params?.type) {
437           this.serviceType = params.type;
438           this.serviceForm.get('service_type').setValue(this.serviceType);
439         }
440       });
441     } else if (this.router.url.includes('services/(modal:edit')) {
442       this.editing = true;
443       this.pageURL = 'services';
444       this.route.params.subscribe((params: { type: string; name: string }) => {
445         this.serviceName = params.name;
446         this.serviceType = params.type;
447       });
448     }
449   }
450
451   ngOnInit(): void {
452     this.action = this.actionLabels.CREATE;
453     this.resolveRoute();
454
455     this.cephServiceService
456       .list(new HttpParams({ fromObject: { limit: -1, offset: 0 } }))
457       .observable.subscribe((services: CephServiceSpec[]) => {
458         this.serviceList = services;
459         this.services = services.filter((service: any) =>
460           this.INGRESS_SUPPORTED_SERVICE_TYPES.includes(service.service_type)
461         );
462       });
463
464     this.cephServiceService.getKnownTypes().subscribe((resp: Array<string>) => {
465       // Remove service types:
466       // osd       - This is deployed a different way.
467       // container - This should only be used in the CLI.
468       this.hiddenServices.push('osd', 'container');
469
470       this.serviceTypes = _.difference(resp, this.hiddenServices).sort();
471     });
472     this.hostService.getAllHosts().subscribe((resp: object[]) => {
473       const options: SelectOption[] = [];
474       _.forEach(resp, (host: object) => {
475         if (_.get(host, 'sources.orchestrator', false)) {
476           const option = new SelectOption(false, _.get(host, 'hostname'), '');
477           options.push(option);
478         }
479       });
480       this.hosts.options = [...options];
481     });
482     this.hostService.getLabels().subscribe((resp: string[]) => {
483       this.labels = resp;
484     });
485     this.poolService.getList().subscribe((resp: Pool[]) => {
486       this.pools = resp;
487       this.rbdPools = this.pools.filter(this.rbdService.isRBDPool);
488       if (!this.editing && this.serviceType) {
489         this.onServiceTypeChange(this.serviceType);
490       }
491     });
492
493     if (this.editing) {
494       this.action = this.actionLabels.EDIT;
495       this.disableForEditing(this.serviceType);
496       this.cephServiceService
497         .list(new HttpParams({ fromObject: { limit: -1, offset: 0 } }), this.serviceName)
498         .observable.subscribe((response: CephServiceSpec[]) => {
499           const formKeys = ['service_type', 'service_id', 'unmanaged'];
500           formKeys.forEach((keys) => {
501             this.serviceForm.get(keys).setValue(response[0][keys]);
502           });
503           if (!response[0]['unmanaged']) {
504             const placementKey = Object.keys(response[0]['placement'])[0];
505             let placementValue: string;
506             ['hosts', 'label'].indexOf(placementKey) >= 0
507               ? (placementValue = placementKey)
508               : (placementValue = 'hosts');
509             this.serviceForm.get('placement').setValue(placementValue);
510             this.serviceForm.get('count').setValue(response[0]['placement']['count']);
511             if (response[0]?.placement[placementValue]) {
512               this.serviceForm.get(placementValue).setValue(response[0]?.placement[placementValue]);
513             }
514           }
515           switch (this.serviceType) {
516             case 'iscsi':
517               const specKeys = ['pool', 'api_password', 'api_user', 'trusted_ip_list', 'api_port'];
518               specKeys.forEach((key) => {
519                 this.serviceForm.get(key).setValue(response[0].spec[key]);
520               });
521               this.serviceForm.get('ssl').setValue(response[0].spec?.api_secure);
522               if (response[0].spec?.api_secure) {
523                 this.serviceForm.get('ssl_cert').setValue(response[0].spec?.ssl_cert);
524                 this.serviceForm.get('ssl_key').setValue(response[0].spec?.ssl_key);
525               }
526               break;
527             case 'nvmeof':
528               this.serviceForm.get('pool').setValue(response[0].spec.pool);
529               this.serviceForm.get('group').setValue(response[0].spec.group);
530               break;
531             case 'rgw':
532               this.serviceForm
533                 .get('rgw_frontend_port')
534                 .setValue(response[0].spec?.rgw_frontend_port);
535               this.setRgwFields(
536                 response[0].spec?.rgw_realm,
537                 response[0].spec?.rgw_zonegroup,
538                 response[0].spec?.rgw_zone
539               );
540               this.serviceForm.get('ssl').setValue(response[0].spec?.ssl);
541               if (response[0].spec?.ssl) {
542                 this.serviceForm
543                   .get('ssl_cert')
544                   .setValue(response[0].spec?.rgw_frontend_ssl_certificate);
545               }
546               break;
547             case 'ingress':
548               const ingressSpecKeys = [
549                 'backend_service',
550                 'virtual_ip',
551                 'frontend_port',
552                 'monitor_port',
553                 'virtual_interface_networks',
554                 'ssl'
555               ];
556               ingressSpecKeys.forEach((key) => {
557                 this.serviceForm.get(key).setValue(response[0].spec[key]);
558               });
559               if (response[0].spec?.ssl) {
560                 this.serviceForm.get('ssl_cert').setValue(response[0].spec?.ssl_cert);
561                 this.serviceForm.get('ssl_key').setValue(response[0].spec?.ssl_key);
562               }
563               break;
564             case 'smb':
565               const smbSpecKeys = [
566                 'cluster_id',
567                 'config_uri',
568                 'features',
569                 'join_sources',
570                 'user_sources',
571                 'custom_dns',
572                 'include_ceph_users'
573               ];
574               smbSpecKeys.forEach((key) => {
575                 if (key === 'features') {
576                   if (response[0].spec?.features) {
577                     response[0].spec.features.forEach((feature) => {
578                       this.serviceForm.get(`features.${feature}`).setValue(true);
579                     });
580                   }
581                 } else {
582                   this.serviceForm.get(key).setValue(response[0].spec[key]);
583                 }
584               });
585               break;
586             case 'snmp-gateway':
587               const snmpCommonSpecKeys = ['snmp_version', 'snmp_destination'];
588               snmpCommonSpecKeys.forEach((key) => {
589                 this.serviceForm.get(key).setValue(response[0].spec[key]);
590               });
591               if (this.serviceForm.getValue('snmp_version') === 'V3') {
592                 const snmpV3SpecKeys = [
593                   'engine_id',
594                   'auth_protocol',
595                   'privacy_protocol',
596                   'snmp_v3_auth_username',
597                   'snmp_v3_auth_password',
598                   'snmp_v3_priv_password'
599                 ];
600                 snmpV3SpecKeys.forEach((key) => {
601                   if (key !== null) {
602                     if (
603                       key === 'snmp_v3_auth_username' ||
604                       key === 'snmp_v3_auth_password' ||
605                       key === 'snmp_v3_priv_password'
606                     ) {
607                       this.serviceForm.get(key).setValue(response[0].spec['credentials'][key]);
608                     } else {
609                       this.serviceForm.get(key).setValue(response[0].spec[key]);
610                     }
611                   }
612                 });
613               } else {
614                 this.serviceForm
615                   .get('snmp_community')
616                   .setValue(response[0].spec['credentials']['snmp_community']);
617               }
618               break;
619             case 'grafana':
620               this.serviceForm.get('grafana_port').setValue(response[0].spec.port);
621               this.serviceForm
622                 .get('grafana_admin_password')
623                 .setValue(response[0].spec.initial_admin_password);
624               break;
625           }
626         });
627     }
628   }
629
630   getDefaultsEntitiesForRgw(
631     defaultRealmId: string,
632     defaultZonegroupId: string,
633     defaultZoneId: string
634   ): { defaultRealmName: string; defaultZonegroupName: string; defaultZoneName: string } {
635     const defaultRealm = this.realmList.find((x: { id: string }) => x.id === defaultRealmId);
636     const defaultZonegroup = this.zonegroupList.find(
637       (x: { id: string }) => x.id === defaultZonegroupId
638     );
639     const defaultZone = this.zoneList.find((x: { id: string }) => x.id === defaultZoneId);
640     const defaultRealmName = defaultRealm !== undefined ? defaultRealm.name : null;
641     const defaultZonegroupName = defaultZonegroup !== undefined ? defaultZonegroup.name : 'default';
642     const defaultZoneName = defaultZone !== undefined ? defaultZone.name : 'default';
643     if (defaultZonegroupName === 'default' && !this.zonegroupNames.includes(defaultZonegroupName)) {
644       const defaultZonegroup = new RgwZonegroup();
645       defaultZonegroup.name = 'default';
646       this.zonegroupList.push(defaultZonegroup);
647     }
648     if (defaultZoneName === 'default' && !this.zoneNames.includes(defaultZoneName)) {
649       const defaultZone = new RgwZone();
650       defaultZone.name = 'default';
651       this.zoneList.push(defaultZone);
652     }
653     return {
654       defaultRealmName: defaultRealmName,
655       defaultZonegroupName: defaultZonegroupName,
656       defaultZoneName: defaultZoneName
657     };
658   }
659
660   getDefaultPlacementCount(serviceType: string) {
661     /**
662      * `defaults` from src/pybind/mgr/cephadm/module.py
663      */
664     switch (serviceType) {
665       case 'mon':
666         this.serviceForm.get('count').setValue(5);
667         break;
668       case 'mgr':
669       case 'mds':
670       case 'rgw':
671       case 'ingress':
672       case 'rbd-mirror':
673         this.serviceForm.get('count').setValue(2);
674         break;
675       case 'iscsi':
676       case 'cephfs-mirror':
677       case 'nfs':
678       case 'grafana':
679       case 'alertmanager':
680       case 'prometheus':
681       case 'loki':
682       case 'container':
683       case 'snmp-gateway':
684       case 'elastic-serach':
685       case 'jaeger-collector':
686       case 'jaeger-query':
687       case 'smb':
688         this.serviceForm.get('count').setValue(1);
689         break;
690       default:
691         this.serviceForm.get('count').setValue(null);
692     }
693   }
694
695   setRgwFields(realm_name?: string, zonegroup_name?: string, zone_name?: string) {
696     const observables = [
697       this.rgwRealmService.getAllRealmsInfo(),
698       this.rgwZonegroupService.getAllZonegroupsInfo(),
699       this.rgwZoneService.getAllZonesInfo()
700     ];
701     this.sub = forkJoin(observables).subscribe(
702       (multisiteInfo: [object, object, object]) => {
703         this.multisiteInfo = multisiteInfo;
704         this.realmList =
705           this.multisiteInfo[0] !== undefined && this.multisiteInfo[0].hasOwnProperty('realms')
706             ? this.multisiteInfo[0]['realms']
707             : [];
708         this.zonegroupList =
709           this.multisiteInfo[1] !== undefined && this.multisiteInfo[1].hasOwnProperty('zonegroups')
710             ? this.multisiteInfo[1]['zonegroups']
711             : [];
712         this.zoneList =
713           this.multisiteInfo[2] !== undefined && this.multisiteInfo[2].hasOwnProperty('zones')
714             ? this.multisiteInfo[2]['zones']
715             : [];
716         this.realmNames = this.realmList.map((realm) => {
717           return realm['name'];
718         });
719         this.zonegroupNames = this.zonegroupList.map((zonegroup) => {
720           return zonegroup['name'];
721         });
722         this.zoneNames = this.zoneList.map((zone) => {
723           return zone['name'];
724         });
725         this.defaultRealmId = multisiteInfo[0]['default_realm'];
726         this.defaultZonegroupId = multisiteInfo[1]['default_zonegroup'];
727         this.defaultZoneId = multisiteInfo[2]['default_zone'];
728         this.defaultsInfo = this.getDefaultsEntitiesForRgw(
729           this.defaultRealmId,
730           this.defaultZonegroupId,
731           this.defaultZoneId
732         );
733         if (!this.editing) {
734           this.serviceForm.get('realm_name').setValue(this.defaultsInfo['defaultRealmName']);
735           this.serviceForm
736             .get('zonegroup_name')
737             .setValue(this.defaultsInfo['defaultZonegroupName']);
738           this.serviceForm.get('zone_name').setValue(this.defaultsInfo['defaultZoneName']);
739         } else {
740           if (realm_name && !this.realmNames.includes(realm_name)) {
741             const realm = new RgwRealm();
742             realm.name = realm_name;
743             this.realmList.push(realm);
744           }
745           if (zonegroup_name && !this.zonegroupNames.includes(zonegroup_name)) {
746             const zonegroup = new RgwZonegroup();
747             zonegroup.name = zonegroup_name;
748             this.zonegroupList.push(zonegroup);
749           }
750           if (zone_name && !this.zoneNames.includes(zone_name)) {
751             const zone = new RgwZone();
752             zone.name = zone_name;
753             this.zoneList.push(zone);
754           }
755           if (zonegroup_name === undefined && zone_name === undefined) {
756             zonegroup_name = 'default';
757             zone_name = 'default';
758           }
759           this.serviceForm.get('realm_name').setValue(realm_name);
760           this.serviceForm.get('zonegroup_name').setValue(zonegroup_name);
761           this.serviceForm.get('zone_name').setValue(zone_name);
762         }
763         if (this.realmList.length === 0) {
764           this.showRealmCreationForm = true;
765         } else {
766           this.showRealmCreationForm = false;
767         }
768       },
769       (_error) => {
770         const defaultZone = new RgwZone();
771         defaultZone.name = 'default';
772         const defaultZonegroup = new RgwZonegroup();
773         defaultZonegroup.name = 'default';
774         this.zoneList.push(defaultZone);
775         this.zonegroupList.push(defaultZonegroup);
776       }
777     );
778   }
779
780   onNvmeofGroupChange(groupName: string) {
781     const pool = this.serviceForm.get('pool').value;
782     if (pool) this.serviceForm.get('service_id').setValue(`${pool}.${groupName}`);
783     else this.serviceForm.get('service_id').setValue(groupName);
784   }
785
786   getDefaultBlockPool(): string {
787     // returns 'rbd' pool otherwise the first block pool
788     return (
789       this.rbdPools?.find((p: Pool) => p.pool_name === 'rbd')?.pool_name ||
790       this.rbdPools?.[0].pool_name
791     );
792   }
793
794   setNvmeofServiceIdAndPool(): void {
795     const defaultBlockPool: string = this.getDefaultBlockPool();
796     const group: string = this.serviceForm.get('group').value;
797     if (defaultBlockPool && group) {
798       this.serviceForm.get('pool').setValue(defaultBlockPool);
799       this.serviceForm.get('service_id').setValue(`${defaultBlockPool}.${group}`);
800     } else {
801       this.serviceForm.get('service_id').setValue(null);
802     }
803   }
804
805   requiresServiceId(serviceType: string) {
806     return ['mds', 'rgw', 'nfs', 'iscsi', 'nvmeof', 'smb', 'ingress'].includes(serviceType);
807   }
808
809   setServiceId(serviceId: string): void {
810     const requiresServiceId: boolean = this.requiresServiceId(serviceId);
811     if (requiresServiceId && serviceId === 'nvmeof') {
812       this.setNvmeofServiceIdAndPool();
813     } else if (requiresServiceId) {
814       this.serviceForm.get('service_id').setValue(null);
815     } else {
816       this.serviceForm.get('service_id').setValue(serviceId);
817     }
818   }
819
820   onServiceTypeChange(selectedServiceType: string) {
821     this.setServiceId(selectedServiceType);
822
823     this.serviceIds = this.serviceList
824       ?.filter((service) => service['service_type'] === selectedServiceType)
825       .map((service) => service['service_id']);
826
827     this.getDefaultPlacementCount(selectedServiceType);
828
829     if (selectedServiceType === 'rgw') {
830       this.setRgwFields();
831     }
832   }
833
834   onPlacementChange(selected: string) {
835     if (selected === 'label') {
836       this.serviceForm.get('count').setValue(null);
837     }
838   }
839
840   onBlockPoolChange() {
841     const selectedBlockPool = this.serviceForm.get('pool').value;
842     const group = this.serviceForm.get('group').value;
843     if (selectedBlockPool && group) {
844       this.serviceForm.get('service_id').setValue(`${selectedBlockPool}.${group}`);
845     } else if (selectedBlockPool) {
846       this.serviceForm.get('service_id').setValue(selectedBlockPool);
847     } else {
848       this.serviceForm.get('service_id').setValue(null);
849     }
850   }
851
852   disableForEditing(serviceType: string) {
853     const disableForEditKeys = ['service_type', 'service_id'];
854     disableForEditKeys.forEach((key) => {
855       this.serviceForm.get(key).disable();
856     });
857     switch (serviceType) {
858       case 'ingress':
859         this.serviceForm.get('backend_service').disable();
860         break;
861       case 'nvmeof':
862         this.serviceForm.get('pool').disable();
863         this.serviceForm.get('group').disable();
864         break;
865     }
866   }
867
868   searchLabels = (text$: Observable<string>) => {
869     return merge(
870       text$.pipe(debounceTime(200), distinctUntilChanged()),
871       this.labelFocus,
872       this.labelClick.pipe(filter(() => !this.typeahead.isPopupOpen()))
873     ).pipe(
874       map((value) =>
875         this.labels
876           .filter((label: string) => label.toLowerCase().indexOf(value.toLowerCase()) > -1)
877           .slice(0, 10)
878       )
879     );
880   };
881
882   fileUpload(files: FileList, controlName: string) {
883     const file: File = files[0];
884     const reader = new FileReader();
885     reader.addEventListener('load', (event: ProgressEvent<FileReader>) => {
886       const control: AbstractControl = this.serviceForm.get(controlName);
887       control.setValue(event.target.result);
888       control.markAsDirty();
889       control.markAsTouched();
890       control.updateValueAndValidity();
891     });
892     reader.readAsText(file, 'utf8');
893   }
894
895   prePopulateId() {
896     const control: AbstractControl = this.serviceForm.get('service_id');
897     const backendService = this.serviceForm.getValue('backend_service');
898     // Set Id as read-only
899     control.reset({ value: backendService, disabled: true });
900   }
901
902   onSubmit() {
903     const self = this;
904     const values: object = this.serviceForm.getRawValue();
905     const serviceType: string = values['service_type'];
906     let taskUrl = `service/${URLVerbs.CREATE}`;
907     if (this.editing) {
908       taskUrl = `service/${URLVerbs.EDIT}`;
909     }
910     const serviceSpec: object = {
911       service_type: serviceType,
912       placement: {},
913       unmanaged: values['unmanaged']
914     };
915     if (serviceType === 'rgw') {
916       serviceSpec['rgw_realm'] = values['realm_name'] ? values['realm_name'] : null;
917       serviceSpec['rgw_zonegroup'] =
918         values['zonegroup_name'] !== 'default' ? values['zonegroup_name'] : null;
919       serviceSpec['rgw_zone'] = values['zone_name'] !== 'default' ? values['zone_name'] : null;
920     }
921
922     const serviceId: string = values['service_id'];
923     let serviceName: string = serviceType;
924     if (_.isString(serviceId) && !_.isEmpty(serviceId) && serviceId !== serviceType) {
925       serviceName = `${serviceType}.${serviceId}`;
926       serviceSpec['service_id'] = serviceId;
927     }
928
929     // These services has some fields to be
930     // filled out even if unmanaged is true
931     switch (serviceType) {
932       case 'ingress':
933         serviceSpec['backend_service'] = values['backend_service'];
934         serviceSpec['service_id'] = values['backend_service'];
935         if (_.isNumber(values['frontend_port']) && values['frontend_port'] > 0) {
936           serviceSpec['frontend_port'] = values['frontend_port'];
937         }
938         if (_.isString(values['virtual_ip']) && !_.isEmpty(values['virtual_ip'])) {
939           serviceSpec['virtual_ip'] = values['virtual_ip'].trim();
940         }
941         if (_.isNumber(values['monitor_port']) && values['monitor_port'] > 0) {
942           serviceSpec['monitor_port'] = values['monitor_port'];
943         }
944         break;
945
946       case 'nvmeof':
947         serviceSpec['pool'] = values['pool'];
948         serviceSpec['group'] = values['group'];
949         break;
950       case 'iscsi':
951         serviceSpec['pool'] = values['pool'];
952         break;
953
954       case 'smb':
955         serviceSpec['cluster_id'] = values['cluster_id']?.trim();
956         serviceSpec['config_uri'] = values['config_uri']?.trim();
957         for (const feature in values['features']) {
958           if (values['features'][feature]) {
959             (serviceSpec['features'] = serviceSpec['features'] || []).push(feature);
960           }
961         }
962         serviceSpec['custom_dns'] = values['custom_dns']?.trim();
963         serviceSpec['join_sources'] = values['join_sources']?.trim();
964         serviceSpec['user_sources'] = values['user_sources']?.trim();
965         serviceSpec['include_ceph_users'] = values['include_ceph_users']?.trim();
966         break;
967
968       case 'snmp-gateway':
969         serviceSpec['credentials'] = {};
970         serviceSpec['snmp_version'] = values['snmp_version'];
971         serviceSpec['snmp_destination'] = values['snmp_destination'];
972         if (values['snmp_version'] === 'V3') {
973           serviceSpec['engine_id'] = values['engine_id'];
974           serviceSpec['auth_protocol'] = values['auth_protocol'];
975           serviceSpec['credentials']['snmp_v3_auth_username'] = values['snmp_v3_auth_username'];
976           serviceSpec['credentials']['snmp_v3_auth_password'] = values['snmp_v3_auth_password'];
977           if (values['privacy_protocol'] !== null) {
978             serviceSpec['privacy_protocol'] = values['privacy_protocol'];
979             serviceSpec['credentials']['snmp_v3_priv_password'] = values['snmp_v3_priv_password'];
980           }
981         } else {
982           serviceSpec['credentials']['snmp_community'] = values['snmp_community'];
983         }
984         break;
985     }
986
987     if (!values['unmanaged']) {
988       switch (values['placement']) {
989         case 'hosts':
990           if (values['hosts'].length > 0) {
991             serviceSpec['placement']['hosts'] = values['hosts'];
992           }
993           break;
994         case 'label':
995           serviceSpec['placement']['label'] = values['label'];
996           break;
997       }
998       if (_.isNumber(values['count']) && values['count'] > 0) {
999         serviceSpec['placement']['count'] = values['count'];
1000       }
1001       switch (serviceType) {
1002         case 'rgw':
1003           if (_.isNumber(values['rgw_frontend_port']) && values['rgw_frontend_port'] > 0) {
1004             serviceSpec['rgw_frontend_port'] = values['rgw_frontend_port'];
1005           }
1006           serviceSpec['ssl'] = values['ssl'];
1007           if (values['ssl']) {
1008             serviceSpec['rgw_frontend_ssl_certificate'] = values['ssl_cert']?.trim();
1009           }
1010           break;
1011         case 'iscsi':
1012           if (_.isString(values['trusted_ip_list']) && !_.isEmpty(values['trusted_ip_list'])) {
1013             serviceSpec['trusted_ip_list'] = values['trusted_ip_list'].trim();
1014           }
1015           if (_.isNumber(values['api_port']) && values['api_port'] > 0) {
1016             serviceSpec['api_port'] = values['api_port'];
1017           }
1018           serviceSpec['api_user'] = values['api_user'];
1019           serviceSpec['api_password'] = values['api_password'];
1020           serviceSpec['api_secure'] = values['ssl'];
1021           if (values['ssl']) {
1022             serviceSpec['ssl_cert'] = values['ssl_cert']?.trim();
1023             serviceSpec['ssl_key'] = values['ssl_key']?.trim();
1024           }
1025           break;
1026         case 'ingress':
1027           serviceSpec['ssl'] = values['ssl'];
1028           if (values['ssl']) {
1029             serviceSpec['ssl_cert'] = values['ssl_cert']?.trim();
1030             serviceSpec['ssl_key'] = values['ssl_key']?.trim();
1031           }
1032           serviceSpec['virtual_interface_networks'] = values['virtual_interface_networks'];
1033           break;
1034         case 'grafana':
1035           serviceSpec['port'] = values['grafana_port'];
1036           serviceSpec['initial_admin_password'] = values['grafana_admin_password'];
1037       }
1038     }
1039
1040     this.taskWrapperService
1041       .wrapTaskAroundCall({
1042         task: new FinishedTask(taskUrl, {
1043           service_name: serviceName
1044         }),
1045         call: this.editing
1046           ? this.cephServiceService.update(serviceSpec)
1047           : this.cephServiceService.create(serviceSpec)
1048       })
1049       .subscribe({
1050         error() {
1051           self.serviceForm.setErrors({ cdSubmitButton: true });
1052         },
1053         complete: () => {
1054           this.pageURL === 'services'
1055             ? this.router.navigate([this.pageURL, { outlets: { modal: null } }])
1056             : this.activeModal.close();
1057         }
1058       });
1059   }
1060
1061   clearValidations() {
1062     const snmpVersion = this.serviceForm.getValue('snmp_version');
1063     const privacyProtocol = this.serviceForm.getValue('privacy_protocol');
1064     if (snmpVersion === 'V3') {
1065       this.serviceForm.get('snmp_community').clearValidators();
1066     } else {
1067       this.serviceForm.get('engine_id').clearValidators();
1068       this.serviceForm.get('auth_protocol').clearValidators();
1069       this.serviceForm.get('privacy_protocol').clearValidators();
1070       this.serviceForm.get('snmp_v3_auth_username').clearValidators();
1071       this.serviceForm.get('snmp_v3_auth_password').clearValidators();
1072     }
1073     if (privacyProtocol === null) {
1074       this.serviceForm.get('snmp_v3_priv_password').clearValidators();
1075     }
1076   }
1077
1078   createMultisiteSetup() {
1079     this.bsModalRef = this.modalService.show(CreateRgwServiceEntitiesComponent, {
1080       size: 'lg'
1081     });
1082     this.bsModalRef.componentInstance.submitAction.subscribe(() => {
1083       this.setRgwFields();
1084     });
1085   }
1086 }