]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/services/cephfs.py
import quincy beta 17.1.0
[ceph.git] / ceph / src / pybind / mgr / dashboard / services / cephfs.py
CommitLineData
11fdf7f2 1# -*- coding: utf-8 -*-
11fdf7f2 2
9f95a23c 3import datetime
f67539c2 4import logging
9f95a23c 5import os
f67539c2
TL
6from contextlib import contextmanager
7
11fdf7f2
TL
8import cephfs
9
9f95a23c
TL
10from .. import mgr
11
9f95a23c 12logger = logging.getLogger('cephfs')
11fdf7f2
TL
13
14
15class CephFS(object):
16 @classmethod
17 def list_filesystems(cls):
18 fsmap = mgr.get("fs_map")
19 return [{'id': fs['id'], 'name': fs['mdsmap']['fs_name']}
20 for fs in fsmap['filesystems']]
21
9f95a23c
TL
22 @classmethod
23 def fs_name_from_id(cls, fs_id):
24 """
25 Get the filesystem name from ID.
26 :param fs_id: The filesystem ID.
27 :type fs_id: int | str
28 :return: The filesystem name or None.
29 :rtype: str | None
30 """
31 fs_map = mgr.get("fs_map")
32 fs_info = list(filter(lambda x: str(x['id']) == str(fs_id),
33 fs_map['filesystems']))
34 if not fs_info:
35 return None
36 return fs_info[0]['mdsmap']['fs_name']
37
11fdf7f2 38 def __init__(self, fs_name=None):
9f95a23c 39 logger.debug("initializing cephfs connection")
11fdf7f2 40 self.cfs = cephfs.LibCephFS(rados_inst=mgr.rados)
9f95a23c 41 logger.debug("mounting cephfs filesystem: %s", fs_name)
11fdf7f2
TL
42 if fs_name:
43 self.cfs.mount(filesystem_name=fs_name)
44 else:
45 self.cfs.mount()
9f95a23c 46 logger.debug("mounted cephfs filesystem")
11fdf7f2
TL
47
48 def __del__(self):
9f95a23c 49 logger.debug("shutting down cephfs filesystem")
11fdf7f2
TL
50 self.cfs.shutdown()
51
52 @contextmanager
53 def opendir(self, dirpath):
54 d = None
55 try:
56 d = self.cfs.opendir(dirpath)
57 yield d
58 finally:
59 if d:
60 self.cfs.closedir(d)
61
9f95a23c
TL
62 def ls_dir(self, path, depth):
63 """
64 List directories of specified path with additional information.
65 :param path: The root directory path.
66 :type path: str | bytes
67 :param depth: The number of steps to go down the directory tree.
68 :type depth: int | str
69 :return: A list of directory dicts which consist of name, path,
70 parent, snapshots and quotas.
71 :rtype: list
72 """
73 paths = self._ls_dir(path, int(depth))
74 # Convert (bytes => string), prettify paths (strip slashes)
75 # and append additional information.
76 return [self.get_directory(p) for p in paths if p != path.encode()]
77
78 def _ls_dir(self, path, depth):
79 """
80 List directories of specified path.
81 :param path: The root directory path.
82 :type path: str | bytes
83 :param depth: The number of steps to go down the directory tree.
84 :type depth: int
85 :return: A list of directory paths (bytes encoded).
86 Example:
87 ls_dir('/photos', 1) => [
88 b'/photos/flowers', b'/photos/cars'
89 ]
90 :rtype: list
91 """
f67539c2 92 if isinstance(path, str):
9f95a23c
TL
93 path = path.encode()
94 logger.debug("get_dir_list dirpath=%s depth=%s", path,
95 depth)
96 if depth == 0:
97 return [path]
98 logger.debug("opening dirpath=%s", path)
99 with self.opendir(path) as d:
11fdf7f2 100 dent = self.cfs.readdir(d)
9f95a23c 101 paths = [path]
11fdf7f2 102 while dent:
9f95a23c
TL
103 logger.debug("found entry=%s", dent.d_name)
104 if dent.d_name in [b'.', b'..']:
11fdf7f2
TL
105 dent = self.cfs.readdir(d)
106 continue
107 if dent.is_dir():
9f95a23c
TL
108 logger.debug("found dir=%s", dent.d_name)
109 subdir_path = os.path.join(path, dent.d_name)
110 paths.extend(self._ls_dir(subdir_path, depth - 1))
11fdf7f2
TL
111 dent = self.cfs.readdir(d)
112 return paths
113
9f95a23c
TL
114 def get_directory(self, path):
115 """
116 Transforms path of directory into a meaningful dictionary.
117 :param path: The root directory path.
118 :type path: str | bytes
119 :return: Dict consists of name, path, parent, snapshots and quotas.
120 :rtype: dict
121 """
122 path = path.decode()
123 not_root = path != os.sep
124 return {
125 'name': os.path.basename(path) if not_root else path,
126 'path': path,
127 'parent': os.path.dirname(path) if not_root else None,
128 'snapshots': self.ls_snapshots(path),
129 'quotas': self.get_quotas(path) if not_root else None
130 }
131
132 def dir_exists(self, path):
11fdf7f2 133 try:
9f95a23c 134 with self.opendir(path):
11fdf7f2
TL
135 return True
136 except cephfs.ObjectNotFound:
137 return False
138
9f95a23c
TL
139 def mk_dirs(self, path):
140 """
141 Create a directory.
142 :param path: The path of the directory.
143 """
144 if path == os.sep:
11fdf7f2 145 raise Exception('Cannot create root directory "/"')
9f95a23c
TL
146 if self.dir_exists(path):
147 return
148 logger.info("Creating directory: %s", path)
149 self.cfs.mkdirs(path, 0o755)
150
151 def rm_dir(self, path):
152 """
153 Remove a directory.
154 :param path: The path of the directory.
155 """
156 if path == os.sep:
157 raise Exception('Cannot remove root directory "/"')
158 if not self.dir_exists(path):
11fdf7f2 159 return
9f95a23c
TL
160 logger.info("Removing directory: %s", path)
161 self.cfs.rmdir(path)
162
163 def mk_snapshot(self, path, name=None, mode=0o755):
164 """
165 Create a snapshot.
166 :param path: The path of the directory.
167 :type path: str
168 :param name: The name of the snapshot. If not specified,
169 a name using the current time in RFC3339 UTC format
170 will be generated.
171 :type name: str | None
172 :param mode: The permissions the directory should have
173 once created.
174 :type mode: int
175 :return: Returns the name of the snapshot.
176 :rtype: str
177 """
178 if name is None:
179 now = datetime.datetime.now()
180 tz = now.astimezone().tzinfo
181 name = now.replace(tzinfo=tz).isoformat('T')
182 client_snapdir = self.cfs.conf_get('client_snapdir')
183 snapshot_path = os.path.join(path, client_snapdir, name)
184 logger.info("Creating snapshot: %s", snapshot_path)
185 self.cfs.mkdir(snapshot_path, mode)
186 return name
187
188 def ls_snapshots(self, path):
189 """
190 List snapshots for the specified path.
191 :param path: The path of the directory.
192 :type path: str
193 :return: A list of dictionaries containing the name and the
194 creation time of the snapshot.
195 :rtype: list
196 """
197 result = []
198 client_snapdir = self.cfs.conf_get('client_snapdir')
199 path = os.path.join(path, client_snapdir).encode()
200 with self.opendir(path) as d:
201 dent = self.cfs.readdir(d)
202 while dent:
203 if dent.is_dir():
204 if dent.d_name not in [b'.', b'..'] and not dent.d_name.startswith(b'_'):
205 snapshot_path = os.path.join(path, dent.d_name)
206 stat = self.cfs.stat(snapshot_path)
207 result.append({
208 'name': dent.d_name.decode(),
209 'path': snapshot_path.decode(),
210 'created': '{}Z'.format(stat.st_ctime.isoformat('T'))
211 })
212 dent = self.cfs.readdir(d)
213 return result
214
215 def rm_snapshot(self, path, name):
216 """
217 Remove a snapshot.
218 :param path: The path of the directory.
219 :type path: str
220 :param name: The name of the snapshot.
221 :type name: str
222 """
223 client_snapdir = self.cfs.conf_get('client_snapdir')
224 snapshot_path = os.path.join(path, client_snapdir, name)
225 logger.info("Removing snapshot: %s", snapshot_path)
226 self.cfs.rmdir(snapshot_path)
227
228 def get_quotas(self, path):
229 """
230 Get the quotas of the specified path.
231 :param path: The path of the directory/file.
232 :type path: str
233 :return: Returns a dictionary containing 'max_bytes'
234 and 'max_files'.
235 :rtype: dict
236 """
237 try:
238 max_bytes = int(self.cfs.getxattr(path, 'ceph.quota.max_bytes'))
239 except cephfs.NoData:
240 max_bytes = 0
241 try:
242 max_files = int(self.cfs.getxattr(path, 'ceph.quota.max_files'))
243 except cephfs.NoData:
244 max_files = 0
245 return {'max_bytes': max_bytes, 'max_files': max_files}
11fdf7f2 246
9f95a23c
TL
247 def set_quotas(self, path, max_bytes=None, max_files=None):
248 """
249 Set the quotas of the specified path.
250 :param path: The path of the directory/file.
251 :type path: str
252 :param max_bytes: The byte limit.
253 :type max_bytes: int | None
254 :param max_files: The file limit.
255 :type max_files: int | None
256 """
257 if max_bytes is not None:
258 self.cfs.setxattr(path, 'ceph.quota.max_bytes',
259 str(max_bytes).encode(), 0)
260 if max_files is not None:
261 self.cfs.setxattr(path, 'ceph.quota.max_files',
262 str(max_files).encode(), 0)