importing etc_network_interfaces tests
authorWolfgang Bumiller <w.bumiller@proxmox.com>
Thu, 25 Jun 2015 09:54:31 +0000 (11:54 +0200)
committerDietmar Maurer <dietmar@proxmox.com>
Fri, 26 Jun 2015 05:49:16 +0000 (07:49 +0200)
13 files changed:
Makefile
test/Makefile [new file with mode: 0644]
test/etc_network_interfaces/Makefile [new file with mode: 0644]
test/etc_network_interfaces/base [new file with mode: 0644]
test/etc_network_interfaces/loopback [new file with mode: 0644]
test/etc_network_interfaces/proc_net_dev [new file with mode: 0644]
test/etc_network_interfaces/proc_net_if_inet6 [new file with mode: 0644]
test/etc_network_interfaces/runtest.pl [new file with mode: 0755]
test/etc_network_interfaces/t.base.pl [new file with mode: 0644]
test/etc_network_interfaces/t.bridge-v4-v6.pl [new file with mode: 0644]
test/etc_network_interfaces/t.keep-option-order.pl [new file with mode: 0644]
test/etc_network_interfaces/t.ovs_bridge_allow.pl [new file with mode: 0644]
test/etc_network_interfaces/t.unhandled-interfaces-to-manual.pl [new file with mode: 0644]

index 7469272..47fa8fc 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -40,6 +40,9 @@ clean:
 .PHONY: distclean
 distclean: clean
 
+.PHONY: check
+check:
+       $(MAKE) -C test check
 
 .PHONY: upload
 upload: ${DEB}
diff --git a/test/Makefile b/test/Makefile
new file mode 100644 (file)
index 0000000..82851cb
--- /dev/null
@@ -0,0 +1,12 @@
+SUBDIRS = etc_network_interfaces
+
+all:
+
+.PHONY: check install clean distclean
+
+check:
+       for d in $(SUBDIRS); do $(MAKE) -C $$d check; done
+
+install: check
+distclean: clean
+clean:
diff --git a/test/etc_network_interfaces/Makefile b/test/etc_network_interfaces/Makefile
new file mode 100644 (file)
index 0000000..aeb4141
--- /dev/null
@@ -0,0 +1,7 @@
+all:
+.PHONY: check install clean distclean
+install: check
+clean:
+distclean: clean
+check:
+       ./runtest.pl
diff --git a/test/etc_network_interfaces/base b/test/etc_network_interfaces/base
new file mode 100644 (file)
index 0000000..4d6a573
--- /dev/null
@@ -0,0 +1,16 @@
+# network interface settings; autogenerated
+# Please do NOT modify this file directly, unless you know what
+# you're doing.
+#
+# If you want to manage part of the network configuration manually,
+# please utilize the 'source' or 'source-directory' directives to do
+# so.
+# PVE will preserve these directives, but will NOT its network
+# configuration from sourced files, so do not attempt to move any of
+# the PVE managed interfaces into external files!
+
+auto lo
+iface lo inet loopback
+
+iface eth0 inet manual
+
diff --git a/test/etc_network_interfaces/loopback b/test/etc_network_interfaces/loopback
new file mode 100644 (file)
index 0000000..85b8346
--- /dev/null
@@ -0,0 +1,14 @@
+# network interface settings; autogenerated
+# Please do NOT modify this file directly, unless you know what
+# you're doing.
+#
+# If you want to manage part of the network configuration manually,
+# please utilize the 'source' or 'source-directory' directives to do
+# so.
+# PVE will preserve these directives, but will NOT its network
+# configuration from sourced files, so do not attempt to move any of
+# the PVE managed interfaces into external files!
+
+auto lo
+iface lo inet loopback
+
diff --git a/test/etc_network_interfaces/proc_net_dev b/test/etc_network_interfaces/proc_net_dev
new file mode 100644 (file)
index 0000000..01df936
--- /dev/null
@@ -0,0 +1,5 @@
+Inter-|   Receive                                                |  Transmit
+ face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
+ vmbr0:    0          0    0    0    0     0          0         0     0          0    0    0    0     0       0          0
+  eth0:    0          0    0    0    0     0          0         0     0          0    0    0    0     0       0          0
+    lo:    0          0    0    0    0     0          0         0     0          0    0    0    0     0       0          0
diff --git a/test/etc_network_interfaces/proc_net_if_inet6 b/test/etc_network_interfaces/proc_net_if_inet6
new file mode 100644 (file)
index 0000000..441e1d2
--- /dev/null
@@ -0,0 +1,3 @@
+00000000000000000000000000000001 01 80 10 80       lo
+fe80000000000000ae9e17fffe846a7a 03 40 20 80    vmbr0
+fc050000000000000000000010000001 03 70 00 80    vmbr0
diff --git a/test/etc_network_interfaces/runtest.pl b/test/etc_network_interfaces/runtest.pl
new file mode 100755 (executable)
index 0000000..ba26dcd
--- /dev/null
@@ -0,0 +1,208 @@
+#!/usr/bin/perl
+
+use lib '../../src';
+use strict;
+use warnings;
+
+use Carp;
+use POSIX;
+use IO::Handle;
+
+use PVE::INotify;
+
+# Current config, r() parses a network interface string into this variable
+our $config;
+
+##
+## Temporary files:
+##
+# perl conveniently lets you open a string as filehandle so we allow tests
+# to temporarily save interface files to virtual files:
+my %saved_files;
+
+# Load a temp-file and return it as a string, if it didn't exist, try loading
+# a real file.
+sub load($) {
+    my ($from) = @_;
+
+    if (my $local = $saved_files{$from}) {
+      return $local;
+    }
+
+    open my $fh, '<', $from or die "failed to open $from: $!";
+    local $/ = undef;
+    my $data = <$fh>;
+    close $fh;
+    return $data;
+}
+
+# Save a temporary file.
+sub save($$) {
+    my ($file, $data) = @_;
+    $saved_files{$file} = $data;
+}
+
+# Delete a temporary file
+sub delfile($) {
+    my $file = @_;
+    die "no such file: $file" if !delete $saved_files{$file};
+}
+
+# Delete all temporary files.
+sub flush_files() {
+    foreach (keys %saved_files) {
+       delete $saved_files{$_} if $_ !~ m,^shared/,;
+    }
+}
+
+##
+## Interface parsing:
+##
+
+# Read an interfaces file with optional /proc/net/dev and /proc/net/if_inet6
+# file content strings, which default to the provided ones.
+sub r($;$$) {
+    my ($ifaces, $proc_net_dev, $proc_net_if_inet6) = @_;
+    $proc_net_dev //= load('proc_net_dev');
+    $proc_net_if_inet6 //= load('proc_net_if_inet6');
+    open my $fh1, '<', \$ifaces;
+    open my $fh2, '<', \$proc_net_dev;
+    open my $fh3, '<', \$proc_net_if_inet6;
+    $config = PVE::INotify::__read_etc_network_interfaces($fh1, $fh2, $fh3);
+    close $fh1;
+}
+
+# Turn the current network config into a string.
+sub w() {
+    return PVE::INotify::__write_etc_network_interfaces($config);
+}
+
+##
+## Interface modification helpers
+##
+
+# Update an interface
+sub update_iface($$%) {
+    my ($name, $families, %extra) = @_;
+
+    my $ifaces = $config->{ifaces};
+    my $if = $ifaces->{$name};
+
+    die "no such interface: $name\n" if !$if;
+
+    $if->{exists} = 1;
+
+    # merge extra flags (like bridge_ports, ovs_*) directly
+    $if->{$_} = $extra{$_} foreach keys %extra;
+
+    return if !$families;
+
+    my $if_families = $if->{families} ||= [];
+    foreach my $family (@$families) {
+       my $type = delete $family->{family};
+       @$if_families = ((grep { $_ ne $type } @$if_families), $type);
+
+       (my $suffix = $type) =~ s/^inet//;
+       $if->{"method$suffix"} = $family->{address} ? 'static' : 'manual';
+       foreach(qw(address netmask gateway options)) {
+           if (my $value = delete $family->{$_}) {
+               $if->{"$_${suffix}"} = $value;
+           }
+       }
+    }
+}
+
+# Create an interface and error if it already exists.
+sub new_iface($$$%) {
+    my ($name, $type, $families, %extra) = @_;
+    my $ifaces = $config->{ifaces};
+    croak "interface already exists: $name" if $ifaces->{$name};
+    $ifaces->{$name} = { type => $type };
+    update_iface($name, $families, %extra);
+}
+
+# Delete an interface and error if it did not exist.
+sub delete_iface($;$) {
+    my ($name, $family) = @_;
+    my $ifaces = $config->{ifaces};
+    my $if = $ifaces->{$name} ||= {};
+    croak "interface doesn't exist: $name" if !$if;
+
+    if (!$family) {
+      delete $ifaces->{$name};
+      return;
+    }
+
+    my $families = $if->{families};
+    @$families = grep {$_ ne $family} @$families;
+    (my $suffix = $family) =~ s/^inet//;
+    delete $if->{"$_$suffix"} foreach qw(address netmask gateway options);
+}
+
+##
+## Test helpers:
+##
+
+# Compare two strings line by line and show a diff/error if they differ.
+sub diff($$) {
+    my ($a, $b) = @_;
+    return if $a eq $b;
+
+    my ($ra, $wa) = POSIX::pipe();
+    my ($rb, $wb) = POSIX::pipe();
+    my $ha = IO::Handle->new_from_fd($wa, 'w');
+    my $hb = IO::Handle->new_from_fd($wb, 'w');
+
+    open my $diffproc, '-|', 'diff', '-up', "/dev/fd/$ra", "/dev/fd/$rb"
+       or die "failed to run program 'diff': $!";
+    POSIX::close($ra);
+    POSIX::close($rb);
+
+    open my $f1, '<', \$a;
+    open my $f2, '<', \$b;
+    my ($line1, $line2);
+    do {
+       $ha->print($line1) if defined($line1 = <$f1>);
+       $hb->print($line2) if defined($line2 = <$f2>);
+    } while (defined($line1 // $line2));
+    close $f1;
+    close $f2;
+    close $ha;
+    close $hb;
+
+    local $/ = undef;
+    my $diff = <$diffproc>;
+    close $diffproc;
+    die "files differ:\n$diff";
+}
+
+# Write the current interface config and compare the result to a string.
+sub expect($) {
+    my ($expected) = @_;
+    my $got = w();
+    diff($expected, $got);
+}
+
+##
+## Main test execution:
+##
+# (sorted, it's not used right now but tests could pass on temporary files by
+# prefixing the name with shared/ and thus you might want to split a larger
+# test into t.01.first-part.pl, t.02.second-part.pl, etc.
+my $total = 0;
+my $failed = 0;
+for our $Test (sort <t.*.pl>) {
+    $total++;
+    flush_files();
+    eval {
+       require $Test;
+    };
+    if ($@) {
+       print "FAIL: $Test\n$@\n\n";
+       $failed++;
+    } else {
+       print "PASS: $Test\n";
+    }
+}
+
+die "$failed out of $total tests failed\n" if $failed;
diff --git a/test/etc_network_interfaces/t.base.pl b/test/etc_network_interfaces/t.base.pl
new file mode 100644 (file)
index 0000000..9980a2c
--- /dev/null
@@ -0,0 +1,12 @@
+my $wanted = load('base');
+
+# parse the empty file
+r('');
+expect $wanted;
+
+# idempotency
+# save, re-parse, and re-check
+r(w());
+expect $wanted;
+
+1;
diff --git a/test/etc_network_interfaces/t.bridge-v4-v6.pl b/test/etc_network_interfaces/t.bridge-v4-v6.pl
new file mode 100644 (file)
index 0000000..93e5bb5
--- /dev/null
@@ -0,0 +1,84 @@
+my $ip = '10.0.0.2';
+my $nm = '255.255.255.0';
+my $gw = '10.0.0.1';
+my $ip6 = 'fc05::1:2';
+my $nm6 = '112';
+my $gw6 = 'fc05::1:1';
+
+r(load('base'));
+
+new_iface('vmbr0', 'bridge', [{ family => 'inet' }], autostart => 1, bridge_ports => 'eth0');
+
+expect load('base') . <<"EOF";
+auto vmbr0
+iface vmbr0 inet manual
+       bridge_ports eth0
+       bridge_stp off
+       bridge_fd 0
+
+EOF
+
+# add an ip and disable previously enabled autostart
+update_iface('vmbr0',
+    [ { family => 'inet',
+       address => $ip,
+       netmask => $nm,
+       gateway => $gw } ],
+    autostart => 0);
+
+expect load('base') . <<"EOF";
+iface vmbr0 inet static
+       address  $ip
+       netmask  $nm
+       gateway  $gw
+       bridge_ports eth0
+       bridge_stp off
+       bridge_fd 0
+
+EOF
+save('with-ipv4', w());
+
+update_iface('vmbr0',
+    [ { family => 'inet6',
+       address => $ip6,
+       netmask => $nm6,
+       gateway => $gw6 } ]);
+
+expect load('with-ipv4') . <<"EOF";
+iface vmbr0 inet6 static
+       address  $ip6
+       netmask  $nm6
+       gateway  $gw6
+
+EOF
+
+# idempotency
+save('idem', w());
+r(load('idem'));
+expect load('idem');
+
+# delete vmbr0's inet
+delete_iface('vmbr0', 'inet');
+
+# bridge ports must now appear in the inet6 block
+expect load('base') . <<"EOF";
+iface vmbr0 inet6 static
+       address  $ip6
+       netmask  $nm6
+       gateway  $gw6
+       bridge_ports eth0
+       bridge_stp off
+       bridge_fd 0
+
+EOF
+
+# idempotency
+save('idem', w());
+r(load('idem'));
+expect load('idem');
+
+# delete vmbr0 completely
+delete_iface('vmbr0');
+expect load('base');
+
+1;
diff --git a/test/etc_network_interfaces/t.keep-option-order.pl b/test/etc_network_interfaces/t.keep-option-order.pl
new file mode 100644 (file)
index 0000000..d1e07a8
--- /dev/null
@@ -0,0 +1,28 @@
+#
+# Order of option lines between interfaces should be preserved:
+# eth0 is unconfigured and will thus end up at the end as 'manual'
+#
+my $ordered = <<'ORDERED';
+source /etc/network/config1
+
+iface eth1 inet manual
+
+source-directory /etc/network/interfaces.d
+
+iface eth2 inet manual
+
+iface eth3 inet manual
+
+ORDERED
+
+r("$ordered", <<'/proc/net/dev'
+eth0:
+eth1:
+eth2:
+eth3:
+/proc/net/dev
+);
+
+expect(load('loopback') . $ordered . "iface eth0 inet manual\n\n");
+
+1;
diff --git a/test/etc_network_interfaces/t.ovs_bridge_allow.pl b/test/etc_network_interfaces/t.ovs_bridge_allow.pl
new file mode 100644 (file)
index 0000000..7a0b8ce
--- /dev/null
@@ -0,0 +1,116 @@
+use strict;
+
+my $ip = '192.168.0.100';
+my $nm = '255.255.255.0';
+my $gw = '192.168.0.1';
+
+# replace proc_net_dev with one with a bunch of interfaces
+save('proc_net_dev', <<'/proc/net/dev');
+eth0:
+eth1:
+eth2:
+eth3:
+/proc/net/dev
+
+r('');
+
+new_iface('vmbr0', 'OVSBridge',
+    [ { family => 'inet',
+        address => $ip,
+        netmask => $nm,
+        gateway => $gw } ],
+    autostart => 1);
+
+update_iface('eth0', [], autostart => 1);
+update_iface('eth1', [], autostart => 1);
+update_iface('eth2', [], autostart => 1);
+#update_iface('eth3', [], autostart => 1);
+
+# Check the bridge and eth interfaces
+expect load('loopback') . <<"/etc/network/interfaces";
+auto eth0
+iface eth0 inet manual
+
+auto eth1
+iface eth1 inet manual
+
+auto eth2
+iface eth2 inet manual
+
+iface eth3 inet manual
+
+auto vmbr0
+iface vmbr0 inet static
+       address  $ip
+       netmask  $nm
+       gateway  $gw
+       ovs_type OVSBridge
+
+/etc/network/interfaces
+
+# Adding an interface to the bridge needs to add allow- lines:
+update_iface('vmbr0', [], ovs_ports => 'eth1 eth2');
+expect load('loopback') . <<"/etc/network/interfaces";
+auto eth0
+iface eth0 inet manual
+
+auto eth1
+allow-vmbr0 eth1
+iface eth1 inet manual
+       ovs_type OVSPort
+       ovs_bridge vmbr0
+
+auto eth2
+allow-vmbr0 eth2
+iface eth2 inet manual
+       ovs_type OVSPort
+       ovs_bridge vmbr0
+
+iface eth3 inet manual
+
+auto vmbr0
+iface vmbr0 inet static
+       address  $ip
+       netmask  $nm
+       gateway  $gw
+       ovs_type OVSBridge
+       ovs_ports eth1 eth2
+
+/etc/network/interfaces
+
+# Idempotency - make sure "allow-$BRIDGE $IFACE" don't get duplicated
+# they're stripped from $config->{options} at load-time since they're
+# auto-generated when writing OVSPorts.
+save('idem', w());
+r(load('idem'));
+expect load('idem');
+
+# Removing an ovs_port also has to remove the corresponding allow- line!
+# Also remember that adding interfaces to the ovs bridge removed their
+# autostart property, so eth2 is now without an autostart!
+update_iface('vmbr0', [], ovs_ports => 'eth1');
+# eth2 is now autoremoved and thus loses its priority, so it appears after eth3
+expect load('loopback') . <<"/etc/network/interfaces";
+auto eth0
+iface eth0 inet manual
+
+allow-vmbr0 eth1
+iface eth1 inet manual
+       ovs_type OVSPort
+       ovs_bridge vmbr0
+
+iface eth3 inet manual
+
+iface eth2 inet manual
+
+auto vmbr0
+iface vmbr0 inet static
+       address  $ip
+       netmask  $nm
+       gateway  $gw
+       ovs_type OVSBridge
+       ovs_ports eth1
+
+/etc/network/interfaces
+
+1;
diff --git a/test/etc_network_interfaces/t.unhandled-interfaces-to-manual.pl b/test/etc_network_interfaces/t.unhandled-interfaces-to-manual.pl
new file mode 100644 (file)
index 0000000..6c77e33
--- /dev/null
@@ -0,0 +1,25 @@
+r('', <<'/proc/net/dev'
+Inter-|   Receive                                                |  Transmit
+ face |bytes    packets errs drop fifo frame compressed multicast|bytes    packets errs drop fifo colls carrier compressed
+These eth interfaces show up:
+  eth0:
+eth1:
+       eth2:
+  eth3:
+    lo:
+All other stuff is being ignored eth99:
+eth100 is not actually available:
+ ethBAD: this one's now allowed either
+/proc/net/dev
+);
+
+expect load('base') . <<'IFACES';
+iface eth1 inet manual
+
+iface eth2 inet manual
+
+iface eth3 inet manual
+
+IFACES
+
+1;