diff --git a/chaosk8s/daemonset/__init__.py b/chaosk8s/daemonset/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chaosk8s/daemonset/probes.py b/chaosk8s/daemonset/probes.py new file mode 100644 index 0000000..e357f33 --- /dev/null +++ b/chaosk8s/daemonset/probes.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +from typing import Union +import urllib3 +from chaoslib.types import Secrets +from chaoslib.exceptions import ActivityFailed +from kubernetes import client +from logzero import logger +from chaosk8s import create_k8s_api_client + +__all__ = ["daemonset_ready"] + + +def daemonset_ready(name: str, ns: str = "default", + label_selector: str = "name in ({name})", + timeout: int = 30, + secrets: Secrets = None): + """ + Check that the DaemonSet has no misscheduled or unavailable pods + + Raises :exc:`chaoslib.exceptions.ActivityFailed` when the state is not + as expected. + """ + api = create_k8s_api_client(secrets) + + v1 = client.AppsV1Api(api) + + ret = v1.list_namespaced_daemon_set(ns, label_selector=label_selector) + logger.debug("Found {d} daemonsets".format(d=len(ret.items))) + + if not ret.items: + raise ActivityFailed( + "DaemonSet '{name}' was not found".format(name=name)) + + for ds in ret.items: + logger.debug("DaemonSet has '{u}' unavailable replicas and \ + '{m}' misscheduled".format( + u=ds.status.number_unavailable, + m=ds.status.number_misscheduled)) + if ( + (ds.status.number_unavailable is not None + and ds.status.number_unavailable != 0) + or ds.status.number_misscheduled != 0 + ): + raise ActivityFailed( + "DaemonSet has '{u}' unavailable replicas " + "and '{m}' misscheduled".format( + u=ds.status.number_unavailable, + m=ds.status.number_misscheduled)) + return True diff --git a/tests/test_daemonset.py b/tests/test_daemonset.py new file mode 100644 index 0000000..6afbe76 --- /dev/null +++ b/tests/test_daemonset.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +from unittest.mock import MagicMock, patch, ANY, call + +import pytest +from kubernetes import stream +from chaoslib.exceptions import ActivityFailed, InvalidActivity +from chaoslib.provider.python import validate_python_activity + +from chaosk8s.daemonset.probes import daemonset_ready + + +@patch('chaosk8s.has_local_config_file', autospec=True) +@patch('chaosk8s.daemonset.probes.client', autospec=True) +@patch('chaosk8s.client') +def test_daemonset_ready(cl, client, has_conf): + has_conf.return_value = False + + ds = MagicMock() + ds.status.number_unavailable = None + ds.status.number_misscheduled = 0 + result = MagicMock() + result.items = [ds,ds] + + v1 = MagicMock() + v1.list_namespaced_daemon_set.return_value = result + client.AppsV1Api.return_value = v1 + + assert daemonset_ready("test") + + +@patch('chaosk8s.has_local_config_file', autospec=True) +@patch('chaosk8s.daemonset.probes.client', autospec=True) +@patch('chaosk8s.client') +def test_daemonset_not_ready(cl, client, has_conf): + has_conf.return_value = False + + ds = MagicMock() + ds.status.number_unavailable = 1 + ds.status.number_misscheduled = 0 + result = MagicMock() + result.items = [ds,ds] + + v1 = MagicMock() + v1.list_namespaced_daemon_set.return_value = result + client.AppsV1Api.return_value = v1 + + with pytest.raises(ActivityFailed) as excinfo: + daemonset_ready("test") + assert "DaemonSet has '1' unavailable replicas and '0' misscheduled" in str(excinfo.value) + +@patch('chaosk8s.has_local_config_file', autospec=True) +@patch('chaosk8s.daemonset.probes.client', autospec=True) +@patch('chaosk8s.client') +def test_daemonset_ready_not_found(cl, client, has_conf): + has_conf.return_value = False + result = MagicMock() + result.items = [] + + v1 = MagicMock() + v1.list_namespaced_daemon_set.return_value = result + client.AppsV1Api.return_value = v1 + + name = "test" + with pytest.raises(ActivityFailed) as excinfo: + daemonset_ready(name) + assert "DaemonSet '{name}' was not found".format(name=name) in str(excinfo.value) \ No newline at end of file