]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/controllers/nfsganesha.py
18a2a3bf029c60b09f397a1fa80aaa94db211228
[ceph.git] / ceph / src / pybind / mgr / dashboard / controllers / nfsganesha.py
1 # -*- coding: utf-8 -*-
2 from __future__ import absolute_import
3
4 from functools import partial
5 import logging
6
7 import cherrypy
8 import cephfs
9
10 from . import ApiController, RESTController, UiApiController, BaseController, \
11 Endpoint, Task, ReadPermission, ControllerDoc, EndpointDoc
12 from ..security import Scope
13 from ..services.cephfs import CephFS
14 from ..services.cephx import CephX
15 from ..services.exception import serialize_dashboard_exception
16 from ..services.ganesha import Ganesha, GaneshaConf, NFSException
17 from ..services.rgw_client import RgwClient
18
19
20 logger = logging.getLogger('controllers.ganesha')
21
22
23 # documentation helpers
24 EXPORT_SCHEMA = {
25 'export_id': (int, 'Export ID'),
26 'path': (str, 'Export path'),
27 'cluster_id': (str, 'Cluster identifier'),
28 'daemons': ([str], 'List of NFS Ganesha daemons identifiers'),
29 'pseudo': (str, 'Pseudo FS path'),
30 'tag': (str, 'NFSv3 export tag'),
31 'access_type': (str, 'Export access type'),
32 'squash': (str, 'Export squash policy'),
33 'security_label': (str, 'Security label'),
34 'protocols': ([int], 'List of protocol types'),
35 'transports': ([str], 'List of transport types'),
36 'fsal': ({
37 'name': (str, 'name of FSAL'),
38 'user_id': (str, 'CephX user id', True),
39 'filesystem': (str, 'CephFS filesystem ID', True),
40 'sec_label_xattr': (str, 'Name of xattr for security label', True),
41 'rgw_user_id': (str, 'RGW user id', True)
42 }, 'FSAL configuration'),
43 'clients': ([{
44 'addresses': ([str], 'list of IP addresses'),
45 'access_type': (str, 'Client access type'),
46 'squash': (str, 'Client squash policy')
47 }], 'List of client configurations'),
48 }
49
50
51 CREATE_EXPORT_SCHEMA = {
52 'path': (str, 'Export path'),
53 'cluster_id': (str, 'Cluster identifier'),
54 'daemons': ([str], 'List of NFS Ganesha daemons identifiers'),
55 'pseudo': (str, 'Pseudo FS path'),
56 'tag': (str, 'NFSv3 export tag'),
57 'access_type': (str, 'Export access type'),
58 'squash': (str, 'Export squash policy'),
59 'security_label': (str, 'Security label'),
60 'protocols': ([int], 'List of protocol types'),
61 'transports': ([str], 'List of transport types'),
62 'fsal': ({
63 'name': (str, 'name of FSAL'),
64 'user_id': (str, 'CephX user id', True),
65 'filesystem': (str, 'CephFS filesystem ID', True),
66 'sec_label_xattr': (str, 'Name of xattr for security label', True),
67 'rgw_user_id': (str, 'RGW user id', True)
68 }, 'FSAL configuration'),
69 'clients': ([{
70 'addresses': ([str], 'list of IP addresses'),
71 'access_type': (str, 'Client access type'),
72 'squash': (str, 'Client squash policy')
73 }], 'List of client configurations'),
74 'reload_daemons': (bool,
75 'Trigger reload of NFS-Ganesha daemons configuration',
76 True)
77 }
78
79
80 # pylint: disable=not-callable
81 def NfsTask(name, metadata, wait_for): # noqa: N802
82 def composed_decorator(func):
83 return Task("nfs/{}".format(name), metadata, wait_for,
84 partial(serialize_dashboard_exception,
85 include_http_status=True))(func)
86 return composed_decorator
87
88
89 @ApiController('/nfs-ganesha', Scope.NFS_GANESHA)
90 @ControllerDoc("NFS-Ganesha Management API", "NFS-Ganesha")
91 class NFSGanesha(RESTController):
92
93 @EndpointDoc("Status of NFS-Ganesha management feature",
94 responses={200: {
95 'available': (bool, "Is API available?"),
96 'message': (str, "Error message")
97 }})
98 @Endpoint()
99 @ReadPermission
100 def status(self):
101 status = {'available': True, 'message': None}
102 try:
103 Ganesha.get_ganesha_clusters()
104 except NFSException as e:
105 status['message'] = str(e) # type: ignore
106 status['available'] = False
107
108 return status
109
110
111 @ApiController('/nfs-ganesha/export', Scope.NFS_GANESHA)
112 @ControllerDoc(group="NFS-Ganesha")
113 class NFSGaneshaExports(RESTController):
114 RESOURCE_ID = "cluster_id/export_id"
115
116 @EndpointDoc("List all NFS-Ganesha exports",
117 responses={200: [EXPORT_SCHEMA]})
118 def list(self):
119 result = []
120 for cluster_id in Ganesha.get_ganesha_clusters():
121 result.extend(
122 [export.to_dict()
123 for export in GaneshaConf.instance(cluster_id).list_exports()])
124 return result
125
126 @NfsTask('create', {'path': '{path}', 'fsal': '{fsal.name}',
127 'cluster_id': '{cluster_id}'}, 2.0)
128 @EndpointDoc("Creates a new NFS-Ganesha export",
129 parameters=CREATE_EXPORT_SCHEMA,
130 responses={201: EXPORT_SCHEMA})
131 def create(self, path, cluster_id, daemons, pseudo, tag, access_type,
132 squash, security_label, protocols, transports, fsal, clients,
133 reload_daemons=True):
134 if fsal['name'] not in Ganesha.fsals_available():
135 raise NFSException("Cannot create this export. "
136 "FSAL '{}' cannot be managed by the dashboard."
137 .format(fsal['name']))
138
139 ganesha_conf = GaneshaConf.instance(cluster_id)
140 ex_id = ganesha_conf.create_export({
141 'path': path,
142 'pseudo': pseudo,
143 'cluster_id': cluster_id,
144 'daemons': daemons,
145 'tag': tag,
146 'access_type': access_type,
147 'squash': squash,
148 'security_label': security_label,
149 'protocols': protocols,
150 'transports': transports,
151 'fsal': fsal,
152 'clients': clients
153 })
154 if reload_daemons:
155 ganesha_conf.reload_daemons(daemons)
156 return ganesha_conf.get_export(ex_id).to_dict()
157
158 @EndpointDoc("Get an NFS-Ganesha export",
159 parameters={
160 'cluster_id': (str, 'Cluster identifier'),
161 'export_id': (int, "Export ID")
162 },
163 responses={200: EXPORT_SCHEMA})
164 def get(self, cluster_id, export_id):
165 export_id = int(export_id)
166 ganesha_conf = GaneshaConf.instance(cluster_id)
167 if not ganesha_conf.has_export(export_id):
168 raise cherrypy.HTTPError(404)
169 return ganesha_conf.get_export(export_id).to_dict()
170
171 @NfsTask('edit', {'cluster_id': '{cluster_id}', 'export_id': '{export_id}'},
172 2.0)
173 @EndpointDoc("Updates an NFS-Ganesha export",
174 parameters=dict(export_id=(int, "Export ID"),
175 **CREATE_EXPORT_SCHEMA),
176 responses={200: EXPORT_SCHEMA})
177 def set(self, cluster_id, export_id, path, daemons, pseudo, tag, access_type,
178 squash, security_label, protocols, transports, fsal, clients,
179 reload_daemons=True):
180 export_id = int(export_id)
181 ganesha_conf = GaneshaConf.instance(cluster_id)
182
183 if not ganesha_conf.has_export(export_id):
184 raise cherrypy.HTTPError(404)
185
186 if fsal['name'] not in Ganesha.fsals_available():
187 raise NFSException("Cannot make modifications to this export. "
188 "FSAL '{}' cannot be managed by the dashboard."
189 .format(fsal['name']))
190
191 old_export = ganesha_conf.update_export({
192 'export_id': export_id,
193 'path': path,
194 'cluster_id': cluster_id,
195 'daemons': daemons,
196 'pseudo': pseudo,
197 'tag': tag,
198 'access_type': access_type,
199 'squash': squash,
200 'security_label': security_label,
201 'protocols': protocols,
202 'transports': transports,
203 'fsal': fsal,
204 'clients': clients
205 })
206 daemons = list(daemons)
207 for d_id in old_export.daemons:
208 if d_id not in daemons:
209 daemons.append(d_id)
210 if reload_daemons:
211 ganesha_conf.reload_daemons(daemons)
212 return ganesha_conf.get_export(export_id).to_dict()
213
214 @NfsTask('delete', {'cluster_id': '{cluster_id}',
215 'export_id': '{export_id}'}, 2.0)
216 @EndpointDoc("Deletes an NFS-Ganesha export",
217 parameters={
218 'cluster_id': (str, 'Cluster identifier'),
219 'export_id': (int, "Export ID"),
220 'reload_daemons': (bool,
221 'Trigger reload of NFS-Ganesha daemons'
222 ' configuration',
223 True)
224 })
225 def delete(self, cluster_id, export_id, reload_daemons=True):
226 export_id = int(export_id)
227 ganesha_conf = GaneshaConf.instance(cluster_id)
228
229 if not ganesha_conf.has_export(export_id):
230 raise cherrypy.HTTPError(404)
231
232 export = ganesha_conf.remove_export(export_id)
233 if reload_daemons:
234 ganesha_conf.reload_daemons(export.daemons)
235
236
237 @ApiController('/nfs-ganesha/daemon')
238 @ControllerDoc(group="NFS-Ganesha")
239 class NFSGaneshaService(RESTController):
240
241 @EndpointDoc("List NFS-Ganesha daemons information",
242 responses={200: [{
243 'daemon_id': (str, 'Daemon identifier'),
244 'cluster_id': (str, 'Cluster identifier'),
245 'status': (int,
246 'Status of daemon (1=RUNNING, 0=STOPPED, -1=ERROR',
247 True),
248 'desc': (str, 'Error description (if status==-1)', True)
249 }]})
250 def list(self):
251 status_dict = Ganesha.get_daemons_status()
252 if status_dict:
253 return [
254 {
255 'daemon_id': daemon_id,
256 'cluster_id': cluster_id,
257 'status': status_dict[cluster_id][daemon_id]['status'],
258 'desc': status_dict[cluster_id][daemon_id]['desc']
259 }
260 for cluster_id in status_dict
261 for daemon_id in status_dict[cluster_id]
262 ]
263
264 result = []
265 for cluster_id in Ganesha.get_ganesha_clusters():
266 result.extend(
267 [{'daemon_id': daemon_id, 'cluster_id': cluster_id}
268 for daemon_id in GaneshaConf.instance(cluster_id).list_daemons()])
269 return result
270
271
272 @UiApiController('/nfs-ganesha')
273 class NFSGaneshaUi(BaseController):
274 @Endpoint('GET', '/cephx/clients')
275 def cephx_clients(self):
276 return [client for client in CephX.list_clients()]
277
278 @Endpoint('GET', '/fsals')
279 def fsals(self):
280 return Ganesha.fsals_available()
281
282 @Endpoint('GET', '/lsdir')
283 def lsdir(self, root_dir=None, depth=1):
284 if root_dir is None:
285 root_dir = "/"
286 depth = int(depth)
287 if depth > 5:
288 logger.warning("Limiting depth to maximum value of 5: "
289 "input depth=%s", depth)
290 depth = 5
291 root_dir = '{}{}'.format(root_dir.rstrip('/'), '/')
292 try:
293 cfs = CephFS()
294 root_dir = root_dir.encode()
295 paths = cfs.ls_dir(root_dir, depth)
296 # Convert (bytes => string) and prettify paths (strip slashes).
297 paths = [p.decode().rstrip('/') for p in paths if p != root_dir]
298 except (cephfs.ObjectNotFound, cephfs.PermissionError):
299 paths = []
300 return {'paths': paths}
301
302 @Endpoint('GET', '/cephfs/filesystems')
303 def filesystems(self):
304 return CephFS.list_filesystems()
305
306 @Endpoint('GET', '/rgw/buckets')
307 def buckets(self, user_id=None):
308 return RgwClient.instance(user_id).get_buckets()
309
310 @Endpoint('GET', '/clusters')
311 def clusters(self):
312 return Ganesha.get_ganesha_clusters()