* Use vde_switch instead of QEMU's multicast feature to tie QEMU VMs

together into virtual networks.  This has several advantages:

  - It's more secure because the QEMU instances use Unix domain
    sockets to talk to the switch.

  - It doesn't depend on the host's network interfaces.  (Local
    multicast fails if there is no default gateway, so for instance it
    fails if a laptop is not connected to any network.)

  - VDE devices can be connected together to form arbitrary network
    topologies.

  - VDE has a "wirefilter" tool to emulate delays and packet loss,
    which are useful for network testing.

svn path=/nixos/trunk/; revision=25526
This commit is contained in:
Eelco Dolstra 2011-01-12 18:47:23 +00:00
parent fd48855284
commit b3dbcbe249
4 changed files with 51 additions and 51 deletions

View File

@ -4,12 +4,6 @@
qemuNICFlags = nic: net: machine: qemuNICFlags = nic: net: machine:
"-net nic,vlan=${toString nic},macaddr=52:54:00:12:${toString net}:${toString machine},model=virtio " + "-net nic,vlan=${toString nic},macaddr=52:54:00:12:${toString net}:${toString machine},model=virtio " +
# Use 232.0.1.<vlan> as the multicast address to connect VMs on "-net vde,vlan=${toString nic},sock=$QEMU_VDE_SOCKET_${toString net} ";
# the same vlan, but allow it to be overriden using the
# $QEMU_MCAST_ADDR_<vlan> environment variable. The test driver
# sets this variable to prevent collisions between parallel
# builds.
"-net socket,vlan=${toString nic},mcast=" +
"\${QEMU_MCAST_ADDR_${toString net}:-232.0.1.${toString net}:1234} ";
} }

View File

@ -10,16 +10,6 @@ use Cwd;
use File::Basename; use File::Basename;
# Stuff our PID in the multicast address/port to prevent collissions
# with other NixOS VM networks. See
# http://www.iana.org/assignments/multicast-addresses/.
my $mcastPrefix = "232.18";
my $mcastSuffix = ($$ >> 8) . ":" . (64000 + ($$ & 0xff));
print STDERR "using multicast addresses $mcastPrefix.<vlan>.$mcastSuffix\n";
for (my $n = 0; $n < 256; $n++) {
$ENV{"QEMU_MCAST_ADDR_$n"} = "$mcastPrefix.$n.$mcastSuffix";
}
my $showGraphics = defined $ENV{'DISPLAY'}; my $showGraphics = defined $ENV{'DISPLAY'};

View File

@ -5,6 +5,7 @@ use Machine;
use Term::ReadLine; use Term::ReadLine;
use IO::File; use IO::File;
use Logger; use Logger;
use Cwd;
$SIG{PIPE} = 'IGNORE'; # because Unix domain sockets may die unexpectedly $SIG{PIPE} = 'IGNORE'; # because Unix domain sockets may die unexpectedly
@ -13,6 +14,19 @@ STDERR->autoflush(1);
my $log = new Logger; my $log = new Logger;
# Start vde_switch for each network required by the test.
my %vlans;
foreach my $vlan (split / /, $ENV{VLANS} || "") {
next if defined $vlans{$vlan};
$log->log("starting VDE switch for network $vlan");
my $socket = Cwd::abs_path "./vde$vlan.ctl";
system("vde_switch -d -s $socket") == 0
or die "cannot start vde_switch";
$ENV{"QEMU_VDE_SOCKET_$vlan"} = $socket;
$vlans{$vlan} = 1;
}
my %vms; my %vms;
my $context = ""; my $context = "";
@ -83,23 +97,26 @@ sub runTests {
# Copy the kernel coverage data for each machine, if the kernel # Copy the kernel coverage data for each machine, if the kernel
# has been compiled with coverage instrumentation. # has been compiled with coverage instrumentation.
foreach my $vm (values %vms) { $log->nest("collecting coverage data", sub {
my $gcovDir = "/sys/kernel/debug/gcov"; foreach my $vm (values %vms) {
my $gcovDir = "/sys/kernel/debug/gcov";
next unless $vm->isUp(); next unless $vm->isUp();
my ($status, $out) = $vm->execute("test -e $gcovDir"); my ($status, $out) = $vm->execute("test -e $gcovDir");
next if $status != 0; next if $status != 0;
# Figure out where to put the *.gcda files so that the report # Figure out where to put the *.gcda files so that the
# generator can find the corresponding kernel sources. # report generator can find the corresponding kernel
my $kernelDir = $vm->mustSucceed("echo \$(dirname \$(readlink -f /var/run/current-system/kernel))/.build/linux-*"); # sources.
chomp $kernelDir; my $kernelDir = $vm->mustSucceed("echo \$(dirname \$(readlink -f /var/run/current-system/kernel))/.build/linux-*");
my $coverageDir = "/hostfs" . $vm->stateDir() . "/coverage-data/$kernelDir"; chomp $kernelDir;
my $coverageDir = "/hostfs" . $vm->stateDir() . "/coverage-data/$kernelDir";
# Copy all the *.gcda files. # Copy all the *.gcda files.
$vm->execute("for d in $gcovDir/nix/store/*/.build/linux-*; do for i in \$(cd \$d && find -name '*.gcda'); do echo \$i; mkdir -p $coverageDir/\$(dirname \$i); cp -v \$d/\$i $coverageDir/\$i; done; done"); $vm->execute("for d in $gcovDir/nix/store/*/.build/linux-*; do for i in \$(cd \$d && find -name '*.gcda'); do echo \$i; mkdir -p $coverageDir/\$(dirname \$i); cp -v \$d/\$i $coverageDir/\$i; done; done");
} }
});
if ($nrTests != 0) { if ($nrTests != 0) {
$log->log("$nrSucceeded out of $nrTests tests succeeded", $log->log("$nrSucceeded out of $nrTests tests succeeded",
@ -118,12 +135,14 @@ sub createDisk {
END { END {
foreach my $vm (values %vms) { $log->nest("cleaning up", sub {
if ($vm->{pid}) { foreach my $vm (values %vms) {
$log->log("killing " . $vm->{name} . " (pid " . $vm->{pid} . ")"); if ($vm->{pid}) {
kill 9, $vm->{pid}; $log->log("killing " . $vm->{name} . " (pid " . $vm->{pid} . ")");
kill 9, $vm->{pid};
}
} }
} });
$log->close(); $log->close();
} }

View File

@ -27,31 +27,27 @@ rec {
cp ${./test-driver/Logger.pm} $libDir/Logger.pm cp ${./test-driver/Logger.pm} $libDir/Logger.pm
wrapProgram $out/bin/nixos-test-driver \ wrapProgram $out/bin/nixos-test-driver \
--prefix PATH : "${imagemagick}/bin" \ --prefix PATH : "${pkgs.qemu_kvm}/bin:${pkgs.vde2}/bin:${imagemagick}/bin" \
--prefix PERL5LIB : "${lib.makePerlPath [ perlPackages.TermReadLineGnu perlPackages.XMLWriter ]}:$out/lib/perl5/site_perl" --prefix PERL5LIB : "${lib.makePerlPath [ perlPackages.TermReadLineGnu perlPackages.XMLWriter ]}:$out/lib/perl5/site_perl"
''; '';
}; };
# Run an automated test suite in the given virtual network. # Run an automated test suite in the given virtual network.
# `network' must be the result of a call to the # `driver' is the script that runs the network.
# `buildVirtualNetwork' function. `tests' is a Perl fragment runTests = driver:
# describing the tests.
runTests = network: tests:
stdenv.mkDerivation { stdenv.mkDerivation {
name = "vm-test-run"; name = "vm-test-run";
requiredSystemFeatures = [ "kvm" ]; requiredSystemFeatures = [ "kvm" ];
inherit tests; buildInputs = [ pkgs.libxslt ];
buildInputs = [ pkgs.qemu_kvm pkgs.libxslt ];
buildCommand = buildCommand =
'' ''
mkdir -p $out/nix-support mkdir -p $out/nix-support
LOGFILE=$out/log.xml ${testDriver}/bin/nixos-test-driver ${network}/vms/*/bin/run-*-vm || failed=1 LOGFILE=$out/log.xml tests="testScript" ${driver}/bin/nixos-test-driver || failed=1
# Generate a pretty-printed log. # Generate a pretty-printed log.
xsltproc --output $out/log.html ${./test-driver/log2html.xsl} $out/log.xml xsltproc --output $out/log.html ${./test-driver/log2html.xsl} $out/log.xml
@ -138,10 +134,6 @@ rec {
then t.testScript { inherit (vms) nodes; } then t.testScript { inherit (vms) nodes; }
else t.testScript; else t.testScript;
test = runTests vms testScript;
report = makeReport test;
# Generate a convenience wrapper for running the test driver # Generate a convenience wrapper for running the test driver
# interactively with the specified network. # interactively with the specified network.
driver = runCommand "nixos-test-driver" driver = runCommand "nixos-test-driver"
@ -150,14 +142,19 @@ rec {
} }
'' ''
mkdir -p $out/bin mkdir -p $out/bin
echo "$testScript" > $out/test-script
ln -s ${vms}/bin/* $out/bin/ ln -s ${vms}/bin/* $out/bin/
ln -s ${testDriver}/bin/* $out/bin/ ln -s ${testDriver}/bin/* $out/bin/
wrapProgram $out/bin/nixos-test-driver \ wrapProgram $out/bin/nixos-test-driver \
--add-flags "${vms}/vms/*/bin/run-*-vm" \ --add-flags "${vms}/vms/*/bin/run-*-vm" \
--run "testScript=\"\$(cat $out/test-script)\"" \ --run "testScript=\"\$(cat $out/test-script)\"" \
--set testScript '"$testScript"' --set testScript '"$testScript"' \
echo "$testScript" > $out/test-script --set VLANS '"${toString (map (m: m.config.virtualisation.vlans) (lib.attrValues vms.nodes))}"' \
''; # " ''; # "
test = runTests driver;
report = makeReport test;
}; };