Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add/extend some K8s-related utilities. #26

Merged
merged 5 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions k8s_test_harness/util/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

K8S_DAEMONSET = "daemonset.apps"
K8S_DEPLOYMENT = "deployment.apps"
K8S_STATEFULSET = "statefulset.apps"

K8S_CONDITION_AVAILABLE = "Available"
K8S_CONDITION_READY = "Ready"
80 changes: 74 additions & 6 deletions k8s_test_harness/util/k8s_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# See LICENSE file for licensing details
#

import functools
import itertools
import json
import logging
Expand Down Expand Up @@ -32,6 +33,27 @@ def purge_k8s_snap(instance: harness.Instance):
instance.exec(["sudo", "snap", "remove", "k8s", "--purge"])


def describe_resources_on_error(resource_type: str):
def _decorator(fun):
@functools.wraps(fun)
def _inner(instance: harness.Instance, *args, **kwargs):
try:
return fun(instance, *args, **kwargs)
except Exception:
proc = instance.exec(
["k8s", "kubectl", "describe", resource_type], capture_output=True
)
LOG.info(
f"### All current '{resource_type}' definitions: "
f"{proc.stdout.decode()}"
)
raise

return _inner

return _decorator


# Validates that the K8s node is in Ready state.
def wait_until_k8s_ready(
control_node: harness.Instance, instances: List[harness.Instance]
Expand Down Expand Up @@ -129,9 +151,11 @@ def wait_for_resource(
name: str,
namespace: str = constants.K8S_NS_DEFAULT,
condition: str = constants.K8S_CONDITION_AVAILABLE,
retry_times: int = 5,
retry_delay_s: int = 60,
):
"""Waits for the given resource to reach the given condition."""
exec_util.stubbornly(retries=5, delay_s=1).on(instance).exec(
exec_util.stubbornly(retries=retry_times, delay_s=retry_delay_s).on(instance).exec(
[
"k8s",
"kubectl",
Expand All @@ -142,26 +166,44 @@ def wait_for_resource(
resource_type,
name,
"--timeout",
"60s",
"1s",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this should be 1min I guess.

Copy link
Contributor Author

@aznashwan aznashwan Jul 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have “shifted” the wait from the ‘kubectl’ call to the retry function itself via the default values for the kwarga, with the total wait time still amounting to more or less 1minute.

The ‘--timeout’ kwarg to ‘kubectl’ is still required though, otherwise we risk it waiting indefinitely…

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with the total wait time still amounting to more or less 1minute.

Actually it just hit me that the original was about 5 minutes, updated the default kwargs to be about 5minutes again.

]
)


@describe_resources_on_error("pods")
@describe_resources_on_error("deployment")
def wait_for_deployment(
instance: harness.Instance,
name: str,
namespace: str = constants.K8S_NS_DEFAULT,
condition: str = constants.K8S_CONDITION_AVAILABLE,
retry_times: int = 5,
retry_delay_s: int = 60,
):
"""Waits for the given deployment to reach the given condition."""
wait_for_resource(instance, constants.K8S_DEPLOYMENT, name, namespace, condition)
wait_for_resource(
instance,
constants.K8S_DEPLOYMENT,
name,
namespace=namespace,
condition=condition,
retry_times=retry_times,
retry_delay_s=retry_delay_s,
)


@describe_resources_on_error("pods")
@describe_resources_on_error("daemonsets")
def wait_for_daemonset(
instance: harness.Instance, name: str, namespace: str = constants.K8S_NS_DEFAULT
instance: harness.Instance,
name: str,
namespace: str = constants.K8S_NS_DEFAULT,
retry_times: int = 5,
retry_delay_s: int = 60,
):
"""Waits for the given daemonset to become available."""
exec_util.stubbornly(retries=5, delay_s=1).on(instance).exec(
exec_util.stubbornly(retries=retry_times, delay_s=retry_delay_s).on(instance).exec(
[
"k8s",
"kubectl",
Expand All @@ -172,7 +214,33 @@ def wait_for_daemonset(
constants.K8S_DAEMONSET,
name,
"--timeout",
"60s",
"1s",
]
)


@describe_resources_on_error("pods")
@describe_resources_on_error("statefulsets")
def wait_for_statefulset(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably just make this a wait_for_resource in the future. Let's leave it like this for now (as we would need to update all Rocks).

instance: harness.Instance,
name: str,
namespace: str = constants.K8S_NS_DEFAULT,
retry_times: int = 5,
retry_delay_s: int = 60,
):
"""Waits for the given daemonset to become available."""
exec_util.stubbornly(retries=retry_times, delay_s=retry_delay_s).on(instance).exec(
[
"k8s",
"kubectl",
"rollout",
"status",
"--namespace",
namespace,
constants.K8S_STATEFULSET,
name,
"--timeout",
"1s",
]
)

Expand Down
12 changes: 5 additions & 7 deletions k8s_test_harness/util/platform_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import platform


ROCKCRAFT_PLATFORM_AMD64 = "amd64"
ROCKCRAFT_PLATFORM_I386 = "i386"
ROCKCRAFT_PLATFORM_ARM64 = "arm64"
Expand All @@ -17,12 +16,12 @@
_PYTHON_MACHINE_TO_ROCKCRAFT_PLATFORM_ARCHITECTURE_MAP = {
"x86": ROCKCRAFT_PLATFORM_I386,
"x86_64": ROCKCRAFT_PLATFORM_AMD64,
"arm64": ROCKCRAFT_PLATFORM_ARM64
"arm64": ROCKCRAFT_PLATFORM_ARM64,
}


def get_current_rockcraft_platform_architecture() -> str:
""" Returns a string containing the rockcraft-specific platform
"""Returns a string containing the rockcraft-specific platform
architecture label of the currently running process.

https://documentation.ubuntu.com/rockcraft/en/latest/reference/rockcraft.yaml/#platforms
Expand All @@ -33,13 +32,12 @@ def get_current_rockcraft_platform_architecture() -> str:

machine = platform.machine()
if not machine:
raise OSError(
"Failed to get current platform through `platform.machine()`.")
raise OSError("Failed to get current platform through `platform.machine()`.")

if machine not in _PYTHON_MACHINE_TO_ROCKCRAFT_PLATFORM_ARCHITECTURE_MAP:
raise ValueError(
f"Unknown platform machine type '{machine}'. Known values are: "
f"{list(_PYTHON_MACHINE_TO_ROCKCRAFT_PLATFORM_ARCHITECTURE_MAP)}")
f"{list(_PYTHON_MACHINE_TO_ROCKCRAFT_PLATFORM_ARCHITECTURE_MAP)}"
)

return _PYTHON_MACHINE_TO_ROCKCRAFT_PLATFORM_ARCHITECTURE_MAP[machine]

Loading