Compare commits

..

4 Commits

Author SHA1 Message Date
d3d274b2a1 A big rework is needed
A rework of exhaust temp, everything for PSU2 and
CPU2 temp is needed.

On R430 there is only Inlet Temp sensor
which crashes the program.

Also if there is only one cpu installed then the program
will also crash as there is no OID for cpu2

Adding a try: for above mentioned OIDs and then
combining output into one Queue entry should fix it
2026-03-01 21:29:28 +01:00
583a72ac98 Added OIDs for IDRAC9 as they are slightly different from 7 and 8 2026-03-01 21:28:58 +01:00
11dc0397e7 A small HowTo for SNMP on IDRAC7-9 2026-03-01 21:28:41 +01:00
44665891ab Added a totalBoardPower to table for IDRAC9 2026-03-01 21:28:26 +01:00
7 changed files with 255 additions and 30 deletions

13
IDRACconfigutarion.md Normal file
View File

@@ -0,0 +1,13 @@
# IDRAC7/8
First under **IDRAC Settings** go to **Network**, and then to **Services**. There scroll down to **SNMP Agent** section and check the **Enabled** checkbox, set **SNMP Protocol** to **SNMP v3**. Under **SNMP Discovery Port Number** the SNMP port can be changed.
Thereafter under **IDRAC Settings** and **User Authentication** create a user. Do it by clicking a **User ID** number that does not have any user associated with it. Then click **Next** and check the **Enable User** checkbox, set a proper **User Name** for the user, set password under both **New Password** and **Confirm New Password**. After doing that, scroll down to **SNMP v3** section, check the **Enable SNMP v3** checkbox and select **SHA** under **Authentication type** and **AES** under **Privacy type**.
Remember to click **Apply** at the end to save the user.
# IDRAC9
Hover over **IDRAC Settings** and click **Services**. There go to **SNMP Agent** and do the same thing as in IDRAC7 or IDRAC8.
After enabling SNMP go to **Users** under **IDRAC Settings** and click **Local Users**. Click **Add** and fill the same fields as in IDRAC7 or IDRAC8.

52
MIBs.md
View File

@@ -1,4 +1,4 @@
# IDRAC8 # IDRAC7/8
## temperatureProbeReading ## temperatureProbeReading
@@ -67,6 +67,56 @@ Uptime in seconds
# IDRAC9
Here the power OID is different :(
## Power
Here are the OIDs for string
SNMPv2-SMI::enterprises.674.10892.5.4.600.30.1.8.1.1 = STRING: "PS1 Current 1"
SNMPv2-SMI::enterprises.674.10892.5.4.600.30.1.8.1.2 = STRING: "PS2 Current 2"
SNMPv2-SMI::enterprises.674.10892.5.4.600.30.1.8.1.3 = STRING: "System Board Pwr Consumption"
### Current and Watts
and here are OIDs for intigers. They show miliamps*100. So when ...6.1.1 show 2 it means 2/10 so 100mA = 0.1A.
BUT the last one is the total board power which is in Watts
SNMPv2-SMI::enterprises.674.10892.5.4.600.30.1.6.1.1 = INTEGER: 2
SNMPv2-SMI::enterprises.674.10892.5.4.600.30.1.6.1.2 = INTEGER: 10
SNMPv2-SMI::enterprises.674.10892.5.4.600.30.1.6.1.3 = INTEGER: 240
### Voltage
Here are OIDs for Voltage. They need to be divided by 1000 to get 236.000 Volts
PSU1 and PSU2
SNMPv2-SMI::enterprises.674.10892.5.4.600.20.1.6.1.33 = INTEGER: 236000
SNMPv2-SMI::enterprises.674.10892.5.4.600.20.1.6.1.34 = INTEGER: 236000
## Temperatures
Here are the strings for the temp readings
SNMPv2-SMI::enterprises.674.10892.5.4.700.20.1.8.1.1 = STRING: "CPU1 Temp"
SNMPv2-SMI::enterprises.674.10892.5.4.700.20.1.8.1.2 = STRING: "CPU2 Temp"
SNMPv2-SMI::enterprises.674.10892.5.4.700.20.1.8.1.3 = STRING: "System Board Inlet Temp"
SNMPv2-SMI::enterprises.674.10892.5.4.700.20.1.8.1.4 = STRING: "System Board Exhaust Temp"
These are temperature readings for inlet and exhaust. They need to be divided by 10 so that 260/10 = 26 C
SNMPv2-SMI::enterprises.674.10892.5.4.700.20.1.6.1.1 = INTEGER: 550
SNMPv2-SMI::enterprises.674.10892.5.4.700.20.1.6.1.2 = INTEGER: 560
SNMPv2-SMI::enterprises.674.10892.5.4.700.20.1.6.1.3 = INTEGER: 260
SNMPv2-SMI::enterprises.674.10892.5.4.700.20.1.6.1.4 = INTEGER: 310
# NXOS 9.3 # NXOS 9.3

View File

@@ -99,6 +99,7 @@ class snmpPyIDRACData():
uptimeD: float | None = None uptimeD: float | None = None
cpu2Temp: float | None = None cpu2Temp: float | None = None
powerDrawPSU2: int | None = None powerDrawPSU2: int | None = None
powerDrawBoard: int | None = None
voltagePSU2: int | None = None voltagePSU2: int | None = None
@@ -107,6 +108,7 @@ class snmpPyIDRACData():
f"{self.hostname}\n" f"{self.hostname}\n"
f"PSU1 {self.powerDrawPSU1} Watts\n" f"PSU1 {self.powerDrawPSU1} Watts\n"
f"PSU2 {self.powerDrawPSU2} Watts\n" f"PSU2 {self.powerDrawPSU2} Watts\n"
f"Board {self.powerDrawBoard} Watts\n"
f"PSU1 {self.voltagePSU1} Volts\n" f"PSU1 {self.voltagePSU1} Volts\n"
f"PSU2 {self.voltagePSU2} Volts\n" f"PSU2 {self.voltagePSU2} Volts\n"
f"Inlet {self.inletTemp} C\n" f"Inlet {self.inletTemp} C\n"

View File

@@ -263,8 +263,8 @@ async def ciscoPoolRemote(remoteIP: str, queueToInsrt: asyncio.Queue):
# return returnObj # return returnObj
await queueToInsrt.put(returnDict) await queueToInsrt.put(returnDict)
# IDRAC get data for snmpPyIDRACData class # IDRAC78 get data for snmpPyIDRACData class
async def idracPoolRemote_v3(remoteIP: str, queueToInsrt: asyncio.Queue): async def idrac7_8PoolRemote_v3(remoteIP: str, queueToInsrt: asyncio.Queue):
snmpEngine = SnmpEngine() snmpEngine = SnmpEngine()
iterator = get_cmd( iterator = get_cmd(
@@ -281,17 +281,17 @@ async def idracPoolRemote_v3(remoteIP: str, queueToInsrt: asyncio.Queue):
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.30.1.6.1.1")), #1 ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.30.1.6.1.1")), #1
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.30.1.6.1.2")), #2 ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.30.1.6.1.2")), #2
# (Volts) # (Volts)
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.20.1.6.1.31")), #3 # ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.20.1.6.1.31")), #3
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.20.1.6.1.32")), #4 # ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.20.1.6.1.32")), #4
# Get Inlet, Exhaust, CPU1, CPU2 # Get Inlet, Exhaust, CPU1, CPU2
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.1")), #5 ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.1")), #3
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.2")), #6 ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.2")), #4
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.3")), #7 ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.3")), #5
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.4")), #8 ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.4")), #6
# Uptime in seconds # Uptime in seconds
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.2.5.0")), #9 ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.2.5.0")), #7
) )
errorIndication, errorStatus, errorIndex, varBinds = await iterator errorIndication, errorStatus, errorIndex, varBinds = await iterator
@@ -309,8 +309,54 @@ async def idracPoolRemote_v3(remoteIP: str, queueToInsrt: asyncio.Queue):
for oid, val in varBinds: for oid, val in varBinds:
print(f"{oid.prettyPrint()} = {val.prettyPrint()}") print(f"{oid.prettyPrint()} = {val.prettyPrint()}")
# for element in varBinds: iterator2 = get_cmd(
# print(element) snmpEngine,
# SNMPv1 = mpModel=0 (SNMPv2c would be mpModel=1)
# CommunityData("public", mpModel=0),
USMUSRDATA,
await UdpTransportTarget.create((remoteIP, SNMPORT)),
ContextData(),
# Hostname (sysName.0)
ObjectType(ObjectIdentity(".1.3.6.1.2.1.1.5.0")), #0
# PSU1 powerDraw and PSU2 powerDraw (Amps)
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.30.1.6.1.1")), #1
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.30.1.6.1.2")), #2
# (Volts)
# ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.20.1.6.1.31")), #3
# ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.20.1.6.1.32")), #4
# Get Inlet, Exhaust, CPU1, CPU2
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.1")), #3
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.2")), #4
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.3")), #5
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.4")), #6
# Uptime in seconds
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.2.5.0")), #7
)
errorIndication2, errorStatus2, errorIndex2, varBinds2 = await iterator2
if errorIndication2:
print(errorIndication2)
elif errorStatus2:
print(
"{} at {}".format(
errorStatus2.prettyPrint(),
errorIndex2 and varBinds[int(errorIndex2) - 1][0] or "?",
)
)
else:
for oid, val in varBinds2:
print(f"{oid.prettyPrint()} = {val.prettyPrint()}")
voltResult = await walk_column_v3(snmpEngine, remoteIP, ".1.3.6.1.4.1.674.10892.5.4.600.20.1.6.1")
voltList = []
for thing in voltResult:
voltList.append(voltResult[thing])
snmpEngine.close_dispatcher() snmpEngine.close_dispatcher()
# print(varBinds[1][-1]) # print(varBinds[1][-1])
@@ -319,27 +365,27 @@ async def idracPoolRemote_v3(remoteIP: str, queueToInsrt: asyncio.Queue):
hostname=varBinds[0][-1], hostname=varBinds[0][-1],
# Hostname # Hostname
powerDrawPSU1=round((int(varBinds[1][-1]) / 10) * (int(varBinds[3][-1]) / 1000), ROUND_PREC), powerDrawPSU1=round((int(varBinds[1][-1]) / 10) * (int(voltList[0]) / 1000), ROUND_PREC),
powerDrawPSU2=round((int(varBinds[2][-1]) / 10) * (int(varBinds[4][-1]) / 1000), ROUND_PREC), powerDrawPSU2=round((int(varBinds[2][-1]) / 10) * (int(voltList[1]) / 1000), ROUND_PREC),
# PSU1 and PSU2 power draw in Watts # PSU1 and PSU2 power draw in Watts
voltagePSU1=round((int(varBinds[3][-1]) / 1000), ROUND_PREC), voltagePSU1=round((int(voltList[0]) / 1000), ROUND_PREC),
voltagePSU2=round((int(varBinds[4][-1]) / 1000), ROUND_PREC), voltagePSU2=round((int(voltList[1]) / 1000), ROUND_PREC),
# PSU1 and PSU2 voltages # PSU1 and PSU2 voltages
inletTemp=int(varBinds[5][-1] / 10), inletTemp=int(varBinds[3][-1] / 10),
exhaustTemp=int(varBinds[6][-1] / 10), exhaustTemp=int(varBinds[4][-1] / 10),
# Inlet and Exhaust temp # Inlet and Exhaust temp
cpu1Temp=int(varBinds[7][-1] / 10), cpu1Temp=int(varBinds[5][-1] / 10),
cpu2Temp=int(varBinds[8][-1] / 10), cpu2Temp=int(varBinds[6][-1] / 10),
# CPU1 and CPU2 temp # CPU1 and CPU2 temp
uptimeS=int(varBinds[9][-1]), uptimeS=int(varBinds[7][-1]),
# seconds # seconds
uptimeH=round(((int(varBinds[9][-1]) / 60) / 60), ROUND_PREC), uptimeH=round(((int(varBinds[7][-1]) / 60) / 60), ROUND_PREC),
# seconds->minutes->hours # seconds->minutes->hours
uptimeD=round((((int(varBinds[9][-1]) / 60) / 60) / 24), ROUND_PREC) uptimeD=round((((int(varBinds[7][-1]) / 60) / 60) / 24), ROUND_PREC)
# seconds->minutes->hours->days # seconds->minutes->hours->days
) )
@@ -352,6 +398,110 @@ async def idracPoolRemote_v3(remoteIP: str, queueToInsrt: asyncio.Queue):
await queueToInsrt.put(returnDict) await queueToInsrt.put(returnDict)
# IDRAC get data for snmpPyIDRACData class
async def idrac9PoolRemote_v3(remoteIP: str, queueToInsrt: asyncio.Queue):
snmpEngine = SnmpEngine()
iterator = get_cmd(
snmpEngine,
# SNMPv1 = mpModel=0 (SNMPv2c would be mpModel=1)
# CommunityData("public", mpModel=0),
USMUSRDATA,
await UdpTransportTarget.create((remoteIP, SNMPORT)),
ContextData(),
# Hostname (sysName.0)
ObjectType(ObjectIdentity(".1.3.6.1.2.1.1.5.0")), #0
# PSU1 powerDraw and PSU2 powerDraw (Amps)
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.30.1.6.1.1")), #1
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.30.1.6.1.2")), #2
# (Volts) nevermid, do not querry
# ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.20.1.6.1.33")), #3
# ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.20.1.6.1.34")), #4
# Get Inlet, Exhaust, CPU1, CPU2
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.3")), #3
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.4")), #4
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.1")), #5
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.2")), #6
# Uptime in seconds
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.2.5.0")), #7
# Raw board power draw
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.4.600.30.1.6.1.3")), #8
)
errorIndication, errorStatus, errorIndex, varBinds = await iterator
if errorIndication:
print(errorIndication)
elif errorStatus:
print(
"{} at {}".format(
errorStatus.prettyPrint(),
errorIndex and varBinds[int(errorIndex) - 1][0] or "?",
)
)
else:
for oid, val in varBinds:
print(f"{oid.prettyPrint()} = {val.prettyPrint()}")
# .1.3.6.1.4.1.674.10892.5.4.600.20.1.6.1
voltResult = await walk_column_v3(snmpEngine, remoteIP, ".1.3.6.1.4.1.674.10892.5.4.600.20.1.6.1")
voltList = []
for thing in voltResult:
voltList.append(voltResult[thing])
snmpEngine.close_dispatcher()
# print(varBinds[1][-1])
# print(type(varBinds[1][-1]))
returnObj = snmpPyIDRACData(
hostname=varBinds[0][-1],
# Hostname
powerDrawPSU1=round((int(varBinds[1][-1]) / 10) * (int(voltList[0]) / 1000), ROUND_PREC),
powerDrawPSU2=round((int(varBinds[2][-1]) / 10) * (int(voltList[1]) / 1000), ROUND_PREC),
# PSU1 and PSU2 power draw in Watts
# Total board power consumption
powerDrawBoard=int(varBinds[8][-1]),
voltagePSU1=round((int(voltList[0]) / 1000), ROUND_PREC),
voltagePSU2=round((int(voltList[1]) / 1000), ROUND_PREC),
# PSU1 and PSU2 voltages
inletTemp=int(varBinds[3][-1] / 10),
exhaustTemp=int(varBinds[4][-1] / 10),
# Inlet and Exhaust temp
cpu1Temp=int(varBinds[5][-1] / 10),
cpu2Temp=int(varBinds[6][-1] / 10),
# CPU1 and CPU2 temp
uptimeS=int(varBinds[7][-1]),
# seconds
uptimeH=round(((int(varBinds[7][-1]) / 60) / 60), ROUND_PREC),
# seconds->minutes->hours
uptimeD=round((((int(varBinds[7][-1]) / 60) / 60) / 24), ROUND_PREC)
# seconds->minutes->hours->days
)
returnDict = {
"source": "IDRAC",
"value": returnObj,
"type": "snmpPyIDRACData"
}
# return returnObj
await queueToInsrt.put(returnDict)
# IDRAC get FAN data # IDRAC get FAN data
async def walk_column_v3(snmpEngine, remoteIP: str, base_oid: str) -> dict: async def walk_column_v3(snmpEngine, remoteIP: str, base_oid: str) -> dict:
rows = {} rows = {}

View File

@@ -10,7 +10,8 @@ from classes import *
# Program ENV--------------------------------------- # Program ENV---------------------------------------
try: try:
IDRAC_HOST_LIST: Final[list] = os.getenv("IDRAC_HOST_LIST", None).split(";") IDRAC78_HOST_LIST: Final[list] = os.getenv("IDRAC78_HOST_LIST", None).split(";")
IDRAC9_HOST_LIST: Final[list] = os.getenv("IDRAC9_HOST_LIST", None).split(";")
CISCO_HOST_LIST: Final[list] = os.getenv("CISCO_HOST_LIST", None).split(";") CISCO_HOST_LIST: Final[list] = os.getenv("CISCO_HOST_LIST", None).split(";")
except: except:
raise Exception("No IDRAC hosts variable") raise Exception("No IDRAC hosts variable")
@@ -19,19 +20,24 @@ except:
# Flightchecks------------------------------------------- # Flightchecks-------------------------------------------
# if HOST_LIST empty, raise Exception No hosts passed # if HOST_LIST empty, raise Exception No hosts passed
if not IDRAC_HOST_LIST and not CISCO_HOST_LIST: if not IDRAC78_HOST_LIST and not CISCO_HOST_LIST and not IDRAC9_HOST_LIST:
raise Exception("No hosts passed\nExiting...") raise Exception("No hosts passed\nExiting...")
# for host in HOST_LIST check if valid IP and create a list with strings # for host in HOST_LIST check if valid IP and create a list with strings
for idracHost in IDRAC_HOST_LIST: for idrac78Host in IDRAC78_HOST_LIST:
try: try:
ip = str(ipaddress.IPv4Address(idracHost)) ip = str(ipaddress.IPv4Address(idrac78Host))
except ValueError: except ValueError:
raise Exception(f" IP {ip} for IDRAC devices is invalid.\nExiting...") raise Exception(f" IP {ip} for IDRAC7/8 devices is invalid.\nExiting...")
for ciscoHost in CISCO_HOST_LIST: for ciscoHost in CISCO_HOST_LIST:
try: try:
ip = str(ipaddress.IPv4Address(ciscoHost)) ip = str(ipaddress.IPv4Address(ciscoHost))
except ValueError: except ValueError:
raise Exception(f" IP {ip} for Cisco devices is invalid.\nExiting...") raise Exception(f" IP {ip} for Cisco devices is invalid.\nExiting...")
for idrac9Host in IDRAC9_HOST_LIST:
try:
ip = str(ipaddress.IPv4Address(idrac9Host))
except ValueError:
raise Exception(f" IP {ip} for IDRAC9 devices is invalid.\nExiting...")
# Done under "Program ENV" on line ~18 # Done under "Program ENV" on line ~18
@@ -52,8 +58,10 @@ async def main():
# IDRAC part # IDRAC part
async with asyncio.TaskGroup() as tg: async with asyncio.TaskGroup() as tg:
for IdracIP in IDRAC_HOST_LIST: for Idrac78IP in IDRAC78_HOST_LIST:
tg.create_task(idracPoolRemote_v3(IdracIP, mainQueue)) tg.create_task(idrac7_8PoolRemote_v3(Idrac78IP, mainQueue))
for Idrac9IP in IDRAC9_HOST_LIST:
tg.create_task(idrac9PoolRemote_v3(Idrac9IP, mainQueue))
# FANS # FANS
# tg.create_task(idracPoolRemoteFAN_v3(IdracIP, mainQueue)) # tg.create_task(idracPoolRemoteFAN_v3(IdracIP, mainQueue))

View File

@@ -11,6 +11,7 @@ idracMeasurement = Table(
Column("powerDrawPSU1", Numeric(7, 2)), Column("powerDrawPSU1", Numeric(7, 2)),
Column("powerDrawPSU2", Numeric(7, 2)), Column("powerDrawPSU2", Numeric(7, 2)),
Column("powerDrawBoard", Numeric(7, 2)),
Column("voltagePSU1", Numeric(7, 2)), Column("voltagePSU1", Numeric(7, 2)),
Column("voltagePSU2", Numeric(7, 2)), Column("voltagePSU2", Numeric(7, 2)),

View File

@@ -44,6 +44,7 @@ CREATE TABLE IDRACmeasurement (
hostname VARCHAR(64) NOT NULL, hostname VARCHAR(64) NOT NULL,
powerDrawPSU1 DECIMAL(7,2), powerDrawPSU1 DECIMAL(7,2),
powerDrawPSU2 DECIMAL(7,2) NULL, powerDrawPSU2 DECIMAL(7,2) NULL,
powerDrawBoard DECIMAL(7,2) NULL, -- Thing for IDRAC9 as it has a Raw Power Consumption value
voltagePSU1 DECIMAL(7,2), voltagePSU1 DECIMAL(7,2),
voltagePSU2 DECIMAL(7,2) NULL, voltagePSU2 DECIMAL(7,2) NULL,
inletTemp DECIMAL(5,2), inletTemp DECIMAL(5,2),