]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py
import ceph 15.2.10
[ceph.git] / ceph / src / pybind / mgr / volumes / fs / operations / versions / subvolume_base.py
CommitLineData
92f5a8d4 1import os
adb31ebb 2import stat
92f5a8d4
TL
3import uuid
4import errno
5import logging
6from hashlib import md5
adb31ebb 7from typing import Dict, Union
92f5a8d4
TL
8
9import cephfs
10
f6b5b4d7 11from ..pin_util import pin
adb31ebb 12from .subvolume_attrs import SubvolumeTypes, SubvolumeStates
92f5a8d4
TL
13from .metadata_manager import MetadataManager
14from ..trash import create_trashcan, open_trashcan
15from ...fs_util import get_ancestor_xattr
16from ...exception import MetadataMgrException, VolumeException
adb31ebb 17from .op_sm import SubvolumeOpSm
cd265ab1 18from .auth_metadata import AuthMetadataManager
92f5a8d4
TL
19
20log = logging.getLogger(__name__)
21
22class SubvolumeBase(object):
23 LEGACY_CONF_DIR = "_legacy"
24
cd265ab1
TL
25 def __init__(self, mgr, fs, vol_spec, group, subvolname, legacy=False):
26 self.mgr = mgr
92f5a8d4 27 self.fs = fs
cd265ab1 28 self.auth_mdata_mgr = AuthMetadataManager(fs)
92f5a8d4
TL
29 self.cmode = None
30 self.user_id = None
31 self.group_id = None
32 self.vol_spec = vol_spec
33 self.group = group
34 self.subvolname = subvolname
35 self.legacy_mode = legacy
36 self.load_config()
37
38 @property
39 def uid(self):
40 return self.user_id
41
92f5a8d4
TL
42 @uid.setter
43 def uid(self, val):
44 self.user_id = val
45
9f95a23c
TL
46 @property
47 def gid(self):
48 return self.group_id
49
92f5a8d4
TL
50 @gid.setter
51 def gid(self, val):
52 self.group_id = val
53
9f95a23c
TL
54 @property
55 def mode(self):
56 return self.cmode
57
92f5a8d4
TL
58 @mode.setter
59 def mode(self, val):
60 self.cmode = val
61
62 @property
63 def base_path(self):
64 return os.path.join(self.group.path, self.subvolname.encode('utf-8'))
65
66 @property
67 def config_path(self):
68 return os.path.join(self.base_path, b".meta")
69
70 @property
71 def legacy_dir(self):
72 return os.path.join(self.vol_spec.base_dir.encode('utf-8'), SubvolumeBase.LEGACY_CONF_DIR.encode('utf-8'))
73
74 @property
75 def legacy_config_path(self):
76 m = md5()
77 m.update(self.base_path)
9f95a23c 78 meta_config = "{0}.meta".format(m.digest().hex())
92f5a8d4
TL
79 return os.path.join(self.legacy_dir, meta_config.encode('utf-8'))
80
81 @property
82 def namespace(self):
83 return "{0}{1}".format(self.vol_spec.fs_namespace, self.subvolname)
84
85 @property
86 def group_name(self):
87 return self.group.group_name
88
89 @property
90 def subvol_name(self):
91 return self.subvolname
92
93 @property
94 def legacy_mode(self):
95 return self.legacy
96
97 @legacy_mode.setter
98 def legacy_mode(self, mode):
99 self.legacy = mode
100
adb31ebb
TL
101 @property
102 def path(self):
103 """ Path to subvolume data directory """
104 raise NotImplementedError
105
f6b5b4d7
TL
106 @property
107 def features(self):
adb31ebb
TL
108 """ List of features supported by the subvolume, containing items from SubvolumeFeatures """
109 raise NotImplementedError
110
111 @property
112 def state(self):
113 """ Subvolume state, one of SubvolumeStates """
114 raise NotImplementedError
115
116 @property
117 def subvol_type(self):
118 return SubvolumeTypes.from_value(self.metadata_mgr.get_global_option(MetadataManager.GLOBAL_META_KEY_TYPE))
119
120 @property
121 def purgeable(self):
122 """ Boolean declaring if subvolume can be purged """
f6b5b4d7
TL
123 raise NotImplementedError
124
92f5a8d4
TL
125 def load_config(self):
126 if self.legacy_mode:
127 self.metadata_mgr = MetadataManager(self.fs, self.legacy_config_path, 0o640)
128 else:
129 self.metadata_mgr = MetadataManager(self.fs, self.config_path, 0o640)
130
adb31ebb
TL
131 def get_attrs(self, pathname):
132 # get subvolume attributes
133 attrs = {} # type: Dict[str, Union[int, str, None]]
134 stx = self.fs.statx(pathname,
135 cephfs.CEPH_STATX_UID | cephfs.CEPH_STATX_GID | cephfs.CEPH_STATX_MODE,
136 cephfs.AT_SYMLINK_NOFOLLOW)
137
138 attrs["uid"] = int(stx["uid"])
139 attrs["gid"] = int(stx["gid"])
140 attrs["mode"] = int(int(stx["mode"]) & ~stat.S_IFMT(stx["mode"]))
141
142 try:
143 attrs["data_pool"] = self.fs.getxattr(pathname, 'ceph.dir.layout.pool').decode('utf-8')
144 except cephfs.NoData:
145 attrs["data_pool"] = None
146
147 try:
148 attrs["pool_namespace"] = self.fs.getxattr(pathname, 'ceph.dir.layout.pool_namespace').decode('utf-8')
149 except cephfs.NoData:
150 attrs["pool_namespace"] = None
151
152 try:
153 attrs["quota"] = int(self.fs.getxattr(pathname, 'ceph.quota.max_bytes').decode('utf-8'))
154 except cephfs.NoData:
155 attrs["quota"] = None
156
157 return attrs
158
159 def set_attrs(self, path, attrs):
160 # set subvolume attributes
92f5a8d4 161 # set size
adb31ebb
TL
162 quota = attrs.get("quota")
163 if quota is not None:
92f5a8d4 164 try:
adb31ebb 165 self.fs.setxattr(path, 'ceph.quota.max_bytes', str(quota).encode('utf-8'), 0)
92f5a8d4 166 except cephfs.InvalidValue as e:
adb31ebb 167 raise VolumeException(-errno.EINVAL, "invalid size specified: '{0}'".format(quota))
92f5a8d4
TL
168 except cephfs.Error as e:
169 raise VolumeException(-e.args[0], e.args[1])
170
171 # set pool layout
adb31ebb
TL
172 data_pool = attrs.get("data_pool")
173 if data_pool is not None:
92f5a8d4 174 try:
adb31ebb 175 self.fs.setxattr(path, 'ceph.dir.layout.pool', data_pool.encode('utf-8'), 0)
92f5a8d4
TL
176 except cephfs.InvalidValue:
177 raise VolumeException(-errno.EINVAL,
adb31ebb 178 "invalid pool layout '{0}' -- need a valid data pool".format(data_pool))
92f5a8d4
TL
179 except cephfs.Error as e:
180 raise VolumeException(-e.args[0], e.args[1])
181
182 # isolate namespace
183 xattr_key = xattr_val = None
adb31ebb
TL
184 pool_namespace = attrs.get("pool_namespace")
185 if pool_namespace is not None:
92f5a8d4
TL
186 # enforce security isolation, use separate namespace for this subvolume
187 xattr_key = 'ceph.dir.layout.pool_namespace'
adb31ebb
TL
188 xattr_val = pool_namespace
189 elif not data_pool:
92f5a8d4
TL
190 # If subvolume's namespace layout is not set, then the subvolume's pool
191 # layout remains unset and will undesirably change with ancestor's
192 # pool layout changes.
193 xattr_key = 'ceph.dir.layout.pool'
e306af50
TL
194 xattr_val = None
195 try:
196 self.fs.getxattr(path, 'ceph.dir.layout.pool').decode('utf-8')
197 except cephfs.NoData as e:
198 xattr_val = get_ancestor_xattr(self.fs, os.path.split(path)[0], "ceph.dir.layout.pool")
92f5a8d4
TL
199 if xattr_key and xattr_val:
200 try:
201 self.fs.setxattr(path, xattr_key, xattr_val.encode('utf-8'), 0)
202 except cephfs.Error as e:
203 raise VolumeException(-e.args[0], e.args[1])
204
205 # set uid/gid
adb31ebb 206 uid = attrs.get("uid")
92f5a8d4
TL
207 if uid is None:
208 uid = self.group.uid
209 else:
210 try:
92f5a8d4
TL
211 if uid < 0:
212 raise ValueError
213 except ValueError:
214 raise VolumeException(-errno.EINVAL, "invalid UID")
adb31ebb
TL
215
216 gid = attrs.get("gid")
92f5a8d4
TL
217 if gid is None:
218 gid = self.group.gid
219 else:
220 try:
92f5a8d4
TL
221 if gid < 0:
222 raise ValueError
223 except ValueError:
224 raise VolumeException(-errno.EINVAL, "invalid GID")
adb31ebb 225
92f5a8d4
TL
226 if uid is not None and gid is not None:
227 self.fs.chown(path, uid, gid)
228
229 def _resize(self, path, newsize, noshrink):
230 try:
231 newsize = int(newsize)
232 if newsize <= 0:
233 raise VolumeException(-errno.EINVAL, "Invalid subvolume size")
234 except ValueError:
235 newsize = newsize.lower()
236 if not (newsize == "inf" or newsize == "infinite"):
237 raise VolumeException(-errno.EINVAL, "invalid size option '{0}'".format(newsize))
238 newsize = 0
239 noshrink = False
240
241 try:
242 maxbytes = int(self.fs.getxattr(path, 'ceph.quota.max_bytes').decode('utf-8'))
243 except cephfs.NoData:
244 maxbytes = 0
245 except cephfs.Error as e:
246 raise VolumeException(-e.args[0], e.args[1])
247
248 subvolstat = self.fs.stat(path)
249 if newsize > 0 and newsize < subvolstat.st_size:
250 if noshrink:
251 raise VolumeException(-errno.EINVAL, "Can't resize the subvolume. The new size '{0}' would be lesser than the current "
252 "used size '{1}'".format(newsize, subvolstat.st_size))
253
254 if not newsize == maxbytes:
255 try:
256 self.fs.setxattr(path, 'ceph.quota.max_bytes', str(newsize).encode('utf-8'), 0)
257 except cephfs.Error as e:
258 raise VolumeException(-e.args[0], "Cannot set new size for the subvolume. '{0}'".format(e.args[1]))
259 return newsize, subvolstat.st_size
260
f6b5b4d7
TL
261 def pin(self, pin_type, pin_setting):
262 return pin(self.fs, self.base_path, pin_type, pin_setting)
263
92f5a8d4 264 def init_config(self, version, subvolume_type, subvolume_path, subvolume_state):
adb31ebb 265 self.metadata_mgr.init(version, subvolume_type.value, subvolume_path, subvolume_state.value)
92f5a8d4
TL
266 self.metadata_mgr.flush()
267
268 def discover(self):
269 log.debug("discovering subvolume '{0}' [mode: {1}]".format(self.subvolname, "legacy" if self.legacy_mode else "new"))
270 try:
271 self.fs.stat(self.base_path)
272 self.metadata_mgr.refresh()
273 log.debug("loaded subvolume '{0}'".format(self.subvolname))
274 except MetadataMgrException as me:
275 if me.errno == -errno.ENOENT and not self.legacy_mode:
276 self.legacy_mode = True
277 self.load_config()
278 self.discover()
279 else:
280 raise
281 except cephfs.Error as e:
282 if e.args[0] == errno.ENOENT:
283 raise VolumeException(-errno.ENOENT, "subvolume '{0}' does not exist".format(self.subvolname))
284 raise VolumeException(-e.args[0], "error accessing subvolume '{0}'".format(self.subvolname))
285
adb31ebb
TL
286 def _trash_dir(self, path):
287 create_trashcan(self.fs, self.vol_spec)
288 with open_trashcan(self.fs, self.vol_spec) as trashcan:
289 trashcan.dump(path)
290 log.info("subvolume path '{0}' moved to trashcan".format(path))
291
292 def _link_dir(self, path, bname):
293 create_trashcan(self.fs, self.vol_spec)
294 with open_trashcan(self.fs, self.vol_spec) as trashcan:
295 trashcan.link(path, bname)
296 log.info("subvolume path '{0}' linked in trashcan bname {1}".format(path, bname))
297
92f5a8d4
TL
298 def trash_base_dir(self):
299 if self.legacy_mode:
300 self.fs.unlink(self.legacy_config_path)
adb31ebb 301 self._trash_dir(self.base_path)
92f5a8d4
TL
302
303 def create_base_dir(self, mode):
304 try:
305 self.fs.mkdirs(self.base_path, mode)
306 except cephfs.Error as e:
307 raise VolumeException(-e.args[0], e.args[1])
1911f103
TL
308
309 def info (self):
adb31ebb
TL
310 subvolpath = self.metadata_mgr.get_global_option(MetadataManager.GLOBAL_META_KEY_PATH)
311 etype = self.subvol_type
1911f103
TL
312 st = self.fs.statx(subvolpath, cephfs.CEPH_STATX_BTIME | cephfs.CEPH_STATX_SIZE |
313 cephfs.CEPH_STATX_UID | cephfs.CEPH_STATX_GID |
314 cephfs.CEPH_STATX_MODE | cephfs.CEPH_STATX_ATIME |
315 cephfs.CEPH_STATX_MTIME | cephfs.CEPH_STATX_CTIME,
316 cephfs.AT_SYMLINK_NOFOLLOW)
317 usedbytes = st["size"]
318 try:
319 nsize = int(self.fs.getxattr(subvolpath, 'ceph.quota.max_bytes').decode('utf-8'))
320 except cephfs.NoData:
321 nsize = 0
322
323 try:
324 data_pool = self.fs.getxattr(subvolpath, 'ceph.dir.layout.pool').decode('utf-8')
e306af50 325 pool_namespace = self.fs.getxattr(subvolpath, 'ceph.dir.layout.pool_namespace').decode('utf-8')
1911f103
TL
326 except cephfs.Error as e:
327 raise VolumeException(-e.args[0], e.args[1])
328
adb31ebb 329 return {'path': subvolpath, 'type': etype.value, 'uid': int(st["uid"]), 'gid': int(st["gid"]),
1911f103
TL
330 'atime': str(st["atime"]), 'mtime': str(st["mtime"]), 'ctime': str(st["ctime"]),
331 'mode': int(st["mode"]), 'data_pool': data_pool, 'created_at': str(st["btime"]),
332 'bytes_quota': "infinite" if nsize == 0 else nsize, 'bytes_used': int(usedbytes),
e306af50 333 'bytes_pcent': "undefined" if nsize == 0 else '{0:.2f}'.format((float(usedbytes) / nsize) * 100.0),
adb31ebb 334 'pool_namespace': pool_namespace, 'features': self.features, 'state': self.state.value}