# -*- 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()