]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Option to select archive option while Import Multi-site Token 68411/head
authorAashish Sharma <aashish@li-e9bf2ecc-2ad7-11b2-a85c-baf05c5182ab.ibm.com>
Thu, 16 Apr 2026 05:51:22 +0000 (11:21 +0530)
committerAashish Sharma <aashish@li-e9bf2ecc-2ad7-11b2-a85c-baf05c5182ab.ibm.com>
Tue, 21 Apr 2026 08:00:42 +0000 (13:30 +0530)
Fixes: https://tracker.ceph.com/issues/76054
Signed-off-by: Aashish Sharma <aasharma@redhat.com>
12 files changed:
src/pybind/mgr/dashboard/controllers/rgw.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.ts
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/shared/api/rgw-multisite.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-realm.service.ts
src/pybind/mgr/dashboard/openapi.yaml
src/pybind/mgr/dashboard/services/ceph_service.py
src/pybind/mgr/dashboard/services/rgw_client.py
src/pybind/mgr/rgw/module.py
src/python-common/ceph/rgw/rgwam_core.py

index f0d3d0dce4952276a93cd344a768224eb60fda19..35a8b3eebb239da846a1f0667a57d30c57104cd9 100755 (executable)
@@ -133,7 +133,7 @@ class RgwMultisiteStatus(RESTController):
                                     zonegroup_endpoints=None, zone_name=None, tier_type=None,
                                     zone_endpoints=None, username=None, cluster_fsid=None,
                                     replication_zone_name=None, cluster_details=None,
-                                    selectedRealmName=None):
+                                    selectedRealmName=None, secondary_tier_type=None):
         multisite_instance = RgwMultisiteAutomation()
         result = multisite_instance.setup_multisite_replication(realm_name, zonegroup_name,
                                                                 zonegroup_endpoints, zone_name,
@@ -141,7 +141,8 @@ class RgwMultisiteStatus(RESTController):
                                                                 username, cluster_fsid,
                                                                 replication_zone_name,
                                                                 cluster_details,
-                                                                selectedRealmName)
+                                                                selectedRealmName,
+                                                                secondary_tier_type)
         return result
 
     @RESTController.Collection(method='PUT', path='/setup-rgw-credentials')
@@ -1385,9 +1386,10 @@ class RgwRealm(RESTController):
     @UpdatePermission
     @allow_empty_body
     # pylint: disable=W0613
-    def import_realm_token(self, realm_token, zone_name, port, placement_spec=None):
+    def import_realm_token(self, realm_token, zone_name, port, placement_spec=None, tier_type=None):
         try:
-            result = CephService.import_realm_token(realm_token, zone_name, port, placement_spec)
+            result = CephService.import_realm_token(realm_token, zone_name, port, placement_spec,
+                                                    tier_type)
             return result
         except NoRgwDaemonsException as e:
             raise DashboardException(e, http_status_code=404, component='rgw')
index ee5633ec5fe196bca1707aeb93d710e68baae74e..da95ab0ef607d0c958e6bfa5bed1d66c05f46eaa 100644 (file)
                 i18n>The chosen zone name is already in use.</span>
         </div>
       </div>
-
+      <div class="form-group row">
+        <div class="cd-col-form-offset">
+          <div class="custom-control custom-checkbox">
+            <input class="custom-control-input"
+                   id="archive_zone"
+                   type="checkbox"
+                   formControlName="archive_zone">
+            <label class="custom-control-label cds-ml-2"
+                   for="archive_zone"
+                   i18n>Archive</label>
+            <cd-helper i18n>
+            <span
+              >Enable archival storage to keep all object versions and protect data from deletion or corruption.</span
+            >
+          </cd-helper>
+          </div>
+        </div>
+      </div>
       <legend i18n>Service Details</legend>
       <div class="form-group row">
         <div class="cd-col-form-offset">
@@ -60,7 +77,7 @@
                    id="unmanaged"
                    type="checkbox"
                    formControlName="unmanaged">
-            <label class="custom-control-label"
+            <label class="custom-control-label cds-ml-2"
                    for="unmanaged"
                    i18n>Unmanaged</label>
             <cd-helper i18n>If set to true, the orchestrator will not start nor stop any daemon associated with this service.
index 92f1b68ce10947ce6c638c98a019238321d6c096..20bc2a911b83a285608622f59e1da4cef23203e2 100644 (file)
@@ -102,7 +102,8 @@ export class RgwMultisiteImportComponent implements OnInit {
       ]),
       hosts: new FormControl([]),
       count: new FormControl(null, [CdValidators.number(false)]),
-      unmanaged: new FormControl(false)
+      unmanaged: new FormControl(false),
+      archive_zone: new FormControl(false)
     });
   }
 
@@ -111,6 +112,7 @@ export class RgwMultisiteImportComponent implements OnInit {
     const placementSpec: object = {
       placement: {}
     };
+    const tier_type: string = values['archive_zone'] ? 'archive' : '';
     if (!values['unmanaged']) {
       switch (values['placement']) {
         case 'hosts':
@@ -131,7 +133,8 @@ export class RgwMultisiteImportComponent implements OnInit {
         values['realmToken'],
         values['zoneName'],
         values['rgw_frontend_port'],
-        placementSpec
+        placementSpec,
+        tier_type
       )
       .subscribe(
         () => {
index af96cc9bd56a54e69146cba9e6884a0d2dc4374e..48bb7db5ffe2f84a8b9d2b990c2a6a28287412a3 100644 (file)
                         i18n>This field is required.</span>
                 </div>
               </div>
+              <div class="form-group row">
+                <div class="cd-col-form-offset">
+                  <input type="checkbox"
+                         formControlName="secondary_archive_zone"
+                         id="secondary_archive_zone">
+                  <label for="secondary_archive_zone"
+                         class="custom-control-label cds-ml-3"
+                         i18n>Archive</label>
+                  <cd-help-text>
+                    <span i18n>Enable archival storage to keep all object versions and protect data from deletion or corruption.</span>
+                  </cd-help-text>
+                </div>
+              </div>
             </ng-template>
             <div *ngSwitchCase="3"
                  class="ms-5">
index f908df6750a78e77f44240fec12cf7bacc54990a..ce11ead5b0f471fc1800e3c4f7844bf56a0ff5fb 100644 (file)
@@ -237,7 +237,8 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
         validators: [Validators.required]
       }),
       configType: new UntypedFormControl(ConfigType.NewRealm, {}),
-      selectedRealm: new UntypedFormControl(null, {})
+      selectedRealm: new UntypedFormControl(null, {}),
+      secondary_archive_zone: new UntypedFormControl(false, {})
     });
 
     if (!this.isMultiClusterConfigured) {
@@ -319,6 +320,7 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
       } else {
         const cluster = values['cluster'];
         const replicationZoneName = values['replicationZoneName'];
+        const secondaryTierType = values['secondary_archive_zone'] ? 'archive' : '';
         let selectedRealmName = '';
 
         if (this.multisiteSetupForm.get('configType').value === ConfigType.ExistingRealm) {
@@ -336,6 +338,7 @@ export class RgwMultisiteWizardComponent extends BaseModal implements OnInit {
             username,
             cluster,
             replicationZoneName,
+            secondaryTierType,
             this.clusterDetailsArray,
             selectedRealmName
           )
index 25bc6b7dc666b120f03088a480ad8e760546bd55..e28370d50a3a56293c4d6170d2f6134c8c1f5da2 100644 (file)
@@ -96,6 +96,7 @@ export class RgwMultisiteService {
     username: string,
     cluster?: string,
     replicationZoneName?: string,
+    secondaryTierType?: string,
     clusterDetailsArray?: any,
     selectedRealmName?: string
   ) {
@@ -124,6 +125,10 @@ export class RgwMultisiteService {
       params = params.set('selectedRealmName', selectedRealmName);
     }
 
+    if (secondaryTierType) {
+      params = params.set('secondary_tier_type', secondaryTierType);
+    }
+
     return this.http.post(`${this.uiUrl}/multisite-replications`, null, { params: params });
   }
 
index 777ff061e147f6c8eab4ffb03a1fb561f90d1244..b2e0f1afe2add6f80d166d79917afd40041d4dfb 100644 (file)
@@ -72,12 +72,19 @@ export class RgwRealmService {
     };
   }
 
-  importRealmToken(realm_token: string, zone_name: string, port: number, placementSpec: object) {
+  importRealmToken(
+    realm_token: string,
+    zone_name: string,
+    port: number,
+    placementSpec: object,
+    tier_type: string
+  ) {
     let requestBody = {
       realm_token: realm_token,
       zone_name: zone_name,
       port: port,
-      placement_spec: placementSpec
+      placement_spec: placementSpec,
+      tier_type: tier_type
     };
     return this.http.post(`${this.url}/import_realm_token`, requestBody);
   }
index 7e0b76dd2873cc141b97ad556fb6fb9e0c2b1923..e302ddb2cdac4e7ce56b77b05a4ccbce453991b5 100644 (file)
@@ -19282,6 +19282,8 @@ paths:
                   type: string
                 realm_token:
                   type: string
+                tier_type:
+                  type: string
                 zone_name:
                   type: string
               required:
index ac55a92b2be7dcf922d63c3de7f88fb1b58d06dd..0dc2c03421a45f0f0c5ba0816015cc48f1bd3cc8 100644 (file)
@@ -289,10 +289,10 @@ class CephService(object):
         return tokens_info
 
     @classmethod
-    def import_realm_token(cls, realm_token, zone_name, port, placement_spec):
+    def import_realm_token(cls, realm_token, zone_name, port, placement_spec, tier_type=None):
         tokens_info = mgr.remote('rgw', 'import_realm_token', zone_name=zone_name,
                                  realm_token=realm_token, port=port, placement=placement_spec,
-                                 start_radosgw=True)
+                                 tier_type=tier_type, start_radosgw=True)
         return tokens_info
 
     @classmethod
index 881cb0c63723d9236b369c43acb300f3811c136d..bb80c5f01ffb155521beb468f99a46cf94cefe07 100755 (executable)
@@ -1336,7 +1336,8 @@ class RgwMultisiteAutomation:
                                     username: str, cluster_fsid: Optional[str] = None,
                                     replication_zone_name: Optional[str] = None,
                                     cluster_details: Optional[str] = None,
-                                    selectedRealmName: Optional[str] = None):
+                                    selectedRealmName: Optional[str] = None,
+                                    secondary_tier_type: Optional[str] = None):
 
         logger.info("Starting multisite replication setup")
 
@@ -1361,7 +1362,7 @@ class RgwMultisiteAutomation:
 
         return self.export_and_import_realm(
             realm_name, zonegroup_name, cluster_fsid, replication_zone_name,
-            cluster_details_dict, selectedRealmName, username
+            cluster_details_dict, selectedRealmName, username, secondary_tier_type
         )
 
     def get_updated_endpoints(self, endpoints: str, orch: OrchClient) -> list[str]:
@@ -1433,7 +1434,7 @@ class RgwMultisiteAutomation:
     def export_and_import_realm(self, realm: str, zg: str,
                                 fsid: Optional[str], rep_zone: Optional[str],
                                 details_dict: dict, selectedRealm: Optional[str],
-                                username: str):
+                                username: str, secondary_tier_type: Optional[str] = None):
         try:
             realm_token_info = CephService.get_realm_tokens()
             if fsid and realm_token_info and rep_zone and details_dict:
@@ -1443,7 +1444,8 @@ class RgwMultisiteAutomation:
                             configuration, and establishing the target zone"
                 )
                 self.import_realm_token_to_cluster(fsid, realm, zg, realm_token_info, username,
-                                                   rep_zone, details_dict, selectedRealm)
+                                                   rep_zone, details_dict, selectedRealm,
+                                                   secondary_tier_type)
             else:
                 self.update_progress("Realm Export Token fetched successfully", 'complete')
             logger.info("Multisite replication setup completed")
@@ -1456,7 +1458,8 @@ class RgwMultisiteAutomation:
 
     def import_realm_token_to_cluster(self, cluster_fsid, realm_name, zonegroup_name,
                                       realm_token_info, username, replication_zone_name,
-                                      cluster_details, selectedRealmName):
+                                      cluster_details, selectedRealmName,
+                                      secondary_tier_type=None):
         try:
             if selectedRealmName:
                 rgw_service_manager = RgwServiceManager()
@@ -1473,7 +1476,7 @@ class RgwMultisiteAutomation:
 
             token_import_response = self._import_realm_token(
                 cluster_url, cluster_token, realm_export_token,
-                replication_zone_name)
+                replication_zone_name, secondary_tier_type)
 
             self.progress_done += 1
             self.update_progress(
@@ -1582,7 +1585,8 @@ class RgwMultisiteAutomation:
                                                     token=cluster_token)
         logger.info("setting config response: %s", config_info)
 
-    def _import_realm_token(self, cluster_url, cluster_token, realm_token, zone_name):
+    def _import_realm_token(self, cluster_url, cluster_token, realm_token, zone_name,
+                            secondary_tier_type=None):
         multi_cluster_instance = MultiCluster()
         # pylint: disable=protected-access
         available_port = multi_cluster_instance._proxy(
@@ -1594,6 +1598,8 @@ class RgwMultisiteAutomation:
             'port': available_port,
             'placement_spec': {"placement": {}}
         }
+        if secondary_tier_type:
+            payload['tier_type'] = secondary_tier_type
         # pylint: disable=protected-access
         token_import_response = multi_cluster_instance._proxy(
             method='POST', base_url=cluster_url, path='api/rgw/realm/import_realm_token',
index f7749a26dd5a0af073717b47e28795b9efdb8890..8412f0e2d12e0fb094ba8975e296db533fc26e4c 100644 (file)
@@ -449,8 +449,14 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
         """Bootstrap new rgw zone that syncs with zone on another cluster in the same realm"""
 
         try:
-            created_zones = self.rgw_zone_create(zone_name, realm_token, port, placement,
-                                                 start_radosgw, zone_endpoints, self.secondary_zone_period_retry_limit, inbuf)
+            created_zones = self.rgw_zone_create(zone_name=zone_name,
+                                                 realm_token=realm_token,
+                                                 port=port,
+                                                 placement=placement,
+                                                 start_radosgw=start_radosgw,
+                                                 zone_endpoints=zone_endpoints,
+                                                 secondary_zone_period_retry_limit=self.secondary_zone_period_retry_limit,
+                                                 inbuf=inbuf)
             return HandleCommandResult(retval=0, stdout=f"Zones {', '.join(created_zones)} created successfully")
         except RGWAMException as e:
             return HandleCommandResult(retval=e.retcode, stderr=f'Failed to create zone: {str(e)}')
@@ -460,6 +466,7 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
                         realm_token: Optional[str] = None,
                         port: Optional[int] = None,
                         placement: Optional[Union[str, Dict[str, Any]]] = None,
+                        tier_type: Optional[str] = None,
                         start_radosgw: Optional[bool] = True,
                         zone_endpoints: Optional[str] = None,
                         secondary_zone_period_retry_limit: Optional[int] = None,
@@ -489,7 +496,9 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
         try:
             created_zones = []
             for rgw_spec in rgw_specs:
-                RGWAM(self.env).zone_create(rgw_spec, start_radosgw, secondary_zone_period_retry_limit)
+                RGWAM(self.env).zone_create(rgw_spec, start_radosgw,
+                                            secondary_zone_period_retry_limit,
+                                            tier_type)
                 if rgw_spec.rgw_zone is not None:
                     created_zones.append(rgw_spec.rgw_zone)
                     return created_zones
@@ -530,8 +539,9 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
                            realm_token: Optional[str] = None,
                            port: Optional[int] = None,
                            placement: Optional[dict] = None,
+                           tier_type: Optional[str] = None,
                            start_radosgw: Optional[bool] = True,
                            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,
+        self.rgw_zone_create(zone_name, realm_token, port, placement_spec, tier_type, start_radosgw,
                              zone_endpoints, secondary_zone_period_retry_limit=5)
index e11eb8e528a6ed236a407d4f7d30c87e030718a1..20537e49b7e61d22d14beddf2d6167d57332ea31 100644 (file)
@@ -289,7 +289,8 @@ class ZoneOp:
 
     def create(self, realm: EntityKey, zonegroup: EntityKey, zone: EntityKey = None,
                endpoints=None, is_master=True,
-               access_key=None, secret=None):
+               access_key=None, secret=None,
+               tier_type=None, master_zone_name=None):
 
         ze = ZoneEnv(self.env, realm=realm, zg=zonegroup).init_zone(zone, gen=True)
 
@@ -300,6 +301,11 @@ class ZoneOp:
         opt_arg(params, '--access-key', access_key)
         opt_arg(params, '--secret', secret)
         opt_arg(params, '--endpoints', endpoints)
+        opt_arg(params, '--tier-type', tier_type)
+
+        if tier_type == 'archive':
+            opt_arg(params, '--sync-from-all', 'false')
+            opt_arg(params, '--sync-from', master_zone_name)
 
         return RGWAdminJSONCmd(ze).run(params)
 
@@ -486,14 +492,17 @@ class RGWAM:
             raise RGWAMException('failed to create zonegroup', e)
 
     def create_zone(self, realm, zg, zone_name, zone_is_master, access_key=None,
-                    secret=None, endpoints=None):
+                    secret=None, endpoints=None, tier_type=None,
+                    master_zone_name=None):
         try:
             zone_info = self.zone_op().create(realm, zg,
                                               EntityName(zone_name),
                                               endpoints,
                                               is_master=zone_is_master,
                                               access_key=access_key,
-                                              secret=secret)
+                                              secret=secret,
+                                              tier_type=tier_type,
+                                              master_zone_name=master_zone_name)
 
             zone = EntityKey(zone_info['name'], zone_info['id'])
             logging.info(f'Created zone name={zone.name} id={zone.id}')
@@ -850,7 +859,8 @@ class RGWAM:
                     return zone.get('name')
         return None
 
-    def zone_create(self, rgw_spec, start_radosgw, secondary_zone_period_retry_limit=5):
+    def zone_create(self, rgw_spec, start_radosgw, secondary_zone_period_retry_limit=5,
+                    tier_type=None):
 
         if not rgw_spec.rgw_realm_token:
             raise RGWAMException('missing realm token')
@@ -881,12 +891,15 @@ class RGWAM:
         logging.info('Period: ' + period.id)
 
         zonegroup = period.get_master_zonegroup()
+        master_zone = self.period_op().get_master_zone(realm, zonegroup)
+        master_zone_name = master_zone['name'] if master_zone else None
         if not zonegroup:
             raise RGWAMException(f'Cannot find master zonegroup of realm {realm_name}')
 
         zone = self.create_zone(realm, zonegroup, rgw_spec.rgw_zone,
                                 False,  # secondary zone
-                                access_key, secret, endpoints=rgw_spec.zone_endpoints)
+                                access_key, secret, endpoints=rgw_spec.zone_endpoints,
+                                tier_type=tier_type, master_zone_name=master_zone_name)
 
         # Adding a retry limit for period update in case the default 10s timeout is not sufficient
         rgw_limit = 0