| 47 |
rodolico |
1 |
#!/usr/bin/env perl
|
|
|
2 |
|
|
|
3 |
# test_integration.pl
|
|
|
4 |
# Integration test script for havirt
|
|
|
5 |
# Tests the havirt modules and functions directly
|
|
|
6 |
#
|
|
|
7 |
# Usage: ./test_integration.pl [--verbose] [--dryrun]
|
|
|
8 |
#
|
|
|
9 |
# Copyright 2026 Daily Data, Inc.
|
|
|
10 |
|
|
|
11 |
use strict;
|
|
|
12 |
use warnings;
|
|
|
13 |
use FindBin;
|
|
|
14 |
use File::Spec;
|
|
|
15 |
use Cwd 'abs_path';
|
|
|
16 |
use File::Basename;
|
|
|
17 |
# Add parent directory to lib path (tests/../ to find modules)
|
|
|
18 |
use lib dirname( dirname( abs_path( __FILE__ ) ) );
|
|
|
19 |
|
|
|
20 |
use Data::Dumper;
|
|
|
21 |
use Getopt::Long;
|
|
|
22 |
|
|
|
23 |
# Load havirt modules
|
|
|
24 |
use havirt;
|
|
|
25 |
use domain;
|
|
|
26 |
use node;
|
|
|
27 |
use cluster;
|
|
|
28 |
|
|
|
29 |
# Test framework variables
|
|
|
30 |
my $tests_run = 0;
|
|
|
31 |
my $tests_passed = 0;
|
|
|
32 |
my $tests_failed = 0;
|
|
|
33 |
my @failed_tests;
|
|
|
34 |
|
|
|
35 |
# Command line options
|
|
|
36 |
my $verbose = 0;
|
|
|
37 |
my $dryrun = 1; # Default to dryrun for safety
|
|
|
38 |
|
|
|
39 |
GetOptions(
|
|
|
40 |
'verbose|v+' => \$verbose,
|
|
|
41 |
'dryrun|n!' => \$dryrun,
|
|
|
42 |
) or die "Error parsing command line\n";
|
|
|
43 |
|
|
|
44 |
# Color codes for output
|
|
|
45 |
my $RED = "\033[0;31m";
|
|
|
46 |
my $GREEN = "\033[0;32m";
|
|
|
47 |
my $YELLOW = "\033[1;33m";
|
|
|
48 |
my $BLUE = "\033[0;34m";
|
|
|
49 |
my $NC = "\033[0m";
|
|
|
50 |
|
|
|
51 |
# Logging functions
|
|
|
52 |
sub log_info {
|
|
|
53 |
print "${BLUE}[INFO]${NC} ", @_, "\n";
|
|
|
54 |
}
|
|
|
55 |
|
|
|
56 |
sub log_success {
|
|
|
57 |
print "${GREEN}[PASS]${NC} ", @_, "\n";
|
|
|
58 |
$tests_passed++;
|
|
|
59 |
}
|
|
|
60 |
|
|
|
61 |
sub log_failure {
|
|
|
62 |
my $msg = shift;
|
|
|
63 |
print "${RED}[FAIL]${NC} $msg\n";
|
|
|
64 |
$tests_failed++;
|
|
|
65 |
push @failed_tests, $msg;
|
|
|
66 |
}
|
|
|
67 |
|
|
|
68 |
sub log_test {
|
|
|
69 |
print "${YELLOW}[TEST]${NC} ", @_, "\n";
|
|
|
70 |
$tests_run++;
|
|
|
71 |
}
|
|
|
72 |
|
|
|
73 |
# Test function wrapper
|
|
|
74 |
sub run_test {
|
|
|
75 |
my ($test_name, $code) = @_;
|
|
|
76 |
|
|
|
77 |
log_test($test_name);
|
|
|
78 |
|
|
|
79 |
eval {
|
|
|
80 |
my $result = $code->();
|
|
|
81 |
if (defined $result) {
|
|
|
82 |
log_success($test_name);
|
|
|
83 |
print " Result: $result\n" if $verbose && $result;
|
|
|
84 |
} else {
|
|
|
85 |
log_success($test_name);
|
|
|
86 |
}
|
|
|
87 |
};
|
|
|
88 |
|
|
|
89 |
if ($@) {
|
|
|
90 |
log_failure("$test_name - Error: $@");
|
|
|
91 |
}
|
|
|
92 |
}
|
|
|
93 |
|
|
|
94 |
# Test that a function returns defined value
|
|
|
95 |
sub test_returns_defined {
|
|
|
96 |
my ($test_name, $code) = @_;
|
|
|
97 |
|
|
|
98 |
log_test($test_name);
|
|
|
99 |
|
|
|
100 |
eval {
|
|
|
101 |
my $result = $code->();
|
|
|
102 |
if (defined $result) {
|
|
|
103 |
log_success($test_name);
|
|
|
104 |
print " Result: " . (ref($result) ? Dumper($result) : $result) . "\n" if $verbose > 1;
|
|
|
105 |
} else {
|
|
|
106 |
log_failure("$test_name - returned undefined");
|
|
|
107 |
}
|
|
|
108 |
};
|
|
|
109 |
|
|
|
110 |
if ($@) {
|
|
|
111 |
log_failure("$test_name - Error: $@");
|
|
|
112 |
}
|
|
|
113 |
}
|
|
|
114 |
|
|
|
115 |
print "=" x 50 . "\n";
|
|
|
116 |
print " havirt Integration Test Suite\n";
|
|
|
117 |
print "=" x 50 . "\n\n";
|
|
|
118 |
|
|
|
119 |
log_info("Running in " . ($dryrun ? "DRY-RUN" : "LIVE") . " mode");
|
|
|
120 |
log_info("Verbose level: $verbose");
|
|
|
121 |
print "\n";
|
|
|
122 |
|
|
|
123 |
# Initialize config (in parent directory)
|
|
|
124 |
my $configFileName = dirname($FindBin::RealBin) . '/config.yaml';
|
|
|
125 |
my $config;
|
|
|
126 |
|
|
|
127 |
print "=" x 50 . "\n";
|
|
|
128 |
print "Section 1: Configuration Tests\n";
|
|
|
129 |
print "=" x 50 . "\n\n";
|
|
|
130 |
|
|
|
131 |
run_test("Check config file exists", sub {
|
|
|
132 |
return -f $configFileName ? "Config file found" : undef;
|
|
|
133 |
});
|
|
|
134 |
|
|
|
135 |
test_returns_defined("Load configuration", sub {
|
|
|
136 |
$config = havirt::readConfig($configFileName);
|
|
|
137 |
return $config ? "Config loaded" : undef;
|
|
|
138 |
});
|
|
|
139 |
|
|
|
140 |
# Set test flags
|
|
|
141 |
if ($config) {
|
|
|
142 |
$config->{'flags'}->{'dryrun'} = $dryrun;
|
|
|
143 |
$config->{'flags'}->{'verbose'} = $verbose;
|
|
|
144 |
$main::config = $config;
|
|
|
145 |
}
|
|
|
146 |
|
|
|
147 |
print "\n";
|
|
|
148 |
|
|
|
149 |
# Test havirt.pm functions
|
|
|
150 |
print "=" x 50 . "\n";
|
|
|
151 |
print "Section 2: havirt.pm Core Functions\n";
|
|
|
152 |
print "=" x 50 . "\n\n";
|
|
|
153 |
|
|
|
154 |
my $statusDB;
|
|
|
155 |
|
|
|
156 |
test_returns_defined("Read status database", sub {
|
|
|
157 |
$statusDB = havirt::readDB($config);
|
|
|
158 |
$main::statusDB = $statusDB;
|
|
|
159 |
return $statusDB ? "Status DB loaded" : undef;
|
|
|
160 |
});
|
|
|
161 |
|
|
|
162 |
if ($statusDB) {
|
|
|
163 |
run_test("Check nodes in status DB", sub {
|
|
|
164 |
my $node_count = scalar(keys %{$statusDB->{'node'}});
|
|
|
165 |
return "Found $node_count nodes";
|
|
|
166 |
});
|
|
|
167 |
|
|
|
168 |
run_test("Check domains in status DB", sub {
|
|
|
169 |
my $domain_count = scalar(keys %{$statusDB->{'domain'}});
|
|
|
170 |
return "Found $domain_count domains";
|
|
|
171 |
});
|
|
|
172 |
}
|
|
|
173 |
|
|
|
174 |
test_returns_defined("Test makeCommand function", sub {
|
|
|
175 |
my $cmd = havirt::makeCommand("localhost", "test command");
|
|
|
176 |
return $cmd;
|
|
|
177 |
});
|
|
|
178 |
|
|
|
179 |
run_test("Test diffArray function", sub {
|
|
|
180 |
my @array1 = ('a', 'b', 'c', 'd');
|
|
|
181 |
my @array2 = ('b', 'd', 'e');
|
|
|
182 |
my @diff = havirt::diffArray(\@array1, \@array2);
|
|
|
183 |
my $diff_str = join(',', @diff);
|
|
|
184 |
return "Diff result: $diff_str" if @diff;
|
|
|
185 |
return "Empty diff";
|
|
|
186 |
});
|
|
|
187 |
|
|
|
188 |
print "\n";
|
|
|
189 |
|
|
|
190 |
# Test node.pm functions
|
|
|
191 |
print "=" x 50 . "\n";
|
|
|
192 |
print "Section 3: node.pm Module Tests\n";
|
|
|
193 |
print "=" x 50 . "\n\n";
|
|
|
194 |
|
|
|
195 |
test_returns_defined("Node help", sub {
|
|
|
196 |
my $help = node::help();
|
|
|
197 |
return $help ? "Help returned" : undef;
|
|
|
198 |
});
|
|
|
199 |
|
|
|
200 |
test_returns_defined("Node list", sub {
|
|
|
201 |
my $list = node::list();
|
|
|
202 |
return $list ? "Node list returned" : undef;
|
|
|
203 |
});
|
|
|
204 |
|
|
|
205 |
if ($statusDB && $statusDB->{'node'}) {
|
|
|
206 |
my @nodes = keys %{$statusDB->{'node'}};
|
|
|
207 |
if (@nodes) {
|
|
|
208 |
my $test_node = $nodes[0];
|
|
|
209 |
log_info("Using node '$test_node' for tests");
|
|
|
210 |
|
|
|
211 |
run_test("Get domains on node $test_node", sub {
|
|
|
212 |
my @domains = havirt::getDomainsOnNode($statusDB, $test_node);
|
|
|
213 |
return "Found " . scalar(@domains) . " domains on $test_node";
|
|
|
214 |
});
|
|
|
215 |
}
|
|
|
216 |
}
|
|
|
217 |
|
|
|
218 |
print "\n";
|
|
|
219 |
|
|
|
220 |
# Test domain.pm functions
|
|
|
221 |
print "=" x 50 . "\n";
|
|
|
222 |
print "Section 4: domain.pm Module Tests\n";
|
|
|
223 |
print "=" x 50 . "\n\n";
|
|
|
224 |
|
|
|
225 |
test_returns_defined("Domain help", sub {
|
|
|
226 |
my $help = domain::help();
|
|
|
227 |
return $help ? "Help returned" : undef;
|
|
|
228 |
});
|
|
|
229 |
|
|
|
230 |
test_returns_defined("Domain list", sub {
|
|
|
231 |
my $list = domain::list();
|
|
|
232 |
return $list ? "Domain list returned" : undef;
|
|
|
233 |
});
|
|
|
234 |
|
|
|
235 |
# Test MAC address generation
|
|
|
236 |
run_test("Test MAC address generation", sub {
|
|
|
237 |
my $mac = domain::makeMac();
|
|
|
238 |
return $mac =~ /^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/ ? "Generated MAC: $mac" : undef;
|
|
|
239 |
});
|
|
|
240 |
|
|
|
241 |
# Test VNC port finding
|
|
|
242 |
run_test("Test VNC port finding", sub {
|
|
|
243 |
my $port = domain::findVNCPort($statusDB);
|
|
|
244 |
return $port && $port >= 5900 ? "Found VNC port: $port" : "Default port: 5900";
|
|
|
245 |
});
|
|
|
246 |
|
|
|
247 |
# Test domain parsing if config files exist
|
|
|
248 |
my $conf_dir = $config->{'config dir'} if $config;
|
|
|
249 |
if ($conf_dir && -d $conf_dir) {
|
|
|
250 |
my @xml_files = glob("$conf_dir/*.xml");
|
|
|
251 |
if (@xml_files) {
|
|
|
252 |
my $test_xml = $xml_files[0];
|
|
|
253 |
my $domain_name = basename($test_xml, '.xml');
|
|
|
254 |
|
|
|
255 |
run_test("Parse domain config: $domain_name", sub {
|
|
|
256 |
my $parsed = domain::parseDomain($test_xml);
|
|
|
257 |
return $parsed ? "Parsed successfully" : undef;
|
|
|
258 |
});
|
|
|
259 |
}
|
|
|
260 |
}
|
|
|
261 |
|
|
|
262 |
print "\n";
|
|
|
263 |
|
|
|
264 |
# Test cluster.pm functions
|
|
|
265 |
print "=" x 50 . "\n";
|
|
|
266 |
print "Section 5: cluster.pm Module Tests\n";
|
|
|
267 |
print "=" x 50 . "\n\n";
|
|
|
268 |
|
|
|
269 |
test_returns_defined("Cluster help", sub {
|
|
|
270 |
my $help = cluster::help();
|
|
|
271 |
return $help ? "Help returned" : undef;
|
|
|
272 |
});
|
|
|
273 |
|
|
|
274 |
test_returns_defined("Cluster status", sub {
|
|
|
275 |
my $status = cluster::status();
|
|
|
276 |
return $status ? "Status returned" : undef;
|
|
|
277 |
});
|
|
|
278 |
|
|
|
279 |
run_test("Get cluster stats", sub {
|
|
|
280 |
my $stats = cluster::getClusterStats($statusDB);
|
|
|
281 |
if ($stats) {
|
|
|
282 |
my $node_count = scalar(@{$stats->{'nodes'}}) if $stats->{'nodes'};
|
|
|
283 |
return "Cluster stats retrieved" . ($node_count ? " ($node_count nodes)" : "");
|
|
|
284 |
}
|
|
|
285 |
return undef;
|
|
|
286 |
});
|
|
|
287 |
|
|
|
288 |
run_test("Test humanReadable function", sub {
|
|
|
289 |
my $hr = cluster::humanReadable(1073741824); # 1GB in bytes
|
|
|
290 |
return "1GB = $hr";
|
|
|
291 |
});
|
|
|
292 |
|
|
|
293 |
run_test("Test percent function", sub {
|
|
|
294 |
my $pct = cluster::percent(50, 200);
|
|
|
295 |
return "50/200 = $pct";
|
|
|
296 |
});
|
|
|
297 |
|
|
|
298 |
print "\n";
|
|
|
299 |
|
|
|
300 |
# Test data structure integrity
|
|
|
301 |
print "=" x 50 . "\n";
|
|
|
302 |
print "Section 6: Data Structure Validation\n";
|
|
|
303 |
print "=" x 50 . "\n\n";
|
|
|
304 |
|
|
|
305 |
if ($statusDB) {
|
|
|
306 |
run_test("Validate node data structure", sub {
|
|
|
307 |
foreach my $node_name (keys %{$statusDB->{'node'}}) {
|
|
|
308 |
my $node = $statusDB->{'node'}{$node_name};
|
|
|
309 |
return undef unless ref($node) eq 'HASH';
|
|
|
310 |
}
|
|
|
311 |
return "All node structures valid";
|
|
|
312 |
});
|
|
|
313 |
|
|
|
314 |
run_test("Validate domain data structure", sub {
|
|
|
315 |
foreach my $domain_name (keys %{$statusDB->{'domain'}}) {
|
|
|
316 |
my $domain = $statusDB->{'domain'}{$domain_name};
|
|
|
317 |
return undef unless ref($domain) eq 'HASH';
|
|
|
318 |
}
|
|
|
319 |
return "All domain structures valid";
|
|
|
320 |
});
|
|
|
321 |
|
|
|
322 |
run_test("Check for orphaned domains", sub {
|
|
|
323 |
my @orphaned;
|
|
|
324 |
foreach my $domain_name (keys %{$statusDB->{'domain'}}) {
|
|
|
325 |
my $domain = $statusDB->{'domain'}{$domain_name};
|
|
|
326 |
if ($domain->{'node'} && !exists $statusDB->{'node'}{$domain->{'node'}}) {
|
|
|
327 |
push @orphaned, $domain_name;
|
|
|
328 |
}
|
|
|
329 |
}
|
|
|
330 |
if (@orphaned) {
|
|
|
331 |
return "Found " . scalar(@orphaned) . " orphaned domains: " . join(', ', @orphaned);
|
|
|
332 |
}
|
|
|
333 |
return "No orphaned domains";
|
|
|
334 |
});
|
|
|
335 |
}
|
|
|
336 |
|
|
|
337 |
print "\n";
|
|
|
338 |
|
|
|
339 |
# Final summary
|
|
|
340 |
print "=" x 50 . "\n";
|
|
|
341 |
print " Test Summary\n";
|
|
|
342 |
print "=" x 50 . "\n\n";
|
|
|
343 |
|
|
|
344 |
print "Total tests run: $tests_run\n";
|
|
|
345 |
print "Tests passed: ${GREEN}$tests_passed${NC}\n";
|
|
|
346 |
print "Tests failed: ${RED}$tests_failed${NC}\n";
|
|
|
347 |
print "\n";
|
|
|
348 |
|
|
|
349 |
if ($tests_failed > 0) {
|
|
|
350 |
print "${RED}Failed tests:${NC}\n";
|
|
|
351 |
foreach my $failed (@failed_tests) {
|
|
|
352 |
print " - $failed\n";
|
|
|
353 |
}
|
|
|
354 |
print "\n";
|
|
|
355 |
}
|
|
|
356 |
|
|
|
357 |
if ($tests_failed == 0) {
|
|
|
358 |
print "${GREEN}All tests passed!${NC}\n";
|
|
|
359 |
exit 0;
|
|
|
360 |
} else {
|
|
|
361 |
print "${RED}Some tests failed.${NC}\n";
|
|
|
362 |
exit 1;
|
|
|
363 |
}
|