Skip to content

Commit

Permalink
Merge pull request #17 from olxbr/feature/improve-healthcheck
Browse files Browse the repository at this point in the history
Generate json for healthcheck
  • Loading branch information
dmvieira authored Mar 16, 2020
2 parents ab6605c + 9e8f054 commit f50984b
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 56 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ check-sec:
@bandit -r .

rabbitmq:
@docker run --rm --name barterdude_rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.7.24-management-alpine
@docker run -d --rm --name barterdude_rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.7.24-management-alpine
39 changes: 21 additions & 18 deletions barterdude/hooks/healthcheck.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json

from barterdude import BarterDude
from barterdude.hooks import HttpHook
from asyncworker.rabbitmq.message import RabbitMQMessage
Expand All @@ -14,6 +16,11 @@ def _remove_old(instants: deque, old_timestamp: float):
return len(instants)


def _response(status, body):
body["status"] = "ok" if status == 200 else "fail"
return web.Response(status=status, body=json.dumps(body))


class Healthcheck(HttpHook):
def __init__(
self,
Expand Down Expand Up @@ -43,26 +50,22 @@ async def on_fail(self, message: RabbitMQMessage, error: Exception):

async def __call__(self, req: web.Request):
if self.__force_fail:
return web.Response(
body="Healthcheck fail called manually",
status=500
)
return _response(500, {
"message": "Healthcheck fail called manually"
})

old_timestamp = time() - self.__health_window
success = _remove_old(self.__success, old_timestamp)
fail = _remove_old(self.__fail, old_timestamp)
if success == 0 and fail == 0:
return web.Response(
body="No messages until now",
status=200
)
return _response(200, {
"message": f"No messages in last {self.__health_window}s"
})

rate = success / (success + fail)
if rate >= self.__success_rate:
return web.Response(
body=f"Bater like a pro! Success rate: {rate}",
status=200
)
else:
return web.Response(
body=f"Success rate {rate} bellow {self.__success_rate}",
status=500
)
return _response(200 if rate >= self.__success_rate else 500, {
"message":
f"Success rate: {rate} (expected: {self.__success_rate})",
"fail": fail,
"success": success
})
40 changes: 23 additions & 17 deletions tests/test_hooks/test_healthcheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

@freeze_time()
class TestHealthcheck(TestCase):
maxDiff = None

def setUp(self):
self.success_rate = 0.9
self.health_window = 60.0
Expand All @@ -22,16 +24,20 @@ async def test_should_pass_healthcheck_when_no_messages(self):
response = await self.healthcheck(Mock())
self.assertEqual(response.status, 200)
self.assertEqual(response.content_type, "text/plain")
self.assertEqual(response.body._value, b"No messages until now")
self.assertEqual(
response.body._value.decode('utf-8'),
'{"message": "No messages in last 60.0s", "status": "ok"}'
)

async def test_should_pass_healthcheck_when_only_sucess(self):
await self.healthcheck.on_success(None)
response = await self.healthcheck(Mock())
self.assertEqual(response.status, 200)
self.assertEqual(response.content_type, "text/plain")
self.assertEqual(
response.body._value,
b"Bater like a pro! Success rate: 1.0"
response.body._value.decode('utf-8'),
'{"message": "Success rate: 1.0 (expected: 0.9)", '
'"fail": 0, "success": 1, "status": "ok"}'
)

async def test_should_pass_healthcheck_when_success_rate_is_high(self):
Expand All @@ -42,8 +48,9 @@ async def test_should_pass_healthcheck_when_success_rate_is_high(self):
self.assertEqual(response.status, 200)
self.assertEqual(response.content_type, "text/plain")
self.assertEqual(
response.body._value,
b"Bater like a pro! Success rate: 0.9"
response.body._value.decode('utf-8'),
'{"message": "Success rate: 0.9 (expected: 0.9)", '
'"fail": 1, "success": 9, "status": "ok"}'
)

async def test_should_fail_healthcheck_when_only_fail(self):
Expand All @@ -52,8 +59,9 @@ async def test_should_fail_healthcheck_when_only_fail(self):
self.assertEqual(response.status, 500)
self.assertEqual(response.content_type, "text/plain")
self.assertEqual(
response.body._value,
bytes(f"Success rate 0.0 bellow {self.success_rate}", "utf-8")
response.body._value.decode('utf-8'),
'{"message": "Success rate: 0.0 (expected: 0.9)", '
'"fail": 1, "success": 0, "status": "fail"}'
)

async def test_should_fail_healthcheck_when_success_rate_is_low(self):
Expand All @@ -63,8 +71,9 @@ async def test_should_fail_healthcheck_when_success_rate_is_low(self):
self.assertEqual(response.status, 500)
self.assertEqual(response.content_type, "text/plain")
self.assertEqual(
response.body._value,
bytes(f"Success rate 0.5 bellow {self.success_rate}", "utf-8")
response.body._value.decode('utf-8'),
'{"message": "Success rate: 0.5 (expected: 0.9)", '
'"fail": 1, "success": 1, "status": "fail"}'
)

async def test_should_fail_when_force_fail_is_called(self):
Expand All @@ -74,8 +83,8 @@ async def test_should_fail_when_force_fail_is_called(self):
self.assertEqual(response.status, 500)
self.assertEqual(response.content_type, "text/plain")
self.assertEqual(
response.body._value,
b"Healthcheck fail called manually"
response.body._value.decode('utf-8'),
'{"message": "Healthcheck fail called manually", "status": "fail"}'
)

async def test_should_erase_old_messages(self):
Expand All @@ -85,11 +94,8 @@ async def test_should_erase_old_messages(self):
await self.healthcheck.on_fail(None, None)
await self.healthcheck.on_success(None)
response = await self.healthcheck(Mock())
rate = 1 / (1 + (self.health_window - tick) // tick)
self.assertEqual(
response.body._value,
bytes(
f"Success rate {rate} bellow {self.success_rate}",
"utf-8"
)
response.body._value.decode('utf-8'),
'{"message": "Success rate: 0.125 (expected: 0.9)", '
'"fail": 7, "success": 1, "status": "fail"}'
)
31 changes: 11 additions & 20 deletions tests_integration/test_rabbitmq_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ async def setUp(self):
self.app = BarterDude(hostname=self.rabbitmq_host)

async def tearDown(self):
await self.app.shutdown()
await self.queue_manager.connection.channel.queue_delete(
self.input_queue
)
Expand Down Expand Up @@ -89,8 +90,6 @@ async def handler(message):
for message in self.messages:
self.assertTrue(message["key"] in received_messages)

await self.app.shutdown()

async def test_process_messages_successfully_even_with_crashed_hook(self):
received_messages = set()

Expand All @@ -108,8 +107,6 @@ async def handler(message):
for message in self.messages:
self.assertTrue(message["key"] in received_messages)

await self.app.shutdown()

async def test_process_one_message_and_publish(self):
@self.app.consume_amqp([self.input_queue], coroutines=1)
async def forward(message):
Expand All @@ -132,7 +129,6 @@ async def handler(message):
)
await asyncio.sleep(1)
self.assertEquals(received_message, self.messages[1])
await self.app.shutdown()

async def test_process_message_requeue_with_requeue(self):
handler_called = 0
Expand All @@ -151,7 +147,6 @@ async def handler(messages):
)
await asyncio.sleep(1)
self.assertEquals(handler_called, 2)
await self.app.shutdown()

async def test_process_message_reject_with_requeue(self):
handler_called = 0
Expand All @@ -170,7 +165,6 @@ async def handler(messages):
)
await asyncio.sleep(1)
self.assertEquals(handler_called, 1)
await self.app.shutdown()

async def test_process_message_reject_without_requeue(self):
handler_called = 0
Expand All @@ -188,7 +182,6 @@ async def handler(message):
)
await asyncio.sleep(1)
self.assertEquals(handler_called, 1)
await self.app.shutdown()

async def test_process_messages_and_requeue_only_one(self):
first_read = set()
Expand Down Expand Up @@ -221,8 +214,6 @@ async def handler(message):

self.assertSetEqual(second_read, {self.messages[0]["key"]})

await self.app.shutdown()

async def test_obtains_healthcheck(self):
monitor = Monitor(Healthcheck(self.app))

Expand All @@ -245,9 +236,11 @@ async def handler(message):
text = await response.text()

self.assertEquals(status_code, 200)
self.assertEquals(text, "Bater like a pro! Success rate: 1.0")

await self.app.shutdown()
self.assertEquals(
text,
'{"message": "Success rate: 1.0 (expected: 0.95)", '
'"fail": 0, "success": 1, "status": "ok"}'
)

async def test_obtains_healthcheck_even_with_crashed_hook(self):
monitor = Monitor(ErrorHook(), Healthcheck(self.app))
Expand All @@ -271,9 +264,11 @@ async def handler(message):
text = await response.text()

self.assertEquals(status_code, 200)
self.assertEquals(text, "Bater like a pro! Success rate: 1.0")

await self.app.shutdown()
self.assertEquals(
text,
'{"message": "Success rate: 1.0 (expected: 0.95)", '
'"fail": 0, "success": 1, "status": "ok"}'
)

async def test_obtains_prometheus_metrics(self):
labels = {"app_name": "barterdude_consumer"}
Expand Down Expand Up @@ -302,8 +297,6 @@ async def handler(message):
'barterdude_received_number_before_consume_messages_total'
'{app_name="barterdude_consumer"} 1.0'))

await self.app.shutdown()

async def test_register_multiple_prometheus_hooks(self):
"""This test raised the following error:
ValueError: Duplicated timeseries in CollectorRegistry"""
Expand Down Expand Up @@ -347,5 +340,3 @@ async def handler(message):
self.assertIn("'delivery_tag': 1", cm.output[1])
self.assertIn(f"'exception': \"{error_str}\"", cm.output[1])
self.assertIn("'traceback': [", cm.output[1])

await self.app.shutdown()

0 comments on commit f50984b

Please sign in to comment.