]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/volumes: add protect/unprotect and snap clone interface
authorVenky Shankar <vshankar@redhat.com>
Mon, 2 Dec 2019 08:08:45 +0000 (03:08 -0500)
committerVenky Shankar <vshankar@redhat.com>
Fri, 31 Jan 2020 10:09:14 +0000 (05:09 -0500)
Signed-off-by: Venky Shankar <vshankar@redhat.com>
src/pybind/mgr/volumes/fs/operations/template.py
src/pybind/mgr/volumes/fs/operations/versions/metadata_manager.py
src/pybind/mgr/volumes/fs/operations/versions/subvolume_v1.py

index 14785e18dba7953bd2bbbc7c9109702187f634f1..fa903b793696d0e77a429751a9166b19869848f9 100644 (file)
@@ -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.")
index 34ffe918df4ebd1f9d1a91b26514b6f1f50aba67..c54da537e863b1855dbd7481f4ac9767b06487cb 100644 (file)
@@ -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)]
index 026ed32489a6b5ce55fe5a2201cfe3a9fa967069..c767727ab854f14dac0cb2be586508579256f833 100644 (file)
@@ -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")