]>
Commit | Line | Data |
---|---|---|
c07f9fc5 FG |
1 | |
2 | import json | |
3 | import re | |
4 | import rados | |
5 | import rbd | |
6 | from remote_view_cache import RemoteViewCache | |
7 | ||
8 | class DaemonsAndPools(RemoteViewCache): | |
9 | def _get(self): | |
10 | daemons = self.get_daemons() | |
11 | return { | |
12 | 'daemons': daemons, | |
13 | 'pools': self.get_pools(daemons) | |
14 | } | |
15 | ||
16 | def get_daemons(self): | |
17 | daemons = [] | |
18 | for server in self._module.list_servers(): | |
19 | for service in server['services']: | |
20 | if service['type'] == 'rbd-mirror': | |
21 | metadata = self._module.get_metadata('rbd-mirror', | |
22 | service['id']) | |
23 | status = self._module.get_daemon_status('rbd-mirror', | |
24 | service['id']) | |
25 | try: | |
26 | status = json.loads(status['json']) | |
27 | except: | |
28 | status = {} | |
29 | ||
30 | # extract per-daemon service data and health | |
31 | daemon = { | |
32 | 'id': service['id'], | |
33 | 'instance_id': metadata['instance_id'], | |
34 | 'version': metadata['ceph_version'], | |
35 | 'server_hostname': server['hostname'], | |
36 | 'service': service, | |
37 | 'server': server, | |
38 | 'metadata': metadata, | |
39 | 'status': status | |
40 | } | |
41 | daemon = dict(daemon, **self.get_daemon_health(daemon)) | |
42 | daemons.append(daemon) | |
43 | ||
44 | return sorted(daemons, key=lambda k: k['id']) | |
45 | ||
46 | def get_daemon_health(self, daemon): | |
47 | health = { | |
48 | 'health_color': 'info', | |
49 | 'health' : 'Unknown' | |
50 | } | |
51 | for pool_id, pool_data in daemon['status'].items(): | |
52 | if (health['health'] != 'error' and | |
53 | [k for k,v in pool_data.get('callouts', {}).items() if v['level'] == 'error']): | |
54 | health = { | |
55 | 'health_color': 'error', | |
56 | 'health': 'Error' | |
57 | } | |
58 | elif (health['health'] != 'error' and | |
59 | [k for k,v in pool_data.get('callouts', {}).items() if v['level'] == 'warning']): | |
60 | health = { | |
61 | 'health_color': 'warning', | |
62 | 'health': 'Warning' | |
63 | } | |
64 | elif health['health_color'] == 'info': | |
65 | health = { | |
66 | 'health_color': 'success', | |
67 | 'health': 'OK' | |
68 | } | |
69 | return health | |
70 | ||
71 | def get_pools(self, daemons): | |
72 | status, pool_names = self._module.rbd_pool_ls.get() | |
73 | if pool_names is None: | |
74 | self.log.warning("Failed to get RBD pool list") | |
75 | return {} | |
76 | ||
77 | pool_stats = {} | |
78 | rbdctx = rbd.RBD() | |
79 | for pool_name in pool_names: | |
80 | self.log.debug("Constructing IOCtx " + pool_name) | |
81 | try: | |
82 | ioctx = self._module.rados.open_ioctx(pool_name) | |
83 | except: | |
84 | self.log.exception("Failed to open pool " + pool_name) | |
85 | continue | |
86 | ||
87 | try: | |
88 | mirror_mode = rbdctx.mirror_mode_get(ioctx) | |
89 | except: | |
90 | self.log.exception("Failed to query mirror mode " + pool_name) | |
91 | ||
92 | stats = {} | |
93 | if mirror_mode == rbd.RBD_MIRROR_MODE_DISABLED: | |
94 | continue | |
95 | elif mirror_mode == rbd.RBD_MIRROR_MODE_IMAGE: | |
96 | mirror_mode = "image" | |
97 | elif mirror_mode == rbd.RBD_MIRROR_MODE_POOL: | |
98 | mirror_mode = "pool" | |
99 | else: | |
100 | mirror_mode = "unknown" | |
101 | stats['health_color'] = "warning" | |
102 | stats['health'] = "Warning" | |
103 | ||
104 | pool_stats[pool_name] = dict(stats, **{ | |
105 | 'mirror_mode': mirror_mode | |
106 | }) | |
107 | ||
108 | for daemon in daemons: | |
109 | for pool_id, pool_data in daemon['status'].items(): | |
110 | stats = pool_stats.get(pool_data['name'], None) | |
111 | if stats is None: | |
112 | continue | |
113 | ||
114 | if pool_data.get('leader', False): | |
115 | # leader instance stores image counts | |
116 | stats['leader_id'] = daemon['metadata']['instance_id'] | |
117 | stats['image_local_count'] = pool_data.get('image_local_count', 0) | |
118 | stats['image_remote_count'] = pool_data.get('image_remote_count', 0) | |
119 | ||
120 | if (stats.get('health_color', '') != 'error' and | |
121 | pool_data.get('image_error_count', 0) > 0): | |
122 | stats['health_color'] = 'error' | |
123 | stats['health'] = 'Error' | |
124 | elif (stats.get('health_color', '') != 'error' and | |
125 | pool_data.get('image_warning_count', 0) > 0): | |
126 | stats['health_color'] = 'warning' | |
127 | stats['health'] = 'Warning' | |
128 | elif stats.get('health', None) is None: | |
129 | stats['health_color'] = 'success' | |
130 | stats['health'] = 'OK' | |
131 | ||
132 | for name, stats in pool_stats.items(): | |
133 | if stats.get('health', None) is None: | |
134 | # daemon doesn't know about pool | |
135 | stats['health_color'] = 'error' | |
136 | stats['health'] = 'Error' | |
137 | elif stats.get('leader_id', None) is None: | |
138 | # no daemons are managing the pool as leader instance | |
139 | stats['health_color'] = 'warning' | |
140 | stats['health'] = 'Warning' | |
141 | return pool_stats | |
142 | ||
143 | ||
144 | class PoolDatum(RemoteViewCache): | |
145 | def __init__(self, module_inst, pool_name): | |
146 | super(PoolDatum, self).__init__(module_inst) | |
147 | self.pool_name = pool_name | |
148 | ||
149 | def _get(self): | |
150 | data = {} | |
151 | self.log.debug("Constructing IOCtx " + self.pool_name) | |
152 | try: | |
153 | ioctx = self._module.rados.open_ioctx(self.pool_name) | |
154 | except: | |
155 | self.log.exception("Failed to open pool " + pool_name) | |
156 | return None | |
157 | ||
158 | mirror_state = { | |
159 | 'down': { | |
160 | 'health': 'issue', | |
161 | 'state_color': 'warning', | |
162 | 'state': 'Unknown', | |
163 | 'description': None | |
164 | }, | |
165 | rbd.MIRROR_IMAGE_STATUS_STATE_UNKNOWN: { | |
166 | 'health': 'issue', | |
167 | 'state_color': 'warning', | |
168 | 'state': 'Unknown' | |
169 | }, | |
170 | rbd.MIRROR_IMAGE_STATUS_STATE_ERROR: { | |
171 | 'health': 'issue', | |
172 | 'state_color': 'error', | |
173 | 'state': 'Error' | |
174 | }, | |
175 | rbd.MIRROR_IMAGE_STATUS_STATE_SYNCING: { | |
176 | 'health': 'syncing' | |
177 | }, | |
178 | rbd.MIRROR_IMAGE_STATUS_STATE_STARTING_REPLAY: { | |
179 | 'health': 'ok', | |
180 | 'state_color': 'success', | |
181 | 'state': 'Starting' | |
182 | }, | |
183 | rbd.MIRROR_IMAGE_STATUS_STATE_REPLAYING: { | |
184 | 'health': 'ok', | |
185 | 'state_color': 'success', | |
186 | 'state': 'Replaying' | |
187 | }, | |
188 | rbd.MIRROR_IMAGE_STATUS_STATE_STOPPING_REPLAY: { | |
189 | 'health': 'ok', | |
190 | 'state_color': 'success', | |
191 | 'state': 'Stopping' | |
192 | }, | |
193 | rbd.MIRROR_IMAGE_STATUS_STATE_STOPPED: { | |
194 | 'health': 'ok', | |
195 | 'state_color': 'info', | |
196 | 'state': 'Primary' | |
197 | } | |
198 | } | |
199 | ||
200 | rbdctx = rbd.RBD() | |
201 | try: | |
202 | mirror_image_status = rbdctx.mirror_image_status_list(ioctx) | |
203 | data['mirror_images'] = sorted([ | |
204 | dict({ | |
205 | 'name': image['name'], | |
206 | 'description': image['description'] | |
207 | }, **mirror_state['down' if not image['up'] else image['state']]) | |
208 | for image in mirror_image_status | |
209 | ], key=lambda k: k['name']) | |
210 | except rbd.ImageNotFound: | |
211 | pass | |
212 | except: | |
213 | self.log.exception("Failed to list mirror image status " + self.pool_name) | |
214 | ||
215 | return data | |
216 | ||
217 | class Toplevel(RemoteViewCache): | |
218 | def __init__(self, module_inst, daemons_and_pools): | |
219 | super(Toplevel, self).__init__(module_inst) | |
220 | self.daemons_and_pools = daemons_and_pools | |
221 | ||
222 | def _get(self): | |
223 | status, data = self.daemons_and_pools.get() | |
224 | if data is None: | |
225 | self.log.warning("Failed to get rbd-mirror daemons and pools") | |
226 | daemons = {} | |
227 | daemons = data.get('daemons', []) | |
228 | pools = data.get('pools', {}) | |
229 | ||
230 | warnings = 0 | |
231 | errors = 0 | |
232 | for daemon in daemons: | |
233 | if daemon['health_color'] == 'error': | |
234 | errors += 1 | |
235 | elif daemon['health_color'] == 'warning': | |
236 | warnings += 1 | |
237 | for pool_name, pool in pools.items(): | |
238 | if pool['health_color'] == 'error': | |
239 | errors += 1 | |
240 | elif pool['health_color'] == 'warning': | |
241 | warnings += 1 | |
242 | return {'warnings': warnings, 'errors': errors} | |
243 | ||
244 | ||
245 | class ContentData(RemoteViewCache): | |
246 | def __init__(self, module_inst, daemons_and_pools, pool_data): | |
247 | super(ContentData, self).__init__(module_inst) | |
248 | ||
249 | self.daemons_and_pools = daemons_and_pools | |
250 | self.pool_data = pool_data | |
251 | ||
252 | def _get(self): | |
253 | status, pool_names = self._module.rbd_pool_ls.get() | |
254 | if pool_names is None: | |
255 | self.log.warning("Failed to get RBD pool list") | |
256 | return None | |
257 | ||
258 | status, data = self.daemons_and_pools.get() | |
259 | if data is None: | |
260 | self.log.warning("Failed to get rbd-mirror daemons list") | |
261 | data = {} | |
262 | daemons = data.get('daemons', []) | |
263 | pool_stats = data.get('pools', {}) | |
264 | ||
265 | pools = [] | |
266 | image_error = [] | |
267 | image_syncing = [] | |
268 | image_ready = [] | |
269 | for pool_name in pool_names: | |
270 | pool = self.get_pool_datum(pool_name) or {} | |
271 | stats = pool_stats.get(pool_name, {}) | |
272 | if stats.get('mirror_mode', None) is None: | |
273 | continue | |
274 | ||
275 | mirror_images = pool.get('mirror_images', []) | |
276 | for mirror_image in mirror_images: | |
277 | image = { | |
278 | 'pool_name': pool_name, | |
279 | 'name': mirror_image['name'] | |
280 | } | |
281 | ||
282 | if mirror_image['health'] == 'ok': | |
283 | image.update({ | |
284 | 'state_color': mirror_image['state_color'], | |
285 | 'state': mirror_image['state'], | |
286 | 'description': mirror_image['description'] | |
287 | }) | |
288 | image_ready.append(image) | |
289 | elif mirror_image['health'] == 'syncing': | |
290 | p = re.compile("bootstrapping, IMAGE_COPY/COPY_OBJECT (.*)%") | |
291 | image.update({ | |
292 | 'progress': (p.findall(mirror_image['description']) or [0])[0] | |
293 | }) | |
294 | image_syncing.append(image) | |
295 | else: | |
296 | image.update({ | |
297 | 'state_color': mirror_image['state_color'], | |
298 | 'state': mirror_image['state'], | |
299 | 'description': mirror_image['description'] | |
300 | }) | |
301 | image_error.append(image) | |
302 | ||
303 | pools.append(dict({ | |
304 | 'name': pool_name | |
305 | }, **stats)) | |
306 | ||
307 | return { | |
308 | 'daemons': daemons, | |
309 | 'pools' : pools, | |
310 | 'image_error': image_error, | |
311 | 'image_syncing': image_syncing, | |
312 | 'image_ready': image_ready | |
313 | } | |
314 | ||
315 | def get_pool_datum(self, pool_name): | |
316 | pool_datum = self.pool_data.get(pool_name, None) | |
317 | if pool_datum is None: | |
318 | pool_datum = PoolDatum(self._module, pool_name) | |
319 | self.pool_data[pool_name] = pool_datum | |
320 | ||
321 | status, value = pool_datum.get() | |
322 | return value | |
323 | ||
324 | class Controller: | |
325 | def __init__(self, module_inst): | |
326 | self.daemons_and_pools = DaemonsAndPools(module_inst) | |
327 | self.pool_data = {} | |
328 | self.toplevel = Toplevel(module_inst, self.daemons_and_pools) | |
329 | self.content_data = ContentData(module_inst, self.daemons_and_pools, | |
330 | self.pool_data) | |
331 |