Files
webmin/net/t/run-tests.t
Ilia Ross 6574373761 Fix to detect NetworkManager networking on Debian
ⓘ Prefer Netplan when Debian has Netplan YAML config, otherwise select the existing NetworkManager backend for Debian systems with saved NM connection profiles, with regression tests for backend selection.

https://github.com/webmin/webmin/issues/2559
2026-06-02 01:33:59 +02:00

236 lines
6.9 KiB
Perl

#!/usr/bin/perl
use strict;
use warnings;
use Test::More;
use Cwd qw(abs_path);
use File::Basename qw(dirname);
use File::Path qw(make_path);
use File::Temp qw(tempdir);
my $root = abs_path(dirname(__FILE__)."/../..") or die "rootdir: $!";
my $tmp = tempdir(CLEANUP => 1);
my %file_cache;
my @commands;
my %command_status;
my %command_output;
sub write_text
{
my ($file, $text) = @_;
open(my $fh, ">", $file) || die "write $file: $!";
print $fh $text;
close($fh) || die "close $file: $!";
delete($file_cache{$file});
}
sub read_text
{
my ($file) = @_;
open(my $fh, "<", $file) || die "read $file: $!";
local $/ = undef;
my $text = <$fh>;
close($fh) || die "close $file: $!";
return $text;
}
sub read_file_lines
{
my ($file) = @_;
if (!exists($file_cache{$file})) {
open(my $fh, "<", $file) || die "read_file_lines $file: $!";
my @lines = <$fh>;
close($fh) || die "close $file: $!";
chomp(@lines);
$file_cache{$file} = \@lines;
}
return $file_cache{$file};
}
sub flush_file_lines
{
my ($file) = @_;
open(my $fh, ">", $file) || die "flush $file: $!";
foreach my $line (@{$file_cache{$file}}) {
print $fh $line, "\n";
}
close($fh) || die "close $file: $!";
}
sub lock_file { return 1; }
sub unlock_file { return 1; }
sub error { die join("", @_), "\n"; }
sub unflush_file_lines { delete($file_cache{$_[0]}); }
sub has_command { return $_[0] eq "netplan" ? "/usr/sbin/netplan" : undef; }
sub execute_command_logged
{
my ($cmd, undef, $stdout, $stderr) = @_;
push(@commands, $cmd);
my $out = $command_output{$cmd} || "";
$$stdout = $out if (ref($stdout));
$$stderr = $out if (ref($stderr) && $stderr ne $stdout);
return $command_status{$cmd} || 0;
}
sub backquote_logged
{
my ($cmd) = @_;
push(@commands, $cmd);
$? = $command_status{$cmd} || 0;
return $command_output{$cmd} || "";
}
sub check_ipaddress { return $_[0] =~ /^\d+\.\d+\.\d+\.\d+$/; }
sub check_ip6address { return $_[0] =~ /:/; }
sub check_ipaddress_any { return &check_ipaddress($_[0]) || &check_ip6address($_[0]); }
sub mask_to_prefix { return $_[0] eq "255.255.255.0" ? 24 : $_[0]; }
sub prefix_to_mask { return $_[0] == 24 ? "255.255.255.0" : $_[0]; }
sub indexof
{
my ($needle, @haystack) = @_;
for(my $i=0; $i<@haystack; $i++) {
return $i if ($haystack[$i] eq $needle);
}
return -1;
}
unshift(@INC, "$root/net", $root);
do "$root/net/net-detect.pl" || die "net-detect.pl: $@ $!";
my $detect_root = tempdir(CLEANUP => 1);
my $detect_netplan = "$detect_root/netplan";
my $detect_no_netplan = "$detect_root/no-netplan";
my $detect_nm = "$detect_root/NetworkManager/system-connections";
my $detect_nm_empty = "$detect_root/NetworkManager-empty/system-connections";
make_path($detect_netplan, $detect_nm, $detect_nm_empty);
write_text("$detect_nm/eth0.nmconnection", "");
is(main::net_auto_backend("debian-linux", $detect_netplan, $detect_nm_empty),
"netplan", "Debian uses Netplan when the config directory exists");
is(main::net_auto_backend("debian-linux", $detect_no_netplan, $detect_nm),
"nm", "Debian uses NetworkManager when only nmconnection files exist");
is(main::net_auto_backend("redhat-linux", $detect_no_netplan, $detect_nm),
"nm", "Red Hat still uses NetworkManager when nmconnection files exist");
write_text("$detect_netplan/50-cloud-init.yaml", "");
is(main::net_auto_backend("debian-linux", $detect_netplan, $detect_nm),
"netplan", "Debian prefers Netplan over NetworkManager when YAML exists");
unlink("$detect_netplan/50-cloud-init.yaml");
is(main::net_auto_backend("debian-linux", $detect_no_netplan, $detect_nm_empty),
undef, "Debian falls back when no Netplan or NetworkManager config exists");
do "$root/net/netplan-lib.pl" || die "netplan-lib.pl: $@ $!";
{
no warnings 'once';
$main::netplan_dir = $tmp;
}
my $netplan = "$tmp/50-cloud-init.yaml";
write_text($netplan, <<'YAML');
network:
version: 2
ethernets:
eth0:
dhcp4: true
nameservers:
addresses:
- 1.1.1.1
search:
- example.com
eth1:
dhcp4: true
YAML
my @boot = main::boot_interfaces();
is(scalar(grep { !defined($_->{'virtual'}) || $_->{'virtual'} eq '' } @boot),
2, "parsed two boot interfaces");
my ($eth0) = grep { $_->{'fullname'} eq "eth0" } @boot;
$eth0->{'nameserver'} = [ "127.0.0.1", "2001:4860:4860::8888" ];
$eth0->{'search'} = [ "example.com" ];
main::save_interface($eth0, \@boot);
my $saved = read_text($netplan);
like($saved, qr/^ eth0:\n dhcp4: true\n nameservers:\n addresses: \[127\.0\.0\.1, '2001:4860:4860::8888'\]\n search: \[example\.com\]/m,
"save_interface preserves existing two-space Netplan hierarchy");
like($saved, qr/^ eth1:\n dhcp4: true/m,
"untouched sibling interface keeps matching indentation");
unlike($saved, qr/^ eth0:/m,
"rewritten interface is not moved to an eight-space sibling indent");
write_text($netplan, <<'YAML');
network:
version: 2
ethernets:
eth0:
dhcp4: true
nameservers:
addresses:
- 1.1.1.1
eth1:
dhcp4: true
YAML
@boot = main::boot_interfaces();
my ($need_apply, $generated_resolv) =
main::os_save_dns_config({ 'nameserver' => [ "127.0.0.1" ],
'domain' => [ "example.com" ] });
ok($need_apply, "DNS changes request a netplan apply");
ok($generated_resolv, "Netplan DNS save reports resolv.conf as generated");
$saved = read_text($netplan);
like($saved, qr/^ eth0:\n dhcp4: true\n nameservers:\n addresses: \[127\.0\.0\.1\]\n search: \[example\.com\]/m,
"os_save_dns_config updates existing nameservers block");
unlike($saved, qr/eth1:\n(?:.*\n)*?\s+nameservers:/,
"os_save_dns_config does not add nameservers to every interface");
@commands = ( );
%command_status = (
"(cd / && /usr/sbin/netplan generate)" => 1,
);
%command_output = (
"(cd / && /usr/sbin/netplan generate)" => "bad yaml\n",
);
is(main::apply_network(), "bad yaml\n",
"apply_network returns validation errors");
is_deeply(\@commands, [ "(cd / && /usr/sbin/netplan generate)" ],
"apply_network skips apply when generate fails");
@commands = ( );
%command_status = ( );
%command_output = ( );
is(main::apply_network(), undef, "apply_network applies after validation");
is_deeply(\@commands,
[ "(cd / && /usr/sbin/netplan generate)",
"(cd / && /usr/sbin/netplan apply)" ],
"apply_network validates before applying");
do "$root/net/nm-lib.pl" || die "nm-lib.pl: $@ $!";
my $nmfile = "$tmp/eth0.nmconnection";
write_text($nmfile, <<'NM');
[connection]
id=eth0
uuid=11111111-2222-3333-4444-555555555555
type=ethernet
interface-name=eth0
[ipv4]
method=auto
[ipv6]
method=disabled
NM
my $nmcfg = main::read_nm_config($nmfile);
my $nmiface = {
'name' => 'eth0',
'fullname' => 'eth0',
'file' => $nmfile,
'cfg' => $nmcfg,
'edit' => 1,
'up' => 1,
'dhcp' => 1,
'address6' => [ ],
'netmask6' => [ ],
'nameserver' => [ "2001:4860:4860::8888" ],
};
@commands = ( );
main::save_interface($nmiface, [ $nmiface ]);
like(join("\n", @commands), qr/ipv6\.dns/,
"NetworkManager save_interface writes IPv6 nameservers");
done_testing();