]>
git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py
6 from hashlib
import md5
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
.update(self
.base_path
)
81 meta_config
= "{0}.meta".format(m
.digest().hex())
82 return os
.path
.join(self
.legacy_dir
, meta_config
.encode('utf-8'))
86 return "{0}{1}".format(self
.vol_spec
.fs_namespace
, self
.subvolname
)
90 return self
.group
.group_name
93 def subvol_name(self
):
94 return self
.subvolname
97 def legacy_mode(self
):
101 def legacy_mode(self
, mode
):
106 """ Path to subvolume data directory """
107 raise NotImplementedError
112 List of features supported by the subvolume,
113 containing items from SubvolumeFeatures
115 raise NotImplementedError
119 """ Subvolume state, one of SubvolumeStates """
120 return SubvolumeStates
.from_value(self
.metadata_mgr
.get_global_option(MetadataManager
.GLOBAL_META_KEY_STATE
))
123 def subvol_type(self
):
124 return (SubvolumeTypes
.from_value(self
.metadata_mgr
.get_global_option
125 (MetadataManager
.GLOBAL_META_KEY_TYPE
)))
129 """ Boolean declaring if subvolume can be purged """
130 raise NotImplementedError
132 def load_config(self
):
134 self
.fs
.stat(self
.legacy_config_path
)
135 self
.legacy_mode
= True
136 except cephfs
.Error
as e
:
139 log
.debug("loading config "
140 "'{0}' [mode: {1}]".format(self
.subvolname
, "legacy"
141 if self
.legacy_mode
else "new"))
143 self
.metadata_mgr
= MetadataManager(self
.fs
,
144 self
.legacy_config_path
,
147 self
.metadata_mgr
= MetadataManager(self
.fs
,
148 self
.config_path
, 0o640)
150 def get_attrs(self
, pathname
):
151 # get subvolume attributes
152 attrs
= {} # type: Dict[str, Union[int, str, None]]
153 stx
= self
.fs
.statx(pathname
,
154 cephfs
.CEPH_STATX_UID | cephfs
.CEPH_STATX_GID
155 | cephfs
.CEPH_STATX_MODE
,
156 cephfs
.AT_SYMLINK_NOFOLLOW
)
158 attrs
["uid"] = int(stx
["uid"])
159 attrs
["gid"] = int(stx
["gid"])
160 attrs
["mode"] = int(int(stx
["mode"]) & ~stat
.S_IFMT(stx
["mode"]))
163 attrs
["data_pool"] = self
.fs
.getxattr(pathname
,
164 'ceph.dir.layout.pool'
166 except cephfs
.NoData
:
167 attrs
["data_pool"] = None
170 attrs
["pool_namespace"] = self
.fs
.getxattr(pathname
,
174 except cephfs
.NoData
:
175 attrs
["pool_namespace"] = None
178 attrs
["quota"] = int(self
.fs
.getxattr(pathname
,
179 'ceph.quota.max_bytes'
181 except cephfs
.NoData
:
182 attrs
["quota"] = None
186 def set_attrs(self
, path
, attrs
):
187 # set subvolume attributes
189 quota
= attrs
.get("quota")
190 if quota
is not None:
192 self
.fs
.setxattr(path
, 'ceph.quota.max_bytes',
193 str(quota
).encode('utf-8'), 0)
194 except cephfs
.InvalidValue
:
195 raise VolumeException(-errno
.EINVAL
,
196 "invalid size specified: '{0}'".format(quota
))
197 except cephfs
.Error
as e
:
198 raise VolumeException(-e
.args
[0], e
.args
[1])
201 data_pool
= attrs
.get("data_pool")
202 if data_pool
is not None:
204 self
.fs
.setxattr(path
, 'ceph.dir.layout.pool',
205 data_pool
.encode('utf-8'), 0)
206 except cephfs
.InvalidValue
:
207 raise VolumeException(-errno
.EINVAL
,
208 "invalid pool layout '{0}'"
209 "--need a valid data pool"
211 except cephfs
.Error
as e
:
212 raise VolumeException(-e
.args
[0], e
.args
[1])
215 xattr_key
= xattr_val
= None
216 pool_namespace
= attrs
.get("pool_namespace")
217 if pool_namespace
is not None:
218 # enforce security isolation, use separate namespace
220 xattr_key
= 'ceph.dir.layout.pool_namespace'
221 xattr_val
= pool_namespace
223 # If subvolume's namespace layout is not set,
224 # then the subvolume's pool
225 # layout remains unset and will undesirably change with ancestor's
226 # pool layout changes.
227 xattr_key
= 'ceph.dir.layout.pool'
230 self
.fs
.getxattr(path
, 'ceph.dir.layout.pool').decode('utf-8')
231 except cephfs
.NoData
:
232 xattr_val
= get_ancestor_xattr(self
.fs
, os
.path
.split(path
)[0],
233 "ceph.dir.layout.pool")
234 if xattr_key
and xattr_val
:
236 self
.fs
.setxattr(path
, xattr_key
, xattr_val
.encode('utf-8'), 0)
237 except cephfs
.Error
as e
:
238 raise VolumeException(-e
.args
[0], e
.args
[1])
241 uid
= attrs
.get("uid")
249 raise VolumeException(-errno
.EINVAL
, "invalid UID")
251 gid
= attrs
.get("gid")
259 raise VolumeException(-errno
.EINVAL
, "invalid GID")
261 if uid
is not None and gid
is not None:
262 self
.fs
.chown(path
, uid
, gid
)
265 mode
= attrs
.get("mode", None)
267 self
.fs
.lchmod(path
, mode
)
269 def _resize(self
, path
, newsize
, noshrink
):
271 newsize
= int(newsize
)
273 raise VolumeException(-errno
.EINVAL
,
274 "Invalid subvolume size")
276 newsize
= newsize
.lower()
277 if not (newsize
== "inf" or newsize
== "infinite"):
278 raise (VolumeException(-errno
.EINVAL
,
279 "invalid size option '{0}'"
285 maxbytes
= int(self
.fs
.getxattr(path
,
286 'ceph.quota.max_bytes'
288 except cephfs
.NoData
:
290 except cephfs
.Error
as e
:
291 raise VolumeException(-e
.args
[0], e
.args
[1])
293 subvolstat
= self
.fs
.stat(path
)
294 if newsize
> 0 and newsize
< subvolstat
.st_size
:
296 raise VolumeException(-errno
.EINVAL
,
297 "Can't resize the subvolume. "
298 "The new size '{0}' would be "
299 "lesser than the current "
304 if not newsize
== maxbytes
:
306 self
.fs
.setxattr(path
, 'ceph.quota.max_bytes',
307 str(newsize
).encode('utf-8'), 0)
308 except cephfs
.Error
as e
:
309 raise (VolumeException(-e
.args
[0],
310 "Cannot set new size"
311 "for the subvolume. '{0}'"
313 return newsize
, subvolstat
.st_size
315 def pin(self
, pin_type
, pin_setting
):
316 return pin(self
.fs
, self
.base_path
, pin_type
, pin_setting
)
318 def init_config(self
, version
, subvolume_type
,
319 subvolume_path
, subvolume_state
):
320 self
.metadata_mgr
.init(version
, subvolume_type
.value
,
321 subvolume_path
, subvolume_state
.value
)
322 self
.metadata_mgr
.flush()
325 log
.debug("discovering subvolume "
326 "'{0}' [mode: {1}]".format(self
.subvolname
, "legacy"
327 if self
.legacy_mode
else "new"))
329 self
.fs
.stat(self
.base_path
)
330 self
.metadata_mgr
.refresh()
331 log
.debug("loaded subvolume '{0}'".format(self
.subvolname
))
332 subvolpath
= self
.metadata_mgr
.get_global_option(MetadataManager
.GLOBAL_META_KEY_PATH
)
333 # subvolume with retained snapshots has empty path, don't mistake it for
334 # fabricated metadata.
335 if (not self
.legacy_mode
and self
.state
!= SubvolumeStates
.STATE_RETAINED
and
336 self
.base_path
.decode('utf-8') != str(Path(subvolpath
).parent
)):
337 raise MetadataMgrException(-errno
.ENOENT
, 'fabricated .meta')
338 except MetadataMgrException
as me
:
339 if me
.errno
in (-errno
.ENOENT
, -errno
.EINVAL
) and not self
.legacy_mode
:
340 log
.warn("subvolume '{0}', {1}, "
341 "assuming legacy_mode".format(self
.subvolname
, me
.error_str
))
342 self
.legacy_mode
= True
347 except cephfs
.Error
as e
:
348 if e
.args
[0] == errno
.ENOENT
:
349 raise (VolumeException(-errno
.ENOENT
,
352 .format(self
.subvolname
)))
353 raise VolumeException(-e
.args
[0],
354 "error accessing subvolume '{0}'"
355 .format(self
.subvolname
))
357 def _trash_dir(self
, path
):
358 create_trashcan(self
.fs
, self
.vol_spec
)
359 with
open_trashcan(self
.fs
, self
.vol_spec
) as trashcan
:
361 log
.info("subvolume path '{0}' moved to trashcan".format(path
))
363 def _link_dir(self
, path
, bname
):
364 create_trashcan(self
.fs
, self
.vol_spec
)
365 with
open_trashcan(self
.fs
, self
.vol_spec
) as trashcan
:
366 trashcan
.link(path
, bname
)
367 log
.info("subvolume path '{0}' "
368 "linked in trashcan bname {1}".format(path
, bname
))
370 def trash_base_dir(self
):
372 self
.fs
.unlink(self
.legacy_config_path
)
373 self
._trash
_dir
(self
.base_path
)
375 def create_base_dir(self
, mode
):
377 self
.fs
.mkdirs(self
.base_path
, mode
)
378 except cephfs
.Error
as e
:
379 raise VolumeException(-e
.args
[0], e
.args
[1])
382 subvolpath
= (self
.metadata_mgr
.get_global_option(
383 MetadataManager
.GLOBAL_META_KEY_PATH
))
384 etype
= self
.subvol_type
385 st
= self
.fs
.statx(subvolpath
, cephfs
.CEPH_STATX_BTIME
386 | cephfs
.CEPH_STATX_SIZE
387 | cephfs
.CEPH_STATX_UID | cephfs
.CEPH_STATX_GID
388 | cephfs
.CEPH_STATX_MODE | cephfs
.CEPH_STATX_ATIME
389 | cephfs
.CEPH_STATX_MTIME
390 | cephfs
.CEPH_STATX_CTIME
,
391 cephfs
.AT_SYMLINK_NOFOLLOW
)
392 usedbytes
= st
["size"]
394 nsize
= int(self
.fs
.getxattr(subvolpath
,
395 'ceph.quota.max_bytes'
397 except cephfs
.NoData
:
401 data_pool
= self
.fs
.getxattr(subvolpath
,
402 'ceph.dir.layout.pool'
404 pool_namespace
= self
.fs
.getxattr(subvolpath
,
405 'ceph.dir.layout.pool_namespace'
407 except cephfs
.Error
as e
:
408 raise VolumeException(-e
.args
[0], e
.args
[1])
410 return {'path': subvolpath
,
412 'uid': int(st
["uid"]),
413 'gid': int(st
["gid"]),
414 'atime': str(st
["atime"]),
415 'mtime': str(st
["mtime"]),
416 'ctime': str(st
["ctime"]),
417 'mode': int(st
["mode"]),
418 'data_pool': data_pool
,
419 'created_at': str(st
["btime"]),
420 'bytes_quota': "infinite" if nsize
== 0 else nsize
,
421 'bytes_used': int(usedbytes
),
422 'bytes_pcent': "undefined"
424 else '{0:.2f}'.format((float(usedbytes
) / nsize
) * 100.0),
425 'pool_namespace': pool_namespace
,
426 'features': self
.features
, 'state': self
.state
.value
}
428 def set_user_metadata(self
, keyname
, value
):
429 self
.metadata_mgr
.add_section(MetadataManager
.USER_METADATA_SECTION
)
430 self
.metadata_mgr
.update_section(MetadataManager
.USER_METADATA_SECTION
, keyname
, str(value
))
431 self
.metadata_mgr
.flush()
433 def get_user_metadata(self
, keyname
):
435 value
= self
.metadata_mgr
.get_option(MetadataManager
.USER_METADATA_SECTION
, keyname
)
436 except MetadataMgrException
as me
:
437 if me
.errno
== -errno
.ENOENT
:
438 raise VolumeException(-errno
.ENOENT
, "key '{0}' does not exist.".format(keyname
))
439 raise VolumeException(-me
.args
[0], me
.args
[1])
442 def list_user_metadata(self
):
443 return self
.metadata_mgr
.list_all_options_from_section(MetadataManager
.USER_METADATA_SECTION
)
445 def remove_user_metadata(self
, keyname
):
447 ret
= self
.metadata_mgr
.remove_option(MetadataManager
.USER_METADATA_SECTION
, keyname
)
449 raise VolumeException(-errno
.ENOENT
, "key '{0}' does not exist.".format(keyname
))
450 self
.metadata_mgr
.flush()
451 except MetadataMgrException
as me
:
452 if me
.errno
== -errno
.ENOENT
:
453 raise VolumeException(-errno
.ENOENT
, "subvolume metadata not does not exist")
454 raise VolumeException(-me
.args
[0], me
.args
[1])
456 def get_snap_section_name(self
, snapname
):
457 section
= "SNAP_METADATA" + "_" + snapname
;
460 def set_snapshot_metadata(self
, snapname
, keyname
, value
):
461 section
= self
.get_snap_section_name(snapname
)
462 self
.metadata_mgr
.add_section(section
)
463 self
.metadata_mgr
.update_section(section
, keyname
, str(value
))
464 self
.metadata_mgr
.flush()
466 def get_snapshot_metadata(self
, snapname
, keyname
):
468 value
= self
.metadata_mgr
.get_option(self
.get_snap_section_name(snapname
), keyname
)
469 except MetadataMgrException
as me
:
470 if me
.errno
== -errno
.ENOENT
:
471 raise VolumeException(-errno
.ENOENT
, "key '{0}' does not exist.".format(keyname
))
472 raise VolumeException(-me
.args
[0], me
.args
[1])
475 def list_snapshot_metadata(self
, snapname
):
476 return self
.metadata_mgr
.list_all_options_from_section(self
.get_snap_section_name(snapname
))
478 def remove_snapshot_metadata(self
, snapname
, keyname
):
480 ret
= self
.metadata_mgr
.remove_option(self
.get_snap_section_name(snapname
), keyname
)
482 raise VolumeException(-errno
.ENOENT
, "key '{0}' does not exist.".format(keyname
))
483 self
.metadata_mgr
.flush()
484 except MetadataMgrException
as me
:
485 if me
.errno
== -errno
.ENOENT
:
486 raise VolumeException(-errno
.ENOENT
, "snapshot metadata not does not exist")
487 raise VolumeException(-me
.args
[0], me
.args
[1])