Add new waiter (#306)

Add new waiter

SUMMARY

This refactors the waiter logic from common.py into a separate module.

ISSUE TYPE

COMPONENT NAME

ADDITIONAL INFORMATION

Reviewed-by: None <None>
Reviewed-by: Alina Buzachis <None>
Reviewed-by: None <None>
This commit is contained in:
Mike Graves
2021-12-13 11:06:13 -05:00
parent 7fb89a7b6f
commit f168a3f67f
6 changed files with 463 additions and 11 deletions

View File

@@ -1,11 +1,34 @@
---
kind: Namespace
apiVersion: v1
metadata:
name: test-1
---
kind: Pod
apiVersion: v1
metadata:
name: foo
namespace: bar
name: pod-1
namespace: test-1
spec:
containers:
- image: busybox
name: busybox
---
kind: ConfigMap
kind: PodList
apiVersion: v1
metadata:
name: foo
namespace: bar
metadata: {}
items:
- kind: Pod
apiVersion: v1
metadata:
name: pod-1
namespace: test-1
spec:
containers:
- image: busybox
name: busybox
---
kind: ConfigMapList
apiVersion: v1
metadata: {}
items: []

View File

@@ -0,0 +1,48 @@
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: deploy-1
namespace: test-1
generation: 1
spec:
replicas: 2
selector:
matchLabels:
app: foo
template:
metadata:
labels:
app: foo
spec:
containers:
- image: busybox
name: busybox
status:
availableReplicas: 2
replicas: 2
observedGeneration: 1
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: deploy-2
namespace: test-1
generation: 1
spec:
replicas: 2
selector:
matchLabels:
app: foo
template:
metadata:
labels:
app: foo
spec:
containers:
- image: busybox
name: busybox
status:
availableReplicas: 1
replicas: 2
observedGeneration: 1

View File

@@ -0,0 +1,63 @@
---
kind: Pod
apiVersion: v1
metadata:
namespace: test-1
name: pod-1
spec:
containers:
- image: busybox
name: busybox
status:
containerStatuses:
- name: busybox
ready: true
conditions:
- type: "www.example.com/gate"
status: "True"
---
kind: Pod
apiVersion: v1
metadata:
namespace: test-1
name: pod-2
spec:
containers:
- image: busybox
name: busybox
---
kind: Pod
apiVersion: v1
metadata:
namespace: test-1
name: pod-3
spec:
containers:
- image: busybox
name: busybox
status:
phase: Pending
conditions:
- type: "www.example.com/gate"
status: "Unknown"
containerStatuses:
- name: busybox
ready: true
---
kind: Pod
apiVersion: v1
metadata:
namespace: test-1
name: pod-4
spec:
containers:
- image: busybox
name: busybox
status:
phase: Pending
conditions:
- type: "www.example.com/other"
status: "Unknown"
containerStatuses:
- name: busybox
ready: true

View File

@@ -30,9 +30,9 @@ def test_create_definitions_loads_from_file():
current = Path(os.path.dirname(os.path.abspath(__file__)))
params = {"src": current / "fixtures/definitions.yml"}
results = create_definitions(params)
assert len(results) == 2
assert results[0].kind == "Pod"
assert results[1].kind == "ConfigMap"
assert len(results) == 3
assert results[0].kind == "Namespace"
assert results[1].kind == "Pod"
def test_create_definitions_loads_from_params():
@@ -160,8 +160,8 @@ def test_from_yaml_loads_dictionary():
def test_from_file_loads_definitions():
current = Path(os.path.dirname(os.path.abspath(__file__)))
result = list(from_file(current / "fixtures/definitions.yml"))
assert result[0]["kind"] == "Pod"
assert result[1]["kind"] == "ConfigMap"
assert result[0]["kind"] == "Namespace"
assert result[1]["kind"] == "Pod"
def test_flatten_list_kind_flattens():

View File

@@ -0,0 +1,114 @@
import os
import time
from pathlib import Path
from unittest.mock import Mock
import pytest
import yaml
from kubernetes.dynamic.resource import ResourceInstance
from kubernetes.dynamic.exceptions import NotFoundError
from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import (
clock,
custom_condition,
deployment_ready,
DummyWaiter,
exists,
get_waiter,
pod_ready,
resource_absent,
Waiter,
)
def resources(filepath):
current = Path(os.path.dirname(os.path.abspath(__file__)))
with open(current / filepath) as fp:
return [ResourceInstance(None, d) for d in yaml.safe_load_all(fp)]
RESOURCES = resources("fixtures/definitions.yml")
PODS = resources("fixtures/pods.yml")
DEPLOYMENTS = resources("fixtures/deployments.yml")
def test_clock_times_out():
start = time.monotonic()
for x in clock(5, 1):
pass
elapsed = int(time.monotonic() - start)
assert x == 5
assert 5 <= elapsed <= 6
@pytest.mark.parametrize(
"resource,expected",
zip(RESOURCES + [None, {}], [True, True, True, False, False, False]),
)
def test_exists_and_absent_checks_for_existence(resource, expected):
assert exists(resource) is expected
assert resource_absent(resource) is not expected
@pytest.mark.parametrize("pod,expected", zip(PODS, [True, False, True, True]))
def test_pod_ready_checks_readiness(pod, expected):
assert pod_ready(pod) is expected
@pytest.mark.parametrize("pod,expected", zip(PODS, [True, False, False, False]))
def test_custom_condition_checks_readiness(pod, expected):
condition = {"type": "www.example.com/gate", "status": "True"}
assert custom_condition(condition, pod) is expected
@pytest.mark.parametrize("deployment,expected", zip(DEPLOYMENTS, [True, False]))
def test_deployment_ready_checks_readiness(deployment, expected):
assert deployment_ready(deployment) is expected
def test_dummywaiter_returns_resource_immediately():
resource = {
"kind": "Pod",
"apiVersion": "v1",
"metadata": {"name": "foopod", "namespace": "foobar"},
}
result, instance, elapsed = DummyWaiter().wait(resource, 10, 100)
assert result is True
assert instance == resource
assert elapsed == 0
def test_waiter_waits_for_missing_resource():
spec = {"get.side_effect": NotFoundError(Mock())}
client = Mock(**spec)
resource = Mock()
result, instance, elapsed = Waiter(client, resource, exists).wait(
RESOURCES[0], 3, 1
)
assert result is False
assert instance is None
assert abs(elapsed - 3) <= 1
@pytest.mark.parametrize("resource,expected", zip(RESOURCES, [True, True, True, False]))
def test_waiter_waits_for_resource_to_exist(resource, expected):
result = resource.to_dict()
spec = {"get.side_effect": [NotFoundError(Mock()), resource, resource, resource]}
client = Mock(**spec)
success, instance, elapsed = Waiter(client, Mock(), exists).wait(result, 3, 1)
assert success is expected
assert instance == result
assert abs(elapsed - 2) <= 1
def test_get_waiter_returns_correct_waiter():
assert get_waiter(Mock(), PODS[0]).predicate == pod_ready
waiter = get_waiter(Mock(), PODS[0], check_mode=True)
assert isinstance(waiter, DummyWaiter)
assert get_waiter(Mock(), PODS[0], state="absent").predicate == resource_absent
assert (
get_waiter(
Mock(), PODS[0], condition={"type": "Ready", "status": "True"}
).predicate.func
== custom_condition
)