diff --git a/README.md b/README.md index e5f079e..f2ca0c1 100644 --- a/README.md +++ b/README.md @@ -14,10 +14,4 @@ If you add a new API, be careful to add ```request.header.origin``` as cacheKey, ## Deploying -Deploys are automated using Github Actions and Flux. - -To deploy to the `testnet` environment, simply commit to the `main` branch. - -To deploy to the `mainnet` environment, create a release in Github using a tag in the format `v0.0.0` - -Currently we do not have an automated mechanism to be warned if some automated deployment fails. So, after triggering the deploy, you should check if a commit was made by `fluxcdbot` in https://github.com/HathorNetwork/ops-tools/commits/master, updating the project's manifests with the new Docker image tag. \ No newline at end of file +See in the [SOP](https://github.com/HathorNetwork/ops-tools/blob/master/docs/sops/hathor-explorer-service.md#deployment). diff --git a/gateways/clients/hathor_core_client.py b/gateways/clients/hathor_core_client.py index 8f2ec5f..78b7522 100644 --- a/gateways/clients/hathor_core_client.py +++ b/gateways/clients/hathor_core_client.py @@ -24,6 +24,7 @@ TX_ACC_WEIGHT_ENDPOINT = "/v1a/transaction_acc_weight" VERSION_ENDPOINT = "/v1a/version" FEATURE_ENDPOINT = "/v1a/feature" +HEALTH_ENDPOINT = "/v1a/health" class HathorCoreAsyncClient: diff --git a/gateways/healthcheck_gateway.py b/gateways/healthcheck_gateway.py index 6d8631a..2d8703a 100644 --- a/gateways/healthcheck_gateway.py +++ b/gateways/healthcheck_gateway.py @@ -3,7 +3,7 @@ from common.configuration import ELASTIC_INDEX from gateways.clients.cache_client import CacheClient from gateways.clients.elastic_search_client import ElasticSearchClient -from gateways.clients.hathor_core_client import VERSION_ENDPOINT, HathorCoreAsyncClient +from gateways.clients.hathor_core_client import HEALTH_ENDPOINT, HathorCoreAsyncClient from gateways.clients.wallet_service_db_client import WalletServiceDBClient # The default lambda timeout for the Healtcheck Lambda is set to @@ -32,11 +32,11 @@ def __init__( wallet_service_db_client or WalletServiceDBClient() ) - async def get_hathor_core_version(self) -> Optional[dict]: - """Retrieve hathor-core version information""" + async def get_hathor_core_health(self) -> Optional[dict]: + """Retrieve hathor-core health information""" return await self.hathor_core_async_client.get( - VERSION_ENDPOINT, timeout=HEALTHCHECK_CLIENT_TIMEOUT_IN_SECONDS + HEALTH_ENDPOINT, timeout=HEALTHCHECK_CLIENT_TIMEOUT_IN_SECONDS ) def ping_redis(self) -> bool: diff --git a/package-lock.json b/package-lock.json index cf3168b..fd4877d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hathor-explorer-service", - "version": "0.13.0", + "version": "0.14.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "hathor-explorer-service", - "version": "0.13.0", + "version": "0.14.0", "license": "MIT", "dependencies": { "@apidevtools/swagger-cli": "^4.0.4", diff --git a/package.json b/package.json index b08ca4b..a10be04 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hathor-explorer-service", - "version": "0.13.0", + "version": "0.14.0", "description": "Hathor Explorer Service Serverless deps", "dependencies": { "@apidevtools/swagger-cli": "^4.0.4", diff --git a/pyproject.toml b/pyproject.toml index f678d71..940066f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "hathor-explorer-service" -version = "0.13.0" +version = "0.14.0" description = "" authors = ["Hathor Labs "] license = "MIT" diff --git a/tests/unit/gateways/test_healthcheck_gateway.py b/tests/unit/gateways/test_healthcheck_gateway.py index 6ef9ca8..3adb31f 100644 --- a/tests/unit/gateways/test_healthcheck_gateway.py +++ b/tests/unit/gateways/test_healthcheck_gateway.py @@ -18,15 +18,15 @@ def setUp(self): wallet_service_db_client=self.wallet_service_db_client, ) - async def test_get_hathor_core_version(self): - async def mock_get_hathor_core_version(endpoint, **kwargs): - return {"version": "0.39.0"} + async def test_get_hathor_core_health(self): + async def mock_get_hathor_core_health(endpoint, **kwargs): + return {"status": "pass"} - self.hathor_core_async_client.get.side_effect = mock_get_hathor_core_version - result = await self.healthcheck_gateway.get_hathor_core_version() - self.assertEqual(result, {"version": "0.39.0"}) + self.hathor_core_async_client.get.side_effect = mock_get_hathor_core_health + result = await self.healthcheck_gateway.get_hathor_core_health() + self.assertEqual(result, {"status": "pass"}) self.hathor_core_async_client.get.assert_called_once_with( - "/v1a/version", timeout=5 + "/v1a/health", timeout=5 ) def test_ping_redis(self): diff --git a/tests/unit/usecases/test_healthcheck.py b/tests/unit/usecases/test_healthcheck.py index acc1ba6..fa59d36 100644 --- a/tests/unit/usecases/test_healthcheck.py +++ b/tests/unit/usecases/test_healthcheck.py @@ -18,11 +18,11 @@ def setUp(self): ) def test_all_components_healthy(self): - async def mock_get_hathor_core_version(): - return {"version": "0.38.0"} + async def mock_get_hathor_core_health(): + return {"status": "pass"} - self.mock_healthcheck_gateway.get_hathor_core_version.side_effect = ( - mock_get_hathor_core_version + self.mock_healthcheck_gateway.get_hathor_core_health.side_effect = ( + mock_get_hathor_core_health ) self.mock_healthcheck_gateway.ping_wallet_service_db.return_value = (True, "1") self.mock_healthcheck_gateway.ping_redis.return_value = True @@ -80,11 +80,11 @@ async def mock_get_hathor_core_version(): ) def test_hathor_core_returns_error(self): - async def mock_get_hathor_core_version(): + async def mock_get_hathor_core_health(): return {"error": "Unable to connect"} - self.mock_healthcheck_gateway.get_hathor_core_version.side_effect = ( - mock_get_hathor_core_version + self.mock_healthcheck_gateway.get_hathor_core_health.side_effect = ( + mock_get_hathor_core_health ) self.mock_healthcheck_gateway.ping_wallet_service_db.return_value = (True, "1") self.mock_healthcheck_gateway.ping_redis.return_value = True @@ -142,11 +142,11 @@ async def mock_get_hathor_core_version(): ) def test_wallet_service_db_raises_exception(self): - async def mock_get_hathor_core_version(): - return {"version": "0.38.0"} + async def mock_get_hathor_core_health(): + return {"status": "pass"} - self.mock_healthcheck_gateway.get_hathor_core_version.side_effect = ( - mock_get_hathor_core_version + self.mock_healthcheck_gateway.get_hathor_core_health.side_effect = ( + mock_get_hathor_core_health ) self.mock_healthcheck_gateway.ping_wallet_service_db.side_effect = Exception( "Unable to connect" @@ -206,11 +206,11 @@ async def mock_get_hathor_core_version(): ) def test_wallet_service_db_reports_unhealthy(self): - async def mock_get_hathor_core_version(): - return {"version": "0.38.0"} + async def mock_get_hathor_core_health(): + return {"status": "pass"} - self.mock_healthcheck_gateway.get_hathor_core_version.side_effect = ( - mock_get_hathor_core_version + self.mock_healthcheck_gateway.get_hathor_core_health.side_effect = ( + mock_get_hathor_core_health ) self.mock_healthcheck_gateway.ping_wallet_service_db.return_value = ( False, @@ -271,11 +271,11 @@ async def mock_get_hathor_core_version(): ) def test_redis_raises_exception(self): - async def mock_get_hathor_core_version(): - return {"version": "0.38.0"} + async def mock_get_hathor_core_health(): + return {"status": "pass"} - self.mock_healthcheck_gateway.get_hathor_core_version.side_effect = ( - mock_get_hathor_core_version + self.mock_healthcheck_gateway.get_hathor_core_health.side_effect = ( + mock_get_hathor_core_health ) self.mock_healthcheck_gateway.ping_wallet_service_db.return_value = (True, "1") self.mock_healthcheck_gateway.ping_redis.side_effect = Exception( @@ -335,11 +335,11 @@ async def mock_get_hathor_core_version(): ) def test_redis_reports_unhealthy(self): - async def mock_get_hathor_core_version(): - return {"version": "0.38.0"} + async def mock_get_hathor_core_health(): + return {"status": "pass"} - self.mock_healthcheck_gateway.get_hathor_core_version.side_effect = ( - mock_get_hathor_core_version + self.mock_healthcheck_gateway.get_hathor_core_health.side_effect = ( + mock_get_hathor_core_health ) self.mock_healthcheck_gateway.ping_wallet_service_db.return_value = (True, "1") self.mock_healthcheck_gateway.ping_redis.return_value = False @@ -397,11 +397,11 @@ async def mock_get_hathor_core_version(): ) def test_elasticsearch_raises_exception(self): - async def mock_get_hathor_core_version(): - return {"version": "0.38.0"} + async def mock_get_hathor_core_health(): + return {"status": "pass"} - self.mock_healthcheck_gateway.get_hathor_core_version.side_effect = ( - mock_get_hathor_core_version + self.mock_healthcheck_gateway.get_hathor_core_health.side_effect = ( + mock_get_hathor_core_health ) self.mock_healthcheck_gateway.ping_wallet_service_db.return_value = (True, "1") self.mock_healthcheck_gateway.ping_redis.return_value = True @@ -459,11 +459,11 @@ async def mock_get_hathor_core_version(): ) def test_elasticsearch_reports_unhealthy(self): - async def mock_get_hathor_core_version(): - return {"version": "0.38.0"} + async def mock_get_hathor_core_health(): + return {"status": "pass"} - self.mock_healthcheck_gateway.get_hathor_core_version.side_effect = ( - mock_get_hathor_core_version + self.mock_healthcheck_gateway.get_hathor_core_health.side_effect = ( + mock_get_hathor_core_health ) self.mock_healthcheck_gateway.ping_wallet_service_db.return_value = (True, "1") self.mock_healthcheck_gateway.ping_redis.return_value = True @@ -521,11 +521,11 @@ async def mock_get_hathor_core_version(): ) def test_elasticsearch_report_yellow(self): - async def mock_get_hathor_core_version(): - return {"version": "0.38.0"} + async def mock_get_hathor_core_health(): + return {"status": "pass"} - self.mock_healthcheck_gateway.get_hathor_core_version.side_effect = ( - mock_get_hathor_core_version + self.mock_healthcheck_gateway.get_hathor_core_health.side_effect = ( + mock_get_hathor_core_health ) self.mock_healthcheck_gateway.ping_wallet_service_db.return_value = (True, "1") self.mock_healthcheck_gateway.ping_redis.return_value = True diff --git a/usecases/get_healthcheck.py b/usecases/get_healthcheck.py index d57daf7..5a2a9ac 100644 --- a/usecases/get_healthcheck.py +++ b/usecases/get_healthcheck.py @@ -6,6 +6,7 @@ HealthcheckCallbackResponse, HealthcheckDatastoreComponent, HealthcheckHTTPComponent, + HealthcheckStatus, ) from common.configuration import ( @@ -54,18 +55,33 @@ def __init__( self.healthcheck.add_component(component) async def _get_fullnode_health(self): - # TODO: We need to use the hathor-core's /health endpoint when it's available - version_response = await self.healthcheck_gateway.get_hathor_core_version() + health_response = await self.healthcheck_gateway.get_hathor_core_health() - if "error" in version_response: + if "error" in health_response: return HealthcheckCallbackResponse( - status="fail", - output=f"Fullnode healthcheck errored: {version_response['error']}", + status=HealthcheckStatus.FAIL, + output=f"Fullnode healthcheck errored: {health_response['error']}", + ) + + status = health_response["status"] + + # Here we're assuming that a 'warn' status will be considered as unhealthy + is_healthy = status == HealthcheckStatus.PASS + is_unhealthy = status in [HealthcheckStatus.FAIL, HealthcheckStatus.WARN] + + if is_unhealthy: + output = f"Fullnode is not healthy: {str(health_response)}" + elif is_healthy: + output = "Fullnode is healthy" + else: + status = HealthcheckStatus.FAIL + output = ( + f"Fullnode returned an unexpected health status: {str(health_response)}" ) return HealthcheckCallbackResponse( - status="pass", - output="Fullnode is healthy", + status=status, + output=output, ) async def _get_wallet_service_db_health(self): @@ -73,12 +89,12 @@ async def _get_wallet_service_db_health(self): is_healthy, output = self.healthcheck_gateway.ping_wallet_service_db() except Exception as e: return HealthcheckCallbackResponse( - status="fail", + status=HealthcheckStatus.FAIL, output=f"Wallet service DB healthcheck errored: {repr(e)}", ) return HealthcheckCallbackResponse( - status="pass" if is_healthy else "fail", + status=HealthcheckStatus.PASS if is_healthy else HealthcheckStatus.FAIL, output="Wallet service DB is healthy" if is_healthy else f"Wallet service DB didn't respond as expected: {output}", @@ -89,12 +105,12 @@ async def _get_redis_health(self): is_healthy = self.healthcheck_gateway.ping_redis() except Exception as e: return HealthcheckCallbackResponse( - status="fail", + status=HealthcheckStatus.FAIL, output=f"Redis healthcheck errored: {repr(e)}", ) return HealthcheckCallbackResponse( - status="pass" if is_healthy else "fail", + status=HealthcheckStatus.PASS if is_healthy else HealthcheckStatus.FAIL, output="Redis is healthy" if is_healthy else "Redis reported as unhealthy", ) @@ -103,13 +119,13 @@ async def _get_elasticsearch_health(self): elasticsearch_info = self.healthcheck_gateway.get_elasticsearch_health() except Exception as e: return HealthcheckCallbackResponse( - status="fail", + status=HealthcheckStatus.FAIL, output=f"Elasticsearch healthcheck errored: {repr(e)}", ) if elasticsearch_info["status"] == "red": return HealthcheckCallbackResponse( - status="fail", + status=HealthcheckStatus.FAIL, output=f"Elasticsearch is not healthy: {str(elasticsearch_info)}", ) if elasticsearch_info["status"] == "yellow": @@ -119,7 +135,7 @@ async def _get_elasticsearch_health(self): ) return HealthcheckCallbackResponse( - status="pass", + status=HealthcheckStatus.PASS, output=str(elasticsearch_info), )