]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/rbd_mirroring.py
bump version to 12.2.12-pve1
[ceph.git] / ceph / src / pybind / mgr / dashboard / rbd_mirroring.py
CommitLineData
c07f9fc5
FG
1
2import json
3import re
4import rados
5import rbd
6from remote_view_cache import RemoteViewCache
7
8class 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
144class 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
217class 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
245class 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
324class 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