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