]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | # -*- coding: utf-8 -*- |
2 | from __future__ import absolute_import | |
3 | ||
4 | import json | |
5 | ||
11fdf7f2 | 6 | from .. import mgr |
9f95a23c | 7 | from ..rest_client import RequestException |
11fdf7f2 TL |
8 | from ..security import Permission, Scope |
9 | from ..services.ceph_service import CephService | |
10 | from ..services.iscsi_cli import IscsiGatewaysConfig | |
9f95a23c TL |
11 | from ..services.iscsi_client import IscsiClient |
12 | from ..tools import partial_dict | |
a4b75251 | 13 | from . import APIDoc, APIRouter, BaseController, Endpoint, EndpointDoc |
9f95a23c | 14 | from .host import get_hosts |
11fdf7f2 | 15 | |
f67539c2 TL |
16 | HEALTH_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 | |
113 | class 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 |
280 | class 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() |