]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py
f7aa2ec81586b4850b9598f4c3c351a07addd2a3
6 from hashlib
import md5
7 from typing
import Dict
, Union
11 from ..pin_util
import pin
12 from .subvolume_attrs
import SubvolumeTypes
13 from .metadata_manager
import MetadataManager
14 from ..trash
import create_trashcan
, open_trashcan
15 from ...fs_util
import get_ancestor_xattr
16 from ...exception
import MetadataMgrException
, VolumeException
17 from .auth_metadata
import AuthMetadataManager
19 log
= logging
.getLogger(__name__
)
22 class SubvolumeBase(object):
23 LEGACY_CONF_DIR
= "_legacy"
25 def __init__(self
, mgr
, fs
, vol_spec
, group
, subvolname
, legacy
=False):
28 self
.auth_mdata_mgr
= AuthMetadataManager(fs
)
32 self
.vol_spec
= vol_spec
34 self
.subvolname
= subvolname
35 self
.legacy_mode
= legacy
64 return os
.path
.join(self
.group
.path
, self
.subvolname
.encode('utf-8'))
67 def config_path(self
):
68 return os
.path
.join(self
.base_path
, b
".meta")
72 return (os
.path
.join(self
.vol_spec
.base_dir
.encode('utf-8'),
73 SubvolumeBase
.LEGACY_CONF_DIR
.encode('utf-8')))
76 def legacy_config_path(self
):
78 m
.update(self
.base_path
)
79 meta_config
= "{0}.meta".format(m
.digest().hex())
80 return os
.path
.join(self
.legacy_dir
, meta_config
.encode('utf-8'))
84 return "{0}{1}".format(self
.vol_spec
.fs_namespace
, self
.subvolname
)
88 return self
.group
.group_name
91 def subvol_name(self
):
92 return self
.subvolname
95 def legacy_mode(self
):
99 def legacy_mode(self
, mode
):
104 """ Path to subvolume data directory """
105 raise NotImplementedError
110 List of features supported by the subvolume,
111 containing items from SubvolumeFeatures
113 raise NotImplementedError
117 """ Subvolume state, one of SubvolumeStates """
118 raise NotImplementedError
121 def subvol_type(self
):
122 return (SubvolumeTypes
.from_value(self
.metadata_mgr
.get_global_option
123 (MetadataManager
.GLOBAL_META_KEY_TYPE
)))
127 """ Boolean declaring if subvolume can be purged """
128 raise NotImplementedError
130 def load_config(self
):
132 self
.metadata_mgr
= MetadataManager(self
.fs
,
133 self
.legacy_config_path
,
136 self
.metadata_mgr
= MetadataManager(self
.fs
,
137 self
.config_path
, 0o640)
139 def get_attrs(self
, pathname
):
140 # get subvolume attributes
141 attrs
= {} # type: Dict[str, Union[int, str, None]]
142 stx
= self
.fs
.statx(pathname
,
143 cephfs
.CEPH_STATX_UID | cephfs
.CEPH_STATX_GID
144 | cephfs
.CEPH_STATX_MODE
,
145 cephfs
.AT_SYMLINK_NOFOLLOW
)
147 attrs
["uid"] = int(stx
["uid"])
148 attrs
["gid"] = int(stx
["gid"])
149 attrs
["mode"] = int(int(stx
["mode"]) & ~stat
.S_IFMT(stx
["mode"]))
152 attrs
["data_pool"] = self
.fs
.getxattr(pathname
,
153 'ceph.dir.layout.pool'
155 except cephfs
.NoData
:
156 attrs
["data_pool"] = None
159 attrs
["pool_namespace"] = self
.fs
.getxattr(pathname
,
163 except cephfs
.NoData
:
164 attrs
["pool_namespace"] = None
167 attrs
["quota"] = int(self
.fs
.getxattr(pathname
,
168 'ceph.quota.max_bytes'
170 except cephfs
.NoData
:
171 attrs
["quota"] = None
175 def set_attrs(self
, path
, attrs
):
176 # set subvolume attributes
178 quota
= attrs
.get("quota")
179 if quota
is not None:
181 self
.fs
.setxattr(path
, 'ceph.quota.max_bytes',
182 str(quota
).encode('utf-8'), 0)
183 except cephfs
.InvalidValue
:
184 raise VolumeException(-errno
.EINVAL
,
185 "invalid size specified: '{0}'".format(quota
))
186 except cephfs
.Error
as e
:
187 raise VolumeException(-e
.args
[0], e
.args
[1])
190 data_pool
= attrs
.get("data_pool")
191 if data_pool
is not None:
193 self
.fs
.setxattr(path
, 'ceph.dir.layout.pool',
194 data_pool
.encode('utf-8'), 0)
195 except cephfs
.InvalidValue
:
196 raise VolumeException(-errno
.EINVAL
,
197 "invalid pool layout '{0}'"
198 "--need a valid data pool"
200 except cephfs
.Error
as e
:
201 raise VolumeException(-e
.args
[0], e
.args
[1])
204 xattr_key
= xattr_val
= None
205 pool_namespace
= attrs
.get("pool_namespace")
206 if pool_namespace
is not None:
207 # enforce security isolation, use separate namespace
209 xattr_key
= 'ceph.dir.layout.pool_namespace'
210 xattr_val
= pool_namespace
212 # If subvolume's namespace layout is not set,
213 # then the subvolume's pool
214 # layout remains unset and will undesirably change with ancestor's
215 # pool layout changes.
216 xattr_key
= 'ceph.dir.layout.pool'
219 self
.fs
.getxattr(path
, 'ceph.dir.layout.pool').decode('utf-8')
220 except cephfs
.NoData
:
221 xattr_val
= get_ancestor_xattr(self
.fs
, os
.path
.split(path
)[0],
222 "ceph.dir.layout.pool")
223 if xattr_key
and xattr_val
:
225 self
.fs
.setxattr(path
, xattr_key
, xattr_val
.encode('utf-8'), 0)
226 except cephfs
.Error
as e
:
227 raise VolumeException(-e
.args
[0], e
.args
[1])
230 uid
= attrs
.get("uid")
238 raise VolumeException(-errno
.EINVAL
, "invalid UID")
240 gid
= attrs
.get("gid")
248 raise VolumeException(-errno
.EINVAL
, "invalid GID")
250 if uid
is not None and gid
is not None:
251 self
.fs
.chown(path
, uid
, gid
)
254 mode
= attrs
.get("mode", None)
256 self
.fs
.lchmod(path
, mode
)
258 def _resize(self
, path
, newsize
, noshrink
):
260 newsize
= int(newsize
)
262 raise VolumeException(-errno
.EINVAL
,
263 "Invalid subvolume size")
265 newsize
= newsize
.lower()
266 if not (newsize
== "inf" or newsize
== "infinite"):
267 raise (VolumeException(-errno
.EINVAL
,
268 "invalid size option '{0}'"
274 maxbytes
= int(self
.fs
.getxattr(path
,
275 'ceph.quota.max_bytes'
277 except cephfs
.NoData
:
279 except cephfs
.Error
as e
:
280 raise VolumeException(-e
.args
[0], e
.args
[1])
282 subvolstat
= self
.fs
.stat(path
)
283 if newsize
> 0 and newsize
< subvolstat
.st_size
:
285 raise VolumeException(-errno
.EINVAL
,
286 "Can't resize the subvolume. "
287 "The new size '{0}' would be "
288 "lesser than the current "
293 if not newsize
== maxbytes
:
295 self
.fs
.setxattr(path
, 'ceph.quota.max_bytes',
296 str(newsize
).encode('utf-8'), 0)
297 except cephfs
.Error
as e
:
298 raise (VolumeException(-e
.args
[0],
299 "Cannot set new size"
300 "for the subvolume. '{0}'"
302 return newsize
, subvolstat
.st_size
304 def pin(self
, pin_type
, pin_setting
):
305 return pin(self
.fs
, self
.base_path
, pin_type
, pin_setting
)
307 def init_config(self
, version
, subvolume_type
,
308 subvolume_path
, subvolume_state
):
309 self
.metadata_mgr
.init(version
, subvolume_type
.value
,
310 subvolume_path
, subvolume_state
.value
)
311 self
.metadata_mgr
.flush()
314 log
.debug("discovering subvolume "
315 "'{0}' [mode: {1}]".format(self
.subvolname
, "legacy"
316 if self
.legacy_mode
else "new"))
318 self
.fs
.stat(self
.base_path
)
319 self
.metadata_mgr
.refresh()
320 log
.debug("loaded subvolume '{0}'".format(self
.subvolname
))
321 except MetadataMgrException
as me
:
322 if me
.errno
== -errno
.ENOENT
and not self
.legacy_mode
:
323 self
.legacy_mode
= True
328 except cephfs
.Error
as e
:
329 if e
.args
[0] == errno
.ENOENT
:
330 raise (VolumeException(-errno
.ENOENT
,
333 .format(self
.subvolname
)))
334 raise VolumeException(-e
.args
[0],
335 "error accessing subvolume '{0}'"
336 .format(self
.subvolname
))
338 def _trash_dir(self
, path
):
339 create_trashcan(self
.fs
, self
.vol_spec
)
340 with
open_trashcan(self
.fs
, self
.vol_spec
) as trashcan
:
342 log
.info("subvolume path '{0}' moved to trashcan".format(path
))
344 def _link_dir(self
, path
, bname
):
345 create_trashcan(self
.fs
, self
.vol_spec
)
346 with
open_trashcan(self
.fs
, self
.vol_spec
) as trashcan
:
347 trashcan
.link(path
, bname
)
348 log
.info("subvolume path '{0}' "
349 "linked in trashcan bname {1}".format(path
, bname
))
351 def trash_base_dir(self
):
353 self
.fs
.unlink(self
.legacy_config_path
)
354 self
._trash
_dir
(self
.base_path
)
356 def create_base_dir(self
, mode
):
358 self
.fs
.mkdirs(self
.base_path
, mode
)
359 except cephfs
.Error
as e
:
360 raise VolumeException(-e
.args
[0], e
.args
[1])
363 subvolpath
= (self
.metadata_mgr
.get_global_option(
364 MetadataManager
.GLOBAL_META_KEY_PATH
))
365 etype
= self
.subvol_type
366 st
= self
.fs
.statx(subvolpath
, cephfs
.CEPH_STATX_BTIME
367 | cephfs
.CEPH_STATX_SIZE
368 | cephfs
.CEPH_STATX_UID | cephfs
.CEPH_STATX_GID
369 | cephfs
.CEPH_STATX_MODE | cephfs
.CEPH_STATX_ATIME
370 | cephfs
.CEPH_STATX_MTIME
371 | cephfs
.CEPH_STATX_CTIME
,
372 cephfs
.AT_SYMLINK_NOFOLLOW
)
373 usedbytes
= st
["size"]
375 nsize
= int(self
.fs
.getxattr(subvolpath
,
376 'ceph.quota.max_bytes'
378 except cephfs
.NoData
:
382 data_pool
= self
.fs
.getxattr(subvolpath
,
383 'ceph.dir.layout.pool'
385 pool_namespace
= self
.fs
.getxattr(subvolpath
,
386 'ceph.dir.layout.pool_namespace'
388 except cephfs
.Error
as e
:
389 raise VolumeException(-e
.args
[0], e
.args
[1])
391 return {'path': subvolpath
,
393 'uid': int(st
["uid"]),
394 'gid': int(st
["gid"]),
395 'atime': str(st
["atime"]),
396 'mtime': str(st
["mtime"]),
397 'ctime': str(st
["ctime"]),
398 'mode': int(st
["mode"]),
399 'data_pool': data_pool
,
400 'created_at': str(st
["btime"]),
401 'bytes_quota': "infinite" if nsize
== 0 else nsize
,
402 'bytes_used': int(usedbytes
),
403 'bytes_pcent': "undefined"
405 else '{0:.2f}'.format((float(usedbytes
) / nsize
) * 100.0),
406 'pool_namespace': pool_namespace
,
407 'features': self
.features
, 'state': self
.state
.value
}
409 def set_user_metadata(self
, keyname
, value
):
410 self
.metadata_mgr
.add_section(MetadataManager
.USER_METADATA_SECTION
)
411 self
.metadata_mgr
.update_section(MetadataManager
.USER_METADATA_SECTION
, keyname
, str(value
))
412 self
.metadata_mgr
.flush()
414 def get_user_metadata(self
, keyname
):
416 value
= self
.metadata_mgr
.get_option(MetadataManager
.USER_METADATA_SECTION
, keyname
)
417 except MetadataMgrException
as me
:
418 if me
.errno
== -errno
.ENOENT
:
419 raise VolumeException(-errno
.ENOENT
, "key '{0}' does not exist.".format(keyname
))
420 raise VolumeException(-me
.args
[0], me
.args
[1])
423 def list_user_metadata(self
):
424 return self
.metadata_mgr
.list_all_options_from_section(MetadataManager
.USER_METADATA_SECTION
)
426 def remove_user_metadata(self
, keyname
):
428 ret
= self
.metadata_mgr
.remove_option(MetadataManager
.USER_METADATA_SECTION
, keyname
)
430 raise VolumeException(-errno
.ENOENT
, "key '{0}' does not exist.".format(keyname
))
431 self
.metadata_mgr
.flush()
432 except MetadataMgrException
as me
:
433 if me
.errno
== -errno
.ENOENT
:
434 raise VolumeException(-errno
.ENOENT
, "subvolume metadata not does not exist")
435 raise VolumeException(-me
.args
[0], me
.args
[1])
437 def get_snap_section_name(self
, snapname
):
438 section
= "SNAP_METADATA" + "_" + snapname
;
441 def set_snapshot_metadata(self
, snapname
, keyname
, value
):
442 section
= self
.get_snap_section_name(snapname
)
443 self
.metadata_mgr
.add_section(section
)
444 self
.metadata_mgr
.update_section(section
, keyname
, str(value
))
445 self
.metadata_mgr
.flush()
447 def get_snapshot_metadata(self
, snapname
, keyname
):
449 value
= self
.metadata_mgr
.get_option(self
.get_snap_section_name(snapname
), keyname
)
450 except MetadataMgrException
as me
:
451 if me
.errno
== -errno
.ENOENT
:
452 raise VolumeException(-errno
.ENOENT
, "key '{0}' does not exist.".format(keyname
))
453 raise VolumeException(-me
.args
[0], me
.args
[1])
456 def list_snapshot_metadata(self
, snapname
):
457 return self
.metadata_mgr
.list_all_options_from_section(self
.get_snap_section_name(snapname
))
459 def remove_snapshot_metadata(self
, snapname
, keyname
):
461 ret
= self
.metadata_mgr
.remove_option(self
.get_snap_section_name(snapname
), keyname
)
463 raise VolumeException(-errno
.ENOENT
, "key '{0}' does not exist.".format(keyname
))
464 self
.metadata_mgr
.flush()
465 except MetadataMgrException
as me
:
466 if me
.errno
== -errno
.ENOENT
:
467 raise VolumeException(-errno
.ENOENT
, "snapshot metadata not does not exist")
468 raise VolumeException(-me
.args
[0], me
.args
[1])