diff --git a/poetry.lock b/poetry.lock index 09885cc..3cc1bf5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -662,13 +662,13 @@ files = [ [[package]] name = "hathorlib" -version = "0.5.1" +version = "0.6.0" description = "Hathor Network base objects library" optional = false python-versions = ">=3.9,<4" files = [ - {file = "hathorlib-0.5.1-py3-none-any.whl", hash = "sha256:0725d7e7b833dc465b4961e7157caaabaa3f0e0e4c98c51e5a40160905218f01"}, - {file = "hathorlib-0.5.1.tar.gz", hash = "sha256:efb41a27a0e0f8b9a0c5d61cb3f82dbb935b2494429bbb0ba4da673c8dd7255d"}, + {file = "hathorlib-0.6.0-py3-none-any.whl", hash = "sha256:9c86716b39e277713531b892db2f07351bacba9b5e113daa1a58f98092414987"}, + {file = "hathorlib-0.6.0.tar.gz", hash = "sha256:5e953a22d597f6942505ee08941cc315246ef11a14d10c06c0a79dfe1c2944ea"}, ] [package.dependencies] @@ -1105,13 +1105,13 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale [[package]] name = "python-healthchecklib" -version = "0.1.0" +version = "0.1.1" description = "Opinionated healthcheck library" optional = false python-versions = ">=3.8.1,<4.0.0" files = [ - {file = "python_healthchecklib-0.1.0-py3-none-any.whl", hash = "sha256:95d94fcae7f281adf16624014ae789dfa38d1be327cc38b02ee82bad70671f2f"}, - {file = "python_healthchecklib-0.1.0.tar.gz", hash = "sha256:afa0572d37902c50232d99acf0065836082bb027109c9c98e8d5acfefd381595"}, + {file = "python_healthchecklib-0.1.1-py3-none-any.whl", hash = "sha256:51ad9e7e782145977bf322cbe2095198a8b61473b09d43e79018e47483840d15"}, + {file = "python_healthchecklib-0.1.1.tar.gz", hash = "sha256:bac6cdd9ef5825f6deb0cbe5f6d97260f3f402e111fc7fe2146444bdb77fd892"}, ] [[package]] @@ -1254,4 +1254,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = ">=3.9,<4.0" -content-hash = "17792bf63dca989709da600a8906d6485d953d58e343414104b7c552647af82b" +content-hash = "cf8d792bc3861ef6bbd1b6b6c6fbcfa9fb3cb0e20b66598ff5247528214c3375" diff --git a/pyproject.toml b/pyproject.toml index e6080da..ac4fe09 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ structlog = "~22.3.0" prometheus-client = "^0.9.0" idna_ssl = "^1.1.0" asynctest = "^0.13.0" -hathorlib = {version = "^0.5.1", extras = ["client"]} +hathorlib = {version = "^0.6.0", extras = ["client"]} dataclasses = {version = "^0.8", python = ">=3.6,<3.7"} python-healthchecklib = "^0.1.0" diff --git a/tests/test_healthcheck.py b/tests/test_healthcheck.py index 2cf7199..23e5087 100644 --- a/tests/test_healthcheck.py +++ b/tests/test_healthcheck.py @@ -13,8 +13,8 @@ class HathorClientMock: _base_url = "http://localhost:8080" - async def version(self): - return {"version": "1.0.0"} + async def health(self): + return {"status": "pass"} class TestFullnodeHealthCheck(asynctest.TestCase): # type: ignore[misc] @@ -26,26 +26,54 @@ def setUp(self) -> None: async def test_get_health_check_with_a_healthy_fullnode(self): """Test the response we should generated for a healthy fullnode""" - # Mock the implementation of the hathor_client.version. + # Mock the implementation of the hathor_client.health. async def side_effect(): - return {"version": "1.0.0"} + return {"status": "pass"} - self.mock_hathor_client.version.side_effect = side_effect + self.mock_hathor_client.health.side_effect = side_effect self.mock_hathor_client._base_url = "http://localhost:8080" result = await self.fullnode_health_check.get_health_check() self.assertEqual(result.status, "pass") - self.assertEqual(result.output, "Fullnode is responding correctly") + self.assertEqual(result.output, "Fullnode is healthy") - async def test_get_health_check_with_an_unhealthy_fullnode(self): + async def test_get_health_check_raises_exception(self): """Test the response we should generated for an unhealthy fullnode""" - self.mock_hathor_client.version.side_effect = Exception("error") + self.mock_hathor_client.health.side_effect = Exception("error") self.mock_hathor_client._base_url = "http://localhost:8080" result = await self.fullnode_health_check.get_health_check() self.assertEqual(result.status, "fail") self.assertEqual(result.output, "Couldn't connect to fullnode: error") + async def test_get_health_check_invalid_status(self): + """Test the response we should generated for an unhealthy fullnode""" + # Mock the implementation of the hathor_client.health. + async def side_effect(): + return {"status": "invalid"} + + self.mock_hathor_client.health.side_effect = side_effect + self.mock_hathor_client._base_url = "http://localhost:8080" + + result = await self.fullnode_health_check.get_health_check() + self.assertEqual(result.status, "fail") + self.assertEqual( + result.output, "Fullnode returned invalid status: {'status': 'invalid'}" + ) + + async def test_get_health_check_unhealthy_fullnode(self): + """Test the response we should generated for an unhealthy fullnode""" + # Mock the implementation of the hathor_client.health. + async def side_effect(): + return {"status": "fail"} + + self.mock_hathor_client.health.side_effect = side_effect + self.mock_hathor_client._base_url = "http://localhost:8080" + + result = await self.fullnode_health_check.get_health_check() + self.assertEqual(result.status, "fail") + self.assertEqual(result.output, "Fullnode is not healthy: {'status': 'fail'}") + class TestMiningHealthCheck(asynctest.TestCase): # type: ignore[misc] def setUp(self): @@ -157,12 +185,12 @@ def setUp(self): async def test_get_health_check_success(self): """Tests the response we should generate when everything is ok""" - # Mock the implementation of the hathor_client.version. + # Mock the implementation of the hathor_client.health. async def side_effect(): - return {"version": "1.0.0"} + return {"status": "pass"} - self.mock_hathor_client.version = MagicMock() - self.mock_hathor_client.version.side_effect = side_effect + self.mock_hathor_client.health = MagicMock() + self.mock_hathor_client.health.side_effect = side_effect self.mock_hathor_client._base_url = "http://localhost:8080" @@ -184,15 +212,13 @@ async def side_effect(): ) self.assertEqual(result.checks["fullnode"][0].component_name, "fullnode") self.assertEqual(result.checks["fullnode"][0].component_type, "http") - self.assertEqual( - result.checks["fullnode"][0].output, "Fullnode is responding correctly" - ) + self.assertEqual(result.checks["fullnode"][0].output, "Fullnode is healthy") self.assertEqual(result.status, HealthcheckStatus.PASS) async def test_get_health_check_fullnode_failure(self): """Tests the response we should generate when the fullnode is unhealthy""" - self.mock_hathor_client.version = MagicMock() - self.mock_hathor_client.version.side_effect = Exception("error") + self.mock_hathor_client.health = MagicMock() + self.mock_hathor_client.health.side_effect = Exception("error") self.mock_hathor_client._base_url = "http://localhost:8080" self.mock_manager.has_any_miner.return_value = True @@ -213,12 +239,12 @@ async def test_get_health_check_fullnode_failure(self): async def test_get_health_check_mining_failure(self): """Tests the response we should generate when the mining is unhealthy""" - # Mock the implementation of the hathor_client.version. + # Mock the implementation of the hathor_client.health. async def side_effect(): - return {"version": "1.0.0"} + return {"status": "pass"} - self.mock_hathor_client.version = MagicMock() - self.mock_hathor_client.version.side_effect = side_effect + self.mock_hathor_client.health = MagicMock() + self.mock_hathor_client.health.side_effect = side_effect self.mock_hathor_client._base_url = "http://localhost:8080" self.mock_manager.has_any_miner.return_value = True @@ -231,7 +257,5 @@ async def side_effect(): "No miners submitted a job in the last 1 hour", ) self.assertEqual(result.checks["fullnode"][0].status, HealthcheckStatus.PASS) - self.assertEqual( - result.checks["fullnode"][0].output, "Fullnode is responding correctly" - ) + self.assertEqual(result.checks["fullnode"][0].output, "Fullnode is healthy") self.assertEqual(result.status, HealthcheckStatus.FAIL) diff --git a/txstratum/healthcheck/healthcheck.py b/txstratum/healthcheck/healthcheck.py index f0a5e53..5c8e297 100644 --- a/txstratum/healthcheck/healthcheck.py +++ b/txstratum/healthcheck/healthcheck.py @@ -71,21 +71,29 @@ def __init__(self, backend: "HathorClient") -> None: async def get_health_check(self) -> HealthcheckCallbackResponse: """Return the fullnode health check status.""" - response = HealthcheckCallbackResponse( - status=HealthcheckStatus.PASS, - output="Fullnode is responding correctly", - ) - try: - # TODO: We need to get the health information from the fullnode, but it's not implemented yet - await self.backend.version() - - return response + health = await self.backend.health() + + if health["status"] in [HealthcheckStatus.FAIL, HealthcheckStatus.WARN]: + return HealthcheckCallbackResponse( + status=HealthcheckStatus(health["status"]), + output="Fullnode is not healthy: %s" % str(health), + ) + elif health["status"] == HealthcheckStatus.PASS: + return HealthcheckCallbackResponse( + status=HealthcheckStatus.PASS, + output="Fullnode is healthy", + ) + else: + return HealthcheckCallbackResponse( + status=HealthcheckStatus.FAIL, + output="Fullnode returned invalid status: %s" % str(health), + ) except Exception as e: - response.status = HealthcheckStatus.FAIL - response.output = f"Couldn't connect to fullnode: {str(e)}" - - return response + return HealthcheckCallbackResponse( + status=HealthcheckStatus.FAIL, + output=f"Couldn't connect to fullnode: {str(e)}", + ) class MiningHealthCheck(ComponentHealthCheckInterface):