-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_examples.py
More file actions
executable file
Β·417 lines (353 loc) Β· 14.8 KB
/
Copy pathtest_examples.py
File metadata and controls
executable file
Β·417 lines (353 loc) Β· 14.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
#!/usr/bin/env python3
"""
Test runner for dockvirt examples across different systems.
Automatically tests all examples with different OS configurations.
"""
import subprocess
import sys
import time
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from click.testing import CliRunner
import re
import os
from dockvirt.cli import main as cli_main
class ExampleTester:
"""Test runner for dockvirt examples."""
def __init__(self):
self.examples_dir = Path(__file__).parent.parent / "examples"
self.test_results: Dict[str, Any] = {}
self.test_os_variants = ["ubuntu22.04", "fedora38"]
self.runner = CliRunner()
# Ensure CLI uses system libvirt unless overridden
self.env = os.environ.copy()
self.env.setdefault("LIBVIRT_DEFAULT_URI", "qemu:///system")
def parse_dockvirt(self, example_dir: Path) -> Dict[str, str]:
"""Parse .dockvirt key=value pairs into a dict."""
cfg: Dict[str, str] = {}
fpath = example_dir / ".dockvirt"
if not fpath.exists():
return cfg
try:
with fpath.open("r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" in line:
k, v = line.split("=", 1)
cfg[k.strip()] = v.strip()
except Exception as e:
print(f" β οΈ Failed to parse {fpath}: {e}")
return cfg
def get_examples(self) -> List[Path]:
"""Get list of example directories."""
examples = []
for item in self.examples_dir.iterdir():
if item.is_dir() and not item.name.startswith("."):
# Check if it has .dockvirt file or Dockerfile
if (item / ".dockvirt").exists() or (item / "Dockerfile").exists():
examples.append(item)
return examples
def build_example_image(self, example_dir: Path) -> Tuple[bool, str]:
"""Build Docker image for example."""
print(f" π¦ Building Docker image for {example_dir.name}...")
# Get image name from .dockvirt or use directory name
settings = self.parse_dockvirt(example_dir)
image_name = settings.get("image", f"test-{example_dir.name}")
# Host-side Docker build is optional now.
# Skip if instructed or if Docker is unavailable; VM will build using cloud-init.
if os.environ.get("SKIP_HOST_BUILD") == "1":
print(" βοΈ Skipping host Docker build (SKIP_HOST_BUILD=1). VM will build.")
return True, image_name
if not (example_dir / "Dockerfile").exists():
print(" βοΈ No Dockerfile in example; skipping host build.")
return True, image_name
docker_ok = subprocess.run(
"docker --version", shell=True, capture_output=True, text=True
).returncode == 0
if not docker_ok:
print(" βοΈ Docker not found on host; skipping host build. VM will build.")
return True, image_name
# Attempt host build; if it fails, continue (VM will build).
result = subprocess.run(
f"docker build -t {image_name} .",
shell=True,
capture_output=True,
text=True,
cwd=example_dir,
timeout=600, # 10 minutes for build
)
if result.returncode == 0:
print(f" β
Image {image_name} built successfully")
return True, image_name
else:
print(" β οΈ Host build failed; proceeding (VM will build image).")
return True, image_name
def test_example_vm(
self, example_dir: Path, image_name: str, os_variant: str
) -> Tuple[bool, Optional[str]]:
"""Test example by creating VM and checking if it responds."""
vm_name = f"test-{example_dir.name}-{os_variant}"
settings = self.parse_dockvirt(example_dir)
domain = settings.get("domain", f"{vm_name}.test.local")
port = settings.get("port", "80")
print(f" π Creating VM: {vm_name} with {os_variant}...")
# Create VM
result = self.runner.invoke(
cli_main,
[
"up",
"--name",
vm_name,
"--domain",
domain,
"--image",
image_name,
"--port",
str(port),
"--os",
os_variant,
],
catch_exceptions=False, # So we see the full traceback
env=self.env,
)
if result.exit_code != 0:
print(f" β Failed to create VM: {result.output}")
return False, result.output
print(f" β
VM {vm_name} created successfully")
# Wait for VM to be ready
print(" β³ Waiting for VM to be ready...")
time.sleep(60) # Wait 1 minute for startup
# Try to get VM IP and test connectivity
ip_result = self.runner.invoke(
cli_main, ["ip", "--name", vm_name], env=self.env
)
ip_ok = False
ip_http_ok = False
domain_resolves = False
domain_http_ok = False
if ip_result.exit_code == 0 and ip_result.output.strip():
# Extract bare IPv4 address from CLI output
m = re.search(r"(\d{1,3}(?:\.\d{1,3}){3})", ip_result.output)
ip = m.group(1) if m else ""
if ip:
print(f" π VM IP: {ip}")
ip_ok = True
else:
print(" β οΈ Could not parse IP from CLI output")
# Test HTTP connectivity (IP)
ip_url = f"http://{ip}/" if str(port) == "80" else f"http://{ip}:{port}/"
http_result = subprocess.run(
f"curl -s -o /dev/null -w '%{{http_code}}' {ip_url}",
shell=True,
capture_output=True,
text=True,
timeout=30,
)
if http_result.returncode == 0 and http_result.stdout.strip() == "200":
print(" β
VM responding to HTTP requests")
ip_http_ok = True
else:
print(f" β οΈ VM created but HTTP not responding (code: {http_result.stdout.strip()})")
# Check domain DNS resolution
try:
dns = subprocess.run(
f"getent hosts {domain}", shell=True,
capture_output=True, text=True, timeout=5
)
domain_resolves = dns.returncode == 0 and dns.stdout.strip() != ""
except Exception:
domain_resolves = False
if domain_resolves:
print(f" π§ Domain resolves: {domain}")
else:
print(f" β Domain does not resolve locally: {domain}")
print(f" Tip: sudo sh -c 'echo \"{ip} {domain}\" >> /etc/hosts'")
# HTTP check via domain URL (respect port)
dom_url = (
f"http://{domain}/" if str(port) == "80" else f"http://{domain}:{port}/"
)
http_dom = subprocess.run(
f"curl -s -o /dev/null -w '%{{http_code}}' {dom_url}",
shell=True, capture_output=True, text=True, timeout=15
)
if http_dom.returncode == 0 and http_dom.stdout.strip() == "200":
print(f" β
HTTP via domain OK: {dom_url}")
domain_http_ok = True
else:
print(f" β οΈ HTTP via domain failed (code: {http_dom.stdout.strip()}): {dom_url}")
else:
print(" β οΈ Could not get VM IP address.")
# Cleanup - destroy VM
print(f" π§Ή Cleaning up VM {vm_name}...")
self.runner.invoke(cli_main, ["down", "--name", vm_name], env=self.env)
# Require domain resolution and HTTP success via domain
success = domain_resolves and domain_http_ok
error = None
if not success:
reasons = []
if not ip_ok:
reasons.append("no IP")
if ip_ok and not ip_http_ok:
reasons.append("HTTP on IP failed")
if not domain_resolves:
reasons.append("domain not resolving locally")
if domain_resolves and not domain_http_ok:
reasons.append("HTTP via domain failed")
error = ", ".join(reasons) if reasons else "unknown"
return success, error
def test_example(self, example_dir: Path) -> Dict[str, Any]:
"""Test single example across different OS variants."""
print(f"\nπ Testing example: {example_dir.name}")
print("=" * 50)
results = {
"name": example_dir.name,
"path": str(example_dir),
"build_success": False,
"image_name": "",
"domain": self.parse_dockvirt(example_dir).get("domain", ""),
"os_tests": {},
}
# Build Docker image
build_success, image_name = self.build_example_image(example_dir)
results["build_success"] = build_success
results["image_name"] = image_name
if not build_success:
return results
# Test with different OS variants
for os_variant in self.test_os_variants:
print(f"\n π₯οΈ Testing with {os_variant}...")
try:
vm_success, error_msg = self.test_example_vm(
example_dir, image_name, os_variant
)
results["os_tests"][os_variant] = {
"success": vm_success,
"error": error_msg,
"domain": results["domain"],
}
if vm_success:
print(f" β
{os_variant}: SUCCESS")
else:
print(f" β {os_variant}: FAILED")
except Exception as e:
results["os_tests"][os_variant] = {
"success": False,
"error": str(e),
}
print(f" β {os_variant}: ERROR - {e}")
return results
def generate_report(self) -> str:
"""Generate test report."""
report = []
report.append("# DockerVirt Examples Test Report")
report.append("=" * 50)
report.append("")
total_examples = len(self.test_results)
successful_builds = sum(
1 for r in self.test_results.values() if r["build_success"]
)
report.append("**Summary:**")
report.append(f"- Total Examples: {total_examples}")
report.append(f"- Successful Builds: {successful_builds}")
report.append("")
for example_name, result in self.test_results.items():
report.append(f"## {example_name}")
report.append("")
if result["build_success"]:
report.append(f"β
**Build:** SUCCESS (image: {result['image_name']})")
else:
report.append(f"β **Build:** FAILED")
if result.get("os_tests"):
report.append("")
report.append("**OS Compatibility:**")
for os_variant, test_result in result["os_tests"].items():
dom = test_result.get("domain", "")
if test_result["success"]:
report.append(f"- β
{os_variant}: SUCCESS (domain: {dom})")
else:
error = test_result.get("error", "Unknown error")
report.append(f"- β {os_variant}: FAILED - {error} (domain: {dom})")
report.append("")
return "\n".join(report)
def run_all_tests(self) -> bool:
"""Run tests for all examples."""
print("π§ͺ Starting dockvirt examples testing...")
print(
"This may take a while as it tests each example with multiple OS variants"
)
print("")
examples = self.get_examples()
if not examples:
print("β No examples found in examples/ directory")
return False
print(f"Found {len(examples)} examples to test:")
for example in examples:
print(f" - {example.name}")
print("")
# Test each example
for example_dir in examples:
try:
result = self.test_example(example_dir)
self.test_results[example_dir.name] = result
except KeyboardInterrupt:
print("\nβΉοΈ Testing interrupted by user")
return False
except Exception as e:
print(f"\nβ Error testing {example_dir.name}: {e}")
self.test_results[example_dir.name] = {
"name": example_dir.name,
"error": str(e),
"build_success": False,
"os_tests": {},
}
# Generate report
print("\n" + "=" * 50)
print("π TEST RESULTS")
print("=" * 50)
report = self.generate_report()
print(report)
# Save report to file
report_file = Path("test_results.md")
with open(report_file, "w") as f:
f.write(report)
print(f"\nπ Detailed report saved to: {report_file}")
# Return success if all builds and tests passed
all_tests_ok = all(
r.get("build_success", False)
and all(t.get("success", False) for t in r.get("os_tests", {}).values())
for r in self.test_results.values()
)
return all_tests_ok
def main():
"""Main test runner."""
if len(sys.argv) > 1 and sys.argv[1] == "--help":
print("Usage: python test_examples.py [example_name]")
print("")
print("Test all examples or specific example with different OS variants.")
print("")
print("Examples:")
print(" python test_examples.py # Test all examples")
print(" python test_examples.py 1-static-nginx # Test specific example")
return
tester = ExampleTester()
if len(sys.argv) > 1:
# Test specific example
example_name = sys.argv[1]
example_dir = tester.examples_dir / example_name
if not example_dir.exists():
print(f"β Example '{example_name}' not found")
sys.exit(1)
result = tester.test_example(example_dir)
tester.test_results[example_name] = result
success = (
result.get("build_success", False)
and all(t.get("success", False) for t in result.get("os_tests", {}).values())
)
else:
# Test all examples
success = tester.run_all_tests()
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()