]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: Replace dashboard service
authorZack Cerza <zack@redhat.com>
Mon, 29 Oct 2018 22:07:27 +0000 (16:07 -0600)
committerZack Cerza <zack@redhat.com>
Tue, 27 Nov 2018 23:08:53 +0000 (16:08 -0700)
This splits out the collection of health and log data from the
/api/dashboard/health controller into /api/health/{full,minimal} and
/api/logs/all.

/health/full contains all the data (minus logs) that /dashboard/health
did, whereas /health/minimal contains only what is needed for the health
component to function. /logs/all contains exactly what the logs portion
of /dashboard/health did.

By using /health/minimal, on a vstart cluster we pull ~1.4KB of data
every 5s, where we used to pull ~6KB; those numbers would get larger
with larger clusters. Once we split out log data, that will drop to
~0.4KB.

Fixes: http://tracker.ceph.com/issues/36675
Signed-off-by: Zack Cerza <zack@redhat.com>
20 files changed:
qa/suites/rados/mgr/tasks/dashboard.yaml
qa/tasks/mgr/dashboard/test_dashboard.py [deleted file]
qa/tasks/mgr/dashboard/test_health.py [new file with mode: 0644]
qa/tasks/mgr/dashboard/test_logs.py [new file with mode: 0644]
qa/tasks/mgr/dashboard/test_pool.py
src/pybind/mgr/dashboard/controllers/dashboard.py [deleted file]
src/pybind/mgr/dashboard/controllers/health.py [new file with mode: 0644]
src/pybind/mgr/dashboard/controllers/logs.py [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/crushmap/crushmap.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/logs/logs.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/dashboard.service.spec.ts [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/shared/api/dashboard.service.ts [deleted file]
src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/api/logs.service.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/shared/api/logs.service.ts [new file with mode: 0644]

index 95eba5723deffae093f64cb9192874b1806775f2..6bb4a9a03b241e8d693a601cc605bf5ffc23335a 100644 (file)
@@ -30,8 +30,9 @@ tasks:
         - tasks.mgr.dashboard.test_auth
         - tasks.mgr.dashboard.test_cephfs
         - tasks.mgr.dashboard.test_cluster_configuration
-        - tasks.mgr.dashboard.test_dashboard
+        - tasks.mgr.dashboard.test_health
         - tasks.mgr.dashboard.test_host
+        - tasks.mgr.dashboard.test_logs
         - tasks.mgr.dashboard.test_monitor
         - tasks.mgr.dashboard.test_osd
         - tasks.mgr.dashboard.test_perf_counters
diff --git a/qa/tasks/mgr/dashboard/test_dashboard.py b/qa/tasks/mgr/dashboard/test_dashboard.py
deleted file mode 100644 (file)
index 82578aa..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-
-from .helper import DashboardTestCase
-
-
-class DashboardTest(DashboardTestCase):
-    CEPHFS = True
-
-    def test_health(self):
-        data = self._get("/api/dashboard/health")
-        self.assertStatus(200)
-
-        self.assertIn('health', data)
-        self.assertIn('mon_status', data)
-        self.assertIn('fs_map', data)
-        self.assertIn('osd_map', data)
-        self.assertIn('clog', data)
-        self.assertIn('audit_log', data)
-        self.assertIn('pools', data)
-        self.assertIn('mgr_map', data)
-        self.assertIn('df', data)
-        self.assertIn('scrub_status', data)
-        self.assertIn('pg_info', data)
-        self.assertIn('client_perf', data)
-        self.assertIn('hosts', data)
-        self.assertIn('rgw', data)
-        self.assertIn('iscsi_daemons', data)
-        self.assertIsNotNone(data['health'])
-        self.assertIsNotNone(data['mon_status'])
-        self.assertIsNotNone(data['fs_map'])
-        self.assertIsNotNone(data['osd_map'])
-        self.assertIsNotNone(data['clog'])
-        self.assertIsNotNone(data['audit_log'])
-        self.assertIsNotNone(data['pools'])
-        self.assertIsNotNone(data['scrub_status'])
-        self.assertIsNotNone(data['pg_info'])
-        self.assertIsNotNone(data['client_perf'])
-        self.assertIsNotNone(data['hosts'])
-        self.assertIsNotNone(data['rgw'])
-        self.assertIsNotNone(data['iscsi_daemons'])
-
-        cluster_pools = self.ceph_cluster.mon_manager.list_pools()
-        self.assertEqual(len(cluster_pools), len(data['pools']))
-        for pool in data['pools']:
-            self.assertIn(pool['pool_name'], cluster_pools)
-
-        self.assertIsNotNone(data['mgr_map'])
-        self.assertIsNotNone(data['df'])
-
-
-    @DashboardTestCase.RunAs('test', 'test', ['pool-manager'])
-    def test_health_permissions(self):
-        data = self._get("/api/dashboard/health")
-        self.assertStatus(200)
-
-        self.assertIn('health', data)
-        self.assertNotIn('mon_status', data)
-        self.assertNotIn('fs_map', data)
-        self.assertNotIn('osd_map', data)
-        self.assertNotIn('clog', data)
-        self.assertNotIn('audit_log', data)
-        self.assertIn('pools', data)
-        self.assertNotIn('mgr_map', data)
-        self.assertIn('df', data)
-        self.assertNotIn('scrub_status', data)
-        self.assertNotIn('pg_info', data)
-        self.assertIn('client_perf', data)
-        self.assertNotIn('hosts', data)
-        self.assertNotIn('rgw', data)
-        self.assertNotIn('iscsi_daemons', data)
-        self.assertIsNotNone(data['health'])
-        self.assertIsNotNone(data['pools'])
-        self.assertIsNotNone(data['client_perf'])
-
-        cluster_pools = self.ceph_cluster.mon_manager.list_pools()
-        self.assertEqual(len(cluster_pools), len(data['pools']))
-        for pool in data['pools']:
-            self.assertIn(pool['pool_name'], cluster_pools)
-
-        self.assertIsNotNone(data['df'])
diff --git a/qa/tasks/mgr/dashboard/test_health.py b/qa/tasks/mgr/dashboard/test_health.py
new file mode 100644 (file)
index 0000000..421567c
--- /dev/null
@@ -0,0 +1,261 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+from .helper import DashboardTestCase, JAny, JLeaf, JList, JObj
+
+
+class HealthTest(DashboardTestCase):
+    CEPHFS = True
+
+    def test_minimal_health(self):
+        data = self._get('/api/health/minimal')
+        self.assertStatus(200)
+        schema = JObj({
+            'client_perf': JObj({
+                'read_bytes_sec': int,
+                'read_op_per_sec': int,
+                'recovering_bytes_per_sec': int,
+                'write_bytes_sec': int,
+                'write_op_per_sec': int
+            }),
+            'df': JObj({
+                'stats': JObj({
+                    'total_avail_bytes': int,
+                    'total_bytes': int,
+                    'total_objects': int,
+                    'total_used_bytes': int,
+                })
+            }),
+            'fs_map': JObj({
+                'filesystems': JList(
+                    JObj({
+                        'mdsmap': JObj({
+                            'info': JObj(
+                                {},
+                                allow_unknown=True,
+                                unknown_schema=JObj({
+                                    'state': str
+                                })
+                            )
+                        })
+                    }),
+                ),
+                'standbys': JList(JObj({})),
+            }),
+            'health': JObj({
+                'checks': JList(str),
+                'status': str,
+            }),
+            'hosts': int,
+            'iscsi_daemons': int,
+            'mgr_map': JObj({
+                'active_name': str,
+                'standbys': JList(JLeaf(dict))
+            }),
+            'mon_status': JObj({
+                'monmap': JObj({
+                    'mons': JList(JLeaf(dict)),
+                }),
+                'quorum': JList(int)
+            }),
+            'osd_map': JObj({
+                'osds': JList(
+                    JObj({
+                        'in': int,
+                        'up': int,
+                    })),
+            }),
+            'pg_info': JObj({
+                'pgs_per_osd': int,
+                'statuses': JObj({}, allow_unknown=True, unknown_schema=int)
+            }),
+            'pools': JList(JLeaf(dict)),
+            'rgw': int,
+            'scrub_status': str
+        })
+        self.assertSchema(data, schema)
+
+    def test_full_health(self):
+        data = self._get('/api/health/full')
+        self.assertStatus(200)
+        schema = JObj({
+            'client_perf': JObj({
+                'read_bytes_sec': int,
+                'read_op_per_sec': int,
+                'recovering_bytes_per_sec': int,
+                'write_bytes_sec': int,
+                'write_op_per_sec': int
+            }),
+            'df': JObj({
+                'pools': JList(JObj({
+                    'stats': JObj({
+                        'wr': int,
+                        'quota_objects': int,
+                        'bytes_used': int,
+                        'max_avail': int,
+                        'rd': int,
+                        'rd_bytes': int,
+                        'objects': int,
+                        'percent_used': float,
+                        'kb_used': int,
+                        'quota_bytes': int,
+                        'raw_bytes_used': int,
+                        'wr_bytes': int,
+                        'dirty': int
+                    }),
+                    'name': str,
+                    'id': int
+                })),
+                'stats': JObj({
+                    'total_avail_bytes': int,
+                    'total_bytes': int,
+                    'total_objects': int,
+                    'total_percent_used': float,
+                    'total_used_bytes': int
+                })
+            }),
+            'fs_map': JObj({
+                'compat': JObj({
+                    'compat': JObj({}, allow_unknown=True, unknown_schema=str),
+                    'incompat': JObj(
+                        {}, allow_unknown=True, unknown_schema=str),
+                    'ro_compat': JObj(
+                        {}, allow_unknown=True, unknown_schema=str)
+                }),
+                'default_fscid': int,
+                'epoch': int,
+                'feature_flags': JObj(
+                    {}, allow_unknown=True, unknown_schema=bool),
+                'filesystems': JList(
+                    JObj({
+                        'id': int,
+                        'mdsmap': JObj({
+                            # TODO: Expand mdsmap schema
+                            'info': JObj(
+                                {},
+                                allow_unknown=True,
+                                unknown_schema=JObj({
+                                    'state': str
+                                }, allow_unknown=True)
+                            )
+                        }, allow_unknown=True)
+                    }),
+                ),
+                'standbys': JList(JObj({}, allow_unknown=True)),
+            }),
+            'health': JObj({
+                'checks': JList(str),
+                'status': str,
+            }),
+            'hosts': int,
+            'iscsi_daemons': int,
+            'mgr_map': JObj({
+                'active_addrs': JObj({
+                    'addrvec': JList(JObj({
+                        'addr': str,
+                        'nonce': int,
+                        'type': str
+                    }))
+                }),
+                'active_change': str,  # timestamp
+                'active_gid': int,
+                'active_name': str,
+                'always_on_modules': JObj(
+                    {},
+                    allow_unknown=True, unknown_schema=JList(str)
+                ),
+                'available': bool,
+                'available_modules': JList(JObj({
+                    'can_run': bool,
+                    'error_string': str,
+                    'name': str
+                })),
+                'epoch': int,
+                'modules': JList(str),
+                'services': JObj(
+                    {'dashboard': str},  # This module should always be present
+                    allow_unknown=True, unknown_schema=str
+                ),
+                'standbys': JList(JObj({
+                    'available_modules': JList(JObj({
+                        'can_run': bool,
+                        'error_string': str,
+                        'name': str
+                    })),
+                    'gid': int,
+                    'name': str
+                }))
+            }),
+            'mon_status': JObj({
+                'election_epoch': int,
+                'extra_probe_peers': JList(JAny(none=True)),
+                'feature_map': JObj(
+                    {}, allow_unknown=True, unknown_schema=JList(JObj({
+                        'features': str,
+                        'num': int,
+                        'release': str
+                    }))
+                ),
+                'features': JObj({
+                    'quorum_con': str,
+                    'quorum_mon': JList(str),
+                    'required_con': str,
+                    'required_mon': JList(str)
+                }),
+                'monmap': JObj({
+                    # TODO: expand on monmap schema
+                    'mons': JList(JLeaf(dict)),
+                }, allow_unknown=True),
+                'name': str,
+                'outside_quorum': JList(int),
+                'quorum': JList(int),
+                'quorum_age': str,
+                'rank': int,
+                'state': str,
+                # TODO: What type should be expected here?
+                'sync_provider': JList(JAny(none=True))
+            }),
+            'osd_map': JObj({
+                # TODO: define schema for crush map and osd_metadata, among
+                # others
+                'osds': JList(
+                    JObj({
+                        'in': int,
+                        'up': int,
+                    }, allow_unknown=True)),
+            }, allow_unknown=True),
+            'pg_info': JObj({
+                'pgs_per_osd': int,
+                'statuses': JObj({}, allow_unknown=True, unknown_schema=int)
+            }),
+            'pools': JList(JLeaf(dict)),
+            'rgw': int,
+            'scrub_status': str
+        })
+        self.assertSchema(data, schema)
+
+        cluster_pools = self.ceph_cluster.mon_manager.list_pools()
+        self.assertEqual(len(cluster_pools), len(data['pools']))
+        for pool in data['pools']:
+            self.assertIn(pool['pool_name'], cluster_pools)
+
+    @DashboardTestCase.RunAs('test', 'test', ['pool-manager'])
+    def test_health_permissions(self):
+        data = self._get('/api/health/full')
+        self.assertStatus(200)
+
+        schema = JObj({
+            'client_perf': JObj({}, allow_unknown=True),
+            'df': JObj({}, allow_unknown=True),
+            'health': JObj({
+                'checks': JList(str),
+                'status': str
+            }),
+            'pools': JList(JLeaf(dict)),
+        })
+        self.assertSchema(data, schema)
+
+        cluster_pools = self.ceph_cluster.mon_manager.list_pools()
+        self.assertEqual(len(cluster_pools), len(data['pools']))
+        for pool in data['pools']:
+            self.assertIn(pool['pool_name'], cluster_pools)
diff --git a/qa/tasks/mgr/dashboard/test_logs.py b/qa/tasks/mgr/dashboard/test_logs.py
new file mode 100644 (file)
index 0000000..17d5d83
--- /dev/null
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+from .helper import DashboardTestCase, JList, JObj
+
+
+class LogsTest(DashboardTestCase):
+    CEPHFS = True
+
+    def test_logs(self):
+        data = self._get("/api/logs/all")
+        self.assertStatus(200)
+        log_entry_schema = JList(JObj({
+            'addrs': JObj({
+                'addrvec': JList(JObj({
+                    'addr': str,
+                    'nonce': int,
+                    'type': str
+                }))
+            }),
+            'channel': str,
+            'message': str,
+            'name': str,
+            'priority': str,
+            'rank': str,
+            'seq': int,
+            'stamp': str
+        }))
+        schema = JObj({
+            'audit_log': log_entry_schema,
+            'clog': log_entry_schema
+        })
+        self.assertSchema(data, schema)
+
+    @DashboardTestCase.RunAs('test', 'test', ['pool-manager'])
+    def test_log_perms(self):
+        self._get("/api/logs/all")
+        self.assertStatus(403)
index 91b63810d9e4a7c714f9b1aa3b088b8543af9320..92276d2280668216ee9ffc62c860d05784e06cb0 100644 (file)
@@ -47,7 +47,7 @@ class PoolTest(DashboardTestCase):
             log.exception("test_pool_create: pool=%s", pool)
             raise
 
-        health = self._get('/api/dashboard/health')['health']
+        health = self._get('/api/health/minimal')['health']
         self.assertEqual(health['status'], 'HEALTH_OK', msg='health={}'.format(health))
 
     def _get_pool(self, pool_name):
@@ -86,7 +86,7 @@ class PoolTest(DashboardTestCase):
         # Feel free to test it locally.
         prop = 'pg_num'
         pgp_prop = 'pg_placement_num'
-        health = lambda: self._get('/api/dashboard/health')['health']['status'] == 'HEALTH_OK'
+        health = lambda: self._get('/api/health/minimal')['health']['status'] == 'HEALTH_OK'
         t = 0;
         while (int(value) != pool[pgp_prop] or not health()) and t < 180:
             time.sleep(2)
diff --git a/src/pybind/mgr/dashboard/controllers/dashboard.py b/src/pybind/mgr/dashboard/controllers/dashboard.py
deleted file mode 100644 (file)
index 75fc5f6..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import absolute_import
-
-import collections
-import json
-
-from . import ApiController, Endpoint, BaseController
-from .. import mgr
-from ..security import Permission, Scope
-from ..services.ceph_service import CephService
-from ..services.tcmu_service import TcmuService
-from ..tools import NotificationQueue
-
-
-LOG_BUFFER_SIZE = 30
-
-
-@ApiController('/dashboard')
-class Dashboard(BaseController):
-    def __init__(self):
-        super(Dashboard, self).__init__()
-
-        self._log_initialized = False
-
-        self.log_buffer = collections.deque(maxlen=LOG_BUFFER_SIZE)
-        self.audit_buffer = collections.deque(maxlen=LOG_BUFFER_SIZE)
-
-    def append_log(self, log_struct):
-        if log_struct['channel'] == "audit":
-            self.audit_buffer.appendleft(log_struct)
-        else:
-            self.log_buffer.appendleft(log_struct)
-
-    def load_buffer(self, buf, channel_name):
-        lines = CephService.send_command('mon', 'log last', channel=channel_name,
-                                         num=LOG_BUFFER_SIZE)
-        for l in lines:
-            buf.appendleft(l)
-
-    @Endpoint()
-    def health(self):
-        if not self._log_initialized:
-            self._log_initialized = True
-
-            self.load_buffer(self.log_buffer, "cluster")
-            self.load_buffer(self.audit_buffer, "audit")
-
-            NotificationQueue.register(self.append_log, 'clog')
-
-        result = {
-            "health": self.health_data(),
-        }
-
-        if self._has_permissions(Permission.READ, Scope.LOG):
-            result['clog'] = list(self.log_buffer)
-            result['audit_log'] = list(self.audit_buffer)
-
-        if self._has_permissions(Permission.READ, Scope.MONITOR):
-            result['mon_status'] = self.mon_status()
-
-        if self._has_permissions(Permission.READ, Scope.CEPHFS):
-            result['fs_map'] = mgr.get('fs_map')
-
-        if self._has_permissions(Permission.READ, Scope.OSD):
-            osd_map = self.osd_map()
-            # Not needed, skip the effort of transmitting this to UI
-            del osd_map['pg_temp']
-            result['osd_map'] = osd_map
-            result['scrub_status'] = CephService.get_scrub_status()
-            result['pg_info'] = CephService.get_pg_info()
-
-        if self._has_permissions(Permission.READ, Scope.MANAGER):
-            result['mgr_map'] = mgr.get("mgr_map")
-
-        if self._has_permissions(Permission.READ, Scope.POOL):
-            pools = CephService.get_pool_list_with_stats()
-            result['pools'] = pools
-
-            df = mgr.get("df")
-            df['stats']['total_objects'] = sum(
-                [p['stats']['objects'] for p in df['pools']])
-            result['df'] = df
-
-            result['client_perf'] = CephService.get_client_perf()
-
-        if self._has_permissions(Permission.READ, Scope.HOSTS):
-            result['hosts'] = len(mgr.list_servers())
-
-        if self._has_permissions(Permission.READ, Scope.RGW):
-            result['rgw'] = len(CephService.get_service_list('rgw'))
-
-        if self._has_permissions(Permission.READ, Scope.ISCSI):
-            result['iscsi_daemons'] = TcmuService.get_iscsi_daemons_amount()
-
-        return result
-
-    def mon_status(self):
-        mon_status_data = mgr.get("mon_status")
-        return json.loads(mon_status_data['json'])
-
-    def osd_map(self):
-        osd_map = mgr.get("osd_map")
-
-        assert osd_map is not None
-
-        osd_map['tree'] = mgr.get("osd_map_tree")
-        osd_map['crush'] = mgr.get("osd_map_crush")
-        osd_map['crush_map_text'] = mgr.get("osd_map_crush_map_text")
-        osd_map['osd_metadata'] = mgr.get("osd_metadata")
-
-        return osd_map
-
-    def health_data(self):
-        health_data = mgr.get("health")
-        health = json.loads(health_data['json'])
-
-        # Transform the `checks` dict into a list for the convenience
-        # of rendering from javascript.
-        checks = []
-        for k, v in health['checks'].items():
-            v['type'] = k
-            checks.append(v)
-
-        checks = sorted(checks, key=lambda c: c['severity'])
-
-        health['checks'] = checks
-
-        return health
diff --git a/src/pybind/mgr/dashboard/controllers/health.py b/src/pybind/mgr/dashboard/controllers/health.py
new file mode 100644 (file)
index 0000000..4e67f23
--- /dev/null
@@ -0,0 +1,193 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+import json
+
+from . import ApiController, Endpoint, BaseController
+
+from .. import mgr
+from ..security import Permission, Scope
+from ..services.ceph_service import CephService
+from ..services.tcmu_service import TcmuService
+
+
+class HealthData(object):
+    """
+    A class to be used in combination with BaseController to allow either
+    "full" or "minimal" sets of health data to be collected.
+
+    To function properly, it needs BaseCollector._has_permissions to be passed
+    in as ``auth_callback``.
+    """
+
+    def __init__(self, auth_callback, minimal=True):
+        self._has_permissions = auth_callback
+        self._minimal = minimal
+
+    @staticmethod
+    def _partial_dict(orig, keys):
+        return {k: orig[k] for k in keys}
+
+    def all_health(self):
+        result = {
+            "health": self.basic_health(),
+        }
+
+        if self._has_permissions(Permission.READ, Scope.MONITOR):
+            result['mon_status'] = self.mon_status()
+
+        if self._has_permissions(Permission.READ, Scope.CEPHFS):
+            result['fs_map'] = self.fs_map()
+
+        if self._has_permissions(Permission.READ, Scope.OSD):
+            result['osd_map'] = self.osd_map()
+            result['scrub_status'] = self.scrub_status()
+            result['pg_info'] = self.pg_info()
+
+        if self._has_permissions(Permission.READ, Scope.MANAGER):
+            result['mgr_map'] = self.mgr_map()
+
+        if self._has_permissions(Permission.READ, Scope.POOL):
+            result['pools'] = self.pools()
+            result['df'] = self.df()
+            result['client_perf'] = self.client_perf()
+
+        if self._has_permissions(Permission.READ, Scope.HOSTS):
+            result['hosts'] = self.host_count()
+
+        if self._has_permissions(Permission.READ, Scope.RGW):
+            result['rgw'] = self.rgw_count()
+
+        if self._has_permissions(Permission.READ, Scope.ISCSI):
+            result['iscsi_daemons'] = self.iscsi_daemons()
+
+        return result
+
+    def basic_health(self):
+        health_data = mgr.get("health")
+        health = json.loads(health_data['json'])
+
+        # Transform the `checks` dict into a list for the convenience
+        # of rendering from javascript.
+        checks = []
+        for k, v in health['checks'].items():
+            v['type'] = k
+            checks.append(v)
+
+        checks = sorted(checks, key=lambda c: c['severity'])
+        health['checks'] = checks
+        return health
+
+    def client_perf(self):
+        result = CephService.get_client_perf()
+        if self._minimal:
+            result = self._partial_dict(
+                result,
+                ['read_bytes_sec', 'read_op_per_sec',
+                 'recovering_bytes_per_sec', 'write_bytes_sec',
+                 'write_op_per_sec']
+            )
+        return result
+
+    def df(self):
+        df = mgr.get('df')
+        df['stats']['total_objects'] = sum(
+            [p['stats']['objects'] for p in df['pools']])
+        if self._minimal:
+            df = dict(stats=self._partial_dict(
+                df['stats'],
+                ['total_avail_bytes', 'total_bytes', 'total_objects',
+                 'total_used_bytes']
+            ))
+        return df
+
+    def fs_map(self):
+        fs_map = mgr.get('fs_map')
+        if self._minimal:
+            fs_map = self._partial_dict(fs_map, ['filesystems', 'standbys'])
+            fs_map['standbys'] = [{}] * len(fs_map['standbys'])
+            fs_map['filesystems'] = [self._partial_dict(item, ['mdsmap']) for
+                                     item in fs_map['filesystems']]
+            for fs in fs_map['filesystems']:
+                mdsmap_info = fs['mdsmap']['info']
+                min_mdsmap_info = dict()
+                for k, v in mdsmap_info.items():
+                    min_mdsmap_info[k] = self._partial_dict(v, ['state'])
+                fs['mdsmap'] = dict(info=min_mdsmap_info)
+        return fs_map
+
+    def host_count(self):
+        return len(mgr.list_servers())
+
+    def iscsi_daemons(self):
+        return TcmuService.get_iscsi_daemons_amount()
+
+    def mgr_map(self):
+        mgr_map = mgr.get('mgr_map')
+        if self._minimal:
+            mgr_map = self._partial_dict(mgr_map, ['active_name', 'standbys'])
+            mgr_map['standbys'] = [{}] * len(mgr_map['standbys'])
+        return mgr_map
+
+    def mon_status(self):
+        mon_status = json.loads(mgr.get('mon_status')['json'])
+        if self._minimal:
+            mon_status = self._partial_dict(mon_status, ['monmap', 'quorum'])
+            mon_status['monmap'] = self._partial_dict(
+                mon_status['monmap'], ['mons']
+            )
+            mon_status['monmap']['mons'] = [{}] * \
+                len(mon_status['monmap']['mons'])
+        return mon_status
+
+    def osd_map(self):
+        osd_map = mgr.get('osd_map')
+        assert osd_map is not None
+        # Not needed, skip the effort of transmitting this to UI
+        del osd_map['pg_temp']
+        if self._minimal:
+            osd_map = self._partial_dict(osd_map, ['osds'])
+            osd_map['osds'] = [
+                self._partial_dict(item, ['in', 'up'])
+                for item in osd_map['osds']
+            ]
+        else:
+            osd_map['tree'] = mgr.get('osd_map_tree')
+            osd_map['crush'] = mgr.get('osd_map_crush')
+            osd_map['crush_map_text'] = mgr.get('osd_map_crush_map_text')
+            osd_map['osd_metadata'] = mgr.get('osd_metadata')
+        return osd_map
+
+    def pg_info(self):
+        pg_info = CephService.get_pg_info()
+        if self._minimal:
+            pg_info = self._partial_dict(pg_info, ['pgs_per_osd', 'statuses'])
+        return pg_info
+
+    def pools(self):
+        pools = CephService.get_pool_list_with_stats()
+        if self._minimal:
+            pools = [{}] * len(pools)
+        return pools
+
+    def rgw_count(self):
+        return len(CephService.get_service_list('rgw'))
+
+    def scrub_status(self):
+        return CephService.get_scrub_status()
+
+
+@ApiController('/health')
+class Health(BaseController):
+    def __init__(self):
+        super(Health, self).__init__()
+        self.health_full = HealthData(self._has_permissions, minimal=False)
+        self.health_minimal = HealthData(self._has_permissions, minimal=True)
+
+    @Endpoint()
+    def full(self):
+        return self.health_full.all_health()
+
+    @Endpoint()
+    def minimal(self):
+        return self.health_minimal.all_health()
diff --git a/src/pybind/mgr/dashboard/controllers/logs.py b/src/pybind/mgr/dashboard/controllers/logs.py
new file mode 100644 (file)
index 0000000..9dc5286
--- /dev/null
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+import collections
+
+from . import ApiController, Endpoint, BaseController, ReadPermission
+from ..security import Scope
+from ..services.ceph_service import CephService
+from ..tools import NotificationQueue
+
+
+LOG_BUFFER_SIZE = 30
+
+
+@ApiController('/logs', Scope.LOG)
+class Logs(BaseController):
+    def __init__(self):
+        super(Logs, self).__init__()
+        self._log_initialized = False
+        self.log_buffer = collections.deque(maxlen=LOG_BUFFER_SIZE)
+        self.audit_buffer = collections.deque(maxlen=LOG_BUFFER_SIZE)
+
+    def append_log(self, log_struct):
+        if log_struct['channel'] == 'audit':
+            self.audit_buffer.appendleft(log_struct)
+        else:
+            self.log_buffer.appendleft(log_struct)
+
+    def load_buffer(self, buf, channel_name):
+        lines = CephService.send_command(
+            'mon', 'log last', channel=channel_name, num=LOG_BUFFER_SIZE)
+        for l in lines:
+            buf.appendleft(l)
+
+    def initialize_buffers(self):
+        if not self._log_initialized:
+            self._log_initialized = True
+
+            self.load_buffer(self.log_buffer, 'cluster')
+            self.load_buffer(self.audit_buffer, 'audit')
+
+            NotificationQueue.register(self.append_log, 'clog')
+
+    @Endpoint()
+    @ReadPermission
+    def all(self):
+        self.initialize_buffers()
+        return dict(
+            clog=list(self.log_buffer),
+            audit_log=list(self.audit_buffer),
+        )
index 7c959569145f58957d906695758eda5c330c2930..cf884056ec4a12417ccfddefbde08bc83cf42970 100644 (file)
@@ -8,7 +8,7 @@ import { TreeModule } from 'ng2-tree';
 import { TabsModule } from 'ngx-bootstrap/tabs';
 
 import { configureTestBed } from '../../../../testing/unit-test-helper';
-import { DashboardService } from '../../../shared/api/dashboard.service';
+import { HealthService } from '../../../shared/api/health.service';
 import { SharedModule } from '../../../shared/shared.module';
 import { CrushmapComponent } from './crushmap.component';
 
@@ -19,7 +19,7 @@ describe('CrushmapComponent', () => {
   configureTestBed({
     imports: [HttpClientTestingModule, TreeModule, TabsModule.forRoot(), SharedModule],
     declarations: [CrushmapComponent],
-    providers: [DashboardService]
+    providers: [HealthService]
   });
 
   beforeEach(() => {
@@ -39,21 +39,21 @@ describe('CrushmapComponent', () => {
   });
 
   describe('test tree', () => {
-    let dashboardService: DashboardService;
+    let healthService: HealthService;
     const prepareGetHealth = (nodes: object[]) => {
-      spyOn(dashboardService, 'getHealth').and.returnValue(
+      spyOn(healthService, 'getFullHealth').and.returnValue(
         of({ osd_map: { tree: { nodes: nodes } } })
       );
       fixture.detectChanges();
     };
 
     beforeEach(() => {
-      dashboardService = debugElement.injector.get(DashboardService);
+      healthService = debugElement.injector.get(HealthService);
     });
 
     it('should display "No nodes!" if ceph tree nodes is empty array', () => {
       prepareGetHealth([]);
-      expect(dashboardService.getHealth).toHaveBeenCalled();
+      expect(healthService.getFullHealth).toHaveBeenCalled();
       expect(component.tree.value).toEqual('No nodes!');
     });
 
index 1e843a3f7028f6fc357c6c958526e7ffc4bab8b4..945b1ed5fb4320a362d44c8da206c008e4789139 100644 (file)
@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
 
 import { NodeEvent, TreeModel } from 'ng2-tree';
 
-import { DashboardService } from '../../../shared/api/dashboard.service';
+import { HealthService } from '../../../shared/api/health.service';
 
 @Component({
   selector: 'cd-crushmap',
@@ -14,10 +14,10 @@ export class CrushmapComponent implements OnInit {
   metadata: any;
   metadataKeyMap: { [key: number]: number } = {};
 
-  constructor(private dashboardService: DashboardService) {}
+  constructor(private healthService: HealthService) {}
 
   ngOnInit() {
-    this.dashboardService.getHealth().subscribe((data: any) => {
+    this.healthService.getFullHealth().subscribe((data: any) => {
       this.tree = this._abstractTreeData(data);
     });
   }
index 19d3ec975068a120c516c3671346d6fd643f956d..d3a9a341ad7aedd987989b3060a94c53b19302ed 100644 (file)
@@ -1,6 +1,6 @@
 import { Component, OnDestroy, OnInit } from '@angular/core';
 
-import { DashboardService } from '../../../shared/api/dashboard.service';
+import { LogsService } from '../../../shared/api/logs.service';
 
 @Component({
   selector: 'cd-logs',
@@ -11,7 +11,7 @@ export class LogsComponent implements OnInit, OnDestroy {
   contentData: any;
   interval: number;
 
-  constructor(private dashboardService: DashboardService) {}
+  constructor(private logsService: LogsService) {}
 
   ngOnInit() {
     this.getInfo();
@@ -25,7 +25,7 @@ export class LogsComponent implements OnInit, OnDestroy {
   }
 
   getInfo() {
-    this.dashboardService.getHealth().subscribe((data: any) => {
+    this.logsService.getLogs().subscribe((data: any) => {
       this.contentData = data;
     });
   }
index efe3657e3f10b9c3758c08e32dba54665d4d23cd..54d9e40bd1551560ff1b23922bc4167b29bbc688 100644 (file)
@@ -1,34 +1,34 @@
-<div *ngIf="contentData"
+<div *ngIf="healthData"
      class="container-fluid">
   <cd-info-group groupTitle="Status"
                  i18n-groupTitle
                  class="row info-group"
-                 *ngIf="contentData.health?.status
-                 || contentData.mon_status
-                 || contentData.osd_map
-                 || contentData.mgr_map
-                 || contentData.hosts != null
-                 || contentData.rgw != null
-                 || contentData.fs_map
-                 || contentData.iscsi_daemons != null">
+                 *ngIf="healthData.health?.status
+                 || healthData.mon_status
+                 || healthData.osd_map
+                 || healthData.mgr_map
+                 || healthData.hosts != null
+                 || healthData.rgw != null
+                 || healthData.fs_map
+                 || healthData.iscsi_daemons != null">
 
     <cd-info-card cardTitle="Cluster Status"
                   i18n-cardTitle
                   class="col-sm-6 col-md-4 col-lg-3"
-                  [contentClass]="contentData.health?.checks?.length > 0 ? 'content-highlight text-area-size-2' : 'content-highlight'"
-                  *ngIf="contentData.health?.status">
-      <ng-container *ngIf="contentData.health?.checks?.length > 0">
+                  [contentClass]="healthData.health?.checks?.length > 0 ? 'content-highlight text-area-size-2' : 'content-highlight'"
+                  *ngIf="healthData.health?.status">
+      <ng-container *ngIf="healthData.health?.checks?.length > 0">
         <ng-template #healthChecks>
           <p class="logs-link"
              i18n>&rarr; See <a routerLink="/logs">Logs</a> for more details.</p>
           <ul>
-            <li *ngFor="let check of contentData.health.checks">
+            <li *ngFor="let check of healthData.health.checks">
               <span [ngStyle]="check.severity | healthColor">{{ check.type }}</span>: {{ check.summary.message }}
             </li>
           </ul>
         </ng-template>
         <div class="info-card-content-clickable"
-             [ngStyle]="contentData.health.status | healthColor"
+             [ngStyle]="healthData.health.status | healthColor"
              [popover]="healthChecks"
              triggers=""
              #healthChecksTarget="bs-popover"
              container="body"
              containerClass="info-card-popover-cluster-status"
              (click)="healthChecksTarget.toggle()">
-          {{ contentData.health.status }}
+          {{ healthData.health.status }}
         </div>
       </ng-container>
-      <ng-container *ngIf="contentData.health?.checks?.length == 0">
-        <div [ngStyle]="contentData.health.status | healthColor">
-          {{ contentData.health.status }}
+      <ng-container *ngIf="healthData.health?.checks?.length == 0">
+        <div [ngStyle]="healthData.health.status | healthColor">
+          {{ healthData.health.status }}
         </div>
       </ng-container>
     </cd-info-card>
                   link="/monitor"
                   class="col-sm-6 col-md-4 col-lg-3"
                   contentClass="content-highlight"
-                  *ngIf="contentData.mon_status">
-      {{ contentData.mon_status | monSummary }}
+                  *ngIf="healthData.mon_status">
+      {{ healthData.mon_status | monSummary }}
     </cd-info-card>
 
     <cd-info-card cardTitle="OSDs"
                   i18n-cardTitle
                   link="/osd"
                   class="col-sm-6 col-md-4 col-lg-3"
-                  *ngIf="(contentData.osd_map | osdSummary) as transformedResult"
+                  *ngIf="(healthData.osd_map | osdSummary) as transformedResult"
                   [contentClass]="(transformedResult.length == 5 ? 'text-area-size-3' : 'text-area-size-2') + ' content-highlight'">
       <span *ngFor="let result of transformedResult"
             [ngClass]="result.class">
@@ -71,8 +71,8 @@
                   i18n-cardTitle
                   class="col-sm-6 col-md-4 col-lg-3"
                   contentClass="content-highlight text-area-size-2"
-                  *ngIf="contentData.mgr_map">
-      <span *ngFor="let result of (contentData.mgr_map | mgrSummary)"
+                  *ngIf="healthData.mgr_map">
+      <span *ngFor="let result of (healthData.mgr_map | mgrSummary)"
             [ngClass]="result.class"
             [title]="result.titleText != null ? result.titleText : ''">
         {{ result.content }}
@@ -84,8 +84,8 @@
                   link="/hosts"
                   class="col-sm-6 col-md-4 col-lg-3"
                   contentClass="content-medium content-highlight"
-                  *ngIf="contentData.hosts != null">
-      {{ contentData.hosts }} total
+                  *ngIf="healthData.hosts != null">
+      {{ healthData.hosts }} total
     </cd-info-card>
 
     <cd-info-card cardTitle="Object Gateways"
                   link="/rgw/daemon"
                   class="col-sm-6 col-md-4 col-lg-3"
                   contentClass="content-medium content-highlight"
-                  *ngIf="contentData.rgw != null">
-      {{ contentData.rgw }} total
+                  *ngIf="healthData.rgw != null">
+      {{ healthData.rgw }} total
     </cd-info-card>
     <cd-info-card cardTitle="Metadata Servers"
                   i18n-cardTitle
                   class="col-sm-6 col-md-4 col-lg-3"
-                  *ngIf="(contentData.fs_map | mdsSummary) as transformedResult"
+                  *ngIf="(healthData.fs_map | mdsSummary) as transformedResult"
                   [contentClass]="(transformedResult.length > 1 ? 'text-area-size-2' : '') + ' content-highlight'">
       <span *ngFor="let result of transformedResult"
             [ngClass]="result.class">
                   link="/block/iscsi"
                   class="col-sm-6 col-md-4 col-lg-3"
                   contentClass="content-medium content-highlight"
-                  *ngIf="contentData.iscsi_daemons != null">
-      {{ contentData.iscsi_daemons }} total
+                  *ngIf="healthData.iscsi_daemons != null">
+      {{ healthData.iscsi_daemons }} total
     </cd-info-card>
   </cd-info-group>
 
   <cd-info-group groupTitle="Performance"
                  i18n-groupTitle
                  class="row info-group"
-                 *ngIf="contentData.client_perf || contentData.scrub_status">
+                 *ngIf="healthData.client_perf || healthData.scrub_status">
 
     <div class="cd-container-flex">
       <cd-info-card cardTitle="Client IOPS"
                     class="cd-col-5"
                     cardClass="card-medium"
                     contentClass="content-medium content-highlight"
-                    *ngIf="contentData.client_perf">
-        {{ (contentData.client_perf.read_op_per_sec + contentData.client_perf.write_op_per_sec) | round:1 }}
+                    *ngIf="healthData.client_perf">
+        {{ (healthData.client_perf.read_op_per_sec + healthData.client_perf.write_op_per_sec) | round:1 }}
       </cd-info-card>
 
       <cd-info-card cardTitle="Client Throughput"
                     class="cd-col-5"
                     cardClass="card-medium"
                     contentClass="content-medium content-highlight"
-                    *ngIf="contentData.client_perf">
-        {{ ((contentData.client_perf.read_bytes_sec + contentData.client_perf.write_bytes_sec) | dimlessBinary) + '/s' }}
+                    *ngIf="healthData.client_perf">
+        {{ ((healthData.client_perf.read_bytes_sec + healthData.client_perf.write_bytes_sec) | dimlessBinary) + '/s' }}
       </cd-info-card>
 
       <cd-info-card cardTitle="Client Read/Write"
                     i18n-cardTitle
                     class="cd-col-5"
                     cardClass="card-medium"
-                    [contentClass]="(contentData.client_perf.read_op_per_sec + contentData.client_perf.write_op_per_sec) <= 0 ? 'content-medium content-highlight' : 'content-chart'"
-                    *ngIf="contentData.client_perf">
-        <cd-health-pie *ngIf="(contentData.client_perf.read_op_per_sec + contentData.client_perf.write_op_per_sec) > 0"
-                       [data]="contentData"
+                    [contentClass]="(healthData.client_perf.read_op_per_sec + healthData.client_perf.write_op_per_sec) <= 0 ? 'content-medium content-highlight' : 'content-chart'"
+                    *ngIf="healthData.client_perf">
+        <cd-health-pie *ngIf="(healthData.client_perf.read_op_per_sec + healthData.client_perf.write_op_per_sec) > 0"
+                       [data]="healthData"
                        [isBytesData]="false"
                        chartType="pie"
                        [displayLegend]="true"
                        (prepareFn)="prepareReadWriteRatio($event[0], $event[1])">
         </cd-health-pie>
-        <span *ngIf="(contentData.client_perf.read_op_per_sec + contentData.client_perf.write_op_per_sec) <= 0">
+        <span *ngIf="(healthData.client_perf.read_op_per_sec + healthData.client_perf.write_op_per_sec) <= 0">
           N/A
         </span>
       </cd-info-card>
                     class="cd-col-5"
                     cardClass="card-medium"
                     contentClass="content-medium content-highlight"
-                    *ngIf="contentData.client_perf">
-        {{ (contentData.client_perf.recovering_bytes_per_sec | dimlessBinary) + '/s' }}
+                    *ngIf="healthData.client_perf">
+        {{ (healthData.client_perf.recovering_bytes_per_sec | dimlessBinary) + '/s' }}
       </cd-info-card>
 
       <cd-info-card cardTitle="Scrub"
                     class="cd-col-5"
                     cardClass="card-medium"
                     contentClass="content-medium content-highlight"
-                    *ngIf="contentData.scrub_status">
-        {{ contentData.scrub_status }}
+                    *ngIf="healthData.scrub_status">
+        {{ healthData.scrub_status }}
       </cd-info-card>
     </div>
   </cd-info-group>
   <cd-info-group groupTitle="Capacity"
                  i18n-groupTitle
                  class="row info-group"
-                 *ngIf="contentData.pools
-                 || contentData.df
-                 || contentData.df?.stats?.total_objects != null
-                 || contentData.pg_info">
+                 *ngIf="healthData.pools
+                 || healthData.df
+                 || healthData.df?.stats?.total_objects != null
+                 || healthData.pg_info">
 
     <div class="cd-container-flex">
       <cd-info-card cardTitle="Pools"
                     class="cd-col-5"
                     cardClass="card-medium"
                     contentClass="content-medium content-highlight"
-                    *ngIf="contentData.pools">
-        {{ contentData.pools.length }}
+                    *ngIf="healthData.pools">
+        {{ healthData.pools.length }}
       </cd-info-card>
 
       <cd-info-card cardTitle="Raw Capacity"
                     class="cd-col-5"
                     cardClass="card-medium"
                     contentClass="content-chart"
-                    *ngIf="contentData.df">
-        <cd-health-pie [data]="contentData"
+                    *ngIf="healthData.df">
+        <cd-health-pie [data]="healthData"
                        [isBytesData]="true"
                        [displayLegend]="true"
                        (prepareFn)="prepareRawUsage($event[0], $event[1])">
                     class="cd-col-5"
                     cardClass="card-medium"
                     contentClass="content-medium content-highlight"
-                    *ngIf="contentData.df?.stats?.total_objects != null">
-        {{ contentData.df?.stats?.total_objects }}
+                    *ngIf="healthData.df?.stats?.total_objects != null">
+        {{ healthData.df?.stats?.total_objects }}
       </cd-info-card>
 
       <cd-info-card cardTitle="PGs per OSD"
                     class="cd-col-5"
                     cardClass="card-medium"
                     contentClass="content-medium content-highlight"
-                    *ngIf="contentData.pg_info">
-        {{ contentData.pg_info.pgs_per_osd | dimless }}
+                    *ngIf="healthData.pg_info">
+        {{ healthData.pg_info.pgs_per_osd | dimless }}
       </cd-info-card>
 
       <cd-info-card cardTitle="PG Status"
                     cardClass="card-medium"
                     contentClass="content-chart"
                     (click)="pgStatusTarget.toggle()"
-                    *ngIf="contentData.pg_info">
+                    *ngIf="healthData.pg_info">
         <ng-template #pgStatus>
           <p class="logs-link"
              i18n>&rarr; See <a routerLink="/logs">Logs</a> for more details.</p>
           <ul>
-            <li *ngFor="let pgStatesText of contentData.pg_info.statuses | keyvalue">
+            <li *ngFor="let pgStatesText of healthData.pg_info.statuses | keyvalue">
               {{ pgStatesText.key }}: {{ pgStatesText.value }}
             </li>
           </ul>
                triggers=""
                #pgStatusTarget="bs-popover"
                placement="bottom">
-            <cd-health-pie [data]="contentData"
+            <cd-health-pie [data]="healthData"
                            chartType="pie"
                            [displayLegend]="true"
                            (prepareFn)="preparePgStatus($event[0], $event[1])">
index 44593d6ac97878c51bbf84eb79f0b172257d3822..f4de0412581bcfc5c5c60fbfaa8772a7500704b4 100644 (file)
@@ -7,9 +7,8 @@ import { PopoverModule } from 'ngx-bootstrap/popover';
 import { of } from 'rxjs';
 
 import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper';
-import { DashboardService } from '../../../shared/api/dashboard.service';
+import { HealthService } from '../../../shared/api/health.service';
 import { SharedModule } from '../../../shared/shared.module';
-import { LogColorPipe } from '../log-color.pipe';
 import { MdsSummaryPipe } from '../mds-summary.pipe';
 import { MgrSummaryPipe } from '../mgr-summary.pipe';
 import { MonSummaryPipe } from '../mon-summary.pipe';
@@ -47,7 +46,6 @@ describe('HealthComponent', () => {
       MdsSummaryPipe,
       MgrSummaryPipe,
       PgStatusStylePipe,
-      LogColorPipe,
       PgStatusPipe
     ],
     schemas: [NO_ERRORS_SCHEMA],
@@ -57,7 +55,7 @@ describe('HealthComponent', () => {
   beforeEach(() => {
     fixture = TestBed.createComponent(HealthComponent);
     component = fixture.componentInstance;
-    getHealthSpy = spyOn(TestBed.get(DashboardService), 'getHealth');
+    getHealthSpy = spyOn(TestBed.get(HealthService), 'getMinimalHealth');
   });
 
   it('should create', () => {
index eb65a16f7ad052df9c3052da344fda2a23927f8e..5ec12fee396d42a802712b26d4497d55b135e291 100644 (file)
@@ -3,7 +3,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
 import { I18n } from '@ngx-translate/i18n-polyfill';
 import * as _ from 'lodash';
 
-import { DashboardService } from '../../../shared/api/dashboard.service';
+import { HealthService } from '../../../shared/api/health.service';
 
 @Component({
   selector: 'cd-health',
@@ -11,15 +11,15 @@ import { DashboardService } from '../../../shared/api/dashboard.service';
   styleUrls: ['./health.component.scss']
 })
 export class HealthComponent implements OnInit, OnDestroy {
-  contentData: any;
+  healthData: any;
   interval: number;
 
-  constructor(private dashboardService: DashboardService, private i18n: I18n) {}
+  constructor(private healthService: HealthService, private i18n: I18n) {}
 
   ngOnInit() {
-    this.getInfo();
+    this.getHealth();
     this.interval = window.setInterval(() => {
-      this.getInfo();
+      this.getHealth();
     }, 5000);
   }
 
@@ -27,9 +27,9 @@ export class HealthComponent implements OnInit, OnDestroy {
     clearInterval(this.interval);
   }
 
-  getInfo() {
-    this.dashboardService.getHealth().subscribe((data: any) => {
-      this.contentData = data;
+  getHealth() {
+    this.healthService.getMinimalHealth().subscribe((data: any) => {
+      this.healthData = data;
     });
   }
 
@@ -38,9 +38,9 @@ export class HealthComponent implements OnInit, OnDestroy {
     const ratioData = [];
 
     ratioLabels.push(this.i18n('Writes'));
-    ratioData.push(this.contentData.client_perf.write_op_per_sec);
+    ratioData.push(this.healthData.client_perf.write_op_per_sec);
     ratioLabels.push(this.i18n('Reads'));
-    ratioData.push(this.contentData.client_perf.read_op_per_sec);
+    ratioData.push(this.healthData.client_perf.read_op_per_sec);
 
     chart.dataset[0].data = ratioData;
     chart.labels = ratioLabels;
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/dashboard.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/dashboard.service.spec.ts
deleted file mode 100644 (file)
index dcfde30..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
-import { TestBed } from '@angular/core/testing';
-
-import { configureTestBed } from '../../../testing/unit-test-helper';
-import { DashboardService } from './dashboard.service';
-
-describe('DashboardService', () => {
-  let service: DashboardService;
-  let httpTesting: HttpTestingController;
-
-  configureTestBed({
-    providers: [DashboardService],
-    imports: [HttpClientTestingModule]
-  });
-
-  beforeEach(() => {
-    service = TestBed.get(DashboardService);
-    httpTesting = TestBed.get(HttpTestingController);
-  });
-
-  afterEach(() => {
-    httpTesting.verify();
-  });
-
-  it('should be created', () => {
-    expect(service).toBeTruthy();
-  });
-
-  it('should call getHealth', () => {
-    service.getHealth().subscribe();
-    const req = httpTesting.expectOne('api/dashboard/health');
-    expect(req.request.method).toBe('GET');
-  });
-});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/dashboard.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/dashboard.service.ts
deleted file mode 100644 (file)
index 04bdece..0000000
+++ /dev/null
@@ -1,15 +0,0 @@
-import { HttpClient } from '@angular/common/http';
-import { Injectable } from '@angular/core';
-
-import { ApiModule } from './api.module';
-
-@Injectable({
-  providedIn: ApiModule
-})
-export class DashboardService {
-  constructor(private http: HttpClient) {}
-
-  getHealth() {
-    return this.http.get('api/dashboard/health');
-  }
-}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.spec.ts
new file mode 100644 (file)
index 0000000..8273640
--- /dev/null
@@ -0,0 +1,40 @@
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { TestBed } from '@angular/core/testing';
+
+import { configureTestBed } from '../../../testing/unit-test-helper';
+import { HealthService } from './health.service';
+
+describe('HealthService', () => {
+  let service: HealthService;
+  let httpTesting: HttpTestingController;
+
+  configureTestBed({
+    providers: [HealthService],
+    imports: [HttpClientTestingModule]
+  });
+
+  beforeEach(() => {
+    service = TestBed.get(HealthService);
+    httpTesting = TestBed.get(HttpTestingController);
+  });
+
+  afterEach(() => {
+    httpTesting.verify();
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+
+  it('should call getFullHealth', () => {
+    service.getFullHealth().subscribe();
+    const req = httpTesting.expectOne('api/health/full');
+    expect(req.request.method).toBe('GET');
+  });
+
+  it('should call getMinimalHealth', () => {
+    service.getMinimalHealth().subscribe();
+    const req = httpTesting.expectOne('api/health/minimal');
+    expect(req.request.method).toBe('GET');
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/health.service.ts
new file mode 100644 (file)
index 0000000..e3ac393
--- /dev/null
@@ -0,0 +1,19 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+
+import { ApiModule } from './api.module';
+
+@Injectable({
+  providedIn: ApiModule
+})
+export class HealthService {
+  constructor(private http: HttpClient) {}
+
+  getFullHealth() {
+    return this.http.get('api/health/full');
+  }
+
+  getMinimalHealth() {
+    return this.http.get('api/health/minimal');
+  }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/logs.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/logs.service.spec.ts
new file mode 100644 (file)
index 0000000..8e7777a
--- /dev/null
@@ -0,0 +1,34 @@
+import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
+import { TestBed } from '@angular/core/testing';
+
+import { configureTestBed } from '../../../testing/unit-test-helper';
+import { LogsService } from './logs.service';
+
+describe('LogsService', () => {
+  let service: LogsService;
+  let httpTesting: HttpTestingController;
+
+  configureTestBed({
+    providers: [LogsService],
+    imports: [HttpClientTestingModule]
+  });
+
+  beforeEach(() => {
+    service = TestBed.get(LogsService);
+    httpTesting = TestBed.get(HttpTestingController);
+  });
+
+  afterEach(() => {
+    httpTesting.verify();
+  });
+
+  it('should be created', () => {
+    expect(service).toBeTruthy();
+  });
+
+  it('should call getLogs', () => {
+    service.getLogs().subscribe();
+    const req = httpTesting.expectOne('api/logs/all');
+    expect(req.request.method).toBe('GET');
+  });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/logs.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/logs.service.ts
new file mode 100644 (file)
index 0000000..81b868a
--- /dev/null
@@ -0,0 +1,15 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+
+import { ApiModule } from './api.module';
+
+@Injectable({
+  providedIn: ApiModule
+})
+export class LogsService {
+  constructor(private http: HttpClient) {}
+
+  getLogs() {
+    return this.http.get('api/logs/all');
+  }
+}