Skip to content

Commit

Permalink
Update kubernetes probes (#50)
Browse files Browse the repository at this point in the history
* Update kubernetes probes and create version endpoint.

* Remove hot version test.

* Fix version endpoint.
  • Loading branch information
everaldorodrigo authored Sep 11, 2024
1 parent 7e69e66 commit 45ceca2
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 39 deletions.
10 changes: 9 additions & 1 deletion biothings_annotator/application/views/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .annotator import StatusView, BatchCurieView, CurieView, TrapiView, CurieLegacyView, TrapiLegacyView
from .annotator import StatusView, VersionView, BatchCurieView, CurieView, TrapiView, CurieLegacyView, TrapiLegacyView


def build_routes() -> list[dict]:
Expand All @@ -20,6 +20,13 @@ def build_routes() -> list[dict]:
"name": "status_endpoint",
}

# --- VERSION ROUTE ---
version_route_main = {
"handler": VersionView.as_view(),
"uri": r"/version",
"name": "version_endpoint",
}

# --- CURIE ROUTES ---
curie_route_main = {
"handler": CurieView.as_view(),
Expand All @@ -45,6 +52,7 @@ def build_routes() -> list[dict]:

route_collection = [
status_route_main,
version_route_main,
curie_route_main,
curie_route_mirror,
batch_curie_route,
Expand Down
33 changes: 32 additions & 1 deletion biothings_annotator/application/views/annotator.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

import logging
import json
import pathlib
import git

import sanic
from sanic.views import HTTPMethodView
from sanic.exceptions import SanicException
from sanic.request import Request
from sanic import response

Expand Down Expand Up @@ -43,6 +44,36 @@ async def get(self, _: Request):
return sanic.json(result)


class VersionView(HTTPMethodView):
def __init__(self):
super().__init__()
application = sanic.Sanic.get_app()
cache = application.config.CACHE_MAX_AGE
self.default_headers = {"Cache-Control": f"max-age={cache}, public"}

async def get(self, _: Request):
try:
# Resolve the absolute path to the current file
file_path = pathlib.Path(__file__).resolve()

# Use git.Repo to find the root of the repository
repo = git.Repo(file_path, search_parent_directories=True)

if repo.bare:
repo_dir = repo.working_tree_dir
logger.error(f"Git repository not found in directory: {repo_dir}")
result = {"version": "Unknown"}
return sanic.json(result)

commit_hash = repo.head.commit.hexsha # Get the latest commit hash
result = {"version": commit_hash}
return sanic.json(result, headers=self.default_headers)
except Exception as exc:
logger.error(f"Error getting GitHub commit hash: {exc}")
result = {"version": "Unknown"}
return sanic.json(result)


class CurieView(HTTPMethodView):
def __init__(self):
super().__init__()
Expand Down
38 changes: 31 additions & 7 deletions deploy/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ metadata:
{{- include "biothings-annotator.labels" . | nindent 4 }}
spec:
strategy:
type: {{ .Values.deployment.strategyType }}
type: {{ .Values.deployment.strategy.type }}
{{- if eq .Values.deployment.strategy.type "RollingUpdate" }}
rollingUpdate:
maxUnavailable: {{ .Values.deployment.strategy.rollingUpdate.maxUnavailable | default 1 }}
maxSurge: {{ .Values.deployment.strategy.rollingUpdate.maxSurge | default 1 }}
{{- end }}
replicas: {{ .Values.deployment.replicaCount }}
selector:
matchLabels:
Expand Down Expand Up @@ -34,14 +39,33 @@ spec:
- name: http
containerPort: {{ .Values.containers.port }}
protocol: TCP
livenessProbe:
startupProbe: # To determine if a container application has started successfully.
httpGet:
path: /
port: http
readinessProbe:
path: /status
port: {{ .Values.containers.port }}
initialDelaySeconds: 10 # The number of seconds to wait after the container has started before performing the first startup probe.
periodSeconds: 15 # How often (in seconds) to perform the startup probe.
timeoutSeconds: 10 # The number of seconds after which the probe times out.
successThreshold: 1 # The number of consecutive successes required to consider the container started successfully.
failureThreshold: 5 # The number of consecutive failures required to consider the container startup to have failed.
readinessProbe: # To determine when the container is ready to start accepting traffic
httpGet:
path: /
port: http
path: /status
port: {{ .Values.containers.port }}
initialDelaySeconds: 20 # The number of seconds to wait after the container has started before performing the first readiness probe.
periodSeconds: 20 # How often (in seconds) to perform the readiness probe.
timeoutSeconds: 10 # The number of seconds after which the probe times out.
successThreshold: 1 # The number of consecutive successes required to consider the container ready after it has been failing.
failureThreshold: 3 # The number of consecutive failures required to consider the container not ready.
livenessProbe: # To determine if a container is still running
httpGet:
path: /status
port: {{ .Values.containers.port }}
initialDelaySeconds: 60 # The number of seconds to wait after the container has started before performing the first liveness probe.
periodSeconds: 60 # How often (in seconds) to perform the liveness probe.
timeoutSeconds: 30 # The number of seconds after which the probe times out.
successThreshold: 1 # The number of consecutive successes required to consider the container healthy after it has been failing.
failureThreshold: 3 # The number of consecutive failures required to consider the container unhealthy and restart it.
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
Expand Down
27 changes: 0 additions & 27 deletions deploy/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,33 +23,6 @@ containers:
name: biothingsannotator
es_host: ES_HOST_VALUE
port: 9000
startupProbe: # To determine if a container application has started successfully.
httpGet:
path: /status
port: 9000
initialDelaySeconds: 10 # The number of seconds to wait after the container has started before performing the first startup probe.
periodSeconds: 15 # How often (in seconds) to perform the startup probe.
timeoutSeconds: 10 # The number of seconds after which the probe times out.
successThreshold: 1 # The number of consecutive successes required to consider the container started successfully.
failureThreshold: 5 # The number of consecutive failures required to consider the container startup to have failed.
readinessProbe: # To determine when the container is ready to start accepting traffic
httpGet:
path: /status
port: 9000
initialDelaySeconds: 20 # The number of seconds to wait after the container has started before performing the first readiness probe.
periodSeconds: 20 # How often (in seconds) to perform the readiness probe.
timeoutSeconds: 10 # The number of seconds after which the probe times out.
successThreshold: 1 # The number of consecutive successes required to consider the container ready after it has been failing.
failureThreshold: 3 # The number of consecutive failures required to consider the container not ready.
livenessProbe: # To determine if a container is still running
httpGet:
path: /status
port: 9000
initialDelaySeconds: 60 # The number of seconds to wait after the container has started before performing the first liveness probe.
periodSeconds: 60 # How often (in seconds) to perform the liveness probe.
timeoutSeconds: 30 # The number of seconds after which the probe times out.
successThreshold: 1 # The number of consecutive successes required to consider the container healthy after it has been failing.
failureThreshold: 3 # The number of consecutive failures required to consider the container unhealthy and restart it.

env:
OPENTELEMETRY_ENABLED_VALUE: True
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ dependencies = [
"brotli >= 1.1.0",
"biothings_client >= 0.3.1",
"sanic[ext] >= 23.12.1",
"GitPython >= 3.1.43",
]

[project.optional-dependencies]
Expand Down
88 changes: 85 additions & 3 deletions tests/test_application_endpoints.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import json

import pytest
import git
import re
import sanic
from typing import Union

from biothings_annotator import utils
from biothings_annotator.annotator import Annotator
from unittest.mock import patch
from unittest.mock import patch, PropertyMock


@pytest.mark.parametrize("endpoint", ["/status/"])
Expand Down Expand Up @@ -84,6 +84,88 @@ def test_status_get_failed_data_check(test_annotator: sanic.Sanic, endpoint: str
assert response.json == expected_response_body


@pytest.mark.parametrize("endpoint", ["/version/"])
def test_version_get_success(test_annotator: sanic.Sanic, endpoint: str):
"""
Tests the Version endpoint GET for a successful case
Mocking git.Repo to return a valid commit hash
"""
with patch.object(git.Repo, "head", create=True) as mock_repo_head, \
patch.object(git.Repo, "bare", new_callable=PropertyMock, return_value=False), \
patch.object(git.Repo, "__init__", lambda *args, **kwargs: None):

mock_repo_head.commit.hexsha = "abc123"

request, response = test_annotator.test_client.request(endpoint, http_method="get")

assert request.method == "GET"
assert request.query_string == ""
assert request.scheme == "http"
assert request.server_path == endpoint

expected_response_body = {"version": "abc123"}
assert response.http_version == "HTTP/1.1"
assert response.content_type == "application/json"
assert response.is_success
assert not response.is_error
assert response.is_closed
assert response.status_code == 200
assert response.encoding == "utf-8"
assert response.json == expected_response_body


@pytest.mark.parametrize("endpoint", ["/version/"])
def test_version_get_bare_repo(test_annotator: sanic.Sanic, endpoint: str):
"""
Tests the Version endpoint GET when the repository is bare (missing repository)
Mocking git.Repo to simulate a bare repository
"""
with patch.object(git.Repo, "bare", new_callable=PropertyMock, return_value=True), \
patch.object(git.Repo, "__init__", lambda *args, **kwargs: None):

request, response = test_annotator.test_client.request(endpoint, http_method="get")

assert request.method == "GET"
assert request.query_string == ""
assert request.scheme == "http"
assert request.server_path == endpoint

expected_response_body = {"version": "Unknown"}
assert response.http_version == "HTTP/1.1"
assert response.content_type == "application/json"
assert response.is_success
assert not response.is_error
assert response.is_closed
assert response.status_code == 200
assert response.encoding == "utf-8"
assert response.json == expected_response_body


@pytest.mark.parametrize("endpoint", ["/version/"])
def test_version_get_exception(test_annotator: sanic.Sanic, endpoint: str):
"""
Tests the Version endpoint GET when an Exception is raised
Mocking git.Repo to raise an exception
"""
with patch.object(git.Repo, "__init__", side_effect=Exception("Simulated error")):
request, response = test_annotator.test_client.request(endpoint, http_method="get")

assert request.method == "GET"
assert request.query_string == ""
assert request.scheme == "http"
assert request.server_path == endpoint

expected_response_body = {"version": "Unknown"}
assert response.http_version == "HTTP/1.1"
assert response.content_type == "application/json"
assert response.is_success
assert not response.is_error
assert response.is_closed
assert response.status_code == 200
assert response.encoding == "utf-8"
assert response.json == expected_response_body


def test_curie_get(test_annotator: sanic.Sanic):
"""
Tests the CURIE endpoint GET
Expand Down

0 comments on commit 45ceca2

Please sign in to comment.