]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py
9142262dd2df17e42c34c4e0feb38ec28cd7bcdd
[ceph.git] / ceph / src / pybind / mgr / volumes / fs / operations / versions / subvolume_base.py
1 import os
2 import uuid
3 import errno
4 import logging
5 from hashlib import md5
6
7 import cephfs
8
9 from .metadata_manager import MetadataManager
10 from ..trash import create_trashcan, open_trashcan
11 from ...fs_util import get_ancestor_xattr
12 from ...exception import MetadataMgrException, VolumeException
13
14 log = logging.getLogger(__name__)
15
16 class SubvolumeBase(object):
17 LEGACY_CONF_DIR = "_legacy"
18
19 SUBVOLUME_TYPE_NORMAL = "subvolume"
20 SUBVOLUME_TYPE_CLONE = "clone"
21
22 def __init__(self, fs, vol_spec, group, subvolname, legacy=False):
23 self.fs = fs
24 self.cmode = None
25 self.user_id = None
26 self.group_id = None
27 self.vol_spec = vol_spec
28 self.group = group
29 self.subvolname = subvolname
30 self.legacy_mode = legacy
31 self.load_config()
32
33 @property
34 def uid(self):
35 return self.user_id
36
37 @uid.setter
38 def uid(self, val):
39 self.user_id = val
40
41 @property
42 def gid(self):
43 return self.group_id
44
45 @gid.setter
46 def gid(self, val):
47 self.group_id = val
48
49 @property
50 def mode(self):
51 return self.cmode
52
53 @mode.setter
54 def mode(self, val):
55 self.cmode = val
56
57 @property
58 def base_path(self):
59 return os.path.join(self.group.path, self.subvolname.encode('utf-8'))
60
61 @property
62 def config_path(self):
63 return os.path.join(self.base_path, b".meta")
64
65 @property
66 def legacy_dir(self):
67 return os.path.join(self.vol_spec.base_dir.encode('utf-8'), SubvolumeBase.LEGACY_CONF_DIR.encode('utf-8'))
68
69 @property
70 def legacy_config_path(self):
71 m = md5()
72 m.update(self.base_path)
73 meta_config = "{0}.meta".format(m.digest().hex())
74 return os.path.join(self.legacy_dir, meta_config.encode('utf-8'))
75
76 @property
77 def namespace(self):
78 return "{0}{1}".format(self.vol_spec.fs_namespace, self.subvolname)
79
80 @property
81 def group_name(self):
82 return self.group.group_name
83
84 @property
85 def subvol_name(self):
86 return self.subvolname
87
88 @property
89 def legacy_mode(self):
90 return self.legacy
91
92 @legacy_mode.setter
93 def legacy_mode(self, mode):
94 self.legacy = mode
95
96 def load_config(self):
97 if self.legacy_mode:
98 self.metadata_mgr = MetadataManager(self.fs, self.legacy_config_path, 0o640)
99 else:
100 self.metadata_mgr = MetadataManager(self.fs, self.config_path, 0o640)
101
102 def _set_attrs(self, path, size, isolate_namespace, pool, uid, gid):
103 # set size
104 if size is not None:
105 try:
106 self.fs.setxattr(path, 'ceph.quota.max_bytes', str(size).encode('utf-8'), 0)
107 except cephfs.InvalidValue as e:
108 raise VolumeException(-errno.EINVAL, "invalid size specified: '{0}'".format(size))
109 except cephfs.Error as e:
110 raise VolumeException(-e.args[0], e.args[1])
111
112 # set pool layout
113 if pool:
114 try:
115 self.fs.setxattr(path, 'ceph.dir.layout.pool', pool.encode('utf-8'), 0)
116 except cephfs.InvalidValue:
117 raise VolumeException(-errno.EINVAL,
118 "invalid pool layout '{0}' -- need a valid data pool".format(pool))
119 except cephfs.Error as e:
120 raise VolumeException(-e.args[0], e.args[1])
121
122 # isolate namespace
123 xattr_key = xattr_val = None
124 if isolate_namespace:
125 # enforce security isolation, use separate namespace for this subvolume
126 xattr_key = 'ceph.dir.layout.pool_namespace'
127 xattr_val = self.namespace
128 elif not pool:
129 # If subvolume's namespace layout is not set, then the subvolume's pool
130 # layout remains unset and will undesirably change with ancestor's
131 # pool layout changes.
132 xattr_key = 'ceph.dir.layout.pool'
133 xattr_val = get_ancestor_xattr(self.fs, path, "ceph.dir.layout.pool")
134 if xattr_key and xattr_val:
135 try:
136 self.fs.setxattr(path, xattr_key, xattr_val.encode('utf-8'), 0)
137 except cephfs.Error as e:
138 raise VolumeException(-e.args[0], e.args[1])
139
140 # set uid/gid
141 if uid is None:
142 uid = self.group.uid
143 else:
144 try:
145 uid = int(uid)
146 if uid < 0:
147 raise ValueError
148 except ValueError:
149 raise VolumeException(-errno.EINVAL, "invalid UID")
150 if gid is None:
151 gid = self.group.gid
152 else:
153 try:
154 gid = int(gid)
155 if gid < 0:
156 raise ValueError
157 except ValueError:
158 raise VolumeException(-errno.EINVAL, "invalid GID")
159 if uid is not None and gid is not None:
160 self.fs.chown(path, uid, gid)
161
162 def _resize(self, path, newsize, noshrink):
163 try:
164 newsize = int(newsize)
165 if newsize <= 0:
166 raise VolumeException(-errno.EINVAL, "Invalid subvolume size")
167 except ValueError:
168 newsize = newsize.lower()
169 if not (newsize == "inf" or newsize == "infinite"):
170 raise VolumeException(-errno.EINVAL, "invalid size option '{0}'".format(newsize))
171 newsize = 0
172 noshrink = False
173
174 try:
175 maxbytes = int(self.fs.getxattr(path, 'ceph.quota.max_bytes').decode('utf-8'))
176 except cephfs.NoData:
177 maxbytes = 0
178 except cephfs.Error as e:
179 raise VolumeException(-e.args[0], e.args[1])
180
181 subvolstat = self.fs.stat(path)
182 if newsize > 0 and newsize < subvolstat.st_size:
183 if noshrink:
184 raise VolumeException(-errno.EINVAL, "Can't resize the subvolume. The new size '{0}' would be lesser than the current "
185 "used size '{1}'".format(newsize, subvolstat.st_size))
186
187 if not newsize == maxbytes:
188 try:
189 self.fs.setxattr(path, 'ceph.quota.max_bytes', str(newsize).encode('utf-8'), 0)
190 except cephfs.Error as e:
191 raise VolumeException(-e.args[0], "Cannot set new size for the subvolume. '{0}'".format(e.args[1]))
192 return newsize, subvolstat.st_size
193
194 def init_config(self, version, subvolume_type, subvolume_path, subvolume_state):
195 self.metadata_mgr.init(version, subvolume_type, subvolume_path, subvolume_state)
196 self.metadata_mgr.flush()
197
198 def discover(self):
199 log.debug("discovering subvolume '{0}' [mode: {1}]".format(self.subvolname, "legacy" if self.legacy_mode else "new"))
200 try:
201 self.fs.stat(self.base_path)
202 self.metadata_mgr.refresh()
203 log.debug("loaded subvolume '{0}'".format(self.subvolname))
204 except MetadataMgrException as me:
205 if me.errno == -errno.ENOENT and not self.legacy_mode:
206 self.legacy_mode = True
207 self.load_config()
208 self.discover()
209 else:
210 raise
211 except cephfs.Error as e:
212 if e.args[0] == errno.ENOENT:
213 raise VolumeException(-errno.ENOENT, "subvolume '{0}' does not exist".format(self.subvolname))
214 raise VolumeException(-e.args[0], "error accessing subvolume '{0}'".format(self.subvolname))
215
216 def trash_base_dir(self):
217 if self.legacy_mode:
218 self.fs.unlink(self.legacy_config_path)
219 subvol_path = self.base_path
220 create_trashcan(self.fs, self.vol_spec)
221 with open_trashcan(self.fs, self.vol_spec) as trashcan:
222 trashcan.dump(subvol_path)
223 log.info("subvolume with path '{0}' moved to trashcan".format(subvol_path))
224
225 def create_base_dir(self, mode):
226 try:
227 self.fs.mkdirs(self.base_path, mode)
228 except cephfs.Error as e:
229 raise VolumeException(-e.args[0], e.args[1])
230
231 def info (self):
232 subvolpath = self.metadata_mgr.get_global_option('path')
233 etype = self.metadata_mgr.get_global_option(MetadataManager.GLOBAL_META_KEY_TYPE)
234 st = self.fs.statx(subvolpath, cephfs.CEPH_STATX_BTIME | cephfs.CEPH_STATX_SIZE |
235 cephfs.CEPH_STATX_UID | cephfs.CEPH_STATX_GID |
236 cephfs.CEPH_STATX_MODE | cephfs.CEPH_STATX_ATIME |
237 cephfs.CEPH_STATX_MTIME | cephfs.CEPH_STATX_CTIME,
238 cephfs.AT_SYMLINK_NOFOLLOW)
239 usedbytes = st["size"]
240 try:
241 nsize = int(self.fs.getxattr(subvolpath, 'ceph.quota.max_bytes').decode('utf-8'))
242 except cephfs.NoData:
243 nsize = 0
244
245 try:
246 data_pool = self.fs.getxattr(subvolpath, 'ceph.dir.layout.pool').decode('utf-8')
247 except cephfs.Error as e:
248 raise VolumeException(-e.args[0], e.args[1])
249
250 return {'path': subvolpath, 'type': etype, 'uid': int(st["uid"]), 'gid': int(st["gid"]),
251 'atime': str(st["atime"]), 'mtime': str(st["mtime"]), 'ctime': str(st["ctime"]),
252 'mode': int(st["mode"]), 'data_pool': data_pool, 'created_at': str(st["btime"]),
253 'bytes_quota': "infinite" if nsize == 0 else nsize, 'bytes_used': int(usedbytes),
254 'bytes_pcent': "undefined" if nsize == 0 else '{0:.2f}'.format((float(usedbytes) / nsize) * 100.0)}