]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/smb: Add client support mode for macOS-specific SMB features
authorShweta Sodani <Shweta.Sodani@ibm.com>
Thu, 11 Jun 2026 13:21:33 +0000 (18:51 +0530)
committerShweta Sodani <Shweta.Sodani@ibm.com>
Tue, 16 Jun 2026 10:28:05 +0000 (15:58 +0530)
This commit introduces a new cluster-level configuration option to enable
client-specific SMB optimizations, starting with macOS support.

Usage:
  ceph smb cluster create <cluster-id> --client-compat macos
  ceph smb cluster update client-compat macos <cluster-id>

Signed-off-by: Shweta Sodani <ssodani@redhat.com>
src/pybind/mgr/smb/enums.py
src/pybind/mgr/smb/handler.py
src/pybind/mgr/smb/module.py
src/pybind/mgr/smb/resources.py

index 698cb6dde024301e5bf1abd6f70a00ac1712291a..fac7ba54bbfd4a269a38641623c660d6c6b07f02 100644 (file)
@@ -124,6 +124,19 @@ class ShowResults(_StrEnum):
     COLLAPSED = 'collapsed'
 
 
+class ClientSupportMode(_StrEnum):
+    """Determines if client-specific SMB features should be enabled.
+
+    - DEFAULT: Standard SMB behavior without client-specific optimizations
+    - MACOS: Enable macOS-specific features (AAPL extensions, fruit VFS, etc.)
+
+    Future values could include: WINDOWS_OPTIMIZED, LINUX_OPTIMIZED, etc.
+    """
+
+    DEFAULT = 'default'
+    MACOS = 'macos'
+
+
 class PasswordFilter(_StrEnum):
     """Filter type for password values."""
 
index f376368bd6db13699a85861c42f5f1f0ac7a34b4..6f6954d0b2e039ff9ccbc39f69428d009a3cde6f 100644 (file)
@@ -759,6 +759,7 @@ def order_resources(
 @dataclasses.dataclass(frozen=True)
 class _ShareConf:
     resource: resources.Share
+    cluster: resources.Cluster
     resolver: PathResolver
     cephx_entity: str
     ceph_cluster: str
@@ -807,7 +808,13 @@ class _ClusterConf:
         return cls(
             change_group.cluster,
             [
-                _ShareConf(s, resolver, cephx_entity, ceph_cluster)
+                _ShareConf(
+                    s,
+                    change_group.cluster,
+                    resolver,
+                    cephx_entity,
+                    ceph_cluster,
+                )
                 for s in change_group.shares
             ],
             change_group,
@@ -847,7 +854,14 @@ def _generate_share(conf: _ShareConf) -> Dict[str, Dict[str, str]]:
         if conf.ceph_cluster
         else '/etc/ceph/ceph.conf'
     )
-    modules = ["acl_xattr", "ceph_snapshots"]
+    # Build VFS modules list based on cluster configuration
+    modules = []
+    # Add macOS support modules if enabled
+    if conf.cluster.is_macos_compatibility_enabled:
+        modules.extend(["fruit", "streams_xattr"])
+
+    # Add standard modules
+    modules.extend(["acl_xattr", "ceph_snapshots"])
 
     if qos := cephfs.qos:
         vfs_rl = "aio_ratelimit"
index 4f69eb9b2e5dad6eb4b19140919d1afe55c231f9..9badbe574600770e9c59cf2be9af2caaaf442445 100644 (file)
@@ -31,6 +31,7 @@ from . import (
 from .cli import SMBCLICommand
 from .enums import (
     AuthMode,
+    ClientSupportMode,
     InputPasswordFilter,
     JoinSourceType,
     PasswordFilter,
@@ -223,6 +224,7 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
         placement: Optional[str] = None,
         clustering: Optional[SMBClustering] = None,
         public_addrs: Optional[List[str]] = None,
+        client_compat: Optional[ClientSupportMode] = None,
         password_filter: InputPasswordFilter = InputPasswordFilter.NONE,
         password_filter_out: Optional[PasswordFilter] = None,
     ) -> results.Result:
@@ -333,6 +335,7 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
             placement=pspec,
             clustering=clustering,
             public_addrs=c_public_addrs,
+            client_compat=client_compat,
         )
         to_apply.append(cluster)
         return self._apply_res(
@@ -442,6 +445,75 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
             password_filter_out=password_filter,
         )
 
+    @SMBCLICommand('cluster update client-compat', perm='rw')
+    def cluster_update_client_compat(
+        self,
+        client_compat: ClientSupportMode,
+        cluster_id: str,
+    ) -> Simplified:
+        """Update client compatibility mode for an SMB cluster and all its shares"""
+        # Get the existing cluster
+        clusters = self._handler.matching_resources(
+            [f'ceph.smb.cluster.{cluster_id}']
+        )
+
+        active_clusters = [
+            c for c in clusters if isinstance(c, resources.Cluster)
+        ]
+
+        if not active_clusters:
+            raise ValueError(f"Cluster {cluster_id} not found")
+
+        if len(active_clusters) > 1:
+            raise ValueError(f"Multiple clusters found matching {cluster_id}")
+
+        cluster = active_clusters[0]
+
+        # Create updated cluster with new client_compat setting
+        updated_cluster = replace(cluster, client_compat=client_compat)
+
+        # Get all shares for this cluster
+        shares = self._handler.matching_resources(
+            [f'ceph.smb.share.{cluster_id}']
+        )
+
+        active_shares = [s for s in shares if isinstance(s, resources.Share)]
+
+        # Prepare resources to update: cluster + all shares
+        resources_to_update: List[resources.SMBResource] = [updated_cluster]
+        resources_to_update.extend(active_shares)
+
+        # Apply the updates
+        result_group = self._apply_res(resources_to_update)
+
+        # Process results
+        cluster_updated = False
+        successful_share_updates = []
+        failed_share_updates = []
+
+        for result in result_group:
+            if result.success:
+                if isinstance(result.src, resources.Cluster):
+                    cluster_updated = True
+                elif hasattr(result.src, 'share_id'):
+                    successful_share_updates.append(result.src.share_id)
+            else:
+                if isinstance(result.src, resources.Share) and hasattr(
+                    result.src, 'share_id'
+                ):
+                    failed_share_updates.append(
+                        {"share_id": result.src.share_id, "error": result.msg}
+                    )
+
+        return {
+            "cluster_id": cluster_id,
+            "client_compat": client_compat.value,
+            "cluster_updated": cluster_updated,
+            "successful_share_updates": successful_share_updates,
+            "failed_share_updates": failed_share_updates,
+            "total_shares": len(active_shares),
+        }
+
     @SMBCLICommand('cluster update cephfs qos', perm='rw')
     def cluster_update_qos(
         self,
index fec79e8b10222ff7931ac8ba38be2384b9ef0ce3..0128b6346d2bb142a78752baf8f6a77941f220a7 100644 (file)
@@ -29,6 +29,7 @@ from . import resourcelib, validation
 from .enums import (
     AuthMode,
     CephFSStorageProvider,
+    ClientSupportMode,
     HostAccess,
     Intent,
     JoinSourceType,
@@ -902,6 +903,8 @@ class Cluster(_RBase):
     debug_level: Optional[dict[str, str]] = None
     # configure the keybridge (KMS integration) for this cluster
     keybridge: Optional[KeyBridge] = None
+    # client support mode for client-specific optimizations (macOS, etc.)
+    client_compat: Optional[ClientSupportMode] = None
 
     def validate(self) -> None:
         if not self.cluster_id:
@@ -950,6 +953,24 @@ class Cluster(_RBase):
     def clustering_mode(self) -> SMBClustering:
         return self.clustering if self.clustering else SMBClustering.DEFAULT
 
+    @property
+    def effective_client_compat(self) -> ClientSupportMode:
+        """Return the effective client compat mode.
+
+        Returns ClientSupportMode.DEFAULT if not explicitly set, ensuring
+        client-specific features are disabled by default.
+        """
+        return (
+            self.client_compat
+            if self.client_compat
+            else ClientSupportMode.DEFAULT
+        )
+
+    @property
+    def is_macos_compatibility_enabled(self) -> bool:
+        """Return true if macOS-specific SMB features should be enabled."""
+        return self.effective_client_compat == ClientSupportMode.MACOS
+
     @property
     def remote_control_is_enabled(self) -> bool:
         """Return true if a remote control service should be enabled for this