Skip to content

Commit 176da48

Browse files
authored
feat(client): add heartbeat as client API function (#63)
* WIP: heartbeat * tests(client): added tests for client * docs(client): added docstring Signed-off-by: Michael Schilonka <[email protected]>
1 parent e6594c1 commit 176da48

File tree

5 files changed

+132
-0
lines changed

5 files changed

+132
-0
lines changed

client/beiboot/api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
from .delete import * # noqa
44
from .list import * # noqa
55
from .read import * # noqa
6+
from .heartbeat import * # noqa

client/beiboot/api/heartbeat.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import logging
2+
from datetime import datetime
3+
from typing import Optional
4+
from beiboot.configuration import ClientConfiguration, default_configuration
5+
import kubernetes as k8s
6+
7+
from beiboot.api import stopwatch
8+
from beiboot.types import Beiboot
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
@stopwatch
14+
def write_heartbeat(
15+
client_id: str,
16+
bbt: Beiboot,
17+
timestamp: Optional[datetime] = None,
18+
config: ClientConfiguration = default_configuration,
19+
) -> datetime:
20+
"""
21+
Create a client contact entry to the heartbeat configmap for a Beiboot. Returns the timestamp written to the
22+
configmap.
23+
24+
:param client_id: The client id to write the contact entry on behalf of
25+
:type client_id: str
26+
:param bbt: The Beiboot to write a heartbeat for
27+
:type bbt: Beiboot
28+
:param timestamp: Optional timestamp to write to the configmap
29+
:type timestamp: datetime
30+
"""
31+
32+
if timestamp is None:
33+
timestamp = datetime.utcnow()
34+
35+
_timestamp = timestamp.isoformat()
36+
configmap = k8s.client.V1ConfigMap( # type: ignore
37+
api_version="v1",
38+
kind="ConfigMap",
39+
data={client_id: _timestamp},
40+
metadata=k8s.client.V1ObjectMeta( # type: ignore
41+
name=config.CLIENT_HEARTBEAT_CONFIGMAP_NAME,
42+
namespace=bbt.namespace,
43+
),
44+
)
45+
try:
46+
config.K8S_CORE_API.patch_namespaced_config_map(
47+
name=config.CLIENT_HEARTBEAT_CONFIGMAP_NAME,
48+
namespace=bbt.namespace,
49+
body=configmap,
50+
)
51+
logger.debug(f"Successfully heartbeat for client {client_id} to {_timestamp}")
52+
except k8s.client.exceptions.ApiException as e: # type: ignore
53+
if e.status == 404:
54+
raise RuntimeError(
55+
f"Cannot write heartbeat, the required configmap '{config.CLIENT_HEARTBEAT_CONFIGMAP_NAME}' does not exist"
56+
) from None
57+
else:
58+
raise RuntimeError(f"Cannot write heartbeat: {e}") from None
59+
60+
return timestamp

client/beiboot/configuration.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def __init__(
2525
):
2626
self.NAMESPACE = namespace
2727
self.CONFIGMAP_NAME = "beiboot-config"
28+
self.CLIENT_HEARTBEAT_CONFIGMAP_NAME = "beiboot-clients"
2829
self.REGISTRY_URL = (
2930
registry_url.rstrip("/") if registry_url else "quay.io/getdeck"
3031
)

client/tests/e2e/test_heartbeat.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
from datetime import datetime
2+
import json
3+
4+
from beiboot import api
5+
from beiboot.types import BeibootRequest, BeibootParameters
6+
from tests.e2e.base import TestClientBase
7+
8+
from beiboot.types import BeibootState
9+
from beiboot.configuration import default_configuration
10+
11+
12+
class TestBaseSetup(TestClientBase):
13+
14+
beiboot_name = "mycluster-hb"
15+
16+
def test_write_heartbeat(self, operator, kubectl, timeout):
17+
18+
req = BeibootRequest(
19+
name=self.beiboot_name,
20+
parameters=BeibootParameters(
21+
nodes=1,
22+
serverStorageRequests="500Mi",
23+
serverResources={"requests": {"cpu": "0.25", "memory": "0.25Gi"}},
24+
nodeResources={"requests": {"cpu": "0.25", "memory": "0.25Gi"}},
25+
),
26+
)
27+
bbt = api.create(req)
28+
29+
bbt.wait_for_state(awaited_state=BeibootState.PENDING, timeout=timeout)
30+
31+
timestamp = datetime.utcnow()
32+
33+
api.write_heartbeat("test-client", bbt, timestamp)
34+
configmap = kubectl(
35+
[
36+
"-n",
37+
bbt.namespace,
38+
"get",
39+
"configmap",
40+
default_configuration.CLIENT_HEARTBEAT_CONFIGMAP_NAME,
41+
"--output",
42+
"json",
43+
]
44+
)
45+
cm = json.loads(configmap)
46+
assert "test-client" in cm["data"].keys()
47+
assert cm["data"]["test-client"] == str(timestamp.isoformat())
48+
49+
api.write_heartbeat("test-client1", bbt)
50+
configmap = kubectl(
51+
[
52+
"-n",
53+
bbt.namespace,
54+
"get",
55+
"configmap",
56+
default_configuration.CLIENT_HEARTBEAT_CONFIGMAP_NAME,
57+
"--output",
58+
"json",
59+
]
60+
)
61+
cm = json.loads(configmap)
62+
assert "test-client1" in cm["data"].keys()
63+
assert datetime.fromisoformat(cm["data"]["test-client1"])

client/tests/unit/test_api.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
from datetime import datetime
23

34
from beiboot import api
45
from beiboot.types import BeibootRequest
@@ -46,3 +47,9 @@ def test_d_read_events(self, operator):
4647
beiboot = api.read("test-read-all")
4748
events = beiboot.events_by_timestamp
4849
assert type(events) == dict
50+
51+
def test_e_write_heartbeat(self, operator, kubectl):
52+
bbt = api.read(name="test-read")
53+
timestamp = datetime.utcnow()
54+
with pytest.raises(RuntimeError):
55+
api.write_heartbeat("test-client", bbt, timestamp)

0 commit comments

Comments
 (0)