From f93afc474675f364972ee2719ad284f0ac850740 Mon Sep 17 00:00:00 2001 From: Tomer Haskalovitch Date: Mon, 14 Jul 2025 21:53:30 +0300 Subject: [PATCH] mgr/dashboard: split ns add to separate api and cli functions Signed-off-by: Tomer Haskalovitch --- .../mgr/dashboard/controllers/nvmeof.py | 69 ++++++++++++++++++- .../mgr/dashboard/services/nvmeof_cli.py | 27 ++++++++ .../mgr/dashboard/tests/test_nvmeof_cli.py | 15 +++- 3 files changed, 107 insertions(+), 4 deletions(-) diff --git a/src/pybind/mgr/dashboard/controllers/nvmeof.py b/src/pybind/mgr/dashboard/controllers/nvmeof.py index 34517159533a2..060c846474eaa 100644 --- a/src/pybind/mgr/dashboard/controllers/nvmeof.py +++ b/src/pybind/mgr/dashboard/controllers/nvmeof.py @@ -9,7 +9,7 @@ from orchestrator import OrchestratorError from .. import mgr from ..model import nvmeof as model from ..security import Scope -from ..services.nvmeof_cli import NvmeofCLICommand +from ..services.nvmeof_cli import NvmeofCLICommand, convert_to_bytes from ..services.orchestrator import OrchClient from ..tools import str_to_bool from . import APIDoc, APIRouter, BaseController, CreatePermission, \ @@ -425,7 +425,6 @@ else: ) }, ) - @NvmeofCLICommand("nvmeof ns add", model.NamespaceCreation) @convert_to_model(model.NamespaceCreation) @handle_nvmeof_error def create( @@ -463,6 +462,49 @@ else: ) ) + @NvmeofCLICommand("nvmeof ns add", model.NamespaceCreation) + @convert_to_model(model.NamespaceCreation) + @handle_nvmeof_error + def create_cli( + self, + nqn: str, + rbd_image_name: str, + rbd_pool: str = "rbd", + create_image: Optional[bool] = False, + size: Optional[str] = None, + rbd_image_size: Optional[str] = None, + trash_image: Optional[bool] = False, + block_size: int = 512, + load_balancing_group: Optional[int] = None, + force: Optional[bool] = False, + no_auto_visible: Optional[bool] = False, + disable_auto_resize: Optional[bool] = False, + read_only: Optional[bool] = False, + gw_group: Optional[str] = None, + traddr: Optional[str] = None, + ): + size_b = rbd_image_size_b = None + if size: + size_b = convert_to_bytes(size, default_unit='MB') + if rbd_image_size: + rbd_image_size_b = convert_to_bytes(rbd_image_size, default_unit='MB') + return NVMeoFClient(gw_group=gw_group, traddr=traddr).stub.namespace_add( + NVMeoFClient.pb2.namespace_add_req( + subsystem_nqn=nqn, + rbd_image_name=rbd_image_name, + rbd_pool_name=rbd_pool, + block_size=block_size, + create_image=create_image, + size=rbd_image_size_b or size_b, + trash_image=trash_image, + anagrpid=load_balancing_group, + force=force, + no_auto_visible=no_auto_visible, + disable_auto_resize=disable_auto_resize, + read_only=read_only + ) + ) + @ReadPermission @Endpoint('PUT', '{nsid}/set_qos') @EndpointDoc( @@ -556,7 +598,6 @@ else: "traddr": Param(str, "NVMeoF gateway address", True, None), }, ) - @NvmeofCLICommand("nvmeof ns resize", model=model.RequestStatus) @convert_to_model(model.RequestStatus) @handle_nvmeof_error def resize( @@ -576,6 +617,28 @@ else: ) ) + @NvmeofCLICommand("nvmeof ns resize", model=model.RequestStatus) + @convert_to_model(model.RequestStatus) + @handle_nvmeof_error + def resize_cli( + self, + nqn: str, + nsid: str, + rbd_image_size: str, + gw_group: Optional[str] = None, + traddr: Optional[str] = None + ): + if rbd_image_size: + rbd_image_size_b = convert_to_bytes(rbd_image_size, default_unit='MB') + mib = 1024 * 1024 + rbd_image_size_mb = rbd_image_size_b // mib + + return NVMeoFClient(gw_group=gw_group, traddr=traddr).stub.namespace_resize( + NVMeoFClient.pb2.namespace_resize_req( + subsystem_nqn=nqn, nsid=int(nsid), new_size=rbd_image_size_mb + ) + ) + @ReadPermission @Endpoint('PUT', '{nsid}/add_host') @EndpointDoc( diff --git a/src/pybind/mgr/dashboard/services/nvmeof_cli.py b/src/pybind/mgr/dashboard/services/nvmeof_cli.py index f1887d86da219..75a586a5f8f1f 100644 --- a/src/pybind/mgr/dashboard/services/nvmeof_cli.py +++ b/src/pybind/mgr/dashboard/services/nvmeof_cli.py @@ -55,6 +55,33 @@ def remove_nvmeof_gateway(_, name: str, daemon_name: str = ''): return -errno.EINVAL, '', str(ex) +MULTIPLES = ['', "K", "M", "G", "T", "P"] +UNITS = { + f"{prefix}{suffix}": 1024 ** mult + for mult, prefix in enumerate(MULTIPLES) + for suffix in ['', 'B', 'iB'] + if not (prefix == '' and suffix == 'iB') +} + + +def convert_to_bytes(size: Union[int, str], default_unit=None): + if isinstance(size, int): + number = size + size = str(size) + else: + num_str = ''.join(filter(str.isdigit, size)) + number = int(num_str) + unit_str = ''.join(filter(str.isalpha, size)) + if not unit_str: + if not default_unit: + raise ValueError("default unit was not provided") + unit_str = default_unit + + if unit_str in UNITS: + return number * UNITS[unit_str] + raise ValueError(f"Invalid unit: {unit_str}") + + def convert_from_bytes(num_in_bytes): units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] size = float(num_in_bytes) diff --git a/src/pybind/mgr/dashboard/tests/test_nvmeof_cli.py b/src/pybind/mgr/dashboard/tests/test_nvmeof_cli.py index bceaa7f04c127..96b7f9ce2e352 100644 --- a/src/pybind/mgr/dashboard/tests/test_nvmeof_cli.py +++ b/src/pybind/mgr/dashboard/tests/test_nvmeof_cli.py @@ -9,7 +9,7 @@ from mgr_module import CLICommand, HandleCommandResult from ..model.nvmeof import CliFlags, CliHeader from ..services.nvmeof_cli import AnnotatedDataTextOutputFormatter, \ - NvmeofCLICommand, convert_from_bytes + NvmeofCLICommand, convert_from_bytes, convert_to_bytes from ..tests import CLICommandTestMixin @@ -416,3 +416,16 @@ class TestConverFromBytes: assert convert_from_bytes(1048576) == '1MB' assert convert_from_bytes(123) == '123B' assert convert_from_bytes(5368709120) == '5GB' + + +class TestConvertToBytes: + def test_valid_inputs(self): + assert convert_to_bytes('200MB') == 209715200 + assert convert_to_bytes('1MB') == 1048576 + assert convert_to_bytes('123B') == 123 + assert convert_to_bytes('5GB') == 5368709120 + + def test_default_unit(self): + with pytest.raises(ValueError): + assert convert_to_bytes('5') == 5368709120 + assert convert_to_bytes('5', default_unit='GB') == 5368709120 -- 2.39.5