Merge pull request #35068 from bk201/wip-45163

mgr/cephadm: config iSCSI gateways in Dashboard
This commit is contained in:
Sebastian Wagner 2020-05-18 20:35:50 +02:00 committed by GitHub
commit a65784e60a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 150 additions and 111 deletions

View File

@ -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):

View File

@ -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)

View 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)

View File

@ -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:

View File

@ -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:

View File

@ -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):