From 130cadf6e571e8f3559fefa9b3e25b02e20b3d42 Mon Sep 17 00:00:00 2001 From: Kiefer Chang Date: Thu, 11 Jun 2020 17:53:57 +0800 Subject: [PATCH] mgr/cephadm: set Ganesha pool/namespace settings in the Dashboard - Config Ganesha pool and namespace settings in Dashboard. - New `config_dashboard` option in cephadm. If the flag is Flase, cephadm won't manage Dashboard settings for daemons. This might be useful when users want to config API URLs by themselves or for debugging. Fixes: https://tracker.ceph.com/issues/45155 Signed-off-by: Kiefer Chang --- src/pybind/mgr/cephadm/module.py | 9 +- .../mgr/cephadm/services/cephadmservice.py | 82 ++++++++++++++++--- src/pybind/mgr/cephadm/services/iscsi.py | 61 +++++++------- src/pybind/mgr/cephadm/services/monitoring.py | 6 +- src/pybind/mgr/cephadm/services/nfs.py | 24 ++++++ 5 files changed, 133 insertions(+), 49 deletions(-) diff --git a/src/pybind/mgr/cephadm/module.py b/src/pybind/mgr/cephadm/module.py index f862a70e366a..d2b605c19b74 100644 --- a/src/pybind/mgr/cephadm/module.py +++ b/src/pybind/mgr/cephadm/module.py @@ -247,6 +247,12 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule): 'desc': 'internal - do not modify', # used to track track spec and other data migrations. }, + { + 'name': 'config_dashboard', + 'type': 'bool', + 'default': True, + 'desc': 'manage configs like API endpoints in Dashboard.' + } ] def __init__(self, *args, **kwargs): @@ -280,6 +286,7 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule): self.allow_ptrace = False self.prometheus_alerts_path = '' self.migration_current = None + self.config_dashboard = True self._cons = {} # type: Dict[str, Tuple[remoto.backends.BaseConnection,remoto.backends.LegacyModuleExecute]] @@ -1897,7 +1904,7 @@ you may want to run: continue # These daemon types require additional configs after creation - if dd.daemon_type in ['grafana', 'iscsi', 'prometheus', 'alertmanager']: + if dd.daemon_type in ['grafana', 'iscsi', 'prometheus', 'alertmanager', 'nfs']: daemons_post[dd.daemon_type].append(dd) deps = self._calc_daemon_deps(dd.daemon_type, dd.daemon_id) diff --git a/src/pybind/mgr/cephadm/services/cephadmservice.py b/src/pybind/mgr/cephadm/services/cephadmservice.py index 6acbb60493b5..c5ff300be503 100644 --- a/src/pybind/mgr/cephadm/services/cephadmservice.py +++ b/src/pybind/mgr/cephadm/services/cephadmservice.py @@ -1,5 +1,5 @@ import logging -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, List, Callable, Any from mgr_module import MonCommandFailed @@ -22,6 +22,11 @@ class CephadmService: def daemon_check_post(self, daemon_descrs: List[DaemonDescription]): """The post actions needed to be done after daemons are checked""" + if self.mgr.config_dashboard: + self.config_dashboard(daemon_descrs) + + def config_dashboard(self, daemon_descrs: List[DaemonDescription]): + """Config dashboard settings.""" raise NotImplementedError() def get_active_daemon(self, daemon_descrs: List[DaemonDescription]) -> DaemonDescription: @@ -36,25 +41,76 @@ class CephadmService: get_mon_cmd: str, set_mon_cmd: str, service_url: str): - """A helper to get and set service_url via Dashboard's MON command.""" + """A helper to get and set service_url via Dashboard's MON command. + + If result of get_mon_cmd differs from service_url, set_mon_cmd will + be sent to set the service_url. + """ + def get_set_cmd_dicts(out: str) -> List[dict]: + cmd_dict = { + 'prefix': set_mon_cmd, + 'value': service_url + } + return [cmd_dict] if service_url != out else [] + + self._check_and_set_dashboard( + service_name=service_name, + get_cmd=get_mon_cmd, + get_set_cmd_dicts=get_set_cmd_dicts + ) + + def _check_and_set_dashboard(self, + service_name: str, + get_cmd: str, + get_set_cmd_dicts: Callable[[str], List[dict]]): + """A helper to set configs in the Dashboard. + + The method is useful for the pattern: + - Getting a config from Dashboard by using a Dashboard command. e.g. current iSCSI + gateways. + - Parse or deserialize previous output. e.g. Dashboard command returns a JSON string. + - Determine if the config need to be update. NOTE: This step is important because if a + Dashboard command modified Ceph config, cephadm's config_notify() is called. Which + kicks the serve() loop and the logic using this method is likely to be called again. + A config should be updated only when needed. + - Update a config in Dashboard by using a Dashboard command. + + :param service_name: the service name to be used for logging + :type service_name: str + :param get_cmd: Dashboard command prefix to get config. e.g. dashboard get-grafana-api-url + :type get_cmd: str + :param get_set_cmd_dicts: function to create a list, and each item is a command dictionary. + e.g. + [ + { + 'prefix': 'dashboard iscsi-gateway-add', + 'service_url': 'http://admin:admin@aaa:5000', + 'name': 'aaa' + }, + { + 'prefix': 'dashboard iscsi-gateway-add', + 'service_url': 'http://admin:admin@bbb:5000', + 'name': 'bbb' + } + ] + The function should return empty list if no command need to be sent. + :type get_set_cmd_dicts: Callable[[str], List[dict]] + """ + try: _, out, _ = self.mgr.check_mon_command({ - 'prefix': get_mon_cmd + 'prefix': get_cmd }) except MonCommandFailed as e: - logger.warning('Failed to get service URL for %s: %s', service_name, e) + logger.warning('Failed to get Dashboard config for %s: %s', service_name, e) return - if out.strip() != service_url: + cmd_dicts = get_set_cmd_dicts(out.strip()) + for cmd_dict in list(cmd_dicts): try: - logger.info( - 'Setting service URL %s for %s in the Dashboard', service_url, service_name) - _, out, _ = self.mgr.check_mon_command({ - 'prefix': set_mon_cmd, - 'value': service_url, - }) + logger.info('Setting Dashboard config for %s: command: %s', service_name, cmd_dict) + _, out, _ = self.mgr.check_mon_command(cmd_dict) except MonCommandFailed as e: - logger.warning('Failed to set service URL %s for %s in the Dashboard: %s', - service_url, service_name, e) + logger.warning('Failed to set Dashboard config for %s: %s', service_name, e) class MonService(CephadmService): diff --git a/src/pybind/mgr/cephadm/services/iscsi.py b/src/pybind/mgr/cephadm/services/iscsi.py index 9b10d0a16526..5e63c4788bf1 100644 --- a/src/pybind/mgr/cephadm/services/iscsi.py +++ b/src/pybind/mgr/cephadm/services/iscsi.py @@ -61,39 +61,36 @@ class IscsiService(CephadmService): 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._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: + def config_dashboard(self, daemon_descrs: List[DaemonDescription]): + def get_set_cmd_dicts(out: str) -> List[dict]: + gateways = json.loads(out)['gateways'] + cmd_dicts = [] + 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._inventory_get_addr(dd.hostname) + service_url = 'http://{}:{}@{}:{}'.format( + spec.api_user, spec.api_password, host, spec.api_port or '5000') + gw = gateways.get(host) + if not gw or gw['service_url'] != service_url: logger.info('Adding iSCSI gateway %s to Dashboard', service_url) - _, out, _ = self.mgr.check_mon_command({ + cmd_dicts.append({ 'prefix': 'dashboard iscsi-gateway-add', 'service_url': service_url, - 'name': dd.hostname + 'name': host }) - except MonCommandFailed as e: - logger.warning( - 'Failed to add iSCSI gateway %s to the Dashboard: %s', service_url, e) + return cmd_dicts + + self._check_and_set_dashboard( + service_name='iSCSI', + get_cmd='dashboard iscsi-gateway-list', + get_set_cmd_dicts=get_set_cmd_dicts + ) diff --git a/src/pybind/mgr/cephadm/services/monitoring.py b/src/pybind/mgr/cephadm/services/monitoring.py index b17a448755b2..47fa90deb19a 100644 --- a/src/pybind/mgr/cephadm/services/monitoring.py +++ b/src/pybind/mgr/cephadm/services/monitoring.py @@ -60,7 +60,7 @@ class GrafanaService(CephadmService): # Use the least-created one as the active daemon return daemon_descrs[-1] - def daemon_check_post(self, daemon_descrs: List[DaemonDescription]): + def config_dashboard(self, daemon_descrs: List[DaemonDescription]): # TODO: signed cert dd = self.get_active_daemon(daemon_descrs) service_url = 'https://{}:{}'.format( @@ -128,7 +128,7 @@ class AlertmanagerService(CephadmService): # TODO: if there are multiple daemons, who is the active one? return daemon_descrs[0] - def daemon_check_post(self, daemon_descrs: List[DaemonDescription]): + def config_dashboard(self, daemon_descrs: List[DaemonDescription]): dd = self.get_active_daemon(daemon_descrs) service_url = 'http://{}:{}'.format(self._inventory_get_addr(dd.hostname), self.DEFAULT_SERVICE_PORT) self._set_service_url_on_dashboard( @@ -216,7 +216,7 @@ class PrometheusService(CephadmService): # TODO: if there are multiple daemons, who is the active one? return daemon_descrs[0] - def daemon_check_post(self, daemon_descrs: List[DaemonDescription]): + def config_dashboard(self, daemon_descrs: List[DaemonDescription]): dd = self.get_active_daemon(daemon_descrs) service_url = 'http://{}:{}'.format( self._inventory_get_addr(dd.hostname), self.DEFAULT_SERVICE_PORT) diff --git a/src/pybind/mgr/cephadm/services/nfs.py b/src/pybind/mgr/cephadm/services/nfs.py index 4c7f79133491..e3f3aa4335b8 100644 --- a/src/pybind/mgr/cephadm/services/nfs.py +++ b/src/pybind/mgr/cephadm/services/nfs.py @@ -7,6 +7,7 @@ from ceph.deployment.service_spec import NFSServiceSpec import orchestrator from orchestrator import OrchestratorError +from orchestrator import DaemonDescription import cephadm from .. import utils @@ -70,6 +71,29 @@ class NFSService(CephadmService): daemon_id, host, spec)) return self.mgr._create_daemon('nfs', daemon_id, host) + def config_dashboard(self, daemon_descrs: List[DaemonDescription]): + + def get_set_cmd_dicts(out: str) -> List[dict]: + locations: List[str] = [] + for dd in daemon_descrs: + spec = cast(NFSServiceSpec, + self.mgr.spec_store.specs.get(dd.service_name(), None)) + if not spec or not spec.service_id: + logger.warning('No ServiceSpec or service_id found for %s', dd) + continue + locations.append('{}:{}/{}'.format(spec.service_id, spec.pool, spec.namespace)) + new_value = ','.join(locations) + if new_value and new_value != out: + return [{'prefix': 'dashboard set-ganesha-clusters-rados-pool-namespace', + 'value': new_value}] + return [] + + self._check_and_set_dashboard( + service_name='Ganesha', + get_cmd='dashboard get-ganesha-clusters-rados-pool-namespace', + get_set_cmd_dicts=get_set_cmd_dicts + ) + class NFSGanesha(object): def __init__(self, -- 2.47.3