From: Naman Munet Date: Wed, 18 Mar 2026 08:18:06 +0000 (+0530) Subject: mgr/dashboard: sync policy created for a bucket in Object >> Multi-site >> Sync-polic... X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=refs%2Fheads%2Fsync-policy-replication;p=ceph.git mgr/dashboard: sync policy created for a bucket in Object >> Multi-site >> Sync-policy, is not reflecting under bucket's replication Fixes: https://tracker.ceph.com/issues/75581 Signed-off-by: Naman Munet --- diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py index ec1ed24d540f..a41c20c79f2b 100755 --- a/src/pybind/mgr/dashboard/controllers/rgw.py +++ b/src/pybind/mgr/dashboard/controllers/rgw.py @@ -508,17 +508,42 @@ class RgwBucket(RgwRESTController): return None rgw_client = RgwClient.instance(owner, daemon_name) - zonegroup_name = RgwClient.admin_instance(daemon_name=daemon_name).get_default_zonegroup() - policy_exists = multisite.policy_group_exists(_SYNC_GROUP_ID, zonegroup_name) - if replication and not policy_exists: - multisite.create_dashboard_admin_sync_group(zonegroup_name=zonegroup_name) + if replication: + zonegroup_name = RgwClient.admin_instance( + daemon_name=daemon_name).get_default_zonegroup() + policy_exists = multisite.policy_group_exists(_SYNC_GROUP_ID, zonegroup_name) + if not policy_exists: + multisite.create_dashboard_admin_sync_group(zonegroup_name=zonegroup_name) + return rgw_client.set_bucket_replication(bucket_name) - return rgw_client.set_bucket_replication(bucket_name, replication) + return rgw_client.delete_bucket_replication(bucket_name) def _get_replication(self, bucket_name: str, owner, daemon_name): + multisite = RgwMultisite() rgw_client = RgwClient.instance(owner, daemon_name) - return rgw_client.get_bucket_replication(bucket_name) + replication_config = rgw_client.get_bucket_replication(bucket_name) + + # Check if there's a valid S3 replication config with actual rules + if replication_config and replication_config.get('Rule'): + return replication_config + + # If no S3 replication config exists, check for sync policies + try: + sync_policy = multisite.get_sync_policy(bucket_name=bucket_name) + # Check if there are sync policy groups with 'enabled' status + if sync_policy and sync_policy.get('groups'): + for group in sync_policy['groups']: + group_status = group.get('status', '').lower() + if group_status == 'enabled': + # A sync policy exists but there is no S3 replication rule. + # The UI should direct the user to the multisite sync-policy + # page rather than showing this as a normal replication rule. + return {'syncPolicyOnly': True} + except Exception: + pass + + return {'Role': ''} @staticmethod def strip_tenant_from_bucket_name(bucket_name): diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html index 3c51747db8e1..76e80e548abe 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html @@ -194,7 +194,15 @@ Replication policy -
{{ selection.replication | json}}
+ + @if(hasSyncPolicyOnly) { + Sync policy configured. View on + Sync-policy page. + } @else { +
{{ selection.replication | json}}
+ } + = {}; replicationStatus = $localize`Disabled`; + hasSyncPolicyOnly = false; bucketRateLimit: RgwRateLimitConfig; constructor(private rgwBucketService: RgwBucketService, private cd: ChangeDetectorRef) {} @@ -78,8 +79,13 @@ export class RgwBucketDetailsComponent implements OnChanges { extraxtDetailsfromResponse() { this.aclPermissions = this.parseXmlAcl(this.selection.acl, this.selection.owner); - if (this.selection.replication?.['Rule']?.['Status']) { + this.hasSyncPolicyOnly = this.selection.replication?.['syncPolicyOnly'] === true; + if (this.hasSyncPolicyOnly) { + this.replicationStatus = $localize`Enabled`; + } else if (this.selection.replication?.['Rule']?.['Status']) { this.replicationStatus = this.selection.replication?.['Rule']?.['Status']; + } else { + this.replicationStatus = $localize`Disabled`; } this.rgwBucketService.getBucketRateLimit(this.selection.bid).subscribe((resp: any) => { if (resp && resp.bucket_ratelimit !== undefined) { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html index 4106254cae84..59eb8e374021 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html @@ -370,11 +370,13 @@
Replication - + Enable Enables replication for the objects in the bucket. diff --git a/src/pybind/mgr/dashboard/services/rgw_client.py b/src/pybind/mgr/dashboard/services/rgw_client.py index 5355a9ef1856..d790f254df2c 100755 --- a/src/pybind/mgr/dashboard/services/rgw_client.py +++ b/src/pybind/mgr/dashboard/services/rgw_client.py @@ -1114,7 +1114,7 @@ class RgwClient(RestClient): return retention_period_days, retention_period_years @RestClient.api_put('/{bucket_name}?replication') - def set_bucket_replication(self, bucket_name, replication: bool, request=None): + def set_bucket_replication(self, bucket_name, request=None): # pGenerate the minimum replication configuration # required for enabling the replication root = ET.Element('ReplicationConfiguration', @@ -1127,7 +1127,7 @@ class RgwClient(RestClient): rule_id.text = _SYNC_PIPE_ID status = ET.SubElement(rule, 'Status') - status.text = 'Enabled' if replication else 'Disabled' + status.text = 'Enabled' filter_elem = ET.SubElement(rule, 'Filter') prefix = ET.SubElement(filter_elem, 'Prefix') @@ -1158,6 +1158,18 @@ class RgwClient(RestClient): return None raise e + @RestClient.api_delete('/{bucket_name}?replication') + def delete_bucket_replication(self, bucket_name, request=None): + # pylint: disable=unused-argument + try: + request() + except RequestException as e: + if e.content: + content = json_str_to_object(e.content) + if content.get('Code') == 'ReplicationConfigurationNotFoundError': + return None + raise DashboardException(msg=str(e), component='rgw') + @RestClient.api_post('?Action=CreateTopic&Name={name}') def create_topic(self, request=None, name: str = '', daemon_name: str = '', @@ -2997,6 +3009,31 @@ class RgwMultisite: # period update --commit self.update_period() + @staticmethod + def _is_not_found_sync_policy_error(error: DashboardException) -> bool: + message = str(error).lower() + not_found_markers = ['not found', 'no such', 'does not exist'] + return any(marker in message for marker in not_found_markers) + + def remove_dashboard_admin_sync_group(self, zonegroup_name: str = ''): + if not self.policy_group_exists(_SYNC_GROUP_ID, zonegroup_name): + return + + try: + self.remove_sync_pipe(_SYNC_GROUP_ID, _SYNC_PIPE_ID) + except DashboardException as error: + if not self._is_not_found_sync_policy_error(error): + raise + + try: + self.remove_sync_flow(_SYNC_GROUP_ID, _SYNC_FLOW_ID, + SyncFlowTypes.symmetrical.value) + except DashboardException as error: + if not self._is_not_found_sync_policy_error(error): + raise + + self.remove_sync_policy_group(_SYNC_GROUP_ID, update_period=True) + def policy_group_exists(self, group_name: str, zonegroup_name: str): try: _ = self.get_sync_policy_group(