'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,