]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py
7 from typing
import Dict
, Union
8 from pathlib
import Path
12 from ..pin_util
import pin
13 from .subvolume_attrs
import SubvolumeTypes
14 from .metadata_manager
import MetadataManager
15 from ..trash
import create_trashcan
, open_trashcan
16 from ...fs_util
import get_ancestor_xattr
17 from ...exception
import MetadataMgrException
, VolumeException
18 from .auth_metadata
import AuthMetadataManager
19 from .subvolume_attrs
import SubvolumeStates
21 log
= logging
.getLogger(__name__
)
24 class SubvolumeBase(object):
25 LEGACY_CONF_DIR
= "_legacy"
27 def __init__(self
, mgr
, fs
, vol_spec
, group
, subvolname
, legacy
=False):
30 self
.auth_mdata_mgr
= AuthMetadataManager(fs
)
34 self
.vol_spec
= vol_spec
36 self
.subvolname
= subvolname
37 self
.legacy_mode
= legacy
66 return os
.path
.join(self
.group
.path
, self
.subvolname
.encode('utf-8'))
69 def config_path(self
):
70 return os
.path
.join(self
.base_path
, b
".meta")
74 return (os
.path
.join(self
.vol_spec
.base_dir
.encode('utf-8'),
75 SubvolumeBase
.LEGACY_CONF_DIR
.encode('utf-8')))
78 def legacy_config_path(self
):
80 m
= hashlib
.md5(self
.base_path
)
83 m
= hashlib
.md5(self
.base_path
, usedforsecurity
=False) # type: ignore
85 raise VolumeException(-errno
.EINVAL
,
86 "require python's hashlib library to support usedforsecurity flag in FIPS enabled systems")
88 meta_config
= "{0}.meta".format(m
.hexdigest())
89 return os
.path
.join(self
.legacy_dir
, meta_config
.encode('utf-8'))
93 return "{0}{1}".format(self
.vol_spec
.fs_namespace
, self
.subvolname
)
97 return self
.group
.group_name
100 def subvol_name(self
):
101 return self
.subvolname
104 def legacy_mode(self
):
108 def legacy_mode(self
, mode
):
113 """ Path to subvolume data directory """
114 raise NotImplementedError
119 List of features supported by the subvolume,
120 containing items from SubvolumeFeatures
122 raise NotImplementedError
126 """ Subvolume state, one of SubvolumeStates """
127 return SubvolumeStates
.from_value(self
.metadata_mgr
.get_global_option(MetadataManager
.GLOBAL_META_KEY_STATE
))
130 def subvol_type(self
):
131 return (SubvolumeTypes
.from_value(self
.metadata_mgr
.get_global_option
132 (MetadataManager
.GLOBAL_META_KEY_TYPE
)))
136 """ Boolean declaring if subvolume can be purged """
137 raise NotImplementedError
139 def clean_stale_snapshot_metadata(self
):
140 """ Clean up stale snapshot metadata """
141 raise NotImplementedError
143 def load_config(self
):
145 self
.fs
.stat(self
.legacy_config_path
)
146 self
.legacy_mode
= True
150 log
.debug("loading config "
151 "'{0}' [mode: {1}]".format(self
.subvolname
, "legacy"
152 if self
.legacy_mode
else "new"))
154 self
.metadata_mgr
= MetadataManager(self
.fs
,
155 self
.legacy_config_path
,
158 self
.metadata_mgr
= MetadataManager(self
.fs
,
159 self
.config_path
, 0o640)
161 def get_attrs(self
, pathname
):
162 # get subvolume attributes
163 attrs
: Dict
[str, Union
[int, str, None]] = {}
164 stx
= self
.fs
.statx(pathname
,
165 cephfs
.CEPH_STATX_UID | cephfs
.CEPH_STATX_GID
166 | cephfs
.CEPH_STATX_MODE
,
167 cephfs
.AT_SYMLINK_NOFOLLOW
)
169 attrs
["uid"] = int(stx
["uid"])
170 attrs
["gid"] = int(stx
["gid"])
171 attrs
["mode"] = int(int(stx
["mode"]) & ~stat
.S_IFMT(stx
["mode"]))
174 attrs
["data_pool"] = self
.fs
.getxattr(pathname
,
175 'ceph.dir.layout.pool'
177 except cephfs
.NoData
:
178 attrs
["data_pool"] = None
181 attrs
["pool_namespace"] = self
.fs
.getxattr(pathname
,
185 except cephfs
.NoData
:
186 attrs
["pool_namespace"] = None
189 attrs
["quota"] = int(self
.fs
.getxattr(pathname
,
190 'ceph.quota.max_bytes'
192 except cephfs
.NoData
:
193 attrs
["quota"] = None
197 def set_attrs(self
, path
, attrs
):
198 # set subvolume attributes
200 quota
= attrs
.get("quota")
201 if quota
is not None:
203 self
.fs
.setxattr(path
, 'ceph.quota.max_bytes',
204 str(quota
).encode('utf-8'), 0)
205 except cephfs
.InvalidValue
:
206 raise VolumeException(-errno
.EINVAL
,
207 "invalid size specified: '{0}'".format(quota
))
208 except cephfs
.Error
as e
:
209 raise VolumeException(-e
.args
[0], e
.args
[1])
212 data_pool
= attrs
.get("data_pool")
213 if data_pool
is not None:
215 self
.fs
.setxattr(path
, 'ceph.dir.layout.pool',
216 data_pool
.encode('utf-8'), 0)
217 except cephfs
.InvalidValue
:
218 raise VolumeException(-errno
.EINVAL
,
219 "invalid pool layout '{0}'"
220 "--need a valid data pool"
222 except cephfs
.Error
as e
:
223 raise VolumeException(-e
.args
[0], e
.args
[1])
226 xattr_key
= xattr_val
= None
227 pool_namespace
= attrs
.get("pool_namespace")
228 if pool_namespace
is not None:
229 # enforce security isolation, use separate namespace
231 xattr_key
= 'ceph.dir.layout.pool_namespace'
232 xattr_val
= pool_namespace
234 # If subvolume's namespace layout is not set,
235 # then the subvolume's pool
236 # layout remains unset and will undesirably change with ancestor's
237 # pool layout changes.
238 xattr_key
= 'ceph.dir.layout.pool'
241 self
.fs
.getxattr(path
, 'ceph.dir.layout.pool').decode('utf-8')
242 except cephfs
.NoData
:
243 xattr_val
= get_ancestor_xattr(self
.fs
, os
.path
.split(path
)[0],
244 "ceph.dir.layout.pool")
245 if xattr_key
and xattr_val
:
247 self
.fs
.setxattr(path
, xattr_key
, xattr_val
.encode('utf-8'), 0)
248 except cephfs
.Error
as e
:
249 raise VolumeException(-e
.args
[0], e
.args
[1])
252 uid
= attrs
.get("uid")
260 raise VolumeException(-errno
.EINVAL
, "invalid UID")
262 gid
= attrs
.get("gid")
270 raise VolumeException(-errno
.EINVAL
, "invalid GID")
272 if uid
is not None and gid
is not None:
273 self
.fs
.chown(path
, uid
, gid
)
276 mode
= attrs
.get("mode", None)
278 self
.fs
.lchmod(path
, mode
)
280 def _resize(self
, path
, newsize
, noshrink
):
282 newsize
= int(newsize
)
284 raise VolumeException(-errno
.EINVAL
,
285 "Invalid subvolume size")
287 newsize
= newsize
.lower()
288 if not (newsize
== "inf" or newsize
== "infinite"):
289 raise (VolumeException(-errno
.EINVAL
,
290 "invalid size option '{0}'"
296 maxbytes
= int(self
.fs
.getxattr(path
,
297 'ceph.quota.max_bytes'
299 except cephfs
.NoData
:
301 except cephfs
.Error
as e
:
302 raise VolumeException(-e
.args
[0], e
.args
[1])
304 subvolstat
= self
.fs
.stat(path
)
305 if newsize
> 0 and newsize
< subvolstat
.st_size
:
307 raise VolumeException(-errno
.EINVAL
,
308 "Can't resize the subvolume. "
309 "The new size '{0}' would be "
310 "lesser than the current "
315 if not newsize
== maxbytes
:
317 self
.fs
.setxattr(path
, 'ceph.quota.max_bytes',
318 str(newsize
).encode('utf-8'), 0)
319 except cephfs
.Error
as e
:
320 raise (VolumeException(-e
.args
[0],
321 "Cannot set new size"
322 "for the subvolume. '{0}'"
324 return newsize
, subvolstat
.st_size
326 def pin(self
, pin_type
, pin_setting
):
327 return pin(self
.fs
, self
.base_path
, pin_type
, pin_setting
)
329 def init_config(self
, version
, subvolume_type
,
330 subvolume_path
, subvolume_state
):
331 self
.metadata_mgr
.init(version
, subvolume_type
.value
,
332 subvolume_path
, subvolume_state
.value
)
333 self
.metadata_mgr
.flush()
336 log
.debug("discovering subvolume "
337 "'{0}' [mode: {1}]".format(self
.subvolname
, "legacy"
338 if self
.legacy_mode
else "new"))
340 self
.fs
.stat(self
.base_path
)
341 self
.metadata_mgr
.refresh()
342 log
.debug("loaded subvolume '{0}'".format(self
.subvolname
))
343 subvolpath
= self
.metadata_mgr
.get_global_option(MetadataManager
.GLOBAL_META_KEY_PATH
)
344 # subvolume with retained snapshots has empty path, don't mistake it for
345 # fabricated metadata.
346 if (not self
.legacy_mode
and self
.state
!= SubvolumeStates
.STATE_RETAINED
and
347 self
.base_path
.decode('utf-8') != str(Path(subvolpath
).parent
)):
348 raise MetadataMgrException(-errno
.ENOENT
, 'fabricated .meta')
349 except MetadataMgrException
as me
:
350 if me
.errno
in (-errno
.ENOENT
, -errno
.EINVAL
) and not self
.legacy_mode
:
351 log
.warn("subvolume '{0}', {1}, "
352 "assuming legacy_mode".format(self
.subvolname
, me
.error_str
))
353 self
.legacy_mode
= True
358 except cephfs
.Error
as e
:
359 if e
.args
[0] == errno
.ENOENT
:
360 raise (VolumeException(-errno
.ENOENT
,
363 .format(self
.subvolname
)))
364 raise VolumeException(-e
.args
[0],
365 "error accessing subvolume '{0}'"
366 .format(self
.subvolname
))
368 def _trash_dir(self
, path
):
369 create_trashcan(self
.fs
, self
.vol_spec
)
370 with
open_trashcan(self
.fs
, self
.vol_spec
) as trashcan
:
372 log
.info("subvolume path '{0}' moved to trashcan".format(path
))
374 def _link_dir(self
, path
, bname
):
375 create_trashcan(self
.fs
, self
.vol_spec
)
376 with
open_trashcan(self
.fs
, self
.vol_spec
) as trashcan
:
377 trashcan
.link(path
, bname
)
378 log
.info("subvolume path '{0}' "
379 "linked in trashcan bname {1}".format(path
, bname
))
381 def trash_base_dir(self
):
383 self
.fs
.unlink(self
.legacy_config_path
)
384 self
._trash
_dir
(self
.base_path
)
386 def create_base_dir(self
, mode
):
388 self
.fs
.mkdirs(self
.base_path
, mode
)
389 except cephfs
.Error
as e
:
390 raise VolumeException(-e
.args
[0], e
.args
[1])
393 subvolpath
= (self
.metadata_mgr
.get_global_option(
394 MetadataManager
.GLOBAL_META_KEY_PATH
))
395 etype
= self
.subvol_type
396 st
= self
.fs
.statx(subvolpath
, cephfs
.CEPH_STATX_BTIME
397 | cephfs
.CEPH_STATX_SIZE
398 | cephfs
.CEPH_STATX_UID | cephfs
.CEPH_STATX_GID
399 | cephfs
.CEPH_STATX_MODE | cephfs
.CEPH_STATX_ATIME
400 | cephfs
.CEPH_STATX_MTIME
401 | cephfs
.CEPH_STATX_CTIME
,
402 cephfs
.AT_SYMLINK_NOFOLLOW
)
403 usedbytes
= st
["size"]
405 nsize
= int(self
.fs
.getxattr(subvolpath
,
406 'ceph.quota.max_bytes'
408 except cephfs
.NoData
:
412 data_pool
= self
.fs
.getxattr(subvolpath
,
413 'ceph.dir.layout.pool'
415 pool_namespace
= self
.fs
.getxattr(subvolpath
,
416 'ceph.dir.layout.pool_namespace'
418 except cephfs
.Error
as e
:
419 raise VolumeException(-e
.args
[0], e
.args
[1])
421 return {'path': subvolpath
,
423 'uid': int(st
["uid"]),
424 'gid': int(st
["gid"]),
425 'atime': str(st
["atime"]),
426 'mtime': str(st
["mtime"]),
427 'ctime': str(st
["ctime"]),
428 'mode': int(st
["mode"]),
429 'data_pool': data_pool
,
430 'created_at': str(st
["btime"]),
431 'bytes_quota': "infinite" if nsize
== 0 else nsize
,
432 'bytes_used': int(usedbytes
),
433 'bytes_pcent': "undefined"
435 else '{0:.2f}'.format((float(usedbytes
) / nsize
) * 100.0),
436 'pool_namespace': pool_namespace
,
437 'features': self
.features
, 'state': self
.state
.value
}
439 def set_user_metadata(self
, keyname
, value
):
441 self
.metadata_mgr
.add_section(MetadataManager
.USER_METADATA_SECTION
)
442 self
.metadata_mgr
.update_section(MetadataManager
.USER_METADATA_SECTION
, keyname
, str(value
))
443 self
.metadata_mgr
.flush()
444 except MetadataMgrException
as me
:
445 log
.error(f
"Failed to set user metadata key={keyname} value={value} on subvolume={self.subvol_name} "
446 f
"group={self.group_name} reason={me.args[1]}, errno:{-me.args[0]}, {os.strerror(-me.args[0])}")
447 raise VolumeException(-me
.args
[0], me
.args
[1])
449 def get_user_metadata(self
, keyname
):
451 value
= self
.metadata_mgr
.get_option(MetadataManager
.USER_METADATA_SECTION
, keyname
)
452 except MetadataMgrException
as me
:
453 if me
.errno
== -errno
.ENOENT
:
454 raise VolumeException(-errno
.ENOENT
, "key '{0}' does not exist.".format(keyname
))
455 raise VolumeException(-me
.args
[0], me
.args
[1])
458 def list_user_metadata(self
):
459 return self
.metadata_mgr
.list_all_options_from_section(MetadataManager
.USER_METADATA_SECTION
)
461 def remove_user_metadata(self
, keyname
):
463 ret
= self
.metadata_mgr
.remove_option(MetadataManager
.USER_METADATA_SECTION
, keyname
)
465 raise VolumeException(-errno
.ENOENT
, "key '{0}' does not exist.".format(keyname
))
466 self
.metadata_mgr
.flush()
467 except MetadataMgrException
as me
:
468 if me
.errno
== -errno
.ENOENT
:
469 raise VolumeException(-errno
.ENOENT
, "subvolume metadata does not exist")
470 log
.error(f
"Failed to remove user metadata key={keyname} on subvolume={self.subvol_name} "
471 f
"group={self.group_name} reason={me.args[1]}, errno:{-me.args[0]}, {os.strerror(-me.args[0])}")
472 raise VolumeException(-me
.args
[0], me
.args
[1])
474 def get_snap_section_name(self
, snapname
):
475 section
= "SNAP_METADATA" + "_" + snapname
;
478 def set_snapshot_metadata(self
, snapname
, keyname
, value
):
480 section
= self
.get_snap_section_name(snapname
)
481 self
.metadata_mgr
.add_section(section
)
482 self
.metadata_mgr
.update_section(section
, keyname
, str(value
))
483 self
.metadata_mgr
.flush()
484 except MetadataMgrException
as me
:
485 log
.error(f
"Failed to set snapshot metadata key={keyname} value={value} on snap={snapname} "
486 f
"subvolume={self.subvol_name} group={self.group_name} "
487 f
"reason={me.args[1]}, errno:{-me.args[0]}, {os.strerror(-me.args[0])}")
488 raise VolumeException(-me
.args
[0], me
.args
[1])
490 def get_snapshot_metadata(self
, snapname
, keyname
):
492 value
= self
.metadata_mgr
.get_option(self
.get_snap_section_name(snapname
), keyname
)
493 except MetadataMgrException
as me
:
494 if me
.errno
== -errno
.ENOENT
:
495 raise VolumeException(-errno
.ENOENT
, "key '{0}' does not exist.".format(keyname
))
496 log
.error(f
"Failed to get snapshot metadata key={keyname} on snap={snapname} "
497 f
"subvolume={self.subvol_name} group={self.group_name} "
498 f
"reason={me.args[1]}, errno:{-me.args[0]}, {os.strerror(-me.args[0])}")
499 raise VolumeException(-me
.args
[0], me
.args
[1])
502 def list_snapshot_metadata(self
, snapname
):
503 return self
.metadata_mgr
.list_all_options_from_section(self
.get_snap_section_name(snapname
))
505 def remove_snapshot_metadata(self
, snapname
, keyname
):
507 ret
= self
.metadata_mgr
.remove_option(self
.get_snap_section_name(snapname
), keyname
)
509 raise VolumeException(-errno
.ENOENT
, "key '{0}' does not exist.".format(keyname
))
510 self
.metadata_mgr
.flush()
511 except MetadataMgrException
as me
:
512 if me
.errno
== -errno
.ENOENT
:
513 raise VolumeException(-errno
.ENOENT
, "snapshot metadata not does not exist")
514 log
.error(f
"Failed to remove snapshot metadata key={keyname} on snap={snapname} "
515 f
"subvolume={self.subvol_name} group={self.group_name} "
516 f
"reason={me.args[1]}, errno:{-me.args[0]}, {os.strerror(-me.args[0])}")
517 raise VolumeException(-me
.args
[0], me
.args
[1])