pytest-pve-cloud 3.1.4__tar.gz → 3.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {pytest_pve_cloud-3.1.4/src/pytest_pve_cloud.egg-info → pytest_pve_cloud-3.2.0}/PKG-INFO +2 -2
  2. {pytest_pve_cloud-3.1.4 → pytest_pve_cloud-3.2.0}/pyproject.toml +1 -0
  3. {pytest_pve_cloud-3.1.4 → pytest_pve_cloud-3.2.0}/requirements.txt +1 -1
  4. pytest_pve_cloud-3.2.0/src/pve_cloud_test/_version.py +1 -0
  5. pytest_pve_cloud-3.2.0/src/pve_cloud_test/cloud_fixtures.py +230 -0
  6. pytest_pve_cloud-3.1.4/src/pve_cloud_test/cloud_fixtures.py → pytest_pve_cloud-3.2.0/src/pve_cloud_test/k8s_fixtures.py +109 -147
  7. {pytest_pve_cloud-3.1.4 → pytest_pve_cloud-3.2.0}/src/pve_cloud_test/options.py +3 -15
  8. {pytest_pve_cloud-3.1.4 → pytest_pve_cloud-3.2.0}/src/pve_cloud_test/tdd_watchdog.py +41 -31
  9. pytest_pve_cloud-3.2.0/src/pve_cloud_test/terraform.py +220 -0
  10. {pytest_pve_cloud-3.1.4 → pytest_pve_cloud-3.2.0}/src/pve_cloud_test/test_env_schema.yaml +14 -6
  11. {pytest_pve_cloud-3.1.4 → pytest_pve_cloud-3.2.0/src/pytest_pve_cloud.egg-info}/PKG-INFO +2 -2
  12. pytest_pve_cloud-3.2.0/src/pytest_pve_cloud.egg-info/entry_points.txt +3 -0
  13. {pytest_pve_cloud-3.1.4 → pytest_pve_cloud-3.2.0}/src/pytest_pve_cloud.egg-info/requires.txt +1 -1
  14. pytest_pve_cloud-3.1.4/src/pve_cloud_test/_version.py +0 -1
  15. pytest_pve_cloud-3.1.4/src/pve_cloud_test/k8s_fixtures.py +0 -168
  16. pytest_pve_cloud-3.1.4/src/pve_cloud_test/terraform.py +0 -87
  17. pytest_pve_cloud-3.1.4/src/pytest_pve_cloud.egg-info/entry_points.txt +0 -2
  18. {pytest_pve_cloud-3.1.4 → pytest_pve_cloud-3.2.0}/LICENSE.md +0 -0
  19. {pytest_pve_cloud-3.1.4 → pytest_pve_cloud-3.2.0}/README.md +0 -0
  20. {pytest_pve_cloud-3.1.4 → pytest_pve_cloud-3.2.0}/setup.cfg +0 -0
  21. {pytest_pve_cloud-3.1.4 → pytest_pve_cloud-3.2.0}/src/pytest_pve_cloud.egg-info/SOURCES.txt +0 -0
  22. {pytest_pve_cloud-3.1.4 → pytest_pve_cloud-3.2.0}/src/pytest_pve_cloud.egg-info/dependency_links.txt +0 -0
  23. {pytest_pve_cloud-3.1.4 → pytest_pve_cloud-3.2.0}/src/pytest_pve_cloud.egg-info/top_level.txt +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-pve-cloud
3
- Version: 3.1.4
3
+ Version: 3.2.0
4
4
  Author-email: Tobias Huebner <tobias.huebner@vmzberlin.com>
5
5
  License-Expression: GPL-3.0-or-later
6
6
  License-File: LICENSE.md
7
- Requires-Dist: py-pve-cloud<3.2.0,>=3.1.4
7
+ Requires-Dist: py-pve-cloud<3.3.0,>=3.2.0
8
8
  Requires-Dist: kubernetes==34.1.0
9
9
  Requires-Dist: pytest==8.4.2
10
10
  Requires-Dist: ansible-runner==2.4.2
@@ -19,3 +19,4 @@ version = {attr = "pve_cloud_test._version.__version__"}
19
19
 
20
20
  [project.scripts]
21
21
  tddog = "pve_cloud_test.tdd_watchdog:main"
22
+ mock-server = "pve_cloud_test.terraform:launch_gw_mock_manually"
@@ -1,4 +1,4 @@
1
- py-pve-cloud>=3.1.4,<3.2.0
1
+ py-pve-cloud>=3.2.0,<3.3.0
2
2
  kubernetes==34.1.0
3
3
  pytest==8.4.2
4
4
  ansible-runner==2.4.2
@@ -0,0 +1 @@
1
+ __version__ = "3.2.0"
@@ -0,0 +1,230 @@
1
+ import functools
2
+ import inspect
3
+ import logging
4
+ import os
5
+ import re
6
+
7
+ import jsonschema
8
+ import paramiko
9
+ import pytest
10
+ import redis
11
+ import yaml
12
+ from proxmoxer import ProxmoxAPI
13
+ from pve_cloud.lib.inventory import (get_cloud_domain, get_pve_inventory,
14
+ get_target_cluster)
15
+ from pve_cloud.lib.ssh import connect_host
16
+
17
+ from pve_cloud_test.tdd_watchdog import get_ipv4
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ def get_tdd_version(artifact_key):
23
+ if os.getenv("TDDOG_LOCAL_IFACE"):
24
+ # get version for image from redis
25
+ r = redis.Redis(host="localhost", port=6379, db=0)
26
+ local_build_version = r.get(f"version.{artifact_key}").decode()
27
+
28
+ if local_build_version:
29
+ logger.info(f"found local version {local_build_version}")
30
+
31
+ return local_build_version, get_ipv4(os.getenv("TDDOG_LOCAL_IFACE"))
32
+ else:
33
+ logger.warning(
34
+ f"did not find local build pve cloud version for {artifact_key} even though TDDOG_LOCAL_IFACE env var is defined"
35
+ )
36
+
37
+ return None, None
38
+
39
+
40
+ def get_tdd_ip():
41
+ if os.getenv("TDDOG_LOCAL_IFACE"):
42
+ return get_ipv4(os.getenv("TDDOG_LOCAL_IFACE"))
43
+
44
+ return None
45
+
46
+
47
+ # this prepends a custom wrapper func to all our e2e fixtures and allows easy toggeling
48
+ # cloud fixtures can be annotated with this and and a value tuple of tags as value
49
+ # they also automatically get the standard pytest fixture decorator
50
+ # depending on the pytest --fixture-tags paramater, which takes a csv of fixture tags
51
+ # the fixtures are automatically skipped if not in the csv
52
+ def cloud_fixture(*tags):
53
+ def decorator(func):
54
+ func._tags = tags
55
+
56
+ logger.info(f"called decorator for {func.__name__}")
57
+
58
+ @pytest.fixture(scope="session")
59
+ @functools.wraps(func)
60
+ def wrapper(*args, **kwargs):
61
+ logger.info(f"called wrapper for {func.__name__}")
62
+
63
+ # get pytest request object to extract globally set --fixture-tags
64
+ request = kwargs.get("request")
65
+ if request is None:
66
+ raise RuntimeError(
67
+ f"Cannot find request object defined in {func.__name__} fixture args! Pytest requests object needs to be in params for cloud_fixture to work!"
68
+ )
69
+
70
+ # if this is defined we skip fixtures alltogether
71
+ skip_fixtures = request.config.getoption("--skip-fixtures")
72
+ if skip_fixtures:
73
+ logger.info(
74
+ f"Skipping fixture {func.__name__} due --skip-fixtures flag"
75
+ )
76
+
77
+ # mimic fixture returns and pass blanks
78
+ yield
79
+ return
80
+
81
+ # filter out fixtures that are not specifically targeted
82
+ allowed_tags_opt = request.config.getoption("--fixture-tags")
83
+ if allowed_tags_opt:
84
+ allowed_tags = allowed_tags_opt.split(",")
85
+ if not any(tag in allowed_tags for tag in func._tags):
86
+ logger.info(f"Skipping fixture {func.__name__} due to tags")
87
+
88
+ # mimic fixture returns and pass blanks
89
+ yield
90
+ return
91
+
92
+ # when fixtures are executed we give the option to skip the cleanup part
93
+ skip_cleanup = request.config.getoption("--skip-cleanup")
94
+
95
+ result = func(*args, **kwargs)
96
+
97
+ if inspect.isgenerator(result):
98
+ logger.info("is generator")
99
+
100
+ if skip_cleanup:
101
+ logger.info(
102
+ "yielding and skipping cleanup due to --skip-cleanup flag"
103
+ )
104
+
105
+ yield next(
106
+ result, None
107
+ ) # might not yield conditionally (like setup_ceph_dhcp_lxcs fixture)
108
+
109
+ else:
110
+ yield from result
111
+
112
+ else:
113
+ logger.info("is result")
114
+ yield result # still yield because of wrappers
115
+
116
+ return wrapper
117
+
118
+ return decorator
119
+
120
+
121
+ # load the test environment yaml from parameters
122
+ @pytest.fixture(scope="session")
123
+ def get_test_env(request):
124
+ test_pve_yaml_file = os.getenv("PVE_CLOUD_TEST_CONF")
125
+ assert test_pve_yaml_file
126
+
127
+ assert test_pve_yaml_file is not None
128
+ with open(test_pve_yaml_file, "r") as file:
129
+ test_pve_conf = yaml.safe_load(file)
130
+
131
+ logger.info(f"terraform inv file {test_pve_yaml_file}")
132
+
133
+ # load schema and validate
134
+ with open(
135
+ os.path.dirname(os.path.realpath(__file__)) + "/test_env_schema.yaml"
136
+ ) as file:
137
+ test_env_schema = yaml.safe_load(file)
138
+
139
+ jsonschema.validate(instance=test_pve_conf, schema=test_env_schema)
140
+
141
+ # render vlan tag
142
+ if "pve_test_net0_vlan_tag" in test_pve_conf:
143
+ test_pve_conf["net0_vlan_tag_rendered"] = (
144
+ f",tag={test_pve_conf['pve_test_net0_vlan_tag']}"
145
+ )
146
+
147
+ # validate that target copy pve system is directly accessible (no jump host validation supported)
148
+ copy_cloud_domain = get_cloud_domain(test_pve_conf["kubernetes"]["copy_target_pve"])
149
+ copy_pve_inventory = get_pve_inventory(copy_cloud_domain)
150
+
151
+ copy_target_cluster = get_target_cluster(
152
+ copy_pve_inventory,
153
+ test_pve_conf["kubernetes"]["copy_target_pve"],
154
+ target_cloud_domain=copy_cloud_domain,
155
+ )
156
+
157
+ assert "jump_hosts" not in copy_pve_inventory[copy_target_cluster]
158
+
159
+ return test_pve_conf
160
+
161
+
162
+ def get_first_host(get_test_env):
163
+ return get_test_env["pve_test_cluster_hosts"][
164
+ next(iter(get_test_env["pve_test_cluster_hosts"]))
165
+ ]["ansible_host"]
166
+
167
+
168
+ @pytest.fixture(scope="session")
169
+ def fetch_default_gw_ns(get_test_env):
170
+
171
+ with connect_host(
172
+ get_first_host(get_test_env), get_test_env.get("pve_test_cluster_jump_host")
173
+ ) as client:
174
+
175
+ _, stdout, _ = client.exec_command(
176
+ "ip route show default 2>/dev/null | awk '{print $3}'"
177
+ )
178
+ gateway = stdout.read().decode("utf-8").strip()
179
+ logger.info(gateway)
180
+
181
+ _, stdout, _ = client.exec_command(
182
+ "grep -E '^nameserver [0-9]+' /etc/resolv.conf 2>/dev/null | awk '{print $2}'"
183
+ )
184
+ nameservers = stdout.read().decode("utf-8").strip().splitlines()
185
+ logger.info(nameservers)
186
+
187
+ return gateway, " ".join(nameservers)
188
+
189
+
190
+ @pytest.fixture(scope="session")
191
+ def get_cloud_secrets(get_test_env):
192
+ logger.info("setting pve cloud auth env variables for tf")
193
+
194
+ with connect_host(
195
+ get_first_host(get_test_env), get_test_env.get("pve_test_cluster_jump_host")
196
+ ) as ssh:
197
+ _, stdout, _ = ssh.exec_command("sudo cat /etc/pve/cloud/secrets/patroni.pass")
198
+ patroni_pass = stdout.read().decode("utf-8")
199
+
200
+ pg_conn_str = f"postgres://postgres:{patroni_pass}@{get_test_env['pve_test_cluster_floating_internal']}:5000/tf_states?sslmode=disable"
201
+ pg_conn_str_orm = f"postgresql+psycopg2://postgres:{patroni_pass}@{get_test_env['pve_test_cluster_floating_internal']}:5000/pve_cloud?sslmode=disable"
202
+
203
+ # fetch bind update key for ingress dns validation
204
+ _, stdout, _ = ssh.exec_command("sudo cat /etc/pve/cloud/secrets/internal.key")
205
+ bind_key_file = stdout.read().decode("utf-8")
206
+
207
+ bind_internal_key = re.search(r'secret\s+"([^"]+)";', bind_key_file).group(1)
208
+
209
+ return {
210
+ "bind_internal_key": bind_internal_key,
211
+ "pg_conn_str": pg_conn_str,
212
+ "pg_conn_str_orm": pg_conn_str_orm,
213
+ }
214
+
215
+
216
+ # connect proxmoxer to pve cluster
217
+ @pytest.fixture(scope="session")
218
+ def get_proxmoxer(get_test_env):
219
+ first_test_host = get_test_env["pve_test_cluster_hosts"][
220
+ next(iter(get_test_env["pve_test_cluster_hosts"]))
221
+ ]
222
+
223
+ proxmox = ProxmoxAPI(
224
+ first_test_host["ansible_host"], user="root", backend="ssh_paramiko"
225
+ )
226
+ nodes = proxmox.nodes.get()
227
+
228
+ assert nodes
229
+
230
+ return proxmox
@@ -1,144 +1,17 @@
1
- import functools
2
- import inspect
3
1
  import logging
4
- import os
5
2
  import tempfile
6
3
 
7
- import jsonschema
4
+ import paramiko
8
5
  import pytest
9
- import redis
10
6
  import yaml
7
+ from kubernetes import client, config
11
8
  from proxmoxer import ProxmoxAPI
12
- from pve_cloud.lib.inventory import (get_cloud_domain, get_online_pve_host,
13
- get_pve_inventory, get_target_cluster)
14
9
 
15
- from pve_cloud_test.tdd_watchdog import get_ipv4
10
+ from pve_cloud_test.cloud_fixtures import get_test_env
16
11
 
17
12
  logger = logging.getLogger(__name__)
18
13
 
19
14
 
20
- def get_tdd_version(artifact_key):
21
- if os.getenv("TDDOG_LOCAL_IFACE"):
22
- # get version for image from redis
23
- r = redis.Redis(host="localhost", port=6379, db=0)
24
- local_build_version = r.get(f"version.{artifact_key}").decode()
25
-
26
- if local_build_version:
27
- logger.info(f"found local version {local_build_version}")
28
-
29
- return local_build_version, get_ipv4(os.getenv("TDDOG_LOCAL_IFACE"))
30
- else:
31
- logger.warning(
32
- f"did not find local build pve cloud version for {artifact_key} even though TDDOG_LOCAL_IFACE env var is defined"
33
- )
34
-
35
- return None, None
36
-
37
-
38
- def get_tdd_ip():
39
- if os.getenv("TDDOG_LOCAL_IFACE"):
40
- return get_ipv4(os.getenv("TDDOG_LOCAL_IFACE"))
41
-
42
- return None
43
-
44
-
45
- # this prepends a custom wrapper func to all our e2e fixtures and allows easy toggeling
46
- # cloud fixtures can be annotated with this and and a value tuple of tags as value
47
- # they also automatically get the standard pytest fixture decorator
48
- # depending on the pytest --fixture-tags paramater, which takes a csv of fixture tags
49
- # the fixtures are automatically skipped if not in the csv
50
- def cloud_fixture(*tags):
51
- def decorator(func):
52
- func._tags = tags
53
-
54
- logger.info(f"called decorator for {func.__name__}")
55
-
56
- @pytest.fixture(scope="session")
57
- @functools.wraps(func)
58
- def wrapper(*args, **kwargs):
59
- logger.info(f"called wrapper for {func.__name__}")
60
-
61
- request = kwargs.get("request")
62
- if request is None:
63
- for arg in args:
64
- if hasattr(arg, "config"):
65
- request = arg
66
- break
67
-
68
- if request is None:
69
- logger.warning("Cannot find request object; running fixture anyway")
70
- return func(*args, **kwargs)
71
-
72
- allowed_tags_opt = request.config.getoption("--fixture-tags")
73
- if allowed_tags_opt:
74
- allowed_tags = allowed_tags_opt.split(",")
75
- if not any(tag in allowed_tags for tag in func._tags):
76
- logger.info(f"Skipping fixture {func.__name__} due to tags")
77
- if inspect.isgeneratorfunction(func):
78
- yield
79
- return
80
- else:
81
- return
82
-
83
- result = func(*args, **kwargs)
84
-
85
- if inspect.isgenerator(result):
86
- logger.info("is generator")
87
- yield from result
88
- else:
89
- logger.info("is result")
90
- return result
91
-
92
- return wrapper
93
-
94
- return decorator
95
-
96
-
97
- # load the test environment yaml from parameters
98
- @pytest.fixture(scope="session")
99
- def get_test_env(request):
100
- test_pve_yaml_file = os.getenv("PVE_CLOUD_TEST_CONF")
101
- assert test_pve_yaml_file
102
-
103
- os.environ["TF_VAR_test_pve_conf"] = test_pve_yaml_file
104
-
105
- assert test_pve_yaml_file is not None
106
- with open(test_pve_yaml_file, "r") as file:
107
- test_pve_conf = yaml.safe_load(file)
108
-
109
- logger.info(f"terraform inv file {test_pve_yaml_file}")
110
-
111
- # load schema and validate
112
- with open(
113
- os.path.dirname(os.path.realpath(__file__)) + "/test_env_schema.yaml"
114
- ) as file:
115
- test_env_schema = yaml.safe_load(file)
116
-
117
- jsonschema.validate(instance=test_pve_conf, schema=test_env_schema)
118
-
119
- # render vlan tag
120
- if "pve_test_net0_vlan_tag" in test_pve_conf:
121
- test_pve_conf["net0_vlan_tag_rendered"] = (
122
- f",tag={test_pve_conf['pve_test_net0_vlan_tag']}"
123
- )
124
-
125
- # validate that target copy pve system is directly accessible (no jump host validation supported)
126
- copy_cloud_domain = get_cloud_domain(
127
- test_pve_conf["kubernetes"]["k8s_tls_copy_target_pve"]
128
- )
129
- copy_pve_inventory = get_pve_inventory(copy_cloud_domain)
130
-
131
- copy_target_cluster = get_target_cluster(
132
- copy_pve_inventory,
133
- test_pve_conf["kubernetes"]["k8s_tls_copy_target_pve"],
134
- target_cloud_domain=copy_cloud_domain,
135
- )
136
-
137
- assert "jump_hosts" not in copy_pve_inventory[copy_target_cluster]
138
-
139
- return test_pve_conf
140
-
141
-
142
15
  @pytest.fixture(scope="session")
143
16
  def get_secondary_kubespray_inv(get_test_env):
144
17
  logger.info("create secondary kubespray")
@@ -165,8 +38,6 @@ def get_secondary_kubespray_inv(get_test_env):
165
38
  + get_test_env["cloud_inventory"]["pve_cloud_domain"],
166
39
  "postgres_stack": "ha-postgres."
167
40
  + get_test_env["cloud_inventory"]["pve_cloud_domain"],
168
- "cache_stack": "cloud-cache."
169
- + get_test_env["cloud_inventory"]["pve_cloud_domain"],
170
41
  },
171
42
  "tcp_proxies": [],
172
43
  "external_domains": [],
@@ -212,8 +83,6 @@ def get_secondary_kubespray_inv(get_test_env):
212
83
 
213
84
  temp_kubespray_inv.flush()
214
85
 
215
- os.environ["TF_VAR_e2e_secondary_kubespray_inv"] = temp_kubespray_inv.name
216
-
217
86
  return temp_kubespray_inv.name
218
87
 
219
88
 
@@ -241,8 +110,6 @@ def get_kubespray_inv(get_test_env):
241
110
  + get_test_env["cloud_inventory"]["pve_cloud_domain"],
242
111
  "postgres_stack": "ha-postgres."
243
112
  + get_test_env["cloud_inventory"]["pve_cloud_domain"],
244
- "cache_stack": "cloud-cache."
245
- + get_test_env["cloud_inventory"]["pve_cloud_domain"],
246
113
  },
247
114
  "tcp_proxies": [
248
115
  {
@@ -323,7 +190,7 @@ def get_kubespray_inv(get_test_env):
323
190
  },
324
191
  "parameters": {
325
192
  "cores": 4,
326
- "memory": 12288,
193
+ "memory": 8192,
327
194
  },
328
195
  },
329
196
  ],
@@ -334,23 +201,118 @@ def get_kubespray_inv(get_test_env):
334
201
  )
335
202
  temp_kubespray_inv.flush()
336
203
 
337
- os.environ["TF_VAR_e2e_kubespray_inv"] = temp_kubespray_inv.name
338
-
339
204
  return temp_kubespray_inv.name
340
205
 
341
206
 
342
- # connect proxmoxer to pve cluster
207
+ def get_kubeconfig(get_test_env, pve_host, stack_name):
208
+ # assumes loaded ssh key like all playbooks
209
+ proxmox = ProxmoxAPI(pve_host, user="root", backend="ssh_paramiko")
210
+
211
+ # find k8s master
212
+ master_qemu = None
213
+ host_node = None
214
+ for node in proxmox.nodes.get():
215
+ for qemu in proxmox.nodes(node["node"]).qemu.get():
216
+ if (
217
+ "tags" in qemu
218
+ and stack_name
219
+ + "."
220
+ + get_test_env["cloud_inventory"]["pve_cloud_domain"]
221
+ in qemu["tags"]
222
+ and "master" in qemu["tags"]
223
+ ):
224
+ master_qemu = qemu
225
+ host_node = node["node"]
226
+ break
227
+
228
+ assert master_qemu
229
+ assert host_node
230
+ logger.info(master_qemu)
231
+
232
+ ifaces = (
233
+ proxmox.nodes(host_node)
234
+ .qemu(master_qemu["vmid"])
235
+ .agent("network-get-interfaces")
236
+ .get()
237
+ )
238
+
239
+ master_ipv4 = None
240
+
241
+ for iface in ifaces["result"]:
242
+ if iface["name"] == "lo":
243
+ continue # skip the first loopback device
244
+
245
+ # after that comes the primary interface
246
+ for ip_address in iface["ip-addresses"]:
247
+ if ip_address["ip-address-type"] == "ipv4":
248
+ master_ipv4 = ip_address["ip-address"]
249
+ break
250
+
251
+ assert master_ipv4
252
+
253
+ break
254
+
255
+ # now we can use that address to connect via ssh
256
+ ssh = paramiko.SSHClient()
257
+ ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
258
+ ssh.connect(master_ipv4, username="admin")
259
+
260
+ # since we need root we cant use sftp and root via ssh is disabled
261
+ _, stdout, _ = ssh.exec_command("sudo cat /etc/kubernetes/admin.conf")
262
+
263
+ kubeconfig = (
264
+ stdout.read()
265
+ .decode("utf-8")
266
+ .replace("https://127.0.0.1:6443", f"https://{master_ipv4}:6443")
267
+ )
268
+ assert kubeconfig
269
+
270
+ return kubeconfig
271
+
272
+
343
273
  @pytest.fixture(scope="session")
344
- def get_proxmoxer(get_test_env):
345
- first_test_host = get_test_env["pve_test_cluster_hosts"][
274
+ def get_primary_kubeconfig(get_test_env):
275
+ test_host = get_test_env["pve_test_cluster_hosts"][
346
276
  next(iter(get_test_env["pve_test_cluster_hosts"]))
347
277
  ]
278
+ return get_kubeconfig(get_test_env, test_host["ansible_host"], "pytest-k8s")
348
279
 
349
- proxmox = ProxmoxAPI(
350
- first_test_host["ansible_host"], user="root", backend="ssh_paramiko"
280
+
281
+ @pytest.fixture(scope="session")
282
+ def get_secondary_kubeconfig(get_test_env):
283
+ test_host = get_test_env["pve_test_cluster_hosts"][
284
+ next(iter(get_test_env["pve_test_cluster_hosts"]))
285
+ ]
286
+ return get_kubeconfig(
287
+ get_test_env, test_host["ansible_host"], "pytest-secondary-k8s"
351
288
  )
352
- nodes = proxmox.nodes.get()
353
289
 
354
- assert nodes
355
290
 
356
- return proxmox
291
+ @pytest.fixture(scope="session")
292
+ def get_k8s_api_v1(get_primary_kubeconfig):
293
+ kubeconfig = get_primary_kubeconfig
294
+
295
+ # auth kubernetes api
296
+ with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file:
297
+ temp_file.write(kubeconfig)
298
+ temp_file.flush()
299
+ config.load_kube_config(config_file=temp_file.name)
300
+
301
+ v1 = client.CoreV1Api()
302
+
303
+ return v1
304
+
305
+
306
+ @pytest.fixture(scope="session")
307
+ def get_k8s_secondary_api_v1(get_secondary_kubeconfig):
308
+ kubeconfig = get_secondary_kubeconfig
309
+
310
+ # auth kubernetes api
311
+ with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file:
312
+ temp_file.write(kubeconfig)
313
+ temp_file.flush()
314
+ config.load_kube_config(config_file=temp_file.name)
315
+
316
+ v1 = client.CoreV1Api()
317
+
318
+ return v1
@@ -7,13 +7,13 @@ def pytest_addoption(parser):
7
7
  "--skip-cleanup",
8
8
  action="store_true",
9
9
  default=False,
10
- help="Skips the fixture cleanup part, faster for consequtive runs / tdd.",
10
+ help="Skips the fixture cleanup part and also test cleanups that implement this flag. Setting this keeps the infra state and allows for hands on development.",
11
11
  )
12
12
  parser.addoption(
13
- "--skip-fixture-init",
13
+ "--skip-fixtures",
14
14
  action="store_true",
15
15
  default=False,
16
- help="Skips the initialization part of fixtures. Target run only test on consequtive runs.",
16
+ help="Skips fixtures alltogether. Target run only test on consequtive runs. This will also skip the cleanup of the fixture.",
17
17
  )
18
18
  # only avaible in pxc_cloud collection (decorator is defined there)
19
19
  parser.addoption(
@@ -29,15 +29,3 @@ def pytest_addoption(parser):
29
29
  help="Skips the kubespray playbook part when syncing test kubespray clusters. This saves a lot of time in total.",
30
30
  )
31
31
  parser.addoption("--ansible-verbosity", type=int, choices=[1, 2, 3], default=0)
32
- parser.addoption(
33
- "--skip-apply",
34
- action="store_true",
35
- default=False,
36
- help="Skips the terraform apply part, helps with faster writing tests.",
37
- )
38
- parser.addoption(
39
- "--tf-upgrade",
40
- action="store_true",
41
- default=False,
42
- help="Runs init --upgrade instead of just init for terraform scenarios.",
43
- )
@@ -237,6 +237,9 @@ class PyCodeChangedHandler(FileSystemEventHandler):
237
237
  with open(
238
238
  self.workdir / self.config["build"]["dyn_version_py_path"], "w"
239
239
  ) as f:
240
+ print(
241
+ f"writing {self.config['build']['dyn_version_py_path']} {version}"
242
+ )
240
243
  f.write(f'__version__ = "{version}"\n')
241
244
 
242
245
  error = False
@@ -347,10 +350,15 @@ def init_local(dog_settings, subdir_name):
347
350
  latest_semver_tag.replace(patch=datetime.now().strftime("%m%d%H%S%f"))
348
351
  )
349
352
 
350
- with open(
351
- Path(subdir_name) / dog_settings["local"]["dyn_version_py_path"], "w"
352
- ) as f:
353
- f.write(f'__version__ = "{version}"\n')
353
+ # only write out tdd version file if nothing has yet been generated. otherwise this will collide with
354
+ # the build handlers
355
+ dyn_version_path = Path(subdir_name) / dog_settings["local"]["dyn_version_py_path"]
356
+
357
+ if not dyn_version_path.exists():
358
+ with open(
359
+ Path(subdir_name) / dog_settings["local"]["dyn_version_py_path"], "w"
360
+ ) as f:
361
+ f.write(f'__version__ = "{version}"\n')
354
362
 
355
363
  # just install locally with all deps unmod
356
364
  # install the package
@@ -375,7 +383,7 @@ def init_local(dog_settings, subdir_name):
375
383
  # all subfolders, build a dependency graph based on the [redis] version_key
376
384
  # and dependant projects in [build] sub_rebuild_keys / [init] dep_build_keys.
377
385
  # it will launch those furthest up the chain first
378
- def dog_recursive(done_handler):
386
+ def dog_recursive(done_handler, oneshot):
379
387
 
380
388
  # find all tddog toml files
381
389
  toml_file_graph = {}
@@ -419,20 +427,22 @@ def dog_recursive(done_handler):
419
427
  return
420
428
 
421
429
  # we recursed down to an artifact without any dependencies / processed the dependencies first
430
+ if oneshot:
431
+ run_oneshot(dog_settings, subdir_name)
432
+ else:
433
+ print(f"launching {subdir_name}")
434
+ dog_observers, dog_handlers = launch_dog(
435
+ dog_settings, done_handler, subdir_name
436
+ )
437
+ observers.extend(
438
+ dog_observers
439
+ ) # launch the build observer (that also builds initially)
422
440
 
423
- print(f"launching {subdir_name}")
424
- dog_observers, dog_handlers = launch_dog(
425
- dog_settings, done_handler, subdir_name
426
- )
427
- observers.extend(
428
- dog_observers
429
- ) # launch the build observer (that also builds initially)
430
-
431
- handlers.extend(dog_handlers) # add handler for initial build
441
+ handlers.extend(dog_handlers) # add handler for initial build
432
442
 
433
- # install the project locally if required
434
- if "local" in dog_settings:
435
- init_local(dog_settings, subdir_name)
443
+ # install the project locally if required
444
+ if "local" in dog_settings:
445
+ init_local(dog_settings, subdir_name)
436
446
 
437
447
  # add project to launch guard
438
448
  launched_subdirs.add(subdir_name)
@@ -441,6 +451,11 @@ def dog_recursive(done_handler):
441
451
  for subdir_name, dog_settings in toml_file_graph.values():
442
452
  launch_observers_recursive(subdir_name, dog_settings)
443
453
 
454
+ # oneshot can quit right away
455
+ if oneshot:
456
+ print("oneshot done!")
457
+ return
458
+
444
459
  # trigger initial builds
445
460
  for handler in handlers:
446
461
  handler.run()
@@ -490,21 +505,8 @@ def launch(args):
490
505
 
491
506
  done_handler = DoneHandler()
492
507
 
493
- if args.oneshot:
494
- if not os.path.exists("tddog.toml"):
495
- print("tddog.toml doesnt exist / not in current dir for this project.")
496
- return
497
-
498
- with open("tddog.toml", "rb") as f:
499
- dog_settings = tomllib.load(f)
500
-
501
- print("running oneshot build")
502
- run_oneshot(dog_settings, ".")
503
- print("oneshot build finished")
504
- return
505
-
506
508
  if args.recursive:
507
- dog_recursive(done_handler)
509
+ dog_recursive(done_handler, args.oneshot)
508
510
  else:
509
511
  # standalone launching is simpler, no need for any recursion
510
512
  if not os.path.exists("tddog.toml"):
@@ -515,6 +517,14 @@ def launch(args):
515
517
  dog_settings = tomllib.load(f)
516
518
 
517
519
  pprint.pprint(dog_settings)
520
+ if args.oneshot:
521
+ print("running oneshot build")
522
+ run_oneshot(dog_settings, ".")
523
+ print("oneshot build finished")
524
+ # init local is called in run_oneshot
525
+ return
526
+
527
+ # tdd active flow monitoring changes
518
528
  observers, handlers = launch_dog(dog_settings, done_handler, ".")
519
529
 
520
530
  for handler in handlers:
@@ -0,0 +1,220 @@
1
+ import base64
2
+ import getpass
3
+ import logging
4
+ import os
5
+ import shutil
6
+ import subprocess
7
+ from contextlib import contextmanager
8
+
9
+ import netifaces
10
+ import paramiko
11
+ import yaml
12
+ from jinja2 import Environment, FileSystemLoader
13
+ from pve_cloud.lib.inventory import get_pve_inventory
14
+ from pytest_httpserver import HTTPServer
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ def get_ipv4(iface):
20
+ if iface in netifaces.interfaces():
21
+ info = netifaces.ifaddresses(iface)
22
+ ipv4 = info.get(netifaces.AF_INET, [{}])[0].get("addr")
23
+ return ipv4
24
+ return None
25
+
26
+
27
+ def get_tf_env_vars(
28
+ module_name, scenario_name, kube_v1, get_test_env, get_kubespray_inv
29
+ ):
30
+ # set env vars for terraform backend / variables passed via env
31
+ tf_env_vars = {}
32
+ tf_env_vars["PG_SCHEMA_NAME"] = f"pytest-{module_name}-{scenario_name}"
33
+
34
+ tf_env_vars["TF_VAR_test_pve_conf"] = os.getenv(
35
+ "PVE_CLOUD_TEST_CONF"
36
+ ) # path to test env
37
+
38
+ # current machine IPV4 made accessible for tf var
39
+ tf_env_vars["TF_VAR_dev_machine_ipv4"] = get_ipv4(os.getenv("TDDOG_LOCAL_IFACE"))
40
+
41
+ # connect to pve host and collect secrets / conf
42
+ first_test_host = get_test_env["pve_test_cluster_hosts"][
43
+ next(iter(get_test_env["pve_test_cluster_hosts"]))
44
+ ]
45
+
46
+ ssh = paramiko.SSHClient()
47
+ ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
48
+ ssh.connect(first_test_host["ansible_host"], username="root")
49
+
50
+ _, stdout, _ = ssh.exec_command("sudo cat /etc/pve/cloud/secrets/patroni.pass")
51
+ patroni_pass = stdout.read().decode("utf-8")
52
+
53
+ pg_conn_str = f"postgres://postgres:{patroni_pass}@{get_test_env['pve_test_cluster_floating_internal']}:5000/tf_states?sslmode=disable"
54
+ pg_conn_str_orm = f"postgresql+psycopg2://postgres:{patroni_pass}@{get_test_env['pve_test_cluster_floating_internal']}:5000/pve_cloud?sslmode=disable"
55
+
56
+ # variables that terraform applies in test will use
57
+ tf_env_vars["PG_CONN_STR"] = pg_conn_str
58
+ tf_env_vars["TF_VAR_pve_cloud_pg_cstr"] = pg_conn_str_orm
59
+ tf_env_vars["TF_VAR_pve_ansible_host"] = first_test_host["ansible_host"]
60
+
61
+ pve_inventory = get_pve_inventory(
62
+ get_test_env["cloud_inventory"]["pve_cloud_domain"]
63
+ )
64
+ pve_64 = yaml.safe_dump(pve_inventory)
65
+ tf_env_vars["TF_VAR_pve_inventory_b64"] = base64.b64encode(
66
+ pve_64.encode("utf-8")
67
+ ).decode("utf-8")
68
+
69
+ # render terraformrc jinja2 and set env
70
+ j2_env = Environment(loader=FileSystemLoader(f"{os.getcwd()}/tests"))
71
+ rc_tmp = j2_env.get_template(".terraformrc-e2e.j2")
72
+
73
+ with open(f"{os.getcwd()}/tests/.terraformrc-e2e", "w") as f:
74
+ f.write(rc_tmp.render({"user_name": getpass.getuser()}))
75
+
76
+ tf_env_vars["TF_CLI_CONFIG_FILE"] = f"{os.getcwd()}/tests/.terraformrc-e2e"
77
+
78
+ # write testing kubespray inv and set the path (for provider init)
79
+ tf_env_vars["TF_VAR_e2e_kubespray_inv"] = get_kubespray_inv
80
+
81
+ return tf_env_vars
82
+
83
+
84
+ def apply(
85
+ module_name, scenario_name, kube_v1, get_test_env, get_kubespray_inv, extra_env={}
86
+ ):
87
+ logger.info(f"applying terraform {scenario_name}")
88
+
89
+ tf_env_vars = get_tf_env_vars(
90
+ module_name, scenario_name, kube_v1, get_test_env, get_kubespray_inv
91
+ )
92
+
93
+ # create env to pass to tf procs + write sourcable debug.env file
94
+ terraform_env = os.environ.copy()
95
+
96
+ with open(
97
+ f"{os.getcwd()}/tests/scenarios/{scenario_name}/.debug.env", "w"
98
+ ) as dbg_env:
99
+ for ek, ev in (tf_env_vars | extra_env).items():
100
+ terraform_env[ek] = ev
101
+ dbg_env.write(f"export {ek}='{ev}'\n")
102
+
103
+ # writeout pytest current flag to get same behaviour for tf provider
104
+ dbg_env.write(
105
+ f"export PYTEST_CURRENT_TEST='{os.getenv('PYTEST_CURRENT_TEST')}'"
106
+ )
107
+
108
+ subprocess.run(
109
+ ["terraform", "init", "--upgrade"],
110
+ cwd=f"{os.getcwd()}/tests/scenarios/{scenario_name}",
111
+ env=terraform_env,
112
+ check=True,
113
+ text=True,
114
+ )
115
+
116
+ subprocess.run(
117
+ ["terraform", "apply", "-auto-approve"],
118
+ cwd=f"{os.getcwd()}/tests/scenarios/{scenario_name}",
119
+ env=terraform_env,
120
+ check=True,
121
+ text=True,
122
+ )
123
+
124
+ # wait and assert all pods are running
125
+ while True:
126
+ all_pods_running = True
127
+
128
+ for pod in kube_v1.list_pod_for_all_namespaces().items:
129
+ phase = pod.status.phase
130
+ assert (
131
+ phase != "Failed"
132
+ ), f"pod {pod.metadata.name} failed!" # failed pods end tests immediatly
133
+
134
+ if phase not in ["Running", "Succeeded"]:
135
+ all_pods_running = False
136
+ logger.info(f"pod {pod.metadata.name} in phase {phase}")
137
+
138
+ if all_pods_running:
139
+ break
140
+ else:
141
+ logger.info("pods still initializing")
142
+
143
+
144
+ def destroy(
145
+ module_name, scenario_name, kube_v1, get_test_env, get_kubespray_inv, extra_env={}
146
+ ):
147
+ logger.info(f"destroying terraform {scenario_name}")
148
+
149
+ tf_env_vars = get_tf_env_vars(
150
+ module_name, scenario_name, kube_v1, get_test_env, get_kubespray_inv
151
+ )
152
+
153
+ # create env to pass to tf procs + write sourcable debug.env file
154
+ terraform_env = os.environ.copy()
155
+
156
+ for ek, ev in (tf_env_vars | extra_env).items():
157
+ terraform_env[ek] = ev
158
+
159
+ subprocess.run(
160
+ ["terraform", "destroy", "-auto-approve"],
161
+ cwd=f"{os.getcwd()}/tests/scenarios/{scenario_name}",
162
+ env=terraform_env,
163
+ check=True,
164
+ text=True,
165
+ )
166
+
167
+ shutil.rmtree(f"{os.getcwd()}/tests/scenarios/{scenario_name}/.terraform")
168
+
169
+
170
+ @contextmanager
171
+ def get_mc_gw_http_mock():
172
+ server = HTTPServer(host="0.0.0.0", port=8888)
173
+ server.start()
174
+
175
+ server.expect_request("/get-client-alertmanagers", method="GET").respond_with_json(
176
+ [
177
+ {
178
+ "secret_name": "e2e-dummy",
179
+ "secret_data": {
180
+ "host": "alrtmgr.e2e.dummy.domain",
181
+ "k8s_stack_name": "e2e-dummy-stack",
182
+ "password": "dummy-pw",
183
+ },
184
+ "cloud_domain": "e2e.dummy.domain",
185
+ }
186
+ ]
187
+ )
188
+
189
+ server.expect_request("/get-gotify-master", method="GET").respond_with_json(
190
+ {
191
+ "gotify_present": True,
192
+ "gotify_access": {"host": "gotify.dummy.domain", "password": "dummy-pw"},
193
+ }
194
+ )
195
+
196
+ server.expect_request("/get-victoria-clients", method="GET").respond_with_json(
197
+ [
198
+ {
199
+ "secret_name": "e2e-dummy",
200
+ "secret_data": {
201
+ "host": "vlogs.e2e.dummy.domain",
202
+ "k8s_stack_name": "e2e-dummy-stack",
203
+ },
204
+ "cloud_domain": "e2e.dummy.domain",
205
+ }
206
+ ]
207
+ )
208
+
209
+ server.expect_request("/get-vlselect-auth", method="GET").respond_with_json(
210
+ {"auth_present": True, "vlselect_auth": {"password": "dummy-pw"}}
211
+ )
212
+ try:
213
+ yield server
214
+ finally:
215
+ server.stop()
216
+
217
+
218
+ def launch_gw_mock_manually():
219
+ with get_mc_gw_http_mock():
220
+ input("running server, press key to terminate")
@@ -8,6 +8,7 @@ required:
8
8
  - pve_test_cluster_hosts
9
9
  - pve_test_cluster_floating_external
10
10
  - pve_test_cluster_floating_internal
11
+ - pve_test_cloud_mirror_ip
11
12
  - pve_vm_storage_id
12
13
  - ceph_csi_storage_pool
13
14
  - ssh_pub_key
@@ -55,6 +56,10 @@ properties:
55
56
  type: string
56
57
  description: Central haproxy floating ip for internal traffic and services.
57
58
 
59
+ pve_test_cloud_mirror_ip:
60
+ type: string
61
+ description: Static ip for cloud central mirror vm.
62
+
58
63
  pve_vm_storage_id:
59
64
  type: string
60
65
  description: Storage id string created in the proxmox ui. Can be ceph pool as storage or local-lvm, local-zfs, etc.
@@ -140,19 +145,22 @@ properties:
140
145
  type: object
141
146
  description: config options for test kubespray clusters that the suite creates.
142
147
  required:
143
- - k8s_tls_copy_target_pve
144
- - k8s_tls_copy_stack_name
145
148
  - deployments_domain
149
+ - copy_target_pve
150
+ - tls_copy_stack_name
146
151
  properties:
147
- k8s_tls_copy_target_pve:
152
+ deployments_domain:
153
+ type: string
154
+ description: domain used for test deployments of the suites kubernetes clusters. This needs to be delegated correctly in your central dns.
155
+ copy_target_pve:
148
156
  type: string
149
157
  description: Target proxmox cluster (external) from which to copy tls certificates. Dev machine needs ssh to it and dns needs to be configured.
150
- k8s_tls_copy_stack_name:
158
+ tls_copy_stack_name:
151
159
  type: string
152
160
  description: Stack name inside the target pve from which the test suite will copy a valid acme cert. This clusters cert should be extend to contain the deployments_domain.
153
- deployments_domain:
161
+ harbor_copy_mirror_host:
154
162
  type: string
155
- description: domain used for test deployments of the suites kubernetes clusters. This needs to be delegated correctly in your central dns.
163
+ description: The mirror host to use instead of the harbor that is deployed in the e2e testing suite.
156
164
 
157
165
  terraform_parameters:
158
166
  type: object
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytest-pve-cloud
3
- Version: 3.1.4
3
+ Version: 3.2.0
4
4
  Author-email: Tobias Huebner <tobias.huebner@vmzberlin.com>
5
5
  License-Expression: GPL-3.0-or-later
6
6
  License-File: LICENSE.md
7
- Requires-Dist: py-pve-cloud<3.2.0,>=3.1.4
7
+ Requires-Dist: py-pve-cloud<3.3.0,>=3.2.0
8
8
  Requires-Dist: kubernetes==34.1.0
9
9
  Requires-Dist: pytest==8.4.2
10
10
  Requires-Dist: ansible-runner==2.4.2
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ mock-server = pve_cloud_test.terraform:launch_gw_mock_manually
3
+ tddog = pve_cloud_test.tdd_watchdog:main
@@ -1,4 +1,4 @@
1
- py-pve-cloud<3.2.0,>=3.1.4
1
+ py-pve-cloud<3.3.0,>=3.2.0
2
2
  kubernetes==34.1.0
3
3
  pytest==8.4.2
4
4
  ansible-runner==2.4.2
@@ -1 +0,0 @@
1
- __version__ = "3.1.4"
@@ -1,168 +0,0 @@
1
- import base64
2
- import logging
3
- import os
4
- import re
5
- import tempfile
6
-
7
- import paramiko
8
- import pytest
9
- from kubernetes import client, config
10
- from proxmoxer import ProxmoxAPI
11
- from pve_cloud.lib.inventory import *
12
-
13
- from pve_cloud_test.cloud_fixtures import *
14
-
15
- logger = logging.getLogger(__name__)
16
-
17
-
18
- def get_kubeconfig(get_test_env, pve_host, stack_name):
19
- # assumes loaded ssh key like all playbooks
20
- proxmox = ProxmoxAPI(pve_host, user="root", backend="ssh_paramiko")
21
-
22
- # find k8s master
23
- master_qemu = None
24
- host_node = None
25
- for node in proxmox.nodes.get():
26
- for qemu in proxmox.nodes(node["node"]).qemu.get():
27
- if (
28
- "tags" in qemu
29
- and stack_name
30
- + "."
31
- + get_test_env["cloud_inventory"]["pve_cloud_domain"]
32
- in qemu["tags"]
33
- and "master" in qemu["tags"]
34
- ):
35
- master_qemu = qemu
36
- host_node = node["node"]
37
- break
38
-
39
- assert master_qemu
40
- assert host_node
41
- logger.info(master_qemu)
42
-
43
- ifaces = (
44
- proxmox.nodes(host_node)
45
- .qemu(master_qemu["vmid"])
46
- .agent("network-get-interfaces")
47
- .get()
48
- )
49
-
50
- master_ipv4 = None
51
-
52
- for iface in ifaces["result"]:
53
- if iface["name"] == "lo":
54
- continue # skip the first loopback device
55
-
56
- # after that comes the primary interface
57
- for ip_address in iface["ip-addresses"]:
58
- if ip_address["ip-address-type"] == "ipv4":
59
- master_ipv4 = ip_address["ip-address"]
60
- break
61
-
62
- assert master_ipv4
63
-
64
- break
65
-
66
- # now we can use that address to connect via ssh
67
- ssh = paramiko.SSHClient()
68
- ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
69
- ssh.connect(master_ipv4, username="admin")
70
-
71
- # since we need root we cant use sftp and root via ssh is disabled
72
- _, stdout, _ = ssh.exec_command("sudo cat /etc/kubernetes/admin.conf")
73
-
74
- kubeconfig = (
75
- stdout.read()
76
- .decode("utf-8")
77
- .replace("https://127.0.0.1:6443", f"https://{master_ipv4}:6443")
78
- )
79
- assert kubeconfig
80
-
81
- return kubeconfig
82
-
83
-
84
- @pytest.fixture(scope="session")
85
- def get_primary_kubeconfig(get_test_env):
86
- test_host = get_test_env["pve_test_cluster_hosts"][
87
- next(iter(get_test_env["pve_test_cluster_hosts"]))
88
- ]
89
- return get_kubeconfig(get_test_env, test_host["ansible_host"], "pytest-k8s")
90
-
91
-
92
- @pytest.fixture(scope="session")
93
- def get_secondary_kubeconfig(get_test_env):
94
- test_host = get_test_env["pve_test_cluster_hosts"][
95
- next(iter(get_test_env["pve_test_cluster_hosts"]))
96
- ]
97
- return get_kubeconfig(
98
- get_test_env, test_host["ansible_host"], "pytest-secondary-k8s"
99
- )
100
-
101
-
102
- @pytest.fixture(scope="session")
103
- def set_pve_cloud_auth(request, get_test_env, get_kubespray_inv):
104
- logger.info("setting pve cloud auth env variables for tf")
105
- first_test_host = get_test_env["pve_test_cluster_hosts"][
106
- next(iter(get_test_env["pve_test_cluster_hosts"]))
107
- ]
108
-
109
- ssh = paramiko.SSHClient()
110
- ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
111
- ssh.connect(first_test_host["ansible_host"], username="root")
112
-
113
- _, stdout, _ = ssh.exec_command("sudo cat /etc/pve/cloud/secrets/patroni.pass")
114
- patroni_pass = stdout.read().decode("utf-8")
115
-
116
- pg_conn_str = f"postgres://postgres:{patroni_pass}@{get_test_env['pve_test_cluster_floating_internal']}:5000/tf_states?sslmode=disable"
117
- pg_conn_str_orm = f"postgresql+psycopg2://postgres:{patroni_pass}@{get_test_env['pve_test_cluster_floating_internal']}:5000/pve_cloud?sslmode=disable"
118
-
119
- # variables that terraform applies in test will use
120
- os.environ["PG_CONN_STR"] = pg_conn_str
121
- os.environ["TF_VAR_pve_cloud_pg_cstr"] = pg_conn_str_orm
122
- os.environ["TF_VAR_pve_ansible_host"] = first_test_host["ansible_host"]
123
-
124
- pve_inventory = get_pve_inventory(
125
- get_test_env["cloud_inventory"]["pve_cloud_domain"]
126
- )
127
- pve_64 = yaml.safe_dump(pve_inventory)
128
- os.environ["TF_VAR_pve_inventory_b64"] = base64.b64encode(
129
- pve_64.encode("utf-8")
130
- ).decode("utf-8")
131
-
132
- # fetch bind update key for ingress dns validation
133
- _, stdout, _ = ssh.exec_command("sudo cat /etc/pve/cloud/secrets/internal.key")
134
- bind_key_file = stdout.read().decode("utf-8")
135
-
136
- bind_internal_key = re.search(r'secret\s+"([^"]+)";', bind_key_file).group(1)
137
-
138
- return {"bind_internal_key": bind_internal_key}
139
-
140
-
141
- @pytest.fixture(scope="session")
142
- def get_k8s_api_v1(get_primary_kubeconfig):
143
- kubeconfig = get_primary_kubeconfig
144
-
145
- # auth kubernetes api
146
- with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file:
147
- temp_file.write(kubeconfig)
148
- temp_file.flush()
149
- config.load_kube_config(config_file=temp_file.name)
150
-
151
- v1 = client.CoreV1Api()
152
-
153
- return v1
154
-
155
-
156
- @pytest.fixture(scope="session")
157
- def get_k8s_secondary_api_v1(get_secondary_kubeconfig):
158
- kubeconfig = get_secondary_kubeconfig
159
-
160
- # auth kubernetes api
161
- with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file:
162
- temp_file.write(kubeconfig)
163
- temp_file.flush()
164
- config.load_kube_config(config_file=temp_file.name)
165
-
166
- v1 = client.CoreV1Api()
167
-
168
- return v1
@@ -1,87 +0,0 @@
1
- import getpass
2
- import logging
3
- import os
4
- import shutil
5
- import subprocess
6
-
7
- import netifaces
8
- from jinja2 import Environment, FileSystemLoader
9
-
10
- logger = logging.getLogger(__name__)
11
-
12
-
13
- def get_ipv4(iface):
14
- if iface in netifaces.interfaces():
15
- info = netifaces.ifaddresses(iface)
16
- ipv4 = info.get(netifaces.AF_INET, [{}])[0].get("addr")
17
- return ipv4
18
- return None
19
-
20
-
21
- def apply(module_name, scenario_name, v1, upgrade=False, inject_rc=False):
22
- logger.info(f"applying terraform {scenario_name}")
23
- os.environ["PG_SCHEMA_NAME"] = f"pytest-{module_name}-{scenario_name}"
24
-
25
- # now we can set env / vars and apply our test scenario
26
- init_cmd = ["terraform", "init"]
27
- if upgrade:
28
- init_cmd.append("--upgrade")
29
-
30
- # render terraformrc jinja2
31
- j2_env = Environment(loader=FileSystemLoader(f"{os.getcwd()}/tests"))
32
- rc_tmp = j2_env.get_template(".terraformrc-e2e.j2")
33
-
34
- with open(f"{os.getcwd()}/tests/.terraformrc-e2e", "w") as f:
35
- f.write(rc_tmp.render({"user_name": getpass.getuser()}))
36
-
37
- init_env = os.environ.copy()
38
- if inject_rc:
39
- init_env["TF_CLI_CONFIG_FILE"] = f"{os.getcwd()}/tests/.terraformrc-e2e"
40
-
41
- # current machine IPV4 made accessible for tf var
42
- os.environ["TF_VAR_dev_machine_ipv4"] = get_ipv4(os.getenv("TDDOG_LOCAL_IFACE"))
43
-
44
- subprocess.run(
45
- init_cmd,
46
- cwd=f"{os.getcwd()}/tests/scenarios/{scenario_name}",
47
- env=init_env,
48
- check=True,
49
- text=True,
50
- )
51
- subprocess.run(
52
- ["terraform", "apply", "-auto-approve"],
53
- cwd=f"{os.getcwd()}/tests/scenarios/{scenario_name}",
54
- check=True,
55
- text=True,
56
- )
57
-
58
- # wait and assert all pods are running
59
- while True:
60
- all_pods_running = True
61
-
62
- for pod in v1.list_pod_for_all_namespaces().items:
63
- phase = pod.status.phase
64
- assert (
65
- phase != "Failed"
66
- ), f"pod {pod.metadata.name} failed!" # failed pods end tests immediatly
67
-
68
- if phase not in ["Running", "Succeeded"]:
69
- all_pods_running = False
70
- logger.info(f"pod {pod.metadata.name} in phase {phase}")
71
-
72
- if all_pods_running:
73
- break
74
- else:
75
- logger.info("pods still initializing")
76
-
77
-
78
- def destroy(scenario_name):
79
- logger.info(f"destroying terraform {scenario_name}")
80
- subprocess.run(
81
- ["terraform", "destroy", "-auto-approve"],
82
- cwd=f"{os.getcwd()}/tests/scenarios/{scenario_name}",
83
- check=True,
84
- text=True,
85
- )
86
-
87
- shutil.rmtree(f"{os.getcwd()}/tests/scenarios/{scenario_name}/.terraform")
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- tddog = pve_cloud_test.tdd_watchdog:main