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