]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/cephadm: configure the dashboard gateways
authorNizamudeen A <nia@redhat.com>
Tue, 5 Dec 2023 10:11:01 +0000 (15:41 +0530)
committerNizamudeen A <nia@redhat.com>
Tue, 30 Jan 2024 10:09:35 +0000 (15:39 +0530)
cephadm configures the nvmeof gateways and add the gateways to a config
store which dashboard will later on fetch to make the grpc calls.

Fixes: https://tracker.ceph.com/issues/64201
Signed-off-by: Nizamudeen A <nia@redhat.com>
.gitmodules
src/pybind/mgr/cephadm/serve.py
src/pybind/mgr/cephadm/services/nvmeof.py
src/pybind/mgr/dashboard/ceph-nvmeof [new submodule]
src/pybind/mgr/dashboard/constraints.txt
src/pybind/mgr/dashboard/services/nvmeof_cli.py [new file with mode: 0644]
src/pybind/mgr/dashboard/services/nvmeof_conf.py [new file with mode: 0644]

index 088ae3b577ce2f1489e213f8d9a22d8cfaab19c8..e47fe6495f5fee57738b844c8efdb3f64836ef6e 100644 (file)
@@ -75,3 +75,6 @@
 [submodule "src/jaegertracing/opentelemetry-cpp"]
        path = src/jaegertracing/opentelemetry-cpp
        url = https://github.com/open-telemetry/opentelemetry-cpp.git
+[submodule "nvmeof"]
+       path = src/pybind/mgr/dashboard/ceph-nvmeof
+       url = https://github.com/ceph/ceph-nvmeof.git
index 116e97238691995b05392b2d063c94c14b095b00..4afe0a3b7aca52c1449c047a9689290c2879c38a 100644 (file)
@@ -40,7 +40,7 @@ if TYPE_CHECKING:
 
 logger = logging.getLogger(__name__)
 
-REQUIRES_POST_ACTIONS = ['grafana', 'iscsi', 'prometheus', 'alertmanager', 'rgw']
+REQUIRES_POST_ACTIONS = ['grafana', 'iscsi', 'prometheus', 'alertmanager', 'rgw', 'nvmeof']
 
 
 class CephadmServe:
index 7d2dd16cf0d6e3228e511e8f06c11c31d6b6fb62..32cb71b67447953afa13c882ab69d8384ef12157 100644 (file)
@@ -2,6 +2,7 @@ import errno
 import logging
 import json
 from typing import List, cast, Optional
+from ipaddress import ip_address, IPv6Address
 
 from mgr_module import HandleCommandResult
 from ceph.deployment.service_spec import NvmeofServiceSpec
@@ -55,8 +56,39 @@ class NvmeofService(CephService):
         return daemon_spec
 
     def config_dashboard(self, daemon_descrs: List[DaemonDescription]) -> None:
-        # TODO: what integration do we need with the dashboard?
-        pass
+        def get_set_cmd_dicts(out: str) -> List[dict]:
+            gateways = json.loads(out)['gateways']
+            cmd_dicts = []
+
+            spec = cast(NvmeofServiceSpec,
+                        self.mgr.spec_store.all_specs.get(daemon_descrs[0].service_name(), None))
+
+            for dd in daemon_descrs:
+                assert dd.hostname is not None
+
+                if not spec:
+                    logger.warning(f'No ServiceSpec found for {dd.service_name()}')
+                    continue
+
+                ip = utils.resolve_ip(self.mgr.inventory.get_addr(dd.hostname))
+                if type(ip_address(ip)) is IPv6Address:
+                    ip = f'[{ip}]'
+                service_url = '{}:{}'.format(ip, spec.port or '5500')
+                gw = gateways.get(dd.hostname)
+                if not gw or gw['service_url'] != service_url:
+                    logger.info(f'Adding NVMeoF gateway {service_url} to Dashboard')
+                    cmd_dicts.append({
+                        'prefix': 'dashboard nvmeof-gateway-add',
+                        'inbuf': service_url,
+                        'name': dd.hostname
+                    })
+            return cmd_dicts
+
+        self._check_and_set_dashboard(
+            service_name='nvmeof',
+            get_cmd='dashboard nvmeof-gateway-list',
+            get_set_cmd_dicts=get_set_cmd_dicts
+        )
 
     def ok_to_stop(self,
                    daemon_ids: List[str],
@@ -83,7 +115,14 @@ class NvmeofService(CephService):
         Called after the daemon is removed.
         """
         logger.debug(f'Post remove daemon {self.TYPE}.{daemon.daemon_id}')
-        # TODO: remove config for dashboard nvmeof gateways if any
+        # remove config for dashboard nvmeof gateways if any
+        ret, out, err = self.mgr.mon_command({
+            'prefix': 'dashboard nvmeof-gateway-rm',
+            'name': daemon.hostname,
+        })
+        if not ret:
+            logger.info(f'{daemon.hostname} removed from iscsi gateways dashboard config')
+
         # and any certificates being used for mTLS
 
     def purge(self, service_name: str) -> None:
diff --git a/src/pybind/mgr/dashboard/ceph-nvmeof b/src/pybind/mgr/dashboard/ceph-nvmeof
new file mode 160000 (submodule)
index 0000000..c6f6ce7
--- /dev/null
@@ -0,0 +1 @@
+Subproject commit c6f6ce77863f854444dee3d2a59d360f3b4f2255
index fd6141048800a599907d89e73886bc8dd0279c90..590a8c1e1c42c9fa5f61f638db5c8bf35cc52080 100644 (file)
@@ -4,3 +4,5 @@ bcrypt~=3.1
 python3-saml~=1.4
 requests~=2.26
 Routes~=2.4
+grpcio~=1.48
+grpcio-tools~=1.48
diff --git a/src/pybind/mgr/dashboard/services/nvmeof_cli.py b/src/pybind/mgr/dashboard/services/nvmeof_cli.py
new file mode 100644 (file)
index 0000000..5921ab4
--- /dev/null
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+import errno
+import json
+
+from mgr_module import CLICheckNonemptyFileInput, CLIReadCommand, CLIWriteCommand
+
+from ..rest_client import RequestException
+from .nvmeof_conf import NvmeofGatewaysConfig, NvmeofGatewayAlreadyExists, \
+    ManagedByOrchestratorException
+
+@CLIReadCommand('dashboard nvmeof-gateway-list')
+def list_nvmeof_gateways(_):
+    '''
+    List NVMe-oF gateways
+    '''
+    return 0, json.dumps(NvmeofGatewaysConfig.get_gateways_config()), ''
+
+@CLIWriteCommand('dashboard nvmeof-gateway-add')
+@CLICheckNonemptyFileInput(desc='NVMe-oF gateway configuration')
+def add_nvmeof_gateway(_, inbuf, name: str):
+    '''
+    Add NVMe-oF gateway configuration. Gateway URL read from -i <file>
+    '''
+    service_url = inbuf
+    try:
+        NvmeofGatewaysConfig.add_gateway(name, service_url)
+        return 0, 'Success', ''
+    except NvmeofGatewayAlreadyExists as ex:
+        return -errno.EEXIST, '', str(ex)
+    except ManagedByOrchestratorException as ex:
+        return -errno.EINVAL, '', str(ex)
+    except RequestException as ex:
+        return -errno.EINVAL, '', str(ex)
+
+@CLIWriteCommand('dashboard nvmeof-gateway-rm')
+def remove_nvmeof_gateway(_, name: str):
+    '''
+    Remove NVMe-oF gateway configuration
+    '''
+    try:
+        NvmeofGatewaysConfig.remove_gateway(name)
+        return 0, 'Success', ''
+    except ManagedByOrchestratorException as ex:
+        return -errno.EINVAL, '', str(ex)
diff --git a/src/pybind/mgr/dashboard/services/nvmeof_conf.py b/src/pybind/mgr/dashboard/services/nvmeof_conf.py
new file mode 100644 (file)
index 0000000..709635d
--- /dev/null
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+
+import json
+
+from .. import mgr
+
+class NvmeofGatewayAlreadyExists(Exception):
+    def __init__(self, gateway_name):
+        super(NvmeofGatewayAlreadyExists, self).__init__(
+            "NVMe-oF gateway '{}' already exists".format(gateway_name))
+
+class NvmeofGatewayDoesNotExist(Exception):
+    def __init__(self, hostname):
+        super(NvmeofGatewayDoesNotExist, self).__init__(
+            "NVMe-oF gateway '{}' does not exist".format(hostname))
+
+class ManagedByOrchestratorException(Exception):
+    def __init__(self):
+        super(ManagedByOrchestratorException, self).__init__(
+            "NVMe-oF configuration is managed by the orchestrator")
+
+_NVMEOF_STORE_KEY = "_nvmeof_config"
+
+class NvmeofGatewaysConfig(object):
+    @classmethod
+    def _load_config_from_store(cls):
+        json_db = mgr.get_store(_NVMEOF_STORE_KEY,
+                                '{"gateways": {}}')
+        config = json.loads(json_db)
+        cls._save_config(config)
+        return config
+
+    @classmethod
+    def _save_config(cls, config):
+        mgr.set_store(_NVMEOF_STORE_KEY, json.dumps(config))
+    
+    @classmethod
+    def get_gateways_config(cls):
+        return cls._load_config_from_store()
+    
+    @classmethod
+    def add_gateway(cls, name, service_url):
+        config = cls.get_gateways_config()
+        if name in config:
+            raise NvmeofGatewayAlreadyExists(name)
+        config['gateways'][name] = {'service_url': service_url}
+        cls._save_config(config)
+    
+    @classmethod
+    def remove_gateway(cls, name):
+        config = cls.get_gateways_config()
+        if name not in config['gateways']:
+            raise NvmeofGatewayDoesNotExist(name)
+        del config['gateways'][name]
+        cls._save_config(config)