initial code repo
[stor4nfv.git] / src / ceph / src / pybind / mgr / dashboard / rbd_mirroring.py
diff --git a/src/ceph/src/pybind/mgr/dashboard/rbd_mirroring.py b/src/ceph/src/pybind/mgr/dashboard/rbd_mirroring.py
new file mode 100644 (file)
index 0000000..d8a5241
--- /dev/null
@@ -0,0 +1,331 @@
+
+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)
+