]> 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 68513/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 15:03:46 +0000 (20:33 +0530)
Fixes: https://tracker.ceph.com/issues/76054
Signed-off-by: Aashish Sharma <aasharma@redhat.com>
(cherry picked from commit a13f3e0858f4f8af3ca07e9d6e1c0d39bffa030b)

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 ec1ed24d540f6d725aae043b750125e3a7392863..57c3086ec10d341f06f792f7981041c364fb5e25 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 6a3edfbf59a132721d073553cc438f74f9968222..17c5db97c72fcb57ef370da689912bf161038ba1 100644 (file)
@@ -101,7 +101,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)
     });
   }
 
@@ -110,6 +111,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':
@@ -130,7 +132,8 @@ export class RgwMultisiteImportComponent implements OnInit {
         values['realmToken'],
         values['zoneName'],
         values['rgw_frontend_port'],
-        placementSpec
+        placementSpec,
+        tier_type
       )
       .subscribe(
         () => {
index 80969213c367afdbb048d6b4836d6b327da01e84..3624a8bd9f9fe929775f10b63957b30508c699a2 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 43ced0bb43cd4b75d5badf83801ded897f90f34f..47e59b5c55bb756933d2a68d016e93af69f92ad3 100644 (file)
@@ -236,7 +236,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) {
@@ -318,6 +319,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) {
@@ -335,6 +337,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 dec615b3d053da5f04b8b4c1ef4aab5c8ca272a4..7fa760e6d6382a035a517bedbba9f84c0650f9b6 100755 (executable)
@@ -14567,6 +14567,8 @@ paths:
                   type: string
                 realm_token:
                   type: string
+                tier_type:
+                  type: string
                 zone_name:
                   type: string
               required:
index c7a070e5d48e2d03e36720e54e08b4082b9397e7..19e812eb7c9f2500732daba43dee7eca04be6e72 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 5355a9ef18562530c58871c469a826413bf7faac..cb3ce68ea9c41c512ee4a6a3713bc1c1db367b9a 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 c8b4cf690070fe28c953a7e7d9755d9caaec0257..a79740a7adec2e92b4649b7583a6f287dd38a7d3 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 9b19865bfc5012aff74b59605be5641df1bc9373..14b43ad40cc680015807f26f2b210923bc9c1cd4 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