]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
34773cddbf7b4363358072db697bdb6f95fb13bb
[ceph-ci.git] /
1 import { ChangeDetectorRef, Component, NgZone, OnInit } from '@angular/core';
2 import { Location } from '@angular/common';
3 import { UntypedFormControl, Validators } from '@angular/forms';
4 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
5 import { Observable, Subscription, forkJoin } from 'rxjs';
6 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
7 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
8 import { WizardStepModel } from '~/app/shared/models/wizard-steps';
9 import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
10 import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
11 import { RgwDaemon } from '../models/rgw-daemon';
12 import { MultiClusterService } from '~/app/shared/api/multi-cluster.service';
13 import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
14 import { Icons } from '~/app/shared/enum/icons.enum';
15 import { SelectOption } from '~/app/shared/components/select/select-option.model';
16 import _ from 'lodash';
17 import { SelectMessages } from '~/app/shared/components/select/select-messages.model';
18 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
19 import { NotificationService } from '~/app/shared/services/notification.service';
20 import { ActivatedRoute } from '@angular/router';
21 import { map, switchMap } from 'rxjs/operators';
22 import { BaseModal, Step } from 'carbon-components-angular';
23 import { SummaryService } from '~/app/shared/services/summary.service';
24 import { ExecutingTask } from '~/app/shared/models/executing-task';
25 import {
26   STEP_TITLES_EXISTING_REALM,
27   STEP_TITLES_MULTI_CLUSTER_CONFIGURED,
28   STEP_TITLES_SINGLE_CLUSTER
29 } from './multisite-wizard-steps.enum';
30 import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
31 import { MultiCluster, MultiClusterConfig } from '~/app/shared/models/multi-cluster';
32 import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
33
34 interface DaemonStats {
35   rgw_metadata?: {
36     [key: string]: string;
37   };
38 }
39
40 interface EndpointInfo {
41   hostname: string;
42   port: number;
43   frontendConfig: string;
44 }
45
46 enum Protocol {
47   HTTP = 'http',
48   HTTPS = 'https'
49 }
50
51 enum ConfigType {
52   NewRealm = 'newRealm',
53   ExistingRealm = 'existingRealm'
54 }
55
56 @Component({
57   selector: 'cd-rgw-multisite-wizard',
58   templateUrl: './rgw-multisite-wizard.component.html',
59   styleUrls: ['./rgw-multisite-wizard.component.scss']
60 })
61 export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
62   multisiteSetupForm: CdFormGroup;
63   currentStep: WizardStepModel;
64   currentStepSub: Subscription;
65   permissions: Permissions;
66   stepTitles: Step[] = STEP_TITLES_MULTI_CLUSTER_CONFIGURED.map((title) => ({
67     label: title
68   }));
69   stepsToSkip: { [steps: string]: boolean } = {};
70   daemons: RgwDaemon[] = [];
71   selectedCluster = '';
72   clusterDetailsArray: MultiCluster[] = [];
73   isMultiClusterConfigured = false;
74   exportTokenForm: CdFormGroup;
75   realms: any;
76   loading = false;
77   pageURL: string;
78   icons = Icons;
79   rgwEndpoints: { value: any[]; options: any[]; messages: any };
80   executingTask: ExecutingTask;
81   setupCompleted = false;
82   showConfigType = false;
83   realmList: string[] = [];
84   rgwModuleStatus: boolean;
85
86   constructor(
87     private wizardStepsService: WizardStepsService,
88     public activeModal: NgbActiveModal,
89     public actionLabels: ActionLabelsI18n,
90     private rgwDaemonService: RgwDaemonService,
91     private multiClusterService: MultiClusterService,
92     private rgwMultisiteService: RgwMultisiteService,
93     private rgwRealmService: RgwRealmService,
94     public notificationService: NotificationService,
95     private route: ActivatedRoute,
96     private summaryService: SummaryService,
97     private location: Location,
98     private cdr: ChangeDetectorRef,
99     private mgrModuleService: MgrModuleService,
100     private zone: NgZone
101   ) {
102     super();
103     this.pageURL = 'rgw/multisite/configuration';
104     this.currentStepSub = this.wizardStepsService
105       .getCurrentStep()
106       .subscribe((step: WizardStepModel) => {
107         this.currentStep = step;
108       });
109     this.currentStep.stepIndex = 0;
110     this.createForm();
111     this.rgwEndpoints = {
112       value: [],
113       options: [],
114       messages: new SelectMessages({
115         empty: $localize`There are no endpoints.`,
116         filter: $localize`Select endpoints`
117       })
118     };
119   }
120
121   ngOnInit(): void {
122     this.open = this.route.outlet === 'modal';
123     this.loadRGWEndpoints();
124     this.multiClusterService.getCluster().subscribe((clusters: MultiClusterConfig) => {
125       const currentUrl = clusters['current_url'];
126       this.clusterDetailsArray = Object.values(clusters['config'])
127         .flat()
128         .filter((cluster) => cluster['url'] !== currentUrl);
129       this.isMultiClusterConfigured = this.clusterDetailsArray.length > 0;
130       this.stepTitles = (this.isMultiClusterConfigured
131         ? STEP_TITLES_MULTI_CLUSTER_CONFIGURED
132         : STEP_TITLES_SINGLE_CLUSTER
133       ).map((label, index) => ({
134         label,
135         onClick: () => (this.currentStep.stepIndex = index)
136       }));
137       this.wizardStepsService.setTotalSteps(this.stepTitles.length);
138       this.selectedCluster = this.isMultiClusterConfigured
139         ? this.clusterDetailsArray[0]['name']
140         : null;
141     });
142
143     this.summaryService.subscribe((summary) => {
144       this.zone.run(() => {
145         this.executingTask = summary.executing_tasks.find((task) =>
146           task.name.includes('progress/Multisite-Setup')
147         );
148         this.cdr.detectChanges();
149       });
150     });
151
152     this.stepTitles.forEach((step) => {
153       this.stepsToSkip[step.label] = false;
154     });
155
156     this.rgwRealmService.list().subscribe((realms: string[]) => {
157       this.realmList = realms;
158       this.showConfigType = this.realmList.length > 0;
159       if (this.showConfigType) {
160         this.multisiteSetupForm.get('selectedRealm')?.setValue(this.realmList[0]);
161         this.cdr.detectChanges();
162       }
163     });
164
165     this.rgwMultisiteService.getRgwModuleStatus().subscribe((status: boolean) => {
166       this.rgwModuleStatus = status;
167     });
168   }
169
170   private loadRGWEndpoints(): void {
171     this.rgwDaemonService
172       .list()
173       .pipe(
174         switchMap((daemons: RgwDaemon[]) => {
175           this.daemons = daemons;
176           return this.fetchDaemonStats(daemons);
177         })
178       )
179       .subscribe((daemonStatsArray: EndpointInfo[]) => {
180         this.populateRGWEndpoints(daemonStatsArray);
181       });
182   }
183
184   private fetchDaemonStats(daemons: RgwDaemon[]): Observable<EndpointInfo[]> {
185     const observables = daemons.map((daemon) =>
186       this.rgwDaemonService.get(daemon.id).pipe(
187         map((daemonStats: DaemonStats) => ({
188           hostname: daemon.server_hostname,
189           port: daemon.port,
190           frontendConfig: daemonStats?.rgw_metadata?.['frontend_config#0'] || ''
191         }))
192       )
193     );
194     return forkJoin(observables);
195   }
196
197   private populateRGWEndpoints(statsArray: EndpointInfo[]): void {
198     this.rgwEndpoints.value = statsArray.map((stats: EndpointInfo) => {
199       const protocol = stats.frontendConfig.includes('ssl_port') ? Protocol.HTTPS : Protocol.HTTP;
200       return `${protocol}://${stats.hostname}:${stats.port}`;
201     });
202     this.rgwEndpoints.options = this.rgwEndpoints.value.map(
203       (endpoint) => new SelectOption(false, endpoint, '')
204     );
205     this.cdr.detectChanges();
206   }
207
208   createForm() {
209     this.multisiteSetupForm = new CdFormGroup({
210       realmName: new UntypedFormControl('default_realm', {
211         validators: [Validators.required]
212       }),
213       zonegroupName: new UntypedFormControl('default_zonegroup', {
214         validators: [Validators.required]
215       }),
216       zonegroup_endpoints: new UntypedFormControl(null, [Validators.required]),
217       zoneName: new UntypedFormControl('default_zone', {
218         validators: [Validators.required]
219       }),
220       zone_endpoints: new UntypedFormControl(null, {
221         validators: [Validators.required]
222       }),
223       username: new UntypedFormControl('default_system_user', {
224         validators: [Validators.required]
225       }),
226       cluster: new UntypedFormControl(null, {
227         validators: [Validators.required]
228       }),
229       replicationZoneName: new UntypedFormControl('new_replicated_zone', {
230         validators: [Validators.required]
231       }),
232       configType: new UntypedFormControl(ConfigType.NewRealm, {}),
233       selectedRealm: new UntypedFormControl(null, {})
234     });
235
236     if (!this.isMultiClusterConfigured) {
237       this.exportTokenForm = new CdFormGroup({});
238     }
239   }
240
241   showSubmitButtonLabel() {
242     if (this.wizardStepsService.isLastStep()) {
243       if (!this.setupCompleted) {
244         if (this.isMultiClusterConfigured) {
245           return $localize`Configure Multi-Site`;
246         } else {
247           return $localize`Export Multi-Site token`;
248         }
249       } else {
250         return $localize`Close`;
251       }
252     } else {
253       return $localize`Next`;
254     }
255   }
256
257   showCancelButtonLabel() {
258     return !this.wizardStepsService.isFirstStep()
259       ? this.actionLabels.BACK
260       : this.actionLabels.CANCEL;
261   }
262
263   onNextStep() {
264     if (!this.wizardStepsService.isLastStep()) {
265       this.wizardStepsService.moveToNextStep();
266     } else {
267       if (this.setupCompleted) {
268         this.closeModal();
269       } else {
270         this.onSubmit();
271       }
272     }
273     this.wizardStepsService.getCurrentStep().subscribe((step: WizardStepModel) => {
274       this.currentStep = step;
275       if (this.currentStep.stepIndex === 2 && this.isMultiClusterConfigured) {
276         this.stepsToSkip['Select Cluster'] = false;
277       }
278     });
279   }
280
281   onSubmit() {
282     this.loading = true;
283
284     const proceedWithSetup = () => {
285       this.cdr.detectChanges();
286       const values = this.multisiteSetupForm.getRawValue();
287       const realmName = values['realmName'];
288       const zonegroupName = values['zonegroupName'];
289       const zonegroupEndpoints = this.rgwEndpoints.value.join(',');
290       const zoneName = values['zoneName'];
291       const zoneEndpoints = this.rgwEndpoints.value.join(',');
292       const username = values['username'];
293
294       if (!this.isMultiClusterConfigured || this.stepsToSkip['Select Cluster']) {
295         this.rgwMultisiteService
296           .setUpMultisiteReplication(
297             realmName,
298             zonegroupName,
299             zonegroupEndpoints,
300             zoneName,
301             zoneEndpoints,
302             username
303           )
304           .subscribe((data: object[]) => {
305             this.setupCompleted = true;
306             this.rgwMultisiteService.setRestartGatewayMessage(false);
307             this.loading = false;
308             this.realms = data;
309             this.showSuccessNotification();
310           });
311       } else {
312         const cluster = values['cluster'];
313         const replicationZoneName = values['replicationZoneName'];
314         let selectedRealmName = '';
315
316         if (this.multisiteSetupForm.get('configType').value === ConfigType.ExistingRealm) {
317           selectedRealmName = this.multisiteSetupForm.get('selectedRealm').value;
318         }
319
320         this.rgwMultisiteService
321           .setUpMultisiteReplication(
322             realmName,
323             zonegroupName,
324             zonegroupEndpoints,
325             zoneName,
326             zoneEndpoints,
327             username,
328             cluster,
329             replicationZoneName,
330             this.clusterDetailsArray,
331             selectedRealmName
332           )
333           .subscribe(
334             () => {
335               this.setupCompleted = true;
336               this.rgwMultisiteService.setRestartGatewayMessage(false);
337               this.loading = false;
338               this.showSuccessNotification();
339             },
340             () => {
341               this.multisiteSetupForm.setErrors({ cdSubmitButton: true });
342             }
343           );
344       }
345     };
346
347     if (!this.rgwModuleStatus) {
348       this.mgrModuleService.updateModuleState(
349         'rgw',
350         false,
351         null,
352         '',
353         '',
354         false,
355         $localize`RGW module is being enabled. Waiting for the system to reconnect...`
356       );
357       const subscription = this.mgrModuleService.updateCompleted$.subscribe(() => {
358         subscription.unsubscribe();
359         proceedWithSetup();
360       });
361     } else {
362       proceedWithSetup();
363     }
364   }
365
366   showSuccessNotification() {
367     this.notificationService.show(
368       NotificationType.success,
369       $localize`Multi-site setup completed successfully.`
370     );
371   }
372
373   onPreviousStep() {
374     if (!this.wizardStepsService.isFirstStep()) {
375       this.wizardStepsService.moveToPreviousStep();
376     } else {
377       this.location.back();
378     }
379   }
380
381   onSkip() {
382     const stepTitle = this.stepTitles[this.currentStep.stepIndex];
383     this.stepsToSkip[stepTitle.label] = true;
384     this.onNextStep();
385   }
386
387   closeModal(): void {
388     this.location.back();
389   }
390
391   onConfigTypeChange() {
392     const configType = this.multisiteSetupForm.get('configType')?.value;
393     if (configType === ConfigType.ExistingRealm) {
394       this.stepTitles = STEP_TITLES_EXISTING_REALM.map((title) => ({
395         label: title
396       }));
397       this.stepTitles.forEach((steps, index) => {
398         steps.onClick = () => (this.currentStep.stepIndex = index);
399       });
400     } else if (this.isMultiClusterConfigured) {
401       this.stepTitles = STEP_TITLES_MULTI_CLUSTER_CONFIGURED.map((title) => ({
402         label: title
403       }));
404     } else {
405       this.stepTitles = STEP_TITLES_SINGLE_CLUSTER.map((title) => ({
406         label: title
407       }));
408     }
409     this.wizardStepsService.setTotalSteps(this.stepTitles.length);
410   }
411 }