]>
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
, ControllerDoc
, RESTController
, UiApiController
, \
14 from ..exceptions
import DashboardException
15 from ..security
import Scope
16 from ..services
.cephfs
import CephFS
as CephFS_
17 from ..services
.ceph_service
import CephService
18 from ..tools
import ViewCache
21 @ApiController('/cephfs', Scope
.CEPHFS
)
22 class CephFS(RESTController
):
23 def __init__(self
): # pragma: no cover
24 super(CephFS
, self
).__init
__()
26 # Stateful instances of CephFSClients, hold cached results. Key to
28 self
.cephfs_clients
= {}
31 fsmap
= mgr
.get("fs_map")
32 return fsmap
['filesystems']
35 fs_id
= self
.fs_id_to_int(fs_id
)
36 return self
.fs_status(fs_id
)
38 @RESTController.Resource('GET')
39 def clients(self
, fs_id
):
40 fs_id
= self
.fs_id_to_int(fs_id
)
42 return self
._clients
(fs_id
)
44 @RESTController.Resource('DELETE', path
='/client/{client_id}')
45 def evict(self
, fs_id
, client_id
):
46 fs_id
= self
.fs_id_to_int(fs_id
)
47 client_id
= self
.client_id_to_int(client_id
)
49 return self
._evict
(fs_id
, client_id
)
51 @RESTController.Resource('GET')
52 def mds_counters(self
, fs_id
, counters
=None):
53 fs_id
= self
.fs_id_to_int(fs_id
)
54 return self
._mds
_counters
(fs_id
, counters
)
56 def _mds_counters(self
, fs_id
, counters
=None):
58 Result format: map of daemon name to map of counter to list of datapoints
59 rtype: dict[str, dict[str, list]]
63 # Opinionated list of interesting performance counters for the GUI
65 "mds_server.handle_client_request",
67 "mds_cache.num_strays",
69 "mds.exported_inodes",
71 "mds.imported_inodes",
78 result
= {} # type: dict
79 mds_names
= self
._get
_mds
_names
(fs_id
)
81 for mds_name
in mds_names
:
83 for counter
in counters
:
84 data
= mgr
.get_counter("mds", mds_name
, counter
)
86 result
[mds_name
][counter
] = data
[counter
]
88 result
[mds_name
][counter
] = []
93 def fs_id_to_int(fs_id
):
97 raise DashboardException(code
='invalid_cephfs_id',
98 msg
="Invalid cephfs ID {}".format(fs_id
),
102 def client_id_to_int(client_id
):
104 return int(client_id
)
106 raise DashboardException(code
='invalid_cephfs_client_id',
107 msg
="Invalid cephfs client ID {}".format(client_id
),
110 def _get_mds_names(self
, filesystem_id
=None):
113 fsmap
= mgr
.get("fs_map")
114 for fs
in fsmap
['filesystems']:
115 if filesystem_id
is not None and fs
['id'] != filesystem_id
:
117 names
.extend([info
['name']
118 for _
, info
in fs
['mdsmap']['info'].items()])
120 if filesystem_id
is None:
121 names
.extend(info
['name'] for info
in fsmap
['standbys'])
125 def _append_mds_metadata(self
, mds_versions
, metadata_key
):
126 metadata
= mgr
.get_metadata('mds', metadata_key
)
129 mds_versions
[metadata
.get('ceph_version', 'unknown')].append(metadata_key
)
131 # pylint: disable=too-many-statements,too-many-branches
132 def fs_status(self
, fs_id
):
133 mds_versions
= defaultdict(list) # type: dict
135 fsmap
= mgr
.get("fs_map")
137 for fs
in fsmap
['filesystems']:
138 if fs
['id'] == fs_id
:
142 if filesystem
is None:
143 raise cherrypy
.HTTPError(404,
144 "CephFS id {0} not found".format(fs_id
))
148 mdsmap
= filesystem
['mdsmap']
152 for rank
in mdsmap
["in"]:
153 up
= "mds_{0}".format(rank
) in mdsmap
["up"]
155 gid
= mdsmap
['up']["mds_{0}".format(rank
)]
156 info
= mdsmap
['info']['gid_{0}'.format(gid
)]
157 dns
= mgr
.get_latest("mds", info
['name'], "mds_mem.dn")
158 inos
= mgr
.get_latest("mds", info
['name'], "mds_mem.ino")
161 client_count
= mgr
.get_latest("mds", info
['name'],
162 "mds_sessions.session_count")
163 elif client_count
== 0:
164 # In case rank 0 was down, look at another rank's
165 # sessionmap to get an indication of clients.
166 client_count
= mgr
.get_latest("mds", info
['name'],
167 "mds_sessions.session_count")
169 laggy
= "laggy_since" in info
171 state
= info
['state'].split(":")[1]
175 # Populate based on context of state, e.g. client
176 # ops for an active daemon, replay progress, reconnect
178 if state
== "active":
179 activity
= CephService
.get_rate("mds",
181 "mds_server.handle_client_request")
183 activity
= 0.0 # pragma: no cover
185 self
._append
_mds
_metadata
(mds_versions
, info
['name'])
191 "activity": activity
,
209 # Find the standby replays
210 # pylint: disable=unused-variable
211 for gid_str
, daemon_info
in mdsmap
['info'].items():
212 if daemon_info
['state'] != "up:standby-replay":
215 inos
= mgr
.get_latest("mds", daemon_info
['name'], "mds_mem.ino")
216 dns
= mgr
.get_latest("mds", daemon_info
['name'], "mds_mem.dn")
218 activity
= CephService
.get_rate(
219 "mds", daemon_info
['name'], "mds_log.replay")
223 "rank": "{0}-s".format(daemon_info
['rank']),
224 "state": "standby-replay",
225 "mds": daemon_info
['name'],
226 "activity": activity
,
233 pool_stats
= {p
['id']: p
['stats'] for p
in df
['pools']}
234 osdmap
= mgr
.get("osd_map")
235 pools
= {p
['pool']: p
for p
in osdmap
['pools']}
236 metadata_pool_id
= mdsmap
['metadata_pool']
237 data_pool_ids
= mdsmap
['data_pools']
240 for pool_id
in [metadata_pool_id
] + data_pool_ids
:
241 pool_type
= "metadata" if pool_id
== metadata_pool_id
else "data"
242 stats
= pool_stats
[pool_id
]
244 "pool": pools
[pool_id
]['pool_name'],
246 "used": stats
['bytes_used'],
247 "avail": stats
['max_avail']
251 for standby
in fsmap
['standbys']:
252 self
._append
_mds
_metadata
(mds_versions
, standby
['name'])
253 standby_table
.append({
254 'name': standby
['name']
260 "name": mdsmap
['fs_name'],
261 "client_count": client_count
,
265 "standbys": standby_table
,
266 "versions": mds_versions
269 def _clients(self
, fs_id
):
270 cephfs_clients
= self
.cephfs_clients
.get(fs_id
, None)
271 if cephfs_clients
is None:
272 cephfs_clients
= CephFSClients(mgr
, fs_id
)
273 self
.cephfs_clients
[fs_id
] = cephfs_clients
276 status
, clients
= cephfs_clients
.get()
277 except AttributeError:
278 raise cherrypy
.HTTPError(404,
279 "No cephfs with id {0}".format(fs_id
))
282 raise cherrypy
.HTTPError(404,
283 "No cephfs with id {0}".format(fs_id
))
285 # Decorate the metadata with some fields that will be
286 # indepdendent of whether it's a kernel or userspace
287 # client, so that the javascript doesn't have to grok that.
288 for client
in clients
:
289 if "ceph_version" in client
['client_metadata']: # pragma: no cover - no complexity
290 client
['type'] = "userspace"
291 client
['version'] = client
['client_metadata']['ceph_version']
292 client
['hostname'] = client
['client_metadata']['hostname']
293 client
['root'] = client
['client_metadata']['root']
294 elif "kernel_version" in client
['client_metadata']: # pragma: no cover - no complexity
295 client
['type'] = "kernel"
296 client
['version'] = client
['client_metadata']['kernel_version']
297 client
['hostname'] = client
['client_metadata']['hostname']
298 client
['root'] = client
['client_metadata']['root']
299 else: # pragma: no cover - no complexity there
300 client
['type'] = "unknown"
301 client
['version'] = ""
302 client
['hostname'] = ""
309 def _evict(self
, fs_id
, client_id
):
310 clients
= self
._clients
(fs_id
)
311 if not [c
for c
in clients
['data'] if c
['id'] == client_id
]:
312 raise cherrypy
.HTTPError(404,
313 "Client {0} does not exist in cephfs {1}".format(client_id
,
315 CephService
.send_command('mds', 'client evict',
316 srv_spec
='{0}:0'.format(fs_id
), id=client_id
)
319 def _cephfs_instance(fs_id
):
321 :param fs_id: The filesystem identifier.
322 :type fs_id: int | str
323 :return: A instance of the CephFS class.
325 fs_name
= CephFS_
.fs_name_from_id(fs_id
)
327 raise cherrypy
.HTTPError(404, "CephFS id {} not found".format(fs_id
))
328 return CephFS_(fs_name
)
330 @RESTController.Resource('GET')
331 def get_root_directory(self
, fs_id
):
333 The root directory that can't be fetched using ls_dir (api).
334 :param fs_id: The filesystem identifier.
335 :return: The root directory
339 return self
._get
_root
_directory
(self
._cephfs
_instance
(fs_id
))
340 except (cephfs
.PermissionError
, cephfs
.ObjectNotFound
): # pragma: no cover
343 def _get_root_directory(self
, cfs
):
345 The root directory that can't be fetched using ls_dir (api).
346 It's used in ls_dir (ui-api) and in get_root_directory (api).
347 :param cfs: CephFS service instance
349 :return: The root directory
352 return cfs
.get_directory(os
.sep
.encode())
354 @RESTController.Resource('GET')
355 def ls_dir(self
, fs_id
, path
=None, depth
=1):
357 List directories of specified path.
358 :param fs_id: The filesystem identifier.
359 :param path: The path where to start listing the directory content.
360 Defaults to '/' if not set.
361 :type path: str | bytes
362 :param depth: The number of steps to go down the directory tree.
363 :type depth: int | str
364 :return: The names of the directories below the specified path.
367 path
= self
._set
_ls
_dir
_path
(path
)
369 cfs
= self
._cephfs
_instance
(fs_id
)
370 paths
= cfs
.ls_dir(path
, depth
)
371 except (cephfs
.PermissionError
, cephfs
.ObjectNotFound
): # pragma: no cover
375 def _set_ls_dir_path(self
, path
):
377 Transforms input path parameter of ls_dir methods (api and ui-api).
378 :param path: The path where to start listing the directory content.
379 Defaults to '/' if not set.
380 :type path: str | bytes
381 :return: Normalized path or root path
387 path
= os
.path
.normpath(path
)
390 @RESTController.Resource('POST')
392 def mk_dirs(self
, fs_id
, path
):
395 :param fs_id: The filesystem identifier.
396 :param path: The path of the directory.
398 cfs
= self
._cephfs
_instance
(fs_id
)
401 @RESTController.Resource('POST')
403 def rm_dir(self
, fs_id
, path
):
406 :param fs_id: The filesystem identifier.
407 :param path: The path of the directory.
409 cfs
= self
._cephfs
_instance
(fs_id
)
412 @RESTController.Resource('POST')
414 def mk_snapshot(self
, fs_id
, path
, name
=None):
417 :param fs_id: The filesystem identifier.
418 :param path: The path of the directory.
419 :param name: The name of the snapshot. If not specified,
420 a name using the current time in RFC3339 UTC format
422 :return: The name of the snapshot.
425 cfs
= self
._cephfs
_instance
(fs_id
)
426 return cfs
.mk_snapshot(path
, name
)
428 @RESTController.Resource('POST')
430 def rm_snapshot(self
, fs_id
, path
, name
):
433 :param fs_id: The filesystem identifier.
434 :param path: The path of the directory.
435 :param name: The name of the snapshot.
437 cfs
= self
._cephfs
_instance
(fs_id
)
438 cfs
.rm_snapshot(path
, name
)
440 @RESTController.Resource('GET')
441 def get_quotas(self
, fs_id
, path
):
443 Get the quotas of the specified path.
444 :param fs_id: The filesystem identifier.
445 :param path: The path of the directory/file.
446 :return: Returns a dictionary containing 'max_bytes'
450 cfs
= self
._cephfs
_instance
(fs_id
)
451 return cfs
.get_quotas(path
)
453 @RESTController.Resource('POST')
455 def set_quotas(self
, fs_id
, path
, max_bytes
=None, max_files
=None):
457 Set the quotas of the specified path.
458 :param fs_id: The filesystem identifier.
459 :param path: The path of the directory/file.
460 :param max_bytes: The byte limit.
461 :param max_files: The file limit.
463 cfs
= self
._cephfs
_instance
(fs_id
)
464 return cfs
.set_quotas(path
, max_bytes
, max_files
)
467 class CephFSClients(object):
468 def __init__(self
, module_inst
, fscid
):
469 self
._module
= module_inst
474 return CephService
.send_command('mds', 'session ls', srv_spec
='{0}:0'.format(self
.fscid
))
477 @UiApiController('/cephfs', Scope
.CEPHFS
)
478 @ControllerDoc("Dashboard UI helper function; not part of the public API", "CephFSUi")
479 class CephFsUi(CephFS
):
480 RESOURCE_ID
= 'fs_id'
482 @RESTController.Resource('GET')
483 def tabs(self
, fs_id
):
485 fs_id
= self
.fs_id_to_int(fs_id
)
487 # Needed for detail tab
488 fs_status
= self
.fs_status(fs_id
)
489 for pool
in fs_status
['cephfs']['pools']:
490 pool
['size'] = pool
['used'] + pool
['avail']
491 data
['pools'] = fs_status
['cephfs']['pools']
492 data
['ranks'] = fs_status
['cephfs']['ranks']
493 data
['name'] = fs_status
['cephfs']['name']
494 data
['standbys'] = ', '.join([x
['name'] for x
in fs_status
['standbys']])
495 counters
= self
._mds
_counters
(fs_id
)
496 for k
, v
in counters
.items():
498 data
['mds_counters'] = counters
500 # Needed for client tab
501 data
['clients'] = self
._clients
(fs_id
)
505 @RESTController.Resource('GET')
506 def ls_dir(self
, fs_id
, path
=None, depth
=1):
508 The difference to the API version is that the root directory will be send when listing
510 To only do one request this endpoint was created.
511 :param fs_id: The filesystem identifier.
512 :type fs_id: int | str
513 :param path: The path where to start listing the directory content.
514 Defaults to '/' if not set.
515 :type path: str | bytes
516 :param depth: The number of steps to go down the directory tree.
517 :type depth: int | str
518 :return: The names of the directories below the specified path.
521 path
= self
._set
_ls
_dir
_path
(path
)
523 cfs
= self
._cephfs
_instance
(fs_id
)
524 paths
= cfs
.ls_dir(path
, depth
)
526 paths
= [self
._get
_root
_directory
(cfs
)] + paths
527 except (cephfs
.PermissionError
, cephfs
.ObjectNotFound
): # pragma: no cover