From: avanthakkar Date: Mon, 11 Dec 2023 13:20:54 +0000 (+0530) Subject: mgr/dashboard: introduce APIs for NvmeOf management X-Git-Tag: v19.3.0~104^2~2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=1b768332b16f9d0735bf4217375c96ca1ad0881b;p=ceph.git mgr/dashboard: introduce APIs for NvmeOf management Fixes: https://tracker.ceph.com/issues/64201 Signed-off-by: avanthakkar --- diff --git a/src/pybind/mgr/dashboard/controllers/nvmeof.py b/src/pybind/mgr/dashboard/controllers/nvmeof.py index 274985245fe3..6458c47a8856 100644 --- a/src/pybind/mgr/dashboard/controllers/nvmeof.py +++ b/src/pybind/mgr/dashboard/controllers/nvmeof.py @@ -10,14 +10,99 @@ # from ..cephnvmeof.control.cli import GatewayClient +from typing import Optional from ..security import Scope from ..services.nvmeof_client import NVMeoFClient -from . import APIDoc, APIRouter, RESTController, Endpoint, ReadPermission, CreatePermission +# from ..services.proto import gateway_pb2 as pb2 +from . import APIDoc, APIRouter, RESTController, Endpoint, ReadPermission, CreatePermission, \ + DeletePermission, allow_empty_body, UpdatePermission + -@APIRouter('/nvmeof', Scope.ISCSI) +@APIRouter('/nvmeof', Scope.NVME_OF) @APIDoc('NVMe-oF Management API', 'NVMe-oF') class Nvmeof(RESTController): @ReadPermission def list(self): """List all NVMeoF gateways""" return NVMeoFClient().get_subsystems() + + +@APIRouter('/nvmeof/bdev', Scope.NVME_OF) +@APIDoc('NVMe-oF Block Device Management API', 'NVMe-oF') +class NvmeofBdev(RESTController): + @CreatePermission + def create(self, name: str, rbd_pool: str, rbd_image: str, block_size: int, uuid: Optional[str] = None): + """Create a new NVMeoF block device""" + return NVMeoFClient().create_bdev(name, rbd_pool, rbd_image, block_size, uuid) + + @DeletePermission + @allow_empty_body + def delete(self, name: str, force: bool): + """Delete an existing NVMeoF block device""" + return NVMeoFClient().delete_bdev(name, force) + + @Endpoint('PUT') + @UpdatePermission + @allow_empty_body + def resize(self, name: str, size: int): + """Resize an existing NVMeoF block device""" + return NVMeoFClient().resize_bdev(name, size) + + +@APIRouter('/nvmeof/namespace', Scope.NVME_OF) +@APIDoc('NVMe-oF Namespace Management API', 'NVMe-oF') +class NvmeofNamespace(RESTController): + @CreatePermission + def create(self, subsystem_nqn: str, bdev_name: str, nsid: int, anagrpid: Optional[str] = None): + """Create a new NVMeoF namespace""" + return NVMeoFClient().create_namespace(subsystem_nqn, bdev_name, nsid, anagrpid) + + @Endpoint('DELETE', path='{subsystem_nqn}') + def delete(self, subsystem_nqn: str, nsid: int): + """Delete an existing NVMeoF namespace""" + return NVMeoFClient().delete_namespace(subsystem_nqn, nsid) + +@APIRouter('/nvmeof/subsystem', Scope.NVME_OF) +@APIDoc('NVMe-oF Subsystem Management API', 'NVMe-oF') +class NvmeofSubsystem(RESTController): + @CreatePermission + def create(self, subsystem_nqn: str, serial_number: str, max_namespaces: int, + ana_reporting: bool, enable_ha: bool) : + """Create a new NVMeoF subsystem""" + return NVMeoFClient().create_subsystem(subsystem_nqn, serial_number, max_namespaces, + ana_reporting, enable_ha) + + @Endpoint('DELETE', path='{subsystem_nqn}') + def delete(self, subsystem_nqn: str): + """Delete an existing NVMeoF subsystem""" + return NVMeoFClient().delete_subsystem(subsystem_nqn) + + +@APIRouter('/nvmeof/hosts', Scope.NVME_OF) +@APIDoc('NVMe-oF Host Management API', 'NVMe-oF') +class NvmeofHost(RESTController): + @CreatePermission + def create(self, subsystem_nqn: str, host_nqn: str): + """Create a new NVMeoF host""" + return NVMeoFClient().add_host(subsystem_nqn, host_nqn) + + @Endpoint('DELETE') + def delete(self, subsystem_nqn: str, host_nqn: str): + """Delete an existing NVMeoF host""" + return NVMeoFClient().remove_host(subsystem_nqn, host_nqn) + + +@APIRouter('/nvmeof/listener', Scope.NVME_OF) +@APIDoc('NVMe-oF Listener Management API', 'NVMe-oF') +class NvmeofListener(RESTController): + @CreatePermission + def create(self, nqn: str, gateway: str, trtype: str, adrfam: str, + traddr: str, trsvcid: str): + """Create a new NVMeoF listener""" + return NVMeoFClient().create_listener(nqn, gateway, trtype, adrfam, traddr, trsvcid) + + @Endpoint('DELETE') + def delete(self, nqn: str, gateway: str, trtype, adrfam, + traddr: str, trsvcid: str): + """Delete an existing NVMeoF listener""" + return NVMeoFClient().delete_listener(nqn, gateway, trtype, adrfam, traddr, trsvcid) \ No newline at end of file diff --git a/src/pybind/mgr/dashboard/security.py b/src/pybind/mgr/dashboard/security.py index 4c6e5c564af3..2b624aabcc72 100644 --- a/src/pybind/mgr/dashboard/security.py +++ b/src/pybind/mgr/dashboard/security.py @@ -26,6 +26,7 @@ class Scope(object): USER = "user" DASHBOARD_SETTINGS = "dashboard-settings" NFS_GANESHA = "nfs-ganesha" + NVME_OF = "nvme-of" @classmethod def all_scopes(cls): diff --git a/src/pybind/mgr/dashboard/services/access_control.py b/src/pybind/mgr/dashboard/services/access_control.py index 0cbe49bb160a..b45f81fb9b1d 100644 --- a/src/pybind/mgr/dashboard/services/access_control.py +++ b/src/pybind/mgr/dashboard/services/access_control.py @@ -222,6 +222,7 @@ BLOCK_MGR_ROLE = Role( Scope.ISCSI: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE], Scope.RBD_MIRRORING: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE], Scope.GRAFANA: [_P.READ], + Scope.NVME_OF: [_P.READ, _P.CREATE, _P.UPDATE, _P.DELETE], }) diff --git a/src/pybind/mgr/dashboard/services/nvmeof_client.py b/src/pybind/mgr/dashboard/services/nvmeof_client.py index ec8911abc20b..ff7c6dfc8ced 100644 --- a/src/pybind/mgr/dashboard/services/nvmeof_client.py +++ b/src/pybind/mgr/dashboard/services/nvmeof_client.py @@ -1,3 +1,5 @@ +from enum import Enum +from typing import Optional import grpc import json @@ -9,6 +11,7 @@ from .proto import gateway_pb2_grpc as pb2_grpc from google.protobuf.json_format import MessageToJson from .nvmeof_conf import NvmeofGatewaysConfig +from ..tools import str_to_bool logger = logging.getLogger('nvmeof_client') @@ -27,3 +30,99 @@ class NVMeoFClient(object): def get_subsystems(self): response = self.stub.get_subsystems(pb2.get_subsystems_req()) return json.loads(MessageToJson(response)) + + def create_bdev(self, name: str, rbd_pool: str, rbd_image: str, block_size: int, uuid: Optional[str] = None): + response = self.stub.create_bdev(pb2.create_bdev_req( + bdev_name=name, + rbd_pool_name=rbd_pool, + rbd_image_name=rbd_image, + block_size=block_size, + uuid=uuid + )) + return json.loads(MessageToJson(response)) + + def resize_bdev(self, name: str, size: int): + response = self.stub.resize_bdev(pb2.resize_bdev_req( + bdev_name=name, + new_size=size + )) + return json.loads(MessageToJson(response)) + + def delete_bdev(self, name: str, force: bool): + response = self.stub.delete_bdev(pb2.delete_bdev_req( + bdev_name=name, + force=str_to_bool(force) + )) + return json.loads(MessageToJson(response)) + + def create_subsystem(self, subsystem_nqn: str, serial_number: str, max_namespaces: int, + ana_reporting: bool, enable_ha: bool) : + response = self.stub.create_subsystem(pb2.create_subsystem_req( + subsystem_nqn=subsystem_nqn, + serial_number=serial_number, + max_namespaces=int(max_namespaces), + ana_reporting=str_to_bool(ana_reporting), + enable_ha=str_to_bool(enable_ha) + )) + return json.loads(MessageToJson(response)) + + def delete_subsystem(self, subsystem_nqn: str): + response = self.stub.delete_subsystem(pb2.delete_subsystem_req( + subsystem_nqn=subsystem_nqn + )) + return json.loads(MessageToJson(response)) + + def create_namespace(self, subsystem_nqn: str, bdev_name: str, nsid: int, anagrpid: Optional[str] = None): + response = self.stub.add_namespace(pb2.add_namespace_req( + subsystem_nqn=subsystem_nqn, + bdev_name=bdev_name, + nsid=int(nsid), + anagrpid=anagrpid + )) + return json.loads(MessageToJson(response)) + + def delete_namespace(self, subsystem_nqn: str, nsid: int): + response = self.stub.remove_namespace(pb2.remove_namespace_req( + subsystem_nqn=subsystem_nqn, + nsid=nsid + )) + return json.loads(MessageToJson(response)) + + def add_host(self, subsystem_nqn: str, host_nqn: str): + response = self.stub.add_host(pb2.add_host_req( + subsystem_nqn=subsystem_nqn, + host_nqn=host_nqn + )) + return json.loads(MessageToJson(response)) + + def remove_host(self, subsystem_nqn: str, host_nqn: str): + response = self.stub.remove_host(pb2.remove_host_req( + subsystem_nqn=subsystem_nqn, + host_nqn=host_nqn + )) + return json.loads(MessageToJson(response)) + + def create_listener(self, nqn: str, gateway: str, trtype: str, adrfam: str, + traddr: str, trsvcid: str): + req = pb2.create_listener_req( + nqn=nqn, + gateway_name=gateway, + trtype=pb2.TransportType.Value(trtype.upper()), + adrfam=pb2.AddressFamily.Value(adrfam.lower()), + traddr=traddr, + trsvcid=trsvcid, + ) + ret = self.stub.create_listener(req) + return json.loads(MessageToJson(ret)) + + def delete_listener(self, nqn: str, gateway: str, trttype, adrfam, + traddr: str, trsvcid: str): + response = self.stub.delete_listener(pb2.delete_listener_req( + nqn=nqn, + gateway_name=gateway, + trtype=trttype, + adrfam=adrfam, + traddr=traddr, + trsvcid=trsvcid + )) + return json.loads(MessageToJson(response))