]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/controllers/cephfs.py
1 # -*- coding: utf-8 -*-
2 from __future__
import absolute_import
4 from collections
import defaultdict
11 from . import ApiController
, RESTController
, UiApiController
13 from ..exceptions
import DashboardException
14 from ..security
import Scope
15 from ..services
.cephfs
import CephFS
as CephFS_
16 from ..services
.ceph_service
import CephService
17 from ..tools
import ViewCache
20 @ApiController('/cephfs', Scope
.CEPHFS
)
21 class CephFS(RESTController
):
23 super(CephFS
, self
).__init
__()
25 # Stateful instances of CephFSClients, hold cached results. Key to
27 self
.cephfs_clients
= {}
30 fsmap
= mgr
.get("fs_map")
31 return fsmap
['filesystems']
34 fs_id
= self
.fs_id_to_int(fs_id
)
35 return self
.fs_status(fs_id
)
37 @RESTController.Resource('GET')
38 def clients(self
, fs_id
):
39 fs_id
= self
.fs_id_to_int(fs_id
)
41 return self
._clients
(fs_id
)
43 @RESTController.Resource('DELETE', path
='/client/{client_id}')
44 def evict(self
, fs_id
, client_id
):
45 fs_id
= self
.fs_id_to_int(fs_id
)
46 client_id
= self
.client_id_to_int(client_id
)
48 return self
._evict
(fs_id
, client_id
)
50 @RESTController.Resource('GET')
51 def mds_counters(self
, fs_id
, counters
=None):
52 fs_id
= self
.fs_id_to_int(fs_id
)
53 return self
._mds
_counters
(fs_id
, counters
)
55 def _mds_counters(self
, fs_id
, counters
=None):
57 Result format: map of daemon name to map of counter to list of datapoints
58 rtype: dict[str, dict[str, list]]
62 # Opinionated list of interesting performance counters for the GUI
64 "mds_server.handle_client_request",
66 "mds_cache.num_strays",
68 "mds.exported_inodes",
70 "mds.imported_inodes",
77 result
= {} # type: dict
78 mds_names
= self
._get
_mds
_names
(fs_id
)
80 for mds_name
in mds_names
:
82 for counter
in counters
:
83 data
= mgr
.get_counter("mds", mds_name
, counter
)
85 result
[mds_name
][counter
] = data
[counter
]
87 result
[mds_name
][counter
] = []
92 def fs_id_to_int(fs_id
):
96 raise DashboardException(code
='invalid_cephfs_id',
97 msg
="Invalid cephfs ID {}".format(fs_id
),
101 def client_id_to_int(client_id
):
103 return int(client_id
)
105 raise DashboardException(code
='invalid_cephfs_client_id',
106 msg
="Invalid cephfs client ID {}".format(client_id
),
109 def _get_mds_names(self
, filesystem_id
=None):
112 fsmap
= mgr
.get("fs_map")
113 for fs
in fsmap
['filesystems']:
114 if filesystem_id
is not None and fs
['id'] != filesystem_id
:
116 names
.extend([info
['name']
117 for _
, info
in fs
['mdsmap']['info'].items()])
119 if filesystem_id
is None:
120 names
.extend(info
['name'] for info
in fsmap
['standbys'])
124 def _append_mds_metadata(self
, mds_versions
, metadata_key
):
125 metadata
= mgr
.get_metadata('mds', metadata_key
)
128 mds_versions
[metadata
.get('ceph_version', 'unknown')].append(metadata_key
)
130 # pylint: disable=too-many-statements,too-many-branches
131 def fs_status(self
, fs_id
):
132 mds_versions
= defaultdict(list) # type: dict
134 fsmap
= mgr
.get("fs_map")
136 for fs
in fsmap
['filesystems']:
137 if fs
['id'] == fs_id
:
141 if filesystem
is None:
142 raise cherrypy
.HTTPError(404,
143 "CephFS id {0} not found".format(fs_id
))
147 mdsmap
= filesystem
['mdsmap']
151 for rank
in mdsmap
["in"]:
152 up
= "mds_{0}".format(rank
) in mdsmap
["up"]
154 gid
= mdsmap
['up']["mds_{0}".format(rank
)]
155 info
= mdsmap
['info']['gid_{0}'.format(gid
)]
156 dns
= mgr
.get_latest("mds", info
['name'], "mds_mem.dn")
157 inos
= mgr
.get_latest("mds", info
['name'], "mds_mem.ino")
160 client_count
= mgr
.get_latest("mds", info
['name'],
161 "mds_sessions.session_count")
162 elif client_count
== 0:
163 # In case rank 0 was down, look at another rank's
164 # sessionmap to get an indication of clients.
165 client_count
= mgr
.get_latest("mds", info
['name'],
166 "mds_sessions.session_count")
168 laggy
= "laggy_since" in info
170 state
= info
['state'].split(":")[1]
174 # Populate based on context of state, e.g. client
175 # ops for an active daemon, replay progress, reconnect
177 if state
== "active":
178 activity
= CephService
.get_rate("mds",
180 "mds_server.handle_client_request")
184 self
._append
_mds
_metadata
(mds_versions
, info
['name'])
190 "activity": activity
,
208 # Find the standby replays
209 # pylint: disable=unused-variable
210 for gid_str
, daemon_info
in mdsmap
['info'].items():
211 if daemon_info
['state'] != "up:standby-replay":
214 inos
= mgr
.get_latest("mds", daemon_info
['name'], "mds_mem.ino")
215 dns
= mgr
.get_latest("mds", daemon_info
['name'], "mds_mem.dn")
217 activity
= CephService
.get_rate(
218 "mds", daemon_info
['name'], "mds_log.replay")
222 "rank": "{0}-s".format(daemon_info
['rank']),
223 "state": "standby-replay",
224 "mds": daemon_info
['name'],
225 "activity": activity
,
232 pool_stats
= {p
['id']: p
['stats'] for p
in df
['pools']}
233 osdmap
= mgr
.get("osd_map")
234 pools
= {p
['pool']: p
for p
in osdmap
['pools']}
235 metadata_pool_id
= mdsmap
['metadata_pool']
236 data_pool_ids
= mdsmap
['data_pools']
239 for pool_id
in [metadata_pool_id
] + data_pool_ids
:
240 pool_type
= "metadata" if pool_id
== metadata_pool_id
else "data"
241 stats
= pool_stats
[pool_id
]
243 "pool": pools
[pool_id
]['pool_name'],
245 "used": stats
['bytes_used'],
246 "avail": stats
['max_avail']
250 for standby
in fsmap
['standbys']:
251 self
._append
_mds
_metadata
(mds_versions
, standby
['name'])
252 standby_table
.append({
253 'name': standby
['name']
259 "name": mdsmap
['fs_name'],
260 "client_count": client_count
,
264 "standbys": standby_table
,
265 "versions": mds_versions
268 def _clients(self
, fs_id
):
269 cephfs_clients
= self
.cephfs_clients
.get(fs_id
, None)
270 if cephfs_clients
is None:
271 cephfs_clients
= CephFSClients(mgr
, fs_id
)
272 self
.cephfs_clients
[fs_id
] = cephfs_clients
275 status
, clients
= cephfs_clients
.get()
276 except AttributeError:
277 raise cherrypy
.HTTPError(404,
278 "No cephfs with id {0}".format(fs_id
))
281 raise cherrypy
.HTTPError(404,
282 "No cephfs with id {0}".format(fs_id
))
284 # Decorate the metadata with some fields that will be
285 # indepdendent of whether it's a kernel or userspace
286 # client, so that the javascript doesn't have to grok that.
287 for client
in clients
:
288 if "ceph_version" in client
['client_metadata']:
289 client
['type'] = "userspace"
290 client
['version'] = client
['client_metadata']['ceph_version']
291 client
['hostname'] = client
['client_metadata']['hostname']
292 elif "kernel_version" in client
['client_metadata']:
293 client
['type'] = "kernel"
294 client
['version'] = client
['client_metadata']['kernel_version']
295 client
['hostname'] = client
['client_metadata']['hostname']
297 client
['type'] = "unknown"
298 client
['version'] = ""
299 client
['hostname'] = ""
306 def _evict(self
, fs_id
, client_id
):
307 clients
= self
._clients
(fs_id
)
308 if not [c
for c
in clients
['data'] if c
['id'] == client_id
]:
309 raise cherrypy
.HTTPError(404,
310 "Client {0} does not exist in cephfs {1}".format(client_id
,
312 CephService
.send_command('mds', 'client evict',
313 srv_spec
='{0}:0'.format(fs_id
), id=client_id
)
316 def _cephfs_instance(fs_id
):
318 :param fs_id: The filesystem identifier.
319 :type fs_id: int | str
320 :return: A instance of the CephFS class.
322 fs_name
= CephFS_
.fs_name_from_id(fs_id
)
324 raise cherrypy
.HTTPError(404, "CephFS id {} not found".format(fs_id
))
325 return CephFS_(fs_name
)
327 @RESTController.Resource('GET')
328 def get_root_directory(self
, fs_id
):
330 The root directory that can't be fetched using ls_dir (api).
331 :param fs_id: The filesystem identifier.
332 :return: The root directory
336 return self
._get
_root
_directory
(self
._cephfs
_instance
(fs_id
))
337 except (cephfs
.PermissionError
, cephfs
.ObjectNotFound
):
340 def _get_root_directory(self
, cfs
):
342 The root directory that can't be fetched using ls_dir (api).
343 It's used in ls_dir (ui-api) and in get_root_directory (api).
344 :param cfs: CephFS service instance
346 :return: The root directory
349 return cfs
.get_directory(os
.sep
.encode())
351 @RESTController.Resource('GET')
352 def ls_dir(self
, fs_id
, path
=None, depth
=1):
354 List directories of specified path.
355 :param fs_id: The filesystem identifier.
356 :param path: The path where to start listing the directory content.
357 Defaults to '/' if not set.
358 :type path: str | bytes
359 :param depth: The number of steps to go down the directory tree.
360 :type depth: int | str
361 :return: The names of the directories below the specified path.
364 path
= self
._set
_ls
_dir
_path
(path
)
366 cfs
= self
._cephfs
_instance
(fs_id
)
367 paths
= cfs
.ls_dir(path
, depth
)
368 except (cephfs
.PermissionError
, cephfs
.ObjectNotFound
):
372 def _set_ls_dir_path(self
, path
):
374 Transforms input path parameter of ls_dir methods (api and ui-api).
375 :param path: The path where to start listing the directory content.
376 Defaults to '/' if not set.
377 :type path: str | bytes
378 :return: Normalized path or root path
384 path
= os
.path
.normpath(path
)
387 @RESTController.Resource('POST')
388 def mk_dirs(self
, fs_id
, path
):
391 :param fs_id: The filesystem identifier.
392 :param path: The path of the directory.
394 cfs
= self
._cephfs
_instance
(fs_id
)
397 @RESTController.Resource('POST')
398 def rm_dir(self
, fs_id
, path
):
401 :param fs_id: The filesystem identifier.
402 :param path: The path of the directory.
404 cfs
= self
._cephfs
_instance
(fs_id
)
407 @RESTController.Resource('POST')
408 def mk_snapshot(self
, fs_id
, path
, name
=None):
411 :param fs_id: The filesystem identifier.
412 :param path: The path of the directory.
413 :param name: The name of the snapshot. If not specified,
414 a name using the current time in RFC3339 UTC format
416 :return: The name of the snapshot.
419 cfs
= self
._cephfs
_instance
(fs_id
)
420 return cfs
.mk_snapshot(path
, name
)
422 @RESTController.Resource('POST')
423 def rm_snapshot(self
, fs_id
, path
, name
):
426 :param fs_id: The filesystem identifier.
427 :param path: The path of the directory.
428 :param name: The name of the snapshot.
430 cfs
= self
._cephfs
_instance
(fs_id
)
431 cfs
.rm_snapshot(path
, name
)
433 @RESTController.Resource('GET')
434 def get_quotas(self
, fs_id
, path
):
436 Get the quotas of the specified path.
437 :param fs_id: The filesystem identifier.
438 :param path: The path of the directory/file.
439 :return: Returns a dictionary containing 'max_bytes'
443 cfs
= self
._cephfs
_instance
(fs_id
)
444 return cfs
.get_quotas(path
)
446 @RESTController.Resource('POST')
447 def set_quotas(self
, fs_id
, path
, max_bytes
=None, max_files
=None):
449 Set the quotas of the specified path.
450 :param fs_id: The filesystem identifier.
451 :param path: The path of the directory/file.
452 :param max_bytes: The byte limit.
453 :param max_files: The file limit.
455 cfs
= self
._cephfs
_instance
(fs_id
)
456 return cfs
.set_quotas(path
, max_bytes
, max_files
)
459 class CephFSClients(object):
460 def __init__(self
, module_inst
, fscid
):
461 self
._module
= module_inst
466 return CephService
.send_command('mds', 'session ls', srv_spec
='{0}:0'.format(self
.fscid
))
469 @UiApiController('/cephfs', Scope
.CEPHFS
)
470 class CephFsUi(CephFS
):
471 RESOURCE_ID
= 'fs_id'
473 @RESTController.Resource('GET')
474 def tabs(self
, fs_id
):
476 fs_id
= self
.fs_id_to_int(fs_id
)
478 # Needed for detail tab
479 fs_status
= self
.fs_status(fs_id
)
480 for pool
in fs_status
['cephfs']['pools']:
481 pool
['size'] = pool
['used'] + pool
['avail']
482 data
['pools'] = fs_status
['cephfs']['pools']
483 data
['ranks'] = fs_status
['cephfs']['ranks']
484 data
['name'] = fs_status
['cephfs']['name']
485 data
['standbys'] = ', '.join([x
['name'] for x
in fs_status
['standbys']])
486 counters
= self
._mds
_counters
(fs_id
)
487 for k
, v
in counters
.items():
489 data
['mds_counters'] = counters
491 # Needed for client tab
492 data
['clients'] = self
._clients
(fs_id
)
496 @RESTController.Resource('GET')
497 def ls_dir(self
, fs_id
, path
=None, depth
=1):
499 The difference to the API version is that the root directory will be send when listing
501 To only do one request this endpoint was created.
502 :param fs_id: The filesystem identifier.
503 :type fs_id: int | str
504 :param path: The path where to start listing the directory content.
505 Defaults to '/' if not set.
506 :type path: str | bytes
507 :param depth: The number of steps to go down the directory tree.
508 :type depth: int | str
509 :return: The names of the directories below the specified path.
512 path
= self
._set
_ls
_dir
_path
(path
)
514 cfs
= self
._cephfs
_instance
(fs_id
)
515 paths
= cfs
.ls_dir(path
, depth
)
517 paths
= [self
._get
_root
_directory
(cfs
)] + paths
518 except (cephfs
.PermissionError
, cephfs
.ObjectNotFound
):