1. Enable rgw module automatically in the primary and secondary cluster if not enabled during multi-site automation
2. Improve progress bar descriptions and add sub-descriptions for steps
Fixes: https://tracker.ceph.com/issues/71033
Signed-off-by: Aashish Sharma <aasharma@redhat.com>
@allow_empty_body
# pylint: disable=W0613
- def list(self):
- multisite_instance = RgwMultisite()
- result = multisite_instance.list_realms()
- return result
+ def list(self, replicable: Optional[bool] = None):
+ if replicable:
+ try:
+ multisite_automation_instance = RgwMultisiteAutomation()
+ return multisite_automation_instance.get_replicable_realms_list()
+ except NoRgwDaemonsException as e:
+ raise DashboardException(e, http_status_code=404, component='rgw')
+ else:
+ multisite_instance = RgwMultisite()
+ return multisite_instance.list_realms()
@allow_empty_body
# pylint: disable=W0613
import { configureTestBed, PermissionHelper } from '~/testing/unit-test-helper';
import { MgrModuleDetailsComponent } from '../mgr-module-details/mgr-module-details.component';
import { MgrModuleListComponent } from './mgr-module-list.component';
+import { SummaryService } from '~/app/shared/services/summary.service';
describe('MgrModuleListComponent', () => {
let component: MgrModuleListComponent;
fixture = TestBed.createComponent(MgrModuleListComponent);
component = fixture.componentInstance;
mgrModuleService = TestBed.inject(MgrModuleService);
+ const summaryService = TestBed.inject(SummaryService);
+ spyOn(summaryService, 'startPolling');
});
it('should create', () => {
component.updateModuleState();
tick(mgrModuleService.REFRESH_INTERVAL);
tick(mgrModuleService.REFRESH_INTERVAL);
+ tick(mgrModuleService.REFRESH_INTERVAL);
expect(mgrModuleService.enable).toHaveBeenCalledWith('foo');
expect(mgrModuleService.list).toHaveBeenCalledTimes(2);
expect(component.table.refreshBtn).toHaveBeenCalled();
class="align-items-center"
actionName="Enable"
(action)="enableRgwModule()">
- In order to access the import/export/Setup Multi-site Replication feature, the rgw module must be enabled.
+ In order to access the import/export feature, the rgw module must be enabled.
</cd-alert-panel>
<cd-alert-panel *ngIf="restartGatewayMessage"
type="warning"
import { RgwMultisiteSyncPolicyComponent } from '../rgw-multisite-sync-policy/rgw-multisite-sync-policy.component';
import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
-import { MgrModuleInfo } from '~/app/shared/models/mgr-modules.interface';
-import { RGW } from '../utils/constants';
const BASE_URL = 'rgw/multisite/configuration';
}
];
+ this.startPollingMultisiteInfo();
+ this.mgrModuleService.updateCompleted$.subscribe(() => {
+ this.startPollingMultisiteInfo();
+ this.getRgwModuleStatus();
+ });
+ // Only get the module status if you can read from configOpt
+ if (this.permissions.configOpt.read) this.getRgwModuleStatus();
+ }
+
+ startPollingMultisiteInfo(): void {
const observables = [
this.rgwRealmService.getAllRealmsInfo(),
this.rgwZonegroupService.getAllZonegroupsInfo(),
this.rgwZoneService.getAllZonesInfo()
];
+
+ if (this.sub) {
+ this.sub.unsubscribe();
+ }
+
this.sub = this.timerService
.get(() => forkJoin(observables), this.timerServiceVariable.TIMER_SERVICE_PERIOD * 2)
.subscribe(
},
(_error) => {}
);
-
- // Only get the module status if you can read from configOpt
- if (this.permissions.configOpt.read) this.getRgwModuleStatus();
}
ngOnDestroy() {
}
private getRgwModuleStatus() {
- this.mgrModuleService.list().subscribe((moduleData: MgrModuleInfo[]) => {
- this.rgwModuleData = moduleData.filter((module: MgrModuleInfo) => module.name === RGW);
- if (this.rgwModuleData.length > 0) {
- this.rgwModuleStatus = this.rgwModuleData[0].enabled;
- }
+ this.rgwMultisiteService.getRgwModuleStatus().subscribe((status: boolean) => {
+ this.rgwModuleStatus = status;
});
}
import { ToastrModule } from 'ngx-toastr';
import { PipesModule } from '~/app/shared/pipes/pipes.module';
import { ModalModule } from 'carbon-components-angular';
+import { SharedModule } from '~/app/shared/shared.module';
describe('RgwMultisiteSyncPolicyDetailsComponent', () => {
let component: RgwMultisiteSyncPolicyDetailsComponent;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [RgwMultisiteSyncPolicyDetailsComponent],
- imports: [HttpClientTestingModule, ToastrModule.forRoot(), PipesModule, ModalModule]
+ imports: [
+ HttpClientTestingModule,
+ ToastrModule.forRoot(),
+ PipesModule,
+ ModalModule,
+ SharedModule
+ ]
}).compileComponents();
fixture = TestBed.createComponent(RgwMultisiteSyncPolicyDetailsComponent);
import { ComponentsModule } from '~/app/shared/components/components.module';
import { RouterTestingModule } from '@angular/router/testing';
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
+import { SharedModule } from '~/app/shared/shared.module';
describe('RgwMultisiteSyncPolicyFormComponent', () => {
let component: RgwMultisiteSyncPolicyFormComponent;
ToastrModule.forRoot(),
PipesModule,
ComponentsModule,
+ SharedModule,
RouterTestingModule
],
schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA],
<select class="form-select"
id="selectedRealm"
formControlName="selectedRealm">
- <option *ngFor="let realm of realmsInfo"
- [value]="realm.realm">
- {{ realm.realm }}
+ <option *ngFor="let realm of realmList"
+ [value]="realm">
+ {{ realm }}
</option>
</select>
</div>
<ng-template #progressTemplate>
<cd-progress [value]="executingTask?.progress"
- [description]="executingTask?.name?.replace('progress/Multisite-Setup:', '')">
+ [description]="executingTask?.name?.replace('progress/Multisite-Setup:', '')?.split('||')[0]?.trim()"
+ [subDescription]="executingTask?.name?.replace('progress/Multisite-Setup:', '')?.split('||')[1]?.trim()">
</cd-progress>
</ng-template>
</ng-template>
<ng-template #reviewTemplate>
+ <cd-alert-panel type="warning"
+ [showTitle]="false">
+ <span i18n>
+ During the automation process, the RGW module will be enabled on both the source and target clusters, if it is not already enabled.
+ This action may cause a temporary downtime (5-10 seconds) on each cluster.
+ </span>
+ </cd-alert-panel>
<ng-container [ngSwitch]="multisiteSetupForm.get('configType').value">
<ng-container *ngSwitchCase="'newRealm'">
<ng-container *ngTemplateOutlet="newRealmInfo"></ng-container>
-import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
+import { ChangeDetectorRef, Component, NgZone, OnInit } from '@angular/core';
import { Location } from '@angular/common';
import { UntypedFormControl, Validators } from '@angular/forms';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
} from './multisite-wizard-steps.enum';
import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
import { MultiCluster, MultiClusterConfig } from '~/app/shared/models/multi-cluster';
+import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
interface DaemonStats {
rgw_metadata?: {
setupCompleted = false;
showConfigType = false;
realmList: string[] = [];
- realmsInfo: { realm: string; token: string }[];
+ rgwModuleStatus: boolean;
constructor(
private wizardStepsService: WizardStepsService,
private route: ActivatedRoute,
private summaryService: SummaryService,
private location: Location,
- private cdr: ChangeDetectorRef
+ private cdr: ChangeDetectorRef,
+ private mgrModuleService: MgrModuleService,
+ private zone: NgZone
) {
super();
this.pageURL = 'rgw/multisite/configuration';
});
this.summaryService.subscribe((summary) => {
- this.executingTask = summary.executing_tasks.find((task) =>
- task.name.includes('progress/Multisite-Setup')
- );
+ this.zone.run(() => {
+ this.executingTask = summary.executing_tasks.find((task) =>
+ task.name.includes('progress/Multisite-Setup')
+ );
+ this.cdr.detectChanges();
+ });
});
this.stepTitles.forEach((step) => {
this.stepsToSkip[step.label] = false;
});
- this.rgwRealmService.getRealmTokens().subscribe((data: { realm: string; token: string }[]) => {
- const base64Matcher = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$/;
- this.realmsInfo = data.filter((realmInfo) => base64Matcher.test(realmInfo.token));
- this.showConfigType = this.realmsInfo.length > 0;
+ this.rgwRealmService.list().subscribe((realms: string[]) => {
+ this.realmList = realms;
+ this.showConfigType = this.realmList.length > 0;
if (this.showConfigType) {
- this.multisiteSetupForm.get('selectedRealm')?.setValue(this.realmsInfo[0].realm);
+ this.multisiteSetupForm.get('selectedRealm')?.setValue(this.realmList[0]);
this.cdr.detectChanges();
}
});
+
+ this.rgwMultisiteService.getRgwModuleStatus().subscribe((status: boolean) => {
+ this.rgwModuleStatus = status;
+ });
}
private loadRGWEndpoints(): void {
onSubmit() {
this.loading = true;
- const values = this.multisiteSetupForm.getRawValue();
- const realmName = values['realmName'];
- const zonegroupName = values['zonegroupName'];
- const zonegroupEndpoints = this.rgwEndpoints.value.join(',');
- const zoneName = values['zoneName'];
- const zoneEndpoints = this.rgwEndpoints.value.join(',');
- const username = values['username'];
- if (!this.isMultiClusterConfigured || this.stepsToSkip['Select Cluster']) {
- this.rgwMultisiteService
- .setUpMultisiteReplication(
- realmName,
- zonegroupName,
- zonegroupEndpoints,
- zoneName,
- zoneEndpoints,
- username
- )
- .subscribe((data: object[]) => {
- this.setupCompleted = true;
- this.rgwMultisiteService.setRestartGatewayMessage(false);
- this.loading = false;
- this.realms = data;
- this.showSuccessNotification();
- });
- } else {
- const cluster = values['cluster'];
- const replicationZoneName = values['replicationZoneName'];
- let selectedRealmName = '';
- if (this.multisiteSetupForm.get('configType').value === ConfigType.ExistingRealm) {
- selectedRealmName = this.multisiteSetupForm.get('selectedRealm').value;
- }
- this.rgwMultisiteService
- .setUpMultisiteReplication(
- realmName,
- zonegroupName,
- zonegroupEndpoints,
- zoneName,
- zoneEndpoints,
- username,
- cluster,
- replicationZoneName,
- this.clusterDetailsArray,
- selectedRealmName
- )
- .subscribe(
- () => {
+
+ const proceedWithSetup = () => {
+ this.cdr.detectChanges();
+ const values = this.multisiteSetupForm.getRawValue();
+ const realmName = values['realmName'];
+ const zonegroupName = values['zonegroupName'];
+ const zonegroupEndpoints = this.rgwEndpoints.value.join(',');
+ const zoneName = values['zoneName'];
+ const zoneEndpoints = this.rgwEndpoints.value.join(',');
+ const username = values['username'];
+
+ if (!this.isMultiClusterConfigured || this.stepsToSkip['Select Cluster']) {
+ this.rgwMultisiteService
+ .setUpMultisiteReplication(
+ realmName,
+ zonegroupName,
+ zonegroupEndpoints,
+ zoneName,
+ zoneEndpoints,
+ username
+ )
+ .subscribe((data: object[]) => {
this.setupCompleted = true;
this.rgwMultisiteService.setRestartGatewayMessage(false);
this.loading = false;
+ this.realms = data;
this.showSuccessNotification();
- },
- () => {
- this.multisiteSetupForm.setErrors({ cdSubmitButton: true });
- }
- );
+ });
+ } else {
+ const cluster = values['cluster'];
+ const replicationZoneName = values['replicationZoneName'];
+ let selectedRealmName = '';
+
+ if (this.multisiteSetupForm.get('configType').value === ConfigType.ExistingRealm) {
+ selectedRealmName = this.multisiteSetupForm.get('selectedRealm').value;
+ }
+
+ this.rgwMultisiteService
+ .setUpMultisiteReplication(
+ realmName,
+ zonegroupName,
+ zonegroupEndpoints,
+ zoneName,
+ zoneEndpoints,
+ username,
+ cluster,
+ replicationZoneName,
+ this.clusterDetailsArray,
+ selectedRealmName
+ )
+ .subscribe(
+ () => {
+ this.setupCompleted = true;
+ this.rgwMultisiteService.setRestartGatewayMessage(false);
+ this.loading = false;
+ this.showSuccessNotification();
+ },
+ () => {
+ this.multisiteSetupForm.setErrors({ cdSubmitButton: true });
+ }
+ );
+ }
+ };
+
+ if (!this.rgwModuleStatus) {
+ this.mgrModuleService.updateModuleState(
+ 'rgw',
+ false,
+ null,
+ '',
+ '',
+ false,
+ $localize`RGW module is being enabled. Waiting for the system to reconnect...`
+ );
+ const subscription = this.mgrModuleService.updateCompleted$.subscribe(() => {
+ subscription.unsubscribe();
+ proceedWithSetup();
+ });
+ } else {
+ proceedWithSetup();
}
}
import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
import { RgwZoneService } from '~/app/shared/api/rgw-zone.service';
import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
-import { ToastrModule } from 'ngx-toastr';
import { SharedModule } from '~/app/shared/shared.module';
+import { ToastrModule } from 'ngx-toastr';
+import { CommonModule } from '@angular/common';
import { ActivatedRoute } from '@angular/router';
describe('RgwOverviewDashboardComponent', () => {
useValue: { params: { subscribe: (fn: Function) => fn(params) } }
}
],
- imports: [HttpClientTestingModule, ToastrModule.forRoot(), SharedModule]
+ imports: [HttpClientTestingModule, SharedModule, ToastrModule.forRoot(), CommonModule]
}).compileComponents();
fixture = TestBed.createComponent(RgwOverviewDashboardComponent);
component = fixture.componentInstance;
import { ToastrModule } from 'ngx-toastr';
import { SharedModule } from '../shared.module';
import { BlockUIService } from 'ng-block-ui';
+import { SummaryService } from '../services/summary.service';
describe('MgrModuleService', () => {
let service: MgrModuleService;
spyOn(notificationService, 'suspendToasties');
spyOn(blockUIService, 'start');
spyOn(blockUIService, 'stop');
+ const summaryService = TestBed.inject(SummaryService);
+ spyOn(summaryService, 'startPolling');
});
it('should enable module', fakeAsync(() => {
service.updateModuleState(selected.name, selected.enabled);
tick(service.REFRESH_INTERVAL);
tick(service.REFRESH_INTERVAL);
+ tick(service.REFRESH_INTERVAL);
expect(service.enable).toHaveBeenCalledWith('foo');
expect(service.list).toHaveBeenCalledTimes(2);
expect(notificationService.suspendToasties).toHaveBeenCalledTimes(2);
import { Injectable } from '@angular/core';
import { BlockUIService } from 'ng-block-ui';
-import { Observable, timer as observableTimer } from 'rxjs';
+import { Observable, Subject, timer } from 'rxjs';
import { NotificationService } from '../services/notification.service';
import { TableComponent } from '../datatable/table/table.component';
import { Router } from '@angular/router';
import { MgrModuleInfo } from '../models/mgr-modules.interface';
import { NotificationType } from '../enum/notification-type.enum';
+import { delay, retryWhen, switchMap, tap } from 'rxjs/operators';
+import { SummaryService } from '../services/summary.service';
+
+const GLOBAL = 'global';
@Injectable({
providedIn: 'root'
})
export class MgrModuleService {
private url = 'api/mgr/module';
+ updateCompleted$ = new Subject<void>();
readonly REFRESH_INTERVAL = 2000;
private blockUI: BlockUIService,
private http: HttpClient,
private notificationService: NotificationService,
- private router: Router
+ private router: Router,
+ private summaryService: SummaryService
) {}
/**
table: TableComponent = null,
navigateTo: string = '',
notificationText?: string,
- navigateByUrl?: boolean
+ navigateByUrl?: boolean,
+ reconnectingMessage: string = $localize`Reconnecting, please wait ...`
): void {
- let $obs;
- const fnWaitUntilReconnected = () => {
- observableTimer(this.REFRESH_INTERVAL).subscribe(() => {
- // Trigger an API request to check if the connection is
- // re-established.
- this.list().subscribe(
- () => {
- // Resume showing the notification toasties.
- this.notificationService.suspendToasties(false);
- // Unblock the whole UI.
- this.blockUI.stop('global');
- // Reload the data table content.
- if (table) {
- table.refreshBtn();
- }
-
- if (notificationText) {
- this.notificationService.show(
- NotificationType.success,
- $localize`${notificationText}`
- );
- }
-
- if (!navigateTo) return;
-
- const navigate = () => this.router.navigate([navigateTo]);
-
- if (navigateByUrl) {
- this.router.navigateByUrl('/', { skipLocationChange: true }).then(navigate);
- } else {
- navigate();
- }
- },
- () => {
- fnWaitUntilReconnected();
- }
- );
- });
- };
-
- // Note, the Ceph Mgr is always restarted when a module
- // is enabled/disabled.
- if (enabled) {
- $obs = this.disable(module);
- } else {
- $obs = this.enable(module);
- }
- $obs.subscribe(
- () => undefined,
- () => {
- // Suspend showing the notification toasties.
+ const moduleToggle$ = enabled ? this.disable(module) : this.enable(module);
+
+ moduleToggle$.subscribe({
+ next: () => {
+ // Module toggle succeeded
+ this.updateCompleted$.next();
+ },
+ error: () => {
+ // Module toggle failed, trigger reconnect flow
this.notificationService.suspendToasties(true);
- // Block the whole UI to prevent user interactions until
- // the connection to the backend is reestablished
- this.blockUI.start('global', $localize`Reconnecting, please wait ...`);
- fnWaitUntilReconnected();
+ this.blockUI.start(GLOBAL, reconnectingMessage);
+
+ timer(this.REFRESH_INTERVAL)
+ .pipe(
+ switchMap(() => this.list()),
+ retryWhen((errors) =>
+ errors.pipe(
+ tap(() => {
+ // Keep retrying until list() succeeds
+ }),
+ delay(this.REFRESH_INTERVAL)
+ )
+ )
+ )
+ .subscribe({
+ next: () => {
+ // Reconnection successful
+ this.notificationService.suspendToasties(false);
+ this.blockUI.stop(GLOBAL);
+
+ if (table) {
+ table.refreshBtn();
+ }
+
+ if (notificationText) {
+ this.notificationService.show(
+ NotificationType.success,
+ $localize`${notificationText}`
+ );
+ }
+
+ if (navigateTo) {
+ const navigate = () => this.router.navigate([navigateTo]);
+ if (navigateByUrl) {
+ this.router.navigateByUrl('/', { skipLocationChange: true }).then(navigate);
+ } else {
+ navigate();
+ }
+ }
+
+ this.updateCompleted$.next();
+ this.summaryService.startPolling();
+ }
+ });
}
- );
+ });
}
}
import { TestBed } from '@angular/core/testing';
import { configureTestBed } from '~/testing/unit-test-helper';
import { RgwMultisiteService } from './rgw-multisite.service';
+import { BlockUIModule } from 'ng-block-ui';
+import { ToastrModule } from 'ngx-toastr';
+import { SharedModule } from '../shared.module';
const mockSyncPolicyData: any = [
{
configureTestBed({
providers: [RgwMultisiteService],
- imports: [HttpClientTestingModule]
+ imports: [
+ HttpClientTestingModule,
+ BlockUIModule.forRoot(),
+ ToastrModule.forRoot(),
+ SharedModule
+ ]
});
beforeEach(() => {
import { Injectable } from '@angular/core';
import { RgwRealm, RgwZone, RgwZonegroup } from '~/app/ceph/rgw/models/rgw-multisite';
import { RgwDaemonService } from './rgw-daemon.service';
-import { BehaviorSubject } from 'rxjs';
+import { BehaviorSubject, Observable } from 'rxjs';
+import { map } from 'rxjs/operators';
+import { MgrModuleInfo } from '../models/mgr-modules.interface';
+import { RGW } from '~/app/ceph/rgw/utils/constants';
+import { MgrModuleService } from './mgr-module.service';
@Injectable({
providedIn: 'root'
private restartGatewayMessageSource = new BehaviorSubject<boolean>(null);
restartGatewayMessage$ = this.restartGatewayMessageSource.asObservable();
- constructor(private http: HttpClient, public rgwDaemonService: RgwDaemonService) {}
+ constructor(
+ private http: HttpClient,
+ public rgwDaemonService: RgwDaemonService,
+ private mgrModuleService: MgrModuleService
+ ) {}
migrate(realm: RgwRealm, zonegroup: RgwZonegroup, zone: RgwZone, username: string) {
return this.rgwDaemonService.request((params: HttpParams) => {
setRestartGatewayMessage(value: boolean): void {
this.restartGatewayMessageSource.next(value);
}
+
+ getRgwModuleStatus(): Observable<boolean> {
+ return this.mgrModuleService.list().pipe(
+ map((moduleData: MgrModuleInfo[]) => {
+ const rgwModule = moduleData.find((module) => module.name === RGW);
+ return !!rgwModule?.enabled;
+ })
+ );
+ }
}
return this.http.put(`${this.url}/${realm.name}`, requestBody);
}
- list(): Observable<object> {
- return this.http.get<object>(`${this.url}`);
+ list(replicable?: boolean): Observable<object> {
+ let params = new HttpParams();
+ if (replicable) {
+ params = params.appendAll({
+ replicable: replicable.toString()
+ });
+ }
+ return this.http.get<object>(`${this.url}`, { params });
}
get(realm: RgwRealm): Observable<object> {
*ngIf="description">
{{ description }}
</h5>
+
+ <p class="text-center text-muted mt-3"
+ *ngIf="subDescription">
+ {{ subDescription }}
+ </p>
</ng-container>
<div class="w-50 row h-100 d-flex justify-content-center align-items-center mt-4">
@Input() status: string;
@Input() description: string;
@Input() subLabel: string;
+ @Input() subDescription: string;
@Input() completedItems: string;
@Input() actionName: string;
@Input() helperText: string;
- RgwMultisite
/api/rgw/realm:
get:
- parameters: []
+ parameters:
+ - allowEmptyValue: true
+ in: query
+ name: replicable
+ schema:
+ type: string
responses:
'200':
content:
if not selectedRealmName:
self.create_realm_and_zonegroup(
- realm_name, zonegroup_name, zone_name, zonegroup_ip_url)
+ realm_name, zonegroup_name, zone_name, zonegroup_ip_url, username)
self.create_zone_and_user(zone_name, zonegroup_name, username, zone_ip_url)
self.restart_daemons()
logger.error("Failed to update endpoints: %s", e)
raise
- def create_realm_and_zonegroup(self, realm: str, zg: str, zone: str, zg_url: str):
+ def create_realm_and_zonegroup(self, realm: str, zg: str, zone: str, zg_url: str,
+ username: str):
try:
rgw_multisite_instance = RgwMultisite()
- self.update_progress(f"Creating realm: {realm}, zonegroup: {zg} and zone: {zone}")
+ self.update_progress(
+ f"Initializing multi-site configuration || Creating realm: {realm}, \
+ zonegroup: {zg}, and zone: {zone} along \
+ with system user: {username}"
+ )
rgw_multisite_instance.create_realm(realm_name=realm, default=True)
rgw_multisite_instance.create_zonegroup(realm_name=realm, zonegroup_name=zg,
default=True, master=True, endpoints=zg_url)
def restart_daemons(self):
try:
- self.update_progress("Restarting RGW daemons and setting credentials")
+ self.update_progress(
+ "Restarting RGW daemons and configuring credentials || Restarts rgw services and \
+ applies access and secret keys on the source cluster"
+ )
RgwServiceManager().restart_rgw_daemons_and_set_credentials()
self.progress_done += 1
except Exception as e:
try:
realm_token_info = CephService.get_realm_tokens()
if fsid and realm_token_info and rep_zone and details_dict:
- self.update_progress(f"Importing realm token to cluster: {fsid}")
+ self.update_progress(
+ f"Setting up replication on cluster {fsid} || Enabling RGW module on \
+ target cluster (if disabled), importing realm \
+ configuration, and establishing the target zone"
+ )
self.import_realm_token_to_cluster(fsid, realm, zg, realm_token_info, username,
rep_zone, details_dict, selectedRealm)
else:
realm_export_token = self._get_realm_export_token(realm_token_info, realm_name)
cluster_url, cluster_token = self._get_cluster_details(cluster_fsid, cluster_details)
+ self._enable_rgw_module(cluster_url=cluster_url, cluster_token=cluster_token)
+
self._configure_selected_cluster(cluster_url, cluster_token, realm_name,
zonegroup_name, replication_zone_name)
replication_zone_name)
self.progress_done += 1
- self.update_progress(f"Checking for user {username} in the selected cluster \
- and setting credentials")
+ self.update_progress(
+ f"Verifying system user and completing replication setup on \
+ cluster {cluster_fsid} || Ensuring presence of user '{username}' \
+ and assigning necessary RGW credentials"
+ )
self._verify_user_and_daemons(cluster_url, cluster_token, realm_name,
replication_zone_name, username)
self.update_progress("Failed to import realm token to cluster:", 'fail', str(e))
raise
+ def _enable_rgw_module(self, cluster_url, cluster_token):
+ # Enable RGW module if not already enabled
+ mgr_modules_path = 'api/mgr/module'
+ multi_cluster_instance = MultiCluster()
+ # pylint: disable=protected-access
+ mgr_modules_info = multi_cluster_instance._proxy(
+ method='GET',
+ base_url=cluster_url,
+ path=mgr_modules_path,
+ token=cluster_token
+ )
+ logger.debug("mgr modules info in the selected cluster: %s", mgr_modules_info)
+
+ rgw_module = next((mod for mod in mgr_modules_info if mod["name"] == "rgw"), None)
+ rgw_module_status = rgw_module and rgw_module.get('enabled', False)
+
+ if not rgw_module_status:
+ logger.info("RGW module not enabled. Sending request to enable it.")
+ try:
+ # pylint: disable=protected-access
+ multi_cluster_instance._proxy(
+ method='POST',
+ base_url=cluster_url,
+ path='api/mgr/module/rgw/enable',
+ token=cluster_token
+ )
+ except Exception as e: # pylint: disable=broad-except
+ logger.warning("RGW enable request failed (likely due to connection reset).\
+ Ignoring and retrying later: %s", e)
+
+ max_retries = 10
+ delay = 5
+ retries = 0
+
+ while retries < max_retries:
+ time.sleep(delay)
+ try:
+ # pylint: disable=protected-access
+ mgr_modules_info = multi_cluster_instance._proxy(
+ method='GET',
+ base_url=cluster_url,
+ path=mgr_modules_path,
+ token=cluster_token
+ )
+ rgw_module = next((mod for mod in mgr_modules_info if mod["name"] == "rgw"),
+ None)
+ if rgw_module and rgw_module.get('enabled'):
+ logger.info("RGW module is now enabled after %d retries.", retries)
+ break
+ except Exception as e: # pylint: disable=broad-except
+ logger.warning("Failed to fetch RGW module status on retry %d: %s",
+ retries, str(e))
+ retries += 1
+ else:
+ logger.error("RGW module failed to enable after %d retries.", max_retries)
+ raise DashboardException('RGW module failed to enable after maximum retries',
+ http_status_code=500, component='rgw')
+
def _get_realm_export_token(self, realm_token_info, realm_name):
for realm_token in realm_token_info:
if realm_token['realm'] == realm_name:
logger.info("User %s not found yet, retrying in 5 seconds", username)
time.sleep(5)
+ # For a realm to be replicable it must have a master zone,
+ # valid endpoints and access/secret keys should be set
+ def get_replicable_realms_list(self):
+ replicable_realms = []
+ realms_info = RgwMultisite().list_realms()
+ realm_list = realms_info.get('realms', [])
+ for realm_name in realm_list: # pylint: disable=R1702
+ try:
+ realm_period = RgwMultisite().get_realm_period(realm_name)
+ master_zone_name = None
+ for zg in realm_period['period_map']['zonegroups']:
+ if not zg.get('is_master'):
+ continue
+ for zone in zg.get('zones', []):
+ if zone.get('id') == zg.get('master_zone'):
+ if zone.get('endpoints'):
+ master_zone_name = zone.get('name')
+ break
+ if not master_zone_name:
+ continue
+ zone_info = RgwMultisite().get_zone(master_zone_name)
+ system_key = zone_info.get('system_key', {})
+ access_key = system_key.get('access_key')
+ secret_key = system_key.get('secret_key')
+ if access_key and secret_key:
+ replicable_realms.append(realm_name)
+ except Exception as e: # pylint: disable=broad-except
+ logger.warning("Skipping realm '%s' due to error: %s", realm_name, e)
+ continue
+
+ return replicable_realms
+
class RgwRateLimit:
def get_global_rateLimit(self):
raise DashboardException(error, http_status_code=500, component='rgw')
return rgw_realm_list
+ def get_realm_period(self, realm_name: str):
+ realm_period = {}
+ rgw_realm_period_cmd = ['period', 'get', '--rgw-realm', realm_name]
+ try:
+ exit_code, out, _ = mgr.send_rgwadmin_command(rgw_realm_period_cmd)
+ if exit_code > 0:
+ raise DashboardException('Unable to get realm period',
+ http_status_code=500, component='rgw')
+ realm_period = out
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ return realm_period
+
def get_realm(self, realm_name: str):
realm_info = {}
rgw_realm_info_cmd = ['realm', 'get', '--rgw-realm', realm_name]