From: Avan Thakkar Date: Wed, 8 Apr 2026 10:28:55 +0000 (+0530) Subject: mgr/smb: add cluster-level QoS update command for CephFS shares X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=078151a3eda4d2f7e3ec1630b77ecf0d8781f00e;p=ceph.git mgr/smb: add cluster-level QoS update command for CephFS shares Signed-off-by: Avan Thakkar --- diff --git a/src/pybind/mgr/smb/module.py b/src/pybind/mgr/smb/module.py index 53b280069504..15535dd410df 100644 --- a/src/pybind/mgr/smb/module.py +++ b/src/pybind/mgr/smb/module.py @@ -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""" diff --git a/src/pybind/mgr/smb/tests/test_smb.py b/src/pybind/mgr/smb/tests/test_smb.py index 3606ffdf99fe..c9e2eec5a636 100644 --- a/src/pybind/mgr/smb/tests/test_smb.py +++ b/src/pybind/mgr/smb/tests/test_smb.py @@ -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 [ {