Skip to content

Commit 7d265d6

Browse files
committed
Allow some Sonobuoy tests (KaaS) to fail.
Signed-off-by: Thomas Güttler <thomas.guettler@syself.com>
1 parent af2c1f7 commit 7d265d6

1 file changed

Lines changed: 111 additions & 41 deletions

File tree

Tests/kaas/sonobuoy_handler/sonobuoy_handler.py

Lines changed: 111 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from collections import Counter
2-
import json
32
import logging
3+
import re
44
import os
5+
import sys
56
import os.path
67
import shlex
78
import shutil
@@ -14,18 +15,20 @@
1415

1516
def _find_sonobuoy():
1617
"""find sonobuoy in PATH, but also in a fixed location at ~/.local/bin to simplify use with Ansible"""
17-
result = shutil.which('sonobuoy')
18+
result = shutil.which("sonobuoy")
1819
if result:
1920
return result
20-
logger.debug('sonobuoy not in PATH, trying $HOME/.local/bin')
21-
result = os.path.join(os.path.expanduser('~'), '.local', 'bin', 'sonobuoy')
21+
logger.debug("sonobuoy not in PATH, trying $HOME/.local/bin")
22+
result = os.path.join(os.path.expanduser("~"), ".local", "bin", "sonobuoy")
2223
if os.path.exists(result):
2324
return result
24-
logger.debug('sonobuoy executable not found; expect errors')
25+
logger.debug("sonobuoy executable not found; expect errors")
2526

2627

2728
def _fmt_result(counter):
28-
return ', '.join(f"{counter.get(key, 0)} {key}" for key in ('passed', 'failed', 'skipped'))
29+
return ", ".join(
30+
f"{counter.get(key, 0)} {key}" for key in ("passed", "failed", "skipped")
31+
)
2932

3033

3134
class SonobuoyHandler:
@@ -69,20 +72,10 @@ def _sonobuoy_run(self):
6972
def _sonobuoy_delete(self):
7073
self._invoke_sonobuoy("delete", "--wait")
7174

72-
def _sonobuoy_status_result(self):
73-
process = self._invoke_sonobuoy("status", "--json", capture_output=True)
74-
json_data = json.loads(process.stdout)
75-
counter = Counter()
76-
for entry in json_data["plugins"]:
77-
logger.debug(f"plugin {entry['plugin']}: {_fmt_result(entry['result-counts'])}")
78-
for key, value in entry["result-counts"].items():
79-
counter[key] += value
80-
return counter
81-
8275
def _eval_result(self, counter):
8376
"""evaluate test results and return return code"""
8477
result_message = f"sonobuoy reports {_fmt_result(counter)}"
85-
if counter['failed']:
78+
if counter["failed"]:
8679
logger.error(result_message)
8780
return 3
8881
logger.info(result_message)
@@ -95,34 +88,21 @@ def _preflight_check(self):
9588
if not self.sonobuoy:
9689
raise RuntimeError("sonobuoy executable not found; is it in PATH?")
9790

98-
def _sonobuoy_retrieve_result(self, plugin='e2e'):
91+
def _sonobuoy_retrieve_result(self, plugin="e2e"):
9992
"""
10093
Invoke sonobuoy to retrieve results and to store them in a subdirectory of
10194
the working directory. Analyze the results yaml file for given `plugin` and
102-
log each failure as ERROR. Return summary dict like `_sonobuoy_status_result`.
95+
log each failure as ERROR. Return summary dict.
10396
"""
10497
logger.debug(f"retrieving results to {self.result_dir_name}")
10598
result_dir = os.path.join(self.working_directory, self.result_dir_name)
10699
os.makedirs(result_dir, exist_ok=True)
107100

108101
self._invoke_sonobuoy("retrieve", "-x", result_dir)
109-
yaml_path = os.path.join(result_dir, 'plugins', plugin, 'sonobuoy_results.yaml')
102+
yaml_path = os.path.join(result_dir, "plugins", plugin, "sonobuoy_results.yaml")
110103
logger.debug(f"parsing results from {yaml_path}")
111-
with open(yaml_path, "r") as fileobj:
112-
result_obj = yaml.load(fileobj.read(), yaml.SafeLoader)
113-
counter = Counter()
114-
for item1 in result_obj.get('items', ()):
115-
# file ...
116-
for item2 in item1.get('items', ()):
117-
# suite ...
118-
for item in item2.get('items', ()):
119-
# testcase ... or so
120-
status = item.get('status', 'skipped')
121-
counter[status] += 1
122-
if status == 'failed':
123-
logger.error(f"FAILED: {item['name']}") # <-- this is why this method exists!
124-
logger.info(f"{plugin} results: {_fmt_result(counter)}")
125-
return counter
104+
105+
return sonobuoy_parse_result(plugin, yaml_path)
126106

127107
def run(self):
128108
"""
@@ -132,16 +112,106 @@ def run(self):
132112
self._preflight_check()
133113
try:
134114
self._sonobuoy_run()
135-
return_code = self._eval_result(self._sonobuoy_status_result())
115+
counter = self._sonobuoy_retrieve_result()
116+
return_code = self._eval_result(counter)
136117
print(self.check_name + ": " + ("PASS", "FAIL")[min(1, return_code)])
137-
try:
138-
self._sonobuoy_retrieve_result()
139-
except Exception:
140-
# swallow exception for the time being
141-
logger.debug('problem retrieving results', exc_info=True)
142118
return return_code
143119
except BaseException:
144120
logger.exception("something went wrong")
145121
return 112
146122
finally:
147123
self._sonobuoy_delete()
124+
125+
126+
def sonobuoy_parse_result(plugin, sonobuoy_results_yaml_path):
127+
with open(sonobuoy_results_yaml_path, "r") as fileobj:
128+
result_obj = yaml.load(fileobj.read(), yaml.SafeLoader)
129+
counter = Counter()
130+
for item1 in result_obj.get("items", ()):
131+
# file ...
132+
for item2 in item1.get("items", ()):
133+
# suite ...
134+
for item in item2.get("items", ()):
135+
# testcase ... or so
136+
status = item.get("status", "skipped")
137+
if status == "failed":
138+
if ok_to_fail(item["name"]):
139+
status = "failed_ok"
140+
else:
141+
logger.error(f"FAILED: {item['name']}")
142+
counter[status] += 1
143+
144+
logger.info(f"{plugin} results: {_fmt_result(counter)}")
145+
return counter
146+
147+
148+
def ok_to_fail(test_name):
149+
# TODO: Make this configurable
150+
regexs_and_reason = [
151+
# InternalIP still used in tests. Having only an ExternalIP is considered valid by SCS:
152+
(
153+
"HostPort validates that there is no conflict between pods with same hostPort but different hostIP and protocol",
154+
"Fails when a cluster has no InternalIP (only ExternalIP): https://github.com/kubernetes/kubernetes/issues/136626",
155+
),
156+
(
157+
"Services should be able to switch session affinity for NodePort service",
158+
"Fails when a cluster has no InternalIP (only ExternalIP): https://github.com/kubernetes/kubernetes/issues/136626",
159+
),
160+
(
161+
"Services should have session affinity work for NodePort service",
162+
"Fails when a cluster has no InternalIP (only ExternalIP): https://github.com/kubernetes/kubernetes/issues/136626",
163+
),
164+
(
165+
"validates that there exists conflict between pods with same hostPort and protocol but one using 0.0.0.0 hostIP",
166+
"Fails when a cluster has no InternalIP (only ExternalIP): https://github.com/kubernetes/kubernetes/issues/136626",
167+
),
168+
# Was flaky
169+
(
170+
"Netpol NetworkPolicy between server and client should allow ingress access from updated namespace",
171+
"Flaky test. Fix in v1.36 (https://github.com/kubernetes/kubernetes/pull/136715)",
172+
),
173+
# SCTP is optional
174+
(
175+
"Feature:SCTPConnectivity",
176+
"SCTPConnectivity is optional. Currently Cilium does not support it by default.",
177+
),
178+
# Tests skipped by Cilium: https://github.com/cilium/cilium/blob/main/.github/workflows/k8s-kind-network-policies-e2e.yaml#L177-L185
179+
(
180+
"should.allow.egress.access.to.server.in.CIDR.block",
181+
"https://github.com/cilium/cilium/issues/9209",
182+
),
183+
(
184+
"should.ensure.an.IP.overlapping.both.IPBlock.CIDR.and.IPBlock.Except.is.allowed",
185+
"https://github.com/cilium/cilium/issues/9209",
186+
),
187+
(
188+
"should.enforce.except.clause.while.egress.access.to.server.in.CIDR.block",
189+
"https://github.com/cilium/cilium/issues/9209",
190+
),
191+
]
192+
name = test_name
193+
for regex, _reason in regexs_and_reason:
194+
if re.search(regex, name):
195+
return True
196+
return False
197+
198+
199+
def usage():
200+
print(
201+
f"""{sys.argv[0]} path-to/sonobuoy_results.yaml
202+
Read sonobuoy_results.yaml and print the results.
203+
"""
204+
)
205+
sys.exit(3)
206+
207+
208+
if __name__ == "__main__":
209+
if len(sys.argv) < 2:
210+
usage()
211+
sonobuoy_results_yaml_path = sys.argv[1]
212+
if not os.path.exists(sonobuoy_results_yaml_path):
213+
print(f"{sonobuoy_results_yaml_path} does not exist")
214+
usage()
215+
counter = sonobuoy_parse_result("", sys.argv[1])
216+
for key, value in counter.items():
217+
print(f"{key}: {value}")

0 commit comments

Comments
 (0)