From b2ff4d534ad0ec8e306d1e58372200d18c3f5856 Mon Sep 17 00:00:00 2001 From: Ramana Raja Date: Mon, 29 Apr 2019 18:01:49 +0530 Subject: [PATCH] mgr/volumes: allow creation/deletion of FS subvolume groups ... using `ceph fs subvolumegroup create/rm` commands. FS subvolume groups are parent directories of FS subvolumes. They can be directly mapped to OpenStack Manila share groups. Signed-off-by: Ramana Raja --- src/pybind/mgr/volumes/fs/subvolume.py | 29 ++++++ src/pybind/mgr/volumes/module.py | 135 +++++++++++++++++++++---- 2 files changed, 146 insertions(+), 18 deletions(-) diff --git a/src/pybind/mgr/volumes/fs/subvolume.py b/src/pybind/mgr/volumes/fs/subvolume.py index fe849e2ce52fb..e86c169f8ed6e 100644 --- a/src/pybind/mgr/volumes/fs/subvolume.py +++ b/src/pybind/mgr/volumes/fs/subvolume.py @@ -76,6 +76,19 @@ class SubvolumeClient(object): 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) @@ -120,6 +133,14 @@ 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) + + 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): """ Set up metadata, pools and auth for a subvolume. @@ -231,6 +252,14 @@ 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 + def get_subvolume_path(self, subvolume_path): path = self._subvolume_path(subvolume_path) try: diff --git a/src/pybind/mgr/volumes/module.py b/src/pybind/mgr/volumes/module.py index f0a5024a0eabb..e2c8f8356b00c 100644 --- a/src/pybind/mgr/volumes/module.py +++ b/src/pybind/mgr/volumes/module.py @@ -44,36 +44,59 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): 'desc': "Delete a CephFS volume", 'perm': 'rw' }, + { + 'cmd': 'fs subvolumegroup create ' + 'name=vol_name,type=CephString ' + 'name=group_name,type=CephString ', + 'desc': "Create a CephFS subvolume group in a volume", + 'perm': 'rw' + }, + { + 'cmd': 'fs subvolumegroup rm ' + 'name=vol_name,type=CephString ' + 'name=group_name,type=CephString ' + 'name=force,type=CephBool,req=false ', + 'desc': "Delete a CephFS subvolume group in a volume", + 'perm': 'rw' + }, { 'cmd': 'fs subvolume create ' 'name=vol_name,type=CephString ' 'name=sub_name,type=CephString ' - 'name=size,type=CephInt,req=false ', - 'desc': "Create a CephFS subvolume in a volume, and " - "optionally with a specific size(in bytes)", + 'name=size,type=CephInt,req=false ' + 'name=group_name,type=CephString,req=false ', + 'desc': "Create a CephFS subvolume in a volume, and optionally, " + "with a specific size (in bytes) and in a specific " + "subvolume group", 'perm': 'rw' }, { 'cmd': 'fs subvolume rm ' 'name=vol_name,type=CephString ' 'name=sub_name,type=CephString ' + 'name=group_name,type=CephString,req=false ' 'name=force,type=CephBool,req=false ', - 'desc': "Delete a CephFS subvolume in a volume", + 'desc': "Delete a CephFS subvolume in a volume, and optionally, " + "in a specific subvolume group", 'perm': 'rw' }, { 'cmd': 'fs subvolume getpath ' 'name=vol_name,type=CephString ' - 'name=sub_name,type=CephString ', - 'desc': "Get the mountpath of a CephFS subvolume in a volume", + 'name=sub_name,type=CephString ' + 'name=group_name,type=CephString,req=false ', + 'desc': "Get the mountpath of a CephFS subvolume in a volume, " + "and optionally, in a specific subvolume group", 'perm': 'rw' }, { 'cmd': 'fs subvolume snapshot create ' 'name=vol_name,type=CephString ' 'name=sub_name,type=CephString ' - 'name=snap_name,type=CephString ', - 'desc': "Create a snapshot of a CephFS subvolume in a volume", + 'name=snap_name,type=CephString ' + 'name=group_name,type=CephString,req=false ', + 'desc': "Create a snapshot of a CephFS subvolume in a volume, " + "and optionally, in a specific subvolume group", 'perm': 'rw' }, { @@ -81,8 +104,10 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): 'name=vol_name,type=CephString ' 'name=sub_name,type=CephString ' 'name=snap_name,type=CephString ' + 'name=group_name,type=CephString,req=false ' 'name=force,type=CephBool,req=false ', - 'desc': "Delete a snapshot of a CephFS subvolume in a volume", + 'desc': "Delete a snapshot of a CephFS subvolume in a volume, " + "and optionally, in a specific subvolume group", 'perm': 'rw' }, @@ -236,6 +261,55 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): def _volume_exists(self, vol_name): return self._volume_get_fs(vol_name) is not None + def _cmd_fs_subvolumegroup_create(self, inbuf, cmd): + """ + :return: a 3-tuple of return code(int), empty string(str), error message (str) + """ + 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, "", "" + + def _cmd_fs_subvolumegroup_rm(self, inbuf, cmd): + """ + :return: a 3-tuple of return code(int), empty string(str), error message (str) + """ + 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, "", "" + def _cmd_fs_subvolume_create(self, inbuf, cmd): """ :return: a 3-tuple of return code(int), empty string(str), error message (str) @@ -244,6 +318,7 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): 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, "", \ @@ -253,9 +328,11 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): # TODO: validate that subvol size fits in volume size with SubvolumeClient(self, fs_name=vol_name) as svc: - # TODO: support real subvolume groups rather than just - # always having them 1:1 with subvolumes. - svp = SubvolumePath(sub_name, sub_name) + 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, "", "" @@ -268,6 +345,7 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): 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: @@ -280,9 +358,13 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): vol_fscid = fs['id'] with SubvolumeClient(self, fs_name=vol_name) as svc: - # TODO: support real subvolume groups rather than just - # always having them 1:1 with subvolumes. - svp = SubvolumePath(sub_name, sub_name) + 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: @@ -389,11 +471,16 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): 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: - svp = SubvolumePath(sub_name, sub_name) + 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, "", \ @@ -405,12 +492,17 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): 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: - svp = SubvolumePath(sub_name, sub_name) + 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) @@ -424,6 +516,7 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): 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: @@ -433,7 +526,13 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): "Volume '{0}' not found, cannot remove subvolume snapshot '{1}'".format(vol_name, snap_name) with SubvolumeClient(self, fs_name=vol_name) as svc: - svp = SubvolumePath(sub_name, sub_name) + 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, "", "" -- 2.39.5