Skip to content

Commit 943710d

Browse files
committed
tests: test CRD versions
Add test which checks if the CRD versions defined in the cluster match the versions in the subcharts in the git repo. This test ensures that the subcharts in the release version don't unexpectedly change their API versions within a minor Harvester release. To implement this test, additional infrastructure for the Kubernetes API has been added. fixes: #1314 Signed-off-by: Moritz Röhrich <moritz.rohrich@suse.com>
1 parent 259efc1 commit 943710d

5 files changed

Lines changed: 174 additions & 0 deletions

File tree

apiclient/kube_api/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Copyright (c) 2024 SUSE LLC

apiclient/kube_api/api.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Copyright (c) 2024 SUSE LLC
2+
#
3+
# pylint: disable=missing-function-docstring
4+
5+
from urllib.parse import urljoin
6+
7+
import kubernetes
8+
import requests
9+
import yaml
10+
11+
12+
class KubeAPI:
13+
"""
14+
An abstraction of the Kubernetes API.
15+
16+
Example usage:
17+
18+
```
19+
with KubeAPI(endpoint, tls_verify=false) as api:
20+
api.authenticate(username, password, verify=false)
21+
kube_client = api.get_client()
22+
23+
corev1 = kubernetes.client.CoreV1Api(kube_client)
24+
25+
namespaces = corev1.list_namespace()
26+
```
27+
"""
28+
29+
HARVESTER_API_VERSION = "harvesterhci.io/v1beta1"
30+
31+
def __init__(self, endpoint, tls_verify, token=None, session=None):
32+
self.session = session or requests.Session()
33+
self.session.verify = tls_verify
34+
self.session.headers.update(Authorization=token or "")
35+
36+
self.endpoint = endpoint
37+
38+
def __enter__(self):
39+
return self
40+
41+
def __exit__(self, exc_type, exc_value, taceback):
42+
pass
43+
44+
def _post(self, path, **kwargs):
45+
url = self._get_url(path)
46+
return self.session.post(url, **kwargs)
47+
48+
def _get_url(self, path):
49+
return urljoin(self.endpoint, path).format(API_VERSION=self.HARVESTER_API_VERSION)
50+
51+
def get_client(self):
52+
path = "/v1/management.cattle.io.clusters/local"
53+
params = {"action": "generateKubeconfig"}
54+
55+
resp = self._post(path, params=params)
56+
assert resp.status_code == 200, "Failed to generate kubeconfig"
57+
58+
kubeconfig = yaml.safe_load(resp.json()['config'])
59+
return kubernetes.config.new_client_from_config_dict(kubeconfig)
60+
61+
def authenticate(self, user, passwd, **kwargs):
62+
path = "v3-public/localProviders/local?action=login"
63+
resp = self._post(path, json=dict(username=user, password=passwd), **kwargs)
64+
65+
assert resp.status_code == 201, "Failed to authenticate"
66+
67+
token = f"Bearer {resp.json()['token']}"
68+
self.session.headers.update(Authorization=token)
69+
70+
return resp.json()
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Copyright (c) 2024 SUSE LLC
2+
#
3+
# pylint: disable=missing-function-docstring, redefined-outer-name
4+
5+
from urllib.parse import urljoin
6+
7+
import kubernetes
8+
import pytest
9+
import requests
10+
import semver
11+
import yaml
12+
13+
14+
pytest_plugins = [
15+
"harvester_e2e_tests.fixtures.kube_api_client",
16+
"harvester_e2e_tests.fixtures.api_client"
17+
]
18+
19+
20+
@pytest.fixture(scope="session")
21+
def server_version(api_client):
22+
code, data = api_client.settings.get(name="server-version")
23+
assert code == 200
24+
assert data.get("value") is not None
25+
26+
yield semver.VersionInfo.parse(data.get("value").lstrip("v"))
27+
28+
29+
@pytest.fixture(scope="module", params=[
30+
("csi-snapshotter", "volumesnapshotclasses"),
31+
("csi-snapshotter", "volumesnapshotcontents"),
32+
("csi-snapshotter", "volumesnapshots"),
33+
("kubevirt-operator", "crd-kubevirt"),
34+
("whereabouts", "whereabouts.cni.cncf.io_ippools"),
35+
("whereabouts", "whereabouts.cni.cncf.io_overlappingrangeipreservations")
36+
])
37+
def chart_and_file_name(request):
38+
yield request.param
39+
40+
41+
@pytest.fixture(scope="module")
42+
def expected_crd(server_version, chart_and_file_name):
43+
raw_url = "https://raw.githubusercontent.com/harvester/harvester/"
44+
raw_url = urljoin(raw_url, f"v{server_version.major}.{server_version.minor}/")
45+
raw_url = urljoin(raw_url, "deploy/charts/harvester/dependency_charts/")
46+
raw_url = urljoin(raw_url, f"{chart_and_file_name[0]}/")
47+
raw_url = urljoin(raw_url, "crds/")
48+
raw_url = urljoin(raw_url, f"{chart_and_file_name[1]}.yaml")
49+
50+
resp = requests.get(raw_url, allow_redirects=True)
51+
cont = resp.content.decode("utf-8")
52+
data = yaml.safe_load(cont)
53+
yield data
54+
55+
56+
@pytest.fixture(scope="module")
57+
def actual_crd(kube_api_client, expected_crd):
58+
name = expected_crd['metadata']['name']
59+
kube_client = kubernetes.client.ApiextensionsV1Api(kube_api_client)
60+
yield kube_client.read_custom_resource_definition(name=name)
61+
62+
63+
@pytest.mark.api
64+
def test_api_version(expected_crd, actual_crd):
65+
expected_versions = []
66+
for ver in expected_crd['spec']['versions']:
67+
expected_versions.append(ver['name'])
68+
69+
actual_versions = []
70+
for ver in actual_crd.spec.versions:
71+
actual_versions.append(ver.name)
72+
73+
assert expected_crd['metadata']['name'] == actual_crd.metadata.name
74+
75+
# Make sure all expected versions are there
76+
for ver in expected_versions:
77+
assert ver in actual_versions
78+
79+
# Make sure all installed versions are expected
80+
for ver in actual_versions:
81+
assert ver in expected_versions
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright (c) 2024 SUSE LLC
2+
#
3+
# pylint: disable=missing-function-docstring
4+
5+
import pytest
6+
7+
from kube_api import KubeAPI
8+
9+
10+
@pytest.fixture(scope="session")
11+
def kube_api_client(request):
12+
endpoint = request.config.getoption("--endpoint")
13+
username = request.config.getoption("--username")
14+
password = request.config.getoption("--password")
15+
tls_verify = request.config.getoption("--ssl_verify", False)
16+
17+
with KubeAPI(endpoint, tls_verify) as api:
18+
api.authenticate(username, password, verify=tls_verify)
19+
20+
yield api.get_client()

test-requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ pytest-json-report
77
pytest-dependency
88
jinja2
99
bcrypt
10+
kubernetes
11+
semver
1012
requests
1113
paramiko
1214
pycryptodome

0 commit comments

Comments
 (0)