mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-07-29 17:19:39 +00:00
Add networkd fallback for netplan static IP probe
Detect static netplan addresses on older Ubuntu netplan releases by falling back to generated systemd-networkd configuration when netplan get is unavailable. Keep netplan get authoritative when supported. Co-authored-by: karcaw <663031+karcaw@users.noreply.github.com>
This commit is contained in:
@@ -160,10 +160,16 @@ sub get_os {
|
||||
|
||||
sub _netplan_get {
|
||||
my $key = shift;
|
||||
open(my $fh, '-|', 'netplan', 'get', $key) or return undef;
|
||||
my $pid = open(my $fh, '-|');
|
||||
return unless defined $pid;
|
||||
if ($pid == 0) {
|
||||
open(STDERR, '>', '/dev/null');
|
||||
exec 'netplan', 'get', $key;
|
||||
exit 1;
|
||||
}
|
||||
my $val = <$fh>;
|
||||
close $fh;
|
||||
return undef if $?;
|
||||
return if $?;
|
||||
chomp $val if defined $val;
|
||||
return $val;
|
||||
}
|
||||
@@ -177,30 +183,113 @@ sub _command_available {
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub _netplan_has_static_ip {
|
||||
my ($nic, $ip) = @_;
|
||||
sub _networkd_config_dirs {
|
||||
return ('/run/systemd/network', '/etc/systemd/network');
|
||||
}
|
||||
|
||||
return 0 unless _command_available('netplan');
|
||||
sub _networkd_name_matches {
|
||||
my ($names, $nic) = @_;
|
||||
|
||||
(my $escaped_nic = $nic) =~ s/\./\\./g;
|
||||
for my $devtype (qw(ethernets bonds bridges vlans)) {
|
||||
my $dev_obj = _netplan_get("$devtype.$escaped_nic");
|
||||
next unless defined $dev_obj;
|
||||
next if ($dev_obj eq 'null' || $dev_obj eq '');
|
||||
|
||||
my $addresses = _netplan_get("$devtype.$escaped_nic.addresses");
|
||||
next unless defined $addresses;
|
||||
next if ($addresses eq 'null' || $addresses eq '');
|
||||
next unless $addresses =~ /(?:^|[\s\[,])\Q$ip\E(?:\/\d+)?(?:$|[\s\],])/m;
|
||||
|
||||
my $dhcp_val = _netplan_get("$devtype.$escaped_nic.dhcp4");
|
||||
return 0 if defined $dhcp_val && $dhcp_val =~ /true/i;
|
||||
return 1;
|
||||
foreach my $name (split /\s+/, $names) {
|
||||
return 1 if $name eq $nic;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub _networkd_address_matches {
|
||||
my ($addresses, $ip) = @_;
|
||||
|
||||
foreach my $address (split /\s+/, $addresses) {
|
||||
return 1 if $address =~ /^\Q$ip\E(?:\/\d+)?$/;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub _networkd_file_has_static_ip {
|
||||
my ($file, $nic, $ip) = @_;
|
||||
my $section = '';
|
||||
my $matched_nic = 0;
|
||||
my $matched_ip = 0;
|
||||
my $dhcp4 = 0;
|
||||
|
||||
open(my $fh, '<', $file) or return 0;
|
||||
while (my $line = <$fh>) {
|
||||
chomp $line;
|
||||
$line =~ s/^\s+|\s+$//g;
|
||||
next if $line eq '' || $line =~ /^[#;]/;
|
||||
$line =~ s/\s*[#;].*$//;
|
||||
|
||||
if ($line =~ /^\[([^\]]+)\]$/) {
|
||||
$section = lc $1;
|
||||
next;
|
||||
}
|
||||
|
||||
if ($section eq 'match' && $line =~ /^Name\s*=\s*(.+)$/i) {
|
||||
$matched_nic = 1 if _networkd_name_matches($1, $nic);
|
||||
next;
|
||||
}
|
||||
|
||||
if ($section eq 'network' && $line =~ /^Address\s*=\s*(.+)$/i) {
|
||||
$matched_ip = 1 if _networkd_address_matches($1, $ip);
|
||||
next;
|
||||
}
|
||||
|
||||
if ($section eq 'network' && $line =~ /^DHCP\s*=\s*(.+)$/i) {
|
||||
$dhcp4 = 1 if $1 =~ /^(?:yes|true|ipv4|both)$/i;
|
||||
}
|
||||
}
|
||||
close $fh;
|
||||
|
||||
return $matched_nic && $matched_ip && !$dhcp4;
|
||||
}
|
||||
|
||||
sub _networkd_has_static_ip {
|
||||
my ($nic, $ip) = @_;
|
||||
|
||||
foreach my $dir (_networkd_config_dirs()) {
|
||||
next unless -d $dir;
|
||||
opendir(my $dh, $dir) or next;
|
||||
foreach my $file (sort grep { /\.network$/ } readdir($dh)) {
|
||||
return 1 if _networkd_file_has_static_ip("$dir/$file", $nic, $ip);
|
||||
}
|
||||
closedir $dh;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub _netplan_has_static_ip {
|
||||
my ($nic, $ip) = @_;
|
||||
my $netplan_get_supported = 0;
|
||||
|
||||
if (_command_available('netplan')) {
|
||||
(my $escaped_nic = $nic) =~ s/\./\\./g;
|
||||
for my $devtype (qw(ethernets bonds bridges vlans)) {
|
||||
my $dev_obj = _netplan_get("$devtype.$escaped_nic");
|
||||
$netplan_get_supported = 1 if defined $dev_obj;
|
||||
next unless defined $dev_obj;
|
||||
next if ($dev_obj eq 'null' || $dev_obj eq '');
|
||||
|
||||
my $addresses = _netplan_get("$devtype.$escaped_nic.addresses");
|
||||
next unless defined $addresses;
|
||||
next if ($addresses eq 'null' || $addresses eq '');
|
||||
next unless $addresses =~ /(?:^|[\s\[,"'])\Q$ip\E(?:\/\d+)?(?:$|[\s\],"'])/m;
|
||||
|
||||
my $dhcp_val = _netplan_get("$devtype.$escaped_nic.dhcp4");
|
||||
return 0 if defined $dhcp_val && $dhcp_val =~ /true/i;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0 if $netplan_get_supported;
|
||||
|
||||
# Older netplan releases can render networkd files but do not support
|
||||
# "netplan get", so use the generated files as a conservative fallback.
|
||||
return _networkd_has_static_ip($nic, $ip);
|
||||
}
|
||||
|
||||
sub dhcp_query_reply_mac {
|
||||
my ($reply, $node, $ip) = @_;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ use warnings;
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../../xCAT-probe/lib/perl";
|
||||
|
||||
use File::Temp qw(tempdir);
|
||||
use Test::More;
|
||||
|
||||
require probe_utils;
|
||||
@@ -14,8 +15,11 @@ my %netplan = (
|
||||
'ethernets.eth0.addresses' => "- 10.0.0.2/24\n",
|
||||
'ethernets.eth0.dhcp4' => 'false',
|
||||
'ethernets.eth1' => 'renderer: networkd',
|
||||
'ethernets.eth1.addresses' => "- 10.0.0.3/24\n",
|
||||
'ethernets.eth1.dhcp4' => 'true',
|
||||
'ethernets.eth1.addresses' => "- \"10.0.0.3/24\"\n",
|
||||
'ethernets.eth1.dhcp4' => 'false',
|
||||
'ethernets.eth4' => 'renderer: networkd',
|
||||
'ethernets.eth4.addresses' => "- 10.0.0.4/24\n",
|
||||
'ethernets.eth4.dhcp4' => 'true',
|
||||
'vlans.bond0\.123' => 'renderer: networkd',
|
||||
'vlans.bond0\.123.addresses' => "- 10.0.123.5/24\n",
|
||||
);
|
||||
@@ -27,8 +31,89 @@ my %netplan = (
|
||||
|
||||
ok(probe_utils::_netplan_has_static_ip('eth0', '10.0.0.2'), 'static netplan address is detected');
|
||||
ok(!probe_utils::_netplan_has_static_ip('eth0', '10.0.0.99'), 'wrong address is not treated as static');
|
||||
ok(!probe_utils::_netplan_has_static_ip('eth1', '10.0.0.3'), 'dhcp4 true is not treated as static');
|
||||
ok(probe_utils::_netplan_has_static_ip('eth1', '10.0.0.3'), 'quoted netplan address is detected');
|
||||
ok(!probe_utils::_netplan_has_static_ip('eth4', '10.0.0.4'), 'dhcp4 true is not treated as static');
|
||||
ok(probe_utils::_netplan_has_static_ip('bond0.123', '10.0.123.5'), 'dotted VLAN interface is escaped for netplan get');
|
||||
}
|
||||
|
||||
sub write_file {
|
||||
my ($file, $contents) = @_;
|
||||
|
||||
open(my $fh, '>', $file) or die "Unable to write $file: $!";
|
||||
print $fh $contents;
|
||||
close $fh;
|
||||
}
|
||||
|
||||
my $networkd_dir = tempdir(CLEANUP => 1);
|
||||
my $fake_bin = tempdir(CLEANUP => 1);
|
||||
|
||||
write_file("$networkd_dir/10-netplan-eth2.network", <<'EOF');
|
||||
[Match]
|
||||
Name=eth2
|
||||
|
||||
[Network]
|
||||
Address=10.0.2.5/24
|
||||
EOF
|
||||
|
||||
write_file("$networkd_dir/10-netplan-eth3.network", <<'EOF');
|
||||
[Match]
|
||||
Name=eth3
|
||||
|
||||
[Network]
|
||||
Address=10.0.3.5/24
|
||||
DHCP=ipv4
|
||||
EOF
|
||||
|
||||
write_file("$networkd_dir/10-netplan-eth20.network", <<'EOF');
|
||||
[Match]
|
||||
Name=eth20
|
||||
|
||||
[Network]
|
||||
Address=10.0.20.5/24
|
||||
EOF
|
||||
|
||||
my $fake_netplan = "$fake_bin/netplan";
|
||||
write_file($fake_netplan, <<'EOF');
|
||||
#!/bin/sh
|
||||
echo "netplan get is not supported" >&2
|
||||
exit 1
|
||||
EOF
|
||||
chmod oct('755'), $fake_netplan;
|
||||
|
||||
{
|
||||
no warnings 'redefine';
|
||||
local *probe_utils::_networkd_config_dirs = sub { return ($networkd_dir); };
|
||||
|
||||
ok(probe_utils::_networkd_has_static_ip('eth2', '10.0.2.5'), 'networkd fallback detects generated static address');
|
||||
ok(!probe_utils::_networkd_has_static_ip('eth2', '10.0.2.99'), 'networkd fallback rejects wrong address');
|
||||
ok(!probe_utils::_networkd_has_static_ip('eth3', '10.0.3.5'), 'networkd fallback rejects DHCP-enabled IPv4 config');
|
||||
ok(!probe_utils::_networkd_has_static_ip('eth2', '10.0.20.5'), 'networkd fallback requires exact interface match');
|
||||
}
|
||||
|
||||
{
|
||||
no warnings 'redefine';
|
||||
local *probe_utils::_command_available = sub { return $_[0] eq 'netplan' ? 1 : 0; };
|
||||
local *probe_utils::_netplan_get = sub { return; };
|
||||
local *probe_utils::_networkd_config_dirs = sub { return ($networkd_dir); };
|
||||
|
||||
ok(probe_utils::_netplan_has_static_ip('eth2', '10.0.2.5'), 'old netplan without get falls back to generated networkd config');
|
||||
}
|
||||
|
||||
{
|
||||
no warnings 'redefine';
|
||||
local $ENV{PATH} = "$fake_bin:$ENV{PATH}";
|
||||
local *probe_utils::_networkd_config_dirs = sub { return ($networkd_dir); };
|
||||
|
||||
ok(probe_utils::_netplan_has_static_ip('eth2', '10.0.2.5'), 'unsupported netplan get command uses generated networkd fallback');
|
||||
}
|
||||
|
||||
{
|
||||
no warnings 'redefine';
|
||||
local *probe_utils::_command_available = sub { return $_[0] eq 'netplan' ? 1 : 0; };
|
||||
local *probe_utils::_netplan_get = sub { return 'null'; };
|
||||
local *probe_utils::_networkd_config_dirs = sub { return ($networkd_dir); };
|
||||
|
||||
ok(!probe_utils::_netplan_has_static_ip('eth2', '10.0.2.5'), 'supported netplan get remains authoritative when no netplan key matches');
|
||||
}
|
||||
|
||||
done_testing();
|
||||
|
||||
Reference in New Issue
Block a user