From f9ed90ec0bc0827545f2639a37731d35a51893f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:49:02 -0300 Subject: [PATCH 1/6] fix(rspconfig): accept one network value per BMC A node can carry more than one BMC, and rspconfig already opens a session per BMC for rinv, reventlog, rvitals and rspconfig. A setting such as ip= carried a single value, so every BMC of the node received the same one. Two BMCs cannot share an address, so a node like that could not be configured through rspconfig at all. Read a comma separated value as one setting per BMC, in the order the sessions are numbered. Only the ip, netmask and gateway settings read a list, because a comma belongs to the value itself in a free form SNMP community string. A value without a comma still reaches every BMC, so the existing single BMC use is unchanged. An entry that is missing or empty reports the mismatch instead of reaching the address encoders, which reject an empty string. The session then holds the value of its own BMC, because the follow-up callbacks read the subcommand again to decide whether the address came from DHCP. Recovered from the lenovobuild branch. --- xCAT-server/lib/xcat/plugins/ipmi.pm | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/xCAT-server/lib/xcat/plugins/ipmi.pm b/xCAT-server/lib/xcat/plugins/ipmi.pm index ddd6a2bfc..215d93c16 100644 --- a/xCAT-server/lib/xcat/plugins/ipmi.pm +++ b/xCAT-server/lib/xcat/plugins/ipmi.pm @@ -868,6 +868,25 @@ sub next_setnetinfo { &setnetinfo($sessdata); } +# The settings that take one value per BMC. Every other subcommand keeps its +# argument whole, because a comma can belong to the value itself. +my %per_bmc_subcommand = map { $_ => 1 } qw(ip netmask gateway); + +#------------------------------------------------------- +# A node can carry more than one BMC, and each session then runs with its own +# bmcnum. A comma separated value gives one setting per BMC, in that order. +# Returns the value for this BMC, or undef when the list holds none there. +#------------------------------------------------------- +sub per_bmc_argument { + my ($argument, $bmcnum) = @_; + return $argument unless defined $argument and $argument =~ /,/; + $bmcnum = 1 unless defined $bmcnum and $bmcnum =~ /^\d+$/ and $bmcnum > 0; + my @arglist = split /,/, $argument, -1; + my $value = $arglist[ $bmcnum - 1 ]; + return undef unless defined $value and $value ne ''; + return $value; +} + sub setnetinfo { my $sessdata = shift; my $subcommand = $sessdata->{subcommand}; @@ -895,6 +914,19 @@ sub setnetinfo { if ($subcommand eq "thermprofile") { return idpxthermprofile($argument); } + + if ($per_bmc_subcommand{$subcommand} and $argument =~ /,/) { + my $bmcvalue = per_bmc_argument($argument, $sessdata->{bmcnum}); + unless (defined $bmcvalue) { + $callback->({ errorcode => [1], error => ["The value $argument does not carry a setting for BMC " . $sessdata->{bmcnum} . ", give one value per BMC"] }); + return; + } + $argument = $bmcvalue; + + # The follow-up callbacks read the subcommand again, so leave this + # session holding the value of its own BMC + $sessdata->{subcommand} = "$subcommand=$argument"; + } if ($subcommand eq "alert" and ($argument =~ /^(on|en|enable|enabled)$/i)) { $netfun = 0x4; @cmd = (0x12, 0x9, 0x1, 0x18, 0x11, 0x00); From 8a64b224a33b5c9305b30d380be4eca757238be5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:49:03 -0300 Subject: [PATCH 2/6] test(rspconfig): cover one network value per BMC Extract per_bmc_argument and the set of settings that take a list from ipmi.pm, and drive the real routine, because the plugin needs daemon dependencies that the test host does not have. Cover a single value serving every BMC, a list read in session order, a list too short for the BMC being configured, empty entries, and the defensive inputs. Assert that only ip, netmask and gateway read a list, that the caller reports a short list rather than sending it, that the session keeps the value of its own BMC for the follow-up, and that the thermal profile keeps its own argument. --- xCAT-test/unit/rspconfig_per_bmc_argument.t | 78 +++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 xCAT-test/unit/rspconfig_per_bmc_argument.t diff --git a/xCAT-test/unit/rspconfig_per_bmc_argument.t b/xCAT-test/unit/rspconfig_per_bmc_argument.t new file mode 100644 index 000000000..51b9bc0c0 --- /dev/null +++ b/xCAT-test/unit/rspconfig_per_bmc_argument.t @@ -0,0 +1,78 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use FindBin; +use File::Spec; +use Test::More; + +my $plugin = File::Spec->catfile( $FindBin::Bin, '..', '..', + 'xCAT-server', 'lib', 'xcat', 'plugins', 'ipmi.pm' ); +plan skip_all => 'ipmi.pm not found' unless -r $plugin; + +open( my $fh, '<', $plugin ) or die "Unable to read $plugin: $!"; +my $source = do { local $/; <$fh> }; +close($fh); + +# ipmi.pm cannot be loaded here, so lift out the routine and drive the real +# code on its own. +my ($routine) = $source =~ /(sub per_bmc_argument \{.*?\n\}\n)/s; +my ($gate) = $source =~ /(my %per_bmc_subcommand = map.*?;)/s; +BAIL_OUT('could not extract per_bmc_argument from ipmi.pm') unless $routine; +BAIL_OUT('could not extract the per BMC subcommand set from ipmi.pm') unless $gate; +eval "package BmcArg; $gate $routine sub gate { return \\%per_bmc_subcommand } 1;" + or BAIL_OUT("could not evaluate per_bmc_argument: $@"); + +sub pick { return BmcArg::per_bmc_argument(@_); } +my $per_bmc = BmcArg::gate(); + +# A single value reaches every BMC, which is what a node with one BMC needs +# and what every existing command line looks like. +is( pick( '10.0.0.5', 1 ), '10.0.0.5', 'a single value serves the first BMC' ); +is( pick( '10.0.0.5', 2 ), '10.0.0.5', 'a single value serves a later BMC too' ); + +# A list gives one setting per BMC, in session order. +is( pick( '10.0.0.5,10.0.0.6', 1 ), '10.0.0.5', 'the first BMC takes the first value' ); +is( pick( '10.0.0.5,10.0.0.6', 2 ), '10.0.0.6', 'the second BMC takes the second value' ); +is( pick( '10.0.0.5,10.0.0.6,10.0.0.7', 3 ), '10.0.0.7', 'the third BMC takes the third value' ); + +# A list too short reports nothing rather than an empty setting, so the caller +# can tell the operator instead of writing a blank value into a BMC. +is( pick( '10.0.0.5,10.0.0.6', 3 ), undef, 'a list shorter than the BMC number yields nothing' ); + +# An empty element carries no setting, so it reads as missing rather than +# reaching the address encoders, which reject an empty string outright. +is( pick( '10.0.0.5,', 2 ), undef, 'an empty element yields nothing' ); +is( pick( ',10.0.0.6', 1 ), undef, 'a leading empty element yields nothing' ); + +# Defensive input. +is( pick( undef, 1 ), undef, 'an undefined argument stays undefined' ); +is( pick( '10.0.0.5,10.0.0.6' ), '10.0.0.5', 'a missing BMC number reads as the first' ); +is( pick( '10.0.0.5,10.0.0.6', 0 ), '10.0.0.5', 'a zero BMC number reads as the first' ); + +# The netmask and gateway settings travel the same path. +is( pick( '255.255.255.0,255.255.254.0', 2 ), '255.255.254.0', 'a netmask list follows the BMC number' ); + +# Only the settings the man page advertises take a list. Every other +# subcommand keeps its argument whole, because a comma can belong to the value, +# as it does in a free form SNMP community string. +ok( $per_bmc->{$_}, "$_ takes one value per BMC" ) for qw(ip netmask gateway); +ok( !$per_bmc->{$_}, "$_ keeps its argument whole" ) for qw(community snmpdest alert garp thermprofile); + +like( $source, qr/if \(\$per_bmc_subcommand\{\$subcommand\} and \$argument =~ \/,\/\)/, + 'the caller splits only the settings that take a list' ); + +# The follow-up callbacks read the subcommand again, so the session has to hold +# the value of its own BMC by then. +like( $source, qr/\$sessdata->\{subcommand\} = "\$subcommand=\$argument";/, + 'the session keeps the value of its own BMC for the follow-up' ); + +# The caller reports the mismatch rather than sending an empty setting, and +# the thermal profile keeps its own argument. +like( $source, + qr/unless \(defined \$bmcvalue\) \{\s*\n\s*\$callback->\(\{ errorcode => \[1\], error => \["The value \$argument does not carry a setting for BMC/, + 'the caller reports a short list instead of sending it' ); +like( $source, qr/if \(\$subcommand eq "thermprofile"\) \{\n\s*return idpxthermprofile\(\$argument\);/, + 'the thermal profile is answered before the per BMC split' ); + +done_testing(); From d4c3191b07c8a71b6add2ce3fba57373a3f7b8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Fri, 21 Aug 2026 02:49:03 -0300 Subject: [PATCH 3/6] docs(rspconfig): describe one network value per BMC Record that a node with more than one BMC is configured one BMC at a time and that a comma separated value gives one setting per BMC, in both the man page source and the checked-in text. --- .../guides/admin-guides/references/man1/rspconfig.1.rst | 4 ++++ xCAT-client/pods/man1/rspconfig.1.pod | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/docs/source/guides/admin-guides/references/man1/rspconfig.1.rst b/docs/source/guides/admin-guides/references/man1/rspconfig.1.rst index c77901a03..6a8b6b7f6 100644 --- a/docs/source/guides/admin-guides/references/man1/rspconfig.1.rst +++ b/docs/source/guides/admin-guides/references/man1/rspconfig.1.rst @@ -40,6 +40,8 @@ BMC specific: \ **rspconfig**\ \ *noderange*\ {\ **ip | netmask | gateway | backupgateway | garp | vlan**\ } +\ **rspconfig**\ \ *noderange*\ {\ **ip | netmask | gateway**\ }=\ *value*\ [,\ *value*\ ...] + \ **rspconfig**\ \ *noderange*\ \ **garp**\ =\ *time*\ @@ -313,6 +315,8 @@ DESCRIPTION For options \ **autopower | iocap | decfg | memdecfg | procdecfg | time | date | spdump | sysdump | network**\ , user need to use \ *chdef -t site enableASMI=yes*\ to enable ASMI first. +A node whose \ **ipmi.bmc**\ attribute names more than one BMC is configured one BMC at a time, in the order the attribute lists them. Give one value per BMC, separated by commas, when a setting differs between them, for example \ *ip=10.1.1.1,10.1.1.2*\ . A single value is sent to every BMC of the node. + ******* OPTIONS diff --git a/xCAT-client/pods/man1/rspconfig.1.pod b/xCAT-client/pods/man1/rspconfig.1.pod index 412d1daeb..ac32107d1 100644 --- a/xCAT-client/pods/man1/rspconfig.1.pod +++ b/xCAT-client/pods/man1/rspconfig.1.pod @@ -20,6 +20,8 @@ B I B={B | I} B I {B|B|B|B|B|B} +B I {B|B|B}=I[,I...] + B I B=I