fsid = mgr.get('config')['fsid']
except KeyError:
fsid = ''
- if max_attempt == 0 or mgr.ACCESS_CTRL_DB.get_attempt(username) < max_attempt:
+ if max_attempt == 0 or mgr.ACCESS_CTRL_DB.get_attempt(username) < max_attempt: # pylint: disable=R1702,line-too-long # noqa: E501
if user_data:
user_perms = user_data.get('permissions')
pwd_expiration_date = user_data.get('pwdExpirationDate', None)
multicluster_config = Settings.MULTICLUSTER_CONFIG.copy()
try:
if fsid in multicluster_config['config']:
- existing_entries = multicluster_config['config'][fsid]
- if not any((entry['user'] == username or entry['cluster_alias'] == 'local-cluster') for entry in existing_entries): # noqa E501 #pylint: disable=line-too-long
- existing_entries.append({
+ cluster_configurations = multicluster_config['config'][fsid]
+ for config_item in cluster_configurations:
+ if config_item['user'] == username or config_item['cluster_alias'] == 'local-cluster': # noqa E501 #pylint: disable=line-too-long
+ config_item['token'] = token # Update token
+ break
+ else:
+ cluster_configurations.append({
"name": fsid,
"url": origin,
"cluster_alias": "local-cluster",
- "user": username
+ "user": username,
+ "token": token
})
else:
multicluster_config['config'][fsid] = [{
"name": fsid,
"url": origin,
"cluster_alias": "local-cluster",
- "user": username
+ "user": username,
+ "token": token
}]
except KeyError:
"name": fsid,
"url": origin,
"cluster_alias": "local-cluster",
- "user": username
+ "user": username,
+ "token": token
}
]
}
from ..security import Permission, Scope
from ..services.auth import AuthManager, JwtManager
from ..services.ceph_service import CephService
-from ..services.rgw_client import _SYNC_GROUP_ID, NoRgwDaemonsException, RgwClient, RgwMultisite
-from ..services.service import RgwServiceManager
+from ..services.rgw_client import _SYNC_GROUP_ID, NoRgwDaemonsException, \
+ RgwClient, RgwMultisite, RgwMultisiteAutomation
+from ..services.service import RgwServiceManager, wait_for_daemon_to_start
from ..tools import json_str_to_object, str_to_bool
from . import APIDoc, APIRouter, BaseController, CreatePermission, \
CRUDCollectionMethod, CRUDEndpoint, DeletePermission, Endpoint, \
# pylint: disable=W0102,W0613
def setup_multisite_replication(self, daemon_name=None, realm_name=None, zonegroup_name=None,
zonegroup_endpoints=None, zone_name=None, zone_endpoints=None,
- username=None, cluster_fsid=None):
- multisite_instance = RgwMultisite()
+ username=None, cluster_fsid=None, replication_zone_name=None,
+ cluster_details=None):
+ multisite_instance = RgwMultisiteAutomation()
result = multisite_instance.setup_multisite_replication(realm_name, zonegroup_name,
zonegroup_endpoints, zone_name,
zone_endpoints, username,
- cluster_fsid)
+ cluster_fsid,
+ replication_zone_name,
+ cluster_details)
return result
@RESTController.Collection(method='PUT', path='/setup-rgw-credentials')
# pylint: disable=W0102,W0613
def restart_rgw_daemons_and_set_credentials(self):
rgw_service_manager_instance = RgwServiceManager()
- result = rgw_service_manager_instance.restart_rgw_daemons_and_set_credentials()
+ result = rgw_service_manager_instance.configure_rgw_credentials()
+ return result
+
+ @RESTController.Collection(method='GET', path='/available-ports')
+ @allow_empty_body
+ # pylint: disable=W0102,W0613
+ def get_available_ports(self):
+ rgw_service_manager_instance = RgwServiceManager()
+ result = rgw_service_manager_instance.find_available_port()
+ return result
+
+ @RESTController.Collection(method='GET', path='/check-daemons-status')
+ @allow_empty_body
+ # pylint: disable=W0102,W0613
+ def check_daemons_status(self, service_name=None):
+ result = wait_for_daemon_to_start(service_name=service_name)
return result
-<div class="d-flex flex-column justify-content-center align-items-center bold"
- *ngIf="upgradeStatus$ | async as upgradeStatus">
- <ng-container *ngIf="upgradeStatus.in_progress && !upgradeStatus.is_paused; else upgradePaused">
- <h3 class="text-center"
- i18n>
- <i [ngClass]="[icons.large, icons.spin, icons.spinner]"></i>
- </h3>
-
- <h3 class="text-center mt-2">
- {{ executingTask?.description }}
- </h3>
-
- <h5 class="text-center mt-3"
- i18n>{{ upgradeStatus.which }}</h5>
- </ng-container>
-
- <div class="w-50 row h-100 d-flex justify-content-center align-items-center mt-4">
- <div class="text-center w-75">
- <ng-container *ngIf="upgradeStatus.services_complete.length > 0">
- Finished upgrading:
- <span class="text-success">
- {{ upgradeStatus.services_complete }}
- </span>
- </ng-container>
- <div class="mt-2">
- <ngb-progressbar type="info"
- [value]="executingTask?.progress"
- [striped]="true"
- [animated]="!upgradeStatus.is_paused"></ngb-progressbar>
- </div>
-
- <p class="card-text text-muted">
- <span class="float-end">
- {{ executingTask?.progress || 0 }} %
- </span>
- </p>
- </div>
- <h4 class="text-center m-2"
- i18n>{{ upgradeStatus.progress}}</h4>
-
- <h5 *ngIf="upgradeStatus.in_progress"
- class="text-center mt-2"
- i18n>
- {{ upgradeStatus.message }}
- </h5>
+<div *ngIf="upgradeStatus$ | async as upgradeStatus">
+ <ng-container>
+ <cd-progress [value]="executingTask?.progress"
+ [label]="executingTask?.description"
+ [status]="upgradeStatus.in_progress ? 'in-progress' : 'paused'"
+ [subLabel]="upgradeStatus.which"
+ [completedItems]="upgradeStatus.services_complete"
+ [actionName]="'upgrading'"
+ [helperText]="upgradeStatus.progress"
+ [footerText]="upgradeStatus.message"
+ [isPaused]="upgradeStatus.is_paused">
+ </cd-progress>
<div class="text-center mt-3">
<button class="btn btn-light"
aria-label="Stop Upgrade"
i18n>Stop</button>
</div>
- </div>
+ </ng-container>
</div>
<legend class="cd-header"
[showDownloadCopyButton]="false"
defaultTab="cluster-logs"
[scrollable]="true"></cd-logs>
-
-<ng-template #upgradePaused>
- <h3 class="text-center mt-3">
- <i [ngClass]="[icons.large, icons.spinner]"></i>
- </h3>
-
- <h3 class="text-center mt-3 mb-4">
- {{ executingTask?.description }}
- </h3>
-</ng-template>
routerLink="/services">
Cluster->Services</a>
</cd-alert-panel>
- <span *ngIf="!showMigrateAndReplicationActions; else migrateAndReplicationActionTpl">
- <cd-table-actions class="btn-group mb-4 me-2"
- [permission]="permission"
- [selection]="selection"
- [tableActions]="createTableActions"
- [primaryDropDown]="true">
- </cd-table-actions>
- </span>
- <ng-template #migrateAndReplicationActionTpl>
- <cd-table-actions class="btn-group mb-4 me-2"
- [permission]="permission"
- [selection]="selection"
- [tableActions]="multisiteReplicationActions">
- </cd-table-actions>
- <cd-table-actions class="btn-group mb-4 me-2 secondary"
- [permission]="permission"
- [btnColor]="'light'"
- [selection]="selection"
- [tableActions]="migrateTableAction">
- </cd-table-actions>
- </ng-template>
+ <cd-table-actions class="btn-group mb-4 me-2"
+ [permission]="permission"
+ [selection]="selection"
+ [tableActions]="multisiteReplicationActions">
+ </cd-table-actions>
+ <cd-table-actions *ngIf="showMigrateAndReplicationActions"
+ class="btn-group mb-4 me-2 secondary"
+ [permission]="permission"
+ [btnColor]="'light'"
+ [selection]="selection"
+ [tableActions]="migrateTableAction">
+ </cd-table-actions>
+ <cd-table-actions *ngIf="!showMigrateAndReplicationActions"
+ class="btn-group mb-4 me-2"
+ [permission]="permission"
+ [selection]="selection"
+ [tableActions]="createTableActions"
+ [primaryDropDown]="true">
+ </cd-table-actions>
<cd-table-actions class="btn-group mb-4 me-2"
[permission]="permission"
[btnColor]="'light'"
this.zoneIds = [];
this.evaluateMigrateAndReplicationActions();
this.rgwDaemonService.list().subscribe((data: any) => {
- const realmName = data.map((item: { [x: string]: any }) => item['realm_name']);
+ const hasEmptyRealmName = data.some(
+ (item: { [x: string]: any }) =>
+ item['realm_name'] === '' &&
+ !data.some((i: { [x: string]: any }) => i['id'] === item['id'] && i['realm_name'] !== '')
+ );
if (
- this.defaultRealmId != '' &&
- this.defaultZonegroupId != '' &&
- this.defaultZoneId != '' &&
- realmName.includes('')
+ this.defaultRealmId !== '' &&
+ this.defaultZonegroupId !== '' &&
+ this.defaultZoneId !== '' &&
+ hasEmptyRealmName
) {
this.restartGatewayMessage = true;
}
defaultZonegroupId: string,
defaultZoneId: string
): any {
- const defaultRealm = this.realms.find((x: { id: string }) => x.id === defaultRealmId);
- const defaultZonegroup = this.zonegroups.find(
+ const defaultRealm = this.realms?.find((x: { id: string }) => x.id === defaultRealmId);
+ const defaultZonegroup = this.zonegroups?.find(
(x: { id: string }) => x.id === defaultZonegroupId
);
- const defaultZone = this.zones.find((x: { id: string }) => x.id === defaultZoneId);
- const defaultRealmName = defaultRealm !== undefined ? defaultRealm.name : null;
- const defaultZonegroupName = defaultZonegroup !== undefined ? defaultZonegroup.name : null;
- const defaultZoneName = defaultZone !== undefined ? defaultZone.name : null;
+ const defaultZone = this.zones?.find((x: { id: string }) => x.id === defaultZoneId);
+
return {
- defaultRealmName: defaultRealmName,
- defaultZonegroupName: defaultZonegroupName,
- defaultZoneName: defaultZoneName
+ defaultRealmName: defaultRealm?.name,
+ defaultZonegroupName: defaultZonegroup?.name,
+ defaultZoneName: defaultZone?.name
};
}
--- /dev/null
+export enum StepTitles {
+ CreateRealmAndZonegroup = 'Create Realm & Zonegroup',
+ CreateZone = 'Create Zone',
+ SelectCluster = 'Select Cluster',
+ Review = 'Review'
+}
+
+export const STEP_TITLES_MULTI_CLUSTER_CONFIGURED = [
+ StepTitles.CreateRealmAndZonegroup,
+ StepTitles.CreateZone,
+ StepTitles.SelectCluster,
+ StepTitles.Review
+];
+
+export const STEP_TITLES_SINGLE_CLUSTER = [
+ StepTitles.CreateRealmAndZonegroup,
+ StepTitles.CreateZone,
+ StepTitles.Review
+];
#formDir="ngForm"
novalidate>
<ng-container [ngSwitch]="currentStep?.stepIndex">
- <cd-alert-panel *ngIf="loading"
- spacingClass="mb-3"
- type="info">
- <span i18n>Please note that this process can take some time. During this period, do not click the back button or close the wizard. Thank you for your patience.</span>
- </cd-alert-panel>
<div *ngSwitchCase="'0'"
class="ms-5">
+ <cd-alert-panel type="info"
+ spacingClass="mb-3">
+ This wizard enables you to set up multi-site replication within your
+ Ceph environment.If you have already added another cluster to your
+ multi-cluster setup, you can select that cluster in the wizard to
+ automate the replication process.If no additional cluster is currently
+ added, the wizard will guide you through creating the necessary realm,
+ zonegroup, and zone, and provide a realm token.This token can be used
+ later to manually import into a desired cluster to establish replication
+ between the clusters.
+ </cd-alert-panel>
<div class="form-group row">
<label class="cd-col-form-label required"
for="realmName"
placeholder="Realm name..."
id="realmName"
name="realmName"
- formControlName="realmName"
- modal-primary-focus>
+ formControlName="realmName">
<cd-help-text>
<span i18n>Enter a unique name for the Realm. The Realm is a logical grouping of all your Zonegroups.</span>
</cd-help-text>
</div>
<div *ngSwitchCase="'1'"
class="ms-5">
- <h4 class="title"
- i18n>Create Zone</h4>
<div class="form-group row">
<label class="cd-col-form-label required"
for="zonegroupName"
</div>
</div>
</div>
- <div class="ms-5"
+ <div cass="ms-5"
*ngSwitchCase="'2'">
- <div *ngIf="isMultiClusterConfigured; else exportTokenTemplate">
- <h4 class="title"
- i18n>Select Cluster</h4>
+ <div *ngIf="isMultiClusterConfigured; else nonMultiClusterTemplate">
<div class="form-group row">
<label class="cd-col-form-label required"
for="cluster"
- i18n>Cluster</label>
+ i18n>Replication Cluster</label>
<div class="cd-col-form-input">
<select class="form-select"
id="cluster"
</cd-alert-panel>
</div>
</div>
- </div>
- <ng-template #exportTokenTemplate>
- <h4 class="title"
- i18n>Export Token</h4>
- <div *ngFor="let realminfo of realms">
- <div class="form-group row">
- <label class="cd-col-form-label"
- for="realmName"
- i18n>Realm Name</label>
- <div class="cd-col-form-input">
- <input id="realmName"
- name="realmName"
- type="text"
- [value]="realminfo.realm"
- readonly>
- <cd-help-text>
- <span i18n>Name of the realm that will be involved in replication.</span>
- </cd-help-text>
- </div>
- </div>
- <div class="form-group row">
- <label class="cd-col-form-label"
- for="token"
- i18n>Token</label>
- <div class="cd-col-form-input">
- <input id="realmToken"
- name="realmToken"
- type="text"
- [value]="realminfo.token"
- class="me-2 mb-4"
- readonly>
- <cd-copy-2-clipboard-button [source]="realminfo.token"
- [byId]="false">
- </cd-copy-2-clipboard-button>
- <cd-help-text>
- <span i18n>This field displays the token needed to import the multisite configuration into a secondary cluster. Copy this token securely and use it on the secondary cluster to replicate the current multisite setup. Ensure that the token is handled securely to prevent unauthorized access.</span>
- </cd-help-text>
- </div>
+ <div class="form-group row">
+ <label class="cd-col-form-label required"
+ for="zonegroupName"
+ i18n>Replication Zone Name</label>
+ <div class="cd-col-form-input">
+ <input class="form-control"
+ type="text"
+ placeholder="Zone name..."
+ id="replicationZoneName"
+ name="replicationZoneName"
+ formControlName="replicationZoneName">
+ <cd-help-text>
+ <span i18n>Replication zone represents the zone to be created in the replication cluster where your data will be replicated.</span>
+ </cd-help-text>
+ <span class="invalid-feedback"
+ *ngIf="multisiteSetupForm.showError('replicationZoneName', formDir, 'required')"
+ i18n>This field is required.</span>
</div>
- <hr *ngIf="realms.length > 1">
</div>
- </ng-template>
+ </div>
+ </div>
+ <div *ngSwitchCase="'3'"
+ class="ms-5">
+ <div *ngIf="isMultiClusterConfigured">
+ <ng-container *ngIf="!loading; else loadingTemplate">
+ <ng-container *ngIf="!setupCompleted; else progressCompleteTemplate">
+ <ng-container *ngTemplateOutlet="reviewTemplate"></ng-container>
+ </ng-container>
+ </ng-container>
+ </div>
</div>
</ng-container>
</form>
</div>
</div>
<cds-modal-footer>
+ <button cdsButton="secondary"
+ name="skip-cluster-selection"
+ aria-label="Skip"
+ (click)="onSkip()"
+ *ngIf="stepTitles[currentStep.stepIndex]['label'] === 'Select Cluster'"
+ i18n>Skip</button>
<button cdsButton="secondary"
(click)="onPreviousStep()"
[attr.aria-label]="showCancelButtonLabel()"
<button cdsButton="primary"
(click)="onNextStep()"
aria-label="Next"
+ [disabled]="loading"
i18n>{{ showSubmitButtonLabel() }}
<cds-loading [isActive]="loading"
[overlay]="false"
</button>
</cds-modal-footer>
</cds-modal>
+
+<ng-template #nonMultiClusterTemplate>
+ <ng-container *ngIf="!loading; else loadingTemplate">
+ <ng-container *ngIf="!setupCompleted else exportTokenTemplate">
+ <ng-container *ngTemplateOutlet="reviewTemplate"></ng-container>
+ </ng-container>
+ </ng-container>
+</ng-template>
+
+<ng-template #loadingTemplate>
+ <ng-container *ngTemplateOutlet="progressTemplate"></ng-container>
+</ng-template>
+
+<ng-template #progressCompleteTemplate>
+ <div *ngIf="isMultiClusterConfigured && !stepsToSkip['Select Cluster']; else exportTokenTemplate">
+ <div class="text-center text-success"
+ i18n>
+ Multi-site replication setup is complete.
+ </div>
+ </div>
+</ng-template>
+
+<ng-template #progressTemplate>
+ <cd-progress [value]="executingTask?.progress"
+ [description]="executingTask?.name?.replace('progress/Multisite-Setup:', '')">
+ </cd-progress>
+</ng-template>
+
+<ng-template #exportTokenTemplate>
+ <div *ngFor="let realminfo of realms">
+ <div class="form-group row">
+ <label class="cd-col-form-label"
+ for="realmName"
+ i18n>Realm Name</label>
+ <div class="cd-col-form-input">
+ <input id="realmName"
+ name="realmName"
+ type="text"
+ [value]="realminfo.realm"
+ readonly>
+ <cd-help-text>
+ <span i18n>Name of the realm that will be involved in replication.</span>
+ </cd-help-text>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="cd-col-form-label"
+ for="token"
+ i18n>Token</label>
+ <div class="cd-col-form-input">
+ <input id="realmToken"
+ name="realmToken"
+ type="text"
+ [value]="realminfo.token"
+ class="me-2 mb-4"
+ readonly>
+ <cd-copy-2-clipboard-button [source]="realminfo.token"
+ [byId]="false">
+ </cd-copy-2-clipboard-button>
+ <cd-help-text>
+ <span i18n>This field displays the token needed to import the multisite configuration into a secondary cluster. Copy this token securely and use it on the secondary cluster to replicate the current multisite setup. Ensure that the token is handled securely to prevent unauthorized access.</span>
+ </cd-help-text>
+ </div>
+ </div>
+ <hr *ngIf="realms.length > 1">
+ </div>
+</ng-template>
+
+<ng-template #reviewTemplate>
+ <div class="form-group row">
+ <label class="cd-col-form-label"
+ i18n>Realm Name:</label>
+ <div class="cd-col-form-input mt-2 text-muted">
+ <b>{{ multisiteSetupForm.get('realmName').value }}</b>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="cd-col-form-label"
+ i18n>Zonegroup Name:</label>
+ <div class="cd-col-form-input mt-2 text-muted">
+ <b>{{ multisiteSetupForm.get('zonegroupName').value }}</b>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="cd-col-form-label"
+ i18n>Zonegroup Endpoints:</label>
+ <div class="cd-col-form-input mt-2 text-muted">
+ <b>{{ rgwEndpoints.value.join(', ') }}</b>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="cd-col-form-label"
+ i18n>Zone Name:</label>
+ <div class="cd-col-form-input mt-2 text-muted">
+ <b>{{ multisiteSetupForm.get('zoneName').value }}</b>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="cd-col-form-label"
+ i18n>Zone Endpoints:</label>
+ <div class="cd-col-form-input mt-2 text-muted">
+ <b>{{ rgwEndpoints.value.join(', ') }}</b>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="cd-col-form-label"
+ i18n>Username:</label>
+ <div class="cd-col-form-input mt-2 text-muted">
+ <b>{{ multisiteSetupForm.get('username').value }}</b>
+ </div>
+ </div>
+ <div *ngIf="isMultiClusterConfigured && !stepsToSkip['Select Cluster']">
+ <div class="form-group row">
+ <label class="cd-col-form-label"
+ i18n>Selected Replication Cluster:</label>
+ <div class="cd-col-form-input mt-2 text-muted">
+ <b>{{ selectedCluster }}</b>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="cd-col-form-label"
+ i18n>Replication Zone Name:</label>
+ <div class="cd-col-form-input mt-2 text-muted">
+ <b>{{ multisiteSetupForm.get('replicationZoneName').value }}</b>
+ </div>
+ </div>
+ </div>
+</ng-template>
import { Component, OnInit } from '@angular/core';
+import { Location } from '@angular/common';
import { UntypedFormControl, Validators } from '@angular/forms';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { Subscription, forkJoin } from 'rxjs';
import { SelectMessages } from '~/app/shared/components/select/select-messages.model';
import { NotificationType } from '~/app/shared/enum/notification-type.enum';
import { NotificationService } from '~/app/shared/services/notification.service';
-import { ActivatedRoute, Router } from '@angular/router';
+import { ActivatedRoute } from '@angular/router';
import { map, switchMap } from 'rxjs/operators';
import { BaseModal, Step } from 'carbon-components-angular';
-import { Location } from '@angular/common';
+import { SummaryService } from '~/app/shared/services/summary.service';
+import { ExecutingTask } from '~/app/shared/models/executing-task';
+import {
+ STEP_TITLES_MULTI_CLUSTER_CONFIGURED,
+ STEP_TITLES_SINGLE_CLUSTER
+} from './multisite-wizard-steps.enum';
@Component({
selector: 'cd-rgw-multisite-wizard',
currentStep: WizardStepModel;
currentStepSub: Subscription;
permissions: Permissions;
- stepTitles: Step[] = [
- {
- label: 'Create Realm & Zonegroup'
- },
- {
- label: 'Create Zone'
- },
- {
- label: 'Select Cluster'
- }
- ];
+ stepTitles: Step[] = STEP_TITLES_MULTI_CLUSTER_CONFIGURED.map((title) => ({
+ label: title
+ }));
stepsToSkip: { [steps: string]: boolean } = {};
daemons: RgwDaemon[] = [];
selectedCluster = '';
pageURL: string;
icons = Icons;
rgwEndpoints: { value: any[]; options: any[]; messages: any };
+ executingTask: ExecutingTask;
+ setupCompleted = false;
constructor(
private wizardStepsService: WizardStepsService,
private multiClusterService: MultiClusterService,
private rgwMultisiteService: RgwMultisiteService,
public notificationService: NotificationService,
- private router: Router,
private route: ActivatedRoute,
+ private summaryService: SummaryService,
private location: Location
) {
super();
this.multiClusterService.getCluster().subscribe((clusters) => {
this.clusterDetailsArray = Object.values(clusters['config'])
.flat()
- .filter((cluster) => cluster['cluster_alias'] !== 'local-cluster');
+ .filter((cluster) => cluster['url'] !== clusters['current_url']);
this.isMultiClusterConfigured = this.clusterDetailsArray.length > 0;
if (!this.isMultiClusterConfigured) {
- this.stepTitles = [
- {
- label: 'Create Realm & Zonegroup'
- },
- {
- label: 'Create Zone'
- },
- {
- label: 'Export Multi-site token'
- }
- ];
+ this.stepTitles = STEP_TITLES_SINGLE_CLUSTER.map((title) => ({
+ label: title
+ }));
this.stepTitles.forEach((steps, index) => {
steps.onClick = () => (this.currentStep.stepIndex = index);
});
} else {
this.selectedCluster = this.clusterDetailsArray[0]['name'];
}
+ this.wizardStepsService.setTotalSteps(this.stepTitles.length);
+ });
+
+ this.summaryService.subscribe((summary) => {
+ this.executingTask = summary.executing_tasks.filter((tasks) =>
+ tasks.name.includes('progress/Multisite-Setup')
+ )[0];
+ });
+
+ this.stepTitles.forEach((stepTitle) => {
+ this.stepsToSkip[stepTitle.label] = false;
});
}
}),
cluster: new UntypedFormControl(null, {
validators: [Validators.required]
+ }),
+ replicationZoneName: new UntypedFormControl('new_replicated_zone', {
+ validators: [Validators.required]
})
});
}
showSubmitButtonLabel() {
- if (this.isMultiClusterConfigured) {
- return !this.wizardStepsService.isLastStep()
- ? this.actionLabels.NEXT
- : $localize`Configure Multi-site`;
+ if (this.wizardStepsService.isLastStep()) {
+ if (!this.setupCompleted) {
+ if (this.isMultiClusterConfigured) {
+ return $localize`Configure Multi-Site`;
+ } else {
+ return $localize`Export Multi-Site token`;
+ }
+ } else {
+ return $localize`Close`;
+ }
} else {
- return !this.wizardStepsService.isLastStep() ? this.actionLabels.NEXT : $localize`Close`;
+ return $localize`Next`;
}
}
onNextStep() {
if (!this.wizardStepsService.isLastStep()) {
- this.wizardStepsService.getCurrentStep().subscribe((step: WizardStepModel) => {
- this.currentStep = step;
- });
- if (this.currentStep.stepIndex === 2 && !this.isMultiClusterConfigured) {
- this.onSubmit();
+ this.wizardStepsService.moveToNextStep();
+ } else {
+ if (this.setupCompleted) {
+ this.closeModal();
} else {
- this.wizardStepsService.moveToNextStep();
+ this.onSubmit();
}
- } else {
- this.onSubmit();
}
+ this.wizardStepsService.getCurrentStep().subscribe((step: WizardStepModel) => {
+ this.currentStep = step;
+ if (this.currentStep.stepIndex === 2 && this.isMultiClusterConfigured) {
+ this.stepsToSkip['Select Cluster'] = false;
+ }
+ });
}
onSubmit() {
this.loading = true;
- const values = this.multisiteSetupForm.value;
+ 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) {
- if (this.wizardStepsService.isLastStep()) {
- this.activeModal.close();
- this.refreshMultisitePage();
- } else {
- this.rgwMultisiteService
- .setUpMultisiteReplication(
- realmName,
- zonegroupName,
- zonegroupEndpoints,
- zoneName,
- zoneEndpoints,
- username
- )
- .subscribe((data: object[]) => {
- this.loading = false;
- this.realms = data;
- this.wizardStepsService.moveToNextStep();
- this.showSuccessNotification();
- });
- }
+ if (!this.isMultiClusterConfigured || this.stepsToSkip['Select Cluster']) {
+ this.rgwMultisiteService
+ .setUpMultisiteReplication(
+ realmName,
+ zonegroupName,
+ zonegroupEndpoints,
+ zoneName,
+ zoneEndpoints,
+ username
+ )
+ .subscribe((data: object[]) => {
+ this.setupCompleted = true;
+ this.loading = false;
+ this.realms = data;
+ this.showSuccessNotification();
+ });
} else {
const cluster = values['cluster'];
+ const replicationZoneName = values['replicationZoneName'];
this.rgwMultisiteService
.setUpMultisiteReplication(
realmName,
zoneName,
zoneEndpoints,
username,
- cluster
+ cluster,
+ replicationZoneName,
+ this.clusterDetailsArray
)
.subscribe(
() => {
+ this.setupCompleted = true;
+ this.loading = false;
this.showSuccessNotification();
- this.activeModal.close();
- this.refreshMultisitePage();
},
() => {
this.multisiteSetupForm.setErrors({ cdSubmitButton: true });
);
}
- refreshMultisitePage() {
- const currentRoute = this.router.url.split('?')[0];
- const navigateTo = currentRoute.includes('multisite') ? '/pool' : '/';
- this.router.navigateByUrl(navigateTo, { skipLocationChange: true }).then(() => {
- this.router.navigate([currentRoute]);
- });
- }
-
onPreviousStep() {
if (!this.wizardStepsService.isFirstStep()) {
this.wizardStepsService.moveToPreviousStep();
this.stepsToSkip[stepTitle.label] = true;
this.onNextStep();
}
+
+ closeModal(): void {
+ this.location.back();
+ }
}
import {
NgbNavModule,
NgbPopoverModule,
+ NgbProgressbar,
NgbTooltipModule,
NgbTypeaheadModule
} from '@ng-bootstrap/ng-bootstrap';
ModalModule,
ProgressIndicatorModule
} from 'carbon-components-angular';
+import { CephSharedModule } from '../shared/ceph-shared.module';
@NgModule({
imports: [
CommonModule,
+ CephSharedModule,
SharedModule,
FormsModule,
ReactiveFormsModule.withConfig({ callSetDisabledState: 'whenDisabledForLegacyCode' }),
ProgressIndicatorModule,
ButtonModule,
LoadingModule,
- IconModule
+ IconModule,
+ NgbProgressbar
],
exports: [
RgwDaemonListComponent,
zoneName: string,
zoneEndpoints: string,
username: string,
- cluster?: string
+ cluster?: string,
+ replicationZoneName?: string,
+ clusterDetailsArray?: any
) {
let params = new HttpParams()
.set('realm_name', realmName)
params = params.set('cluster_fsid', cluster);
}
+ if (clusterDetailsArray) {
+ params = params.set('cluster_details', JSON.stringify(clusterDetailsArray));
+ }
+
+ if (replicationZoneName) {
+ params = params.set('replication_zone_name', replicationZoneName);
+ }
+
return this.http.post(`${this.uiUrl}/multisite-replications`, null, { params: params });
}
import { HelpTextComponent } from './help-text/help-text.component';
import { FormAdvancedFieldsetComponent } from './form-advanced-fieldset/form-advanced-fieldset.component';
import { UpgradableComponent } from './upgradable/upgradable.component';
+import { ProgressComponent } from './progress/progress.component';
// Icons
import InfoIcon from '@carbon/icons/es/information/16';
CardGroupComponent,
HelpTextComponent,
FormAdvancedFieldsetComponent,
- UpgradableComponent
+ UpgradableComponent,
+ ProgressComponent
],
providers: [],
exports: [
CardGroupComponent,
HelpTextComponent,
FormAdvancedFieldsetComponent,
- UpgradableComponent
+ UpgradableComponent,
+ ProgressComponent
]
})
export class ComponentsModule {
--- /dev/null
+<div class="d-flex flex-column justify-content-center align-items-center bold">
+ <ng-container>
+ <h3 class="text-center"
+ [ngClass]="{ 'mt-3': status === 'in-progress' && isPaused }"
+ *ngIf="!value || actionName === 'upgrading'">
+ <i *ngIf="status === 'in-progress' && isPaused; else spinningIcon"
+ [ngClass]="[icons.large, icons.spinner]"></i>
+ <ng-template #spinningIcon>
+ <i [ngClass]="[icons.large, icons.spin, icons.spinner]"></i>
+ </ng-template>
+ </h3>
+
+ <h3 class="text-center"
+ [ngClass]="status === 'in-progress' && isPaused ? ['mt-3', 'mb-4'] : 'mt-2'"
+ *ngIf="label">
+ {{ label }}
+ </h3>
+
+ <h5 class="text-center mt-3"
+ *ngIf="subLabel && status === 'in-progress' && !isPaused">
+ {{ subLabel }}
+ </h5>
+
+ <h5 class="text-center mt-3"
+ *ngIf="description">
+ {{ description }}
+ </h5>
+ </ng-container>
+
+ <div class="w-50 row h-100 d-flex justify-content-center align-items-center mt-4">
+ <div class="text-center w-75">
+ <ng-container *ngIf="completedItems && completedItems.length > 0"
+ i18n>
+ Finished {{ actionName }}:
+ <span class="text-success">
+ {{ completedItems }}
+ </span>
+ </ng-container>
+
+ <!-- Progress Bar -->
+ <div class="mt-2">
+ <ngb-progressbar type="info"
+ [value]="value"
+ [striped]="true"
+ [animated]="!isPaused"></ngb-progressbar>
+ </div>
+ <p class="card-text text-muted">
+ <span class="float-end">{{ value || 0 }} %</span>
+ </p>
+ </div>
+
+ <h4 class="text-center m-2"
+ *ngIf="helperText">
+ {{ helperText }}
+ </h4>
+
+ <h5 class="text-center mt-2"
+ *ngIf="footerText">
+ {{ footerText }}
+ </h5>
+ </div>
+</div>
--- /dev/null
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ProgressComponent } from './progress.component';
+
+describe('ProgressComponent', () => {
+ let component: ProgressComponent;
+ let fixture: ComponentFixture<ProgressComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ProgressComponent]
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ProgressComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
--- /dev/null
+import { Component, Input } from '@angular/core';
+import { Icons } from '../../enum/icons.enum';
+
+@Component({
+ selector: 'cd-progress',
+ templateUrl: './progress.component.html',
+ styleUrls: ['./progress.component.scss']
+})
+export class ProgressComponent {
+ icons = Icons;
+ @Input() value: number;
+ @Input() label: string;
+ @Input() status: string;
+ @Input() description: string;
+ @Input() subLabel: string;
+ @Input() completedItems: string;
+ @Input() actionName: string;
+ @Input() helperText: string;
+ @Input() footerText: string;
+ @Input() isPaused: boolean;
+}
-import { Component, Input, OnDestroy, OnInit } from '@angular/core';
+import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
import { Step } from 'carbon-components-angular';
import * as _ from 'lodash';
templateUrl: './wizard.component.html',
styleUrls: ['./wizard.component.scss']
})
-export class WizardComponent implements OnInit, OnDestroy {
+export class WizardComponent implements OnInit, OnDestroy, OnChanges {
@Input()
stepsTitle: Step[];
}
ngOnInit(): void {
+ this.initializeSteps();
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (changes.stepsTitle && !changes.stepsTitle.isFirstChange()) {
+ this.initializeSteps();
+ }
+ }
+
+ private initializeSteps(): void {
this.stepsService.setTotalSteps(this.stepsTitle.length);
this.steps = this.stepsService.getSteps();
this.currentStepSub = this.stepsService.getCurrentStep().subscribe((step: WizardStepModel) => {
# pylint: disable=C0302
# pylint: disable=too-many-branches
# pylint: disable=too-many-lines
-import ast
import ipaddress
import json
import logging
import os
import re
import time
+import uuid
import xml.etree.ElementTree as ET # noqa: N814
from enum import Enum
from subprocess import SubprocessError
symmetrical = 'symmetrical'
-class RgwMultisite:
- def migrate_to_multisite(self, realm_name: str, zonegroup_name: str, zone_name: str,
- zonegroup_endpoints: str, zone_endpoints: str, access_key: str,
- secret_key: str):
- rgw_realm_create_cmd = ['realm', 'create', '--rgw-realm', realm_name, '--default']
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_realm_create_cmd, False)
- if exit_code > 0:
- raise DashboardException(e=err, msg='Unable to create realm',
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
-
- rgw_zonegroup_edit_cmd = ['zonegroup', 'rename', '--rgw-zonegroup', 'default',
- '--zonegroup-new-name', zonegroup_name]
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_edit_cmd, False)
- if exit_code > 0:
- raise DashboardException(e=err, msg='Unable to rename zonegroup to {}'.format(zonegroup_name), # noqa E501 #pylint: disable=line-too-long
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
-
- rgw_zone_edit_cmd = ['zone', 'rename', '--rgw-zone',
- 'default', '--zone-new-name', zone_name,
- '--rgw-zonegroup', zonegroup_name]
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_edit_cmd, False)
- if exit_code > 0:
- raise DashboardException(e=err, msg='Unable to rename zone to {}'.format(zone_name), # noqa E501 #pylint: disable=line-too-long
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
-
- rgw_zonegroup_modify_cmd = ['zonegroup', 'modify',
- '--rgw-realm', realm_name,
- '--rgw-zonegroup', zonegroup_name]
- if zonegroup_endpoints:
- rgw_zonegroup_modify_cmd.append('--endpoints')
- rgw_zonegroup_modify_cmd.append(zonegroup_endpoints)
- rgw_zonegroup_modify_cmd.append('--master')
- rgw_zonegroup_modify_cmd.append('--default')
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_modify_cmd)
- if exit_code > 0:
- raise DashboardException(e=err, msg='Unable to modify zonegroup {}'.format(zonegroup_name), # noqa E501 #pylint: disable=line-too-long
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
-
- rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-realm', realm_name,
- '--rgw-zonegroup', zonegroup_name,
- '--rgw-zone', zone_name]
- if zone_endpoints:
- rgw_zone_modify_cmd.append('--endpoints')
- rgw_zone_modify_cmd.append(zone_endpoints)
- rgw_zone_modify_cmd.append('--master')
- rgw_zone_modify_cmd.append('--default')
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd)
- if exit_code > 0:
- raise DashboardException(e=err, msg='Unable to modify zone',
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
-
- if access_key and secret_key:
- rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-zone', zone_name,
- '--access-key', access_key, '--secret', secret_key]
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd)
- if exit_code > 0:
- raise DashboardException(e=err, msg='Unable to modify zone',
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
- self.update_period()
+class RgwMultisiteAutomation:
+ def __init__(self):
+ self.progress_id = str(uuid.uuid4())
+ self.progress_title = ''
+ self.progress_done = 0
+ self.progress_total = 2 # Total number of major steps
+
+ def update_progress(self, progress_title, progress_action='update', failure_msg=None):
+ self.progress_title = 'Multisite-Setup: ' + progress_title
+ progress = (self.progress_done / self.progress_total)
+ if progress_action == 'update':
+ mgr.remote('progress', progress_action, self.progress_id,
+ ev_msg=self.progress_title,
+ ev_progress=progress,
+ add_to_ceph_s=True)
+ if progress_action == 'fail':
+ mgr.remote('progress', 'fail', self.progress_id, failure_msg)
+ if progress_action == 'complete':
+ mgr.remote('progress', 'complete', self.progress_id)
def replace_hostname(self, endpoint, hostname_to_ip):
# Replace the hostname in the endpoint URL with its corresponding IP address.
def setup_multisite_replication(self, realm_name: str, zonegroup_name: str,
zonegroup_endpoints: str, zone_name: str,
zone_endpoints: str, username: str,
- cluster_fsid: Optional[str] = None):
+ cluster_fsid: Optional[str] = None,
+ replication_zone_name: Optional[str] = None,
+ cluster_details: Optional[str] = None):
# Set up multisite replication for Ceph RGW.
logger.info("Starting multisite replication setup")
+ if cluster_details:
+ cluster_details_dict = json.loads(cluster_details)
orch = OrchClient.instance()
+ rgw_multisite_instance = RgwMultisite()
+
+ if cluster_fsid:
+ self.progress_total = 4
def get_updated_endpoints(endpoints):
# Update endpoint URLs by replacing hostnames with IP addresses.
- logger.debug("Updating endpoints: %s", endpoints)
try:
hostname_to_ip = {host['hostname']: host['addr'] for host in (h.to_json() for h in orch.hosts.list())} # noqa E501 # pylint: disable=line-too-long
updated_endpoints = [self.replace_hostname(endpoint, hostname_to_ip) for endpoint in endpoints.split(',')] # noqa E501 # pylint: disable=line-too-long
zone_ip_url = ','.join(get_updated_endpoints(zone_endpoints))
try:
# Create the realm and zonegroup
+ self.update_progress(
+ f"Creating realm: {realm_name}, zonegroup: {zonegroup_name} and zone: {zone_name}")
logger.info("Creating realm: %s", realm_name)
- self.create_realm(realm_name=realm_name, default=True)
+ rgw_multisite_instance.create_realm(realm_name=realm_name, default=True)
logger.info("Creating zonegroup: %s", zonegroup_name)
- self.create_zonegroup(realm_name=realm_name, zonegroup_name=zonegroup_name,
- default=True, master=True, endpoints=zonegroup_ip_url)
+ rgw_multisite_instance.create_zonegroup(realm_name=realm_name,
+ zonegroup_name=zonegroup_name,
+ default=True, master=True,
+ endpoints=zonegroup_ip_url)
except Exception as e:
logger.error("Failed to create realm or zonegroup: %s", e)
+ self.update_progress("Failed to create realm or zonegroup", 'fail', str(e))
raise
try:
# Create the zone and system user, then modify the zone with user credentials
logger.info("Creating zone: %s", zone_name)
- if self.create_zone(zone_name=zone_name, zonegroup_name=zonegroup_name,
- default=True, master=True, endpoints=zone_ip_url,
- access_key=None, secret_key=None):
+ if rgw_multisite_instance.create_zone(zone_name=zone_name,
+ zonegroup_name=zonegroup_name,
+ default=True, master=True,
+ endpoints=zone_ip_url,
+ access_key=None,
+ secret_key=None):
+ self.progress_done += 1
logger.info("Creating system user: %s", username)
- user_details = self.create_system_user(username, zone_name)
+ user_details = rgw_multisite_instance.create_system_user(username, zone_name)
if user_details:
keys = user_details['keys'][0]
- logger.info("Modifying zone with user credentials: %s", username)
- self.modify_zone(zone_name=zone_name, zonegroup_name=zonegroup_name,
- default='true', master='true', endpoints=zone_ip_url,
- access_key=keys['access_key'],
- secret_key=keys['secret_key'])
+ access_key = keys['access_key']
+ secret_key = keys['secret_key']
+ if access_key and secret_key:
+ rgw_multisite_instance.modify_zone(zone_name=zone_name,
+ zonegroup_name=zonegroup_name,
+ default='true', master='true',
+ endpoints=zone_ip_url,
+ access_key=keys['access_key'],
+ secret_key=keys['secret_key'])
+ else:
+ raise ValueError("Access key or secret key is missing")
except Exception as e:
logger.error("Failed to create zone or system user: %s", e)
+ self.update_progress("Failed to create zone or system user:", 'fail', str(e))
raise
try:
- # Restart RGW daemons and set credentials
logger.info("Restarting RGW daemons and setting credentials")
+ self.update_progress("Restarting RGW daemons and setting credentials")
rgw_service_manager = RgwServiceManager()
rgw_service_manager.restart_rgw_daemons_and_set_credentials()
+ self.progress_done += 1
except Exception as e:
- logger.error("Failed to restart RGW daemons: %s", e)
+ logger.error("Failed to restart RGW daemon: %s", e)
+ self.update_progress("Failed to restart RGW daemons:", 'fail', str(e))
raise
try:
# Get realm tokens and import to another cluster if specified
logger.info("Getting realm tokens")
realm_token_info = CephService.get_realm_tokens()
+ logger.info("Realm tokens: %s", realm_token_info)
- if cluster_fsid and realm_token_info:
+ if cluster_fsid and realm_token_info and replication_zone_name and cluster_details_dict:
logger.info("Importing realm token to cluster: %s", cluster_fsid)
+ self.update_progress(f"Importing realm token to cluster: {cluster_fsid}")
self.import_realm_token_to_cluster(cluster_fsid, realm_name,
- realm_token_info, username)
+ zonegroup_name, realm_token_info,
+ username, replication_zone_name,
+ cluster_details_dict)
+ else:
+ self.update_progress("Realm Export Token fetched successfully", 'complete')
except Exception as e:
logger.error("Failed to get realm tokens or import to cluster: %s", e)
+ self.update_progress("Failed to get realm tokens or import to cluster:", 'fail', str(e))
raise
logger.info("Multisite replication setup completed")
return realm_token_info
- def import_realm_token_to_cluster(self, cluster_fsid, realm_name, realm_token_info, username):
- logger.info("Importing realm token to cluster: %s", cluster_fsid)
+ def import_realm_token_to_cluster(self, cluster_fsid, realm_name, zonegroup_name,
+ realm_token_info, username, replication_zone_name,
+ cluster_details):
try:
for realm_token in realm_token_info:
if realm_token['realm'] == realm_name:
break
else:
raise ValueError(f"Realm {realm_name} not found in realm tokens")
- multi_cluster_config_str = str(mgr.get_module_option_ex('dashboard', 'MULTICLUSTER_CONFIG')) # noqa E501 # pylint: disable=line-too-long
- multi_cluster_config = ast.literal_eval(multi_cluster_config_str)
- for fsid, clusters in multi_cluster_config['config'].items():
- if fsid == cluster_fsid:
- for cluster_info in clusters:
- cluster_token = cluster_info.get('token')
- cluster_url = cluster_info.get('url')
- break
- else:
- raise ValueError(f"No cluster token found for fsid: {cluster_fsid}")
+ for cluster in cluster_details:
+ if cluster['name'] == cluster_fsid:
+ cluster_token = cluster['token']
+ cluster_url = cluster['url']
break
- else:
- raise ValueError(f"Cluster fsid {cluster_fsid} not found in multi-cluster config")
if cluster_token:
- placement_spec: Dict[str, Dict] = {"placement": {}}
- payload = {
- 'realm_token': realm_export_token,
- 'zone_name': 'new_replicated_zone',
- 'port': 81,
- 'placement_spec': placement_spec
- }
-
if not cluster_url.endswith('/'):
cluster_url += '/'
path = 'api/rgw/realm/import_realm_token'
try:
multi_cluster_instance = MultiCluster()
+ daemon_name = f"{realm_name}.{replication_zone_name}"
# pylint: disable=protected-access
- response = multi_cluster_instance._proxy(method='POST', base_url=cluster_url,
- path=path, payload=payload,
- token=cluster_token)
- logger.info("Successfully imported realm token to cluster: %s", cluster_fsid)
- self.check_user_in_second_cluster(cluster_url, cluster_token, username)
- return response
+ config_payload = {
+ 'realm_name': realm_name,
+ 'zonegroup_name': zonegroup_name,
+ 'zone_name': replication_zone_name,
+ 'daemon_name': daemon_name,
+ }
+ config_info = multi_cluster_instance._proxy(method='PUT', base_url=cluster_url,
+ path='api/rgw/daemon/set_multisite_config', # noqa E501 # pylint: disable=line-too-long
+ payload=config_payload,
+ token=cluster_token)
+ logger.info("setting config response: %s", config_info)
+ available_port = multi_cluster_instance._proxy(method='GET',
+ base_url=cluster_url,
+ path='ui-api/rgw/multisite/available-ports', # noqa E501 # pylint: disable=line-too-long
+ token=cluster_token)
+ placement_spec: Dict[str, Dict] = {"placement": {}}
+ payload = {
+ 'realm_token': realm_export_token,
+ 'zone_name': replication_zone_name,
+ 'port': available_port,
+ 'placement_spec': placement_spec,
+ }
+ token_import_response = multi_cluster_instance._proxy(method='POST',
+ base_url=cluster_url,
+ path=path,
+ payload=payload,
+ token=cluster_token)
+ logger.info("Import realm token response: %s", token_import_response)
+ self.progress_done += 1
+ self.update_progress(f"Checking for user {username} in the selected cluster and setting credentials") # noqa E501 # pylint: disable=line-too-long
+ service_name = f"rgw.{daemon_name}"
+ daemons_status = multi_cluster_instance._proxy(method='GET',
+ base_url=cluster_url,
+ path=f'ui-api/rgw/multisite/check-daemons-status?service_name={service_name}', # noqa E501 # pylint: disable=line-too-long
+ token=cluster_token)
+ logger.info("Daemons status: %s", daemons_status)
+ if daemons_status is True:
+ self.check_user_in_second_cluster(cluster_url, cluster_token,
+ username, replication_zone_name)
+ else:
+ self.update_progress("Failed to set credentials in selected cluster", 'fail', "RGW daemons failed to start") # noqa E501 # pylint: disable=line-too-long
+ return token_import_response
except requests.RequestException as e:
logger.error("Could not reach %s: %s", cluster_url, e)
raise DashboardException(f"Could not reach {cluster_url}: {e}",
component='dashboard')
except Exception as e:
logger.error("Failed to import realm token to cluster: %s", e)
+ self.update_progress("Failed to import realm token to cluster:", 'fail', str(e))
raise
- def check_user_in_second_cluster(self, cluster_url, cluster_token, username):
+ def check_user_in_second_cluster(self, cluster_url, cluster_token, username,
+ replication_zone_name):
logger.info("Checking for user %s in the second cluster", username)
- path = 'api/rgw/zone/get_user_list?zoneName=new_replicated_zone'
+ path = f'api/rgw/zone/get_user_list?zoneName={replication_zone_name}'
user_found = False
start_time = time.time()
while not user_found:
# pylint: disable=protected-access
user_content = multi_cluster_instance._proxy(method='GET', base_url=cluster_url,
path=path, token=cluster_token)
- logger.info("User content in the second cluster: %s", user_content)
- for user in user_content:
- if user['user_id'] == username:
- user_found = True
- logger.info("User %s found in the second cluster", username)
- # pylint: disable=protected-access
- restart_daemons_content = multi_cluster_instance._proxy(method='PUT', base_url=cluster_url, # noqa E501 # pylint: disable=line-too-long
- path='ui-api/rgw/multisite/setup-rgw-credentials', # noqa E501 # pylint: disable=line-too-long
- token=cluster_token) # noqa E501 # pylint: disable=line-too-long
- logger.info("Restarted RGW daemons in the second cluster: %s", restart_daemons_content) # noqa E501 # pylint: disable=line-too-long
- break
+ if isinstance(user_content, list) and username in user_content:
+ user_found = True
+ logger.info("User %s found in the second cluster", username)
+ # pylint: disable=protected-access
+ set_creds_cont = multi_cluster_instance._proxy(method='PUT', base_url=cluster_url, # noqa E501 # pylint: disable=line-too-long
+ path='ui-api/rgw/multisite/setup-rgw-credentials', # noqa E501 # pylint: disable=line-too-long
+ token=cluster_token) # noqa E501 # pylint: disable=line-too-long
+ logger.info("set credentials in selected cluster response: %s", set_creds_cont) # noqa E501 # pylint: disable=line-too-long # noqa E501 # pylint: disable=line-too-long
+ self.progress_done += 1
+ self.update_progress("Multisite replication setup completed",
+ 'complete')
+ break
except requests.RequestException as e:
logger.error("Error checking user in the second cluster: %s", e)
+ self.update_progress("Error checking user in the second cluster", 'fail', str(e))
logger.info("User %s not found yet, retrying in 5 seconds", username)
time.sleep(5)
+
+class RgwMultisite:
+ def migrate_to_multisite(self, realm_name: str, zonegroup_name: str, zone_name: str,
+ zonegroup_endpoints: str, zone_endpoints: str, access_key: str,
+ secret_key: str):
+ rgw_realm_create_cmd = ['realm', 'create', '--rgw-realm', realm_name, '--default']
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_realm_create_cmd, False)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to create realm',
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+
+ rgw_zonegroup_edit_cmd = ['zonegroup', 'rename', '--rgw-zonegroup', 'default',
+ '--zonegroup-new-name', zonegroup_name]
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_edit_cmd, False)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to rename zonegroup to {}'.format(zonegroup_name), # noqa E501 #pylint: disable=line-too-long
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+
+ rgw_zone_edit_cmd = ['zone', 'rename', '--rgw-zone',
+ 'default', '--zone-new-name', zone_name,
+ '--rgw-zonegroup', zonegroup_name]
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_edit_cmd, False)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to rename zone to {}'.format(zone_name), # noqa E501 #pylint: disable=line-too-long
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+
+ rgw_zonegroup_modify_cmd = ['zonegroup', 'modify',
+ '--rgw-realm', realm_name,
+ '--rgw-zonegroup', zonegroup_name]
+ if zonegroup_endpoints:
+ rgw_zonegroup_modify_cmd.append('--endpoints')
+ rgw_zonegroup_modify_cmd.append(zonegroup_endpoints)
+ rgw_zonegroup_modify_cmd.append('--master')
+ rgw_zonegroup_modify_cmd.append('--default')
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_modify_cmd)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to modify zonegroup {}'.format(zonegroup_name), # noqa E501 #pylint: disable=line-too-long
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+
+ rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-realm', realm_name,
+ '--rgw-zonegroup', zonegroup_name,
+ '--rgw-zone', zone_name]
+ if zone_endpoints:
+ rgw_zone_modify_cmd.append('--endpoints')
+ rgw_zone_modify_cmd.append(zone_endpoints)
+ rgw_zone_modify_cmd.append('--master')
+ rgw_zone_modify_cmd.append('--default')
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to modify zone',
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+
+ if access_key and secret_key:
+ rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-zone', zone_name,
+ '--access-key', access_key, '--secret', secret_key]
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to modify zone',
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ self.update_period()
+
def create_realm(self, realm_name: str, default: bool):
rgw_realm_create_cmd = ['realm', 'create']
cmd_create_realm_options = ['--rgw-realm', realm_name]
- if default:
+ if str_to_bool(default):
cmd_create_realm_options.append('--default')
rgw_realm_create_cmd += cmd_create_realm_options
try:
if realm_name != 'null':
cmd_create_zonegroup_options.append('--rgw-realm')
cmd_create_zonegroup_options.append(realm_name)
- if default != 'false':
+ if str_to_bool(default):
cmd_create_zonegroup_options.append('--default')
- if master != 'false':
+ if str_to_bool(master):
cmd_create_zonegroup_options.append('--master')
if endpoints:
cmd_create_zonegroup_options.append('--endpoints')
if zonegroup_name != 'null':
cmd_create_zone_options.append('--rgw-zonegroup')
cmd_create_zone_options.append(zonegroup_name)
- if default != 'false':
+ if str_to_bool(default):
cmd_create_zone_options.append('--default')
- if master != 'false':
+ if str_to_bool(master):
cmd_create_zone_options.append('--master')
if endpoints != 'null':
cmd_create_zone_options.append('--endpoints')
raise DashboardException(error, http_status_code=500, component='rgw')
def get_user_list(self, zoneName: str):
- all_users_info = []
user_list = []
rgw_user_list_cmd = ['user', 'list', '--rgw-zone', zoneName]
try:
user_list = out
except SubprocessError as error:
raise DashboardException(error, http_status_code=500, component='rgw')
-
- if len(user_list) > 0:
- for user_name in user_list:
- rgw_user_info_cmd = ['user', 'info', '--uid', user_name, '--rgw-zone', zoneName]
- try:
- exit_code, out, _ = mgr.send_rgwadmin_command(rgw_user_info_cmd)
- if exit_code > 0:
- raise DashboardException('Unable to get user info',
- http_status_code=500, component='rgw')
- all_users_info.append(out)
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
- return all_users_info
+ return user_list
def get_multisite_status(self):
is_multisite_configured = True
orch = OrchClient.instance()
service_name = f'{service_type}.{service_id}'
- logger.info("Getting initial service info for: %s", service_name)
info = orch.services.get(service_name)[0].to_dict()
last_refreshed = info['status']['last_refresh']
- logger.info("Reloading service: %s", service_name)
orch.services.reload(service_type, service_id)
- logger.info("Waiting for service refresh: %s", service_name)
wait_for_refresh(orch, service_name, last_refreshed)
- logger.info("Checking daemon status for: %s", service_name)
- daemon_status = wait_for_daemon_to_start(orch, service_name)
+ daemon_status = wait_for_daemon_to_start(service_name)
return daemon_status
def wait_for_refresh(orch, service_name, last_refreshed):
orch = OrchClient.instance()
- logger.info("Waiting for service %s to refresh", service_name)
-
while True:
updated_info = orch.services.get(service_name)[0].to_dict()
if updated_info['status']['last_refresh'] != last_refreshed:
- logger.info("Service %s refreshed", service_name)
break
-def wait_for_daemon_to_start(orch, service_name):
+def wait_for_daemon_to_start(service_name, timeout=30):
orch = OrchClient.instance()
start_time = time.time()
- logger.info("Waiting for daemon %s to start", service_name)
while True:
daemons = [d.to_dict() for d in orch.services.list_daemons(service_name=service_name)]
+ logger.info("Daemon list for service %s: %s", service_name, daemons)
+
+ if not daemons:
+ logger.info("No daemons found for service %s. Retrying...", service_name)
+ # Check if timeout has been reached
+ daemon_start_time = time.time()
+ if time.time() - daemon_start_time > timeout:
+ logger.error("Timeout reached while waiting for daemon list for service %s", service_name) # noqa E501 # pylint: disable=line-too-long
+ raise DashboardException(
+ code='daemon_list_timeout',
+ msg="Timeout reached while waiting for daemon list for service %s." % service_name # noqa E501 # pylint: disable=line-too-long
+ )
+ time.sleep(1)
+ continue # Retry getting daemon list
+
all_running = True
for daemon in daemons:
daemon_state = daemon['status_desc']
- logger.debug("Daemon %s state: %s", daemon['daemon_id'], daemon_state)
if daemon_state in ('unknown', 'error', 'stopped'):
logger.error("Failed to restart daemon %s for service %s. State is %s", daemon['daemon_id'], service_name, daemon_state) # noqa E501 # pylint: disable=line-too-long
raise DashboardException(
code='daemon_restart_failed',
- msg="Failed to restart the daemon %s. Daemon state is %s." % (service_name, daemon_state) # noqa E501 # pylint: disable=line-too-long
+ msg="Failed to restart the daemon %s. Daemon state is %s." % (daemon['daemon_id'], daemon_state) # noqa E501 # pylint: disable=line-too-long
)
- if daemon_state != 'running':
+
+ if daemon_state == 'starting':
+ all_running = False
+
+ elif daemon_state != 'running':
all_running = False
if all_running:
logger.info("All daemons for service %s are running", service_name)
return True
- if time.time() - start_time > 10:
+ if time.time() - start_time > timeout:
logger.error("Timeout reached while waiting for daemon %s to start", service_name)
raise DashboardException(
code='daemon_restart_timeout',
msg="Timeout reached while waiting for daemon %s to start." % service_name
)
- return False
+
+ time.sleep(1) # Adding a short delay before retrying
class RgwServiceManager:
+ def find_available_port(self, starting_port=80):
+ orch = OrchClient.instance()
+ daemons = [d.to_dict() for d in orch.services.list_daemons(daemon_type='rgw')]
+ used_ports = set()
+ for daemon in daemons:
+ ports = daemon.get('ports', [])
+ if ports:
+ used_ports.update(ports)
+ port = starting_port
+ while port in used_ports:
+ port += 1
+ return port
+
def restart_rgw_daemons_and_set_credentials(self):
# Restart RGW daemons and set credentials.
logger.info("Restarting RGW daemons and setting credentials")
zone_endpoints: Optional[str] = None) -> None:
placement_spec = placement.get('placement') if placement else None
self.rgw_zone_create(zone_name, realm_token, port, placement_spec, start_radosgw,
- zone_endpoints)
+ zone_endpoints, secondary_zone_period_retry_limit=5)