summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/controllers/health.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
commit19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch)
tree42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/pybind/mgr/dashboard/controllers/health.py
parentInitial commit. (diff)
downloadceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.tar.xz
ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.zip
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/pybind/mgr/dashboard/controllers/health.py')
-rw-r--r--src/pybind/mgr/dashboard/controllers/health.py294
1 files changed, 294 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/controllers/health.py b/src/pybind/mgr/dashboard/controllers/health.py
new file mode 100644
index 000000000..304402d35
--- /dev/null
+++ b/src/pybind/mgr/dashboard/controllers/health.py
@@ -0,0 +1,294 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+import json
+
+from .. import mgr
+from ..rest_client import RequestException
+from ..security import Permission, Scope
+from ..services.ceph_service import CephService
+from ..services.iscsi_cli import IscsiGatewaysConfig
+from ..services.iscsi_client import IscsiClient
+from ..tools import partial_dict
+from . import APIDoc, APIRouter, BaseController, Endpoint, EndpointDoc
+from .host import get_hosts
+
+HEALTH_MINIMAL_SCHEMA = ({
+ 'client_perf': ({
+ 'read_bytes_sec': (int, ''),
+ 'read_op_per_sec': (int, ''),
+ 'recovering_bytes_per_sec': (int, ''),
+ 'write_bytes_sec': (int, ''),
+ 'write_op_per_sec': (int, ''),
+ }, ''),
+ 'df': ({
+ 'stats': ({
+ 'total_avail_bytes': (int, ''),
+ 'total_bytes': (int, ''),
+ 'total_used_raw_bytes': (int, ''),
+ }, '')
+ }, ''),
+ 'fs_map': ({
+ 'filesystems': ([{
+ 'mdsmap': ({
+ 'session_autoclose': (int, ''),
+ 'balancer': (str, ''),
+ 'up': (str, ''),
+ 'last_failure_osd_epoch': (int, ''),
+ 'in': ([int], ''),
+ 'last_failure': (int, ''),
+ 'max_file_size': (int, ''),
+ 'explicitly_allowed_features': (int, ''),
+ 'damaged': ([int], ''),
+ 'tableserver': (int, ''),
+ 'failed': ([int], ''),
+ 'metadata_pool': (int, ''),
+ 'epoch': (int, ''),
+ 'stopped': ([int], ''),
+ 'max_mds': (int, ''),
+ 'compat': ({
+ 'compat': (str, ''),
+ 'ro_compat': (str, ''),
+ 'incompat': (str, ''),
+ }, ''),
+ 'required_client_features': (str, ''),
+ 'data_pools': ([int], ''),
+ 'info': (str, ''),
+ 'fs_name': (str, ''),
+ 'created': (str, ''),
+ 'standby_count_wanted': (int, ''),
+ 'enabled': (bool, ''),
+ 'modified': (str, ''),
+ 'session_timeout': (int, ''),
+ 'flags': (int, ''),
+ 'ever_allowed_features': (int, ''),
+ 'root': (int, ''),
+ }, ''),
+ 'standbys': (str, ''),
+ }], ''),
+ }, ''),
+ 'health': ({
+ 'checks': (str, ''),
+ 'mutes': (str, ''),
+ 'status': (str, ''),
+ }, ''),
+ 'hosts': (int, ''),
+ 'iscsi_daemons': ({
+ 'up': (int, ''),
+ 'down': (int, '')
+ }, ''),
+ 'mgr_map': ({
+ 'active_name': (str, ''),
+ 'standbys': (str, '')
+ }, ''),
+ 'mon_status': ({
+ 'monmap': ({
+ 'mons': (str, ''),
+ }, ''),
+ 'quorum': ([int], '')
+ }, ''),
+ 'osd_map': ({
+ 'osds': ([{
+ 'in': (int, ''),
+ 'up': (int, ''),
+ }], '')
+ }, ''),
+ 'pg_info': ({
+ 'object_stats': ({
+ 'num_objects': (int, ''),
+ 'num_object_copies': (int, ''),
+ 'num_objects_degraded': (int, ''),
+ 'num_objects_misplaced': (int, ''),
+ 'num_objects_unfound': (int, ''),
+ }, ''),
+ 'pgs_per_osd': (int, ''),
+ 'statuses': (str, '')
+ }, ''),
+ 'pools': (str, ''),
+ 'rgw': (int, ''),
+ 'scrub_status': (str, '')
+})
+
+
+class HealthData(object):
+ """
+ A class to be used in combination with BaseController to allow either
+ "full" or "minimal" sets of health data to be collected.
+
+ To function properly, it needs BaseCollector._has_permissions to be passed
+ in as ``auth_callback``.
+ """
+
+ def __init__(self, auth_callback, minimal=True):
+ self._has_permissions = auth_callback
+ self._minimal = minimal
+
+ def all_health(self):
+ result = {
+ "health": self.basic_health(),
+ }
+
+ if self._has_permissions(Permission.READ, Scope.MONITOR):
+ result['mon_status'] = self.mon_status()
+
+ if self._has_permissions(Permission.READ, Scope.CEPHFS):
+ result['fs_map'] = self.fs_map()
+
+ if self._has_permissions(Permission.READ, Scope.OSD):
+ result['osd_map'] = self.osd_map()
+ result['scrub_status'] = self.scrub_status()
+ result['pg_info'] = self.pg_info()
+
+ if self._has_permissions(Permission.READ, Scope.MANAGER):
+ result['mgr_map'] = self.mgr_map()
+
+ if self._has_permissions(Permission.READ, Scope.POOL):
+ result['pools'] = self.pools()
+ result['df'] = self.df()
+ result['client_perf'] = self.client_perf()
+
+ if self._has_permissions(Permission.READ, Scope.HOSTS):
+ result['hosts'] = self.host_count()
+
+ if self._has_permissions(Permission.READ, Scope.RGW):
+ result['rgw'] = self.rgw_count()
+
+ if self._has_permissions(Permission.READ, Scope.ISCSI):
+ result['iscsi_daemons'] = self.iscsi_daemons()
+
+ return result
+
+ def basic_health(self):
+ health_data = mgr.get("health")
+ health = json.loads(health_data['json'])
+
+ # Transform the `checks` dict into a list for the convenience
+ # of rendering from javascript.
+ checks = []
+ for k, v in health['checks'].items():
+ v['type'] = k
+ checks.append(v)
+
+ checks = sorted(checks, key=lambda c: c['severity'])
+ health['checks'] = checks
+ return health
+
+ def client_perf(self):
+ result = CephService.get_client_perf()
+ if self._minimal:
+ result = partial_dict(
+ result,
+ ['read_bytes_sec', 'read_op_per_sec',
+ 'recovering_bytes_per_sec', 'write_bytes_sec',
+ 'write_op_per_sec']
+ )
+ return result
+
+ def df(self):
+ df = mgr.get('df')
+
+ del df['stats_by_class']
+
+ if self._minimal:
+ df = dict(stats=partial_dict(
+ df['stats'],
+ ['total_avail_bytes', 'total_bytes',
+ 'total_used_raw_bytes']
+ ))
+ return df
+
+ def fs_map(self):
+ fs_map = mgr.get('fs_map')
+ if self._minimal:
+ fs_map = partial_dict(fs_map, ['filesystems', 'standbys'])
+ fs_map['filesystems'] = [partial_dict(item, ['mdsmap']) for
+ item in fs_map['filesystems']]
+ for fs in fs_map['filesystems']:
+ mdsmap_info = fs['mdsmap']['info']
+ min_mdsmap_info = dict()
+ for k, v in mdsmap_info.items():
+ min_mdsmap_info[k] = partial_dict(v, ['state'])
+ return fs_map
+
+ def host_count(self):
+ return len(get_hosts())
+
+ def iscsi_daemons(self):
+ up_counter = 0
+ down_counter = 0
+ for gateway_name in IscsiGatewaysConfig.get_gateways_config()['gateways']:
+ try:
+ IscsiClient.instance(gateway_name=gateway_name).ping()
+ up_counter += 1
+ except RequestException:
+ down_counter += 1
+ return {'up': up_counter, 'down': down_counter}
+
+ def mgr_map(self):
+ mgr_map = mgr.get('mgr_map')
+ if self._minimal:
+ mgr_map = partial_dict(mgr_map, ['active_name', 'standbys'])
+ return mgr_map
+
+ def mon_status(self):
+ mon_status = json.loads(mgr.get('mon_status')['json'])
+ if self._minimal:
+ mon_status = partial_dict(mon_status, ['monmap', 'quorum'])
+ mon_status['monmap'] = partial_dict(
+ mon_status['monmap'], ['mons']
+ )
+ mon_status['monmap']['mons'] = [{}] * \
+ len(mon_status['monmap']['mons'])
+ return mon_status
+
+ def osd_map(self):
+ osd_map = mgr.get('osd_map')
+ assert osd_map is not None
+ # Not needed, skip the effort of transmitting this to UI
+ del osd_map['pg_temp']
+ if self._minimal:
+ osd_map = partial_dict(osd_map, ['osds'])
+ osd_map['osds'] = [
+ partial_dict(item, ['in', 'up', 'state'])
+ for item in osd_map['osds']
+ ]
+ else:
+ osd_map['tree'] = mgr.get('osd_map_tree')
+ osd_map['crush'] = mgr.get('osd_map_crush')
+ osd_map['crush_map_text'] = mgr.get('osd_map_crush_map_text')
+ osd_map['osd_metadata'] = mgr.get('osd_metadata')
+ return osd_map
+
+ def pg_info(self):
+ return CephService.get_pg_info()
+
+ def pools(self):
+ pools = CephService.get_pool_list_with_stats()
+ if self._minimal:
+ pools = [{}] * len(pools)
+ return pools
+
+ def rgw_count(self):
+ return len(CephService.get_service_list('rgw'))
+
+ def scrub_status(self):
+ return CephService.get_scrub_status()
+
+
+@APIRouter('/health')
+@APIDoc("Display Detailed Cluster health Status", "Health")
+class Health(BaseController):
+ def __init__(self):
+ super().__init__()
+ self.health_full = HealthData(self._has_permissions, minimal=False)
+ self.health_minimal = HealthData(self._has_permissions, minimal=True)
+
+ @Endpoint()
+ def full(self):
+ return self.health_full.all_health()
+
+ @Endpoint()
+ @EndpointDoc("Get Cluster's minimal health report",
+ responses={200: HEALTH_MINIMAL_SCHEMA})
+ def minimal(self):
+ return self.health_minimal.all_health()