From: Aashish Sharma Date: Thu, 16 Apr 2026 05:51:22 +0000 (+0530) Subject: mgr/dashboard: Option to select archive option while Import Multi-site Token X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=a13f3e0858f4f8af3ca07e9d6e1c0d39bffa030b;p=ceph.git mgr/dashboard: Option to select archive option while Import Multi-site Token Fixes: https://tracker.ceph.com/issues/76054 Signed-off-by: Aashish Sharma --- diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py index f0d3d0dce495..35a8b3eebb23 100755 --- a/src/pybind/mgr/dashboard/controllers/rgw.py +++ b/src/pybind/mgr/dashboard/controllers/rgw.py @@ -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') diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.html index ee5633ec5fe1..da95ab0ef607 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.html @@ -51,7 +51,24 @@ i18n>The chosen zone name is already in use. - +
+
+
+ + + + Enable archival storage to keep all object versions and protect data from deletion or corruption. + +
+
+
Service Details
@@ -60,7 +77,7 @@ id="unmanaged" type="checkbox" formControlName="unmanaged"> - If set to true, the orchestrator will not start nor stop any daemon associated with this service. diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.ts index 92f1b68ce109..20bc2a911b83 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.ts @@ -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( () => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.html index af96cc9bd56a..48bb7db5ffe2 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.html @@ -289,6 +289,19 @@ i18n>This field is required.
+
+
+ + + + Enable archival storage to keep all object versions and protect data from deletion or corruption. + +
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.ts index f908df6750a7..ce11ead5b0f4 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-wizard/rgw-multisite-wizard.component.ts @@ -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 ) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.ts index 25bc6b7dc666..e28370d50a3a 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.ts @@ -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 }); } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-realm.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-realm.service.ts index 777ff061e147..b2e0f1afe2ad 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-realm.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-realm.service.ts @@ -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); } diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index 7e0b76dd2873..e302ddb2cdac 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -19282,6 +19282,8 @@ paths: type: string realm_token: type: string + tier_type: + type: string zone_name: type: string required: diff --git a/src/pybind/mgr/dashboard/services/ceph_service.py b/src/pybind/mgr/dashboard/services/ceph_service.py index ac55a92b2be7..0dc2c03421a4 100644 --- a/src/pybind/mgr/dashboard/services/ceph_service.py +++ b/src/pybind/mgr/dashboard/services/ceph_service.py @@ -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 diff --git a/src/pybind/mgr/dashboard/services/rgw_client.py b/src/pybind/mgr/dashboard/services/rgw_client.py index 881cb0c63723..bb80c5f01ffb 100755 --- a/src/pybind/mgr/dashboard/services/rgw_client.py +++ b/src/pybind/mgr/dashboard/services/rgw_client.py @@ -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', diff --git a/src/pybind/mgr/rgw/module.py b/src/pybind/mgr/rgw/module.py index f7749a26dd5a..8412f0e2d12e 100644 --- a/src/pybind/mgr/rgw/module.py +++ b/src/pybind/mgr/rgw/module.py @@ -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) diff --git a/src/python-common/ceph/rgw/rgwam_core.py b/src/python-common/ceph/rgw/rgwam_core.py index e11eb8e528a6..20537e49b7e6 100644 --- a/src/python-common/ceph/rgw/rgwam_core.py +++ b/src/python-common/ceph/rgw/rgwam_core.py @@ -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