- 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 <kiefer.chang@suse.com>
'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):
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]]
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)
import logging
-from typing import TYPE_CHECKING, List
+from typing import TYPE_CHECKING, List, Callable, Any
from mgr_module import MonCommandFailed
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:
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):
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
+ )
# 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(
# 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(
# 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)
import orchestrator
from orchestrator import OrchestratorError
+from orchestrator import DaemonDescription
import cephadm
from .. import utils
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,