import datetime
# freq to write cached state to disk
-PERSIST_PERIOD = datetime.timedelta(seconds = 10)
+PERSIST_PERIOD = datetime.timedelta(seconds=10)
# on disk key prefix
HEALTH_HISTORY_KEY_PREFIX = "health_history/"
# apply on offset to "now": used for testing
NOW_OFFSET = None
+
class HealthEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, set):
return list(obj)
return json.JSONEncoder.default(self, obj)
+
class HealthCheckAccumulator(object):
"""
Deuplicated storage of health checks.
"""
- def __init__(self, init_checks = None):
+
+ def __init__(self, init_checks=None):
# check : severity : { summary, detail }
# summary and detail are deduplicated
self._checks = defaultdict(lambda:
- defaultdict(lambda: {
- "summary": set(),
- "detail": set()
- }))
+ defaultdict(lambda: {
+ "summary": set(),
+ "detail": set()
+ }))
if init_checks:
self._update(init_checks)
return changed
+
class HealthHistorySlot(object):
"""
Manage the life cycle of a health history time slot.
need_flush returns true. Once the state has been flushed, reset the dirty
bit by calling mark_flushed.
"""
- def __init__(self, init_health = dict()):
+
+ def __init__(self, init_health=dict()):
self._checks = HealthCheckAccumulator(init_health.get("checks"))
self._slot = self._curr_slot()
self._next_flush = None
self.key(), self._next_flush, self._checks)
def health(self):
- return dict(checks = self._checks.checks())
+ return dict(checks=self._checks.checks())
def key(self):
"""Identifier in the persist store"""
def key_range(hours):
"""Return the time slot keys for the past N hours"""
def inner(curr, hours):
- slot = curr - datetime.timedelta(hours = hours)
+ slot = curr - datetime.timedelta(hours=hours)
return HealthHistorySlot._key(slot)
curr = HealthHistorySlot._curr_slot()
return map(lambda i: inner(curr, i), range(hours))
"""Slot for the current UTC time"""
dt = HealthHistorySlot._now()
return datetime.datetime(
- year = dt.year,
- month = dt.month,
- day = dt.day,
- hour = dt.hour)
+ year=dt.year,
+ month=dt.month,
+ day=dt.day,
+ hour=dt.hour)
# build store data entry
slot = self._health_slot.health()
assert "version" not in slot
- slot.update(dict(version = ON_DISK_VERSION))
+ slot.update(dict(version=ON_DISK_VERSION))
data = json.dumps(slot, cls=health_util.HealthEncoder)
self.log.debug("Storing health key {} data {}".format(
def _health_prune_history(self, hours):
"""Prune old health entries"""
- cutoff = datetime.datetime.utcnow() - datetime.timedelta(hours = hours)
+ cutoff = datetime.datetime.utcnow() - datetime.timedelta(hours=hours)
for key in self._health_filter(lambda ts: ts <= cutoff):
self.log.info("Removing old health slot key {}".format(key))
self.set_store(key, None)
collector.merge(self._health_slot)
return dict(
- current = json.loads(self.get("health")["json"]),
- history = collector.health()
+ current=json.loads(self.get("health")["json"]),
+ history=collector.health()
)
def _version_parse(self, version):
for s in ['osd.numpg', 'osd.stat_bytes', 'osd.stat_bytes_used']:
osd['stats'][s.split('.')[1]] = self.get_latest('osd', str(osd["osd"]), s)
-
def _config_dump(self):
"""Report cluster configuration
configuration defaults; these can be inferred from the version number.
"""
result = CommandResult("")
- args = dict(prefix = "config dump", format = "json")
+ args = dict(prefix="config dump", format="json")
self.send_command(result, "mon", "", json.dumps(args), "")
ret, outb, outs = result.wait()
if ret == 0:
report = {}
report.update({
- "version": dict(full = self.version,
- **self._version_parse(self.version))
+ "version": dict(full=self.version,
+ **self._version_parse(self.version))
})
# crash history
from tests import mock
from ..health import *
+
class HealthChecksTest(unittest.TestCase):
def test_check_accum_empty(self):
# health checks accum initially empty reports empty
self.assertFalse(h.add({
"C0": {
"severity": "S0",
- "summary": { "message": "s0" },
+ "summary": {"message": "s0"},
"detail": []
}
}))
self.assertFalse(h.add({
"C0": {
"severity": "S0",
- "summary": { "message": "s1" },
- "detail": [{ "message": "d1" }]
+ "summary": {"message": "s1"},
+ "detail": [{"message": "d1"}]
}
}))
self.assertFalse(h.add({
"C0": {
"severity": "S0",
- "summary": { "message": "s0" },
- "detail": [{ "message": "d1" }, { "message": "d2" }]
+ "summary": {"message": "s0"},
+ "detail": [{"message": "d1"}, {"message": "d2"}]
}
}))
self.assertTrue(h.add({
"C0": {
"severity": "S0",
- "summary": { "message": "s3" },
+ "summary": {"message": "s3"},
"detail": []
}
}))
self.assertTrue(h.add({
"C0": {
"severity": "S0",
- "summary": { "message": "s1" },
- "detail": [{ "message": "d4" }]
+ "summary": {"message": "s1"},
+ "detail": [{"message": "d4"}]
}
}))
self.assertTrue(h.add({
"C0": {
"severity": "S2",
- "summary": { "message": "s0" },
- "detail": [{ "message": "d0" }]
+ "summary": {"message": "s0"},
+ "detail": [{"message": "d0"}]
}
}))
self.assertTrue(h.add({
"C2": {
"severity": "S0",
- "summary": { "message": "s0" },
- "detail": [{ "message": "d0" }, { "message": "d1" }]
+ "summary": {"message": "s0"},
+ "detail": [{"message": "d0"}, {"message": "d1"}]
}
}))
}
})
+
class HealthHistoryTest(unittest.TestCase):
def _now(self):
# return some time truncated at 30 minutes past the hour. this lets us
# tracking health history.
dt = datetime.datetime.utcnow()
return datetime.datetime(
- year = dt.year,
- month = dt.month,
- day = dt.day,
- hour = dt.hour,
- minute = 30)
+ year=dt.year,
+ month=dt.month,
+ day=dt.day,
+ hour=dt.hour,
+ minute=30)
def test_empty_slot(self):
now = self._now()
h = HealthHistorySlot()
# reports no historical checks
- self.assertEqual(h.health(), { "checks": {} })
+ self.assertEqual(h.health(), {"checks": {}})
# an empty slot doesn't need to be flushed
self.assertFalse(h.need_flush())
self.assertFalse(h.expired())
# an hour from now it would be expired
- future = now + datetime.timedelta(hours = 1)
+ future = now + datetime.timedelta(hours=1)
HealthHistorySlot._now = mock.Mock(return_value=future)
self.assertTrue(h.expired())
h = HealthHistorySlot()
self.assertFalse(h.need_flush())
- self.assertTrue(h.add(dict(checks = {
+ self.assertTrue(h.add(dict(checks={
"C0": {
"severity": "S0",
- "summary": { "message": "s0" },
- "detail": [{ "message": "d0" }]
+ "summary": {"message": "s0"},
+ "detail": [{"message": "d0"}]
}
})))
# no flush needed, yet...
# 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)
+ 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 = {
+ self.assertTrue(h.add(dict(checks={
"C0": {
"severity": "S0",
- "summary": { "message": "s0" },
- "detail": [{ "message": "d0" }]
+ "summary": {"message": "s0"},
+ "detail": [{"message": "d0"}]
}
})))
self.assertFalse(h.expired())
# 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)
+ future = now + datetime.timedelta(seconds=5)
HealthHistorySlot._now = mock.Mock(return_value=future)
self.assertTrue(h.expired())