mirror of
https://github.com/ceph/ceph
synced 2025-02-22 18:47:18 +00:00
Merge pull request #35068 from bk201/wip-45163
mgr/cephadm: config iSCSI gateways in Dashboard
This commit is contained in:
commit
a65784e60a
@ -31,7 +31,8 @@ from orchestrator import OrchestratorError, OrchestratorValidationError, HostSpe
|
||||
from . import remotes
|
||||
from . import utils
|
||||
from .services.cephadmservice import MonService, MgrService, MdsService, RgwService, \
|
||||
RbdMirrorService, CrashService, IscsiService
|
||||
RbdMirrorService, CrashService
|
||||
from .services.iscsi import IscsiService
|
||||
from .services.nfs import NFSService
|
||||
from .services.osd import RemoveUtil, OSDRemoval, OSDService
|
||||
from .services.monitoring import GrafanaService, AlertmanagerService, PrometheusService, \
|
||||
@ -313,7 +314,7 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule):
|
||||
self.prometheus_service = PrometheusService(self)
|
||||
self.node_exporter_service = NodeExporterService(self)
|
||||
self.crash_service = CrashService(self)
|
||||
self.iscsi_servcie = IscsiService(self)
|
||||
self.iscsi_service = IscsiService(self)
|
||||
|
||||
def shutdown(self):
|
||||
self.log.debug('shutdown')
|
||||
@ -1662,13 +1663,13 @@ you may want to run:
|
||||
'prometheus': self.prometheus_service.create,
|
||||
'node-exporter': self.node_exporter_service.create,
|
||||
'crash': self.crash_service.create,
|
||||
'iscsi': self.iscsi_servcie.create,
|
||||
'iscsi': self.iscsi_service.create,
|
||||
}
|
||||
config_fns = {
|
||||
'mds': self.mds_service.config,
|
||||
'rgw': self.rgw_service.config,
|
||||
'nfs': self.nfs_service.config,
|
||||
'iscsi': self.iscsi_servcie.config,
|
||||
'iscsi': self.iscsi_service.config,
|
||||
}
|
||||
create_func = create_fns.get(daemon_type, None)
|
||||
if not create_func:
|
||||
@ -1785,6 +1786,7 @@ you may want to run:
|
||||
|
||||
daemons = self.cache.get_daemons()
|
||||
grafanas = [] # type: List[orchestrator.DaemonDescription]
|
||||
iscsi_daemons = []
|
||||
for dd in daemons:
|
||||
# orphan?
|
||||
spec = self.spec_store.specs.get(dd.service_name(), None)
|
||||
@ -1802,6 +1804,8 @@ you may want to run:
|
||||
if dd.daemon_type == 'grafana':
|
||||
# put running instances at the front of the list
|
||||
grafanas.insert(0, dd)
|
||||
elif dd.daemon_type == 'iscsi':
|
||||
iscsi_daemons.append(dd)
|
||||
deps = self._calc_daemon_deps(dd.daemon_type, dd.daemon_id)
|
||||
last_deps, last_config = self.cache.get_daemon_last_config_deps(
|
||||
dd.hostname, dd.name())
|
||||
@ -1827,21 +1831,10 @@ you may want to run:
|
||||
self._create_daemon(dd.daemon_type, dd.daemon_id,
|
||||
dd.hostname, reconfig=True)
|
||||
|
||||
# make sure the dashboard [does not] references grafana
|
||||
try:
|
||||
current_url = self.get_module_option_ex('dashboard',
|
||||
'GRAFANA_API_URL')
|
||||
if grafanas:
|
||||
host = grafanas[0].hostname
|
||||
url = f'https://{self.inventory.get_addr(host)}:3000'
|
||||
if current_url != url:
|
||||
self.log.info('Setting dashboard grafana config to %s' % url)
|
||||
self.set_module_option_ex('dashboard', 'GRAFANA_API_URL',
|
||||
url)
|
||||
# FIXME: is it a signed cert??
|
||||
except Exception as e:
|
||||
self.log.debug('got exception fetching dashboard grafana state: %s',
|
||||
e)
|
||||
if grafanas:
|
||||
self.grafana_service.daemon_check_post(grafanas)
|
||||
if iscsi_daemons:
|
||||
self.iscsi_service.daemon_check_post(iscsi_daemons)
|
||||
|
||||
def _add_daemon(self, daemon_type, spec,
|
||||
create_func: Callable[..., T], config_func=None) -> List[T]:
|
||||
@ -1973,7 +1966,7 @@ you may want to run:
|
||||
@trivial_completion
|
||||
def add_iscsi(self, spec):
|
||||
# type: (ServiceSpec) -> List[str]
|
||||
return self._add_daemon('iscsi', spec, self.iscsi_servcie.create, self.iscsi_servcie.config)
|
||||
return self._add_daemon('iscsi', spec, self.iscsi_service.create, self.iscsi_service.config)
|
||||
|
||||
@trivial_completion
|
||||
def apply_iscsi(self, spec):
|
||||
|
@ -1,8 +1,8 @@
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from ceph.deployment.service_spec import ServiceSpec, RGWSpec, IscsiServiceSpec
|
||||
from orchestrator import OrchestratorError
|
||||
from ceph.deployment.service_spec import ServiceSpec, RGWSpec
|
||||
from orchestrator import OrchestratorError, DaemonDescription
|
||||
from cephadm import utils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -18,6 +18,10 @@ class CephadmService:
|
||||
def __init__(self, mgr: "CephadmOrchestrator"):
|
||||
self.mgr: "CephadmOrchestrator" = mgr
|
||||
|
||||
def daemon_check_post(self, daemon_descrs: List[DaemonDescription]):
|
||||
"""The post actions needed to be done after daemons are checked"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class MonService(CephadmService):
|
||||
def create(self, name, host, network):
|
||||
@ -180,64 +184,3 @@ class CrashService(CephadmService):
|
||||
'mgr', 'profile crash'],
|
||||
})
|
||||
return self.mgr._create_daemon('crash', daemon_id, host, keyring=keyring)
|
||||
|
||||
|
||||
class IscsiService(CephadmService):
|
||||
def config(self, spec: IscsiServiceSpec):
|
||||
self.mgr._check_pool_exists(spec.pool, spec.service_name())
|
||||
|
||||
logger.info('Saving service %s spec with placement %s' % (
|
||||
spec.service_name(), spec.placement.pretty_str()))
|
||||
self.mgr.spec_store.save(spec)
|
||||
|
||||
def create(self, igw_id, host, spec) -> str:
|
||||
ret, keyring, err = self.mgr.check_mon_command({
|
||||
'prefix': 'auth get-or-create',
|
||||
'entity': utils.name_to_auth_entity('iscsi') + '.' + igw_id,
|
||||
'caps': ['mon', 'profile rbd, '
|
||||
'allow command "osd blacklist", '
|
||||
'allow command "config-key get" with "key" prefix "iscsi/"',
|
||||
'osd', f'allow rwx pool={spec.pool}'],
|
||||
})
|
||||
|
||||
if spec.ssl_cert:
|
||||
if isinstance(spec.ssl_cert, list):
|
||||
cert_data = '\n'.join(spec.ssl_cert)
|
||||
else:
|
||||
cert_data = spec.ssl_cert
|
||||
ret, out, err = self.mgr.mon_command({
|
||||
'prefix': 'config-key set',
|
||||
'key': f'iscsi/{utils.name_to_config_section("iscsi")}.{igw_id}/iscsi-gateway.crt',
|
||||
'val': cert_data,
|
||||
})
|
||||
|
||||
if spec.ssl_key:
|
||||
if isinstance(spec.ssl_key, list):
|
||||
key_data = '\n'.join(spec.ssl_key)
|
||||
else:
|
||||
key_data = spec.ssl_key
|
||||
ret, out, err = self.mgr.mon_command({
|
||||
'prefix': 'config-key set',
|
||||
'key': f'iscsi/{utils.name_to_config_section("iscsi")}.{igw_id}/iscsi-gateway.key',
|
||||
'val': key_data,
|
||||
})
|
||||
|
||||
api_secure = 'false' if spec.api_secure is None else spec.api_secure
|
||||
igw_conf = f"""
|
||||
# generated by cephadm
|
||||
[config]
|
||||
cluster_client_name = {utils.name_to_config_section('iscsi')}.{igw_id}
|
||||
pool = {spec.pool}
|
||||
trusted_ip_list = {spec.trusted_ip_list or ''}
|
||||
minimum_gateways = 1
|
||||
api_port = {spec.api_port or ''}
|
||||
api_user = {spec.api_user or ''}
|
||||
api_password = {spec.api_password or ''}
|
||||
api_secure = {api_secure}
|
||||
log_to_stderr = True
|
||||
log_to_stderr_prefix = debug
|
||||
log_to_file = False
|
||||
"""
|
||||
extra_config = {'iscsi-gateway.cfg': igw_conf}
|
||||
return self.mgr._create_daemon('iscsi', igw_id, host, keyring=keyring,
|
||||
extra_config=extra_config)
|
||||
|
110
src/pybind/mgr/cephadm/services/iscsi.py
Normal file
110
src/pybind/mgr/cephadm/services/iscsi.py
Normal file
@ -0,0 +1,110 @@
|
||||
import json
|
||||
import logging
|
||||
from typing import List, cast
|
||||
|
||||
from mgr_module import MonCommandFailed
|
||||
from ceph.deployment.service_spec import IscsiServiceSpec
|
||||
|
||||
from orchestrator import DaemonDescription
|
||||
from .cephadmservice import CephadmService
|
||||
from .. import utils
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IscsiService(CephadmService):
|
||||
def config(self, spec: IscsiServiceSpec):
|
||||
self.mgr._check_pool_exists(spec.pool, spec.service_name())
|
||||
|
||||
logger.info('Saving service %s spec with placement %s' % (
|
||||
spec.service_name(), spec.placement.pretty_str()))
|
||||
self.mgr.spec_store.save(spec)
|
||||
|
||||
def create(self, igw_id, host, spec) -> str:
|
||||
ret, keyring, err = self.mgr.check_mon_command({
|
||||
'prefix': 'auth get-or-create',
|
||||
'entity': utils.name_to_auth_entity('iscsi') + '.' + igw_id,
|
||||
'caps': ['mon', 'profile rbd, '
|
||||
'allow command "osd blacklist", '
|
||||
'allow command "config-key get" with "key" prefix "iscsi/"',
|
||||
'osd', f'allow rwx pool={spec.pool}'],
|
||||
})
|
||||
|
||||
if spec.ssl_cert:
|
||||
if isinstance(spec.ssl_cert, list):
|
||||
cert_data = '\n'.join(spec.ssl_cert)
|
||||
else:
|
||||
cert_data = spec.ssl_cert
|
||||
ret, out, err = self.mgr.mon_command({
|
||||
'prefix': 'config-key set',
|
||||
'key': f'iscsi/{utils.name_to_config_section("iscsi")}.{igw_id}/iscsi-gateway.crt',
|
||||
'val': cert_data,
|
||||
})
|
||||
|
||||
if spec.ssl_key:
|
||||
if isinstance(spec.ssl_key, list):
|
||||
key_data = '\n'.join(spec.ssl_key)
|
||||
else:
|
||||
key_data = spec.ssl_key
|
||||
ret, out, err = self.mgr.mon_command({
|
||||
'prefix': 'config-key set',
|
||||
'key': f'iscsi/{utils.name_to_config_section("iscsi")}.{igw_id}/iscsi-gateway.key',
|
||||
'val': key_data,
|
||||
})
|
||||
|
||||
api_secure = 'false' if spec.api_secure is None else spec.api_secure
|
||||
igw_conf = f"""
|
||||
# generated by cephadm
|
||||
[config]
|
||||
cluster_client_name = {utils.name_to_config_section('iscsi')}.{igw_id}
|
||||
pool = {spec.pool}
|
||||
trusted_ip_list = {spec.trusted_ip_list or ''}
|
||||
minimum_gateways = 1
|
||||
api_port = {spec.api_port or ''}
|
||||
api_user = {spec.api_user or ''}
|
||||
api_password = {spec.api_password or ''}
|
||||
api_secure = {api_secure}
|
||||
log_to_stderr = True
|
||||
log_to_stderr_prefix = debug
|
||||
log_to_file = False
|
||||
"""
|
||||
extra_config = {'iscsi-gateway.cfg': igw_conf}
|
||||
return self.mgr._create_daemon('iscsi', igw_id, host, keyring=keyring,
|
||||
extra_config=extra_config)
|
||||
|
||||
def daemon_check_post(self, daemon_descrs: List[DaemonDescription]):
|
||||
try:
|
||||
_, out, _ = self.mgr.check_mon_command({
|
||||
'prefix': 'dashboard iscsi-gateway-list'
|
||||
})
|
||||
except MonCommandFailed as e:
|
||||
logger.warning('Failed to get existing iSCSI gateways from the Dashboard: %s', e)
|
||||
return
|
||||
|
||||
gateways = json.loads(out)['gateways']
|
||||
for dd in daemon_descrs:
|
||||
spec = cast(IscsiServiceSpec,
|
||||
self.mgr.spec_store.specs.get(dd.service_name(), None))
|
||||
if not spec:
|
||||
logger.warning('No ServiceSpec found for %s', dd)
|
||||
continue
|
||||
if not all([spec.api_user, spec.api_password]):
|
||||
reason = 'api_user or api_password is not specified in ServiceSpec'
|
||||
logger.warning(
|
||||
'Unable to add iSCSI gateway to the Dashboard for %s: %s', dd, reason)
|
||||
continue
|
||||
host = self.mgr.inventory.get_addr(dd.hostname)
|
||||
service_url = 'http://{}:{}@{}:{}'.format(
|
||||
spec.api_user, spec.api_password, host, spec.api_port or '5000')
|
||||
gw = gateways.get(dd.hostname)
|
||||
if not gw or gw['service_url'] != service_url:
|
||||
try:
|
||||
logger.info('Adding iSCSI gateway %s to Dashboard', service_url)
|
||||
_, out, _ = self.mgr.check_mon_command({
|
||||
'prefix': 'dashboard iscsi-gateway-add',
|
||||
'service_url': service_url,
|
||||
'name': dd.hostname
|
||||
})
|
||||
except MonCommandFailed as e:
|
||||
logger.warning(
|
||||
'Failed to add iSCSI gateway %s to the Dashboard: %s', service_url, e)
|
@ -2,6 +2,7 @@ import logging
|
||||
import os
|
||||
from typing import List, Any, Tuple, Dict
|
||||
|
||||
from orchestrator import DaemonDescription
|
||||
from cephadm.services.cephadmservice import CephadmService
|
||||
from mgr_util import verify_tls, ServerConfigException, create_self_signed_cert
|
||||
|
||||
@ -104,6 +105,19 @@ datasources:
|
||||
}
|
||||
return config_file, sorted(deps)
|
||||
|
||||
def daemon_check_post(self, daemon_descrs: List[DaemonDescription]):
|
||||
# make sure the dashboard [does not] references grafana
|
||||
try:
|
||||
current_url = self.mgr.get_module_option_ex('dashboard', 'GRAFANA_API_URL')
|
||||
host = daemon_descrs[0].hostname
|
||||
url = f'https://{self.mgr.inventory.get_addr(host)}:3000'
|
||||
if current_url != url:
|
||||
logger.info('Setting dashboard grafana config to %s' % url)
|
||||
self.mgr.set_module_option_ex('dashboard', 'GRAFANA_API_URL', url)
|
||||
# FIXME: is it a signed cert??
|
||||
except Exception as e:
|
||||
logger.debug('got exception fetching dashboard grafana state: %s', e)
|
||||
|
||||
|
||||
class AlertmanagerService(CephadmService):
|
||||
def create(self, daemon_id, host) -> str:
|
||||
|
@ -18,12 +18,14 @@ def list_iscsi_gateways(_):
|
||||
|
||||
|
||||
@CLIWriteCommand('dashboard iscsi-gateway-add',
|
||||
'name=service_url,type=CephString',
|
||||
'name=service_url,type=CephString '
|
||||
'name=name,type=CephString,req=false',
|
||||
'Add iSCSI gateway configuration')
|
||||
def add_iscsi_gateway(_, service_url):
|
||||
def add_iscsi_gateway(_, service_url, name=None):
|
||||
try:
|
||||
IscsiGatewaysConfig.validate_service_url(service_url)
|
||||
name = IscsiClient.instance(service_url=service_url).get_hostname()['data']
|
||||
if name is None:
|
||||
name = IscsiClient.instance(service_url=service_url).get_hostname()['data']
|
||||
IscsiGatewaysConfig.add_gateway(name, service_url)
|
||||
return 0, 'Success', ''
|
||||
except IscsiGatewayAlreadyExists as ex:
|
||||
|
@ -3,15 +3,11 @@ from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
|
||||
from orchestrator import OrchestratorError
|
||||
|
||||
try:
|
||||
from urlparse import urlparse
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from mgr_util import merge_dicts
|
||||
from .orchestrator import OrchClient
|
||||
from .. import mgr
|
||||
|
||||
|
||||
@ -76,19 +72,6 @@ class IscsiGatewaysConfig(object):
|
||||
# or we will try to update automatically next time
|
||||
continue
|
||||
|
||||
@staticmethod
|
||||
def _load_config_from_orchestrator():
|
||||
config = {'gateways': {}} # type: dict
|
||||
try:
|
||||
instances = OrchClient.instance().services.list("iscsi")
|
||||
for instance in instances:
|
||||
config['gateways'][instance.hostname] = {
|
||||
'service_url': instance.service_url
|
||||
}
|
||||
except (RuntimeError, OrchestratorError, ImportError):
|
||||
pass
|
||||
return config
|
||||
|
||||
@classmethod
|
||||
def _save_config(cls, config):
|
||||
mgr.set_store(_ISCSI_STORE_KEY, json.dumps(config))
|
||||
@ -110,9 +93,6 @@ class IscsiGatewaysConfig(object):
|
||||
|
||||
@classmethod
|
||||
def remove_gateway(cls, name):
|
||||
if name in cls._load_config_from_orchestrator()['gateways']:
|
||||
raise ManagedByOrchestratorException()
|
||||
|
||||
config = cls._load_config_from_store()
|
||||
if name not in config['gateways']:
|
||||
raise IscsiGatewayDoesNotExist(name)
|
||||
@ -122,10 +102,7 @@ class IscsiGatewaysConfig(object):
|
||||
|
||||
@classmethod
|
||||
def get_gateways_config(cls):
|
||||
orch_config = cls._load_config_from_orchestrator()
|
||||
local_config = cls._load_config_from_store()
|
||||
|
||||
return {'gateways': merge_dicts(orch_config['gateways'], local_config['gateways'])}
|
||||
return cls._load_config_from_store()
|
||||
|
||||
@classmethod
|
||||
def get_gateway_config(cls, name):
|
||||
|
Loading…
Reference in New Issue
Block a user