Skip to content

Commit c36d761

Browse files
authored
[ISV-5073] Support OCP v4.17 (#691)
* [ISV-5073] Support OCP v4.17 - Add ocp to k8s version mapping for 4.17 - Avoid failures from `check_osdk_bundle_validate_*()` checks when the ocp version is missing from the mapping - Avoid failures from check_api_version_constraints() check when the ocp version is missing from the mapping --------- Signed-off-by: Maurizio Porrato <[email protected]>
1 parent f84cf08 commit c36d761

File tree

2 files changed

+128
-61
lines changed

2 files changed

+128
-61
lines changed

operator-pipeline-images/operatorcert/static_tests/community/bundle.py

Lines changed: 77 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
(either Fail or Warn) to describe the issues found in the given Bundle.
77
"""
88

9+
from bisect import bisect
910
import json
1011
import logging
1112
import re
@@ -33,9 +34,24 @@
3334

3435
LOGGER = logging.getLogger("operator-cert")
3536

37+
38+
def _parse_semver(version: str) -> Version:
39+
return Version.parse(version.strip(), optional_minor_and_patch=True).replace(
40+
prerelease=None, build=None
41+
)
42+
43+
44+
def _parse_ocp_version(version: str) -> Version:
45+
return _parse_semver(version.strip().removeprefix("v")).replace(patch=0)
46+
47+
3648
# convert table for OCP <-> k8s versions
3749
# for now these are hardcoded pairs -> if new version of OCP:k8s is released,
3850
# this table should be updated
51+
# This is documented at https://access.redhat.com/solutions/4870701
52+
# When adding an upcoming release the document above won't include the new
53+
# version: to find the corresponding k8s version look at
54+
# https://github.com/openshift/kubernetes/blob/release-4.YY/CHANGELOG/README.md
3955
OCP_TO_K8S = {
4056
"4.1": "1.13",
4157
"4.2": "1.14",
@@ -53,29 +69,47 @@
5369
"4.14": "1.27",
5470
"4.15": "1.28",
5571
"4.16": "1.29",
72+
"4.17": "1.30",
5673
}
5774

75+
OCP_TO_K8S_SEMVER = {
76+
_parse_ocp_version(ocp_ver): _parse_semver(k8s_ver)
77+
for ocp_ver, k8s_ver in OCP_TO_K8S.items()
78+
}
5879

59-
class GraphLoopException(Exception):
80+
81+
def find_closest_ocp_version(ocp_ver: Version) -> Version:
6082
"""
61-
Exception raised when a loop is detected in the update graph
83+
Find the closest openshift version between all known versions
6284
"""
85+
all_ocp_versions = sorted(OCP_TO_K8S_SEMVER.keys())
86+
pos = bisect(all_ocp_versions, ocp_ver)
87+
if pos == 0:
88+
return all_ocp_versions[0]
89+
return all_ocp_versions[pos - 1]
6390

6491

65-
def _parse_semver(version: str) -> Version:
66-
return Version.parse(version.strip(), optional_minor_and_patch=True).replace(
67-
prerelease=None, build=None
68-
)
69-
70-
71-
def _parse_ocp_version(version: str) -> Version:
72-
return _parse_semver(version.strip().removeprefix("v")).replace(patch=0)
92+
def ocp_to_k8s_ver(ocp_ver: str) -> str:
93+
"""
94+
Lookup the corresponding k8s version for an openshift version
95+
"""
96+
try:
97+
return OCP_TO_K8S[ocp_ver]
98+
except KeyError:
99+
closest_ocp = find_closest_ocp_version(_parse_ocp_version(ocp_ver))
100+
LOGGER.warning(
101+
"Using openshift version %s in place of unknown openshift version %s",
102+
closest_ocp,
103+
ocp_ver,
104+
)
105+
k8s = OCP_TO_K8S_SEMVER[closest_ocp]
106+
return f"{k8s.major}.{k8s.minor}"
73107

74108

75-
OCP_TO_K8S_SEMVER = {
76-
_parse_ocp_version(ocp_ver): _parse_semver(k8s_ver)
77-
for ocp_ver, k8s_ver in OCP_TO_K8S.items()
78-
}
109+
class GraphLoopException(Exception):
110+
"""
111+
Exception raised when a loop is detected in the update graph
112+
"""
79113

80114

81115
def run_operator_sdk_bundle_validate(
@@ -87,40 +121,34 @@ def run_operator_sdk_bundle_validate(
87121
ocp_versions = utils.get_ocp_supported_versions(
88122
"community-operators", ocp_annotation
89123
)
90-
ocp_latest_version = ocp_versions[0] if ocp_versions else None
91-
92-
kube_version_for_deprecation_test = OCP_TO_K8S.get(ocp_latest_version)
93-
94-
if kube_version_for_deprecation_test is None:
95-
LOGGER.info(
96-
"There was no OCP version specified. API deprecation test for the testing "
97-
"suite - %s - was run with 'None' value.",
124+
if ocp_versions:
125+
ocp_latest_version = ocp_versions[0]
126+
127+
kube_version_for_deprecation_test = ocp_to_k8s_ver(ocp_latest_version)
128+
129+
cmd = [
130+
"operator-sdk",
131+
"bundle",
132+
"validate",
133+
"-o",
134+
"json-alpha1",
135+
bundle.root,
136+
"--select-optional",
98137
test_suite_selector,
138+
f"--optional-values=k8s-version={kube_version_for_deprecation_test}",
139+
]
140+
sdk_result = json.loads(
141+
subprocess.run(
142+
cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=False
143+
).stdout
99144
)
100-
101-
cmd = [
102-
"operator-sdk",
103-
"bundle",
104-
"validate",
105-
"-o",
106-
"json-alpha1",
107-
bundle.root,
108-
"--select-optional",
109-
test_suite_selector,
110-
f"--optional-values=k8s-version={kube_version_for_deprecation_test}",
111-
]
112-
sdk_result = json.loads(
113-
subprocess.run(
114-
cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, check=False
115-
).stdout
116-
)
117-
for output in sdk_result.get("outputs") or []:
118-
output_type = output.get("type")
119-
output_message = output.get("message", "")
120-
if output_type == "error":
121-
yield Fail(output_message)
122-
else:
123-
yield Warn(output_message)
145+
for output in sdk_result.get("outputs") or []:
146+
output_type = output.get("type")
147+
output_message = output.get("message", "")
148+
if output_type == "error":
149+
yield Fail(output_message)
150+
else:
151+
yield Warn(output_message)
124152

125153

126154
def check_osdk_bundle_validate_operatorhub(bundle: Bundle) -> Iterator[CheckResult]:
@@ -260,14 +288,9 @@ def check_api_version_constraints(bundle: Bundle) -> Iterator[CheckResult]:
260288
# com.redhat.openshift.versions contains a single version
261289
if selector.startswith("="):
262290
# Select a specific version
263-
version = _parse_ocp_version(selector.removeprefix("="))
264-
if version not in OCP_TO_K8S_SEMVER:
265-
yield Fail(
266-
"Unknown OCP version in "
267-
"com.redhat.openshift.versions: "
268-
f"{version.major}.{version.minor}"
269-
)
270-
return
291+
version = find_closest_ocp_version(
292+
_parse_ocp_version(selector.removeprefix("="))
293+
)
271294
ocp_versions = {version}
272295
else:
273296
# Any version >= the specified value

operator-pipeline-images/tests/static_tests/community/test_bundle.py

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,50 +15,94 @@
1515
check_api_version_constraints,
1616
check_replaces_availability,
1717
check_upgrade_graph_loop,
18+
ocp_to_k8s_ver,
1819
)
20+
from semver import Version
1921
from tests.utils import bundle_files, create_files, merge
2022

2123

22-
@pytest.mark.parametrize("version", ["4.8-4.9", ["4.9", "4.8"], None])
2324
@pytest.mark.parametrize(
24-
"osdk_output, expected",
25+
"osdk_output, ocp_versions, expected",
2526
[
2627
(
2728
'{"passed": false, "outputs":'
2829
'[{"type": "error", "message": "foo"}, {"type": "warning", "message": "bar"}]}',
30+
["4.9", "4.8"],
2931
{Fail("foo"), Warn("bar")},
3032
),
33+
(
34+
'{"passed": false, "outputs":'
35+
'[{"type": "error", "message": "foo"}, {"type": "warning", "message": "bar"}]}',
36+
None,
37+
set(),
38+
),
3139
(
3240
'{"passed": true, "outputs": null}',
41+
["4.9", "4.8"],
3342
set(),
3443
),
3544
],
3645
indirect=False,
3746
ids=[
38-
"A warning and an error",
39-
"No warnings or errors",
47+
"A warning and an error from operator-sdk",
48+
"No matching ocp versions",
49+
"No warnings or errors from operator-sdk",
4050
],
4151
)
4252
@patch("operatorcert.static_tests.community.bundle.utils.get_ocp_supported_versions")
4353
@patch("subprocess.run")
4454
def test_run_operator_sdk_bundle_validate(
4555
mock_run: MagicMock,
4656
mock_version: MagicMock,
47-
version: Any,
4857
osdk_output: str,
58+
ocp_versions: Optional[list[str]],
4959
expected: set[CheckResult],
5060
tmp_path: Path,
5161
) -> None:
5262
create_files(tmp_path, bundle_files("test-operator", "0.0.1"))
5363
repo = Repo(tmp_path)
5464
bundle = repo.operator("test-operator").bundle("0.0.1")
55-
mock_version.return_value = version
65+
mock_version.return_value = ocp_versions
5666
process_mock = MagicMock()
5767
process_mock.stdout = osdk_output
5868
mock_run.return_value = process_mock
5969
assert set(run_operator_sdk_bundle_validate(bundle, "")) == expected
6070

6171

72+
@pytest.mark.parametrize(
73+
"ocp_ver, expected",
74+
[
75+
pytest.param("4.2", "1.14", id="Known ocp_ver"),
76+
pytest.param("4.0", "1.13", id="Old ocp_ver"),
77+
pytest.param("4.4", "1.16", id="New ocp_ver"),
78+
],
79+
)
80+
def test_ocp_to_k8s_ver(
81+
monkeypatch: pytest.MonkeyPatch, ocp_ver: str, expected: str
82+
) -> None:
83+
# prevent the test from breaking when the OCP_TO_K8S mapping
84+
# is updated
85+
test_ver_map = {
86+
"4.1": "1.13",
87+
"4.2": "1.14",
88+
"4.3": "1.16",
89+
}
90+
test_ver_map_semver = {
91+
Version.parse(k, optional_minor_and_patch=True): Version.parse(
92+
v, optional_minor_and_patch=True
93+
)
94+
for k, v in test_ver_map.items()
95+
}
96+
monkeypatch.setattr(
97+
"operatorcert.static_tests.community.bundle.OCP_TO_K8S", test_ver_map
98+
)
99+
monkeypatch.setattr(
100+
"operatorcert.static_tests.community.bundle.OCP_TO_K8S_SEMVER",
101+
test_ver_map_semver,
102+
)
103+
assert ocp_to_k8s_ver(ocp_ver) == expected
104+
105+
62106
@patch("operatorcert.static_tests.community.bundle.run_operator_sdk_bundle_validate")
63107
def test_check_osdk_bundle_validate_operator_framework(mock_sdk: MagicMock) -> None:
64108
bundle = MagicMock()
@@ -381,7 +425,7 @@ def test_check_dangling_bundles(tmp_path: Path) -> None:
381425
pytest.param(
382426
{"spec": {"minKubeVersion": "1.0.0"}},
383427
{"com.redhat.openshift.versions": "=v3.1"},
384-
{Fail("Unknown OCP version in com.redhat.openshift.versions: 3.1")},
428+
set(),
385429
id='Valid minKubeVersion, unknown com.redhat.openshift.versions ("=")',
386430
),
387431
pytest.param(

0 commit comments

Comments
 (0)