From 0b4695eef232e9a333284d1d2bba9f062ea338a9 Mon Sep 17 00:00:00 2001 From: Kilian Cavalotti Date: Tue, 25 Aug 2026 17:44:23 -0700 Subject: [PATCH] Accept MIB labels for genpdu enumerated values With PDU2-MIB loaded, SNMP.pm returns an enumerated INTEGER as its label rather than its number: a PX4 answers inletSensorUnits with "amp" rather than "2". That failed the numeric test, fell back to none(-1) and dropped the unit suffix from every rvitals reading. Add pdu2_enum, which accepts the number, the label, or the label(value) form Net-SNMP produces with quick printing off, and use it for the sensor units and for outletSwitchingState. Also stop requiring the switching probe to read on or off. An outlet can be in any SensorStateEnumeration state, and pdu2_get has already ruled out an absent instance, so any answer proves the outlet is switchable. Signed-off-by: Kilian Cavalotti --- xCAT-server/lib/xcat/plugins/pdu.pm | 58 +++++++++++++++++++++++------ xCAT-test/unit/pdu_genpdu.t | 38 +++++++++++++++++++ 2 files changed, 85 insertions(+), 11 deletions(-) diff --git a/xCAT-server/lib/xcat/plugins/pdu.pm b/xCAT-server/lib/xcat/plugins/pdu.pm index 4d454e226..dc8c1bc59 100644 --- a/xCAT-server/lib/xcat/plugins/pdu.pm +++ b/xCAT-server/lib/xcat/plugins/pdu.pm @@ -126,6 +126,16 @@ my %PDU2_SENSOR_UNIT = ( 4 => "VA", 5 => "Wh", 6 => "VAh", 7 => "C", 8 => "Hz", 9 => "%", 20 => "deg", 23 => "var", ); + +#Net-SNMP returns an enumerated INTEGER as its MIB label once the MIB is loaded +#(a PX4 answers "amp" rather than "2"), so both forms have to be accepted. Keys +#are lower cased, since the MIB labels are not. +my %PDU2_UNIT_CODE = ( + none => -1, other => 0, volt => 1, amp => 2, watt => 3, + voltamp => 4, watthour => 5, voltamphour => 6, degreec => 7, + hertz => 8, percent => 9, degrees => 20, var => 23, +); +my %PDU2_STATE_CODE = (on => 7, off => 8); #Ordered for readability rather than by enum value. my @PDU2_INLET_SENSORS = (4, 1, 5, 6, 7, 23, 3, 29, 8, 9, 10); my @PDU2_OUTLET_SENSORS = (1, 5, 8); @@ -660,16 +670,15 @@ sub outletstat { my $session = shift; my $outlet = shift; - #genpdu: PDU2-MIB outletSwitchingState uses SensorStateEnumeration, of which - #on(7) and off(8) are the switching states. The value may come back as the - #integer or as the MIB label depending on which MIBs the Net-SNMP perl - #module has loaded, so both forms are accepted. + #genpdu: outletSwitchingState uses SensorStateEnumeration, of which on(7) + #and off(8) are the switching states. if ($session->{genpdu}) { my $val = $session->get("$PDU2_OUTLETSTATE.$PDU2_PDUID.$outlet"); return "unknown state" unless (defined $val); - if ($val eq "7" or $val eq "on") { return "on"; } - elsif ($val eq "8" or $val eq "off") { return "off"; } - else { return "$val(unknown state)"; } + my $state = pdu2_enum($val, \%PDU2_STATE_CODE); + return "on" if (defined $state and $state == 7); + return "off" if (defined $state and $state == 8); + return "$val(unknown state)"; } my $oid = ".1.3.6.1.4.1.2.6.223.8.2.2.1.11"; @@ -841,6 +850,31 @@ sub pdu2_session_args { #------------------------------------------------------- +=head3 pdu2_enum + + Numeric value of an enumerated INTEGER. Net-SNMP returns those as the MIB + label once the MIB is loaded, as label(value) when quick printing is off, and + as the number when it is not. Returns undef for anything else. + +=cut + +#------------------------------------------------------- +sub pdu2_enum { + my ($val, $codes) = @_; + + return undef unless (defined $val); + $val =~ s/^\s+|\s+$//g; + return $val if ($val =~ /^-?\d+$/); + + #Trust the label over the number in label(value), so a mislabelled agent + #cannot turn volts into amperes. + my ($label, $num) = ($val =~ /^(\w+)\((-?\d+)\)$/) ? ($1, $2) : ($val, undef); + return $codes->{ lc $label } if (defined $codes->{ lc $label }); + return $num; +} + +#------------------------------------------------------- + =head3 pdu2_get Read one object. Returns its value and one of 'ok', 'absent' or 'failed'. @@ -900,10 +934,11 @@ sub pdu2_session_probe { } #Metered-only models such as PX2-1901U answer the measurement tables but - #have no outletSwitchControlTable instances. + #have no outletSwitchControlTable instances. Any state proves the instance + #exists, and an outlet is not always on or off, so this only asks whether + #the read produced one: pdu2_get has already ruled out absent and failed. my ($probe) = pdu2_get($session, "$PDU2_OUTLETSTATE.$PDU2_PDUID.1"); - $session->{genpdu_switchable} = - ((defined $probe) and ($probe =~ /^\d+$/ or $probe =~ /^(on|off)$/i)) ? 1 : 0; + $session->{genpdu_switchable} = (defined $probe) ? 1 : 0; return 1; } @@ -949,7 +984,8 @@ sub pdu2_sensor_fmt { my ($u, $ustate) = pdu2_get($session, "$oids->{units}.$idx"); my ($d, $dstate) = pdu2_get($session, "$oids->{dec}.$idx"); return undef if ($ustate eq 'failed' or $dstate eq 'failed'); - $u = -1 unless (defined $u and $u =~ /^-?\d+$/); + $u = pdu2_enum($u, \%PDU2_UNIT_CODE); + $u = -1 unless (defined $u); $d = 0 unless (defined $d and $d =~ /^\d+$/); $cache->{$st} = [ $u, $d ]; } diff --git a/xCAT-test/unit/pdu_genpdu.t b/xCAT-test/unit/pdu_genpdu.t index 4a2e99660..04723b4dd 100644 --- a/xCAT-test/unit/pdu_genpdu.t +++ b/xCAT-test/unit/pdu_genpdu.t @@ -187,6 +187,36 @@ ok( 'outlet sensors are read for every outlet in the count' ); +#-- rvitals: units as MIB labels ---------------------------------------------- + +#With PDU2-MIB loaded, Net-SNMP returns SensorUnitsEnumeration as its label: +#a PX4 answers "amp" rather than "2", and "amp(2)" when quick printing is off. +sub units_as { + my ($units) = @_; + my %resp = ("$UNITCONF.2.1" => 1); + inlet_sensor(\%resp, 1, $PX4_INLET[0]); + $resp{"$INLET_UNITS.1.1.1"} = $units; + my ($m) = vitals_messages(\%resp, 0); + return $m; +} + +ok( + (grep { $_ eq 'pdu1|inlet 1 RMS Current: 9.503 A' } @{ units_as('amp') }), + 'a unit returned as its MIB label still gets its suffix' +); +ok( + (grep { $_ eq 'pdu1|inlet 1 RMS Current: 9.503 A' } @{ units_as('amp(2)') }), + 'a unit returned as label(value) still gets its suffix' +); +ok( + (grep { $_ eq 'pdu1|inlet 1 RMS Current: 9.503 Wh' } @{ units_as('wattHour') }), + 'a mixed case MIB label is recognised' +); +ok( + (grep { $_ eq 'pdu1|inlet 1 RMS Current: 9.503' } @{ units_as('lux') }), + 'a unit with no suffix defined is reported without one' +); + #-- rvitals: metered-only PDUs ------------------------------------------------ my %metered = ("$UNITCONF.2.1" => 1); @@ -329,6 +359,12 @@ is(scalar(@$probe_msgs), 0, 'a pduCount of 0 does not warn about linked units'); ok($ok, 'a PDU that answers pduCount is usable'); is($probed->{genpdu_switchable}, 1, 'an answered outlet state marks the PDU switchable'); +($ok, $probed, $probe_msgs) = probe({ + $PDUCOUNT => 1, + "$OUTLETSTATE.1.1" => 'standby', +}); +is($probed->{genpdu_switchable}, 1, 'an outlet in a state other than on or off is still switchable'); + ($ok, $probed, $probe_msgs) = probe({ $PDUCOUNT => 1, "$OUTLETSTATE.1.1" => $NOSUCH, @@ -399,6 +435,8 @@ sub outlet_state { is(outlet_state(7), 'on', 'outletSwitchingState on(7) reads as on'); is(outlet_state(8), 'off', 'outletSwitchingState off(8) reads as off'); +is(outlet_state('on'), 'on', 'an outlet state returned as a MIB label reads as on'); +is(outlet_state('off(8)'), 'off', 'an outlet state returned as label(value) reads as off'); is(outlet_state(undef), 'unknown state', 'an unanswered outlet state is unknown'); #-- rinv ----------------------------------------------------------------------