From 66977290a0de08b4c8701b1b3e372d046dd6e3de Mon Sep 17 00:00:00 2001 From: Noah Watkins Date: Wed, 8 Aug 2018 11:24:59 -0700 Subject: [PATCH] mgr/insights: add unit tests wired up to make check Signed-off-by: Noah Watkins --- src/pybind/mgr/CMakeLists.txt | 1 + src/pybind/mgr/insights/CMakeLists.txt | 7 + src/pybind/mgr/insights/__init__.py | 10 +- src/pybind/mgr/insights/run-tox.sh | 29 ++ src/pybind/mgr/insights/tests/__init__.py | 0 src/pybind/mgr/insights/tests/test_health.py | 273 +++++++++++++++++++ src/pybind/mgr/insights/tox.ini | 16 ++ src/test/CMakeLists.txt | 5 + 8 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 src/pybind/mgr/insights/CMakeLists.txt create mode 100644 src/pybind/mgr/insights/run-tox.sh create mode 100644 src/pybind/mgr/insights/tests/__init__.py create mode 100644 src/pybind/mgr/insights/tests/test_health.py create mode 100644 src/pybind/mgr/insights/tox.ini diff --git a/src/pybind/mgr/CMakeLists.txt b/src/pybind/mgr/CMakeLists.txt index 360a923847e43..916c672b9ba44 100644 --- a/src/pybind/mgr/CMakeLists.txt +++ b/src/pybind/mgr/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(dashboard) +add_subdirectory(insights) diff --git a/src/pybind/mgr/insights/CMakeLists.txt b/src/pybind/mgr/insights/CMakeLists.txt new file mode 100644 index 0000000000000..00722a99581ec --- /dev/null +++ b/src/pybind/mgr/insights/CMakeLists.txt @@ -0,0 +1,7 @@ +set(MGR_INSIGHTS_VIRTUALENV ${CEPH_BUILD_VIRTUALENV}/mgr-insights-virtualenv) + +add_custom_target(mgr-insights-test-venv + COMMAND ${CMAKE_SOURCE_DIR}/src/tools/setup-virtualenv.sh --python=${MGR_PYTHON_EXECUTABLE} ${MGR_INSIGHTS_VIRTUALENV} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src/pybind/mgr/insights + COMMENT "insights tests virtualenv is being created") +add_dependencies(tests mgr-insights-test-venv) diff --git a/src/pybind/mgr/insights/__init__.py b/src/pybind/mgr/insights/__init__.py index 8f210ac9247ea..ea61a12fd7eaf 100644 --- a/src/pybind/mgr/insights/__init__.py +++ b/src/pybind/mgr/insights/__init__.py @@ -1 +1,9 @@ -from .module import Module +from __future__ import absolute_import +import os + +if 'UNITTEST' not in os.environ: + from .module import Module +else: + import sys + import mock + sys.modules['ceph_module'] = mock.Mock() diff --git a/src/pybind/mgr/insights/run-tox.sh b/src/pybind/mgr/insights/run-tox.sh new file mode 100644 index 0000000000000..fb7f755142cb0 --- /dev/null +++ b/src/pybind/mgr/insights/run-tox.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# run from ./ or from ../ +: ${MGR_INSIGHTS_VIRTUALENV:=/tmp/mgr-insights-virtualenv} +: ${WITH_PYTHON2:=ON} +: ${WITH_PYTHON3:=ON} +: ${CEPH_BUILD_DIR:=$PWD/.tox} +test -d insights && cd insights + +if [ -e tox.ini ]; then + TOX_PATH=`readlink -f tox.ini` +else + TOX_PATH=`readlink -f $(dirname $0)/tox.ini` +fi + +# tox.ini will take care of this. +unset PYTHONPATH +export CEPH_BUILD_DIR=$CEPH_BUILD_DIR + +source ${MGR_INSIGHTS_VIRTUALENV}/bin/activate + +if [ "$WITH_PYTHON2" = "ON" ]; then + ENV_LIST+="py27" +fi +if [ "$WITH_PYTHON3" = "ON" ]; then + ENV_LIST+="py3" +fi + +tox -c ${TOX_PATH} -e ${ENV_LIST} diff --git a/src/pybind/mgr/insights/tests/__init__.py b/src/pybind/mgr/insights/tests/__init__.py new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/pybind/mgr/insights/tests/test_health.py b/src/pybind/mgr/insights/tests/test_health.py new file mode 100644 index 0000000000000..41c3a5c65c52f --- /dev/null +++ b/src/pybind/mgr/insights/tests/test_health.py @@ -0,0 +1,273 @@ +import unittest +import mock +from ..health import * + +class HealthChecksTest(unittest.TestCase): + def test_check_accum_empty(self): + # health checks accum initially empty reports empty + h = HealthCheckAccumulator() + self.assertEqual(h.checks(), {}) + + h = HealthCheckAccumulator({}) + self.assertEqual(h.checks(), {}) + + def _get_init_checks(self): + return HealthCheckAccumulator({ + "C0": { + "S0": { + "summary": ["s0", "s1"], + "detail": ("d0", "d1") + } + } + }) + + def test_check_init(self): + # initialization with lists and tuples is OK + h = self._get_init_checks() + self.assertEqual(h.checks(), { + "C0": { + "S0": { + "summary": set(["s0", "s1"]), + "detail": set(["d0", "d1"]) + } + } + }) + + def _get_merged_checks(self): + h = self._get_init_checks() + h.merge(HealthCheckAccumulator({ + "C0": { + "S0": { + "summary": ["s0", "s1", "s2"], + "detail": ("d2",) + }, + "S1": { + "summary": ["s0", "s1", "s2"], + "detail": () + } + }, + "C1": { + "S0": { + "summary": [], + "detail": ("d0", "d1", "d2") + } + } + })) + return h + + def test_check_merge(self): + # merging combines and de-duplicates + h = self._get_merged_checks() + self.assertEqual(h.checks(), { + "C0": { + "S0": { + "summary": set(["s0", "s1", "s2"]), + "detail": set(["d0", "d1", "d2"]) + }, + "S1": { + "summary": set(["s0", "s1", "s2"]), + "detail": set([]) + } + }, + "C1": { + "S0": { + "summary": set([]), + "detail": set(["d0", "d1", "d2"]) + } + } + }) + + def test_check_add_no_change(self): + # returns false when nothing changes + h = self._get_merged_checks() + + self.assertFalse(h.add({})) + + self.assertFalse(h.add({ + "C0": { + "severity": "S0", + "summary": { "message": "s0" }, + "detail": [] + } + })) + + self.assertFalse(h.add({ + "C0": { + "severity": "S0", + "summary": { "message": "s1" }, + "detail": [{ "message": "d1" }] + } + })) + + self.assertFalse(h.add({ + "C0": { + "severity": "S0", + "summary": { "message": "s0" }, + "detail": [{ "message": "d1" }, { "message": "d2" }] + } + })) + + def test_check_add_changed(self): + # new checks report change + h = self._get_merged_checks() + + self.assertTrue(h.add({ + "C0": { + "severity": "S0", + "summary": { "message": "s3" }, + "detail": [] + } + })) + + self.assertTrue(h.add({ + "C0": { + "severity": "S0", + "summary": { "message": "s1" }, + "detail": [{ "message": "d4" }] + } + })) + + self.assertTrue(h.add({ + "C0": { + "severity": "S2", + "summary": { "message": "s0" }, + "detail": [{ "message": "d0" }] + } + })) + + self.assertTrue(h.add({ + "C2": { + "severity": "S0", + "summary": { "message": "s0" }, + "detail": [{ "message": "d0" }, { "message": "d1" }] + } + })) + + self.assertEqual(h.checks(), { + "C0": { + "S0": { + "summary": set(["s0", "s1", "s2", "s3"]), + "detail": set(["d0", "d1", "d2", "d4"]) + }, + "S1": { + "summary": set(["s0", "s1", "s2"]), + "detail": set([]) + }, + "S2": { + "summary": set(["s0"]), + "detail": set(["d0"]) + } + }, + "C1": { + "S0": { + "summary": set([]), + "detail": set(["d0", "d1", "d2"]) + } + }, + "C2": { + "S0": { + "summary": set(["s0"]), + "detail": set(["d0", "d1"]) + } + } + }) + +class HealthHistoryTest(unittest.TestCase): + def _now(self): + # return some time truncated at 30 minutes past the hour. this lets us + # fiddle with time offsets without worrying about accidentically landing + # on exactly the top of the hour which is the edge of a time slot for + # tracking health history. + dt = datetime.datetime.utcnow() + return datetime.datetime( + year = dt.year, + month = dt.month, + day = dt.day, + hour = dt.hour, + minute = 30) + + def test_empty_slot(self): + now = self._now() + + HealthHistorySlot._now = mock.Mock(return_value=now) + h = HealthHistorySlot() + + # reports no historical checks + self.assertEqual(h.health(), { "checks": {} }) + + # an empty slot doesn't need to be flushed + self.assertFalse(h.need_flush()) + + def test_expires(self): + now = self._now() + + HealthHistorySlot._now = mock.Mock(return_value=now) + h = HealthHistorySlot() + self.assertFalse(h.expired()) + + # an hour from now it would be expired + future = now + datetime.timedelta(hours = 1) + HealthHistorySlot._now = mock.Mock(return_value=future) + self.assertTrue(h.expired()) + + def test_need_flush(self): + now = self._now() + + HealthHistorySlot._now = mock.Mock(return_value=now) + h = HealthHistorySlot() + self.assertFalse(h.need_flush()) + + self.assertTrue(h.add(dict(checks = { + "C0": { + "severity": "S0", + "summary": { "message": "s0" }, + "detail": [{ "message": "d0" }] + } + }))) + # no flush needed, yet... + self.assertFalse(h.need_flush()) + + # after persist period time elapses, a flush is needed + future = now + PERSIST_PERIOD + HealthHistorySlot._now = mock.Mock(return_value=future) + self.assertTrue(h.need_flush()) + + # mark flush resets + h.mark_flushed() + self.assertFalse(h.need_flush()) + + def test_need_flush_edge(self): + # test needs flush is true because it has expired, not because it has + # been dirty for the persistence period + dt = datetime.datetime.utcnow() + now = datetime.datetime( + year = dt.year, + month = dt.month, + day = dt.day, + hour = dt.hour, + minute = 59, + second = 59) + HealthHistorySlot._now = mock.Mock(return_value=now) + h = HealthHistorySlot() + self.assertFalse(h.expired()) + self.assertFalse(h.need_flush()) + + # now it is dirty, but it doesn't need a flush + self.assertTrue(h.add(dict(checks = { + "C0": { + "severity": "S0", + "summary": { "message": "s0" }, + "detail": [{ "message": "d0" }] + } + }))) + self.assertFalse(h.expired()) + self.assertFalse(h.need_flush()) + + # advance time past the hour so it expires, but not past the persistence + # period deadline for the last event that set the dirty bit + self.assertTrue(PERSIST_PERIOD.total_seconds() > 5) + future = now + datetime.timedelta(seconds = 5) + HealthHistorySlot._now = mock.Mock(return_value=future) + + self.assertTrue(h.expired()) + self.assertTrue(h.need_flush()) diff --git a/src/pybind/mgr/insights/tox.ini b/src/pybind/mgr/insights/tox.ini new file mode 100644 index 0000000000000..989a8bc3cc5fb --- /dev/null +++ b/src/pybind/mgr/insights/tox.ini @@ -0,0 +1,16 @@ +[tox] +envlist = py27,py3 +skipsdist = true +toxworkdir = {env:CEPH_BUILD_DIR} +minversion = 2.8.1 + +[testenv] +deps = + pytest + mock +setenv= + UNITTEST = true + py27: PYTHONPATH = {toxinidir}/../../../../build/lib/cython_modules/lib.2 + py3: PYTHONPATH = {toxinidir}/../../../../build/lib/cython_modules/lib.3 +commands= + {envbindir}/py.test tests/ diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 28c5dc213cc62..670cc544ca770 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -605,6 +605,11 @@ if(WITH_MGR) list(APPEND tox_tests run-tox-mgr-dashboard) set(MGR_DASHBOARD_VIRTUALENV ${CEPH_BUILD_VIRTUALENV}/mgr-dashboard-virtualenv) list(APPEND env_vars_for_tox_tests MGR_DASHBOARD_VIRTUALENV=${MGR_DASHBOARD_VIRTUALENV}) + + add_test(NAME run-tox-mgr-insights COMMAND bash ${CMAKE_SOURCE_DIR}/src/pybind/mgr/insights/run-tox.sh) + list(APPEND tox_tests run-tox-mgr-insights) + set(MGR_INSIGHTS_VIRTUALENV ${CEPH_BUILD_VIRTUALENV}/mgr-insights-virtualenv) + list(APPEND env_vars_for_tox_tests MGR_INSIGHTS_VIRTUALENV=${MGR_INSIGHTS_VIRTUALENV}) endif() set_property( -- 2.39.5