]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Add progress bar to rgw multisite automation wizard 58719/head
authorAashish Sharma <aasharma@li-e74156cc-2f67-11b2-a85c-e98659a63c5c.ibm.com>
Fri, 12 Jul 2024 10:50:15 +0000 (16:20 +0530)
committerAashish Sharma <aasharma@li-e74156cc-2f67-11b2-a85c-e98659a63c5c.ibm.com>
Wed, 25 Sep 2024 08:23:16 +0000 (13:53 +0530)
Fixes: https://tracker.ceph.com/issues/67829
Fixes: https://tracker.ceph.com/issues/67830
Signed-off-by: Aashish Sharma <aasharma@redhat.com>
19 files changed:
src/pybind/mgr/dashboard/controllers/auth.py
src/pybind/mgr/dashboard/controllers/rgw.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/upgrade/upgrade-progress/upgrade-progress.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/multisite-wizard-steps.enum.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/components.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/progress/progress.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/progress/progress.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/progress/progress.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/progress/progress.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/components/wizard/wizard.component.ts
src/pybind/mgr/dashboard/services/rgw_client.py
src/pybind/mgr/dashboard/services/service.py
src/pybind/mgr/rgw/module.py

index 16276af17e4c4eb55e1ca883f276993db3f40975..951486572d90e88c1cc37d22ae28bbf7557a0de1 100644 (file)
@@ -66,7 +66,7 @@ class Auth(RESTController, ControllerAuthMixin):
             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)
@@ -94,20 +94,26 @@ class Auth(RESTController, ControllerAuthMixin):
                     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:
@@ -121,7 +127,8 @@ class Auth(RESTController, ControllerAuthMixin):
                                     "name": fsid,
                                     "url": origin,
                                     "cluster_alias": "local-cluster",
-                                    "user": username
+                                    "user": username,
+                                    "token": token
                                 }
                             ]
                         }
index 398fa341bf278a2922fc503598e5570036d06bee..8667d469060f8ced34a76fafccb284e902edfb63 100755 (executable)
@@ -14,8 +14,9 @@ from ..rest_client import RequestException
 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, \
@@ -118,12 +119,15 @@ class RgwMultisiteStatus(RESTController):
     # 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')
@@ -131,7 +135,22 @@ class RgwMultisiteStatus(RESTController):
     # 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
 
 
index c683eee7d138ead598c592de06d387a0205482ed..c561594e23b1930e050ef3d450ab4bfaf24d8c86 100644 (file)
@@ -1,48 +1,15 @@
-<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"
@@ -65,7 +32,7 @@
               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>
index 9476368f3e78f0384fd4e2b671f424667728959f..e33c0dde432836d07bfbb84fdd1315e46a08c7c0 100644 (file)
        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'"
index edc973572a2ad140bed96ea5354fffa1f2545d98..fbe3110b978ea8179ad30aa10c40edc604857b93 100644 (file)
@@ -413,12 +413,16 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
     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;
       }
@@ -431,18 +435,16 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
     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
     };
   }
 
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/multisite-wizard-steps.enum.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/multisite-wizard-steps.enum.ts
new file mode 100644 (file)
index 0000000..299ffee
--- /dev/null
@@ -0,0 +1,19 @@
+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
+];
index 8956e3ef0b27fbf55a58da91aaa6a15efdf24ffe..9a34ce63bacbd9555207cba2c0b0ff59c995d396 100644 (file)
               #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"
@@ -39,8 +45,7 @@
                          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>
@@ -92,8 +97,6 @@
             </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>
index 623826b73639a177660130f4fc450a0566462b91..3d4b06528c181183550370a32db4b30efe793b33 100644 (file)
@@ -1,4 +1,5 @@
 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';
@@ -16,10 +17,15 @@ import _ from 'lodash';
 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',
@@ -31,17 +37,9 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
   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 = '';
@@ -53,6 +51,8 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
   pageURL: string;
   icons = Icons;
   rgwEndpoints: { value: any[]; options: any[]; messages: any };
+  executingTask: ExecutingTask;
+  setupCompleted = false;
 
   constructor(
     private wizardStepsService: WizardStepsService,
@@ -62,8 +62,8 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
     private multiClusterService: MultiClusterService,
     private rgwMultisiteService: RgwMultisiteService,
     public notificationService: NotificationService,
-    private router: Router,
     private route: ActivatedRoute,
+    private summaryService: SummaryService,
     private location: Location
   ) {
     super();
@@ -118,26 +118,29 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
     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;
     });
   }
 
@@ -161,6 +164,9 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
       }),
       cluster: new UntypedFormControl(null, {
         validators: [Validators.required]
+      }),
+      replicationZoneName: new UntypedFormControl('new_replicated_zone', {
+        validators: [Validators.required]
       })
     });
 
@@ -170,12 +176,18 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
   }
 
   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`;
     }
   }
 
@@ -187,51 +199,50 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
 
   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,
@@ -240,13 +251,15 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
           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 });
@@ -262,14 +275,6 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
     );
   }
 
-  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();
@@ -283,4 +288,8 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
     this.stepsToSkip[stepTitle.label] = true;
     this.onNextStep();
   }
+
+  closeModal(): void {
+    this.location.back();
+  }
 }
index 58f85fb61ff1b366f46965dbddb6ed5e3abe1f9f..3439562c8e223342e71078e970dbc608e9c51b84 100644 (file)
@@ -6,6 +6,7 @@ import { RouterModule, Routes } from '@angular/router';
 import {
   NgbNavModule,
   NgbPopoverModule,
+  NgbProgressbar,
   NgbTooltipModule,
   NgbTypeaheadModule
 } from '@ng-bootstrap/ng-bootstrap';
@@ -71,10 +72,12 @@ import {
   ModalModule,
   ProgressIndicatorModule
 } from 'carbon-components-angular';
+import { CephSharedModule } from '../shared/ceph-shared.module';
 
 @NgModule({
   imports: [
     CommonModule,
+    CephSharedModule,
     SharedModule,
     FormsModule,
     ReactiveFormsModule.withConfig({ callSetDisabledState: 'whenDisabledForLegacyCode' }),
@@ -93,7 +96,8 @@ import {
     ProgressIndicatorModule,
     ButtonModule,
     LoadingModule,
-    IconModule
+    IconModule,
+    NgbProgressbar
   ],
   exports: [
     RgwDaemonListComponent,
index 74aa3def4ebca760a6c803c8143d1dd77687a504..d57cd523a4dfe5a8fbba5572074f2a5e62946b6c 100644 (file)
@@ -79,7 +79,9 @@ export class RgwMultisiteService {
     zoneName: string,
     zoneEndpoints: string,
     username: string,
-    cluster?: string
+    cluster?: string,
+    replicationZoneName?: string,
+    clusterDetailsArray?: any
   ) {
     let params = new HttpParams()
       .set('realm_name', realmName)
@@ -93,6 +95,14 @@ export class RgwMultisiteService {
       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 });
   }
 
index 72bacdddf0c93309db405399e54663d23cb380ee..4c966f42bfbf45d77651fb5a136a7b639898adf6 100644 (file)
@@ -79,6 +79,7 @@ import { CardGroupComponent } from './card-group/card-group.component';
 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';
@@ -161,7 +162,8 @@ import InfoIcon from '@carbon/icons/es/information/16';
     CardGroupComponent,
     HelpTextComponent,
     FormAdvancedFieldsetComponent,
-    UpgradableComponent
+    UpgradableComponent,
+    ProgressComponent
   ],
   providers: [],
   exports: [
@@ -199,7 +201,8 @@ import InfoIcon from '@carbon/icons/es/information/16';
     CardGroupComponent,
     HelpTextComponent,
     FormAdvancedFieldsetComponent,
-    UpgradableComponent
+    UpgradableComponent,
+    ProgressComponent
   ]
 })
 export class ComponentsModule {
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/progress/progress.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/progress/progress.component.html
new file mode 100644 (file)
index 0000000..9d84457
--- /dev/null
@@ -0,0 +1,62 @@
+<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>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/progress/progress.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/progress/progress.component.scss
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/progress/progress.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/progress/progress.component.spec.ts
new file mode 100644 (file)
index 0000000..93f4598
--- /dev/null
@@ -0,0 +1,22 @@
+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();
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/progress/progress.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/progress/progress.component.ts
new file mode 100644 (file)
index 0000000..b39ebf6
--- /dev/null
@@ -0,0 +1,21 @@
+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;
+}
index 72c1e10676467c64bba8b5da45894bdd34605d1f..e3181a8ce821abdade3cc03fcafd98b3f6ab2085 100644 (file)
@@ -1,4 +1,4 @@
-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';
@@ -12,7 +12,7 @@ import { WizardStepsService } from '~/app/shared/services/wizard-steps.service';
   templateUrl: './wizard.component.html',
   styleUrls: ['./wizard.component.scss']
 })
-export class WizardComponent implements OnInit, OnDestroy {
+export class WizardComponent implements OnInit, OnDestroy, OnChanges {
   @Input()
   stepsTitle: Step[];
 
@@ -27,6 +27,16 @@ export class WizardComponent implements OnInit, OnDestroy {
   }
 
   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) => {
index d9925c2b2686b9db8c74da543cc2a0255ff960c2..2441b73b361bee8b7b04bd103b18425460710534 100755 (executable)
@@ -2,13 +2,13 @@
 # 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
@@ -1078,83 +1078,25 @@ class SyncFlowTypes(Enum):
     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.
@@ -1167,15 +1109,22 @@ class RgwMultisite:
     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
@@ -1189,57 +1138,83 @@ class RgwMultisite:
         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:
@@ -1247,41 +1222,62 @@ class RgwMultisite:
                     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}",
@@ -1292,11 +1288,13 @@ class RgwMultisite:
                                              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:
@@ -1311,26 +1309,107 @@ class RgwMultisite:
                 # 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:
@@ -1424,9 +1503,9 @@ class RgwMultisite:
         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')
@@ -1679,9 +1758,9 @@ class RgwMultisite:
         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')
@@ -1880,7 +1959,6 @@ class RgwMultisite:
             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:
@@ -1891,19 +1969,7 @@ class RgwMultisite:
             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
index 792604dcc591d6f022eff550696804397f809548..41fcc4c44461340c5994044f3539a0af03f06990 100644 (file)
@@ -28,68 +28,92 @@ def verify_service_restart(service_type: str, service_id: str):
     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")
index 156529f137940410051b140442ade6af788e9c23..d32ded6c52dc1e7e584c04b6008f21766dda4dde 100644 (file)
@@ -406,4 +406,4 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
                            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)