[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
logger = logging.getLogger(__name__)
-REQUIRES_POST_ACTIONS = ['grafana', 'iscsi', 'prometheus', 'alertmanager', 'rgw']
+REQUIRES_POST_ACTIONS = ['grafana', 'iscsi', 'prometheus', 'alertmanager', 'rgw', 'nvmeof']
class CephadmServe:
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
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],
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:
--- /dev/null
+Subproject commit c6f6ce77863f854444dee3d2a59d360f3b4f2255
python3-saml~=1.4
requests~=2.26
Routes~=2.4
+grpcio~=1.48
+grpcio-tools~=1.48
--- /dev/null
+# -*- 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)
--- /dev/null
+# -*- 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)