[pve-devel] [PATCH v2 08/11] importing etc_network_interfaces tests
Wolfgang Bumiller
w.bumiller at proxmox.com
Thu Jun 25 11:54:31 CEST 2015
---
Makefile | 3 +
test/Makefile | 12 ++
test/etc_network_interfaces/Makefile | 7 +
test/etc_network_interfaces/base | 16 ++
test/etc_network_interfaces/loopback | 14 ++
test/etc_network_interfaces/proc_net_dev | 5 +
test/etc_network_interfaces/proc_net_if_inet6 | 3 +
test/etc_network_interfaces/runtest.pl | 208 +++++++++++++++++++++
test/etc_network_interfaces/t.base.pl | 12 ++
test/etc_network_interfaces/t.bridge-v4-v6.pl | 84 +++++++++
test/etc_network_interfaces/t.keep-option-order.pl | 28 +++
test/etc_network_interfaces/t.ovs_bridge_allow.pl | 116 ++++++++++++
.../t.unhandled-interfaces-to-manual.pl | 25 +++
13 files changed, 533 insertions(+)
create mode 100644 test/Makefile
create mode 100644 test/etc_network_interfaces/Makefile
create mode 100644 test/etc_network_interfaces/base
create mode 100644 test/etc_network_interfaces/loopback
create mode 100644 test/etc_network_interfaces/proc_net_dev
create mode 100644 test/etc_network_interfaces/proc_net_if_inet6
create mode 100755 test/etc_network_interfaces/runtest.pl
create mode 100644 test/etc_network_interfaces/t.base.pl
create mode 100644 test/etc_network_interfaces/t.bridge-v4-v6.pl
create mode 100644 test/etc_network_interfaces/t.keep-option-order.pl
create mode 100644 test/etc_network_interfaces/t.ovs_bridge_allow.pl
create mode 100644 test/etc_network_interfaces/t.unhandled-interfaces-to-manual.pl
diff --git a/Makefile b/Makefile
index 7469272..47fa8fc 100644
--- 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
index 0000000..82851cb
--- /dev/null
+++ b/test/Makefile
@@ -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
index 0000000..aeb4141
--- /dev/null
+++ b/test/etc_network_interfaces/Makefile
@@ -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
index 0000000..4d6a573
--- /dev/null
+++ b/test/etc_network_interfaces/base
@@ -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
index 0000000..85b8346
--- /dev/null
+++ b/test/etc_network_interfaces/loopback
@@ -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
index 0000000..01df936
--- /dev/null
+++ b/test/etc_network_interfaces/proc_net_dev
@@ -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
index 0000000..441e1d2
--- /dev/null
+++ b/test/etc_network_interfaces/proc_net_if_inet6
@@ -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
index 0000000..ba26dcd
--- /dev/null
+++ b/test/etc_network_interfaces/runtest.pl
@@ -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
index 0000000..9980a2c
--- /dev/null
+++ b/test/etc_network_interfaces/t.base.pl
@@ -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
index 0000000..93e5bb5
--- /dev/null
+++ b/test/etc_network_interfaces/t.bridge-v4-v6.pl
@@ -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
index 0000000..d1e07a8
--- /dev/null
+++ b/test/etc_network_interfaces/t.keep-option-order.pl
@@ -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
index 0000000..7a0b8ce
--- /dev/null
+++ b/test/etc_network_interfaces/t.ovs_bridge_allow.pl
@@ -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
index 0000000..6c77e33
--- /dev/null
+++ b/test/etc_network_interfaces/t.unhandled-interfaces-to-manual.pl
@@ -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;
--
2.1.4
More information about the pve-devel
mailing list