--- 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 _______________________________________________ pve-devel mailing list pve-devel@pve.proxmox.com http://pve.proxmox.com/cgi-bin/mailman/listinfo/pve-devel