From 0b80de9b6a3b950848fde8afa7a0d32b4641d26c Mon Sep 17 00:00:00 2001 From: Shyamsundar Ranganathan Date: Wed, 8 Jul 2020 08:15:57 -0400 Subject: [PATCH] mgr/volumes: Derive v2 from v1 to leverage common methods With v2 introduction in subvolumes, there is quite a bit of common code and methods that both v1 and v2 share. To reduce overall code bloat and improve maintainability, derive SubvolumeV2 from SubvolumeV1. Signed-off-by: Shyamsundar Ranganathan --- src/pybind/mgr/volumes/fs/async_cloner.py | 7 +- .../mgr/volumes/fs/operations/subvolume.py | 4 - .../mgr/volumes/fs/operations/template.py | 11 +- .../fs/operations/versions/__init__.py | 4 +- .../fs/operations/{ => versions}/op_sm.py | 44 +-- .../fs/operations/versions/subvolume_attrs.py | 61 +++++ .../fs/operations/versions/subvolume_base.py | 27 +- .../fs/operations/versions/subvolume_v1.py | 31 +-- .../fs/operations/versions/subvolume_v2.py | 250 ++++-------------- 9 files changed, 137 insertions(+), 302 deletions(-) rename src/pybind/mgr/volumes/fs/operations/{ => versions}/op_sm.py (78%) create mode 100644 src/pybind/mgr/volumes/fs/operations/versions/subvolume_attrs.py diff --git a/src/pybind/mgr/volumes/fs/async_cloner.py b/src/pybind/mgr/volumes/fs/async_cloner.py index d227dc594df..ac3b10d6a9c 100644 --- a/src/pybind/mgr/volumes/fs/async_cloner.py +++ b/src/pybind/mgr/volumes/fs/async_cloner.py @@ -10,17 +10,14 @@ import cephfs from .async_job import AsyncJobs from .exception import IndexException, MetadataMgrException, OpSmException, VolumeException from .fs_util import copy_file -from .operations.op_sm import SubvolumeOpSm -from .operations.op_sm import SubvolumeTypes -from .operations.op_sm import SubvolumeActions -from .operations.op_sm import SubvolumeStates +from .operations.versions.op_sm import SubvolumeOpSm +from .operations.versions.subvolume_attrs import SubvolumeTypes, SubvolumeStates, SubvolumeActions from .operations.resolver import resolve from .operations.volume import open_volume, open_volume_lockless from .operations.group import open_group from .operations.subvolume import open_subvol from .operations.clone_index import open_clone_index from .operations.template import SubvolumeOpType -from .operations.versions import SubvolumeBase log = logging.getLogger(__name__) diff --git a/src/pybind/mgr/volumes/fs/operations/subvolume.py b/src/pybind/mgr/volumes/fs/operations/subvolume.py index b7ef88830b5..c2afe45f3f6 100644 --- a/src/pybind/mgr/volumes/fs/operations/subvolume.py +++ b/src/pybind/mgr/volumes/fs/operations/subvolume.py @@ -2,10 +2,6 @@ import os import errno from contextlib import contextmanager -import cephfs - -from .snapshot_util import mksnap, rmsnap -from ..fs_util import listdir, get_ancestor_xattr from ..exception import VolumeException from .template import SubvolumeOpType diff --git a/src/pybind/mgr/volumes/fs/operations/template.py b/src/pybind/mgr/volumes/fs/operations/template.py index dc82766fcb9..3fca8dd87f9 100644 --- a/src/pybind/mgr/volumes/fs/operations/template.py +++ b/src/pybind/mgr/volumes/fs/operations/template.py @@ -58,7 +58,7 @@ class SubvolumeOpType(Enum): CLONE_INTERNAL = 'clone_internal' class SubvolumeTemplate(object): - VERSION = None + VERSION = None # type: int @staticmethod def version(): @@ -147,15 +147,6 @@ class SubvolumeTemplate(object): """ raise VolumeException(-errno.ENOTSUP, "operation not supported.") - def snapshot_path(self, snapname): - """ - return the snapshot path for a given snapshot name - - :param: subvolume snapshot name - :return: snapshot path - """ - raise VolumeException(-errno.ENOTSUP, "operation not supported.") - def list_snapshots(self): """ list all subvolume snapshots. diff --git a/src/pybind/mgr/volumes/fs/operations/versions/__init__.py b/src/pybind/mgr/volumes/fs/operations/versions/__init__.py index 5ef9aad21a0..9c038c8dae2 100644 --- a/src/pybind/mgr/volumes/fs/operations/versions/__init__.py +++ b/src/pybind/mgr/volumes/fs/operations/versions/__init__.py @@ -5,11 +5,11 @@ import importlib import cephfs from .subvolume_base import SubvolumeBase -from .subvolume_base import SubvolumeTypes +from .subvolume_attrs import SubvolumeTypes from .subvolume_v1 import SubvolumeV1 from .subvolume_v2 import SubvolumeV2 from .metadata_manager import MetadataManager -from ..op_sm import SubvolumeOpSm +from .op_sm import SubvolumeOpSm from ..template import SubvolumeOpType from ...exception import MetadataMgrException, OpSmException, VolumeException diff --git a/src/pybind/mgr/volumes/fs/operations/op_sm.py b/src/pybind/mgr/volumes/fs/operations/versions/op_sm.py similarity index 78% rename from src/pybind/mgr/volumes/fs/operations/op_sm.py rename to src/pybind/mgr/volumes/fs/operations/versions/op_sm.py index 17d5ea36b02..1142600cbb2 100644 --- a/src/pybind/mgr/volumes/fs/operations/op_sm.py +++ b/src/pybind/mgr/volumes/fs/operations/versions/op_sm.py @@ -1,47 +1,9 @@ import errno -from enum import Enum, unique from typing import Dict -from .versions.subvolume_base import SubvolumeTypes -from ..exception import OpSmException - -@unique -class SubvolumeStates(Enum): - STATE_INIT = 'init' - STATE_PENDING = 'pending' - STATE_INPROGRESS = 'in-progress' - STATE_FAILED = 'failed' - STATE_COMPLETE = 'complete' - STATE_CANCELED = 'canceled' - STATE_RETAINED = 'snapshot-retained' - - @staticmethod - def from_value(value): - if value == "init": - return SubvolumeStates.STATE_INIT - if value == "pending": - return SubvolumeStates.STATE_PENDING - if value == "in-progress": - return SubvolumeStates.STATE_INPROGRESS - if value == "failed": - return SubvolumeStates.STATE_FAILED - if value == "complete": - return SubvolumeStates.STATE_COMPLETE - if value == "canceled": - return SubvolumeStates.STATE_CANCELED - if value == "snapshot-retained": - return SubvolumeStates.STATE_RETAINED - - raise OpSmException(-errno.EINVAL, "invalid state '{0}'".format(value)) - -@unique -class SubvolumeActions(Enum): - ACTION_NONE = 0 - ACTION_SUCCESS = 1 - ACTION_FAILED = 2 - ACTION_CANCELLED = 3 - ACTION_RETAINED = 4 +from ...exception import OpSmException +from .subvolume_attrs import SubvolumeTypes, SubvolumeStates, SubvolumeActions class TransitionKey(object): def __init__(self, subvol_type, state, action_type): @@ -57,7 +19,7 @@ class TransitionKey(object): return not(self == other) class SubvolumeOpSm(object): - transition_table = {} + transition_table = {} # type: Dict @staticmethod def is_complete_state(state): diff --git a/src/pybind/mgr/volumes/fs/operations/versions/subvolume_attrs.py b/src/pybind/mgr/volumes/fs/operations/versions/subvolume_attrs.py new file mode 100644 index 00000000000..ec7138cbdb6 --- /dev/null +++ b/src/pybind/mgr/volumes/fs/operations/versions/subvolume_attrs.py @@ -0,0 +1,61 @@ +import errno +from enum import Enum, unique + +from ...exception import VolumeException + +@unique +class SubvolumeTypes(Enum): + TYPE_NORMAL = "subvolume" + TYPE_CLONE = "clone" + + @staticmethod + def from_value(value): + if value == "subvolume": + return SubvolumeTypes.TYPE_NORMAL + if value == "clone": + return SubvolumeTypes.TYPE_CLONE + + raise VolumeException(-errno.EINVAL, "invalid subvolume type '{0}'".format(value)) + +@unique +class SubvolumeStates(Enum): + STATE_INIT = 'init' + STATE_PENDING = 'pending' + STATE_INPROGRESS = 'in-progress' + STATE_FAILED = 'failed' + STATE_COMPLETE = 'complete' + STATE_CANCELED = 'canceled' + STATE_RETAINED = 'snapshot-retained' + + @staticmethod + def from_value(value): + if value == "init": + return SubvolumeStates.STATE_INIT + if value == "pending": + return SubvolumeStates.STATE_PENDING + if value == "in-progress": + return SubvolumeStates.STATE_INPROGRESS + if value == "failed": + return SubvolumeStates.STATE_FAILED + if value == "complete": + return SubvolumeStates.STATE_COMPLETE + if value == "canceled": + return SubvolumeStates.STATE_CANCELED + if value == "snapshot-retained": + return SubvolumeStates.STATE_RETAINED + + raise VolumeException(-errno.EINVAL, "invalid state '{0}'".format(value)) + +@unique +class SubvolumeActions(Enum): + ACTION_NONE = 0 + ACTION_SUCCESS = 1 + ACTION_FAILED = 2 + ACTION_CANCELLED = 3 + ACTION_RETAINED = 4 + +@unique +class SubvolumeFeatures(Enum): + FEATURE_SNAPSHOT_CLONE = "snapshot-clone" + FEATURE_SNAPSHOT_RETENTION = "snapshot-retention" + FEATURE_SNAPSHOT_AUTOPROTECT = "snapshot-autoprotect" diff --git a/src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py b/src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py index 6de9916ef01..07690009b13 100644 --- a/src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py +++ b/src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py @@ -1,40 +1,20 @@ import os -import uuid import errno import logging -from enum import Enum, unique from hashlib import md5 import cephfs from ..pin_util import pin +from .subvolume_attrs import SubvolumeTypes, SubvolumeStates from .metadata_manager import MetadataManager from ..trash import create_trashcan, open_trashcan from ...fs_util import get_ancestor_xattr from ...exception import MetadataMgrException, VolumeException +from .op_sm import SubvolumeOpSm log = logging.getLogger(__name__) -@unique -class SubvolumeFeatures(Enum): - FEATURE_SNAPSHOT_CLONE = "snapshot-clone" - FEATURE_SNAPSHOT_AUTOPROTECT = "snapshot-autoprotect" - FEATURE_SNAPSHOT_RETENTION = "snapshot-retention" - -@unique -class SubvolumeTypes(Enum): - TYPE_NORMAL = "subvolume" - TYPE_CLONE = "clone" - - @staticmethod - def from_value(value): - if value == "subvolume": - return SubvolumeTypes.TYPE_NORMAL - if value == "clone": - return SubvolumeTypes.TYPE_CLONE - - raise VolumeException(-errno.EINVAL, "invalid subvolume type '{0}'".format(value)) - class SubvolumeBase(object): LEGACY_CONF_DIR = "_legacy" @@ -114,14 +94,17 @@ class SubvolumeBase(object): @property def path(self): + """ Path to subvolume data directory """ raise NotImplementedError @property def features(self): + """ List of features supported by the subvolume, containing items from SubvolumeFeatures """ raise NotImplementedError @property def state(self): + """ Subvolume state, one of SubvolumeStates """ raise NotImplementedError @property diff --git a/src/pybind/mgr/volumes/fs/operations/versions/subvolume_v1.py b/src/pybind/mgr/volumes/fs/operations/versions/subvolume_v1.py index e587fd33f3a..b1ca48d24be 100644 --- a/src/pybind/mgr/volumes/fs/operations/versions/subvolume_v1.py +++ b/src/pybind/mgr/volumes/fs/operations/versions/subvolume_v1.py @@ -8,11 +8,9 @@ from datetime import datetime import cephfs from .metadata_manager import MetadataManager +from .subvolume_attrs import SubvolumeTypes, SubvolumeStates, SubvolumeFeatures +from .op_sm import SubvolumeOpSm from .subvolume_base import SubvolumeBase -from .subvolume_base import SubvolumeTypes -from .subvolume_base import SubvolumeFeatures -from ..op_sm import SubvolumeOpSm -from ..op_sm import SubvolumeStates from ..template import SubvolumeTemplate from ..snapshot_util import mksnap, rmsnap from ...exception import IndexException, OpSmException, VolumeException, MetadataMgrException @@ -56,6 +54,18 @@ class SubvolumeV1(SubvolumeBase, SubvolumeTemplate): def features(self): return [SubvolumeFeatures.FEATURE_SNAPSHOT_CLONE.value, SubvolumeFeatures.FEATURE_SNAPSHOT_AUTOPROTECT.value] + def snapshot_base_path(self): + """ Base path for all snapshots """ + return os.path.join(self.path, self.vol_spec.snapshot_dir_prefix.encode('utf-8')) + + def snapshot_path(self, snapname): + """ Path to a specific snapshot named 'snapname' """ + return os.path.join(self.snapshot_base_path(), snapname.encode('utf-8')) + + def snapshot_data_path(self, snapname): + """ Path to user data directory within a subvolume snapshot named 'snapname' """ + return self.snapshot_path(snapname) + def create(self, size, isolate_nspace, pool, mode, uid, gid): subvolume_type = SubvolumeTypes.TYPE_NORMAL try: @@ -241,14 +251,6 @@ class SubvolumeV1(SubvolumeBase, SubvolumeTemplate): subvol_path = self.path return self._resize(subvol_path, newsize, noshrink) - def snapshot_path(self, snapname): - return os.path.join(self.path, - self.vol_spec.snapshot_dir_prefix.encode('utf-8'), - snapname.encode('utf-8')) - - def snapshot_data_path(self, snapname): - return self.snapshot_path(snapname) - def create_snapshot(self, snapname): snappath = self.snapshot_path(snapname) mksnap(self.fs, snappath) @@ -268,7 +270,7 @@ class SubvolumeV1(SubvolumeBase, SubvolumeTemplate): rmsnap(self.fs, snappath) def snapshot_info(self, snapname): - snappath = self.snapshot_path(snapname) + snappath = self.snapshot_data_path(snapname) snap_info = {} try: snap_attrs = {'created_at':'ceph.snap.btime', 'size':'ceph.dir.rbytes', @@ -287,8 +289,7 @@ class SubvolumeV1(SubvolumeBase, SubvolumeTemplate): def list_snapshots(self): try: - dirpath = os.path.join(self.path, - self.vol_spec.snapshot_dir_prefix.encode('utf-8')) + dirpath = self.snapshot_base_path() return listdir(self.fs, dirpath) except VolumeException as ve: if ve.errno == -errno.ENOENT: diff --git a/src/pybind/mgr/volumes/fs/operations/versions/subvolume_v2.py b/src/pybind/mgr/volumes/fs/operations/versions/subvolume_v2.py index 23127d3dbb6..14397b59d40 100644 --- a/src/pybind/mgr/volumes/fs/operations/versions/subvolume_v2.py +++ b/src/pybind/mgr/volumes/fs/operations/versions/subvolume_v2.py @@ -3,27 +3,20 @@ import stat import uuid import errno import logging -from datetime import datetime import cephfs from .metadata_manager import MetadataManager -from .subvolume_base import SubvolumeBase -from .subvolume_base import SubvolumeTypes -from .subvolume_base import SubvolumeFeatures -from ..op_sm import SubvolumeOpSm -from ..op_sm import SubvolumeStates +from .subvolume_attrs import SubvolumeTypes, SubvolumeStates, SubvolumeFeatures +from .op_sm import SubvolumeOpSm +from .subvolume_v1 import SubvolumeV1 from ..template import SubvolumeTemplate -from ..snapshot_util import mksnap, rmsnap -from ...exception import IndexException, OpSmException, VolumeException, MetadataMgrException -from ...fs_util import listdir +from ...exception import OpSmException, VolumeException, MetadataMgrException from ..template import SubvolumeOpType -from ..clone_index import open_clone_index, create_clone_index - log = logging.getLogger(__name__) -class SubvolumeV2(SubvolumeBase, SubvolumeTemplate): +class SubvolumeV2(SubvolumeV1): """ Version 2 subvolumes creates a subvolume with path as follows, volumes//// @@ -49,20 +42,47 @@ class SubvolumeV2(SubvolumeBase, SubvolumeTemplate): def version(): return SubvolumeV2.VERSION - @property - def path(self): - try: - # no need to stat the path -- open() does that - return self.metadata_mgr.get_global_option(MetadataManager.GLOBAL_META_KEY_PATH).encode('utf-8') - except MetadataMgrException as me: - raise VolumeException(-errno.EINVAL, "error fetching subvolume metadata") - @property def features(self): return [SubvolumeFeatures.FEATURE_SNAPSHOT_CLONE.value, SubvolumeFeatures.FEATURE_SNAPSHOT_AUTOPROTECT.value, SubvolumeFeatures.FEATURE_SNAPSHOT_RETENTION.value] + @staticmethod + def is_valid_uuid(uuid_str): + try: + uuid.UUID(uuid_str) + return True + except ValueError: + return False + + def snapshot_base_path(self): + return os.path.join(self.base_path, self.vol_spec.snapshot_dir_prefix.encode('utf-8')) + + def snapshot_data_path(self, snapname): + snap_base_path = self.snapshot_path(snapname) + uuid_str = None + try: + with self.fs.opendir(snap_base_path) as dir_handle: + d = self.fs.readdir(dir_handle) + while d: + if d.d_name not in (b".", b".."): + d_full_path = os.path.join(snap_base_path, d.d_name) + stx = self.fs.statx(d_full_path, cephfs.CEPH_STATX_MODE, cephfs.AT_SYMLINK_NOFOLLOW) + if stat.S_ISDIR(stx.get('mode')): + if self.is_valid_uuid(d.d_name.decode('utf-8')): + uuid_str = d.d_name + d = self.fs.readdir(dir_handle) + except cephfs.Error as e: + if e.errno == errno.ENOENT: + raise VolumeException(-errno.ENOENT, "snapshot '{0}' does not exist".format(snapname)) + raise VolumeException(-e.args[0], e.args[1]) + + if not uuid_str: + raise VolumeException(-errno.ENOENT, "snapshot '{0}' does not exist".format(snapname)) + + return os.path.join(snap_base_path, uuid_str) + def _set_incarnation_metadata(self, subvolume_type, qpath, initial_state): self.metadata_mgr.update_global_section(MetadataManager.GLOBAL_META_KEY_TYPE, subvolume_type.value) self.metadata_mgr.update_global_section(MetadataManager.GLOBAL_META_KEY_PATH, qpath) @@ -107,21 +127,6 @@ class SubvolumeV2(SubvolumeBase, SubvolumeTemplate): e = VolumeException(-e.args[0], e.args[1]) raise e - def add_clone_source(self, volname, subvolume, snapname, flush=False): - self.metadata_mgr.add_section("source") - self.metadata_mgr.update_section("source", "volume", volname) - if not subvolume.group.is_default_group(): - self.metadata_mgr.update_section("source", "group", subvolume.group_name) - self.metadata_mgr.update_section("source", "subvolume", subvolume.subvol_name) - self.metadata_mgr.update_section("source", "snapshot", snapname) - if flush: - self.metadata_mgr.flush() - - def remove_clone_source(self, flush=False): - self.metadata_mgr.remove_section("source") - if flush: - self.metadata_mgr.flush() - def create_clone(self, pool, source_volname, source_subvolume, snapname): subvolume_type = SubvolumeTypes.TYPE_CLONE try: @@ -134,7 +139,7 @@ class SubvolumeV2(SubvolumeBase, SubvolumeTemplate): stx = self.fs.statx(source_subvolume.snapshot_data_path(snapname), cephfs.CEPH_STATX_MODE | cephfs.CEPH_STATX_UID | cephfs.CEPH_STATX_GID, cephfs.AT_SYMLINK_NOFOLLOW) - uid= stx.get('uid') + uid = stx.get('uid') gid = stx.get('gid') stx_mode = stx.get('mode') if stx_mode is not None: @@ -249,52 +254,6 @@ class SubvolumeV2(SubvolumeBase, SubvolumeTemplate): except cephfs.Error as e: raise VolumeException(-e.args[0], e.args[1]) - def _get_clone_source(self): - try: - clone_source = { - 'volume' : self.metadata_mgr.get_option("source", "volume"), - 'subvolume': self.metadata_mgr.get_option("source", "subvolume"), - 'snapshot' : self.metadata_mgr.get_option("source", "snapshot"), - } - - try: - clone_source["group"] = self.metadata_mgr.get_option("source", "group") - except MetadataMgrException as me: - if me.errno == -errno.ENOENT: - pass - else: - raise - except MetadataMgrException as me: - raise VolumeException(-errno.EINVAL, "error fetching subvolume metadata") - return clone_source - - @property - def status(self): - state = SubvolumeStates.from_value(self.metadata_mgr.get_global_option(MetadataManager.GLOBAL_META_KEY_STATE)) - subvolume_type = self.subvol_type - subvolume_status = { - 'state' : state.value - } - if not SubvolumeOpSm.is_complete_state(state) and subvolume_type == SubvolumeTypes.TYPE_CLONE: - subvolume_status["source"] = self._get_clone_source() - return subvolume_status - - @property - def state(self): - return SubvolumeStates.from_value(self.metadata_mgr.get_global_option(MetadataManager.GLOBAL_META_KEY_STATE)) - - @state.setter - def state(self, val): - state = val[0].value - flush = val[1] - self.metadata_mgr.update_global_section(MetadataManager.GLOBAL_META_KEY_STATE, state) - if flush: - self.metadata_mgr.flush() - - @property - def type(self): - return SubvolumeTypes.from_value(self.metadata_mgr.get_global_option(MetadataManager.GLOBAL_META_KEY_TYPE)) - def trash_incarnation_dir(self): self._trash_dir(self.path) @@ -302,138 +261,23 @@ class SubvolumeV2(SubvolumeBase, SubvolumeTemplate): if self.list_snapshots(): if not retainsnaps: raise VolumeException(-errno.ENOTEMPTY, "subvolume '{0}' has snapshots".format(self.subvolname)) - self.trash_incarnation_dir() - self.metadata_mgr.update_global_section(MetadataManager.GLOBAL_META_KEY_PATH, "") - self.metadata_mgr.update_global_section(MetadataManager.GLOBAL_META_KEY_STATE, SubvolumeStates.STATE_RETAINED.value) - self.metadata_mgr.flush() + if self.state != SubvolumeStates.STATE_RETAINED: + self.trash_incarnation_dir() + self.metadata_mgr.update_global_section(MetadataManager.GLOBAL_META_KEY_PATH, "") + self.metadata_mgr.update_global_section(MetadataManager.GLOBAL_META_KEY_STATE, SubvolumeStates.STATE_RETAINED.value) + self.metadata_mgr.flush() else: self.trash_base_dir() - def resize(self, newsize, noshrink): - subvol_path = self.path - return self._resize(subvol_path, newsize, noshrink) - def info(self): if self.state != SubvolumeStates.STATE_RETAINED: return super(SubvolumeV2, self).info() return {'type': self.subvol_type.value, 'features': self.features, 'state': SubvolumeStates.STATE_RETAINED.value} - def snapshot_path(self, snapname): - return os.path.join(self.base_path, - self.vol_spec.snapshot_dir_prefix.encode('utf-8'), - snapname.encode('utf-8')) - - @staticmethod - def is_valid_uuid(uuid_str): - try: - uuid.UUID(uuid_str) - return True - except ValueError: - return False - - def snapshot_data_path(self, snapname): - snap_base_path = self.snapshot_path(snapname) - uuid_str = None - try: - with self.fs.opendir(snap_base_path) as dir_handle: - d = self.fs.readdir(dir_handle) - while d: - if d.d_name not in (b".", b".."): - d_full_path = os.path.join(snap_base_path, d.d_name) - stx = self.fs.statx(d_full_path, cephfs.CEPH_STATX_MODE, cephfs.AT_SYMLINK_NOFOLLOW) - if stat.S_ISDIR(stx.get('mode')): - if self.is_valid_uuid(d.d_name.decode('utf-8')): - uuid_str = d.d_name - d = self.fs.readdir(dir_handle) - except cephfs.Error as e: - if e.errno == errno.ENOENT: - raise VolumeException(-errno.ENOENT, "snapshot '{0}' does not exist".format(snapname)) - raise VolumeException(-e.args[0], e.args[1]) - - if not uuid_str: - raise VolumeException(-errno.ENOENT, "snapshot '{0}' does not exist".format(snapname)) - - return os.path.join(snap_base_path, uuid_str) - - def create_snapshot(self, snapname): - snappath = self.snapshot_path(snapname) - mksnap(self.fs, snappath) - - def has_pending_clones(self, snapname): - try: - return self.metadata_mgr.section_has_item('clone snaps', snapname) - except MetadataMgrException as me: - if me.errno == -errno.ENOENT: - return False - raise - def remove_snapshot(self, snapname): - if self.has_pending_clones(snapname): - raise VolumeException(-errno.EAGAIN, "snapshot '{0}' has pending clones".format(snapname)) - snappath = self.snapshot_path(snapname) - rmsnap(self.fs, snappath) + super(SubvolumeV2, self).remove_snapshot(snapname) if self.state == SubvolumeStates.STATE_RETAINED and not self.list_snapshots(): self.trash_base_dir() # tickle the volume purge job to purge this entry, using ESTALE raise VolumeException(-errno.ESTALE, "subvolume '{0}' has been removed as the last retained snapshot is removed".format(self.subvolname)) - - def snapshot_info(self, snapname): - snappath = self.snapshot_data_path(snapname) - snap_info = {} - try: - snap_attrs = {'created_at':'ceph.snap.btime', 'size':'ceph.dir.rbytes', - 'data_pool':'ceph.dir.layout.pool'} - for key, val in snap_attrs.items(): - snap_info[key] = self.fs.getxattr(snappath, val) - return {'size': int(snap_info['size']), - 'created_at': str(datetime.fromtimestamp(float(snap_info['created_at']))), - 'data_pool': snap_info['data_pool'].decode('utf-8'), - 'has_pending_clones': "yes" if self.has_pending_clones(snapname) else "no"} - except cephfs.Error as e: - if e.errno == errno.ENOENT: - raise VolumeException(-errno.ENOENT, - "snapshot '{0}' does not exist".format(snapname)) - raise VolumeException(-e.args[0], e.args[1]) - - def list_snapshots(self): - try: - dirpath = os.path.join(self.base_path, - self.vol_spec.snapshot_dir_prefix.encode('utf-8')) - return listdir(self.fs, dirpath) - except VolumeException as ve: - if ve.errno == -errno.ENOENT: - return [] - raise - - def _add_snap_clone(self, track_id, snapname): - self.metadata_mgr.add_section("clone snaps") - self.metadata_mgr.update_section("clone snaps", track_id, snapname) - self.metadata_mgr.flush() - - def _remove_snap_clone(self, track_id): - self.metadata_mgr.remove_option("clone snaps", track_id) - self.metadata_mgr.flush() - - def attach_snapshot(self, snapname, tgt_subvolume): - if not snapname.encode('utf-8') in self.list_snapshots(): - raise VolumeException(-errno.ENOENT, "snapshot '{0}' does not exist".format(snapname)) - try: - create_clone_index(self.fs, self.vol_spec) - with open_clone_index(self.fs, self.vol_spec) as index: - track_idx = index.track(tgt_subvolume.base_path) - self._add_snap_clone(track_idx, snapname) - except (IndexException, MetadataMgrException) as e: - log.warning("error creating clone index: {0}".format(e)) - raise VolumeException(-errno.EINVAL, "error cloning subvolume") - - def detach_snapshot(self, snapname, track_id): - if not snapname.encode('utf-8') in self.list_snapshots(): - raise VolumeException(-errno.ENOENT, "snapshot '{0}' does not exist".format(snapname)) - try: - with open_clone_index(self.fs, self.vol_spec) as index: - index.untrack(track_id) - self._remove_snap_clone(track_id) - except (IndexException, MetadataMgrException) as e: - log.warning("error delining snapshot from clone: {0}".format(e)) - raise VolumeException(-errno.EINVAL, "error delinking snapshot from clone") -- 2.39.5