]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: add smb endpoints 60759/head
authorPedro Gonzalez Gomez <pegonzal@redhat.com>
Mon, 18 Nov 2024 10:29:40 +0000 (11:29 +0100)
committerPedro Gonzalez Gomez <pegonzal@redhat.com>
Fri, 29 Nov 2024 09:49:30 +0000 (10:49 +0100)
Adds following SMB endpoints:
- cluster: list, get, create
- share: list, delete

Fixes: https://tracker.ceph.com/issues/69044
Signed-off-by: Pedro Gonzalez Gomez <pegonzal@redhat.com>
src/pybind/mgr/dashboard/controllers/cephfs.py
src/pybind/mgr/dashboard/controllers/smb.py [new file with mode: 0644]
src/pybind/mgr/dashboard/openapi.yaml
src/pybind/mgr/dashboard/security.py
src/pybind/mgr/dashboard/services/access_control.py
src/pybind/mgr/dashboard/tests/test_smb.py [new file with mode: 0644]

index 9f9b7501f44cfc6213ba26a5c23fcad9f550ec19..d05b7551365d32b6ed5ab11a4d9e7f55e9175a95 100644 (file)
@@ -2,7 +2,6 @@
 # pylint: disable=too-many-lines
 import errno
 import json
-import logging
 import os
 from collections import defaultdict
 from typing import Any, Dict, List
@@ -30,8 +29,6 @@ GET_STATFS_SCHEMA = {
     'subdirs': (int, '')
 }
 
-logger = logging.getLogger("controllers.rgw")
-
 
 # pylint: disable=R0904
 @APIRouter('/cephfs', Scope.CEPHFS)
diff --git a/src/pybind/mgr/dashboard/controllers/smb.py b/src/pybind/mgr/dashboard/controllers/smb.py
new file mode 100644 (file)
index 0000000..97eff8c
--- /dev/null
@@ -0,0 +1,186 @@
+
+# -*- coding: utf-8 -*-
+
+import json
+import logging
+from typing import List
+
+from smb.enums import Intent
+from smb.proto import Simplified
+from smb.resources import Cluster, Share
+
+from dashboard.controllers._docs import EndpointDoc
+from dashboard.controllers._permissions import CreatePermission, DeletePermission
+from dashboard.exceptions import DashboardException
+
+from .. import mgr
+from ..security import Scope
+from . import APIDoc, APIRouter, ReadPermission, RESTController
+
+logger = logging.getLogger('controllers.smb')
+
+CLUSTER_SCHEMA = {
+    "resource_type": (str, "ceph.smb.cluster"),
+    "cluster_id": (str, "Unique identifier for the cluster"),
+    "auth_mode": (str, "Either 'active-directory' or 'user'"),
+    "intent": (str, "Desired state of the resource, e.g., 'present' or 'removed'"),
+    "domain_settings": ({
+        "realm": (str, "Domain realm, e.g., 'DOMAIN1.SINK.TEST'"),
+        "join_sources": ([{
+            "source_type": (str, "resource"),
+            "ref": (str, "Reference identifier for the join auth resource")
+        }], "List of join auth sources for domain settings")
+    }, "Domain-specific settings for active-directory auth mode"),
+    "user_group_settings": ([{
+        "source_type": (str, "resource"),
+        "ref": (str, "Reference identifier for the user group resource")
+    }], "User group settings for user auth mode"),
+    "custom_dns": ([str], "List of custom DNS server addresses"),
+    "placement": ({
+        "count": (int, "Number of instances to place")
+    }, "Placement configuration for the resource")
+}
+
+CLUSTER_SCHEMA_RESULTS = {
+    "results": ([{
+        "resource": ({
+            "resource_type": (str, "ceph.smb.cluster"),
+            "cluster_id": (str, "Unique identifier for the cluster"),
+            "auth_mode": (str, "Either 'active-directory' or 'user'"),
+            "intent": (str, "Desired state of the resource, e.g., 'present' or 'removed'"),
+            "domain_settings": ({
+                "realm": (str, "Domain realm, e.g., 'DOMAIN1.SINK.TEST'"),
+                "join_sources": ([{
+                    "source_type": (str, "resource"),
+                    "ref": (str, "Reference identifier for the join auth resource")
+                }], "List of join auth sources for domain settings")
+            }, "Domain-specific settings for active-directory auth mode"),
+            "user_group_settings": ([{
+                "source_type": (str, "resource"),
+                "ref": (str, "Reference identifier for the user group resource")
+            }], "User group settings for user auth mode (optional)"),
+            "custom_dns": ([str], "List of custom DNS server addresses (optional)"),
+            "placement": ({
+                "count": (int, "Number of instances to place")
+            }, "Placement configuration for the resource (optional)"),
+        }, "Resource details"),
+        "state": (str, "State of the resource"),
+        "success": (bool, "Indicates whether the operation was successful")
+    }], "List of results with resource details"),
+    "success": (bool, "Overall success status of the operation")
+}
+
+LIST_CLUSTER_SCHEMA = [CLUSTER_SCHEMA]
+
+SHARE_SCHEMA = {
+    "resource_type": (str, "ceph.smb.share"),
+    "cluster_id": (str, "Unique identifier for the cluster"),
+    "share_id": (str, "Unique identifier for the share"),
+    "intent": (str, "Desired state of the resource, e.g., 'present' or 'removed'"),
+    "name": (str, "Name of the share"),
+    "readonly": (bool, "Indicates if the share is read-only"),
+    "browseable": (bool, "Indicates if the share is browseable"),
+    "cephfs": ({
+        "volume": (str, "Name of the CephFS file system"),
+        "path": (str, "Path within the CephFS file system"),
+        "provider": (str, "Provider of the CephFS share, e.g., 'samba-vfs'")
+    }, "Configuration for the CephFS share")
+}
+
+
+@APIRouter('/smb/cluster', Scope.SMB)
+@APIDoc("SMB Cluster Management API", "SMB")
+class SMBCluster(RESTController):
+    _resource: str = 'ceph.smb.cluster'
+
+    @ReadPermission
+    @EndpointDoc("List smb clusters",
+                 responses={200: LIST_CLUSTER_SCHEMA})
+    def list(self) -> List[Cluster]:
+        """
+        List smb clusters
+        """
+        res = mgr.remote('smb', 'show', [self._resource])
+        return res['resources'] if 'resources' in res else [res]
+
+    @ReadPermission
+    @EndpointDoc("Get an smb cluster",
+                 parameters={
+                     'cluster_id': (str, 'Unique identifier for the cluster')
+                 },
+                 responses={200: CLUSTER_SCHEMA})
+    def get(self, cluster_id: str) -> Cluster:
+        """
+        Get an smb cluster by cluster id
+        """
+        return mgr.remote('smb', 'show', [f'{self._resource}.{cluster_id}'])
+
+    @CreatePermission
+    @EndpointDoc("Create smb cluster",
+                 parameters={
+                     'cluster_resource': (str, 'cluster_resource')
+                 },
+                 responses={201: CLUSTER_SCHEMA_RESULTS})
+    def create(self, cluster_resource: Cluster) -> Simplified:
+        """
+        Create an smb cluster
+
+        :param cluster_resource: Dict cluster data
+        :return: Returns cluster resource.
+        :rtype: Dict[str, Any]
+        """
+        try:
+            return mgr.remote(
+                'smb',
+                'apply_resources',
+                json.dumps(cluster_resource)).to_simplified()
+        except RuntimeError as e:
+            raise DashboardException(e, component='smb')
+
+
+@APIRouter('/smb/share', Scope.SMB)
+@APIDoc("SMB Share Management API", "SMB")
+class SMBShare(RESTController):
+    _resource: str = 'ceph.smb.share'
+
+    @ReadPermission
+    @EndpointDoc("List smb shares",
+                 parameters={
+                     'cluster_id': (str, 'Unique identifier for the cluster')
+                 },
+                 responses={200: SHARE_SCHEMA})
+    def list(self, cluster_id: str = '') -> List[Share]:
+        """
+        List all smb shares or all shares for a given cluster
+
+        :param cluster_id: Dict containing cluster information
+        :return: Returns list of shares.
+        :rtype: List[Dict]
+        """
+        res = mgr.remote(
+            'smb',
+            'show',
+            [f'{self._resource}.{cluster_id}' if cluster_id else self._resource])
+        return res['resources'] if 'resources' in res else res
+
+    @DeletePermission
+    @EndpointDoc("Remove smb shares",
+                 parameters={
+                     'cluster_id': (str, 'Unique identifier for the cluster'),
+                     'share_id': (str, 'Unique identifier for the share')
+                 },
+                 responses={204: None})
+    def delete(self, cluster_id: str, share_id: str):
+        """
+        Remove an smb share from a given cluster
+
+        :param cluster_id: Cluster identifier
+        :param share_id: Share identifier
+        :return: None.
+        """
+        resource = {}
+        resource['resource_type'] = self._resource
+        resource['cluster_id'] = cluster_id
+        resource['share_id'] = share_id
+        resource['intent'] = Intent.REMOVED
+        return mgr.remote('smb', 'apply_resources', json.dumps(resource)).one().to_simplified()
index b464344e27a2f76cc7a590529b25704ccd9d01ac..de8362980642914d4b639a537c1c31fb336ca358 100644 (file)
@@ -14055,6 +14055,500 @@ paths:
       - jwt: []
       tags:
       - Settings
+  /api/smb/cluster:
+    get:
+      description: "\n        List smb clusters\n        "
+      parameters: []
+      responses:
+        '200':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              schema:
+                items:
+                  properties:
+                    auth_mode:
+                      description: Either 'active-directory' or 'user'
+                      type: string
+                    cluster_id:
+                      description: Unique identifier for the cluster
+                      type: string
+                    custom_dns:
+                      description: List of custom DNS server addresses
+                      items:
+                        type: string
+                      type: array
+                    domain_settings:
+                      description: Domain-specific settings for active-directory auth
+                        mode
+                      properties:
+                        join_sources:
+                          description: List of join auth sources for domain settings
+                          items:
+                            properties:
+                              ref:
+                                description: Reference identifier for the join auth
+                                  resource
+                                type: string
+                              source_type:
+                                description: resource
+                                type: string
+                            required:
+                            - source_type
+                            - ref
+                            type: object
+                          type: array
+                        realm:
+                          description: Domain realm, e.g., 'DOMAIN1.SINK.TEST'
+                          type: string
+                      required:
+                      - realm
+                      - join_sources
+                      type: object
+                    intent:
+                      description: Desired state of the resource, e.g., 'present'
+                        or 'removed'
+                      type: string
+                    placement:
+                      description: Placement configuration for the resource
+                      properties:
+                        count:
+                          description: Number of instances to place
+                          type: integer
+                      required:
+                      - count
+                      type: object
+                    resource_type:
+                      description: ceph.smb.cluster
+                      type: string
+                    user_group_settings:
+                      description: User group settings for user auth mode
+                      items:
+                        properties:
+                          ref:
+                            description: Reference identifier for the user group resource
+                            type: string
+                          source_type:
+                            description: resource
+                            type: string
+                        required:
+                        - source_type
+                        - ref
+                        type: object
+                      type: array
+                  type: object
+                required:
+                - resource_type
+                - cluster_id
+                - auth_mode
+                - intent
+                - domain_settings
+                - user_group_settings
+                - custom_dns
+                - placement
+                type: array
+          description: OK
+        '400':
+          description: Operation exception. Please check the response body for details.
+        '401':
+          description: Unauthenticated access. Please login first.
+        '403':
+          description: Unauthorized access. Please check your permissions.
+        '500':
+          description: Unexpected error. Please check the response body for the stack
+            trace.
+      security:
+      - jwt: []
+      summary: List smb clusters
+      tags:
+      - SMB
+    post:
+      description: "\n        Create an smb cluster\n\n        :param cluster_resource:\
+        \ Dict cluster data\n        :return: Returns cluster resource.\n        :rtype:\
+        \ Dict[str, Any]\n        "
+      parameters: []
+      requestBody:
+        content:
+          application/json:
+            schema:
+              properties:
+                cluster_resource:
+                  description: cluster_resource
+                  type: string
+              required:
+              - cluster_resource
+              type: object
+      responses:
+        '201':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              schema:
+                properties:
+                  results:
+                    description: List of results with resource details
+                    items:
+                      properties:
+                        resource:
+                          description: Resource details
+                          properties:
+                            auth_mode:
+                              description: Either 'active-directory' or 'user'
+                              type: string
+                            cluster_id:
+                              description: Unique identifier for the cluster
+                              type: string
+                            custom_dns:
+                              description: List of custom DNS server addresses (optional)
+                              items:
+                                type: string
+                              type: array
+                            domain_settings:
+                              description: Domain-specific settings for active-directory
+                                auth mode
+                              properties:
+                                join_sources:
+                                  description: List of join auth sources for domain
+                                    settings
+                                  items:
+                                    properties:
+                                      ref:
+                                        description: Reference identifier for the
+                                          join auth resource
+                                        type: string
+                                      source_type:
+                                        description: resource
+                                        type: string
+                                    required:
+                                    - source_type
+                                    - ref
+                                    type: object
+                                  type: array
+                                realm:
+                                  description: Domain realm, e.g., 'DOMAIN1.SINK.TEST'
+                                  type: string
+                              required:
+                              - realm
+                              - join_sources
+                              type: object
+                            intent:
+                              description: Desired state of the resource, e.g., 'present'
+                                or 'removed'
+                              type: string
+                            placement:
+                              description: Placement configuration for the resource
+                                (optional)
+                              properties:
+                                count:
+                                  description: Number of instances to place
+                                  type: integer
+                              required:
+                              - count
+                              type: object
+                            resource_type:
+                              description: ceph.smb.cluster
+                              type: string
+                            user_group_settings:
+                              description: User group settings for user auth mode
+                                (optional)
+                              items:
+                                properties:
+                                  ref:
+                                    description: Reference identifier for the user
+                                      group resource
+                                    type: string
+                                  source_type:
+                                    description: resource
+                                    type: string
+                                required:
+                                - source_type
+                                - ref
+                                type: object
+                              type: array
+                          required:
+                          - resource_type
+                          - cluster_id
+                          - auth_mode
+                          - intent
+                          - domain_settings
+                          - user_group_settings
+                          - custom_dns
+                          - placement
+                          type: object
+                        state:
+                          description: State of the resource
+                          type: string
+                        success:
+                          description: Indicates whether the operation was successful
+                          type: boolean
+                      required:
+                      - resource
+                      - state
+                      - success
+                      type: object
+                    type: array
+                  success:
+                    description: Overall success status of the operation
+                    type: boolean
+                required:
+                - results
+                - success
+                type: object
+          description: Resource created.
+        '202':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              type: object
+          description: Operation is still executing. Please check the task queue.
+        '400':
+          description: Operation exception. Please check the response body for details.
+        '401':
+          description: Unauthenticated access. Please login first.
+        '403':
+          description: Unauthorized access. Please check your permissions.
+        '500':
+          description: Unexpected error. Please check the response body for the stack
+            trace.
+      security:
+      - jwt: []
+      summary: Create smb cluster
+      tags:
+      - SMB
+  /api/smb/cluster/{cluster_id}:
+    get:
+      description: "\n        Get an smb cluster by cluster id\n        "
+      parameters:
+      - description: Unique identifier for the cluster
+        in: path
+        name: cluster_id
+        required: true
+        schema:
+          type: string
+      responses:
+        '200':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              schema:
+                properties:
+                  auth_mode:
+                    description: Either 'active-directory' or 'user'
+                    type: string
+                  cluster_id:
+                    description: Unique identifier for the cluster
+                    type: string
+                  custom_dns:
+                    description: List of custom DNS server addresses
+                    items:
+                      type: string
+                    type: array
+                  domain_settings:
+                    description: Domain-specific settings for active-directory auth
+                      mode
+                    properties:
+                      join_sources:
+                        description: List of join auth sources for domain settings
+                        items:
+                          properties:
+                            ref:
+                              description: Reference identifier for the join auth
+                                resource
+                              type: string
+                            source_type:
+                              description: resource
+                              type: string
+                          required:
+                          - source_type
+                          - ref
+                          type: object
+                        type: array
+                      realm:
+                        description: Domain realm, e.g., 'DOMAIN1.SINK.TEST'
+                        type: string
+                    required:
+                    - realm
+                    - join_sources
+                    type: object
+                  intent:
+                    description: Desired state of the resource, e.g., 'present' or
+                      'removed'
+                    type: string
+                  placement:
+                    description: Placement configuration for the resource
+                    properties:
+                      count:
+                        description: Number of instances to place
+                        type: integer
+                    required:
+                    - count
+                    type: object
+                  resource_type:
+                    description: ceph.smb.cluster
+                    type: string
+                  user_group_settings:
+                    description: User group settings for user auth mode
+                    items:
+                      properties:
+                        ref:
+                          description: Reference identifier for the user group resource
+                          type: string
+                        source_type:
+                          description: resource
+                          type: string
+                      required:
+                      - source_type
+                      - ref
+                      type: object
+                    type: array
+                required:
+                - resource_type
+                - cluster_id
+                - auth_mode
+                - intent
+                - domain_settings
+                - user_group_settings
+                - custom_dns
+                - placement
+                type: object
+          description: OK
+        '400':
+          description: Operation exception. Please check the response body for details.
+        '401':
+          description: Unauthenticated access. Please login first.
+        '403':
+          description: Unauthorized access. Please check your permissions.
+        '500':
+          description: Unexpected error. Please check the response body for the stack
+            trace.
+      security:
+      - jwt: []
+      summary: Get an smb cluster
+      tags:
+      - SMB
+  /api/smb/share:
+    get:
+      description: "\n        List all smb shares or all shares for a given cluster\n\
+        \n        :param cluster_id: Dict containing cluster information\n       \
+        \ :return: Returns list of shares.\n        :rtype: List[Dict]\n        "
+      parameters:
+      - default: ''
+        description: Unique identifier for the cluster
+        in: query
+        name: cluster_id
+        schema:
+          type: string
+      responses:
+        '200':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              schema:
+                properties:
+                  browseable:
+                    description: Indicates if the share is browseable
+                    type: boolean
+                  cephfs:
+                    description: Configuration for the CephFS share
+                    properties:
+                      path:
+                        description: Path within the CephFS file system
+                        type: string
+                      provider:
+                        description: Provider of the CephFS share, e.g., 'samba-vfs'
+                        type: string
+                      volume:
+                        description: Name of the CephFS file system
+                        type: string
+                    required:
+                    - volume
+                    - path
+                    - provider
+                    type: object
+                  cluster_id:
+                    description: Unique identifier for the cluster
+                    type: string
+                  intent:
+                    description: Desired state of the resource, e.g., 'present' or
+                      'removed'
+                    type: string
+                  name:
+                    description: Name of the share
+                    type: string
+                  readonly:
+                    description: Indicates if the share is read-only
+                    type: boolean
+                  resource_type:
+                    description: ceph.smb.share
+                    type: string
+                  share_id:
+                    description: Unique identifier for the share
+                    type: string
+                required:
+                - resource_type
+                - cluster_id
+                - share_id
+                - intent
+                - name
+                - readonly
+                - browseable
+                - cephfs
+                type: object
+          description: OK
+        '400':
+          description: Operation exception. Please check the response body for details.
+        '401':
+          description: Unauthenticated access. Please login first.
+        '403':
+          description: Unauthorized access. Please check your permissions.
+        '500':
+          description: Unexpected error. Please check the response body for the stack
+            trace.
+      security:
+      - jwt: []
+      summary: List smb shares
+      tags:
+      - SMB
+  /api/smb/share/{cluster_id}/{share_id}:
+    delete:
+      description: "\n        Remove an smb share from a given cluster\n\n       \
+        \ :param cluster_id: Cluster identifier\n        :param share_id: Share identifier\n\
+        \        :return: None.\n        "
+      parameters:
+      - description: Unique identifier for the cluster
+        in: path
+        name: cluster_id
+        required: true
+        schema:
+          type: string
+      - description: Unique identifier for the share
+        in: path
+        name: share_id
+        required: true
+        schema:
+          type: string
+      responses:
+        '202':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              type: object
+          description: Operation is still executing. Please check the task queue.
+        '204':
+          content:
+            application/vnd.ceph.api.v1.0+json:
+              schema:
+                properties: {}
+                type: object
+          description: Resource deleted.
+        '400':
+          description: Operation exception. Please check the response body for details.
+        '401':
+          description: Unauthenticated access. Please login first.
+        '403':
+          description: Unauthorized access. Please check your permissions.
+        '500':
+          description: Unexpected error. Please check the response body for the stack
+            trace.
+      security:
+      - jwt: []
+      summary: Remove smb shares
+      tags:
+      - SMB
   /api/summary:
     get:
       parameters: []
@@ -15478,6 +15972,8 @@ tags:
   name: RgwZonegroup
 - description: Role Management API
   name: Role
+- description: SMB Cluster Management API
+  name: SMB
 - description: Service Management API
   name: Service
 - description: Settings Management API
index 2b624aabcc725ffbaf43ed207cbf836ddce0c946..c329d24e1b34b11d6404d74eaf86650ff526ed4e 100644 (file)
@@ -27,6 +27,7 @@ class Scope(object):
     DASHBOARD_SETTINGS = "dashboard-settings"
     NFS_GANESHA = "nfs-ganesha"
     NVME_OF = "nvme-of"
+    SMB = "smb"
 
     @classmethod
     def all_scopes(cls):
index 21c1a9572bb6a9aaa61583132eeb11863af40d4d..6319802b6cc7ba0e309b01154d68f8efb2f67f6d 100644 (file)
@@ -278,6 +278,16 @@ GANESHA_MGR_ROLE = Role(
         Scope.CEPHFS: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
         Scope.RGW: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
         Scope.GRAFANA: [_P.READ],
+        Scope.SMB: [_P.READ]
+    })
+
+SMB_MGR_ROLE = Role(
+    'smb-manager', 'allows full permissions for the smb scope', {
+        Scope.SMB: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+        Scope.CEPHFS: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+        Scope.RGW: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE],
+        Scope.GRAFANA: [_P.READ],
+        Scope.NFS_GANESHA: [_P.READ]
     })
 
 
@@ -290,6 +300,7 @@ SYSTEM_ROLES = {
     POOL_MGR_ROLE.name: POOL_MGR_ROLE,
     CEPHFS_MGR_ROLE.name: CEPHFS_MGR_ROLE,
     GANESHA_MGR_ROLE.name: GANESHA_MGR_ROLE,
+    SMB_MGR_ROLE.name: SMB_MGR_ROLE,
 }
 
 # static name-like roles list for role mapping
diff --git a/src/pybind/mgr/dashboard/tests/test_smb.py b/src/pybind/mgr/dashboard/tests/test_smb.py
new file mode 100644 (file)
index 0000000..754df48
--- /dev/null
@@ -0,0 +1,197 @@
+import json
+from unittest.mock import Mock
+
+from dashboard.controllers.smb import SMBCluster, SMBShare
+
+from .. import mgr
+from ..tests import ControllerTestCase
+
+
+class SMBClusterTest(ControllerTestCase):
+    _endpoint = '/api/smb/cluster'
+
+    _clusters = {
+        "resources": [{
+            "resource_type": "ceph.smb.cluster",
+            "cluster_id": "clusterADTest",
+            "auth_mode": "active-directory",
+            "intent": "present",
+            "domain_settings": {
+                "realm": "DOMAIN1.SINK.TEST",
+                "join_sources": [
+                    {
+                        "source_type": "resource",
+                        "ref": "join1-admin"
+                    }]
+            },
+            "custom_dns": [
+                "192.168.76.204"
+            ],
+            "placement": {
+                "count": 1
+            }
+        },
+            {
+            "resource_type": "ceph.smb.cluster",
+            "cluster_id": "clusterUserTest",
+            "auth_mode": "user",
+            "intent": "present",
+            "user_group_settings": [
+                {
+                    "source_type": "resource",
+                    "ref": "ug1"
+                }
+            ]
+        }]
+    }
+
+    @classmethod
+    def setup_server(cls):
+        cls.setup_controllers([SMBCluster])
+
+    def test_list_one_cluster(self):
+        mgr.remote = Mock(return_value=self._clusters['resources'][0])
+
+        self._get(self._endpoint)
+        self.assertStatus(200)
+        self.assertJsonBody([self._clusters['resources'][0]])
+
+    def test_list_multiple_clusters(self):
+        mgr.remote = Mock(return_value=self._clusters)
+
+        self._get(self._endpoint)
+        self.assertStatus(200)
+        self.assertJsonBody(self._clusters['resources'])
+
+    def test_get_cluster(self):
+        mgr.remote = Mock(return_value=self._clusters['resources'][0])
+        cluster_id = self._clusters['resources'][0]['cluster_id']
+        self._get(f'{self._endpoint}/{cluster_id}')
+        self.assertStatus(200)
+        self.assertJsonBody(self._clusters['resources'][0])
+        mgr.remote.assert_called_once_with('smb', 'show', [f'ceph.smb.cluster.{cluster_id}'])
+
+    def test_create_ad(self):
+        mock_simplified = Mock()
+        mock_simplified.to_simplified.return_value = json.dumps(self._clusters['resources'][0])
+        mgr.remote = Mock(return_value=mock_simplified)
+
+        _cluster_data = """
+                        { "cluster_resource": {
+                            "resource_type": "ceph.smb.cluster",
+                            "cluster_id": "clusterADTest",
+                            "auth_mode": "active-directory",
+                            "domain_settings": {
+                                "realm": "DOMAIN1.SINK.TEST",
+                                "join_sources": [
+                                    {
+                                        "source_type": "resource",
+                                        "ref": "join1-admin"
+                                    }
+                                ]
+                            }
+                        }
+                        }
+                        """
+
+        self._post(self._endpoint, _cluster_data)
+        self.assertStatus(201)
+        self.assertInJsonBody(json.dumps(self._clusters['resources'][0]))
+
+    def test_create_user(self):
+        mock_simplified = Mock()
+        mock_simplified.to_simplified.return_value = json.dumps(self._clusters['resources'][1])
+        mgr.remote = Mock(return_value=mock_simplified)
+
+        _cluster_data = """
+                    { "cluster_resource": {
+                        "resource_type": "ceph.smb.cluster",
+                        "cluster_id": "clusterUser123Test",
+                        "auth_mode": "user",
+                        "user_group_settings": [
+                            {
+                                "source_type": "resource",
+                                "ref": "ug1"
+                            }
+                        ]
+                    }
+                    }
+                    """
+        self._post(self._endpoint, _cluster_data)
+        self.assertStatus(201)
+        self.assertInJsonBody(json.dumps(self._clusters['resources'][1]))
+
+
+class SMBShareTest(ControllerTestCase):
+    _endpoint = '/api/smb/share'
+
+    _shares = [{
+        "resource_type": "ceph.smb.share",
+        "cluster_id": "clusterUserTest",
+        "share_id": "share1",
+                    "intent": "present",
+                    "name": "share1name",
+                    "readonly": "false",
+                    "browseable": "true",
+                    "cephfs": {
+                        "volume": "fs1",
+                        "path": "/",
+                        "provider": "samba-vfs"
+                    }
+    },
+        {
+        "resource_type": "ceph.smb.share",
+        "cluster_id": "clusterADTest",
+        "share_id": "share2",
+                    "intent": "present",
+                    "name": "share2name",
+                    "readonly": "false",
+                    "browseable": "true",
+                    "cephfs": {
+                        "volume": "fs2",
+                        "path": "/",
+                        "provider": "samba-vfs"
+                    }
+    }
+    ]
+
+    @classmethod
+    def setup_server(cls):
+        cls.setup_controllers([SMBShare])
+
+    def test_list_all(self):
+        mgr.remote = Mock(return_value=self._shares)
+
+        self._get(self._endpoint)
+        self.assertStatus(200)
+        self.assertJsonBody(self._shares)
+
+    def test_list_from_cluster(self):
+        mgr.remote = Mock(return_value=self._shares[0])
+
+        self._get(self._endpoint)
+        self.assertStatus(200)
+        self.assertJsonBody(self._shares[0])
+
+    def test_delete(self):
+        _res = {
+            "resource": {
+                "resource_type": "ceph.smb.share",
+                "cluster_id": "smbCluster1",
+                "share_id": "share1",
+                "intent": "removed"
+            },
+            "state": "removed",
+            "success": "true"
+        }
+        _res_simplified = {
+            "resource_type": "ceph.smb.share",
+            "cluster_id": "smbCluster1",
+            "share_id": "share1",
+            "intent": "removed"
+        }
+        mgr.remote = Mock(return_value=Mock(return_value=_res))
+        mgr.remote.return_value.one.return_value.to_simplified = Mock(return_value=_res_simplified)
+        self._delete(f'{self._endpoint}/smbCluster1/share1')
+        self.assertStatus(204)
+        mgr.remote.assert_called_once_with('smb', 'apply_resources', json.dumps(_res_simplified))