]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/volumes: Derive v2 from v1 to leverage common methods
authorShyamsundar Ranganathan <srangana@redhat.com>
Wed, 8 Jul 2020 12:15:57 +0000 (08:15 -0400)
committerShyamsundar Ranganathan <srangana@redhat.com>
Thu, 30 Jul 2020 01:14:40 +0000 (21:14 -0400)
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 <srangana@redhat.com>
src/pybind/mgr/volumes/fs/async_cloner.py
src/pybind/mgr/volumes/fs/operations/op_sm.py [deleted file]
src/pybind/mgr/volumes/fs/operations/subvolume.py
src/pybind/mgr/volumes/fs/operations/template.py
src/pybind/mgr/volumes/fs/operations/versions/__init__.py
src/pybind/mgr/volumes/fs/operations/versions/op_sm.py [new file with mode: 0644]
src/pybind/mgr/volumes/fs/operations/versions/subvolume_attrs.py [new file with mode: 0644]
src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py
src/pybind/mgr/volumes/fs/operations/versions/subvolume_v1.py
src/pybind/mgr/volumes/fs/operations/versions/subvolume_v2.py

index d227dc594df4794030d2292e0a95022e26d504b5..ac3b10d6a9c3be4544d16f2d003fcf6d9d2a875e 100644 (file)
@@ -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/op_sm.py b/src/pybind/mgr/volumes/fs/operations/op_sm.py
deleted file mode 100644 (file)
index 17d5ea3..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-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
-
-class TransitionKey(object):
-    def __init__(self, subvol_type, state, action_type):
-        self.transition_key = [subvol_type, state, action_type]
-
-    def __hash__(self):
-        return hash(tuple(self.transition_key))
-
-    def __eq__(self, other):
-        return self.transition_key == other.transition_key
-
-    def __neq__(self, other):
-        return not(self == other)
-
-class SubvolumeOpSm(object):
-    transition_table = {}
-
-    @staticmethod
-    def is_complete_state(state):
-        if not isinstance(state, SubvolumeStates):
-            raise OpSmException(-errno.EINVAL, "unknown state '{0}'".format(state))
-        return state == SubvolumeStates.STATE_COMPLETE
-
-    @staticmethod
-    def is_failed_state(state):
-        if not isinstance(state, SubvolumeStates):
-            raise OpSmException(-errno.EINVAL, "unknown state '{0}'".format(state))
-        return state == SubvolumeStates.STATE_FAILED or state == SubvolumeStates.STATE_CANCELED
-
-    @staticmethod
-    def is_init_state(stm_type, state):
-        if not isinstance(state, SubvolumeStates):
-            raise OpSmException(-errno.EINVAL, "unknown state '{0}'".format(state))
-        return state == SubvolumeOpSm.get_init_state(stm_type)
-
-    @staticmethod
-    def get_init_state(stm_type):
-        if not isinstance(stm_type, SubvolumeTypes):
-            raise OpSmException(-errno.EINVAL, "unknown state machine '{0}'".format(stm_type))
-        init_state =  SubvolumeOpSm.transition_table[TransitionKey(stm_type,
-                                                     SubvolumeStates.STATE_INIT,
-                                                     SubvolumeActions.ACTION_NONE)]
-        if not init_state:
-            raise OpSmException(-errno.ENOENT, "initial state for state machine '{0}' not found".format(stm_type))
-        return init_state
-
-    @staticmethod
-    def transition(stm_type, current_state, action):
-        if not isinstance(stm_type, SubvolumeTypes):
-            raise OpSmException(-errno.EINVAL, "unknown state machine '{0}'".format(stm_type))
-        if not isinstance(current_state, SubvolumeStates):
-            raise OpSmException(-errno.EINVAL, "unknown state '{0}'".format(current_state))
-        if not isinstance(action, SubvolumeActions):
-            raise OpSmException(-errno.EINVAL, "unknown action '{0}'".format(action))
-
-        transition = SubvolumeOpSm.transition_table[TransitionKey(stm_type, current_state, action)]
-        if not transition:
-            raise OpSmException(-errno.EINVAL, "invalid action '{0}' on current state {1} for state machine '{2}'".format(action, current_state, stm_type))
-
-        return transition
-
-SubvolumeOpSm.transition_table = {
-    # state transitions for state machine type TYPE_NORMAL
-    TransitionKey(SubvolumeTypes.TYPE_NORMAL,
-                  SubvolumeStates.STATE_INIT,
-                  SubvolumeActions.ACTION_NONE) : SubvolumeStates.STATE_COMPLETE,
-
-    TransitionKey(SubvolumeTypes.TYPE_NORMAL,
-                  SubvolumeStates.STATE_COMPLETE,
-                  SubvolumeActions.ACTION_RETAINED) : SubvolumeStates.STATE_RETAINED,
-
-    # state transitions for state machine type TYPE_CLONE
-    TransitionKey(SubvolumeTypes.TYPE_CLONE,
-                  SubvolumeStates.STATE_INIT,
-                  SubvolumeActions.ACTION_NONE) : SubvolumeStates.STATE_PENDING,
-
-    TransitionKey(SubvolumeTypes.TYPE_CLONE,
-                  SubvolumeStates.STATE_PENDING,
-                  SubvolumeActions.ACTION_SUCCESS) : SubvolumeStates.STATE_INPROGRESS,
-
-    TransitionKey(SubvolumeTypes.TYPE_CLONE,
-                  SubvolumeStates.STATE_PENDING,
-                  SubvolumeActions.ACTION_CANCELLED) : SubvolumeStates.STATE_CANCELED,
-
-    TransitionKey(SubvolumeTypes.TYPE_CLONE,
-                  SubvolumeStates.STATE_INPROGRESS,
-                  SubvolumeActions.ACTION_SUCCESS) : SubvolumeStates.STATE_COMPLETE,
-
-    TransitionKey(SubvolumeTypes.TYPE_CLONE,
-                  SubvolumeStates.STATE_INPROGRESS,
-                  SubvolumeActions.ACTION_CANCELLED) : SubvolumeStates.STATE_CANCELED,
-
-    TransitionKey(SubvolumeTypes.TYPE_CLONE,
-                  SubvolumeStates.STATE_INPROGRESS,
-                  SubvolumeActions.ACTION_FAILED) : SubvolumeStates.STATE_FAILED,
-
-    TransitionKey(SubvolumeTypes.TYPE_CLONE,
-                  SubvolumeStates.STATE_COMPLETE,
-                  SubvolumeActions.ACTION_RETAINED) : SubvolumeStates.STATE_RETAINED,
-
-    TransitionKey(SubvolumeTypes.TYPE_CLONE,
-                  SubvolumeStates.STATE_CANCELED,
-                  SubvolumeActions.ACTION_RETAINED) : SubvolumeStates.STATE_RETAINED,
-
-    TransitionKey(SubvolumeTypes.TYPE_CLONE,
-                  SubvolumeStates.STATE_FAILED,
-                  SubvolumeActions.ACTION_RETAINED) : SubvolumeStates.STATE_RETAINED,
-}
index b7ef88830b5145826e546696bad57f81e5c432ed..c2afe45f3f6cc615a80418a58486182cafcf8854 100644 (file)
@@ -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
 
index dc82766fcb9ec6f52dd3c0f3442b58907b362735..3fca8dd87f9ec0dc27415d0c25daa632053f14e7 100644 (file)
@@ -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.
index 5ef9aad21a00aa17c2d5477ddf310278f72a2b84..9c038c8dae280e1e22865cd7a6b2e5f1e6de7148 100644 (file)
@@ -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/versions/op_sm.py b/src/pybind/mgr/volumes/fs/operations/versions/op_sm.py
new file mode 100644 (file)
index 0000000..1142600
--- /dev/null
@@ -0,0 +1,114 @@
+import errno
+
+from typing import Dict
+
+from ...exception import OpSmException
+from .subvolume_attrs import SubvolumeTypes, SubvolumeStates, SubvolumeActions
+
+class TransitionKey(object):
+    def __init__(self, subvol_type, state, action_type):
+        self.transition_key = [subvol_type, state, action_type]
+
+    def __hash__(self):
+        return hash(tuple(self.transition_key))
+
+    def __eq__(self, other):
+        return self.transition_key == other.transition_key
+
+    def __neq__(self, other):
+        return not(self == other)
+
+class SubvolumeOpSm(object):
+    transition_table = {} # type: Dict
+
+    @staticmethod
+    def is_complete_state(state):
+        if not isinstance(state, SubvolumeStates):
+            raise OpSmException(-errno.EINVAL, "unknown state '{0}'".format(state))
+        return state == SubvolumeStates.STATE_COMPLETE
+
+    @staticmethod
+    def is_failed_state(state):
+        if not isinstance(state, SubvolumeStates):
+            raise OpSmException(-errno.EINVAL, "unknown state '{0}'".format(state))
+        return state == SubvolumeStates.STATE_FAILED or state == SubvolumeStates.STATE_CANCELED
+
+    @staticmethod
+    def is_init_state(stm_type, state):
+        if not isinstance(state, SubvolumeStates):
+            raise OpSmException(-errno.EINVAL, "unknown state '{0}'".format(state))
+        return state == SubvolumeOpSm.get_init_state(stm_type)
+
+    @staticmethod
+    def get_init_state(stm_type):
+        if not isinstance(stm_type, SubvolumeTypes):
+            raise OpSmException(-errno.EINVAL, "unknown state machine '{0}'".format(stm_type))
+        init_state =  SubvolumeOpSm.transition_table[TransitionKey(stm_type,
+                                                     SubvolumeStates.STATE_INIT,
+                                                     SubvolumeActions.ACTION_NONE)]
+        if not init_state:
+            raise OpSmException(-errno.ENOENT, "initial state for state machine '{0}' not found".format(stm_type))
+        return init_state
+
+    @staticmethod
+    def transition(stm_type, current_state, action):
+        if not isinstance(stm_type, SubvolumeTypes):
+            raise OpSmException(-errno.EINVAL, "unknown state machine '{0}'".format(stm_type))
+        if not isinstance(current_state, SubvolumeStates):
+            raise OpSmException(-errno.EINVAL, "unknown state '{0}'".format(current_state))
+        if not isinstance(action, SubvolumeActions):
+            raise OpSmException(-errno.EINVAL, "unknown action '{0}'".format(action))
+
+        transition = SubvolumeOpSm.transition_table[TransitionKey(stm_type, current_state, action)]
+        if not transition:
+            raise OpSmException(-errno.EINVAL, "invalid action '{0}' on current state {1} for state machine '{2}'".format(action, current_state, stm_type))
+
+        return transition
+
+SubvolumeOpSm.transition_table = {
+    # state transitions for state machine type TYPE_NORMAL
+    TransitionKey(SubvolumeTypes.TYPE_NORMAL,
+                  SubvolumeStates.STATE_INIT,
+                  SubvolumeActions.ACTION_NONE) : SubvolumeStates.STATE_COMPLETE,
+
+    TransitionKey(SubvolumeTypes.TYPE_NORMAL,
+                  SubvolumeStates.STATE_COMPLETE,
+                  SubvolumeActions.ACTION_RETAINED) : SubvolumeStates.STATE_RETAINED,
+
+    # state transitions for state machine type TYPE_CLONE
+    TransitionKey(SubvolumeTypes.TYPE_CLONE,
+                  SubvolumeStates.STATE_INIT,
+                  SubvolumeActions.ACTION_NONE) : SubvolumeStates.STATE_PENDING,
+
+    TransitionKey(SubvolumeTypes.TYPE_CLONE,
+                  SubvolumeStates.STATE_PENDING,
+                  SubvolumeActions.ACTION_SUCCESS) : SubvolumeStates.STATE_INPROGRESS,
+
+    TransitionKey(SubvolumeTypes.TYPE_CLONE,
+                  SubvolumeStates.STATE_PENDING,
+                  SubvolumeActions.ACTION_CANCELLED) : SubvolumeStates.STATE_CANCELED,
+
+    TransitionKey(SubvolumeTypes.TYPE_CLONE,
+                  SubvolumeStates.STATE_INPROGRESS,
+                  SubvolumeActions.ACTION_SUCCESS) : SubvolumeStates.STATE_COMPLETE,
+
+    TransitionKey(SubvolumeTypes.TYPE_CLONE,
+                  SubvolumeStates.STATE_INPROGRESS,
+                  SubvolumeActions.ACTION_CANCELLED) : SubvolumeStates.STATE_CANCELED,
+
+    TransitionKey(SubvolumeTypes.TYPE_CLONE,
+                  SubvolumeStates.STATE_INPROGRESS,
+                  SubvolumeActions.ACTION_FAILED) : SubvolumeStates.STATE_FAILED,
+
+    TransitionKey(SubvolumeTypes.TYPE_CLONE,
+                  SubvolumeStates.STATE_COMPLETE,
+                  SubvolumeActions.ACTION_RETAINED) : SubvolumeStates.STATE_RETAINED,
+
+    TransitionKey(SubvolumeTypes.TYPE_CLONE,
+                  SubvolumeStates.STATE_CANCELED,
+                  SubvolumeActions.ACTION_RETAINED) : SubvolumeStates.STATE_RETAINED,
+
+    TransitionKey(SubvolumeTypes.TYPE_CLONE,
+                  SubvolumeStates.STATE_FAILED,
+                  SubvolumeActions.ACTION_RETAINED) : SubvolumeStates.STATE_RETAINED,
+}
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 (file)
index 0000000..ec7138c
--- /dev/null
@@ -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"
index 6de9916ef01f9166a1d8e61b34a873b819af4aea..07690009b13ec2d4f12a57094a2f3444df6cc0d3 100644 (file)
@@ -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
index e587fd33f3a47dedb441c42095ef789df4f4a4d1..b1ca48d24be685446f8deffa9d916ded4c015f24 100644 (file)
@@ -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:
index 23127d3dbb6225a882c998695385bd5beee637ea..14397b59d407e380725ddbf7243fcbdb63b53dbb 100644 (file)
@@ -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/<group-name>/<subvolume-name>/<uuid>/
@@ -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")