2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-09-04 20:17:55 +00:00

Merge pull request #7804 from VersatusHPC/refactor/getadapter-pci-scan

refactor(getadapter): share PCI adapter scan
This commit is contained in:
Daniel Hilst
2026-09-03 14:52:33 -03:00
committed by GitHub
2 changed files with 244 additions and 29 deletions
+22 -29
View File
@@ -18,6 +18,24 @@ transmit_adapter_info() {
fi
}
append_missing_pci_adapters() {
local adapter_pattern=$1
local adapter_file=$2
local model_separator=$3
local pci
local tmp
for pci in $(lspci | awk -v adapter_pattern="$adapter_pattern" '$0 ~ adapter_pattern {print $1}'); do
if ! grep -q "$pci" "$adapter_file" 2>/dev/null; then
tmp="$(lspci | grep "$pci")"
echo "<nic>
<pcilocation>$pci</pcilocation>
<model>${tmp##*"$model_separator"}</model>
</nic>" >> "$adapter_file"
fi
done
}
rm -f "$SCANNICLOG" >/dev/null 2>&1
if [ -f "$ADAPTERFILE" ]; then
@@ -66,35 +84,10 @@ for n in /sys/class/net/*; do
fi
done
for pci in $(lspci |awk '/Ethernet/ {print $1}'); do
if ! grep -q "$pci" "$ADAPTERFILE" 2>/dev/null; then
tmp="$(lspci |grep "$pci")"
echo "<nic>
<pcilocation>$pci</pcilocation>
<model>${tmp##*:}</model>
</nic>" >> "$ADAPTERFILE"
fi
done
for pci in $(lspci |awk '/Network/ {print $1}'); do
if ! grep -q "$pci" "$ADAPTERFILE" 2>/dev/null; then
tmp="$(lspci |grep "$pci")"
echo "<nic>
<pcilocation>$pci</pcilocation>
<model>${tmp##*: }</model>
</nic>" >> "$ADAPTERFILE"
fi
done
for pci in $(lspci |awk '/Mellanox/ {print $1}'); do
if ! grep -q "$pci" "$ADAPTERFILE" 2>/dev/null; then
tmp="$(lspci |grep "$pci")"
echo "<nic>
<pcilocation>$pci</pcilocation>
<model>${tmp##*: }</model>
</nic>" >> "$ADAPTERFILE"
fi
done
# Keep the legacy Ethernet model spacing while sharing the scan.
append_missing_pci_adapters "Ethernet" "$ADAPTERFILE" ":"
append_missing_pci_adapters "Network" "$ADAPTERFILE" ": "
append_missing_pci_adapters "Mellanox" "$ADAPTERFILE" ": "
echo "</xcatrequest>" >> "$ADAPTERFILE"
+222
View File
@@ -0,0 +1,222 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Path qw(make_path);
use File::Spec;
use File::Temp qw(tempdir);
use FindBin;
use lib "$FindBin::Bin/../lib";
use POSIX qw(_exit);
use Test::More;
use XCAT::Test::File qw(repo_path);
my $source_getadapter;
if ( defined $ENV{XCAT_TEST_GETADAPTER} ) {
$source_getadapter = $ENV{XCAT_TEST_GETADAPTER};
die "XCAT_TEST_GETADAPTER=$source_getadapter is not a readable file"
unless length($source_getadapter)
&& -f $source_getadapter
&& -r _;
}
else {
$source_getadapter =
repo_path('xCAT-genesis-scripts/usr/bin/getadapter');
}
plan skip_all => "$source_getadapter is required"
unless -f $source_getadapter && -r _;
my $tmpdir = tempdir( CLEANUP => 1 );
my $test_bin = File::Spec->catdir( $tmpdir, 'bin' );
my $sys_class_net = File::Spec->catdir( $tmpdir, 'sys', 'class', 'net' );
my $adapter_file = File::Spec->catfile( $tmpdir, 'adapterinfo' );
my $scan_log = File::Spec->catfile( $tmpdir, 'adapterscan.log' );
my $getadapter = File::Spec->catfile( $tmpdir, 'getadapter' );
my $stdout_file = File::Spec->catfile( $tmpdir, 'stdout' );
my $stderr_file = File::Spec->catfile( $tmpdir, 'stderr' );
my $test_interface = File::Spec->catdir( $sys_class_net, 'eth0' );
make_path( $test_bin, $test_interface );
my $getadapter_body = read_file($source_getadapter);
my $adapter_file_rewrites =
$getadapter_body =~ s{/tmp/adapterinfo}{$adapter_file}g;
my $scan_log_rewrites =
$getadapter_body =~ s{/tmp/adapterscan\.log}{$scan_log}g;
my $sysfs_rewrites =
$getadapter_body =~ s{/sys/class/net}{$sys_class_net}g;
die 'Unable to sandbox getadapter adapter file'
unless $adapter_file_rewrites;
die 'Unable to sandbox getadapter scan log'
unless $scan_log_rewrites;
die 'Unable to sandbox getadapter sysfs paths'
unless $sysfs_rewrites;
write_executable( $getadapter, $getadapter_body );
write_file( File::Spec->catfile( $test_interface, 'address' ),
"aa:bb:cc:dd:ee:ff\n" );
is( system( 'bash', '-n', $getadapter ), 0,
'getadapter has valid Bash syntax' );
my $lspci_fixture = File::Spec->catfile( $tmpdir, 'lspci.txt' );
my $lspci_log = File::Spec->catfile( $tmpdir, 'lspci.log' );
my $request_copy = File::Spec->catfile( $tmpdir, 'request.xml' );
write_executable(
File::Spec->catfile( $test_bin, 'lspci' ),
<<'SH'
#!/bin/sh
printf 'lspci\n' >>"$XCAT_TEST_LSPCI_LOG"
/bin/cat "$XCAT_TEST_LSPCI_FIXTURE"
SH
);
write_executable(
File::Spec->catfile( $test_bin, 'udevadm' ),
<<'SH'
#!/bin/sh
interface=${2##*/}
printf 'E: INTERFACE=%s\n' "$interface"
printf 'E: DEVPATH=/devices/pci0000:00/0000:01:00.0/net/%s\n' "$interface"
SH
);
write_executable(
File::Spec->catfile( $test_bin, 'ip' ),
"#!/bin/sh\nexit 0\n"
);
write_executable(
File::Spec->catfile( $test_bin, 'openssl' ),
<<'SH'
#!/bin/sh
/bin/cat >"$XCAT_TEST_REQUEST_COPY"
SH
);
write_file(
$lspci_fixture,
<<'LSPCI'
01:00.0 Ethernet controller: Existing Ethernet Adapter
02:00.0 Ethernet controller: Mellanox Ethernet Adapter
03:00.0 Network controller: Mellanox Network Adapter
04:00.0 Network controller: Wireless Adapter
05:00.0 Audio device: Example Audio Device
06:00.0 Infiniband controller: Mellanox Technologies MT27800
LSPCI
);
my ( $status, $stdout, $stderr ) = run_getadapter();
is( $status, 0, 'getadapter completes with overlapping PCI classes' );
is( $stdout, '', 'getadapter keeps stdout empty' );
is( $stderr, '', 'getadapter keeps stderr empty' );
my $request = read_file($adapter_file);
is( read_file($request_copy), $request,
'getadapter transmits the generated request unchanged' );
like(
$request,
qr{\A<xcatrequest>\n<command>getadapter</command>\n<action>update</action>},
'getadapter preserves the request header'
);
like( $request, qr{</xcatrequest>\n\z},
'getadapter preserves the request terminator' );
my @missing_pci = $request =~ m{<pcilocation>([0-9][^<]+)</pcilocation>}g;
is_deeply(
\@missing_pci,
[ qw(02:00.0 03:00.0 04:00.0 06:00.0) ],
'missing adapters retain Ethernet, Network, and Mellanox scan order'
);
my @missing_models = $request =~ m{<model>([^<]*)</model>}g;
is_deeply(
\@missing_models,
[
' Mellanox Ethernet Adapter',
'Mellanox Network Adapter',
'Wireless Adapter',
'Mellanox Technologies MT27800'
],
'missing adapters retain their legacy model spacing'
);
unlike( $request, qr{<model>.*Existing Ethernet Adapter</model>},
'an adapter already recorded by sysfs is not appended again' );
is( line_count($lspci_log), 7,
'overlapping Mellanox adapters do not trigger duplicate detail lookups' );
write_file( $lspci_fixture,
"05:00.0 Audio device: Example Audio Device\n" );
write_file( $lspci_log, '' );
( $status, $stdout, $stderr ) = run_getadapter();
is( $status, 0, 'getadapter completes when no PCI class matches' );
is( $stdout, '', 'an empty PCI scan keeps stdout empty' );
is( $stderr, '', 'an empty PCI scan keeps stderr empty' );
$request = read_file($adapter_file);
my @empty_models = $request =~ m{<model>([^<]*)</model>}g;
is_deeply( \@empty_models, [],
'an empty PCI scan does not append missing adapter models' );
is( line_count($lspci_log), 3,
'an empty PCI scan queries each adapter class once' );
done_testing();
sub run_getadapter
{
local %ENV = (
%ENV,
PATH => "$test_bin:$ENV{PATH}",
XCATMASTER => '192.0.2.1',
XCAT_TEST_LSPCI_FIXTURE => $lspci_fixture,
XCAT_TEST_LSPCI_LOG => $lspci_log,
XCAT_TEST_REQUEST_COPY => $request_copy,
);
my $pid = fork();
die "Unable to fork getadapter: $!" unless defined $pid;
if ( $pid == 0 ) {
open( STDOUT, '>:raw', $stdout_file ) or _exit(126);
open( STDERR, '>:raw', $stderr_file ) or _exit(126);
exec 'bash', $getadapter or _exit(127);
}
my $reaped = waitpid( $pid, 0 );
my $raw_status = $?;
my $status = $reaped == $pid && !( $raw_status & 127 )
? $raw_status >> 8
: 255;
my $stdout = read_file($stdout_file) // '';
my $stderr = read_file($stderr_file) // '';
return ( $status, $stdout, $stderr );
}
sub write_executable
{
my ( $path, $contents ) = @_;
write_file( $path, $contents );
chmod 0755, $path or die "Unable to make $path executable: $!";
}
sub write_file
{
my ( $path, $contents ) = @_;
open( my $fh, '>:raw', $path ) or die "Unable to write $path: $!";
print {$fh} $contents;
close($fh) or die "Unable to close $path: $!";
}
sub read_file
{
my ($path) = @_;
open( my $fh, '<:raw', $path ) or die "Unable to read $path: $!";
my $contents = do { local $/; <$fh> };
close($fh) or die "Unable to close $path: $!";
return $contents;
}
sub line_count
{
my ($path) = @_;
my $contents = read_file($path);
return scalar grep { length($_) } split /\n/, $contents;
}