Skip to content

Commit d2f5c3b

Browse files
committed
mgr/dashboard: Allow the user to re-use existing r
ealm/zg/zone and setup replication 1. Currently, we just allow the user to create a new realm/zg/zone and setup replication using the multi-site replication wizard. The ask is to allow the user to select the pre-existing realm/zg/zone and setup replication via automatic export and import of token as well. 2. Enable rgw module automatically in the selected cluster if its not enabled Fixes: https://tracker.ceph.com/issues/70276 Signed-off-by: Aashish Sharma <[email protected]>
1 parent 1dede9d commit d2f5c3b

File tree

8 files changed

+622
-385
lines changed

8 files changed

+622
-385
lines changed

src/pybind/mgr/dashboard/controllers/rgw.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,15 @@ def migrate(self, daemon_name=None, realm_name=None, zonegroup_name=None, zone_n
122122
def setup_multisite_replication(self, daemon_name=None, realm_name=None, zonegroup_name=None,
123123
zonegroup_endpoints=None, zone_name=None, zone_endpoints=None,
124124
username=None, cluster_fsid=None, replication_zone_name=None,
125-
cluster_details=None):
125+
cluster_details=None, selectedRealmName=None):
126126
multisite_instance = RgwMultisiteAutomation()
127127
result = multisite_instance.setup_multisite_replication(realm_name, zonegroup_name,
128128
zonegroup_endpoints, zone_name,
129129
zone_endpoints, username,
130130
cluster_fsid,
131131
replication_zone_name,
132-
cluster_details)
132+
cluster_details,
133+
selectedRealmName)
133134
return result
134135

135136
@RESTController.Collection(method='PUT', path='/setup-rgw-credentials')

src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/multisite-wizard-steps.enum.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,9 @@ export const STEP_TITLES_SINGLE_CLUSTER = [
1717
StepTitles.CreateZone,
1818
StepTitles.Review
1919
];
20+
21+
export const STEP_TITLES_EXISTING_REALM = [
22+
StepTitles.CreateRealmAndZonegroup,
23+
StepTitles.SelectCluster,
24+
StepTitles.Review
25+
];

src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.html

Lines changed: 240 additions & 176 deletions
Large diffs are not rendered by default.

src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.ts

Lines changed: 131 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { Component, OnInit } from '@angular/core';
1+
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
22
import { Location } from '@angular/common';
33
import { UntypedFormControl, Validators } from '@angular/forms';
44
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
5-
import { Subscription, forkJoin } from 'rxjs';
5+
import { Observable, Subscription, forkJoin } from 'rxjs';
66
import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
77
import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
88
import { WizardStepModel } from '~/app/shared/models/wizard-steps';
@@ -23,9 +23,34 @@ import { BaseModal, Step } from 'carbon-components-angular';
2323
import { SummaryService } from '~/app/shared/services/summary.service';
2424
import { ExecutingTask } from '~/app/shared/models/executing-task';
2525
import {
26+
STEP_TITLES_EXISTING_REALM,
2627
STEP_TITLES_MULTI_CLUSTER_CONFIGURED,
2728
STEP_TITLES_SINGLE_CLUSTER
2829
} 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+
}
2954

3055
@Component({
3156
selector: 'cd-rgw-multisite-wizard',
@@ -43,7 +68,7 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
4368
stepsToSkip: { [steps: string]: boolean } = {};
4469
daemons: RgwDaemon[] = [];
4570
selectedCluster = '';
46-
clusterDetailsArray: any;
71+
clusterDetailsArray: MultiCluster[] = [];
4772
isMultiClusterConfigured = false;
4873
exportTokenForm: CdFormGroup;
4974
realms: any;
@@ -53,6 +78,9 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
5378
rgwEndpoints: { value: any[]; options: any[]; messages: any };
5479
executingTask: ExecutingTask;
5580
setupCompleted = false;
81+
showConfigType = false;
82+
realmList: string[] = [];
83+
realmsInfo: { realm: string; token: string }[];
5684

5785
constructor(
5886
private wizardStepsService: WizardStepsService,
@@ -61,10 +89,12 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
6189
private rgwDaemonService: RgwDaemonService,
6290
private multiClusterService: MultiClusterService,
6391
private rgwMultisiteService: RgwMultisiteService,
92+
private rgwRealmService: RgwRealmService,
6493
public notificationService: NotificationService,
6594
private route: ActivatedRoute,
6695
private summaryService: SummaryService,
67-
private location: Location
96+
private location: Location,
97+
private cdr: ChangeDetectorRef
6898
) {
6999
super();
70100
this.pageURL = 'rgw/multisite/configuration';
@@ -87,63 +117,85 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
87117

88118
ngOnInit(): void {
89119
this.open = this.route.outlet === 'modal';
90-
this.rgwDaemonService
91-
.list()
92-
.pipe(
93-
switchMap((daemons) => {
94-
this.daemons = daemons;
95-
const daemonStatsObservables = daemons.map((daemon) =>
96-
this.rgwDaemonService.get(daemon.id).pipe(
97-
map((daemonStats) => ({
98-
hostname: daemon.server_hostname,
99-
port: daemon.port,
100-
frontendConfig: daemonStats['rgw_metadata']['frontend_config#0']
101-
}))
102-
)
103-
);
104-
return forkJoin(daemonStatsObservables);
105-
})
106-
)
107-
.subscribe((daemonStatsArray) => {
108-
this.rgwEndpoints.value = daemonStatsArray.map((daemonStats) => {
109-
const protocol = daemonStats.frontendConfig.includes('ssl_port') ? 'https' : 'http';
110-
return `${protocol}://${daemonStats.hostname}:${daemonStats.port}`;
111-
});
112-
const options: SelectOption[] = this.rgwEndpoints.value.map(
113-
(endpoint: string) => new SelectOption(false, endpoint, '')
114-
);
115-
this.rgwEndpoints.options = [...options];
116-
});
117-
118-
this.multiClusterService.getCluster().subscribe((clusters) => {
120+
this.loadRGWEndpoints();
121+
this.multiClusterService.getCluster().subscribe((clusters: MultiClusterConfig) => {
122+
const currentUrl = clusters['current_url'];
119123
this.clusterDetailsArray = Object.values(clusters['config'])
120124
.flat()
121-
.filter((cluster) => cluster['url'] !== clusters['current_url']);
125+
.filter((cluster) => cluster['url'] !== currentUrl);
122126
this.isMultiClusterConfigured = this.clusterDetailsArray.length > 0;
123-
if (!this.isMultiClusterConfigured) {
124-
this.stepTitles = STEP_TITLES_SINGLE_CLUSTER.map((title) => ({
125-
label: title
126-
}));
127-
this.stepTitles.forEach((steps, index) => {
128-
steps.onClick = () => (this.currentStep.stepIndex = index);
129-
});
130-
} else {
131-
this.selectedCluster = this.clusterDetailsArray[0]['name'];
132-
}
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+
}));
133134
this.wizardStepsService.setTotalSteps(this.stepTitles.length);
135+
this.selectedCluster = this.isMultiClusterConfigured
136+
? this.clusterDetailsArray[0]['name']
137+
: null;
134138
});
135139

136140
this.summaryService.subscribe((summary) => {
137-
this.executingTask = summary.executing_tasks.filter((tasks) =>
138-
tasks.name.includes('progress/Multisite-Setup')
139-
)[0];
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;
140148
});
141149

142-
this.stepTitles.forEach((stepTitle) => {
143-
this.stepsToSkip[stepTitle.label] = false;
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+
}
144158
});
145159
}
146160

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+
147199
createForm() {
148200
this.multisiteSetupForm = new CdFormGroup({
149201
realmName: new UntypedFormControl('default_realm', {
@@ -167,7 +219,9 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
167219
}),
168220
replicationZoneName: new UntypedFormControl('new_replicated_zone', {
169221
validators: [Validators.required]
170-
})
222+
}),
223+
configType: new UntypedFormControl(ConfigType.NewRealm, {}),
224+
selectedRealm: new UntypedFormControl(null, {})
171225
});
172226

173227
if (!this.isMultiClusterConfigured) {
@@ -244,6 +298,10 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
244298
} else {
245299
const cluster = values['cluster'];
246300
const replicationZoneName = values['replicationZoneName'];
301+
let selectedRealmName = '';
302+
if (this.multisiteSetupForm.get('configType').value === ConfigType.ExistingRealm) {
303+
selectedRealmName = this.multisiteSetupForm.get('selectedRealm').value;
304+
}
247305
this.rgwMultisiteService
248306
.setUpMultisiteReplication(
249307
realmName,
@@ -254,7 +312,8 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
254312
username,
255313
cluster,
256314
replicationZoneName,
257-
this.clusterDetailsArray
315+
this.clusterDetailsArray,
316+
selectedRealmName
258317
)
259318
.subscribe(
260319
() => {
@@ -294,4 +353,25 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
294353
closeModal(): void {
295354
this.location.back();
296355
}
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+
}
297377
}

src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ export class RgwMultisiteService {
8686
username: string,
8787
cluster?: string,
8888
replicationZoneName?: string,
89-
clusterDetailsArray?: any
89+
clusterDetailsArray?: any,
90+
selectedRealmName?: string
9091
) {
9192
let params = new HttpParams()
9293
.set('realm_name', realmName)
@@ -108,6 +109,10 @@ export class RgwMultisiteService {
108109
params = params.set('replication_zone_name', replicationZoneName);
109110
}
110111

112+
if (selectedRealmName) {
113+
params = params.set('selectedRealmName', selectedRealmName);
114+
}
115+
111116
return this.http.post(`${this.uiUrl}/multisite-replications`, null, { params: params });
112117
}
113118

src/pybind/mgr/dashboard/frontend/src/app/shared/models/multi-cluster.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,12 @@ export interface MultiCluster {
99
ssl_certificate: string;
1010
ttl: number;
1111
}
12+
13+
export interface MultiClusterConfig {
14+
current_url: string;
15+
current_user: string;
16+
hub_url: string;
17+
config: {
18+
[clusterId: string]: MultiCluster[];
19+
};
20+
}

0 commit comments

Comments
 (0)