1 # -*- coding: utf-8 -*-
2 from __future__
import absolute_import
7 from functools
import partial
8 from typing
import Any
, Dict
, List
, Optional
11 from mgr_module
import NFS_GANESHA_SUPPORTED_FSALS
14 from ..security
import Scope
15 from ..services
.cephfs
import CephFS
16 from ..services
.exception
import DashboardException
, serialize_dashboard_exception
17 from . import APIDoc
, APIRouter
, BaseController
, Endpoint
, EndpointDoc
, \
18 ReadPermission
, RESTController
, Task
, UIRouter
19 from ._version
import APIVersion
21 logger
= logging
.getLogger('controllers.nfs')
24 class NFSException(DashboardException
):
25 def __init__(self
, msg
):
26 super(NFSException
, self
).__init
__(component
="nfs", msg
=msg
)
29 # documentation helpers
31 'export_id': (int, 'Export ID'),
32 'path': (str, 'Export path'),
33 'cluster_id': (str, 'Cluster identifier'),
34 'pseudo': (str, 'Pseudo FS path'),
35 'access_type': (str, 'Export access type'),
36 'squash': (str, 'Export squash policy'),
37 'security_label': (str, 'Security label'),
38 'protocols': ([int], 'List of protocol types'),
39 'transports': ([str], 'List of transport types'),
41 'name': (str, 'name of FSAL'),
42 'fs_name': (str, 'CephFS filesystem name', True),
43 'sec_label_xattr': (str, 'Name of xattr for security label', True),
44 'user_id': (str, 'User id', True)
45 }, 'FSAL configuration'),
47 'addresses': ([str], 'list of IP addresses'),
48 'access_type': (str, 'Client access type'),
49 'squash': (str, 'Client squash policy')
50 }], 'List of client configurations'),
54 CREATE_EXPORT_SCHEMA
= {
55 'path': (str, 'Export path'),
56 'cluster_id': (str, 'Cluster identifier'),
57 'pseudo': (str, 'Pseudo FS path'),
58 'access_type': (str, 'Export access type'),
59 'squash': (str, 'Export squash policy'),
60 'security_label': (str, 'Security label'),
61 'protocols': ([int], 'List of protocol types'),
62 'transports': ([str], 'List of transport types'),
64 'name': (str, 'name of FSAL'),
65 'fs_name': (str, 'CephFS filesystem name', True),
66 'sec_label_xattr': (str, 'Name of xattr for security label', True)
67 }, 'FSAL configuration'),
69 'addresses': ([str], 'list of IP addresses'),
70 'access_type': (str, 'Client access type'),
71 'squash': (str, 'Client squash policy')
72 }], 'List of client configurations')
76 # pylint: disable=not-callable
77 def NfsTask(name
, metadata
, wait_for
): # noqa: N802
78 def composed_decorator(func
):
79 return Task("nfs/{}".format(name
), metadata
, wait_for
,
80 partial(serialize_dashboard_exception
,
81 include_http_status
=True))(func
)
82 return composed_decorator
85 @APIRouter('/nfs-ganesha', Scope
.NFS_GANESHA
)
86 @APIDoc("NFS-Ganesha Cluster Management API", "NFS-Ganesha")
87 class NFSGanesha(RESTController
):
89 @EndpointDoc("Status of NFS-Ganesha management feature",
91 'available': (bool, "Is API available?"),
92 'message': (str, "Error message")
97 status
= {'available': True, 'message': None}
99 mgr
.remote('nfs', 'cluster_ls')
100 except ImportError as error
:
101 logger
.exception(error
)
102 status
['available'] = False
103 status
['message'] = str(error
) # type: ignore
108 @APIRouter('/nfs-ganesha/cluster', Scope
.NFS_GANESHA
)
109 @APIDoc(group
="NFS-Ganesha")
110 class NFSGaneshaCluster(RESTController
):
112 @RESTController.MethodMap(version
=APIVersion
.EXPERIMENTAL
)
114 return mgr
.remote('nfs', 'cluster_ls')
117 @APIRouter('/nfs-ganesha/export', Scope
.NFS_GANESHA
)
118 @APIDoc(group
="NFS-Ganesha")
119 class NFSGaneshaExports(RESTController
):
120 RESOURCE_ID
= "cluster_id/export_id"
123 def _get_schema_export(export
: Dict
[str, Any
]) -> Dict
[str, Any
]:
125 Method that avoids returning export info not exposed in the export schema
126 e.g., rgw user access/secret keys.
128 schema_fsal_info
= {}
129 for key
in export
['fsal'].keys():
130 if key
in EXPORT_SCHEMA
['fsal'][0].keys(): # type: ignore
131 schema_fsal_info
[key
] = export
['fsal'][key
]
132 export
['fsal'] = schema_fsal_info
135 @EndpointDoc("List all NFS-Ganesha exports",
136 responses
={200: [EXPORT_SCHEMA
]})
137 def list(self
) -> List
[Dict
[str, Any
]]:
139 for export
in mgr
.remote('nfs', 'export_ls'):
140 exports
.append(self
._get
_schema
_export
(export
))
144 @NfsTask('create', {'path': '{path}', 'fsal': '{fsal.name}',
145 'cluster_id': '{cluster_id}'}, 2.0)
146 @EndpointDoc("Creates a new NFS-Ganesha export",
147 parameters
=CREATE_EXPORT_SCHEMA
,
148 responses
={201: EXPORT_SCHEMA
})
149 @RESTController.MethodMap(version
=APIVersion(2, 0)) # type: ignore
150 def create(self
, path
, cluster_id
, pseudo
, access_type
,
151 squash
, security_label
, protocols
, transports
, fsal
, clients
) -> Dict
[str, Any
]:
152 export_mgr
= mgr
.remote('nfs', 'fetch_nfs_export_obj')
153 if export_mgr
.get_export_by_pseudo(cluster_id
, pseudo
):
154 raise DashboardException(msg
=f
'Pseudo {pseudo} is already in use.',
156 if hasattr(fsal
, 'user_id'):
157 fsal
.pop('user_id') # mgr/nfs does not let you customize user_id
161 'cluster_id': cluster_id
,
162 'access_type': access_type
,
164 'security_label': security_label
,
165 'protocols': protocols
,
166 'transports': transports
,
170 ret
, _
, err
= export_mgr
.apply_export(cluster_id
, json
.dumps(raw_ex
))
172 return self
._get
_schema
_export
(
173 export_mgr
.get_export_by_pseudo(cluster_id
, pseudo
))
174 raise NFSException(f
"Export creation failed {err}")
176 @EndpointDoc("Get an NFS-Ganesha export",
178 'cluster_id': (str, 'Cluster identifier'),
179 'export_id': (str, "Export ID")
181 responses
={200: EXPORT_SCHEMA
})
182 def get(self
, cluster_id
, export_id
) -> Optional
[Dict
[str, Any
]]:
183 export_id
= int(export_id
)
184 export
= mgr
.remote('nfs', 'export_get', cluster_id
, export_id
)
186 export
= self
._get
_schema
_export
(export
)
190 @NfsTask('edit', {'cluster_id': '{cluster_id}', 'export_id': '{export_id}'},
192 @EndpointDoc("Updates an NFS-Ganesha export",
193 parameters
=dict(export_id
=(int, "Export ID"),
194 **CREATE_EXPORT_SCHEMA
),
195 responses
={200: EXPORT_SCHEMA
})
196 @RESTController.MethodMap(version
=APIVersion(2, 0)) # type: ignore
197 def set(self
, cluster_id
, export_id
, path
, pseudo
, access_type
,
198 squash
, security_label
, protocols
, transports
, fsal
, clients
) -> Dict
[str, Any
]:
200 if hasattr(fsal
, 'user_id'):
201 fsal
.pop('user_id') # mgr/nfs does not let you customize user_id
205 'cluster_id': cluster_id
,
206 'export_id': export_id
,
207 'access_type': access_type
,
209 'security_label': security_label
,
210 'protocols': protocols
,
211 'transports': transports
,
216 export_mgr
= mgr
.remote('nfs', 'fetch_nfs_export_obj')
217 ret
, _
, err
= export_mgr
.apply_export(cluster_id
, json
.dumps(raw_ex
))
219 return self
._get
_schema
_export
(
220 export_mgr
.get_export_by_pseudo(cluster_id
, pseudo
))
221 raise NFSException(f
"Failed to update export: {err}")
223 @NfsTask('delete', {'cluster_id': '{cluster_id}',
224 'export_id': '{export_id}'}, 2.0)
225 @EndpointDoc("Deletes an NFS-Ganesha export",
227 'cluster_id': (str, 'Cluster identifier'),
228 'export_id': (int, "Export ID")
230 @RESTController.MethodMap(version
=APIVersion(2, 0)) # type: ignore
231 def delete(self
, cluster_id
, export_id
):
232 export_id
= int(export_id
)
234 export
= mgr
.remote('nfs', 'export_get', cluster_id
, export_id
)
236 raise DashboardException(
237 http_status_code
=404,
238 msg
=f
'Export with id {export_id} not found.',
240 mgr
.remote('nfs', 'export_rm', cluster_id
, export
['pseudo'])
243 @UIRouter('/nfs-ganesha', Scope
.NFS_GANESHA
)
244 class NFSGaneshaUi(BaseController
):
245 @Endpoint('GET', '/fsals')
248 return NFS_GANESHA_SUPPORTED_FSALS
250 @Endpoint('GET', '/lsdir')
252 def lsdir(self
, fs_name
, root_dir
=None, depth
=1): # pragma: no cover
255 if not root_dir
.startswith('/'):
256 root_dir
= '/{}'.format(root_dir
)
257 root_dir
= os
.path
.normpath(root_dir
)
263 error_msg
= '`depth` must be greater or equal to 0.'
265 logger
.warning("Limiting depth to maximum value of 5: "
266 "input depth=%s", depth
)
269 error_msg
= '`depth` must be an integer.'
272 raise DashboardException(code
=400,
277 cfs
= CephFS(fs_name
)
279 paths
.extend([p
['path'].rstrip('/')
280 for p
in cfs
.ls_dir(root_dir
, depth
)])
281 except (cephfs
.ObjectNotFound
, cephfs
.PermissionError
):
283 return {'paths': paths
}
285 @Endpoint('GET', '/cephfs/filesystems')
287 def filesystems(self
):
288 return CephFS
.list_filesystems()