# pylint: disable=too-many-lines
import errno
import json
-import logging
import os
from collections import defaultdict
from typing import Any, Dict, List
'subdirs': (int, '')
}
-logger = logging.getLogger("controllers.rgw")
-
# pylint: disable=R0904
@APIRouter('/cephfs', Scope.CEPHFS)
--- /dev/null
+
+# -*- 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()
- 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: []
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
DASHBOARD_SETTINGS = "dashboard-settings"
NFS_GANESHA = "nfs-ganesha"
NVME_OF = "nvme-of"
+ SMB = "smb"
@classmethod
def all_scopes(cls):
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]
})
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
--- /dev/null
+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))