]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/controllers/health.py
import ceph 16.2.7
[ceph.git] / ceph / src / pybind / mgr / dashboard / controllers / health.py
CommitLineData
11fdf7f2
TL
1# -*- coding: utf-8 -*-
2from __future__ import absolute_import
3
4import json
5
11fdf7f2 6from .. import mgr
9f95a23c 7from ..rest_client import RequestException
11fdf7f2
TL
8from ..security import Permission, Scope
9from ..services.ceph_service import CephService
10from ..services.iscsi_cli import IscsiGatewaysConfig
9f95a23c
TL
11from ..services.iscsi_client import IscsiClient
12from ..tools import partial_dict
a4b75251 13from . import APIDoc, APIRouter, BaseController, Endpoint, EndpointDoc
9f95a23c 14from .host import get_hosts
11fdf7f2 15
f67539c2
TL
16HEALTH_MINIMAL_SCHEMA = ({
17 'client_perf': ({
18 'read_bytes_sec': (int, ''),
19 'read_op_per_sec': (int, ''),
20 'recovering_bytes_per_sec': (int, ''),
21 'write_bytes_sec': (int, ''),
22 'write_op_per_sec': (int, ''),
23 }, ''),
24 'df': ({
25 'stats': ({
26 'total_avail_bytes': (int, ''),
27 'total_bytes': (int, ''),
28 'total_used_raw_bytes': (int, ''),
29 }, '')
30 }, ''),
31 'fs_map': ({
32 'filesystems': ([{
33 'mdsmap': ({
34 'session_autoclose': (int, ''),
35 'balancer': (str, ''),
36 'up': (str, ''),
37 'last_failure_osd_epoch': (int, ''),
38 'in': ([int], ''),
39 'last_failure': (int, ''),
40 'max_file_size': (int, ''),
41 'explicitly_allowed_features': (int, ''),
42 'damaged': ([int], ''),
43 'tableserver': (int, ''),
44 'failed': ([int], ''),
45 'metadata_pool': (int, ''),
46 'epoch': (int, ''),
47 'stopped': ([int], ''),
48 'max_mds': (int, ''),
49 'compat': ({
50 'compat': (str, ''),
51 'ro_compat': (str, ''),
52 'incompat': (str, ''),
53 }, ''),
54 'required_client_features': (str, ''),
55 'data_pools': ([int], ''),
56 'info': (str, ''),
57 'fs_name': (str, ''),
58 'created': (str, ''),
59 'standby_count_wanted': (int, ''),
60 'enabled': (bool, ''),
61 'modified': (str, ''),
62 'session_timeout': (int, ''),
63 'flags': (int, ''),
64 'ever_allowed_features': (int, ''),
65 'root': (int, ''),
66 }, ''),
67 'standbys': (str, ''),
68 }], ''),
69 }, ''),
70 'health': ({
71 'checks': (str, ''),
72 'mutes': (str, ''),
73 'status': (str, ''),
74 }, ''),
75 'hosts': (int, ''),
76 'iscsi_daemons': ({
77 'up': (int, ''),
78 'down': (int, '')
79 }, ''),
80 'mgr_map': ({
81 'active_name': (str, ''),
82 'standbys': (str, '')
83 }, ''),
84 'mon_status': ({
85 'monmap': ({
86 'mons': (str, ''),
87 }, ''),
88 'quorum': ([int], '')
89 }, ''),
90 'osd_map': ({
91 'osds': ([{
92 'in': (int, ''),
93 'up': (int, ''),
94 }], '')
95 }, ''),
96 'pg_info': ({
97 'object_stats': ({
98 'num_objects': (int, ''),
99 'num_object_copies': (int, ''),
100 'num_objects_degraded': (int, ''),
101 'num_objects_misplaced': (int, ''),
102 'num_objects_unfound': (int, ''),
103 }, ''),
104 'pgs_per_osd': (int, ''),
105 'statuses': (str, '')
106 }, ''),
107 'pools': (str, ''),
108 'rgw': (int, ''),
109 'scrub_status': (str, '')
110})
111
11fdf7f2
TL
112
113class HealthData(object):
114 """
115 A class to be used in combination with BaseController to allow either
116 "full" or "minimal" sets of health data to be collected.
117
118 To function properly, it needs BaseCollector._has_permissions to be passed
119 in as ``auth_callback``.
120 """
121
122 def __init__(self, auth_callback, minimal=True):
123 self._has_permissions = auth_callback
124 self._minimal = minimal
125
11fdf7f2
TL
126 def all_health(self):
127 result = {
128 "health": self.basic_health(),
129 }
130
131 if self._has_permissions(Permission.READ, Scope.MONITOR):
132 result['mon_status'] = self.mon_status()
133
134 if self._has_permissions(Permission.READ, Scope.CEPHFS):
135 result['fs_map'] = self.fs_map()
136
137 if self._has_permissions(Permission.READ, Scope.OSD):
138 result['osd_map'] = self.osd_map()
139 result['scrub_status'] = self.scrub_status()
140 result['pg_info'] = self.pg_info()
141
142 if self._has_permissions(Permission.READ, Scope.MANAGER):
143 result['mgr_map'] = self.mgr_map()
144
145 if self._has_permissions(Permission.READ, Scope.POOL):
146 result['pools'] = self.pools()
147 result['df'] = self.df()
148 result['client_perf'] = self.client_perf()
149
150 if self._has_permissions(Permission.READ, Scope.HOSTS):
151 result['hosts'] = self.host_count()
152
153 if self._has_permissions(Permission.READ, Scope.RGW):
154 result['rgw'] = self.rgw_count()
155
156 if self._has_permissions(Permission.READ, Scope.ISCSI):
157 result['iscsi_daemons'] = self.iscsi_daemons()
158
159 return result
160
161 def basic_health(self):
162 health_data = mgr.get("health")
163 health = json.loads(health_data['json'])
164
165 # Transform the `checks` dict into a list for the convenience
166 # of rendering from javascript.
167 checks = []
168 for k, v in health['checks'].items():
169 v['type'] = k
170 checks.append(v)
171
172 checks = sorted(checks, key=lambda c: c['severity'])
173 health['checks'] = checks
174 return health
175
176 def client_perf(self):
177 result = CephService.get_client_perf()
178 if self._minimal:
9f95a23c 179 result = partial_dict(
11fdf7f2
TL
180 result,
181 ['read_bytes_sec', 'read_op_per_sec',
182 'recovering_bytes_per_sec', 'write_bytes_sec',
183 'write_op_per_sec']
184 )
185 return result
186
187 def df(self):
188 df = mgr.get('df')
189
190 del df['stats_by_class']
191
11fdf7f2 192 if self._minimal:
9f95a23c 193 df = dict(stats=partial_dict(
11fdf7f2 194 df['stats'],
81eedcae 195 ['total_avail_bytes', 'total_bytes',
11fdf7f2
TL
196 'total_used_raw_bytes']
197 ))
198 return df
199
200 def fs_map(self):
201 fs_map = mgr.get('fs_map')
202 if self._minimal:
9f95a23c 203 fs_map = partial_dict(fs_map, ['filesystems', 'standbys'])
9f95a23c 204 fs_map['filesystems'] = [partial_dict(item, ['mdsmap']) for
11fdf7f2
TL
205 item in fs_map['filesystems']]
206 for fs in fs_map['filesystems']:
207 mdsmap_info = fs['mdsmap']['info']
208 min_mdsmap_info = dict()
209 for k, v in mdsmap_info.items():
9f95a23c 210 min_mdsmap_info[k] = partial_dict(v, ['state'])
11fdf7f2
TL
211 return fs_map
212
213 def host_count(self):
9f95a23c 214 return len(get_hosts())
11fdf7f2
TL
215
216 def iscsi_daemons(self):
9f95a23c
TL
217 up_counter = 0
218 down_counter = 0
219 for gateway_name in IscsiGatewaysConfig.get_gateways_config()['gateways']:
220 try:
221 IscsiClient.instance(gateway_name=gateway_name).ping()
222 up_counter += 1
223 except RequestException:
224 down_counter += 1
225 return {'up': up_counter, 'down': down_counter}
11fdf7f2
TL
226
227 def mgr_map(self):
228 mgr_map = mgr.get('mgr_map')
229 if self._minimal:
9f95a23c 230 mgr_map = partial_dict(mgr_map, ['active_name', 'standbys'])
11fdf7f2
TL
231 return mgr_map
232
233 def mon_status(self):
234 mon_status = json.loads(mgr.get('mon_status')['json'])
235 if self._minimal:
9f95a23c
TL
236 mon_status = partial_dict(mon_status, ['monmap', 'quorum'])
237 mon_status['monmap'] = partial_dict(
11fdf7f2
TL
238 mon_status['monmap'], ['mons']
239 )
240 mon_status['monmap']['mons'] = [{}] * \
241 len(mon_status['monmap']['mons'])
242 return mon_status
243
244 def osd_map(self):
245 osd_map = mgr.get('osd_map')
246 assert osd_map is not None
247 # Not needed, skip the effort of transmitting this to UI
248 del osd_map['pg_temp']
249 if self._minimal:
9f95a23c 250 osd_map = partial_dict(osd_map, ['osds'])
11fdf7f2 251 osd_map['osds'] = [
9f95a23c 252 partial_dict(item, ['in', 'up'])
11fdf7f2
TL
253 for item in osd_map['osds']
254 ]
255 else:
256 osd_map['tree'] = mgr.get('osd_map_tree')
257 osd_map['crush'] = mgr.get('osd_map_crush')
258 osd_map['crush_map_text'] = mgr.get('osd_map_crush_map_text')
259 osd_map['osd_metadata'] = mgr.get('osd_metadata')
260 return osd_map
261
262 def pg_info(self):
81eedcae 263 return CephService.get_pg_info()
11fdf7f2
TL
264
265 def pools(self):
266 pools = CephService.get_pool_list_with_stats()
267 if self._minimal:
268 pools = [{}] * len(pools)
269 return pools
270
271 def rgw_count(self):
272 return len(CephService.get_service_list('rgw'))
273
274 def scrub_status(self):
275 return CephService.get_scrub_status()
276
277
a4b75251
TL
278@APIRouter('/health')
279@APIDoc("Display Detailed Cluster health Status", "Health")
11fdf7f2
TL
280class Health(BaseController):
281 def __init__(self):
a4b75251 282 super().__init__()
11fdf7f2
TL
283 self.health_full = HealthData(self._has_permissions, minimal=False)
284 self.health_minimal = HealthData(self._has_permissions, minimal=True)
285
286 @Endpoint()
287 def full(self):
288 return self.health_full.all_health()
289
290 @Endpoint()
f67539c2
TL
291 @EndpointDoc("Get Cluster's minimal health report",
292 responses={200: HEALTH_MINIMAL_SCHEMA})
11fdf7f2
TL
293 def minimal(self):
294 return self.health_minimal.all_health()