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