From 8d68f1af3a12ce096e52024aa710e03e0006ba0c Mon Sep 17 00:00:00 2001 From: Venky Shankar Date: Mon, 2 Dec 2019 03:08:45 -0500 Subject: [PATCH] mgr/volumes: add protect/unprotect and snap clone interface Signed-off-by: Venky Shankar --- .../mgr/volumes/fs/operations/template.py | 50 ++++++++++ .../operations/versions/metadata_manager.py | 10 ++ .../fs/operations/versions/subvolume_v1.py | 93 ++++++++++++++++++- 3 files changed, 152 insertions(+), 1 deletion(-) diff --git a/src/pybind/mgr/volumes/fs/operations/template.py b/src/pybind/mgr/volumes/fs/operations/template.py index 14785e18dba..fa903b79369 100644 --- a/src/pybind/mgr/volumes/fs/operations/template.py +++ b/src/pybind/mgr/volumes/fs/operations/template.py @@ -116,3 +116,53 @@ class SubvolumeTemplate(object): :return: None """ raise VolumeException(-errno.ENOTSUP, "operation not supported.") + + def is_snapshot_protected(self, snapname): + """ + check if a snapshot is protected. + + :param: snapname: snapshot to protect + :return: True if the snapshot is protected, False otherwise. + """ + raise VolumeException(-errno.ENOTSUP, "operation not supported.") + + def protect_snapshot(self, snapname): + """ + protect a subvolume snapshot. only a protected snapshot can be cloned. + + :param: snapname: snapshot to protect + :return: None + """ + raise VolumeException(-errno.ENOTSUP, "operation not supported.") + + def unprotect_snapshot(self, snapname): + """ + unprotect a subvolume snapshot. fail to unprotect if there are pending + clone operations on the snapshot. + + :param: snapname: snapshot to unprotect + :return: None + """ + raise VolumeException(-errno.ENOTSUP, "operation not supported.") + + def attach_snapshot(self, snapname, tgt_subvolume): + """ + attach a snapshot to a target cloned subvolume. the target subvolume + should be an empty subvolume (type "clone") in "pending" state. + + :param: snapname: snapshot to attach to a clone + :param: tgt_subvolume: target clone subvolume + :return: None + """ + raise VolumeException(-errno.ENOTSUP, "operation not supported.") + + def detach_snapshot(self, snapname, tgt_subvolume): + """ + detach a snapshot from a target cloned subvolume. the target subvolume + should either be in "failed" or "completed" state. + + :param: snapname: snapshot to detach from a clone + :param: tgt_subvolume: target clone subvolume + :return: None + """ + raise VolumeException(-errno.ENOTSUP, "operation not supported.") diff --git a/src/pybind/mgr/volumes/fs/operations/versions/metadata_manager.py b/src/pybind/mgr/volumes/fs/operations/versions/metadata_manager.py index 34ffe918df4..c54da537e86 100644 --- a/src/pybind/mgr/volumes/fs/operations/versions/metadata_manager.py +++ b/src/pybind/mgr/volumes/fs/operations/versions/metadata_manager.py @@ -55,6 +55,11 @@ class MetadataManager(object): self.fs.close(fd) def flush(self): + # cull empty sections + for section in list(self.config.sections()): + if len(self.config.items(section)) == 0: + self.config.remove_section(section) + conf_data = StringIO() self.config.write(conf_data) conf_data.seek(0) @@ -125,3 +130,8 @@ class MetadataManager(object): def get_global_option(self, key): return self.get_option(MetadataManager.GLOBAL_SECTION, key) + + def section_has_item(self, section, item): + if not self.config.has_section(section): + raise MetadataMgrException(-errno.ENOENT, "section '{0}' does not exist".format(section)) + return item in [v[1] for v in self.config.items(section)] 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 026ed32489a..c767727ab85 100644 --- a/src/pybind/mgr/volumes/fs/operations/versions/subvolume_v1.py +++ b/src/pybind/mgr/volumes/fs/operations/versions/subvolume_v1.py @@ -11,9 +11,11 @@ from .subvolume_base import SubvolumeBase from ..op_sm import OpSm from ..template import SubvolumeTemplate from ..snapshot_util import mksnap, rmsnap -from ...exception import OpSmException, VolumeException, MetadataMgrException +from ...exception import IndexException, OpSmException, VolumeException, MetadataMgrException from ...fs_util import listdir +from ..clone_index import open_clone_index, create_clone_index + log = logging.getLogger(__name__) class SubvolumeV1(SubvolumeBase, SubvolumeTemplate): @@ -149,7 +151,29 @@ class SubvolumeV1(SubvolumeBase, SubvolumeTemplate): snapname.encode('utf-8')) mksnap(self.fs, snappath) + def is_snapshot_protected(self, snapname): + try: + self.metadata_mgr.get_option('protected snaps', snapname) + except MetadataMgrException as me: + if me.errno == -errno.ENOENT: + return False + else: + log.warn("error checking protected snap {0} ({1})".format(snapname, me)) + raise VolumeException(-errno.EINVAL, "snapshot protection check failed") + else: + return True + + 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.is_snapshot_protected(snapname): + raise VolumeException(-errno.EINVAL, "snapshot '{0}' is protected".format(snapname)) snappath = os.path.join(self.path, self.vol_spec.snapshot_dir_prefix.encode('utf-8'), snapname.encode('utf-8')) @@ -164,3 +188,70 @@ class SubvolumeV1(SubvolumeBase, SubvolumeTemplate): if ve.errno == -errno.ENOENT: return [] raise + + def _protect_snapshot(self, snapname): + try: + self.metadata_mgr.add_section("protected snaps") + self.metadata_mgr.update_section("protected snaps", snapname, "1") + self.metadata_mgr.flush() + except MetadataMgrException as me: + log.warn("error updating protected snap list ({0})".format(me)) + raise VolumeException(-errno.EINVAL, "error protecting snapshot") + + def _unprotect_snapshot(self, snapname): + try: + self.metadata_mgr.remove_option("protected snaps", snapname) + self.metadata_mgr.flush() + except MetadataMgrException as me: + log.warn("error updating protected snap list ({0})".format(me)) + raise VolumeException(-errno.EINVAL, "error unprotecting snapshot") + + def protect_snapshot(self, snapname): + if not snapname.encode('utf-8') in self.list_snapshots(): + raise VolumeException(-errno.ENOENT, "snapshot '{0}' does not exist".format(snapname)) + if self.is_snapshot_protected(snapname): + raise VolumeException(-errno.EEXIST, "snapshot '{0}' is already protected".format(snapname)) + self._protect_snapshot(snapname) + + def unprotect_snapshot(self, snapname): + if not snapname.encode('utf-8') in self.list_snapshots(): + raise VolumeException(-errno.ENOENT, "snapshot '{0}' does not exist".format(snapname)) + if not self.is_snapshot_protected(snapname): + raise VolumeException(-errno.EEXIST, "snapshot '{0}' is not protected".format(snapname)) + if self.has_pending_clones(snapname): + raise VolumeException(-errno.EEXIST, "snapshot '{0}' has pending clones".format(snapname)) + self._unprotect_snapshot(snapname) + + 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)) + if not self.is_snapshot_protected(snapname): + raise VolumeException(-errno.EINVAL, "snapshot '{0}' is not protected".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.warn("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.warn("error delining snapshot from clone: {0}".format(e)) + raise VolumeException(-errno.EINVAL, "error delinking snapshot from clone") -- 2.39.5