diff --git a/doc/cephfs/fs-volumes.rst b/doc/cephfs/fs-volumes.rst index 74ab42dc310..b51f06757cb 100644 --- a/doc/cephfs/fs-volumes.rst +++ b/doc/cephfs/fs-volumes.rst @@ -476,6 +476,15 @@ This command resizes the subvolume quota, using the size specified by ``new_size``. The ``--no_shrink`` flag prevents the subvolume from shrinking below the current "used size" of the subvolume. +Resizing can also be done using human-friendly units:: + + ceph fs subvolume resize foo subvol1 100KiB + ceph fs subvolume resize foo subvol1 200.45KiB + ceph fs subvolume resize foo subvol1 300KB + +.. note:: Values will be strictly cast to IEC units even when SI units + are input, i.e. 1{K|KB|Ki|KiB} all translate to 1024 bytes. + The subvolume can be resized to an unlimited (but sparse) logical size by passing ``inf`` or ``infinite`` as ````. diff --git a/qa/tasks/cephfs/test_volumes.py b/qa/tasks/cephfs/test_volumes.py index 4b85802780e..9918e588af2 100644 --- a/qa/tasks/cephfs/test_volumes.py +++ b/qa/tasks/cephfs/test_volumes.py @@ -4431,6 +4431,51 @@ class TestSubvolumes(TestVolumesHelper): # verify trash dir is clean self._wait_for_trash_empty() + def test_subvolume_resize_valid_unit(self): + """ + That a subvolume can be resized when provided with valid value + (both int and float accepted) and unit. + """ + readable_values = {"10B": 10, "10K": 10240, "200KB": 204800, + "300Ki": 307200, "300KiB": 307200, + "300.67KiB": 307886, "10M": 10485760, + "100MB": 104857600, "100Mi": 104857600, + "100MiB": 104857600, "100.123MiB": 104986574, + "2G": 2147483648, "2GB": 2147483648, + "4Gi": 4294967296, "4GiB": 4294967296, + "4.2GiB": 4509715660, "1T": 1099511627776, + "2TB": 2199023255552, "2Ti": 2199023255552, + "2TiB": 2199023255552, "2.2TiB": 2418925581107} + subvolume = self._gen_subvol_name() + self._fs_cmd("subvolume", "create", self.volname, subvolume) + for readable_value in readable_values: + self._fs_cmd("subvolume", "resize", self.volname, subvolume, + readable_value) + subvol_info = json.loads(self._get_subvolume_info(self.volname, subvolume)) + self.assertEqual(subvol_info["bytes_quota"], readable_values.get(readable_value)) + + self._fs_cmd("subvolume", "rm", self.volname, subvolume) + self._wait_for_trash_empty() + + def test_subvolume_resize_invalid_unit(self): + """ + That a subvolume resize fails when provided with invalid value + and/or unit. + """ + invalid_values = ("10A", "1y00Ki", "af00", "G", "", " ", "-1t", "-1", + "1GT", "2MM", "5Di", "8Bi", "i", "7iB", "1.K", ".MB", + ".10.G", "10Ki.B", "1.0.3TiB", "20G/.", "Gi32Ki", + "10.64B", "1.B", ".B", "B.1", "KB1", "GB.1", "TB1.1") + subvolume = self._gen_subvol_name() + self._fs_cmd("subvolume", "create", self.volname, subvolume) + for invalid_value in invalid_values: + with self.assertRaises(CommandFailedError): + self._fs_cmd("subvolume", "resize", self.volname, subvolume, + invalid_value) + + self._fs_cmd("subvolume", "rm", self.volname, subvolume) + self._wait_for_trash_empty() + def test_subvolume_rm_force(self): # test removing non-existing subvolume with --force subvolume = self._gen_subvol_name() diff --git a/src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py b/src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py index 5ceb9bcc2bc..edf7d485b51 100644 --- a/src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py +++ b/src/pybind/mgr/volumes/fs/operations/versions/subvolume_base.py @@ -371,7 +371,37 @@ class SubvolumeBase(object): raise VolumeException(-errno.EINVAL, "invalid fscrypt_file specified: '{0}'".format(fscrypt_file)) + def convert_to_bytes(self, subvol_size): + import re + unit_map = {"B": 1, + "K": pow(1024, 1), + "M": pow(1024, 2), + "G": pow(1024, 3), + "T": pow(1024, 4), + "P": pow(1024, 5), + "E": pow(1024, 6)} + pattern = r'^(\d+\.\d+|\d+)([KMGTPE]i?B?|B)$' + match = re.match(pattern, subvol_size) + if match: + value = match.group(1) + unit = match.group(2) + if unit[0] == "B" and "." in value: + raise VolumeException(-errno.EINVAL, + f"Invalid byte value: {value}") + multiplier = unit_map.get(unit[0]) + if multiplier is None: + raise VolumeException(-errno.EINVAL, + f"Invalid subvolume unit: {unit}") + else: + return float(value) * multiplier + else: + return None + def _resize(self, path, newsize, noshrink): + num = self.convert_to_bytes(newsize) + if num is not None: + newsize = num + try: newsize = int(newsize) if newsize <= 0: @@ -396,7 +426,7 @@ class SubvolumeBase(object): raise VolumeException(-e.args[0], e.args[1]) subvolstat = self.fs.stat(path) - if newsize > 0 and newsize < subvolstat.st_size: + if 0 < newsize < subvolstat.st_size: if noshrink: raise VolumeException(-errno.EINVAL, "Can't resize the subvolume. "