]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
85a8d4df8e4457f46a6f2b8292b458b32d95457a
[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       // 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   resolveRoute() {
428     if (this.router.url.includes('services/(modal:create')) {
429       this.pageURL = 'services';
430       this.route.params.subscribe((params: { type: string }) => {
431         if (params?.type) {
432           this.serviceType = params.type;
433           this.serviceForm.get('service_type').setValue(this.serviceType);
434         }
435       });
436     } else if (this.router.url.includes('services/(modal:edit')) {
437       this.editing = true;
438       this.pageURL = 'services';
439       this.route.params.subscribe((params: { type: string; name: string }) => {
440         this.serviceName = params.name;
441         this.serviceType = params.type;
442       });
443     }
444   }
445
446   ngOnInit(): void {
447     this.action = this.actionLabels.CREATE;
448     this.resolveRoute();
449
450     this.cephServiceService
451       .list(new HttpParams({ fromObject: { limit: -1, offset: 0 } }))
452       .observable.subscribe((services: CephServiceSpec[]) => {
453         this.serviceList = services;
454         this.services = services.filter((service: any) =>
455           this.INGRESS_SUPPORTED_SERVICE_TYPES.includes(service.service_type)
456         );
457       });
458
459     this.cephServiceService.getKnownTypes().subscribe((resp: Array<string>) => {
460       // Remove service types:
461       // osd       - This is deployed a different way.
462       // container - This should only be used in the CLI.
463       this.hiddenServices.push('osd', 'container');
464
465       this.serviceTypes = _.difference(resp, this.hiddenServices).sort();
466     });
467     const hostContext = new CdTableFetchDataContext(() => undefined);
468     this.hostService.list(hostContext.toParams(), 'false').subscribe((resp: object[]) => {
469       const options: SelectOption[] = [];
470       _.forEach(resp, (host: object) => {
471         if (_.get(host, 'sources.orchestrator', false)) {
472           const option = new SelectOption(false, _.get(host, 'hostname'), '');
473           options.push(option);
474         }
475       });
476       this.hosts.options = [...options];
477     });
478     this.hostService.getLabels().subscribe((resp: string[]) => {
479       this.labels = resp;
480     });
481     this.poolService.getList().subscribe((resp: Pool[]) => {
482       this.pools = resp;
483       this.rbdPools = this.pools.filter(this.rbdService.isRBDPool);
484       if (!this.editing && this.serviceType) {
485         this.onServiceTypeChange(this.serviceType);
486       }
487     });
488
489     if (this.editing) {
490       this.action = this.actionLabels.EDIT;
491       this.disableForEditing(this.serviceType);
492       this.cephServiceService
493         .list(new HttpParams({ fromObject: { limit: -1, offset: 0 } }), this.serviceName)
494         .observable.subscribe((response: CephServiceSpec[]) => {
495           const formKeys = ['service_type', 'service_id', 'unmanaged'];
496           formKeys.forEach((keys) => {
497             this.serviceForm.get(keys).setValue(response[0][keys]);
498           });
499           if (!response[0]['unmanaged']) {
500             const placementKey = Object.keys(response[0]['placement'])[0];
501             let placementValue: string;
502             ['hosts', 'label'].indexOf(placementKey) >= 0
503               ? (placementValue = placementKey)
504               : (placementValue = 'hosts');
505             this.serviceForm.get('placement').setValue(placementValue);
506             this.serviceForm.get('count').setValue(response[0]['placement']['count']);
507             if (response[0]?.placement[placementValue]) {
508               this.serviceForm.get(placementValue).setValue(response[0]?.placement[placementValue]);
509             }
510           }
511           switch (this.serviceType) {
512             case 'iscsi':
513               const specKeys = ['pool', 'api_password', 'api_user', 'trusted_ip_list', 'api_port'];
514               specKeys.forEach((key) => {
515                 this.serviceForm.get(key).setValue(response[0].spec[key]);
516               });
517               this.serviceForm.get('ssl').setValue(response[0].spec?.api_secure);
518               if (response[0].spec?.api_secure) {
519                 this.serviceForm.get('ssl_cert').setValue(response[0].spec?.ssl_cert);
520                 this.serviceForm.get('ssl_key').setValue(response[0].spec?.ssl_key);
521               }
522               break;
523             case 'nvmeof':
524               this.serviceForm.get('pool').setValue(response[0].spec.pool);
525               break;
526             case 'rgw':
527               this.serviceForm
528                 .get('rgw_frontend_port')
529                 .setValue(response[0].spec?.rgw_frontend_port);
530               this.setRgwFields(
531                 response[0].spec?.rgw_realm,
532                 response[0].spec?.rgw_zonegroup,
533                 response[0].spec?.rgw_zone
534               );
535               this.serviceForm.get('ssl').setValue(response[0].spec?.ssl);
536               if (response[0].spec?.ssl) {
537                 this.serviceForm
538                   .get('ssl_cert')
539                   .setValue(response[0].spec?.rgw_frontend_ssl_certificate);
540               }
541               break;
542             case 'ingress':
543               const ingressSpecKeys = [
544                 'backend_service',
545                 'virtual_ip',
546                 'frontend_port',
547                 'monitor_port',
548                 'virtual_interface_networks',
549                 'ssl'
550               ];
551               ingressSpecKeys.forEach((key) => {
552                 this.serviceForm.get(key).setValue(response[0].spec[key]);
553               });
554               if (response[0].spec?.ssl) {
555                 this.serviceForm.get('ssl_cert').setValue(response[0].spec?.ssl_cert);
556                 this.serviceForm.get('ssl_key').setValue(response[0].spec?.ssl_key);
557               }
558               break;
559             case 'smb':
560               const smbSpecKeys = [
561                 'cluster_id',
562                 'config_uri',
563                 'features',
564                 'join_sources',
565                 'user_sources',
566                 'custom_dns',
567                 'include_ceph_users'
568               ];
569               smbSpecKeys.forEach((key) => {
570                 if (key === 'features') {
571                   if (response[0].spec?.features) {
572                     response[0].spec.features.forEach((feature) => {
573                       this.serviceForm.get(`features.${feature}`).setValue(true);
574                     });
575                   }
576                 } else {
577                   this.serviceForm.get(key).setValue(response[0].spec[key]);
578                 }
579               });
580               break;
581             case 'snmp-gateway':
582               const snmpCommonSpecKeys = ['snmp_version', 'snmp_destination'];
583               snmpCommonSpecKeys.forEach((key) => {
584                 this.serviceForm.get(key).setValue(response[0].spec[key]);
585               });
586               if (this.serviceForm.getValue('snmp_version') === 'V3') {
587                 const snmpV3SpecKeys = [
588                   'engine_id',
589                   'auth_protocol',
590                   'privacy_protocol',
591                   'snmp_v3_auth_username',
592                   'snmp_v3_auth_password',
593                   'snmp_v3_priv_password'
594                 ];
595                 snmpV3SpecKeys.forEach((key) => {
596                   if (key !== null) {
597                     if (
598                       key === 'snmp_v3_auth_username' ||
599                       key === 'snmp_v3_auth_password' ||
600                       key === 'snmp_v3_priv_password'
601                     ) {
602                       this.serviceForm.get(key).setValue(response[0].spec['credentials'][key]);
603                     } else {
604                       this.serviceForm.get(key).setValue(response[0].spec[key]);
605                     }
606                   }
607                 });
608               } else {
609                 this.serviceForm
610                   .get('snmp_community')
611                   .setValue(response[0].spec['credentials']['snmp_community']);
612               }
613               break;
614             case 'grafana':
615               this.serviceForm.get('grafana_port').setValue(response[0].spec.port);
616               this.serviceForm
617                 .get('grafana_admin_password')
618                 .setValue(response[0].spec.initial_admin_password);
619               break;
620           }
621         });
622     }
623   }
624
625   getDefaultsEntitiesForRgw(
626     defaultRealmId: string,
627     defaultZonegroupId: string,
628     defaultZoneId: string
629   ): { defaultRealmName: string; defaultZonegroupName: string; defaultZoneName: string } {
630     const defaultRealm = this.realmList.find((x: { id: string }) => x.id === defaultRealmId);
631     const defaultZonegroup = this.zonegroupList.find(
632       (x: { id: string }) => x.id === defaultZonegroupId
633     );
634     const defaultZone = this.zoneList.find((x: { id: string }) => x.id === defaultZoneId);
635     const defaultRealmName = defaultRealm !== undefined ? defaultRealm.name : null;
636     const defaultZonegroupName = defaultZonegroup !== undefined ? defaultZonegroup.name : 'default';
637     const defaultZoneName = defaultZone !== undefined ? defaultZone.name : 'default';
638     if (defaultZonegroupName === 'default' && !this.zonegroupNames.includes(defaultZonegroupName)) {
639       const defaultZonegroup = new RgwZonegroup();
640       defaultZonegroup.name = 'default';
641       this.zonegroupList.push(defaultZonegroup);
642     }
643     if (defaultZoneName === 'default' && !this.zoneNames.includes(defaultZoneName)) {
644       const defaultZone = new RgwZone();
645       defaultZone.name = 'default';
646       this.zoneList.push(defaultZone);
647     }
648     return {
649       defaultRealmName: defaultRealmName,
650       defaultZonegroupName: defaultZonegroupName,
651       defaultZoneName: defaultZoneName
652     };
653   }
654
655   getDefaultPlacementCount(serviceType: string) {
656     /**
657      * `defaults` from src/pybind/mgr/cephadm/module.py
658      */
659     switch (serviceType) {
660       case 'mon':
661         this.serviceForm.get('count').setValue(5);
662         break;
663       case 'mgr':
664       case 'mds':
665       case 'rgw':
666       case 'ingress':
667       case 'rbd-mirror':
668         this.serviceForm.get('count').setValue(2);
669         break;
670       case 'iscsi':
671       case 'nvmeof':
672       case 'cephfs-mirror':
673       case 'nfs':
674       case 'grafana':
675       case 'alertmanager':
676       case 'prometheus':
677       case 'loki':
678       case 'container':
679       case 'snmp-gateway':
680       case 'elastic-serach':
681       case 'jaeger-collector':
682       case 'jaeger-query':
683       case 'smb':
684         this.serviceForm.get('count').setValue(1);
685         break;
686       default:
687         this.serviceForm.get('count').setValue(null);
688     }
689   }
690
691   setRgwFields(realm_name?: string, zonegroup_name?: string, zone_name?: string) {
692     const observables = [
693       this.rgwRealmService.getAllRealmsInfo(),
694       this.rgwZonegroupService.getAllZonegroupsInfo(),
695       this.rgwZoneService.getAllZonesInfo()
696     ];
697     this.sub = forkJoin(observables).subscribe(
698       (multisiteInfo: [object, object, object]) => {
699         this.multisiteInfo = multisiteInfo;
700         this.realmList =
701           this.multisiteInfo[0] !== undefined && this.multisiteInfo[0].hasOwnProperty('realms')
702             ? this.multisiteInfo[0]['realms']
703             : [];
704         this.zonegroupList =
705           this.multisiteInfo[1] !== undefined && this.multisiteInfo[1].hasOwnProperty('zonegroups')
706             ? this.multisiteInfo[1]['zonegroups']
707             : [];
708         this.zoneList =
709           this.multisiteInfo[2] !== undefined && this.multisiteInfo[2].hasOwnProperty('zones')
710             ? this.multisiteInfo[2]['zones']
711             : [];
712         this.realmNames = this.realmList.map((realm) => {
713           return realm['name'];
714         });
715         this.zonegroupNames = this.zonegroupList.map((zonegroup) => {
716           return zonegroup['name'];
717         });
718         this.zoneNames = this.zoneList.map((zone) => {
719           return zone['name'];
720         });
721         this.defaultRealmId = multisiteInfo[0]['default_realm'];
722         this.defaultZonegroupId = multisiteInfo[1]['default_zonegroup'];
723         this.defaultZoneId = multisiteInfo[2]['default_zone'];
724         this.defaultsInfo = this.getDefaultsEntitiesForRgw(
725           this.defaultRealmId,
726           this.defaultZonegroupId,
727           this.defaultZoneId
728         );
729         if (!this.editing) {
730           this.serviceForm.get('realm_name').setValue(this.defaultsInfo['defaultRealmName']);
731           this.serviceForm
732             .get('zonegroup_name')
733             .setValue(this.defaultsInfo['defaultZonegroupName']);
734           this.serviceForm.get('zone_name').setValue(this.defaultsInfo['defaultZoneName']);
735         } else {
736           if (realm_name && !this.realmNames.includes(realm_name)) {
737             const realm = new RgwRealm();
738             realm.name = realm_name;
739             this.realmList.push(realm);
740           }
741           if (zonegroup_name && !this.zonegroupNames.includes(zonegroup_name)) {
742             const zonegroup = new RgwZonegroup();
743             zonegroup.name = zonegroup_name;
744             this.zonegroupList.push(zonegroup);
745           }
746           if (zone_name && !this.zoneNames.includes(zone_name)) {
747             const zone = new RgwZone();
748             zone.name = zone_name;
749             this.zoneList.push(zone);
750           }
751           if (zonegroup_name === undefined && zone_name === undefined) {
752             zonegroup_name = 'default';
753             zone_name = 'default';
754           }
755           this.serviceForm.get('realm_name').setValue(realm_name);
756           this.serviceForm.get('zonegroup_name').setValue(zonegroup_name);
757           this.serviceForm.get('zone_name').setValue(zone_name);
758         }
759         if (this.realmList.length === 0) {
760           this.showRealmCreationForm = true;
761         } else {
762           this.showRealmCreationForm = false;
763         }
764       },
765       (_error) => {
766         const defaultZone = new RgwZone();
767         defaultZone.name = 'default';
768         const defaultZonegroup = new RgwZonegroup();
769         defaultZonegroup.name = 'default';
770         this.zoneList.push(defaultZone);
771         this.zonegroupList.push(defaultZonegroup);
772       }
773     );
774   }
775
776   setNvmeofServiceId(): void {
777     const defaultRbdPool: string = this.rbdPools?.find((p: Pool) => p.pool_name === 'rbd')
778       ?.pool_name;
779     if (defaultRbdPool) {
780       this.serviceForm.get('pool').setValue(defaultRbdPool);
781       this.serviceForm.get('service_id').setValue(defaultRbdPool);
782     }
783   }
784
785   requiresServiceId(serviceType: string) {
786     return ['mds', 'rgw', 'nfs', 'iscsi', 'nvmeof', 'smb', 'ingress'].includes(serviceType);
787   }
788
789   setServiceId(serviceId: string): void {
790     const requiresServiceId: boolean = this.requiresServiceId(serviceId);
791     if (requiresServiceId && serviceId === 'nvmeof') {
792       this.setNvmeofServiceId();
793     } else if (requiresServiceId) {
794       this.serviceForm.get('service_id').setValue(null);
795     } else {
796       this.serviceForm.get('service_id').setValue(serviceId);
797     }
798   }
799
800   onServiceTypeChange(selectedServiceType: string) {
801     this.setServiceId(selectedServiceType);
802
803     this.serviceIds = this.serviceList
804       ?.filter((service) => service['service_type'] === selectedServiceType)
805       .map((service) => service['service_id']);
806
807     this.getDefaultPlacementCount(selectedServiceType);
808
809     if (selectedServiceType === 'rgw') {
810       this.setRgwFields();
811     }
812   }
813
814   onBlockPoolChange() {
815     const selectedBlockPool = this.serviceForm.get('pool').value;
816     if (selectedBlockPool) {
817       this.serviceForm.get('service_id').setValue(selectedBlockPool);
818     } else {
819       this.serviceForm.get('service_id').setValue(null);
820     }
821   }
822
823   disableForEditing(serviceType: string) {
824     const disableForEditKeys = ['service_type', 'service_id'];
825     disableForEditKeys.forEach((key) => {
826       this.serviceForm.get(key).disable();
827     });
828     switch (serviceType) {
829       case 'ingress':
830         this.serviceForm.get('backend_service').disable();
831         break;
832       case 'nvmeof':
833         this.serviceForm.get('pool').disable();
834         break;
835     }
836   }
837
838   searchLabels = (text$: Observable<string>) => {
839     return merge(
840       text$.pipe(debounceTime(200), distinctUntilChanged()),
841       this.labelFocus,
842       this.labelClick.pipe(filter(() => !this.typeahead.isPopupOpen()))
843     ).pipe(
844       map((value) =>
845         this.labels
846           .filter((label: string) => label.toLowerCase().indexOf(value.toLowerCase()) > -1)
847           .slice(0, 10)
848       )
849     );
850   };
851
852   fileUpload(files: FileList, controlName: string) {
853     const file: File = files[0];
854     const reader = new FileReader();
855     reader.addEventListener('load', (event: ProgressEvent<FileReader>) => {
856       const control: AbstractControl = this.serviceForm.get(controlName);
857       control.setValue(event.target.result);
858       control.markAsDirty();
859       control.markAsTouched();
860       control.updateValueAndValidity();
861     });
862     reader.readAsText(file, 'utf8');
863   }
864
865   prePopulateId() {
866     const control: AbstractControl = this.serviceForm.get('service_id');
867     const backendService = this.serviceForm.getValue('backend_service');
868     // Set Id as read-only
869     control.reset({ value: backendService, disabled: true });
870   }
871
872   onSubmit() {
873     const self = this;
874     const values: object = this.serviceForm.getRawValue();
875     const serviceType: string = values['service_type'];
876     let taskUrl = `service/${URLVerbs.CREATE}`;
877     if (this.editing) {
878       taskUrl = `service/${URLVerbs.EDIT}`;
879     }
880     const serviceSpec: object = {
881       service_type: serviceType,
882       placement: {},
883       unmanaged: values['unmanaged']
884     };
885     if (serviceType === 'rgw') {
886       serviceSpec['rgw_realm'] = values['realm_name'] ? values['realm_name'] : null;
887       serviceSpec['rgw_zonegroup'] =
888         values['zonegroup_name'] !== 'default' ? values['zonegroup_name'] : null;
889       serviceSpec['rgw_zone'] = values['zone_name'] !== 'default' ? values['zone_name'] : null;
890     }
891
892     const serviceId: string = values['service_id'];
893     let serviceName: string = serviceType;
894     if (_.isString(serviceId) && !_.isEmpty(serviceId) && serviceId !== serviceType) {
895       serviceName = `${serviceType}.${serviceId}`;
896       serviceSpec['service_id'] = serviceId;
897     }
898
899     // These services has some fields to be
900     // filled out even if unmanaged is true
901     switch (serviceType) {
902       case 'ingress':
903         serviceSpec['backend_service'] = values['backend_service'];
904         serviceSpec['service_id'] = values['backend_service'];
905         if (_.isNumber(values['frontend_port']) && values['frontend_port'] > 0) {
906           serviceSpec['frontend_port'] = values['frontend_port'];
907         }
908         if (_.isString(values['virtual_ip']) && !_.isEmpty(values['virtual_ip'])) {
909           serviceSpec['virtual_ip'] = values['virtual_ip'].trim();
910         }
911         if (_.isNumber(values['monitor_port']) && values['monitor_port'] > 0) {
912           serviceSpec['monitor_port'] = values['monitor_port'];
913         }
914         break;
915
916       case 'nvmeof':
917       case 'iscsi':
918         serviceSpec['pool'] = values['pool'];
919         break;
920
921       case 'smb':
922         serviceSpec['cluster_id'] = values['cluster_id']?.trim();
923         serviceSpec['config_uri'] = values['config_uri']?.trim();
924         for (const feature in values['features']) {
925           if (values['features'][feature]) {
926             (serviceSpec['features'] = serviceSpec['features'] || []).push(feature);
927           }
928         }
929         serviceSpec['custom_dns'] = values['custom_dns']?.trim();
930         serviceSpec['join_sources'] = values['join_sources']?.trim();
931         serviceSpec['user_sources'] = values['user_sources']?.trim();
932         serviceSpec['include_ceph_users'] = values['include_ceph_users']?.trim();
933         break;
934
935       case 'snmp-gateway':
936         serviceSpec['credentials'] = {};
937         serviceSpec['snmp_version'] = values['snmp_version'];
938         serviceSpec['snmp_destination'] = values['snmp_destination'];
939         if (values['snmp_version'] === 'V3') {
940           serviceSpec['engine_id'] = values['engine_id'];
941           serviceSpec['auth_protocol'] = values['auth_protocol'];
942           serviceSpec['credentials']['snmp_v3_auth_username'] = values['snmp_v3_auth_username'];
943           serviceSpec['credentials']['snmp_v3_auth_password'] = values['snmp_v3_auth_password'];
944           if (values['privacy_protocol'] !== null) {
945             serviceSpec['privacy_protocol'] = values['privacy_protocol'];
946             serviceSpec['credentials']['snmp_v3_priv_password'] = values['snmp_v3_priv_password'];
947           }
948         } else {
949           serviceSpec['credentials']['snmp_community'] = values['snmp_community'];
950         }
951         break;
952     }
953
954     if (!values['unmanaged']) {
955       switch (values['placement']) {
956         case 'hosts':
957           if (values['hosts'].length > 0) {
958             serviceSpec['placement']['hosts'] = values['hosts'];
959           }
960           break;
961         case 'label':
962           serviceSpec['placement']['label'] = values['label'];
963           break;
964       }
965       if (_.isNumber(values['count']) && values['count'] > 0) {
966         serviceSpec['placement']['count'] = values['count'];
967       }
968       switch (serviceType) {
969         case 'rgw':
970           if (_.isNumber(values['rgw_frontend_port']) && values['rgw_frontend_port'] > 0) {
971             serviceSpec['rgw_frontend_port'] = values['rgw_frontend_port'];
972           }
973           serviceSpec['ssl'] = values['ssl'];
974           if (values['ssl']) {
975             serviceSpec['rgw_frontend_ssl_certificate'] = values['ssl_cert']?.trim();
976           }
977           break;
978         case 'iscsi':
979           if (_.isString(values['trusted_ip_list']) && !_.isEmpty(values['trusted_ip_list'])) {
980             serviceSpec['trusted_ip_list'] = values['trusted_ip_list'].trim();
981           }
982           if (_.isNumber(values['api_port']) && values['api_port'] > 0) {
983             serviceSpec['api_port'] = values['api_port'];
984           }
985           serviceSpec['api_user'] = values['api_user'];
986           serviceSpec['api_password'] = values['api_password'];
987           serviceSpec['api_secure'] = values['ssl'];
988           if (values['ssl']) {
989             serviceSpec['ssl_cert'] = values['ssl_cert']?.trim();
990             serviceSpec['ssl_key'] = values['ssl_key']?.trim();
991           }
992           break;
993         case 'ingress':
994           serviceSpec['ssl'] = values['ssl'];
995           if (values['ssl']) {
996             serviceSpec['ssl_cert'] = values['ssl_cert']?.trim();
997             serviceSpec['ssl_key'] = values['ssl_key']?.trim();
998           }
999           serviceSpec['virtual_interface_networks'] = values['virtual_interface_networks'];
1000           break;
1001         case 'grafana':
1002           serviceSpec['port'] = values['grafana_port'];
1003           serviceSpec['initial_admin_password'] = values['grafana_admin_password'];
1004       }
1005     }
1006
1007     this.taskWrapperService
1008       .wrapTaskAroundCall({
1009         task: new FinishedTask(taskUrl, {
1010           service_name: serviceName
1011         }),
1012         call: this.editing
1013           ? this.cephServiceService.update(serviceSpec)
1014           : this.cephServiceService.create(serviceSpec)
1015       })
1016       .subscribe({
1017         error() {
1018           self.serviceForm.setErrors({ cdSubmitButton: true });
1019         },
1020         complete: () => {
1021           this.pageURL === 'services'
1022             ? this.router.navigate([this.pageURL, { outlets: { modal: null } }])
1023             : this.activeModal.close();
1024         }
1025       });
1026   }
1027
1028   clearValidations() {
1029     const snmpVersion = this.serviceForm.getValue('snmp_version');
1030     const privacyProtocol = this.serviceForm.getValue('privacy_protocol');
1031     if (snmpVersion === 'V3') {
1032       this.serviceForm.get('snmp_community').clearValidators();
1033     } else {
1034       this.serviceForm.get('engine_id').clearValidators();
1035       this.serviceForm.get('auth_protocol').clearValidators();
1036       this.serviceForm.get('privacy_protocol').clearValidators();
1037       this.serviceForm.get('snmp_v3_auth_username').clearValidators();
1038       this.serviceForm.get('snmp_v3_auth_password').clearValidators();
1039     }
1040     if (privacyProtocol === null) {
1041       this.serviceForm.get('snmp_v3_priv_password').clearValidators();
1042     }
1043   }
1044
1045   createMultisiteSetup() {
1046     this.bsModalRef = this.modalService.show(CreateRgwServiceEntitiesComponent, {
1047       size: 'lg'
1048     });
1049     this.bsModalRef.componentInstance.submitAction.subscribe(() => {
1050       this.setRgwFields();
1051     });
1052   }
1053 }