]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/smb: add cluster-level QoS update command for CephFS shares
authorAvan Thakkar <athakkar@redhat.com>
Wed, 8 Apr 2026 10:28:55 +0000 (15:58 +0530)
committerAvan Thakkar <athakkar@redhat.com>
Mon, 4 May 2026 10:02:51 +0000 (15:32 +0530)
Signed-off-by: Avan Thakkar <athakkar@redhat.com>
src/pybind/mgr/smb/module.py
src/pybind/mgr/smb/tests/test_smb.py

index 53b2800695047cb5d385bffa0f057ebfe2b506e7..15535dd410df881d03f14605a5410f4d5bc7a885 100644 (file)
@@ -345,6 +345,98 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
             [cluster], password_filter_out=password_filter
         ).one()
 
+    @SMBCLICommand('cluster update cephfs qos', perm='rw')
+    def cluster_update_qos(
+        self,
+        cluster_id: str,
+        read_iops_limit: Optional[int] = None,
+        write_iops_limit: Optional[int] = None,
+        read_bw_limit: Optional[str] = None,
+        write_bw_limit: Optional[str] = None,
+        read_burst_mult: Optional[int] = None,
+        write_burst_mult: Optional[int] = None,
+    ) -> Simplified:
+        """Update QoS settings for all CephFS shares in a cluster"""
+        try:
+            shares = self._handler.matching_resources(
+                [f'ceph.smb.share.{cluster_id}']
+            )
+
+            active_shares = [
+                s for s in shares if isinstance(s, resources.Share)
+            ]
+
+            if not active_shares:
+                raise ValueError(f"No shares found for cluster {cluster_id}")
+
+            shares_to_update: List[resources.SMBResource] = []
+            unchanged_shares = []
+
+            for share in active_shares:
+                if not share.cephfs:
+                    unchanged_shares.append(share.share_id)
+                    continue
+
+                try:
+                    updated_cephfs = share.cephfs.update_qos(
+                        read_iops_limit=read_iops_limit,
+                        write_iops_limit=write_iops_limit,
+                        read_bw_limit=read_bw_limit,
+                        write_bw_limit=write_bw_limit,
+                        read_burst_mult=read_burst_mult,
+                        write_burst_mult=write_burst_mult,
+                    )
+
+                    if updated_cephfs != share.cephfs:
+                        updated_share = replace(share, cephfs=updated_cephfs)
+                        shares_to_update.append(updated_share)
+                    else:
+                        unchanged_shares.append(share.share_id)
+
+                except ValueError as e:
+                    raise ValueError(
+                        f"Error updating share {share.share_id}: {str(e)}"
+                    )
+
+            if not shares_to_update:
+                return {
+                    "cluster_id": cluster_id,
+                    "message": "No shares required QoS updates",
+                    "unchanged_shares": unchanged_shares,
+                    "total_shares": len(active_shares),
+                }
+
+            result_group = self._apply_res(shares_to_update)
+
+            successful_updates = []
+            failed_updates = []
+
+            for result in result_group:
+                if result.success and hasattr(result.src, 'share_id'):
+                    successful_updates.append(result.src.share_id)
+                elif hasattr(result.src, 'share_id'):
+                    failed_updates.append(
+                        {"share_id": result.src.share_id, "error": result.msg}
+                    )
+
+            return {
+                "cluster_id": cluster_id,
+                "successful_updates": successful_updates,
+                "failed_updates": failed_updates,
+                "unchanged_shares": unchanged_shares,
+                "total_shares": len(active_shares),
+                "success": len(failed_updates) == 0,
+            }
+
+        except resources.InvalidResourceError as err:
+            return {
+                "success": False,
+                "error": str(err),
+                "resource": err.resource_data,
+            }
+        except Exception as e:
+            return {"success": False, "error": str(e)}
+
     @SMBCLICommand('share ls', perm='r')
     def share_ls(self, cluster_id: str) -> List[str]:
         """List smb shares in a cluster by ID"""
index 3606ffdf99fe958bccef881075b7e415d8ff64ac..c9e2eec5a63673e1c5c432fb4bb1cab274f2ff2e 100644 (file)
@@ -1030,6 +1030,80 @@ def test_cmd_share_update_qos(tmodule):
     assert updated_share.cephfs.qos.write_burst_mult == 15  # Default
 
 
+def test_cmd_cluster_update_qos(tmodule):
+    cluster = _cluster(
+        cluster_id='qoscluster',
+        auth_mode=smb.enums.AuthMode.USER,
+        user_group_settings=[
+            smb.resources.UserGroupSource(
+                source_type=smb.resources.UserGroupSourceType.EMPTY,
+            ),
+        ],
+    )
+
+    share1 = smb.resources.Share(
+        cluster_id='qoscluster',
+        share_id='share1',
+        name='Share One',
+        cephfs=smb.resources.CephFSStorage(
+            volume='cephfs',
+            path='/share1',
+        ),
+    )
+    share2 = smb.resources.Share(
+        cluster_id='qoscluster',
+        share_id='share2',
+        name='Share Two',
+        cephfs=smb.resources.CephFSStorage(
+            volume='cephfs',
+            path='/share2',
+        ),
+    )
+    share3 = smb.resources.Share(
+        cluster_id='qoscluster',
+        share_id='share3',
+        name='Share Three',
+        cephfs=smb.resources.CephFSStorage(
+            volume='cephfs',
+            path='/share3',
+        ),
+    )
+
+    rg = tmodule._handler.apply([cluster, share1, share2, share3])
+    assert rg.success, rg.to_simplified()
+
+    res, body, status = tmodule.cluster_update_qos.command(
+        cluster_id='qoscluster',
+        read_iops_limit=100,
+        write_iops_limit=200,
+        read_bw_limit="1048576",
+        write_bw_limit="2097152",
+        read_burst_mult=20,
+        write_burst_mult=15,
+    )
+    assert res == 0
+    bdata = json.loads(body)
+    assert bdata['success']
+    assert bdata['cluster_id'] == 'qoscluster'
+    assert bdata['total_shares'] == 3
+    assert len(bdata['successful_updates']) == 3
+    assert len(bdata['failed_updates']) == 0
+
+    for share_id in ['share1', 'share2', 'share3']:
+        updated_shares = tmodule._handler.matching_resources(
+            [f'ceph.smb.share.qoscluster.{share_id}']
+        )
+        assert len(updated_shares) == 1
+        updated_share = updated_shares[0]
+        assert updated_share.cephfs.qos is not None
+        assert updated_share.cephfs.qos.read_iops_limit == 100
+        assert updated_share.cephfs.qos.write_iops_limit == 200
+        assert updated_share.cephfs.qos.read_bw_limit == "1048576"
+        assert updated_share.cephfs.qos.write_bw_limit == "2097152"
+        assert updated_share.cephfs.qos.read_burst_mult == 20
+        assert updated_share.cephfs.qos.write_burst_mult == 15
+
+
 def _keybridge_example():
     return [
         {