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