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