1 # -*- coding: utf-8 -*-
2 from __future__
import absolute_import
4 from functools
import partial
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
20 logger
= logging
.getLogger('controllers.ganesha')
23 # documentation helpers
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'),
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'),
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'),
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'),
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'),
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',
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
89 @ApiController('/nfs-ganesha', Scope
.NFS_GANESHA
)
90 @ControllerDoc("NFS-Ganesha Management API", "NFS-Ganesha")
91 class NFSGanesha(RESTController
):
93 @EndpointDoc("Status of NFS-Ganesha management feature",
95 'available': (bool, "Is API available?"),
96 'message': (str, "Error message")
101 status
= {'available': True, 'message': None}
103 Ganesha
.get_ganesha_clusters()
104 except NFSException
as e
:
105 status
['message'] = str(e
) # type: ignore
106 status
['available'] = False
111 @ApiController('/nfs-ganesha/export', Scope
.NFS_GANESHA
)
112 @ControllerDoc(group
="NFS-Ganesha")
113 class NFSGaneshaExports(RESTController
):
114 RESOURCE_ID
= "cluster_id/export_id"
116 @EndpointDoc("List all NFS-Ganesha exports",
117 responses
={200: [EXPORT_SCHEMA
]})
120 for cluster_id
in Ganesha
.get_ganesha_clusters():
123 for export
in GaneshaConf
.instance(cluster_id
).list_exports()])
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']))
139 ganesha_conf
= GaneshaConf
.instance(cluster_id
)
140 ex_id
= ganesha_conf
.create_export({
143 'cluster_id': cluster_id
,
146 'access_type': access_type
,
148 'security_label': security_label
,
149 'protocols': protocols
,
150 'transports': transports
,
155 ganesha_conf
.reload_daemons(daemons
)
156 return ganesha_conf
.get_export(ex_id
).to_dict()
158 @EndpointDoc("Get an NFS-Ganesha export",
160 'cluster_id': (str, 'Cluster identifier'),
161 'export_id': (int, "Export ID")
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()
171 @NfsTask('edit', {'cluster_id': '{cluster_id}', 'export_id': '{export_id}'},
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
)
183 if not ganesha_conf
.has_export(export_id
):
184 raise cherrypy
.HTTPError(404)
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']))
191 old_export
= ganesha_conf
.update_export({
192 'export_id': export_id
,
194 'cluster_id': cluster_id
,
198 'access_type': access_type
,
200 'security_label': security_label
,
201 'protocols': protocols
,
202 'transports': transports
,
206 daemons
= list(daemons
)
207 for d_id
in old_export
.daemons
:
208 if d_id
not in daemons
:
211 ganesha_conf
.reload_daemons(daemons
)
212 return ganesha_conf
.get_export(export_id
).to_dict()
214 @NfsTask('delete', {'cluster_id': '{cluster_id}',
215 'export_id': '{export_id}'}, 2.0)
216 @EndpointDoc("Deletes an NFS-Ganesha export",
218 'cluster_id': (str, 'Cluster identifier'),
219 'export_id': (int, "Export ID"),
220 'reload_daemons': (bool,
221 'Trigger reload of NFS-Ganesha daemons'
225 def delete(self
, cluster_id
, export_id
, reload_daemons
=True):
226 export_id
= int(export_id
)
227 ganesha_conf
= GaneshaConf
.instance(cluster_id
)
229 if not ganesha_conf
.has_export(export_id
):
230 raise cherrypy
.HTTPError(404)
232 export
= ganesha_conf
.remove_export(export_id
)
234 ganesha_conf
.reload_daemons(export
.daemons
)
237 @ApiController('/nfs-ganesha/daemon')
238 @ControllerDoc(group
="NFS-Ganesha")
239 class NFSGaneshaService(RESTController
):
241 @EndpointDoc("List NFS-Ganesha daemons information",
243 'daemon_id': (str, 'Daemon identifier'),
244 'cluster_id': (str, 'Cluster identifier'),
246 'Status of daemon (1=RUNNING, 0=STOPPED, -1=ERROR',
248 'desc': (str, 'Error description (if status==-1)', True)
251 status_dict
= Ganesha
.get_daemons_status()
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']
260 for cluster_id
in status_dict
261 for daemon_id
in status_dict
[cluster_id
]
265 for cluster_id
in Ganesha
.get_ganesha_clusters():
267 [{'daemon_id': daemon_id
, 'cluster_id': cluster_id
}
268 for daemon_id
in GaneshaConf
.instance(cluster_id
).list_daemons()])
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()]
278 @Endpoint('GET', '/fsals')
280 return Ganesha
.fsals_available()
282 @Endpoint('GET', '/lsdir')
283 def lsdir(self
, root_dir
=None, depth
=1):
288 logger
.warning("Limiting depth to maximum value of 5: "
289 "input depth=%s", depth
)
291 root_dir
= '{}{}'.format(root_dir
.rstrip('/'), '/')
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
):
300 return {'paths': paths
}
302 @Endpoint('GET', '/cephfs/filesystems')
303 def filesystems(self
):
304 return CephFS
.list_filesystems()
306 @Endpoint('GET', '/rgw/buckets')
307 def buckets(self
, user_id
=None):
308 return RgwClient
.instance(user_id
).get_buckets()
310 @Endpoint('GET', '/clusters')
312 return Ganesha
.get_ganesha_clusters()