Skip to content

Commit

Permalink
Merge PR ceph#38652 into master
Browse files Browse the repository at this point in the history
* refs/pull/38652/head:
	mgr/volumes: Filter inherited snapshots while listing snapshots

Reviewed-by: Patrick Donnelly <[email protected]>
Reviewed-by: Ramana Raja <[email protected]>
  • Loading branch information
batrick committed Jan 13, 2021
2 parents 5ab4630 + bd49b64 commit fcdc98e
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 6 deletions.
185 changes: 185 additions & 0 deletions qa/tasks/cephfs/test_volumes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1941,6 +1941,191 @@ def test_subvolume_snapshot_ls(self):
# verify trash dir is clean
self._wait_for_trash_empty()

def test_subvolume_inherited_snapshot_ls(self):
# tests the scenario where 'fs subvolume snapshot ls' command
# should not list inherited snapshots created as part of snapshot
# at ancestral level

snapshots = []
subvolume = self._generate_random_subvolume_name()
group = self._generate_random_group_name()
snap_count = 3

# create group
self._fs_cmd("subvolumegroup", "create", self.volname, group)

# create subvolume in group
self._fs_cmd("subvolume", "create", self.volname, subvolume, "--group_name", group)

# create subvolume snapshots
snapshots = self._generate_random_snapshot_name(snap_count)
for snapshot in snapshots:
self._fs_cmd("subvolume", "snapshot", "create", self.volname, subvolume, snapshot, group)

# Create snapshot at ancestral level
ancestral_snappath1 = os.path.join(".", "volumes", group, ".snap", "ancestral_snap_1")
ancestral_snappath2 = os.path.join(".", "volumes", group, ".snap", "ancestral_snap_2")
self.mount_a.run_shell(['mkdir', '-p', ancestral_snappath1, ancestral_snappath2])

subvolsnapshotls = json.loads(self._fs_cmd('subvolume', 'snapshot', 'ls', self.volname, subvolume, group))
self.assertEqual(len(subvolsnapshotls), snap_count)

# remove ancestral snapshots
self.mount_a.run_shell(['rmdir', ancestral_snappath1, ancestral_snappath2])

# remove snapshot
for snapshot in snapshots:
self._fs_cmd("subvolume", "snapshot", "rm", self.volname, subvolume, snapshot, group)

# remove subvolume
self._fs_cmd("subvolume", "rm", self.volname, subvolume, group)

# verify trash dir is clean
self._wait_for_trash_empty()

# remove group
self._fs_cmd("subvolumegroup", "rm", self.volname, group)

def test_subvolume_inherited_snapshot_info(self):
"""
tests the scenario where 'fs subvolume snapshot info' command
should fail for inherited snapshots created as part of snapshot
at ancestral level
"""

subvolume = self._generate_random_subvolume_name()
group = self._generate_random_group_name()

# create group
self._fs_cmd("subvolumegroup", "create", self.volname, group)

# create subvolume in group
self._fs_cmd("subvolume", "create", self.volname, subvolume, "--group_name", group)

# Create snapshot at ancestral level
ancestral_snap_name = "ancestral_snap_1"
ancestral_snappath1 = os.path.join(".", "volumes", group, ".snap", ancestral_snap_name)
self.mount_a.run_shell(['mkdir', '-p', ancestral_snappath1])

# Validate existence of inherited snapshot
group_path = os.path.join(".", "volumes", group)
inode_number_group_dir = int(self.mount_a.run_shell(['stat', '-c' '%i', group_path]).stdout.getvalue().strip())
inherited_snap = "_{0}_{1}".format(ancestral_snap_name, inode_number_group_dir)
inherited_snappath = os.path.join(".", "volumes", group, subvolume,".snap", inherited_snap)
self.mount_a.run_shell(['ls', inherited_snappath])

# snapshot info on inherited snapshot
try:
self._get_subvolume_snapshot_info(self.volname, subvolume, inherited_snap, group)
except CommandFailedError as ce:
self.assertEqual(ce.exitstatus, errno.EINVAL, "invalid error code on snapshot info of inherited snapshot")
else:
self.fail("expected snapshot info of inherited snapshot to fail")

# remove ancestral snapshots
self.mount_a.run_shell(['rmdir', ancestral_snappath1])

# remove subvolume
self._fs_cmd("subvolume", "rm", self.volname, subvolume, "--group_name", group)

# verify trash dir is clean
self._wait_for_trash_empty()

# remove group
self._fs_cmd("subvolumegroup", "rm", self.volname, group)

def test_subvolume_inherited_snapshot_rm(self):
"""
tests the scenario where 'fs subvolume snapshot rm' command
should fail for inherited snapshots created as part of snapshot
at ancestral level
"""

subvolume = self._generate_random_subvolume_name()
group = self._generate_random_group_name()

# create group
self._fs_cmd("subvolumegroup", "create", self.volname, group)

# create subvolume in group
self._fs_cmd("subvolume", "create", self.volname, subvolume, "--group_name", group)

# Create snapshot at ancestral level
ancestral_snap_name = "ancestral_snap_1"
ancestral_snappath1 = os.path.join(".", "volumes", group, ".snap", ancestral_snap_name)
self.mount_a.run_shell(['mkdir', '-p', ancestral_snappath1])

# Validate existence of inherited snap
group_path = os.path.join(".", "volumes", group)
inode_number_group_dir = int(self.mount_a.run_shell(['stat', '-c' '%i', group_path]).stdout.getvalue().strip())
inherited_snap = "_{0}_{1}".format(ancestral_snap_name, inode_number_group_dir)
inherited_snappath = os.path.join(".", "volumes", group, subvolume,".snap", inherited_snap)
self.mount_a.run_shell(['ls', inherited_snappath])

# inherited snapshot should not be deletable
try:
self._fs_cmd("subvolume", "snapshot", "rm", self.volname, subvolume, inherited_snap, "--group_name", group)
except CommandFailedError as ce:
self.assertEqual(ce.exitstatus, errno.EINVAL, msg="invalid error code when removing inherited snapshot")
else:
self.fail("expected removing inheirted snapshot to fail")

# remove ancestral snapshots
self.mount_a.run_shell(['rmdir', ancestral_snappath1])

# remove subvolume
self._fs_cmd("subvolume", "rm", self.volname, subvolume, group)

# verify trash dir is clean
self._wait_for_trash_empty()

# remove group
self._fs_cmd("subvolumegroup", "rm", self.volname, group)

def test_subvolume_subvolumegroup_snapshot_name_conflict(self):
"""
tests the scenario where creation of subvolume snapshot name
with same name as it's subvolumegroup snapshot name. This should
fail.
"""

subvolume = self._generate_random_subvolume_name()
group = self._generate_random_group_name()
group_snapshot = self._generate_random_snapshot_name()

# create group
self._fs_cmd("subvolumegroup", "create", self.volname, group)

# create subvolume in group
self._fs_cmd("subvolume", "create", self.volname, subvolume, "--group_name", group)

# Create subvolumegroup snapshot
group_snapshot_path = os.path.join(".", "volumes", group, ".snap", group_snapshot)
self.mount_a.run_shell(['mkdir', '-p', group_snapshot_path])

# Validate existence of subvolumegroup snapshot
self.mount_a.run_shell(['ls', group_snapshot_path])

# Creation of subvolume snapshot with it's subvolumegroup snapshot name should fail
try:
self._fs_cmd("subvolume", "snapshot", "create", self.volname, subvolume, group_snapshot, "--group_name", group)
except CommandFailedError as ce:
self.assertEqual(ce.exitstatus, errno.EINVAL, msg="invalid error code when creating subvolume snapshot with same name as subvolume group snapshot")
else:
self.fail("expected subvolume snapshot creation with same name as subvolumegroup snapshot to fail")

# remove subvolumegroup snapshot
self.mount_a.run_shell(['rmdir', group_snapshot_path])

# remove subvolume
self._fs_cmd("subvolume", "rm", self.volname, subvolume, group)

# verify trash dir is clean
self._wait_for_trash_empty()

# remove group
self._fs_cmd("subvolumegroup", "rm", self.volname, group)

def test_subvolume_retain_snapshot_invalid_recreate(self):
"""
ensure retained subvolume recreate does not leave any incarnations in the subvolume and trash
Expand Down
28 changes: 28 additions & 0 deletions src/pybind/mgr/volumes/fs/fs_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,34 @@ def listdir(fs, dirpath):
raise VolumeException(-e.args[0], e.args[1])
return dirs

def is_inherited_snap(snapname):
"""
Returns True if the snapname is inherited else False
"""
return snapname.startswith("_")

def listsnaps(fs, volspec, snapdirpath, filter_inherited_snaps=False):
"""
Get the snap names from a given snap directory path
"""
if os.path.basename(snapdirpath) != volspec.snapshot_prefix.encode('utf-8'):
raise VolumeException(-errno.EINVAL, "Not a snap directory: {0}".format(snapdirpath))
snaps = []
try:
with fs.opendir(snapdirpath) as dir_handle:
d = fs.readdir(dir_handle)
while d:
if (d.d_name not in (b".", b"..")) and d.is_dir():
d_name = d.d_name.decode('utf-8')
if not is_inherited_snap(d_name):
snaps.append(d.d_name)
elif is_inherited_snap(d_name) and not filter_inherited_snaps:
snaps.append(d.d_name)
d = fs.readdir(dir_handle)
except cephfs.Error as e:
raise VolumeException(-e.args[0], e.args[1])
return snaps

def list_one_entry_at_a_time(fs, dirpath):
"""
Get a directory entry (one entry a time)
Expand Down
4 changes: 2 additions & 2 deletions src/pybind/mgr/volumes/fs/operations/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .snapshot_util import mksnap, rmsnap
from .pin_util import pin
from .template import GroupTemplate
from ..fs_util import listdir, get_ancestor_xattr
from ..fs_util import listdir, listsnaps, get_ancestor_xattr
from ..exception import VolumeException

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -81,7 +81,7 @@ def list_snapshots(self):
try:
dirpath = os.path.join(self.path,
self.vol_spec.snapshot_dir_prefix.encode('utf-8'))
return listdir(self.fs, dirpath)
return listsnaps(self.fs, self.vol_spec, dirpath, filter_inherited_snaps=True)
except VolumeException as ve:
if ve.errno == -errno.ENOENT:
return []
Expand Down
22 changes: 18 additions & 4 deletions src/pybind/mgr/volumes/fs/operations/versions/subvolume_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from ..snapshot_util import mksnap, rmsnap
from ..access import allow_access, deny_access
from ...exception import IndexException, OpSmException, VolumeException, MetadataMgrException
from ...fs_util import listdir
from ...fs_util import listsnaps, is_inherited_snap
from ..template import SubvolumeOpType

from ..clone_index import open_clone_index, create_clone_index
Expand Down Expand Up @@ -344,8 +344,19 @@ def resize(self, newsize, noshrink):
return self._resize(subvol_path, newsize, noshrink)

def create_snapshot(self, snapname):
snappath = self.snapshot_path(snapname)
mksnap(self.fs, snappath)
try:
group_snapshot_path = os.path.join(self.group.path,
self.vol_spec.snapshot_dir_prefix.encode('utf-8'),
snapname.encode('utf-8'))
self.fs.stat(group_snapshot_path)
except cephfs.Error as e:
if e.args[0] == errno.ENOENT:
snappath = self.snapshot_path(snapname)
mksnap(self.fs, snappath)
else:
raise VolumeException(-e.args[0], e.args[1])
else:
raise VolumeException(-errno.EINVAL, "subvolumegroup and subvolume snapshot name can't be same")

def has_pending_clones(self, snapname):
try:
Expand All @@ -362,6 +373,9 @@ def remove_snapshot(self, snapname):
rmsnap(self.fs, snappath)

def snapshot_info(self, snapname):
if is_inherited_snap(snapname):
raise VolumeException(-errno.EINVAL,
"snapshot name '{0}' is invalid".format(snapname))
snappath = self.snapshot_data_path(snapname)
snap_info = {}
try:
Expand All @@ -382,7 +396,7 @@ def snapshot_info(self, snapname):
def list_snapshots(self):
try:
dirpath = self.snapshot_base_path()
return listdir(self.fs, dirpath)
return listsnaps(self.fs, self.vol_spec, dirpath, filter_inherited_snaps=True)
except VolumeException as ve:
if ve.errno == -errno.ENOENT:
return []
Expand Down

0 comments on commit fcdc98e

Please sign in to comment.