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__)
+++ /dev/null
-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,
-}
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
CLONE_INTERNAL = 'clone_internal'
class SubvolumeTemplate(object):
- VERSION = None
+ VERSION = None # type: int
@staticmethod
def version():
"""
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.
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
--- /dev/null
+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,
+}
--- /dev/null
+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"
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"
@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
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
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:
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)
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',
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:
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>/
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)
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:
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:
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)
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")