]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: multisite sync-policy page should include daemon selection 68734/head
authorNaman Munet <naman.munet@ibm.com>
Mon, 4 May 2026 12:57:53 +0000 (18:27 +0530)
committerNaman Munet <naman.munet@ibm.com>
Wed, 3 Jun 2026 03:58:16 +0000 (09:28 +0530)
Fixes: https://tracker.ceph.com/issues/71522
Changes includes:
- Added daemon selection support to all sync policy endpoints
- Enhanced backend with daemon context awareness
- Fetch only the sync policies from the specified daemon

Signed-off-by: Naman Munet <naman.munet@ibm.com>
src/pybind/mgr/dashboard/controllers/rgw.py
src/pybind/mgr/dashboard/frontend/src/app/core/context/context.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.ts
src/pybind/mgr/dashboard/openapi.yaml
src/pybind/mgr/dashboard/services/rgw_client.py
src/pybind/mgr/dashboard/tests/test_rgw_client.py

index 63a419912aa4b761a0c441843bd877528f9d906a..9e06597f1713751606c7d2654c39f365c4507ba4 100755 (executable)
@@ -185,60 +185,76 @@ class RgwMultisiteController(RESTController):
     @Endpoint(path='/sync-policy')
     @EndpointDoc("Get the sync policy")
     @ReadPermission
-    def get_sync_policy(self, bucket_name='', zonegroup_name='', all_policy=None):
+    def get_sync_policy(self, bucket_name='', zonegroup_name='', all_policy=None, daemon_name=''):
         multisite_instance = RgwMultisite()
         all_policy = str_to_bool(all_policy)
         if all_policy:
             sync_policy_list = []
-            buckets = json.loads(RgwBucket().list(stats=False))
-            zonegroups_info = RgwMultisite().get_all_zonegroups_info()
-            default_zonegroup = ''
-            if 'zonegroups' in zonegroups_info and 'default_zonegroup' in zonegroups_info:
-                default_zonegroup = next(
-                    (zonegroup['name'] for zonegroup in zonegroups_info['zonegroups']
-                        if 'id' in zonegroup and 'name' in zonegroup
-                        and zonegroup['id'] == zonegroups_info['default_zonegroup']),
-                    ''
-                )
+            # Filter buckets by daemon_name to only get buckets from that daemon's zone/zonegroup
+            buckets = json.loads(RgwBucket().list(
+                stats=False, daemon_name=daemon_name if daemon_name else None))
+            # Get zonegroup from daemon if daemon_name is provided, otherwise use default
+            target_zonegroup = ''
+            if daemon_name:
+                target_zonegroup = (
+                    multisite_instance.get_zonegroup_from_daemon(daemon_name) or '')
+            if not target_zonegroup:
+                zonegroups_info = RgwMultisite().get_all_zonegroups_info()
+                if 'zonegroups' in zonegroups_info and 'default_zonegroup' in zonegroups_info:
+                    target_zonegroup = next(
+                        (zonegroup['name'] for zonegroup in zonegroups_info['zonegroups']
+                            if 'id' in zonegroup and 'name' in zonegroup
+                            and zonegroup['id'] == zonegroups_info['default_zonegroup']),
+                        ''
+                    )
             for bucket in buckets:
-                sync_policy = multisite_instance.get_sync_policy(bucket, zonegroup_name)
-                for policy in sync_policy['groups']:
-                    policy['bucketName'] = bucket
-                    sync_policy_list.append(policy)
-            other_sync_policy = multisite_instance.get_sync_policy(bucket_name, zonegroup_name)
+                try:
+                    sync_policy = multisite_instance.get_sync_policy(
+                        bucket, zonegroup_name, daemon_name)
+                    for policy in sync_policy['groups']:
+                        policy['bucketName'] = bucket
+                        sync_policy_list.append(policy)
+                except DashboardException:
+                    # Skip buckets that don't have sync policies or aren't accessible
+                    logger.debug("Skipping bucket %s - no sync policy or not accessible", bucket)
+                    continue
+            other_sync_policy = multisite_instance.get_sync_policy(
+                bucket_name, zonegroup_name, daemon_name)
             for policy in other_sync_policy['groups']:
-                policy['zonegroup'] = default_zonegroup
+                policy['zonegroup'] = target_zonegroup
                 sync_policy_list.append(policy)
             return sync_policy_list
-        return multisite_instance.get_sync_policy(bucket_name, zonegroup_name)
+        return multisite_instance.get_sync_policy(bucket_name, zonegroup_name, daemon_name)
 
     @Endpoint(path='/sync-policy-group')
     @EndpointDoc("Get the sync policy group")
     @ReadPermission
-    def get_sync_policy_group(self, group_id: str, bucket_name=''):
+    def get_sync_policy_group(self, group_id: str, bucket_name='', daemon_name=''):
         multisite_instance = RgwMultisite()
-        return multisite_instance.get_sync_policy_group(group_id, bucket_name)
+        return multisite_instance.get_sync_policy_group(group_id, bucket_name, '', daemon_name)
 
     @Endpoint(method='POST', path='/sync-policy-group')
     @EndpointDoc("Create the sync policy group")
     @CreatePermission
-    def create_sync_policy_group(self, group_id: str, status: str, bucket_name=''):
+    def create_sync_policy_group(self, group_id: str, status: str, bucket_name='', daemon_name=''):
         multisite_instance = RgwMultisite()
-        return multisite_instance.create_sync_policy_group(group_id, status, bucket_name, True)
+        return multisite_instance.create_sync_policy_group(
+            group_id, status, bucket_name, True, daemon_name)
 
     @Endpoint(method='PUT', path='/sync-policy-group')
     @EndpointDoc("Update the sync policy group")
     @UpdatePermission
-    def update_sync_policy_group(self, group_id: str, status: str, bucket_name=''):
+    def update_sync_policy_group(self, group_id: str, status: str, bucket_name='', daemon_name=''):
         multisite_instance = RgwMultisite()
-        return multisite_instance.update_sync_policy_group(group_id, status, bucket_name, True)
+        return multisite_instance.update_sync_policy_group(
+            group_id, status, bucket_name, True, daemon_name)
 
     @Endpoint(method='DELETE', path='/sync-policy-group')
     @EndpointDoc("Remove the sync policy group")
     @DeletePermission
-    def remove_sync_policy_group(self, group_id: str, bucket_name=''):
+    def remove_sync_policy_group(self, group_id: str, bucket_name='', daemon_name=''):
         multisite_instance = RgwMultisite()
-        return multisite_instance.remove_sync_policy_group(group_id, bucket_name, True)
+        return multisite_instance.remove_sync_policy_group(group_id, bucket_name, True, daemon_name)
 
     @Endpoint(method='PUT', path='/sync-flow')
     @EndpointDoc("Create or update the sync flow")
@@ -247,20 +263,22 @@ class RgwMultisiteController(RESTController):
                          source_zone: Optional[str] = None,
                          destination_zone: Optional[str] = None,
                          zones: Optional[Dict[str, List]] = None,
-                         bucket_name=''):
+                         bucket_name='', daemon_name=''):
         multisite_instance = RgwMultisite()
-        return multisite_instance.create_sync_flow(group_id, flow_id, flow_type, zones,
-                                                   bucket_name, source_zone, destination_zone, True)
+        return multisite_instance.create_sync_flow(
+            group_id, flow_id, flow_type, zones, bucket_name, source_zone,
+            destination_zone, True, daemon_name)
 
     @Endpoint(method='DELETE', path='/sync-flow')
     @EndpointDoc("Remove the sync flow")
     @DeletePermission
     def remove_sync_flow(self, flow_id: str, flow_type: str, group_id: str,
                          source_zone='', destination_zone='', zones: Optional[List[str]] = None,
-                         bucket_name=''):
+                         bucket_name='', daemon_name=''):
         multisite_instance = RgwMultisite()
-        return multisite_instance.remove_sync_flow(group_id, flow_id, flow_type, source_zone,
-                                                   destination_zone, zones, bucket_name, True)
+        return multisite_instance.remove_sync_flow(
+            group_id, flow_id, flow_type, source_zone, destination_zone,
+            zones, bucket_name, True, daemon_name)
 
     @Endpoint(method='PUT', path='/sync-pipe')
     @EndpointDoc("Create or update the sync pipe")
@@ -270,12 +288,12 @@ class RgwMultisiteController(RESTController):
                          destination_zones: Dict[str, Any],
                          source_bucket: str = '',
                          destination_bucket: str = '', bucket_name: str = '',
-                         user: str = '', mode: str = ''):
+                         user: str = '', mode: str = '', daemon_name=''):
         multisite_instance = RgwMultisite()
         return multisite_instance.create_sync_pipe(group_id, pipe_id, source_zones,
                                                    destination_zones, source_bucket,
                                                    destination_bucket, bucket_name, True,
-                                                   user, mode)
+                                                   user, mode, daemon_name)
 
     @Endpoint(method='DELETE', path='/sync-pipe')
     @EndpointDoc("Remove the sync pipe")
@@ -283,10 +301,11 @@ class RgwMultisiteController(RESTController):
     def remove_sync_pipe(self, group_id: str, pipe_id: str,
                          source_zones: Optional[List[str]] = None,
                          destination_zones: Optional[List[str]] = None,
-                         bucket_name: str = ''):
+                         bucket_name: str = '', daemon_name=''):
         multisite_instance = RgwMultisite()
-        return multisite_instance.remove_sync_pipe(group_id, pipe_id, source_zones,
-                                                   destination_zones, bucket_name, True)
+        return multisite_instance.remove_sync_pipe(
+            group_id, pipe_id, source_zones, destination_zones,
+            bucket_name, True, daemon_name)
 
 
 @APIRouter('/rgw/daemon', Scope.RGW)
index c5a45686226c2a41b0b0cdf06b344b9fb4483d7d..e652a923d7c8211c2409519963e84189634b65c0 100644 (file)
@@ -28,13 +28,15 @@ export class ContextComponent implements OnInit, OnDestroy {
   private rgwRoleUrlPrefix = '/rgw/roles';
   private rgwBuckerUrlPrefix = '/rgw/bucket';
   private rgwAccountsUrlPrefix = '/rgw/accounts';
+  private rgwMultisiteSyncPolicyPrefix = '/rgw/multisite/sync-policy';
   permissions: Permissions;
   featureToggleMap$: FeatureTogglesMap$;
   isRgwRoute =
     document.location.href.includes(this.rgwUserUrlPrefix) ||
     document.location.href.includes(this.rgwBuckerUrlPrefix) ||
     document.location.href.includes(this.rgwRoleUrlPrefix) ||
-    document.location.href.includes(this.rgwAccountsUrlPrefix);
+    document.location.href.includes(this.rgwAccountsUrlPrefix) ||
+    document.location.href.includes(this.rgwMultisiteSyncPolicyPrefix);
 
   constructor(
     private authStorageService: AuthStorageService,
@@ -57,7 +59,8 @@ export class ContextComponent implements OnInit, OnDestroy {
               this.rgwBuckerUrlPrefix,
               this.rgwUserUrlPrefix,
               this.rgwRoleUrlPrefix,
-              this.rgwAccountsUrlPrefix
+              this.rgwAccountsUrlPrefix,
+              this.rgwMultisiteSyncPolicyPrefix
             ].some((urlPrefix) => this.router.url.startsWith(urlPrefix)))
         )
     );
index 27b1e5fe54579078b9a37425624fe46e8c2bcafa..0e41b6fd576dfbfa63e9e42faab234041a9b7c8f 100644 (file)
@@ -1,6 +1,6 @@
 import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
 import { TestBed } from '@angular/core/testing';
-import { configureTestBed } from '~/testing/unit-test-helper';
+import { configureTestBed, RgwHelper } from '~/testing/unit-test-helper';
 import { RgwMultisiteService } from './rgw-multisite.service';
 import { BlockUIModule } from 'ng-block-ui';
 import { ToastrModule } from 'ngx-toastr';
@@ -39,6 +39,7 @@ describe('RgwMultisiteService', () => {
   beforeEach(() => {
     service = TestBed.inject(RgwMultisiteService);
     httpTesting = TestBed.inject(HttpTestingController);
+    RgwHelper.selectDaemon();
   });
 
   afterEach(() => {
@@ -51,7 +52,9 @@ describe('RgwMultisiteService', () => {
 
   it('should fetch all the sync policy related or un-related to a bucket', () => {
     service.getSyncPolicy('', '', true).subscribe();
-    const req = httpTesting.expectOne('api/rgw/multisite/sync-policy?all_policy=true');
+    const req = httpTesting.expectOne(
+      `api/rgw/multisite/sync-policy?${RgwHelper.DAEMON_QUERY_PARAM}&all_policy=true`
+    );
     expect(req.request.method).toBe('GET');
     req.flush(mockSyncPolicyData);
   });
@@ -59,7 +62,9 @@ describe('RgwMultisiteService', () => {
   it('should create Sync Policy Group w/o bucket_name', () => {
     const postData = { group_id: 'test', status: 'enabled' };
     service.createSyncPolicyGroup(postData).subscribe();
-    const req = httpTesting.expectOne('api/rgw/multisite/sync-policy-group');
+    const req = httpTesting.expectOne(
+      `api/rgw/multisite/sync-policy-group?${RgwHelper.DAEMON_QUERY_PARAM}`
+    );
     expect(req.request.method).toBe('POST');
     expect(req.request.body).toEqual(postData);
     req.flush(null);
@@ -68,7 +73,9 @@ describe('RgwMultisiteService', () => {
   it('should create Sync Policy Group with bucket_name', () => {
     const postData = { group_id: 'test', status: 'enabled', bucket_name: 'test' };
     service.createSyncPolicyGroup(postData).subscribe();
-    const req = httpTesting.expectOne('api/rgw/multisite/sync-policy-group');
+    const req = httpTesting.expectOne(
+      `api/rgw/multisite/sync-policy-group?${RgwHelper.DAEMON_QUERY_PARAM}`
+    );
     expect(req.request.method).toBe('POST');
     expect(req.request.body).toEqual(postData);
     req.flush(null);
@@ -77,7 +84,9 @@ describe('RgwMultisiteService', () => {
   it('should modify Sync Policy Group', () => {
     const postData = { group_id: 'test', status: 'enabled', bucket_name: 'test' };
     service.modifySyncPolicyGroup(postData).subscribe();
-    const req = httpTesting.expectOne('api/rgw/multisite/sync-policy-group');
+    const req = httpTesting.expectOne(
+      `api/rgw/multisite/sync-policy-group?${RgwHelper.DAEMON_QUERY_PARAM}`
+    );
     expect(req.request.method).toBe('PUT');
     expect(req.request.body).toEqual(postData);
     req.flush(null);
@@ -86,14 +95,18 @@ describe('RgwMultisiteService', () => {
   it('should remove Sync Policy Group', () => {
     const group_id = 'test';
     service.removeSyncPolicyGroup(group_id).subscribe();
-    const req = httpTesting.expectOne('api/rgw/multisite/sync-policy-group/' + group_id);
+    const req = httpTesting.expectOne(
+      `api/rgw/multisite/sync-policy-group/${group_id}?${RgwHelper.DAEMON_QUERY_PARAM}`
+    );
     expect(req.request.method).toBe('DELETE');
     req.flush(null);
   });
 
   it('should fetch the sync policy group with given group_id and bucket_name', () => {
     service.getSyncPolicyGroup('test', 'test').subscribe();
-    const req = httpTesting.expectOne('api/rgw/multisite/sync-policy-group/test?bucket_name=test');
+    const req = httpTesting.expectOne(
+      `api/rgw/multisite/sync-policy-group/test?${RgwHelper.DAEMON_QUERY_PARAM}&bucket_name=test`
+    );
     expect(req.request.method).toBe('GET');
     req.flush(mockSyncPolicyData[1]);
   });
@@ -107,7 +120,9 @@ describe('RgwMultisiteService', () => {
       zones: ['zone1-zg1-realm1']
     };
     service.createEditSyncFlow(payload).subscribe();
-    const req = httpTesting.expectOne('api/rgw/multisite/sync-flow');
+    const req = httpTesting.expectOne(
+      `api/rgw/multisite/sync-flow?${RgwHelper.DAEMON_QUERY_PARAM}`
+    );
     expect(req.request.method).toBe('PUT');
     expect(req.request.body).toEqual(payload);
     req.flush(null);
@@ -123,7 +138,9 @@ describe('RgwMultisiteService', () => {
       destination_zone: ['zone1-zg2-realm2']
     };
     service.createEditSyncFlow(payload).subscribe();
-    const req = httpTesting.expectOne('api/rgw/multisite/sync-flow');
+    const req = httpTesting.expectOne(
+      `api/rgw/multisite/sync-flow?${RgwHelper.DAEMON_QUERY_PARAM}`
+    );
     expect(req.request.method).toBe('PUT');
     expect(req.request.body).toEqual(payload);
     req.flush(null);
@@ -138,7 +155,9 @@ describe('RgwMultisiteService', () => {
       zones: ['zone1-zg1-realm1', 'zone2-zg1-realm1']
     };
     service.createEditSyncFlow(payload).subscribe();
-    const req = httpTesting.expectOne('api/rgw/multisite/sync-flow');
+    const req = httpTesting.expectOne(
+      `api/rgw/multisite/sync-flow?${RgwHelper.DAEMON_QUERY_PARAM}`
+    );
     expect(req.request.method).toBe('PUT');
     expect(req.request.body).toEqual(payload);
     req.flush(null);
@@ -154,7 +173,9 @@ describe('RgwMultisiteService', () => {
       destination_zone: ['zone1-zg2-realm2', 'zone2-zg2-realm2']
     };
     service.createEditSyncFlow(payload).subscribe();
-    const req = httpTesting.expectOne('api/rgw/multisite/sync-flow');
+    const req = httpTesting.expectOne(
+      `api/rgw/multisite/sync-flow?${RgwHelper.DAEMON_QUERY_PARAM}`
+    );
     expect(req.request.method).toBe('PUT');
     expect(req.request.body).toEqual(payload);
     req.flush(null);
@@ -163,7 +184,7 @@ describe('RgwMultisiteService', () => {
   it('should remove Symmetrical Sync flow', () => {
     service.removeSyncFlow('test', 'symmetrical', 'test', 'new-bucket').subscribe();
     const req = httpTesting.expectOne(
-      `api/rgw/multisite/sync-flow/test/symmetrical/test?bucket_name=new-bucket`
+      `api/rgw/multisite/sync-flow/test/symmetrical/test?${RgwHelper.DAEMON_QUERY_PARAM}&bucket_name=new-bucket`
     );
     expect(req.request.method).toBe('DELETE');
     req.flush(null);
@@ -172,7 +193,7 @@ describe('RgwMultisiteService', () => {
   it('should remove Directional Sync flow', () => {
     service.removeSyncFlow('test', 'directional', 'test', 'new-bucket').subscribe();
     const req = httpTesting.expectOne(
-      `api/rgw/multisite/sync-flow/test/directional/test?bucket_name=new-bucket`
+      `api/rgw/multisite/sync-flow/test/directional/test?${RgwHelper.DAEMON_QUERY_PARAM}&bucket_name=new-bucket`
     );
     expect(req.request.method).toBe('DELETE');
     req.flush(null);
@@ -187,7 +208,9 @@ describe('RgwMultisiteService', () => {
       group_id: 'sync-grp'
     };
     service.createEditSyncPipe(payload).subscribe();
-    const req = httpTesting.expectOne('api/rgw/multisite/sync-pipe');
+    const req = httpTesting.expectOne(
+      `api/rgw/multisite/sync-pipe?${RgwHelper.DAEMON_QUERY_PARAM}`
+    );
     expect(req.request.method).toBe('PUT');
     expect(req.request.body).toEqual(payload);
     req.flush(null);
@@ -202,7 +225,9 @@ describe('RgwMultisiteService', () => {
       group_id: 'sync-grp'
     };
     service.createEditSyncFlow(payload).subscribe();
-    const req = httpTesting.expectOne('api/rgw/multisite/sync-flow');
+    const req = httpTesting.expectOne(
+      `api/rgw/multisite/sync-flow?${RgwHelper.DAEMON_QUERY_PARAM}`
+    );
     expect(req.request.method).toBe('PUT');
     expect(req.request.body).toEqual(payload);
     req.flush(null);
@@ -211,7 +236,7 @@ describe('RgwMultisiteService', () => {
   it('should remove Sync Pipe', () => {
     service.removeSyncPipe('test', 'sync-grp', 'new-bucket').subscribe();
     const req = httpTesting.expectOne(
-      `api/rgw/multisite/sync-pipe/sync-grp/test?bucket_name=new-bucket`
+      `api/rgw/multisite/sync-pipe/sync-grp/test?${RgwHelper.DAEMON_QUERY_PARAM}&bucket_name=new-bucket`
     );
     expect(req.request.method).toBe('DELETE');
     req.flush(null);
index 7045c498455ba24bbc236268f59492124ae0603c..b963c1139b83280e3c5e86d4c669a7ab41758765 100644 (file)
@@ -49,40 +49,47 @@ export class RgwMultisiteService {
   }
 
   getSyncPolicy(bucketName?: string, zonegroup?: string, fetchAllPolicy = false) {
-    let params = new HttpParams();
-    if (bucketName) {
-      params = params.append('bucket_name', bucketName);
-    }
-    if (zonegroup) {
-      params = params.append('zonegroup_name', zonegroup);
-    }
-    // fetchAllPolicy - if true, will fetch all the policy either linked or not linked with the buckets
-    params = params.append('all_policy', fetchAllPolicy);
-    return this.http.get(`${this.url}/sync-policy`, { params });
+    return this.rgwDaemonService.request((params: HttpParams) => {
+      if (bucketName) {
+        params = params.append('bucket_name', bucketName);
+      }
+      if (zonegroup) {
+        params = params.append('zonegroup_name', zonegroup);
+      }
+      // fetchAllPolicy - if true, will fetch all the policy either linked or not linked with the buckets
+      params = params.append('all_policy', fetchAllPolicy);
+      return this.http.get(`${this.url}/sync-policy`, { params });
+    });
   }
 
   getSyncPolicyGroup(group_id: string, bucket_name?: string) {
-    let params = new HttpParams();
-    if (bucket_name) {
-      params = params.append('bucket_name', bucket_name);
-    }
-    return this.http.get(`${this.url}/sync-policy-group/${group_id}`, { params });
+    return this.rgwDaemonService.request((params: HttpParams) => {
+      if (bucket_name) {
+        params = params.append('bucket_name', bucket_name);
+      }
+      return this.http.get(`${this.url}/sync-policy-group/${group_id}`, { params });
+    });
   }
 
   createSyncPolicyGroup(payload: { group_id: string; status: string; bucket_name?: string }) {
-    return this.http.post(`${this.url}/sync-policy-group`, payload);
+    return this.rgwDaemonService.request((params: HttpParams) => {
+      return this.http.post(`${this.url}/sync-policy-group`, payload, { params: params });
+    });
   }
 
   modifySyncPolicyGroup(payload: { group_id: string; status: string; bucket_name?: string }) {
-    return this.http.put(`${this.url}/sync-policy-group`, payload);
+    return this.rgwDaemonService.request((params: HttpParams) => {
+      return this.http.put(`${this.url}/sync-policy-group`, payload, { params: params });
+    });
   }
 
   removeSyncPolicyGroup(group_id: string, bucket_name?: string) {
-    let params = new HttpParams();
-    if (bucket_name) {
-      params = params.append('bucket_name', bucket_name);
-    }
-    return this.http.delete(`${this.url}/sync-policy-group/${group_id}`, { params });
+    return this.rgwDaemonService.request((params: HttpParams) => {
+      if (bucket_name) {
+        params = params.append('bucket_name', bucket_name);
+      }
+      return this.http.delete(`${this.url}/sync-policy-group/${group_id}`, { params });
+    });
   }
 
   setUpMultisiteReplication(
@@ -130,42 +137,47 @@ export class RgwMultisiteService {
   }
 
   createEditSyncFlow(payload: any) {
-    return this.http.put(`${this.url}/sync-flow`, payload);
+    return this.rgwDaemonService.request((params: HttpParams) => {
+      return this.http.put(`${this.url}/sync-flow`, payload, { params: params });
+    });
   }
 
   removeSyncFlow(flow_id: string, flow_type: string, group_id: string, bucket_name?: string) {
-    let params = new HttpParams();
-    if (bucket_name) {
-      params = params.append('bucket_name', encodeURIComponent(bucket_name));
-    }
-    return this.http.delete(
-      `${this.url}/sync-flow/${encodeURIComponent(flow_id)}/${flow_type}/${encodeURIComponent(
-        group_id
-      )}`,
-      { params }
-    );
+    return this.rgwDaemonService.request((params: HttpParams) => {
+      if (bucket_name) {
+        params = params.append('bucket_name', encodeURIComponent(bucket_name));
+      }
+      return this.http.delete(
+        `${this.url}/sync-flow/${encodeURIComponent(flow_id)}/${flow_type}/${encodeURIComponent(
+          group_id
+        )}`,
+        { params }
+      );
+    });
   }
 
   createEditSyncPipe(payload: any, user?: string, mode?: string) {
-    let params = new HttpParams();
-    if (user) {
-      params = params.append('user', user);
-    }
-    if (mode) {
-      params = params.append('mode', mode);
-    }
-    return this.http.put(`${this.url}/sync-pipe`, payload, { params });
+    return this.rgwDaemonService.request((params: HttpParams) => {
+      if (user) {
+        params = params.append('user', user);
+      }
+      if (mode) {
+        params = params.append('mode', mode);
+      }
+      return this.http.put(`${this.url}/sync-pipe`, payload, { params });
+    });
   }
 
   removeSyncPipe(pipe_id: string, group_id: string, bucket_name?: string) {
-    let params = new HttpParams();
-    if (bucket_name) {
-      params = params.append('bucket_name', encodeURIComponent(bucket_name));
-    }
-    return this.http.delete(
-      `${this.url}/sync-pipe/${encodeURIComponent(group_id)}/${encodeURIComponent(pipe_id)}`,
-      { params }
-    );
+    return this.rgwDaemonService.request((params: HttpParams) => {
+      if (bucket_name) {
+        params = params.append('bucket_name', encodeURIComponent(bucket_name));
+      }
+      return this.http.delete(
+        `${this.url}/sync-pipe/${encodeURIComponent(group_id)}/${encodeURIComponent(pipe_id)}`,
+        { params }
+      );
+    });
   }
 
   setRestartGatewayMessage(value: boolean): void {
index a43c312b4e67b742a2c5d162d5a45bd78ba180b2..d2e5aca503a3883808b7a38645d291723681e1ce 100644 (file)
@@ -18914,6 +18914,9 @@ paths:
                 bucket_name:
                   default: ''
                   type: string
+                daemon_name:
+                  default: ''
+                  type: string
                 destination_zone:
                   type: string
                 flow_id:
@@ -19002,6 +19005,11 @@ paths:
         name: bucket_name
         schema:
           type: string
+      - default: ''
+        in: query
+        name: daemon_name
+        schema:
+          type: string
       responses:
         '202':
           content:
@@ -19046,6 +19054,9 @@ paths:
                 bucket_name:
                   default: ''
                   type: string
+                daemon_name:
+                  default: ''
+                  type: string
                 destination_bucket:
                   default: ''
                   type: string
@@ -19133,6 +19144,11 @@ paths:
         name: bucket_name
         schema:
           type: string
+      - default: ''
+        in: query
+        name: daemon_name
+        schema:
+          type: string
       responses:
         '202':
           content:
@@ -19184,6 +19200,11 @@ paths:
         name: all_policy
         schema:
           type: string
+      - default: ''
+        in: query
+        name: daemon_name
+        schema:
+          type: string
       responses:
         '200':
           content:
@@ -19219,6 +19240,9 @@ paths:
                 bucket_name:
                   default: ''
                   type: string
+                daemon_name:
+                  default: ''
+                  type: string
                 group_id:
                   type: string
                 status:
@@ -19270,6 +19294,9 @@ paths:
                 bucket_name:
                   default: ''
                   type: string
+                daemon_name:
+                  default: ''
+                  type: string
                 group_id:
                   type: string
                 status:
@@ -19324,6 +19351,11 @@ paths:
         name: bucket_name
         schema:
           type: string
+      - default: ''
+        in: query
+        name: daemon_name
+        schema:
+          type: string
       responses:
         '202':
           content:
@@ -19369,6 +19401,11 @@ paths:
         name: bucket_name
         schema:
           type: string
+      - default: ''
+        in: query
+        name: daemon_name
+        schema:
+          type: string
       responses:
         '200':
           content:
index d9fbc71e6156e6cca3736a7f230a47ef5105fe62..c005ae8e8285703f1a85cc93aa2f210079925d80 100755 (executable)
@@ -1396,9 +1396,9 @@ class RgwMultisiteAutomation:
         try:
             rgw_multisite_instance = RgwMultisite()
             self.update_progress(
-                f"Initializing multi-site configuration || Creating realm: {realm}, \
-                    zonegroup: {zg}, and zone: {zone} along \
-                        with system user: {username}"
+                f"Initializing multi-site configuration || Creating realm: "
+                f"{realm}, zonegroup: {zg}, and zone: {zone} along "
+                f"with system user: {username}"
             )
             rgw_multisite_instance.create_realm(realm_name=realm, default=True)
             rgw_multisite_instance.create_zonegroup(realm_name=realm, zonegroup_name=zg,
@@ -1495,9 +1495,9 @@ class RgwMultisiteAutomation:
 
             self.progress_done += 1
             self.update_progress(
-                f"Verifying system user and completing replication setup on \
-                    cluster {cluster_fsid} || Ensuring presence of user '{username}' \
-                        and assigning necessary RGW credentials"
+                f"Verifying system user and completing replication setup on "
+                f"cluster {cluster_fsid} || Ensuring presence of user "
+                f"'{username}' and assigning necessary RGW credentials"
             )
 
             self._verify_user_and_daemons(cluster_url, cluster_token, realm_name,
@@ -2690,6 +2690,50 @@ class RgwMultisite:
             raise DashboardException(error, http_status_code=500, component='rgw')
         return user_list
 
+    def get_realm_from_daemon(self, daemon_name: Optional[str] = None) -> Optional[str]:
+        """Extract realm_name from daemon if daemon_name is provided."""
+        if not daemon_name:
+            return None
+        try:
+            daemons = _get_daemons()
+            return daemons[daemon_name].realm_name
+        except (KeyError, AttributeError):
+            # If daemon not found or has no realm_name, return None
+            return None
+
+    def get_zonegroup_from_daemon(self, daemon_name: Optional[str] = None) -> Optional[str]:
+        """Extract zonegroup_name from daemon if daemon_name is provided."""
+        if not daemon_name:
+            return None
+        try:
+            daemons = _get_daemons()
+            return daemons[daemon_name].zonegroup_name
+        except (KeyError, AttributeError):
+            # If daemon not found or has no zonegroup_name, return None
+            return None
+
+    def _add_realm_zonegroup_args(
+            self, cmd: List[str], daemon_name: Optional[str] = None,
+            explicit_zonegroup: str = '') -> None:
+        """
+        Add --rgw-realm and --rgw-zonegroup arguments to a radosgw-admin command.
+
+        Args:
+            cmd: The command list to append arguments to
+            daemon_name: The daemon name to extract realm/zonegroup from
+            explicit_zonegroup: If provided, use this instead of daemon's zonegroup
+        """
+        realm_name = self.get_realm_from_daemon(daemon_name)
+        if realm_name:
+            cmd += ['--rgw-realm', realm_name]
+
+        if explicit_zonegroup:
+            zonegroup_name: Optional[str] = explicit_zonegroup
+        else:
+            zonegroup_name = self.get_zonegroup_from_daemon(daemon_name)
+        if zonegroup_name:
+            cmd += ['--rgw-zonegroup', zonegroup_name]
+
     def get_multisite_status(self):
         is_multisite_configured = True
         rgw_realm_list = self.list_realms()
@@ -2805,12 +2849,20 @@ class RgwMultisite:
 
         return ''
 
-    def get_sync_policy(self, bucket_name: str = '', zonegroup_name: str = ''):
+    def get_sync_policy(self, bucket_name: str = '',
+                        zonegroup_name: str = '',
+                        daemon_name: Optional[str] = None):
         rgw_sync_policy_cmd = ['sync', 'policy', 'get']
         if bucket_name:
             rgw_sync_policy_cmd += ['--bucket', bucket_name]
-        if zonegroup_name:
-            rgw_sync_policy_cmd += ['--rgw-zonegroup', zonegroup_name]
+        # Use daemon's realm and zonegroup when daemon_name is provided
+        realm_name = self.get_realm_from_daemon(daemon_name)
+        if realm_name:
+            rgw_sync_policy_cmd += ['--rgw-realm', realm_name]
+        # Using daemon's zonegroup if no explicit zonegroup provided
+        target_zonegroup = zonegroup_name or self.get_zonegroup_from_daemon(daemon_name)
+        if target_zonegroup:
+            rgw_sync_policy_cmd += ['--rgw-zonegroup', target_zonegroup]
         try:
             exit_code, out, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
             if exit_code > 0:
@@ -2821,12 +2873,11 @@ class RgwMultisite:
             raise DashboardException(error, http_status_code=500, component='rgw')
 
     def get_sync_policy_group(self, group_id: str, bucket_name: str = '',
-                              zonegroup_name: str = ''):
+                              zonegroup_name: str = '', daemon_name: Optional[str] = None):
         rgw_sync_policy_cmd = ['sync', 'group', 'get', '--group-id', group_id]
         if bucket_name:
             rgw_sync_policy_cmd += ['--bucket', bucket_name]
-        if zonegroup_name:
-            rgw_sync_policy_cmd += ['--rgw-zonegroup', zonegroup_name]
+        self._add_realm_zonegroup_args(rgw_sync_policy_cmd, daemon_name, zonegroup_name)
         try:
             exit_code, out, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
             if exit_code > 0:
@@ -2837,11 +2888,12 @@ class RgwMultisite:
             raise DashboardException(error, http_status_code=500, component='rgw')
 
     def create_sync_policy_group(self, group_id: str, status: str, bucket_name: str = '',
-                                 update_period=False):
+                                 update_period=False, daemon_name: Optional[str] = None):
         rgw_sync_policy_cmd = ['sync', 'group', 'create', '--group-id', group_id,
                                '--status', SyncStatus[status].value]
         if bucket_name:
             rgw_sync_policy_cmd += ['--bucket', bucket_name]
+        self._add_realm_zonegroup_args(rgw_sync_policy_cmd, daemon_name)
         try:
             exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
             if exit_code > 0:
@@ -2850,14 +2902,16 @@ class RgwMultisite:
         except SubprocessError as error:
             raise DashboardException(error, http_status_code=500, component='rgw')
         if not bucket_name and update_period:
-            self.update_period()
+            realm_name = self.get_realm_from_daemon(daemon_name)
+            self.update_period(realm_name=realm_name)
 
     def update_sync_policy_group(self, group_id: str, status: str, bucket_name: str = '',
-                                 update_period=False):
+                                 update_period=False, daemon_name: Optional[str] = None):
         rgw_sync_policy_cmd = ['sync', 'group', 'modify', '--group-id', group_id,
                                '--status', SyncStatus[status].value]
         if bucket_name:
             rgw_sync_policy_cmd += ['--bucket', bucket_name]
+        self._add_realm_zonegroup_args(rgw_sync_policy_cmd, daemon_name)
         try:
             exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
             if exit_code > 0:
@@ -2866,12 +2920,16 @@ class RgwMultisite:
         except SubprocessError as error:
             raise DashboardException(error, http_status_code=500, component='rgw')
         if not bucket_name and update_period:
-            self.update_period()
+            realm_name = self.get_realm_from_daemon(daemon_name)
+            self.update_period(realm_name=realm_name)
 
-    def remove_sync_policy_group(self, group_id: str, bucket_name='', update_period=False):
+    def remove_sync_policy_group(self, group_id: str, bucket_name='',
+                                 update_period=False,
+                                 daemon_name: Optional[str] = None):
         rgw_sync_policy_cmd = ['sync', 'group', 'remove', '--group-id', group_id]
         if bucket_name:
             rgw_sync_policy_cmd += ['--bucket', bucket_name]
+        self._add_realm_zonegroup_args(rgw_sync_policy_cmd, daemon_name)
         try:
             exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
             if exit_code > 0:
@@ -2880,18 +2938,20 @@ class RgwMultisite:
         except SubprocessError as error:
             raise DashboardException(error, http_status_code=500, component='rgw')
         if not bucket_name and update_period:
-            self.update_period()
+            realm_name = self.get_realm_from_daemon(daemon_name)
+            self.update_period(realm_name=realm_name)
 
     def create_sync_flow(self, group_id: str, flow_id: str, flow_type: str,
                          zones: Optional[Dict[str, List]] = None, bucket_name: str = '',
                          source_zone: Optional[str] = None,
                          destination_zone: Optional[str] = None,
-                         update_period=False):
+                         update_period=False, daemon_name: Optional[str] = None):
         rgw_sync_policy_cmd = ['sync', 'group', 'flow', 'create', '--group-id', group_id,
                                '--flow-id', flow_id, '--flow-type', SyncFlowTypes[flow_type].value]
 
         if bucket_name:
             rgw_sync_policy_cmd += ['--bucket', bucket_name]
+        self._add_realm_zonegroup_args(rgw_sync_policy_cmd, daemon_name)
 
         if SyncFlowTypes[flow_type].value == 'directional':
 
@@ -2925,15 +2985,18 @@ class RgwMultisite:
                         raise DashboardException(error, http_status_code=500, component='rgw')
 
                 if len(zones['removed']) > 0:
-                    self.remove_sync_flow(group_id, flow_id, flow_type, source_zone,
-                                          destination_zone, zones['removed'], bucket_name)
+                    self.remove_sync_flow(
+                        group_id, flow_id, flow_type, source_zone,
+                        destination_zone, zones['removed'], bucket_name,
+                        daemon_name=daemon_name)
         if not bucket_name and update_period:
-            self.update_period()
+            realm_name = self.get_realm_from_daemon(daemon_name)
+            self.update_period(realm_name=realm_name)
 
     def remove_sync_flow(self, group_id: str, flow_id: str, flow_type: str,
                          source_zone='', destination_zone='',
                          zones: Optional[List[str]] = None, bucket_name: str = '',
-                         update_period=False):
+                         update_period=False, daemon_name: Optional[str] = None):
         rgw_sync_policy_cmd = ['sync', 'group', 'flow', 'remove', '--group-id', group_id,
                                '--flow-id', flow_id, '--flow-type', SyncFlowTypes[flow_type].value]
 
@@ -2945,6 +3008,7 @@ class RgwMultisite:
 
         if bucket_name:
             rgw_sync_policy_cmd += ['--bucket', bucket_name]
+        self._add_realm_zonegroup_args(rgw_sync_policy_cmd, daemon_name)
 
         logger.info("Removing sync flow! %s", rgw_sync_policy_cmd)
         try:
@@ -2955,7 +3019,8 @@ class RgwMultisite:
         except SubprocessError as error:
             raise DashboardException(error, http_status_code=500, component='rgw')
         if not bucket_name and update_period:
-            self.update_period()
+            realm_name = self.get_realm_from_daemon(daemon_name)
+            self.update_period(realm_name=realm_name)
 
     def create_sync_pipe(self, group_id: str, pipe_id: str,
                          source_zones: Dict[str, Any],
@@ -2964,7 +3029,7 @@ class RgwMultisite:
                          destination_bucket: str = '',
                          bucket_name: str = '',
                          update_period=False,
-                         user: str = '', mode: str = ''):
+                         user: str = '', mode: str = '', daemon_name: Optional[str] = None):
 
         if source_zones['added'] or destination_zones['added']:
             rgw_sync_policy_cmd = ['sync', 'group', 'pipe', 'create',
@@ -2972,6 +3037,7 @@ class RgwMultisite:
 
             if bucket_name:
                 rgw_sync_policy_cmd += ['--bucket', bucket_name]
+            self._add_realm_zonegroup_args(rgw_sync_policy_cmd, daemon_name)
 
             rgw_sync_policy_cmd += ['--source-bucket', source_bucket]
 
@@ -2998,24 +3064,26 @@ class RgwMultisite:
             except SubprocessError as error:
                 raise DashboardException(error, http_status_code=500, component='rgw')
             if not bucket_name and update_period:
-                self.update_period()
+                realm_name = self.get_realm_from_daemon(daemon_name)
+                self.update_period(realm_name=realm_name)
 
         if ((source_zones['removed'] and '*' not in source_zones['added'])
                 or (destination_zones['removed'] and '*' not in destination_zones['added'])):
             self.remove_sync_pipe(group_id, pipe_id, source_zones['removed'],
                                   destination_zones['removed'],
-                                  bucket_name, True)
+                                  bucket_name, True, daemon_name=daemon_name)
 
     def remove_sync_pipe(self, group_id: str, pipe_id: str,
                          source_zones: Optional[List[str]] = None,
                          destination_zones: Optional[List[str]] = None,
                          bucket_name: str = '',
-                         update_period=False):
+                         update_period=False, daemon_name: Optional[str] = None):
         rgw_sync_policy_cmd = ['sync', 'group', 'pipe', 'remove',
                                '--group-id', group_id, '--pipe-id', pipe_id]
 
         if bucket_name:
             rgw_sync_policy_cmd += ['--bucket', bucket_name]
+        self._add_realm_zonegroup_args(rgw_sync_policy_cmd, daemon_name)
 
         if source_zones:
             rgw_sync_policy_cmd += ['--source-zones', ','.join(source_zones)]
@@ -3032,7 +3100,8 @@ class RgwMultisite:
         except SubprocessError as error:
             raise DashboardException(error, http_status_code=500, component='rgw')
         if not bucket_name and update_period:
-            self.update_period()
+            realm_name = self.get_realm_from_daemon(daemon_name)
+            self.update_period(realm_name=realm_name)
 
     def create_dashboard_admin_sync_group(self, zonegroup_name: str = ''):
 
@@ -3095,9 +3164,9 @@ class RgwTopicmanagement:
     def push_endpoint_password(push_endpoint: str) -> str:
         parsed = urlparse(push_endpoint)
         if parsed.username and parsed.password:
-            netloc = f"{parsed.username}:****@{parsed.hostname}"
+            netloc = f"{parsed.username}: ****@{parsed.hostname}"
             if parsed.port:
-                netloc += f":{parsed.port}"
+                netloc += f": {parsed.port}"
             parsed = parsed._replace(netloc=netloc)
             return urlunparse(parsed)
         return push_endpoint
index 898803dd29a66300579de7551d3051f088057beb..b5b350fd803c4878d3f44b4ff76b9633e4cff668 100644 (file)
@@ -7,7 +7,7 @@ from unittest.mock import Mock, patch
 from .. import mgr
 from ..exceptions import DashboardException
 from ..services.rgw_client import NoRgwDaemonsException, RgwClient, \
-    _determine_rgw_addr, _parse_frontend_config
+    RgwMultisite, _determine_rgw_addr, _parse_frontend_config
 from ..services.service import NoCredentialsException
 from ..settings import Settings
 from ..tests import CLICommandTestMixin, RgwStub
@@ -451,3 +451,259 @@ class TestDictToXML(TestCase):
         expected_xml = "<name>Foo</name>\n<age>30</age>\n"
         result = RgwClient.dict_to_xml(data)
         self.assertEqual(result, expected_xml)
+
+
+class RgwMultisiteTest(TestCase):
+    """Test cases for RgwMultisite class with daemon_name parameter support."""
+
+    def setUp(self):
+        RgwStub.get_daemons()
+
+    @patch('dashboard.services.rgw_client.mgr.send_rgwadmin_command')
+    @patch('dashboard.services.rgw_client._get_daemons')
+    def test_get_sync_policy_with_daemon_name(self, mock_get_daemons, mock_send_command):
+        """Test get_sync_policy with daemon_name parameter."""
+        mock_daemon = Mock()
+        mock_daemon.realm_name = 'test_realm'
+        mock_daemon.zonegroup_name = 'test_zonegroup'
+        mock_get_daemons.return_value = {'test_daemon': mock_daemon}
+
+        mock_send_command.return_value = (0, {'groups': []}, '')
+
+        multisite = RgwMultisite()
+        result = multisite.get_sync_policy(bucket_name='test_bucket', daemon_name='test_daemon')
+
+        self.assertEqual(result, {'groups': []})
+        # Verify the command includes realm and zonegroup
+        call_args = mock_send_command.call_args[0][0]
+        self.assertIn('--rgw-realm', call_args)
+        self.assertIn('test_realm', call_args)
+        self.assertIn('--rgw-zonegroup', call_args)
+        self.assertIn('test_zonegroup', call_args)
+
+    @patch('dashboard.services.rgw_client.mgr.send_rgwadmin_command')
+    @patch('dashboard.services.rgw_client._get_daemons')
+    def test_get_sync_policy_group_with_daemon_name(self, mock_get_daemons, mock_send_command):
+        """Test get_sync_policy_group with daemon_name parameter."""
+        mock_daemon = Mock()
+        mock_daemon.realm_name = 'test_realm'
+        mock_daemon.zonegroup_name = 'test_zonegroup'
+        mock_get_daemons.return_value = {'test_daemon': mock_daemon}
+
+        mock_send_command.return_value = (0, {'id': 'test_group'}, '')
+
+        multisite = RgwMultisite()
+        result = multisite.get_sync_policy_group('test_group', daemon_name='test_daemon')
+
+        self.assertEqual(result, {'id': 'test_group'})
+        call_args = mock_send_command.call_args[0][0]
+        self.assertIn('--rgw-realm', call_args)
+        self.assertIn('test_realm', call_args)
+
+    @patch('dashboard.services.rgw_client.mgr.send_rgwadmin_command')
+    @patch('dashboard.services.rgw_client._get_daemons')
+    def test_create_sync_policy_group_with_daemon_name(self, mock_get_daemons, mock_send_command):
+        """Test create_sync_policy_group with daemon_name parameter."""
+        mock_daemon = Mock()
+        mock_daemon.realm_name = 'test_realm'
+        mock_daemon.zonegroup_name = 'test_zonegroup'
+        mock_get_daemons.return_value = {'test_daemon': mock_daemon}
+
+        mock_send_command.return_value = (0, '', '')
+
+        multisite = RgwMultisite()
+        multisite.create_sync_policy_group('test_group', 'enabled', daemon_name='test_daemon')
+
+        call_args = mock_send_command.call_args[0][0]
+        self.assertIn('sync', call_args)
+        self.assertIn('group', call_args)
+        self.assertIn('create', call_args)
+        self.assertIn('--rgw-realm', call_args)
+        self.assertIn('test_realm', call_args)
+
+    @patch('dashboard.services.rgw_client.mgr.send_rgwadmin_command')
+    @patch('dashboard.services.rgw_client._get_daemons')
+    def test_update_sync_policy_group_with_daemon_name(self, mock_get_daemons, mock_send_command):
+        """Test update_sync_policy_group with daemon_name parameter."""
+        mock_daemon = Mock()
+        mock_daemon.realm_name = 'test_realm'
+        mock_daemon.zonegroup_name = 'test_zonegroup'
+        mock_get_daemons.return_value = {'test_daemon': mock_daemon}
+
+        mock_send_command.return_value = (0, '', '')
+
+        multisite = RgwMultisite()
+        multisite.update_sync_policy_group('test_group', 'enabled', daemon_name='test_daemon')
+
+        call_args = mock_send_command.call_args[0][0]
+        self.assertIn('sync', call_args)
+        self.assertIn('group', call_args)
+        self.assertIn('modify', call_args)
+        self.assertIn('--rgw-realm', call_args)
+
+    @patch('dashboard.services.rgw_client.mgr.send_rgwadmin_command')
+    @patch('dashboard.services.rgw_client._get_daemons')
+    def test_remove_sync_policy_group_with_daemon_name(self, mock_get_daemons, mock_send_command):
+        """Test remove_sync_policy_group with daemon_name parameter."""
+        mock_daemon = Mock()
+        mock_daemon.realm_name = 'test_realm'
+        mock_daemon.zonegroup_name = 'test_zonegroup'
+        mock_get_daemons.return_value = {'test_daemon': mock_daemon}
+
+        mock_send_command.return_value = (0, '', '')
+
+        multisite = RgwMultisite()
+        multisite.remove_sync_policy_group('test_group', daemon_name='test_daemon')
+
+        call_args = mock_send_command.call_args[0][0]
+        self.assertIn('sync', call_args)
+        self.assertIn('group', call_args)
+        self.assertIn('remove', call_args)
+        self.assertIn('--rgw-realm', call_args)
+
+    @patch('dashboard.services.rgw_client.mgr.send_rgwadmin_command')
+    @patch('dashboard.services.rgw_client._get_daemons')
+    def test_create_sync_flow_with_daemon_name(self, mock_get_daemons, mock_send_command):
+        """Test create_sync_flow with daemon_name parameter."""
+        mock_daemon = Mock()
+        mock_daemon.realm_name = 'test_realm'
+        mock_daemon.zonegroup_name = 'test_zonegroup'
+        mock_get_daemons.return_value = {'test_daemon': mock_daemon}
+
+        mock_send_command.return_value = (0, '', '')
+
+        multisite = RgwMultisite()
+        # For symmetrical flow, need to provide zones with added zones
+        multisite.create_sync_flow(
+            'test_group', 'test_flow', 'symmetrical',
+            zones={'added': ['zone1', 'zone2'], 'removed': []},
+            daemon_name='test_daemon'
+        )
+
+        call_args = mock_send_command.call_args[0][0]
+        self.assertIn('sync', call_args)
+        self.assertIn('group', call_args)
+        self.assertIn('flow', call_args)
+        self.assertIn('create', call_args)
+        self.assertIn('--rgw-realm', call_args)
+
+    @patch('dashboard.services.rgw_client.mgr.send_rgwadmin_command')
+    @patch('dashboard.services.rgw_client._get_daemons')
+    def test_remove_sync_flow_with_daemon_name(self, mock_get_daemons, mock_send_command):
+        """Test remove_sync_flow with daemon_name parameter."""
+        mock_daemon = Mock()
+        mock_daemon.realm_name = 'test_realm'
+        mock_daemon.zonegroup_name = 'test_zonegroup'
+        mock_get_daemons.return_value = {'test_daemon': mock_daemon}
+
+        mock_send_command.return_value = (0, '', '')
+
+        multisite = RgwMultisite()
+        multisite.remove_sync_flow(
+            'test_group', 'test_flow', 'symmetrical',
+            daemon_name='test_daemon'
+        )
+
+        call_args = mock_send_command.call_args[0][0]
+        self.assertIn('sync', call_args)
+        self.assertIn('group', call_args)
+        self.assertIn('flow', call_args)
+        self.assertIn('remove', call_args)
+        self.assertIn('--rgw-realm', call_args)
+
+    @patch('dashboard.services.rgw_client.mgr.send_rgwadmin_command')
+    @patch('dashboard.services.rgw_client._get_daemons')
+    def test_create_sync_pipe_with_daemon_name(self, mock_get_daemons, mock_send_command):
+        """Test create_sync_pipe with daemon_name parameter."""
+        mock_daemon = Mock()
+        mock_daemon.realm_name = 'test_realm'
+        mock_daemon.zonegroup_name = 'test_zonegroup'
+        mock_get_daemons.return_value = {'test_daemon': mock_daemon}
+
+        mock_send_command.return_value = (0, '', '')
+
+        multisite = RgwMultisite()
+        multisite.create_sync_pipe(
+            'test_group', 'test_pipe',
+            {'added': ['zone1'], 'removed': []},
+            {'added': ['zone2'], 'removed': []},
+            'source_bucket',
+            daemon_name='test_daemon'
+        )
+
+        call_args = mock_send_command.call_args[0][0]
+        self.assertIn('sync', call_args)
+        self.assertIn('group', call_args)
+        self.assertIn('pipe', call_args)
+        self.assertIn('create', call_args)
+        self.assertIn('--rgw-realm', call_args)
+
+    @patch('dashboard.services.rgw_client.mgr.send_rgwadmin_command')
+    @patch('dashboard.services.rgw_client._get_daemons')
+    def test_remove_sync_pipe_with_daemon_name(self, mock_get_daemons, mock_send_command):
+        """Test remove_sync_pipe with daemon_name parameter."""
+        mock_daemon = Mock()
+        mock_daemon.realm_name = 'test_realm'
+        mock_daemon.zonegroup_name = 'test_zonegroup'
+        mock_get_daemons.return_value = {'test_daemon': mock_daemon}
+
+        mock_send_command.return_value = (0, '', '')
+
+        multisite = RgwMultisite()
+        multisite.remove_sync_pipe(
+            'test_group', 'test_pipe',
+            source_zones=['zone1'],
+            destination_zones=['zone2'],
+            daemon_name='test_daemon'
+        )
+
+        call_args = mock_send_command.call_args[0][0]
+        self.assertIn('sync', call_args)
+        self.assertIn('group', call_args)
+        self.assertIn('pipe', call_args)
+        self.assertIn('remove', call_args)
+        self.assertIn('--rgw-realm', call_args)
+
+    @patch('dashboard.services.rgw_client._get_daemons')
+    def test_get_zonegroup_from_daemon(self, mock_get_daemons):
+        """Test get_zonegroup_from_daemon method."""
+        mock_daemon = Mock()
+        mock_daemon.zonegroup_name = 'test_zonegroup'
+        mock_get_daemons.return_value = {'test_daemon': mock_daemon}
+
+        multisite = RgwMultisite()
+        result = multisite.get_zonegroup_from_daemon('test_daemon')
+
+        self.assertEqual(result, 'test_zonegroup')
+
+    @patch('dashboard.services.rgw_client._get_daemons')
+    def test_get_zonegroup_from_daemon_not_found(self, mock_get_daemons):
+        """Test get_zonegroup_from_daemon with non-existent daemon."""
+        mock_get_daemons.return_value = {}
+
+        multisite = RgwMultisite()
+        result = multisite.get_zonegroup_from_daemon('non_existent_daemon')
+
+        self.assertIsNone(result)
+
+    @patch('dashboard.services.rgw_client._get_daemons')
+    def test_get_realm_from_daemon(self, mock_get_daemons):
+        """Test get_realm_from_daemon method."""
+        mock_daemon = Mock()
+        mock_daemon.realm_name = 'test_realm'
+        mock_get_daemons.return_value = {'test_daemon': mock_daemon}
+
+        multisite = RgwMultisite()
+        result = multisite.get_realm_from_daemon('test_daemon')
+
+        self.assertEqual(result, 'test_realm')
+
+    @patch('dashboard.services.rgw_client._get_daemons')
+    def test_get_realm_from_daemon_not_found(self, mock_get_daemons):
+        """Test get_realm_from_daemon with non-existent daemon."""
+        mock_get_daemons.return_value = {}
+
+        multisite = RgwMultisite()
+        result = multisite.get_realm_from_daemon('non_existent_daemon')
+
+        self.assertIsNone(result)