--- /dev/null
+
+import json
+import re
+import rados
+import rbd
+from remote_view_cache import RemoteViewCache
+
+class DaemonsAndPools(RemoteViewCache):
+ def _get(self):
+ daemons = self.get_daemons()
+ return {
+ 'daemons': daemons,
+ 'pools': self.get_pools(daemons)
+ }
+
+ def get_daemons(self):
+ daemons = []
+ for server in self._module.list_servers():
+ for service in server['services']:
+ if service['type'] == 'rbd-mirror':
+ metadata = self._module.get_metadata('rbd-mirror',
+ service['id'])
+ status = self._module.get_daemon_status('rbd-mirror',
+ service['id'])
+ try:
+ status = json.loads(status['json'])
+ except:
+ status = {}
+
+ # extract per-daemon service data and health
+ daemon = {
+ 'id': service['id'],
+ 'instance_id': metadata['instance_id'],
+ 'version': metadata['ceph_version'],
+ 'server_hostname': server['hostname'],
+ 'service': service,
+ 'server': server,
+ 'metadata': metadata,
+ 'status': status
+ }
+ daemon = dict(daemon, **self.get_daemon_health(daemon))
+ daemons.append(daemon)
+
+ return sorted(daemons, key=lambda k: k['id'])
+
+ def get_daemon_health(self, daemon):
+ health = {
+ 'health_color': 'info',
+ 'health' : 'Unknown'
+ }
+ for pool_id, pool_data in daemon['status'].items():
+ if (health['health'] != 'error' and
+ [k for k,v in pool_data.get('callouts', {}).items() if v['level'] == 'error']):
+ health = {
+ 'health_color': 'error',
+ 'health': 'Error'
+ }
+ elif (health['health'] != 'error' and
+ [k for k,v in pool_data.get('callouts', {}).items() if v['level'] == 'warning']):
+ health = {
+ 'health_color': 'warning',
+ 'health': 'Warning'
+ }
+ elif health['health_color'] == 'info':
+ health = {
+ 'health_color': 'success',
+ 'health': 'OK'
+ }
+ return health
+
+ def get_pools(self, daemons):
+ status, pool_names = self._module.rbd_pool_ls.get()
+ if pool_names is None:
+ self.log.warning("Failed to get RBD pool list")
+ return {}
+
+ pool_stats = {}
+ rbdctx = rbd.RBD()
+ for pool_name in pool_names:
+ self.log.debug("Constructing IOCtx " + pool_name)
+ try:
+ ioctx = self._module.rados.open_ioctx(pool_name)
+ except:
+ self.log.exception("Failed to open pool " + pool_name)
+ continue
+
+ try:
+ mirror_mode = rbdctx.mirror_mode_get(ioctx)
+ except:
+ self.log.exception("Failed to query mirror mode " + pool_name)
+
+ stats = {}
+ if mirror_mode == rbd.RBD_MIRROR_MODE_DISABLED:
+ continue
+ elif mirror_mode == rbd.RBD_MIRROR_MODE_IMAGE:
+ mirror_mode = "image"
+ elif mirror_mode == rbd.RBD_MIRROR_MODE_POOL:
+ mirror_mode = "pool"
+ else:
+ mirror_mode = "unknown"
+ stats['health_color'] = "warning"
+ stats['health'] = "Warning"
+
+ pool_stats[pool_name] = dict(stats, **{
+ 'mirror_mode': mirror_mode
+ })
+
+ for daemon in daemons:
+ for pool_id, pool_data in daemon['status'].items():
+ stats = pool_stats.get(pool_data['name'], None)
+ if stats is None:
+ continue
+
+ if pool_data.get('leader', False):
+ # leader instance stores image counts
+ stats['leader_id'] = daemon['metadata']['instance_id']
+ stats['image_local_count'] = pool_data.get('image_local_count', 0)
+ stats['image_remote_count'] = pool_data.get('image_remote_count', 0)
+
+ if (stats.get('health_color', '') != 'error' and
+ pool_data.get('image_error_count', 0) > 0):
+ stats['health_color'] = 'error'
+ stats['health'] = 'Error'
+ elif (stats.get('health_color', '') != 'error' and
+ pool_data.get('image_warning_count', 0) > 0):
+ stats['health_color'] = 'warning'
+ stats['health'] = 'Warning'
+ elif stats.get('health', None) is None:
+ stats['health_color'] = 'success'
+ stats['health'] = 'OK'
+
+ for name, stats in pool_stats.items():
+ if stats.get('health', None) is None:
+ # daemon doesn't know about pool
+ stats['health_color'] = 'error'
+ stats['health'] = 'Error'
+ elif stats.get('leader_id', None) is None:
+ # no daemons are managing the pool as leader instance
+ stats['health_color'] = 'warning'
+ stats['health'] = 'Warning'
+ return pool_stats
+
+
+class PoolDatum(RemoteViewCache):
+ def __init__(self, module_inst, pool_name):
+ super(PoolDatum, self).__init__(module_inst)
+ self.pool_name = pool_name
+
+ def _get(self):
+ data = {}
+ self.log.debug("Constructing IOCtx " + self.pool_name)
+ try:
+ ioctx = self._module.rados.open_ioctx(self.pool_name)
+ except:
+ self.log.exception("Failed to open pool " + pool_name)
+ return None
+
+ mirror_state = {
+ 'down': {
+ 'health': 'issue',
+ 'state_color': 'warning',
+ 'state': 'Unknown',
+ 'description': None
+ },
+ rbd.MIRROR_IMAGE_STATUS_STATE_UNKNOWN: {
+ 'health': 'issue',
+ 'state_color': 'warning',
+ 'state': 'Unknown'
+ },
+ rbd.MIRROR_IMAGE_STATUS_STATE_ERROR: {
+ 'health': 'issue',
+ 'state_color': 'error',
+ 'state': 'Error'
+ },
+ rbd.MIRROR_IMAGE_STATUS_STATE_SYNCING: {
+ 'health': 'syncing'
+ },
+ rbd.MIRROR_IMAGE_STATUS_STATE_STARTING_REPLAY: {
+ 'health': 'ok',
+ 'state_color': 'success',
+ 'state': 'Starting'
+ },
+ rbd.MIRROR_IMAGE_STATUS_STATE_REPLAYING: {
+ 'health': 'ok',
+ 'state_color': 'success',
+ 'state': 'Replaying'
+ },
+ rbd.MIRROR_IMAGE_STATUS_STATE_STOPPING_REPLAY: {
+ 'health': 'ok',
+ 'state_color': 'success',
+ 'state': 'Stopping'
+ },
+ rbd.MIRROR_IMAGE_STATUS_STATE_STOPPED: {
+ 'health': 'ok',
+ 'state_color': 'info',
+ 'state': 'Primary'
+ }
+ }
+
+ rbdctx = rbd.RBD()
+ try:
+ mirror_image_status = rbdctx.mirror_image_status_list(ioctx)
+ data['mirror_images'] = sorted([
+ dict({
+ 'name': image['name'],
+ 'description': image['description']
+ }, **mirror_state['down' if not image['up'] else image['state']])
+ for image in mirror_image_status
+ ], key=lambda k: k['name'])
+ except rbd.ImageNotFound:
+ pass
+ except:
+ self.log.exception("Failed to list mirror image status " + self.pool_name)
+
+ return data
+
+class Toplevel(RemoteViewCache):
+ def __init__(self, module_inst, daemons_and_pools):
+ super(Toplevel, self).__init__(module_inst)
+ self.daemons_and_pools = daemons_and_pools
+
+ def _get(self):
+ status, data = self.daemons_and_pools.get()
+ if data is None:
+ self.log.warning("Failed to get rbd-mirror daemons and pools")
+ daemons = {}
+ daemons = data.get('daemons', [])
+ pools = data.get('pools', {})
+
+ warnings = 0
+ errors = 0
+ for daemon in daemons:
+ if daemon['health_color'] == 'error':
+ errors += 1
+ elif daemon['health_color'] == 'warning':
+ warnings += 1
+ for pool_name, pool in pools.items():
+ if pool['health_color'] == 'error':
+ errors += 1
+ elif pool['health_color'] == 'warning':
+ warnings += 1
+ return {'warnings': warnings, 'errors': errors}
+
+
+class ContentData(RemoteViewCache):
+ def __init__(self, module_inst, daemons_and_pools, pool_data):
+ super(ContentData, self).__init__(module_inst)
+
+ self.daemons_and_pools = daemons_and_pools
+ self.pool_data = pool_data
+
+ def _get(self):
+ status, pool_names = self._module.rbd_pool_ls.get()
+ if pool_names is None:
+ self.log.warning("Failed to get RBD pool list")
+ return None
+
+ status, data = self.daemons_and_pools.get()
+ if data is None:
+ self.log.warning("Failed to get rbd-mirror daemons list")
+ data = {}
+ daemons = data.get('daemons', [])
+ pool_stats = data.get('pools', {})
+
+ pools = []
+ image_error = []
+ image_syncing = []
+ image_ready = []
+ for pool_name in pool_names:
+ pool = self.get_pool_datum(pool_name) or {}
+ stats = pool_stats.get(pool_name, {})
+ if stats.get('mirror_mode', None) is None:
+ continue
+
+ mirror_images = pool.get('mirror_images', [])
+ for mirror_image in mirror_images:
+ image = {
+ 'pool_name': pool_name,
+ 'name': mirror_image['name']
+ }
+
+ if mirror_image['health'] == 'ok':
+ image.update({
+ 'state_color': mirror_image['state_color'],
+ 'state': mirror_image['state'],
+ 'description': mirror_image['description']
+ })
+ image_ready.append(image)
+ elif mirror_image['health'] == 'syncing':
+ p = re.compile("bootstrapping, IMAGE_COPY/COPY_OBJECT (.*)%")
+ image.update({
+ 'progress': (p.findall(mirror_image['description']) or [0])[0]
+ })
+ image_syncing.append(image)
+ else:
+ image.update({
+ 'state_color': mirror_image['state_color'],
+ 'state': mirror_image['state'],
+ 'description': mirror_image['description']
+ })
+ image_error.append(image)
+
+ pools.append(dict({
+ 'name': pool_name
+ }, **stats))
+
+ return {
+ 'daemons': daemons,
+ 'pools' : pools,
+ 'image_error': image_error,
+ 'image_syncing': image_syncing,
+ 'image_ready': image_ready
+ }
+
+ def get_pool_datum(self, pool_name):
+ pool_datum = self.pool_data.get(pool_name, None)
+ if pool_datum is None:
+ pool_datum = PoolDatum(self._module, pool_name)
+ self.pool_data[pool_name] = pool_datum
+
+ status, value = pool_datum.get()
+ return value
+
+class Controller:
+ def __init__(self, module_inst):
+ self.daemons_and_pools = DaemonsAndPools(module_inst)
+ self.pool_data = {}
+ self.toplevel = Toplevel(module_inst, self.daemons_and_pools)
+ self.content_data = ContentData(module_inst, self.daemons_and_pools,
+ self.pool_data)
+