]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr / volumes: carve out subvolume operations as a separate class
authorVenky Shankar <vshankar@redhat.com>
Fri, 17 May 2019 05:40:37 +0000 (01:40 -0400)
committerVenky Shankar <vshankar@redhat.com>
Fri, 14 Jun 2019 04:26:51 +0000 (00:26 -0400)
Move (and refactor) subvolume specific operations from main module
source to SubVolume class in a new subvolume source. Also, provide
hooks in VolumeClient to forward subvolume specific opertaions to
this class.

Signed-off-by: Venky Shankar <vshankar@redhat.com>
src/pybind/mgr/volumes/fs/subvolume.py
src/pybind/mgr/volumes/fs/volume.py
src/pybind/mgr/volumes/module.py

index 9e476c7a7c86b49c3bab0d3fdbc8917fcf16f127..01767cf331ae5b70d19ef17970bd23291c4aaa0e 100644 (file)
@@ -4,38 +4,16 @@ Copyright (C) 2019 Red Hat, Inc.
 LGPL2.1.  See file COPYING.
 """
 
-import errno
 import logging
 import os
 
 import cephfs
-import rados
 
+from .subvolspec import SubvolumeSpec
 
 log = logging.getLogger(__name__)
 
-# Reserved subvolume group name which we use in paths for subvolumes
-# that are not assigned to a group (i.e. created with group=None)
-NO_GROUP_NAME = "_nogroup"
-
-
-class SubvolumePath(object):
-    """
-    Identify a subvolume's path as group->subvolume
-    The Subvolume ID is a unique identifier, but this is a much more
-    helpful thing to pass around.
-    """
-    def __init__(self, group_id, subvolume_id):
-        self.group_id = group_id
-        self.subvolume_id = subvolume_id
-        assert self.group_id != NO_GROUP_NAME
-        assert self.subvolume_id != "" and self.subvolume_id is not None
-
-    def __str__(self):
-        return "{0}/{1}".format(self.group_id, self.subvolume_id)
-
-
-class SubvolumeClient(object):
+class SubVolume(object):
     """
     Combine libcephfs and librados interfaces to implement a
     'Subvolume' concept implemented as a cephfs directory.
@@ -52,70 +30,14 @@ class SubvolumeClient(object):
     or cephfs.Error exceptions in unexpected situations.
     """
 
-    # Where shall we create our subvolumes?
-    DEFAULT_SUBVOL_PREFIX = "/volumes"
-    DEFAULT_NS_PREFIX = "fsvolumens_"
 
-    def __init__(self, mgr, subvolume_prefix=None, pool_ns_prefix=None, fs_name=None):
+    def __init__(self, mgr, fs_name=None):
         self.fs = None
         self.fs_name = fs_name
         self.connected = False
 
         self.rados = mgr.rados
 
-        self.subvolume_prefix = subvolume_prefix if subvolume_prefix else self.DEFAULT_SUBVOL_PREFIX
-        self.pool_ns_prefix = pool_ns_prefix if pool_ns_prefix else self.DEFAULT_NS_PREFIX
-
-    def _subvolume_path(self, subvolume_path):
-        """
-        Determine the path within CephFS where this subvolume will live
-        :return: absolute path (string)
-        """
-        return os.path.join(
-            self.subvolume_prefix,
-            subvolume_path.group_id if subvolume_path.group_id is not None else NO_GROUP_NAME,
-            subvolume_path.subvolume_id)
-
-    def _group_path(self, group_id):
-        """
-        Determine the path within CephFS where this subvolume group will live
-        :return: absolute path (string)
-        """
-        if group_id is None:
-            raise ValueError("group_id may not be None")
-
-        return os.path.join(
-            self.subvolume_prefix,
-            group_id
-        )
-
-    def connect(self):
-        log.debug("Connecting to cephfs...")
-        self.fs = cephfs.LibCephFS(rados_inst=self.rados)
-        log.debug("CephFS initializing...")
-        self.fs.init()
-        log.debug("CephFS mounting...")
-        self.fs.mount(filesystem_name=self.fs_name.encode('utf-8'))
-        log.debug("Connection to cephfs complete")
-
-    def disconnect(self):
-        log.info("disconnect")
-        if self.fs:
-            log.debug("Disconnecting cephfs...")
-            self.fs.shutdown()
-            self.fs = None
-            log.debug("Disconnecting cephfs complete")
-
-    def __enter__(self):
-        self.connect()
-        return self
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        self.disconnect()
-
-    def __del__(self):
-        self.disconnect()
-
     def _mkdir_p(self, path, mode=0o755):
         try:
             self.fs.stat(path)
@@ -133,80 +55,68 @@ class SubvolumeClient(object):
             except cephfs.ObjectNotFound:
                 self.fs.mkdir(subpath, mode)
 
-    def create_group(self, group_id, mode=0o755):
-        path = self._group_path(group_id)
-        self._mkdir_p(path, mode)
+    ### basic subvolume operations
 
-    def delete_group(self, group_id):
-        path = self._group_path(group_id)
-        self.fs.rmdir(path)
-
-    def create_subvolume(self, subvolume_path, size=None, namespace_isolated=True, mode=0o755):
+    def create_subvolume(self, spec, size=None, namespace_isolated=True, mode=0o755):
         """
         Set up metadata, pools and auth for a subvolume.
 
         This function is idempotent.  It is safe to call this again
         for an already-created subvolume, even if it is in use.
 
-        :param subvolume_path: SubvolumePath instance
+        :param spec: subvolume path specification
         :param size: In bytes, or None for no size limit
         :param namespace_isolated: If true, use separate RADOS namespace for this subvolume
         :return: None
         """
-        path = self._subvolume_path(subvolume_path)
-        log.info("creating subvolume with path: {0}".format(path))
+        subvolpath = spec.subvolume_path
+        log.info("creating subvolume with path: {0}".format(subvolpath))
 
-        self._mkdir_p(path, mode)
+        self._mkdir_p(subvolpath, mode)
 
         if size is not None:
-            self.fs.setxattr(path, 'ceph.quota.max_bytes', str(size).encode('utf-8'), 0)
+            self.fs.setxattr(subvolpath, 'ceph.quota.max_bytes', str(size).encode('utf-8'), 0)
 
-        # enforce security isolation, use separate namespace for this subvolume
+        xattr_key = xattr_val = None
         if namespace_isolated:
-            namespace = "{0}{1}".format(self.pool_ns_prefix, subvolume_path.subvolume_id)
-            log.info("creating subvolume with path: {0}, using rados namespace {1} to isolate data.".format(subvolume_path, namespace))
-            self.fs.setxattr(path, 'ceph.dir.layout.pool_namespace',
-                             namespace.encode('utf-8'), 0)
+            # enforce security isolation, use separate namespace for this subvolume
+            xattr_key = 'ceph.dir.layout.pool_namespace'
+            xattr_val = spec.fs_namespace
         else:
             # If subvolume's namespace layout is not set, then the subvolume's pool
             # layout remains unset and will undesirably change with ancestor's
             # pool layout changes.
-            pool_name = self._get_ancestor_xattr(path, "ceph.dir.layout.pool")
-            self.fs.setxattr(path, 'ceph.dir.layout.pool',
-                             pool_name.encode('utf-8'), 0)
+            xattr_key = 'ceph.dir.layout.pool'
+            xattr_val = self._get_ancestor_xattr(subvolpath, "ceph.dir.layout.pool")
+        # TODO: handle error...
+        self.fs.setxattr(subvolpath, xattr_key, xattr_val.encode('utf-8'), 0)
 
-    def delete_subvolume(self, subvolume_path):
+    def remove_subvolume(self, spec):
         """
         Make a subvolume inaccessible to guests.  This function is idempotent.
         This is the fast part of tearing down a subvolume: you must also later
         call purge_subvolume, which is the slow part.
 
-        :param subvolume_path: Same identifier used in create_subvolume
+        :param spec: subvolume path specification
         :return: None
         """
 
-        path = self._subvolume_path(subvolume_path)
-        log.info("deleting subvolume with path: {0}".format(path))
-
-        # Create the trash folder if it doesn't already exist
-        trash = os.path.join(self.subvolume_prefix, "_deleting")
-        self._mkdir_p(trash)
+        subvolpath = spec.subvolume_path
+        log.info("deleting subvolume with path: {0}".format(subvolpath))
 
-        # We'll move it to here
-        trashed_subvolume = os.path.join(trash, subvolume_path.subvolume_id)
+        # Create the trash directory if it doesn't already exist
+        trashdir = spec.trash_dir
+        self._mkdir_p(trashdir)
 
-        # Move the subvolume to the trash folder
-        self.fs.rename(path, trashed_subvolume)
+        trashpath = spec.trash_path
+        self.fs.rename(subvolpath, trashpath)
 
-    def purge_subvolume(self, subvolume_path):
+    def purge_subvolume(self, spec):
         """
         Finish clearing up a subvolume that was previously passed to delete_subvolume.  This
         function is idempotent.
         """
 
-        trash = os.path.join(self.subvolume_prefix, "_deleting")
-        trashed_subvolume = os.path.join(trash, subvolume_path.subvolume_id)
-
         def rmtree(root_path):
             log.debug("rmtree {0}".format(root_path))
             try:
@@ -228,11 +138,36 @@ class SubvolumeClient(object):
 
                 d = self.fs.readdir(dir_handle)
             self.fs.closedir(dir_handle)
-
             self.fs.rmdir(root_path)
 
-        rmtree(trashed_subvolume)
+        trashpath = spec.trash_path
+        rmtree(trashpath)
+
+    def get_subvolume_path(self, spec):
+        path = spec.subvolume_path
+        try:
+            self.fs.stat(path)
+        except cephfs.ObjectNotFound:
+            return None
+        return path
+
+    ### group operations
+
+    def create_group(self, spec, mode=0o755):
+        path = spec.group_path
+        self._mkdir_p(path, mode)
+
+    def remove_group(self, spec):
+        path = spec.group_path
+        self.fs.rmdir(path)
 
+    def get_group_path(self, spec):
+        path = spec.group_path
+        try:
+            self.fs.stat(path)
+        except cephfs.ObjectNotFound:
+            return None
+        return path
 
     def _get_ancestor_xattr(self, path, attr):
         """
@@ -252,56 +187,67 @@ class SubvolumeClient(object):
             else:
                 return self._get_ancestor_xattr(os.path.split(path)[0], attr)
 
-    def get_group_path(self, group_id):
-        path = self._group_path(group_id)
-        try:
-            self.fs.stat(path)
-        except cephfs.ObjectNotFound:
-            return None
-        return path
+    ### snapshot operations
 
-    def get_subvolume_path(self, subvolume_path):
-        path = self._subvolume_path(subvolume_path)
-        try:
-            self.fs.stat(path)
-        except cephfs.ObjectNotFound:
-            return None
-        return path
-
-    def _snapshot_path(self, dir_path, snapshot_name):
-        return os.path.join(
-            dir_path, self.rados.conf_get('client_snapdir'), snapshot_name
-        )
-
-    def _snapshot_create(self, dir_path, snapshot_name, mode=0o755):
+    def _snapshot_create(self, snappath, mode=0o755):
         """
         Create a snapshot, or do nothing if it already exists.
         """
-        snapshot_path = self._snapshot_path(dir_path, snapshot_name)
         try:
-            self.fs.stat(snapshot_path)
+            self.fs.stat(snappath)
         except cephfs.ObjectNotFound:
-            self.fs.mkdir(snapshot_path, mode)
+            self.fs.mkdir(snappath, mode)
         else:
-            log.warn("Snapshot '{0}' already exists".format(snapshot_name))
+            log.warn("Snapshot '{0}' already exists".format(snappath))
 
-
-    def _snapshot_delete(self, dir_path, snapshot_name):
+    def _snapshot_delete(self, snappath):
         """
         Remove a snapshot, or do nothing if it doesn't exist.
         """
-        snapshot_path = self._snapshot_path(dir_path, snapshot_name)
-        self.fs.stat(snapshot_path)
-        self.fs.rmdir(snapshot_path)
+        self.fs.stat(snappath)
+        self.fs.rmdir(snappath)
+
+    def create_subvolume_snapshot(self, spec, snapname, mode=0o755):
+        snappath = spec.make_subvol_snap_path(self.rados.conf_get('client_snapdir'), snapname)
+        self._snapshot_create(snappath, mode)
+
+    def remove_subvolume_snapshot(self, spec, snapname):
+        snappath = spec.make_subvol_snap_path(self.rados.conf_get('client_snapdir'), snapname)
+        self._snapshot_delete(snappath)
 
-    def create_subvolume_snapshot(self, subvolume_path, snapshot_name, mode=0o755):
-        return self._snapshot_create(self._subvolume_path(subvolume_path), snapshot_name, mode)
+    def create_group_snapshot(self, spec, snapname, mode=0o755):
+        snappath = spec.make_group_snap_path(self.rados.conf_get('client_snapdir'), snapname)
+        self._snapshot_create(snappath, mode)
 
-    def delete_subvolume_snapshot(self, subvolume_path, snapshot_name):
-        return self._snapshot_delete(self._subvolume_path(subvolume_path), snapshot_name)
+    def remove_group_snapshot(self, spec, snapname):
+        snappath = spec.make_group_snap_path(self.rados.conf_get('client_snapdir'), snapname)
+        return self._snapshot_delete(snappath)
 
-    def create_group_snapshot(self, group_id, snapshot_name, mode=0o755):
-        return self._snapshot_create(self._group_path(group_id), snapshot_name, mode)
+    ### context manager routines
 
-    def delete_group_snapshot(self, group_id, snapshot_name):
-        return self._snapshot_delete(self._group_path(group_id), snapshot_name)
+    def connect(self):
+        log.debug("Connecting to cephfs...")
+        self.fs = cephfs.LibCephFS(rados_inst=self.rados)
+        log.debug("CephFS initializing...")
+        self.fs.init()
+        log.debug("CephFS mounting...")
+        self.fs.mount(filesystem_name=self.fs_name.encode('utf-8'))
+        log.debug("Connection to cephfs complete")
+
+    def disconnect(self):
+        log.info("disconnect")
+        if self.fs:
+            log.debug("Disconnecting cephfs...")
+            self.fs.shutdown()
+            self.fs = None
+            log.debug("Disconnecting cephfs complete")
+
+    def __enter__(self):
+        self.connect()
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self.disconnect()
+
+    def __del__(self):
+        self.disconnect()
index b9dc5f1b577d192fda68616af08b077e8e2e9649..a2f4672b52d78b759c295daed3107e376d857d01 100644 (file)
@@ -2,8 +2,12 @@ import json
 import errno
 import logging
 
+import cephfs
 import orchestrator
 
+from .subvolspec import SubvolumeSpec
+from .subvolume import SubVolume
+
 log = logging.getLogger(__name__)
 
 class VolumeClient(object):
@@ -152,3 +156,192 @@ class VolumeClient(object):
         for f in fs_map['filesystems']:
             result.append({'name': f['mdsmap']['fs_name']})
         return 0, json.dumps(result, indent=2), ""
+
+    def group_exists(self, sv, spec):
+        # default group need not be explicitly created (as it gets created
+        # at the time of subvolume, snapshot and other create operations).
+        return spec.is_default_group() or sv.get_group_path(spec)
+
+    ### subvolume operations
+
+    def create_subvolume(self, volname, subvolname, groupname, size):
+        if not self.volume_exists(volname):
+            return -errno.ENOENT, "", \
+                "Volume '{0}' not found, create it with `ceph fs volume create` " \
+                "before trying to create subvolumes".format(volname)
+
+        # TODO: validate that subvol size fits in volume size
+        with SubVolume(self.mgr, fs_name=volname) as sv:
+            spec = SubvolumeSpec(subvolname, groupname)
+            if not self.group_exists(sv, spec):
+                return -errno.ENOENT, "", \
+                    "Subvolume group '{0}' not found, create it with `ceph fs subvolumegroup create` " \
+                    "before creating subvolumes".format(groupname)
+            return sv.create_subvolume(spec, size)
+
+    def remove_subvolume(self, volname, subvolname, groupname, force):
+        fs = self.get_fs(volname)
+        if not fs:
+            if force:
+                return 0, "", ""
+            else:
+                return -errno.ENOENT, "", \
+                    "Volume '{0}' not found, cannot remove subvolume '{1}'".format(volname, subvolname)
+
+        with SubVolume(self.mgr, fs_name=volname) as sv:
+            spec = SubvolumeSpec(subvolname, groupname)
+            if not self.group_exists(sv, spec):
+                if force:
+                    return 0, "", ""
+                else:
+                    return -errno.ENOENT, "", \
+                        "Subvolume group '{0}' not found, cannot remove subvolume '{1}'".format(groupname, subvolname)
+            try:
+                sv.remove_subvolume(spec)
+            except cephfs.ObjectNotFound:
+                if force:
+                    return 0, "", ""
+                else:
+                    return -errno.ENOENT, "", \
+                        "Subvolume '{0}' not found, cannot remove it".format(subvolname)
+            sv.purge_subvolume(spec)
+            return 0, "", ""
+
+    def subvolume_getpath(self, volname, subvolname, groupname):
+        if not self.volume_exists(volname):
+            return -errno.ENOENT, "", "Volume '{0}' not found".format(volname)
+
+        with SubVolume(self.mgr, fs_name=volname) as sv:
+            spec = SubvolumeSpec(subvolname, groupname)
+            if not self.group_exists(sv, spec):
+                return -errno.ENOENT, "", "Subvolume group '{0}' not found".format(groupname)
+            path = sv.get_subvolume_path(spec)
+            if not path:
+                return -errno.ENOENT, "", "Subvolume '{0}' not found".format(groupname)
+            return 0, path, ""
+
+    ### subvolume snapshot
+
+    def create_subvolume_snapshot(self, volname, subvolname, snapname, groupname):
+        if not self.volume_exists(volname):
+            return -errno.ENOENT, "", \
+                   "Volume '{0}' not found, cannot create snapshot '{1}'".format(volname, snapname)
+
+        with SubVolume(self.mgr, fs_name=volname) as sv:
+            spec = SubvolumeSpec(subvolname, groupname)
+            if not self.group_exists(sv, spec):
+                return -errno.ENOENT, "", \
+                    "Subvolume group '{0}' not found, cannot create snapshot '{1}'".format(groupname, snapname)
+            if not sv.get_subvolume_path(spec):
+                return -errno.ENOENT, "", \
+                       "Subvolume '{0}' not found, cannot create snapshot '{1}'".format(subvolname, snapname)
+            sv.create_subvolume_snapshot(spec, snapname)
+        return 0, "", ""
+
+    def remove_subvolume_snapshot(self, volname, subvolname, snapname, groupname, force):
+        if not self.volume_exists(volname):
+            if force:
+                return 0, "", ""
+            else:
+                return -errno.ENOENT, "", \
+                    "Volume '{0}' not found, cannot remove subvolumegroup snapshot '{1}'".format(volname, snapname)
+
+        with SubVolume(self.mgr, fs_name=volname) as sv:
+            spec = SubvolumeSpec(subvolname, groupname)
+            if not self.group_exists(sv, spec):
+                if force:
+                    return 0, "", ""
+                else:
+                    return -errno.ENOENT, "", \
+                        "Subvolume group '{0}' already removed, cannot remove subvolume snapshot '{1}'".format(groupname, snapname)
+            if not sv.get_subvolume_path(spec):
+                if force:
+                    return 0, "", ""
+                else:
+                    return -errno.ENOENT, "", \
+                        "Subvolume '{0}' not found, cannot remove subvolume snapshot '{1}'".format(subvolname, snapname)
+            try:
+                sv.remove_subvolume_snapshot(spec, snapname)
+            except cephfs.ObjectNotFound:
+                if force:
+                    return 0, "", ""
+                else:
+                    return -errno.ENOENT, "", \
+                        "Subvolume snapshot '{0}' not found, cannot remove it".format(snapname)
+        return 0, "", ""
+
+    ### group operations
+
+    def create_subvolume_group(self, volname, groupname):
+        if not self.volume_exists(volname):
+            return -errno.ENOENT, "", \
+                   "Volume '{0}' not found, create it with `ceph fs volume create` " \
+                   "before trying to create subvolume groups".format(volname)
+
+        # TODO: validate that subvol size fits in volume size
+        with SubVolume(self.mgr, fs_name=volname) as sv:
+            spec = SubvolumeSpec("", groupname)
+            sv.create_group(spec)
+        return 0, "", ""
+
+    def remove_subvolume_group(self, volname, groupname, force):
+        if not self.volume_exists(volname):
+            if force:
+                return 0, "", ""
+            else:
+                return -errno.ENOENT, "", \
+                    "Volume '{0}' not found, cannot remove subvolume group '{0}'".format(volname, groupname)
+
+        with SubVolume(self.mgr, fs_name=volname) as sv:
+            # TODO: check whether there are no subvolumes in the group
+            spec = SubvolumeSpec("", groupname)
+            try:
+                sv.remove_group(spec)
+            except cephfs.ObjectNotFound:
+                if force:
+                    return 0, "", ""
+                else:
+                    return -errno.ENOENT, "", \
+                        "Subvolume group '{0}' not found".format(groupname)
+        return 0, "", ""
+
+    ### group snapshot
+
+    def create_subvolume_group_snapshot(self, volname, groupname, snapname):
+        if not self.volume_exists(volname):
+            return -errno.ENOENT, "", \
+                   "Volume '{0}' not found, cannot create snapshot '{1}'".format(volname, snapname)
+
+        with SubVolume(self.mgr, fs_name=volname) as sv:
+            spec = SubvolumeSpec("", groupname)
+            if not self.group_exists(sv, spec):
+                return -errno.ENOENT, "", \
+                    "Subvolume group '{0}' not found, cannot create snapshot '{1}'".format(groupname, snapname)
+            sv.create_group_snapshot(spec, snapname)
+        return 0, "", ""
+
+    def remove_subvolume_group_snapshot(self, volname, groupname, snapname, force):
+        if not self.volume_exists(volname):
+            if force:
+                return 0, "", ""
+            else:
+                return -errno.ENOENT, "", \
+                    "Volume '{0}' not found, cannot remove subvolumegroup snapshot '{1}'".format(volname, snapname)
+
+        with SubVolume(self.mgr, fs_name=volname) as sv:
+            spec = SubvolumeSpec("", groupname)
+            if not self.group_exists(sv, spec):
+                if force:
+                    return 0, "", ""
+                else:
+                    return -errno.ENOENT, "", \
+                        "Subvolume group '{0}' not found, cannot remove it".format(groupname)
+            try:
+                sv.remove_group_snapshot(spec, snapname)
+            except:
+                if force:
+                    return 0, "", ""
+                else:
+                    return -errno.ENOENT, "", \
+                        "Subvolume group snapshot '{0}' not found, cannot remove it".format(snapname)
+        return 0, "", ""
index 80f4a2d7a03ddccb13d98531993a28620fdb635c..214b63fb4de25a22c240cffffc1b00ad9ea30bc5 100644 (file)
@@ -10,7 +10,6 @@ import cephfs
 from mgr_module import MgrModule
 import orchestrator
 
-from .fs.subvolume import SubvolumePath, SubvolumeClient
 from .fs.volume import VolumeClient
 
 class PurgeJob(object):
@@ -24,7 +23,6 @@ class PurgeJob(object):
         self.fscid = volume_fscid
         self.subvolume_path = subvolume_path
 
-
 class Module(orchestrator.OrchestratorClientMixin, MgrModule):
     COMMANDS = [
         {
@@ -200,17 +198,7 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
         vol_name = cmd['vol_name']
         group_name = cmd['group_name']
 
-        if not self._volume_exists(vol_name):
-            return -errno.ENOENT, "", \
-                   "Volume '{0}' not found, create it with `ceph fs volume create` " \
-                   "before trying to create subvolume groups".format(vol_name)
-
-        # TODO: validate that subvol size fits in volume size
-
-        with SubvolumeClient(self, fs_name=vol_name) as svc:
-            svc.create_group(group_name)
-
-        return 0, "", ""
+        return self.vc.create_subvolume_group(vol_name, group_name)
 
     def _cmd_fs_subvolumegroup_rm(self, inbuf, cmd):
         """
@@ -218,29 +206,9 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
         """
         vol_name = cmd['vol_name']
         group_name = cmd['group_name']
-
         force = cmd.get('force', False)
 
-        if not self._volume_exists(vol_name):
-            if force:
-                return 0, "", ""
-            else:
-                return -errno.ENOENT, "", \
-                       "Volume '{0}' not found, cannot remove subvolume group '{0}'".format(vol_name, group_name)
-
-        with SubvolumeClient(self, fs_name=vol_name) as svc:
-            # TODO: check whether there are no subvolumes in the group
-            try:
-                svc.delete_group(group_name)
-            except cephfs.ObjectNotFound:
-                if force:
-                    return 0, "", ""
-                else:
-                    return -errno.ENOENT, "", \
-                           "Subvolume group '{0}' not found, cannot remove it".format(group_name)
-
-
-        return 0, "", ""
+        return self.vc.remove_subvolume_group(vol_name, group_name, force)
 
     def _cmd_fs_subvolume_create(self, inbuf, cmd):
         """
@@ -248,26 +216,10 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
         """
         vol_name = cmd['vol_name']
         sub_name = cmd['sub_name']
-
         size = cmd.get('size', None)
         group_name = cmd.get('group_name', None)
 
-        if not self._volume_exists(vol_name):
-            return -errno.ENOENT, "", \
-                   "Volume '{0}' not found, create it with `ceph fs volume create` " \
-                   "before trying to create subvolumes".format(vol_name)
-
-        # TODO: validate that subvol size fits in volume size
-
-        with SubvolumeClient(self, fs_name=vol_name) as svc:
-            if group_name and not svc.get_group_path(group_name):
-                return -errno.ENOENT, "", \
-                    "Subvolume group '{0}' not found, create it with `ceph fs subvolumegroup create` " \
-                    "before trying to create subvolumes".format(group_name)
-            svp = SubvolumePath(group_name, sub_name)
-            svc.create_subvolume(svp, size)
-
-        return 0, "", ""
+        return self.vc.create_subvolume(vol_name, sub_name, group_name, size)
 
     def _cmd_fs_subvolume_rm(self, inbuf, cmd):
         """
@@ -275,171 +227,46 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
         """
         vol_name = cmd['vol_name']
         sub_name = cmd['sub_name']
-
         force = cmd.get('force', False)
         group_name = cmd.get('group_name', None)
 
-        fs = self._volume_get_fs(vol_name)
-        if fs is None:
-            if force:
-                return 0, "", ""
-            else:
-                return -errno.ENOENT, "", \
-                       "Volume '{0}' not found, cannot remove subvolume '{1}'".format(vol_name, sub_name)
-
-        vol_fscid = fs['id']
-
-        with SubvolumeClient(self, fs_name=vol_name) as svc:
-            if group_name and not svc.get_group_path(group_name):
-                if force:
-                    return 0, "", ""
-                else:
-                    return -errno.ENOENT, "", \
-                           "Subvolume group '{0}' not found, cannot remove subvolume '{1}'".format(group_name, sub_name)
-            svp = SubvolumePath(group_name, sub_name)
-            try:
-                svc.delete_subvolume(svp)
-            except cephfs.ObjectNotFound:
-                if force:
-                    return 0, "", ""
-                else:
-                    return -errno.ENOENT, "", \
-                           "Subvolume '{0}' not found, cannot remove it".format(sub_name)
-            svc.purge_subvolume(svp)
-
-        # TODO: purge subvolume asynchronously
-        # self._background_jobs.put(PurgeJob(vol_fscid, svp))
-
-        return 0, "", ""
+        return self.vc.remove_subvolume(vol_name, sub_name, group_name, force)
 
     def _cmd_fs_subvolume_getpath(self, inbuf, cmd):
         vol_name = cmd['vol_name']
         sub_name = cmd['sub_name']
-
         group_name = cmd.get('group_name', None)
 
-        if not self._volume_exists(vol_name):
-            return -errno.ENOENT, "", "Volume '{0}' not found".format(vol_name)
-
-        with SubvolumeClient(self, fs_name=vol_name) as svc:
-            if group_name and not svc.get_group_path(group_name):
-                return -errno.ENOENT, "", \
-                    "Subvolume group '{0}' not found".format(group_name)
-            svp = SubvolumePath(group_name, sub_name)
-            path = svc.get_subvolume_path(svp)
-            if not path:
-                return -errno.ENOENT, "", \
-                       "Subvolume '{0}' not found".format(sub_name)
-        return 0, path, ""
+        return self.vc.subvolume_getpath(vol_name, sub_name, group_name)
 
     def _cmd_fs_subvolumegroup_snapshot_create(self, inbuf, cmd):
         vol_name = cmd['vol_name']
         group_name = cmd['group_name']
         snap_name = cmd['snap_name']
 
-        if not self._volume_exists(vol_name):
-            return -errno.ENOENT, "", \
-                   "Volume '{0}' not found, cannot create snapshot '{1}'".format(vol_name, snap_name)
-
-        with SubvolumeClient(self, fs_name=vol_name) as svc:
-            if group_name and not svc.get_group_path(group_name):
-                return -errno.ENOENT, "", \
-                    "Subvolume group '{0}' not found, cannot create snapshot '{1}'".format(group_name, snap_name)
-            svc.create_group_snapshot(group_name, snap_name)
-
-        return 0, "", ""
+        return self.vc.create_subvolume_group_snapshot(vol_name, group_name, snap_name)
 
     def _cmd_fs_subvolumegroup_snapshot_rm(self, inbuf, cmd):
         vol_name = cmd['vol_name']
         group_name = cmd['group_name']
         snap_name = cmd['snap_name']
-
         force = cmd.get('force', False)
 
-        if not self._volume_exists(vol_name):
-            if force:
-                return 0, "", ""
-            else:
-                return -errno.ENOENT, "", \
-                       "Volume '{0}' not found, cannot remove subvolumegroup snapshot '{1}'".format(vol_name, snap_name)
-
-        with SubvolumeClient(self, fs_name=vol_name) as svc:
-            if group_name and not svc.get_group_path(group_name):
-                if force:
-                    return 0, "", ""
-                else:
-                    return -errno.ENOENT, "", \
-                           "Subvolume group '{0}' not found, cannot remove subvolumegroup snapshot '{1}'".format(group_name, snap_name)
-            try:
-                svc.delete_group_snapshot(group_name, snap_name)
-            except cephfs.ObjectNotFound:
-                if force:
-                    return 0, "", ""
-                else:
-                    return -errno.ENOENT, "", \
-                           "Subvolume group snapshot '{0}' not found, cannot remove it".format(snap_name)
-
-        return 0, "", ""
+        return self.vc.remove_subvolume_group_snapshot(vol_name, group_name, snap_name, force)
 
     def _cmd_fs_subvolume_snapshot_create(self, inbuf, cmd):
         vol_name = cmd['vol_name']
         sub_name = cmd['sub_name']
         snap_name = cmd['snap_name']
-
         group_name = cmd.get('group_name', None)
 
-        if not self._volume_exists(vol_name):
-            return -errno.ENOENT, "", \
-                   "Volume '{0}' not found, cannot create snapshot '{1}'".format(vol_name, snap_name)
-
-        with SubvolumeClient(self, fs_name=vol_name) as svc:
-            if group_name and not svc.get_group_path(group_name):
-                return -errno.ENOENT, "", \
-                    "Subvolume group '{0}' not found, cannot create snapshot '{1}'".format(group_name, snap_name)
-            svp = SubvolumePath(group_name, sub_name)
-            if not svc.get_subvolume_path(svp):
-                return -errno.ENOENT, "", \
-                       "Subvolume '{0}' not found, cannot create snapshot '{1}'".format(sub_name, snap_name)
-            svc.create_subvolume_snapshot(svp, snap_name)
-
-        return 0, "", ""
+        return self.vc.create_subvolume_snapshot(vol_name, sub_name, snap_name, group_name)
 
     def _cmd_fs_subvolume_snapshot_rm(self, inbuf, cmd):
         vol_name = cmd['vol_name']
         sub_name = cmd['sub_name']
         snap_name = cmd['snap_name']
-
         force = cmd.get('force', False)
         group_name = cmd.get('group_name', None)
 
-        if not self._volume_exists(vol_name):
-            if force:
-                return 0, "", ""
-            else:
-                return -errno.ENOENT, "", \
-                       "Volume '{0}' not found, cannot remove subvolume snapshot '{1}'".format(vol_name, snap_name)
-
-        with SubvolumeClient(self, fs_name=vol_name) as svc:
-            if group_name and not svc.get_group_path(group_name):
-                if force:
-                    return 0, "", ""
-                else:
-                    return -errno.ENOENT, "", \
-                           "Subvolume group '{0}' not found, cannot remove subvolume snapshot '{1}'".format(group_name, snap_name)
-            svp = SubvolumePath(group_name, sub_name)
-            if not svc.get_subvolume_path(svp):
-                if force:
-                    return 0, "", ""
-                else:
-                    return -errno.ENOENT, "", \
-                           "Subvolume '{0}' not found, cannot remove subvolume snapshot '{1}'".format(sub_name, snap_name)
-            try:
-                svc.delete_subvolume_snapshot(svp, snap_name)
-            except cephfs.ObjectNotFound:
-                if force:
-                    return 0, "", ""
-                else:
-                    return -errno.ENOENT, "", \
-                           "Subvolume snapshot '{0}' not found, cannot remove it".format(snap_name)
-
-        return 0, "", ""
+        return self.vc.remove_subvolume_snapshot(vol_name, sub_name, snap_name, group_name, force)