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=e1b24eac6d0c36b5d50e1b76d4aa57f0858690a0;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..39a71ff6ca9f 100755 --- a/src/pybind/mgr/dashboard/controllers/rgw.py +++ b/src/pybind/mgr/dashboard/controllers/rgw.py @@ -508,17 +508,41 @@ 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 + replication_rules_configured = bool(replication_config and replication_config.get('Rule')) + + # Check for sync policies + sync_policy_active = False + try: + sync_policy = multisite.get_sync_policy(bucket_name=bucket_name) + # Check if there are sync policy groups with 'enabled' status + groups = sync_policy.get('groups', []) + sync_policy_active = any(g.get('status', '').lower() == 'enabled' for g in groups) + except (RequestException, DashboardException): + # Silently ignore errors from sync policy check + pass + + return { + 'sync_policy_active': sync_policy_active, + 'replication_rules_configured': replication_rules_configured, + 'policy': replication_config if replication_config else {} + } @staticmethod def strip_tenant_from_bucket_name(bucket_name): diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-bucket-replication.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-bucket-replication.ts new file mode 100644 index 000000000000..e610c7a4e53d --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-bucket-replication.ts @@ -0,0 +1,5 @@ +export interface RgwBucketReplication { + sync_policy_active: boolean; + replication_rules_configured: boolean; + policy: any; +} 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..817088ad4b84 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 { +
{{ replicationData?.policy | json}}
+ } + = {}; replicationStatus = $localize`Disabled`; + hasSyncPolicyOnly = false; + replicationData: RgwBucketReplication; bucketRateLimit: RgwRateLimitConfig; constructor(private rgwBucketService: RgwBucketService, private cd: ChangeDetectorRef) {} @@ -78,8 +81,26 @@ export class RgwBucketDetailsComponent implements OnChanges { extraxtDetailsfromResponse() { this.aclPermissions = this.parseXmlAcl(this.selection.acl, this.selection.owner); - if (this.selection.replication?.['Rule']?.['Status']) { - this.replicationStatus = this.selection.replication?.['Rule']?.['Status']; + + this.replicationData = { + sync_policy_active: this.selection.replication?.sync_policy_active === true, + replication_rules_configured: + this.selection.replication?.replication_rules_configured === true, + policy: this.selection.replication?.policy || {} + }; + + this.hasSyncPolicyOnly = + this.replicationData.sync_policy_active && !this.replicationData.replication_rules_configured; + + if (this.replicationData.sync_policy_active) { + this.replicationStatus = $localize`Enabled`; + } else if ( + this.replicationData.replication_rules_configured && + this.replicationData.policy['Rule']?.['Status'] + ) { + this.replicationStatus = this.replicationData.policy['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/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts index 512f0462c190..c9b7eaa87957 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts @@ -327,11 +327,12 @@ export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewC } if (value['replication']) { const replicationConfig = value['replication']; - if (replicationConfig?.['Rule']?.['Status'] === 'Enabled') { - this.bucketForm.get('replication').setValue(true); - } else { - this.bucketForm.get('replication').setValue(false); - } + // Only consider S3 replication rules, not sync policies (managed on sync-policy page) + const hasReplicationRules = + replicationConfig?.['replication_rules_configured'] === true && + replicationConfig?.['policy']?.['Rule']?.['Status'] === 'Enabled'; + + this.bucketForm.get('replication').setValue(hasReplicationRules); } this.filterAclPermissions(); } diff --git a/src/pybind/mgr/dashboard/services/rgw_client.py b/src/pybind/mgr/dashboard/services/rgw_client.py index 5355a9ef1856..3482fe935b39 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,19 @@ 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') + return None + @RestClient.api_post('?Action=CreateTopic&Name={name}') def create_topic(self, request=None, name: str = '', daemon_name: str = '', @@ -2997,6 +3010,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(