Skip to content

Commit

Permalink
mgr/volumes: add protect/unprotect and snap clone interface
Browse files Browse the repository at this point in the history
Signed-off-by: Venky Shankar <vshankar@redhat.com>
  • Loading branch information
vshankar committed Jan 31, 2020
1 parent 461909b commit 8d68f1a
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 1 deletion.
50 changes: 50 additions & 0 deletions src/pybind/mgr/volumes/fs/operations/template.py
Expand Up @@ -116,3 +116,53 @@ def list_snapshots(self):
: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.")
10 changes: 10 additions & 0 deletions src/pybind/mgr/volumes/fs/operations/versions/metadata_manager.py
Expand Up @@ -55,6 +55,11 @@ def refresh(self):
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)
Expand Down Expand Up @@ -125,3 +130,8 @@ def get_option(self, section, key):

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)]
93 changes: 92 additions & 1 deletion src/pybind/mgr/volumes/fs/operations/versions/subvolume_v1.py
Expand Up @@ -11,9 +11,11 @@
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):
Expand Down Expand Up @@ -149,7 +151,29 @@ def create_snapshot(self, snapname):
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'))
Expand All @@ -164,3 +188,70 @@ def list_snapshots(self):
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")

0 comments on commit 8d68f1a

Please sign in to comment.