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