Compare commits

...

38 Commits

Author SHA1 Message Date
e18aed96c8 Added a docker-compose 2026-03-17 19:39:28 +01:00
e66998b826 Got 3750-X monitoring working 2026-03-17 19:38:27 +01:00
c474f9c9c2 Added error handling for idrac 2026-03-17 19:09:11 +01:00
e06eea68cd Added customizable poolig interval 2026-03-07 22:02:37 +01:00
c9d07e7cef Added timeout exception for timeoud handling 2026-03-07 22:00:53 +01:00
10d14a06db Added a custom exception for timeout handling 2026-03-07 22:00:38 +01:00
78d240a1fc Added timeout crash avoidance 2026-03-06 22:59:56 +01:00
faf343b176 Added monitoring of poweredOff servers 2026-03-02 22:14:46 +01:00
10e6ae2347 Complete restructure to modules.
Using modules and imports makes it much more modular
and managable. There are no 600 row files now
2026-03-02 21:46:51 +01:00
fcf3643ff8 Added print to know which backend was used 2026-03-02 20:28:18 +01:00
0b497c1abe IDRAC7, 8, 9 are working now 2026-03-02 20:12:56 +01:00
674eaef47a Did a dynamic check to see what features a server has 2026-03-02 20:03:24 +01:00
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
28e0a1da34 Added an event that delets rows older than 3 days 2026-03-01 19:43:55 +01:00
222e64c113 Added cisco configuration for SNMPv3 2026-03-01 19:43:42 +01:00
886f7bbd19 Updated some modules and added async sql 2026-02-28 22:58:13 +01:00
f7d28b31b8 Collecting IDRAC stats works.
Data is being inserted to both InfluxDB and mariadb
2026-02-28 22:57:11 +01:00
912ce9c10e Copy sqlTable file to the container 2026-02-28 22:56:53 +01:00
943954ed9d Added a SQL table for use in SQLwriter 2026-02-28 22:56:37 +01:00
1936976312 Updated table for SQL 2026-02-28 22:56:25 +01:00
b074b9d633 Forgot to set README as README.md 2026-02-28 18:58:04 +01:00
15f7783aeb Added info about temps for NXOS 9.3 2026-02-28 18:56:31 +01:00
d8d6a7aad3 Updated README 2026-02-28 18:56:13 +01:00
5ea68b548e changed USE* envs to 0 and 1 2026-02-28 17:48:13 +01:00
cdf3b983bc Moved the project to asyncio 2026-02-28 17:48:01 +01:00
45ab7d2761 Notes on Redfish API standard 2026-02-28 17:47:43 +01:00
1cc6c7d3ee Added SQL structure for 2 tables 2026-02-28 17:47:27 +01:00
2296c89878 . 2026-02-26 20:17:26 +01:00
b4f02df313 Added information about fans 2026-02-26 20:17:15 +01:00
fab9c3fafa Other insignificant tweaks 2026-02-26 19:49:46 +01:00
3092d795e7 Added local influxdb, mariadb and adminer for testing 2026-02-26 19:49:24 +01:00
29e50fe52f Tests with SNMPv1 2026-02-26 19:49:10 +01:00
8882ee5ee8 Tweaked the class that has IDRAC data 2026-02-26 19:48:56 +01:00
7a30a31a38 Moved everything to one folder 2026-02-26 17:34:00 +01:00
147dd9a3ed Added files for switch and router configuration 2026-02-26 17:13:04 +01:00
44 changed files with 1767 additions and 29193 deletions

4
.gitignore vendored
View File

@@ -1,2 +1,2 @@
cont/code/__pycache__
cont/code/venv
cont/__pycache__
cont/venv

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.

92
MIBs.md
View File

@@ -1,4 +1,4 @@
# IDRAC8
# IDRAC7/8
## temperatureProbeReading
@@ -45,6 +45,16 @@ Here the voltage i given without deicmal point (240000) so decimal point needs t
## Fans
.1.3.6.1.4.1.674.10892.5.4.700.12.1.6 (coolingDeviceReading)
Get each fan one by one from .1.1 to .1.XX; Values in RPM
.1.3.6.1.4.1.674.10892.5.4.700.12.1.19 (coolingDeviceFQDD)
Fan names going from 1.1 to 1.XX; Named coolingDeviceFQDD.1.XX
## other
.1.3.6.1.4.1.674.10892.5.4.1100.30.1.23 (processorDeviceBrandName)
@@ -53,4 +63,82 @@ Get the CPU name(s)
.1.3.6.1.4.1.674.10892.5.2.5.0 (systemPowerUpTime)
Uptime in seconds
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
## Temperature
1.3.6.1.4.1.9.9.91.1.1.1.1.4.XXXX (entSensorStatus)
This OID has temperatures
```
iso.3.6.1.4.1.9.9.91.1.1.1.1.4.38486 = INTEGER: 30
iso.3.6.1.4.1.9.9.91.1.1.1.1.4.38487 = INTEGER: 43
iso.3.6.1.4.1.9.9.91.1.1.1.1.4.38488 = INTEGER: 51
iso.3.6.1.4.1.9.9.91.1.1.1.1.4.38489 = INTEGER: 61
```
### Sensor names
```
iso.3.6.1.2.1.47.1.1.1.1.7.38486 = STRING: "module-1 FRONT"
iso.3.6.1.2.1.47.1.1.1.1.7.38487 = STRING: "module-1 BACK"
iso.3.6.1.2.1.47.1.1.1.1.7.38488 = STRING: "module-1 CPU"
iso.3.6.1.2.1.47.1.1.1.1.7.38489 = STRING: "module-1 Sugarbowl"
```

1
README
View File

@@ -1 +0,0 @@
#Hi

5
README.md Normal file
View File

@@ -0,0 +1,5 @@
# Hi
<p align="center">
<img src="https://data.shupogaki.org/assets/HBX2LV4aMAAstst.png" alt="">
</p>

7
REDFISH.md Normal file
View File

@@ -0,0 +1,7 @@
# RedFish
I have found that there is another method for getting data from IDRAC which is Redfish API.
Here is the documentation for IDRAC
https://www.dell.com/support/manuals/en-us/idrac8-lifecycle-controller-v2.70.70.70/idrac8_redfishapiguide_2.70.70.70/

6
ciscoSNMPconf/lob.ios Normal file
View File

@@ -0,0 +1,6 @@
! IOS 15.0(1)SE2
! Optional
access-list 10 permit 192.168.1.50
snmp-server group MONITOR v3 priv access 10
snmp-server user username MONITOR v3 auth sha password123 priv aes 128 password123

9
ciscoSNMPconf/nyater.ios Normal file
View File

@@ -0,0 +1,9 @@
! NXOS 9.3
! ACL
! ip access-list SNMP-SERVERS
! permit ip 192.168.1.50/32 any
snmp-server user snmpuser auth sha 123 priv aes-128 123
! Make the user RO
username snmpuser role network-operator

View File

View File

@@ -3,16 +3,34 @@ FROM alpine:latest
# https://docs.docker.com/reference/dockerfile/#environment-replacement
# ENV PUID=1600
# ENV USER=itsme
# VOLUME [""]
RUN apk update && \
apk add python3 py3-pip su-exec curl && \
mkdir -p /app/snmpython/
apk add python3 py3-pip su-exec curl
RUN adduser -D -s /bin/sh -u 1600 pyusr
RUN mkdir -p /app/snmpython/ && \
chown -R 1600:1600 /app/snmpython/
USER pyusr
WORKDIR /app/snmpython/
COPY ../code/someMain.py /app/snmpython/
COPY ./requirements.txt /app/snmpython/
RUN python3 -m venv pyvenv && \
pyvenv/bin/python3 -m pip install --upgrade pip && \
pyvenv/bin/pip3 install -r requirements.txt
COPY ./mainRE.py /app/snmpython/
RUN mkdir cisco db idrac models snmp
ADD cisco /app/snmpython/cisco
ADD db /app/snmpython/db
ADD idrac /app/snmpython/idrac
ADD models /app/snmpython/models
ADD snmp /app/snmpython/snmp
# COPY ../code/MIBs/ /app/snmpython/MIBs
# COPY ./entrypoint.sh /entrypoint.sh
# RUN chmod +x /entrypoint.sh
@@ -22,11 +40,6 @@ COPY ../code/someMain.py /app/snmpython/
# RUN chown -R "${USER}" /app/snmpython
COPY ./requirements.txt /app/snmpython/
RUN python3 -m venv venv && \
venv/bin/python3 -m pip install --upgrade pip && \
venv/bin/pip3 install -r requirements.txt
# venv/bin/pip3 install -r requirements.txt
# Set user. No need for root past this point
@@ -37,4 +50,4 @@ RUN python3 -m venv venv && \
# HEALTHCHECK --interval=3s --timeout=3s --retries=3 \
# CMD curl --fail http://127.0.0.1:8181/health || exit 1
CMD ["/app/snmpython/someMain.py"]
CMD ["/app/snmpython/pyvenv/bin/python3", "/app/snmpython/mainRE.py"]

0
cont/cisco/__init__.py Normal file
View File

113
cont/cisco/lob.py Normal file
View File

@@ -0,0 +1,113 @@
import os, asyncio
from models.ciscoModel import C3750XciscoData
from models.snmpTimeOut import SNMPTimeoutError
from snmp.walker import walk_column_v3, walk_lobotomy_column_v3
from typing import Annotated, Final
# SNMP
from pysnmp.hlapi.v3arch.asyncio import *
# Cisco
async def lobGetRemote(
remoteIP: str,
queueToInsrt: asyncio.Queue,
SNMPUSER: str,
SNMPAUTHKEY: str,
SNMPPRIVKEY: str,
ROUND_PREC: int = 2,
SNMPORT: int = 161
):
print("starting work on ", remoteIP)
# SNMPUSRDATA
USMUSRDATA = UsmUserData(
userName=SNMPUSER,
authKey=SNMPAUTHKEY,
privKey=SNMPPRIVKEY,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmAesCfb128Protocol,
)
snmpEngine = SnmpEngine()
iterator = get_cmd(
snmpEngine,
USMUSRDATA,
await UdpTransportTarget.create((remoteIP, SNMPORT)),
ContextData(),
# Get Hostname
ObjectType(ObjectIdentity(".1.3.6.1.2.1.1.5.0")), # 0
)
errorIndication, errorStatus, errorIndex, varBinds = await iterator
if errorIndication:
print(f"\n\n{errorIndication}\n\n")
if "No SNMP response received before timeout" in str(errorIndication):
raise SNMPTimeoutError(f"Host {remoteIP} timed out while walking")
raise RuntimeError(errorIndication)
elif errorStatus:
print(
"{} at {}".format(
errorStatus.prettyPrint(),
errorIndex and varBinds[int(errorIndex) - 1][0] or "?",
)
)
else:
for varBind in varBinds:
print(" = ".join([x.prettyPrint() for x in varBind]))
try:
# System data
systemData = await walk_lobotomy_column_v3(snmpEngine, remoteIP, ".1.3.6.1.4.1.9.9.13.1.3", SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY)
# Uptime in seconds
uptimeSeconds = await walk_column_v3(snmpEngine, remoteIP, ".1.3.6.1.6.3.10.2.1.3", SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY)
# Last 5 second CPU usage
last5SecCPUsage = await walk_column_v3(snmpEngine, remoteIP, ".1.3.6.1.4.1.9.2.1.56", SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY)
# Last minute CPU usage
last1MinCPUsage = await walk_column_v3(snmpEngine, remoteIP, ".1.3.6.1.4.1.9.2.1.58", SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY)
# Last 5 minutes CPU usage
last5MinCPUsage = await walk_column_v3(snmpEngine, remoteIP, ".1.3.6.1.4.1.9.2.1.58", SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY)
except SNMPTimeoutError as e:
print(f"{e}\nContinuing regardless...")
return 1
except RuntimeError as e:
print(f"SNMP error on {remoteIP}: {e}")
return 1
snmpEngine.close_dispatcher()
returObj = C3750XciscoData(
hostname=varBinds[0][-1],
systemStatus=systemData[0].split(" ")[2], #[2],
systemTemp=systemData[1], #[2][-1],
last5SecUsage=last5SecCPUsage[0],
last1MinUsage=last1MinCPUsage[0],
last5MinUsage=last5MinCPUsage[0],
uptimeS=int(uptimeSeconds[0])
)
returnDict = {
"source": "CISCO",
"device": "3750X",
"value": returObj,
"type": "C3750XciscoData"
}
await queueToInsrt.put(returnDict)

85
cont/cisco/nyater.py Normal file
View File

@@ -0,0 +1,85 @@
# NYATER
import os, asyncio
from models.ciscoModel import snmpPyCiscoData
from snmp.walker import walk_column_v3
from typing import Annotated, Final
# SNMP
from pysnmp.hlapi.v3arch.asyncio import *
# SNMP ENV-------------------------------------------
ROUND_PREC: Final[int] = int(os.getenv("ROUND_PREC", 2))
SNMPUSER: Final[str] = os.getenv("SNMPUSER", None)
SNMPPRIVKEY: Final[str] = os.getenv("SNMPPRIVKEY", None)
SNMPAUTHKEY: Final[str] = os.getenv("SNMPAUTHKEY", None)
# Right now I'll only use SHA
# SNMPAUTHPROTO: Final[str] = os.getenv("SNMPAUTHPROTO", "SHA")
# SNMPPRIVPROTO: Final[str] = os.getenv("SNMPPRIVPROTO", "SHA")
SNMPORT: Final[int] = int(os.getenv("SNMPORT", 161))
# Check if SNMP ENV are empty
if not SNMPUSER or not SNMPPRIVKEY or not SNMPAUTHKEY:
raise Exception("No SNMP user or/and PrivAuth passed")
# SNMP
USMUSRDATA = UsmUserData(
userName=SNMPUSER,
authKey=SNMPAUTHKEY,
privKey=SNMPPRIVKEY,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmAesCfb128Protocol,
)
# Cisco
async def ciscoPoolRemote(remoteIP: str, queueToInsrt: asyncio.Queue):
print("starting work on ", remoteIP)
snmpEngine = SnmpEngine()
iterator = get_cmd(
snmpEngine,
USMUSRDATA,
await UdpTransportTarget.create((remoteIP, SNMPORT)),
ContextData(),
# Get Hostname
ObjectType(ObjectIdentity(".1.3.6.1.2.1.1.5.0")),
#
)
print(iterator)
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 varBind in varBinds:
print(" = ".join([x.prettyPrint() for x in varBind]))
snmpEngine.close_dispatcher()
returObj = snmpPyCiscoData()
returObj(
hostname="X"
)
returnDict = {
"source": "CISCO",
"value": returObj,
"type": "snmpPyCiscoData"
}
# return returnObj
await queueToInsrt.put(returnDict)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,96 +0,0 @@
from pydantic import BaseModel
from sqlalchemy import Column, Date, Float, Integer, String, text
from datetime import datetime, timezone, timedelta
from typing import Optional, Annotated
from sqlalchemy.dialects.mysql import MEDIUMINT, TINYINT, TEXT, TIMESTAMP, FLOAT
from sqlalchemy.ext.declarative import declarative_base
from dataclasses import dataclass
# -----------
AlchemyBase = declarative_base()
# DATABASE "CLASSESS"--------------------------------------------
def sensorsqldata(table_name: Annotated[str, "Name of the table"]):
class SensorSQLDataClass(AlchemyBase):
__table_args__ = {'extend_existing': True}
__tablename__ = table_name
id = Column(MEDIUMINT(unsigned=True), primary_key=True, autoincrement=True)
time_stamp = Column(
TIMESTAMP,
server_default=text("CURRENT_TIMESTAMP"),
server_onupdate=text("CURRENT_TIMESTAMP"),
nullable=False,
)
TEMP = Column(TINYINT)
TEMP = Column(TEXT)
TEMP = Column(Float)
TEMP = Column(Float)
TEMP = Column(Float, nullable=True)
TEMP = Column(Float, nullable=True)
TEMP = Column(Float, nullable=True)
return SensorSQLDataClass
# Custom--------------------------------------------
class snmpValueToHost():
def __init__(self,
lastV: Annotated[str, "Last value\nNone when initializing"] = None,
remote: Annotated[str, "Needs to be a valid IP address"] = None,
nextV: Annotated[str, "Value to insert next\nNone when initializing"] = None,
lasttime: Annotated[float, "Last time since sending data\nSince Epoch, so time.time()"] = None,
lastStatus: Annotated[bool, "True if value newest value was inserted\nFalse if data is pending to be sent"] = False
):
self.__lastValue = lastV # Save last sent value
self.__remote = remote # Remote IP address of the SNMP client
self.__nextValue = nextV # Next value to send. Maybe will be used
self.__lastSentStatus = lastStatus # If previously sent new data, set to True.
self.__lastSentTime = lasttime if lasttime else time.time() # The time since the Epoch of the last sent data*
def ___str__(self):
return self.__lastValue
def __changeLastSentValue(self, newValue: int) -> bool:
self.__nextValue == newValue
self.__lastValue = self.__nextValue
self.__nextValue == None
return True
def __updateLastSentTime(self):
self.__lastSentTime = time.time()
return True
def __updateSentStatus(self):
if self.__lastSentStatus:
self.__lastSentStatus = False
return False
else:
self.__lastSentStatus = True
return True
def updateStats(self, nextV: Annotated[int, "Value that will be set as the new one"]):
__changeLastSentValue(nextV)
__updateLastSentTime()
...
# Dataclasses--------------------------------------------
@dataclass
class snmpPyData():
"""Dataclass for snmp data"""
hostname: str
powerDrawPSU1: int
powerDrawPSU2: int
voltagePSU1: int
voltagePSU2: int
inletTemp: float
exhaustTemp: float
uptime: int

View File

@@ -1,122 +0,0 @@
import influxdb_client, sqlalchemy, random, os
# from sqlmodel import Field, Session, SQLModel, create_engine, select
from sqlalchemy import create_engine, exc # , Column, Integer, String, Numeric
# from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from classes import *
from typing import Annotated, Final
from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS, ASYNCHRONOUS, WriteOptions
# import pymysql
# FluxQL ENV----------------------------------------
USEINFLUX: Final[bool] = os.getenv("USEINFLUX", True)
if USEINFLUX:
INFLXDBTOKEN: Final[str] = os.getenv("INFLXDBTOKEN", "123" )
INFLUXBCKT: Final[str] = os.getenv("INFLUXBCKT", "SNMPyth")
INFLUXORG: Final[str] = os.getenv("INFLUXORG", "staging")
INFLXDBURL: Final[str] = os.getenv("INFLXDBURL", "http://localhost:8086")
INFXLUXDB_MEASUEREMENT: Final[str] = os.getenv("INFXLUXDB_MEASUEREMENT", "SNMPyth-containrr")
INFLX_SEPARATE_POINTS: Final[float] = float(os.getenv("INFLX_SEPARATE_POINTS", 0.1))
# SQL ENV-------------------------------------------
USESQL: Final[bool] = os.getenv("USESQL", False)
if USESQL:
DBENGINE: Final[str] = os.getenv("DBENGINE", "mysql+pymysql")
DBADDR: Final[str] = os.getenv("DBADDR", "127.0.0.1")
DBUSR: Final[str] = os.getenv("DBUSR", "root")
DBPWD: Final[str] = os.getenv("DBPWD", "6767")
DBNAME: Final[str] = os.getenv("DBNAME", "TEMP_SENSR")
# DBprepare-------------------------------------------
# INFLUX
if USEINFLUX:
fluxdb_client = influxdb_client.InfluxDBClient(url=INFLXDBURL, token=INFLXDBTOKEN, org=INFLUXORG)
write_fluxdb_api = fluxdb_client.write_api(write_options=SYNCHRONOUS)
query_fluxdb_api = fluxdb_client.query_api()
# SQL
if USESQL:
engine = create_engine(
f"{DBENGINE}://{DBUSR}:{DBPWD}@{DBADDR}/{DBNAME}",
pool_pre_ping=True, # Check connection liveness before using and if needed, recconect
pool_recycle=3600 # recycle connections older than N (3600 in this case) seconds
)
Session = sessionmaker(bind=engine)
def tempsnsrIntoSQLDB(
tableInp: Annotated[int, "Name of the table"],
insertData: Annotated[str, "Data to insert"]
) -> int:
f"""
Insert {DBNAME} data into MariaDB
:tableInp: Name of the table
:return: 0 if succesfull, 1 if rolled back on error, 2 if SQL is not enabled 3 if no such table
"""
if not USESQL:
return 2
# Check if table exists
if not sqlalchemy.inspect(engine).has_table(tableInp):
return 3
with Session() as session:
try:
sqlData = sensorsqldata(tableInp)
# print("2")
if inpayload:
# print("3")
session.add(sqlData(
data = insertData,
))
# print("4")
session.commit() #Attempt to commit all the records
# print("good")
return 0
except Exception as e:
print(f"Error {e} when sending to mariadb")
session.rollback() #Rollback the changes on error
return 1
def tempsnsrIntoFluxQLDB(
inpayload: Annotated[snmpPyData, "Payload"]
) -> int:
"""
Insert temperature data into InfluxDB
:inpayload: Payload
:return: 0 if succesfull, 1 if general error, 2 if Influx is not enabled
"""
if not USEINFLUX:
return 2
# Prep InfluxDB data
inflxdb_Data_To_Send = (
influxdb_client.Point(f"{INFXLUXDB_MEASUEREMENT}")
.tag("PLACE", inpayload.name)
.tag("TEMP", sensorIDinp)
.tag("TEMP", whatTheSensor)
.field("TEMP", inpayload.temp)
.field("TEMP", inpayload.humid)
.field("TEMP", inpayload.hicc)
.field("TEMP", inpayload.presss)
.field("TEMP", inpayload.alttd)
)
# print(write_fluxdb_api.write(bucket=INFLUXBCKT, org=INFLUXORG, record=inflxdb_Data_To_Send))
# if write_fluxdb_api.write(bucket=INFLUXBCKT, org=INFLUXORG, record=inflxdb_Data_To_Send) == None:
if write_fluxdb_api.write(bucket=INFLUXBCKT, org=INFLUXORG, record=inflxdb_Data_To_Send):
# return {"STATUS": "succesfully inserted to InfluxDB"}
print("0")
return 0
else:
print("1")
return 1

View File

@@ -1,139 +0,0 @@
import ipaddress, os, re, time, funct, classes
#SNMP
# from easysnmp import Session
# QoL
from typing import Annotated, Final
# Program ENV---------------------------------------
HOST_LIST: Final[list] = os.getenv("HOST_LIST")
SNMPUSER: Final[str] = os.getenv("SNMPUSER", None)
SNMPPRIVKEY: Final[str] = os.getenv("SNMPPRIVKEY", None)
SNMPAUTHKEY: Final[str] = os.getenv("SNMPAUTHKEY", None)
SNMPAUTHPROTO: Final[str] = os.getenv("SNMPAUTHPROTO", "SHA")
SNMPPRIVPROTO: Final[str] = os.getenv("SNMPPRIVPROTO", "SHA")
SNMPORT: Final[int] = os.getenv("SNMPORT", 161)
# Flightchecks-------------------------------------------
if SNMPAUTHPROTO and SNMPPRIVPROTO == "SHA":
USMDATA = UsmUserData(
userName=SNMPUSER,
authKey=SNMPPRIVKEY,
privKey=SNMPAUTHKEY,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmHMACSHAAuthProtocol,
)
elif SNMPAUTHPROTO and SNMPPRIVPROTO == "AES128":
USMDATA = UsmUserData(
userName=SNMPUSER,
authKey=SNMPPRIVKEY,
privKey=SNMPAUTHKEY,
authProtocol=usmAesCfb128Protocol,
privProtocol=usmAesCfb128Protocol,
)
elif SNMPAUTHPROTO == "SHA" and SNMPPRIVPROTO == "AES128":
USMDATA = UsmUserData(
userName=SNMPUSER,
authKey=SNMPPRIVKEY,
privKey=SNMPAUTHKEY,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmAesCfb128Protocol,
)
elif SNMPAUTHPROTO == "AES128" and SNMPPRIVPROTO == "SHA":
USMDATA = UsmUserData(
userName=SNMPUSER,
authKey=SNMPPRIVKEY,
privKey=SNMPAUTHKEY,
authProtocol=usmAesCfb128Protocol,
privProtocol=usmHMACSHAAuthProtocol,
)
else:
raise Exception(f"No PrivAuth option like {SNMPPRIVPROTO}")
# Helper functions
def wattageCalc(amperageInp: float, voltageInp: float) -> float:
return amperageInp * voltageInp
# Check if some neccesary ENVs are passed
if not USEINFLUX and not USESQL:
raise Exception("No database selected to store the data")
# if HOST_LIST empty, raise Exception No hosts passed
if not HOST_LIST:
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:
try:
ip = str(ipaddress.IPv4Address(host))
except ValueError:
raise Exception(f" IP {ip} is invalid.\nExiting...")
# Done under "Program ENV" on line ~18
# Code-------------------------------------------
import asyncio
from pysnmp.hlapi.v3arch.asyncio import *
async def poolRemote(remoteIP: str):
snmpEngine = SnmpEngine()
iterator = get_cmd(
snmpEngine,
UsmUserData(
userName=SNMPUSER,
authKey=SNMPPRIVKEY,
privKey=SNMPAUTHKEY,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmHMACSHAAuthProtocol,
),
await UdpTransportTarget.create((remoteIP, SNMPORT)),
ContextData(),
ObjectType(ObjectIdentity("SNMPv2-MIB", "sysDescr", 0)),
)
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 varBind in varBinds:
print(" = ".join([x.prettyPrint() for x in varBind]))
snmpEngine.close_dispatcher()
# tasks = [poolRemote(ip) for ip in HOST_LIST]
# results = await asyncio.gather(*tasks)
# Create connections for MySQL and InfluxDB
# functions for inserting data into two DBs
# DNS lookup if the user passed a hostname
# if not given a specific IP for DNS server, fallback to 1.1.1.1
# maybe a class?
# store last value and check if it wasn't sent already to the database

View File

@@ -1,16 +0,0 @@
---
services:
mdfanchanger:
container_name: snmpython-dev
user: 1600:1600
image: shupotea/yuruc3/snmpython:dev
build:
dockerfile: ./Dockerfile
environment:
- INFLUXTOKEN: "12345678"
- INFLUXHOST: ""
- HOST_LIST: ["192.168.0.1", "192.168.0.1", "192.168.0.1", "192.168.0.1"]
restart: unless-stopped

View File

@@ -1,11 +0,0 @@
# mysql-connector-python==9.2.0
PyMySQL==1.1.1
sqlmodel==0.0.24
pydantic==2.10.6
pydantic_core==2.27.2
annotated-types==0.7.0
typing==3.7.4.3
pysnmp==7.1.22
# requests==2.32.3
influxdb-client==1.49.0
# influxdb3-python==0.18.0

167
cont/db/Influx.py Normal file
View File

@@ -0,0 +1,167 @@
import influxdb_client, os, asyncio
from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS, ASYNCHRONOUS, WriteOptions
from typing import Annotated, Final
from models.idracModel import snmpPyIDRACData
# FluxQL ENV----------------------------------------
USEINFLUX: Final[int] = int(os.getenv("USEINFLUX", 1))
if USEINFLUX:
INFLXDBTOKEN: Final[str] = os.getenv("INFLXDBTOKEN", "123" )
INFLUXBCKT: Final[str] = os.getenv("INFLUXBCKT", "SNMPyth")
INFLUXORG: Final[str] = os.getenv("INFLUXORG", "staging")
INFLXDBURL: Final[str] = os.getenv("INFLXDBURL", "http://localhost:8086")
INFXLUXDB_MEASUEREMENT: Final[str] = os.getenv("INFXLUXDB_MEASUEREMENT", "SNMPyth-containrr")
INFLX_SEPARATE_POINTS: Final[float] = float(os.getenv("INFLX_SEPARATE_POINTS", 0.1))
# Prepare-------------------------------------------
# INFLUX
if USEINFLUX:
fluxdb_client = influxdb_client.InfluxDBClient(url=INFLXDBURL, token=INFLXDBTOKEN, org=INFLUXORG)
write_fluxdb_api = fluxdb_client.write_api(write_options=ASYNCHRONOUS)
query_fluxdb_api = fluxdb_client.query_api()
else:
fluxdb_client = write_fluxdb_api = query_fluxdb_api = None
async def fluxIDRACWriter(
inpDict: dict
) -> int:
"""
Insert temperature data into InfluxDB
:inputQueue: Asyncio Queue that has what is needed to be sent
"""
# inputQueue have multiple such Dicts
# {
# "source": "IDRAC",
# "value": returnObj,
# "type": "snmpPyIDRACData"
# }
if write_fluxdb_api is None:
return 2
# Prep InfluxDB data
inflxdb_Data_To_Send = (
influxdb_client.Point(INFXLUXDB_MEASUEREMENT)
.tag("SOURCE", inpDict["source"])
.tag("TYPE", inpDict["type"])
.tag("HOSTNAME", inpDict["value"].hostname)
.field("PowerDrawPSU1", inpDict["value"].powerDrawPSU1)
.field("PowerDrawPSU2", inpDict["value"].powerDrawPSU2)
.field("TotalBoardPower", inpDict["value"].powerDrawBoard)
.field("VoltagePSU1", inpDict["value"].voltagePSU1)
.field("VoltagePSU2", inpDict["value"].voltagePSU2)
.field("InletTemperature", inpDict["value"].inletTemp)
.field("ExhaustTemperature", inpDict["value"].exhaustTemp)
.field("TemperatureCPU1", inpDict["value"].cpu1Temp)
.field("TemperatureCPU2", inpDict["value"].cpu2Temp)
.field("UptimeInSeconds", inpDict["value"].uptimeS)
.field("UptimeInHours", inpDict["value"].uptimeH)
.field("UptimeInDays", inpDict["value"].uptimeD)
)
try:
write_fluxdb_api.write(bucket=INFLUXBCKT, org=INFLUXORG, record=inflxdb_Data_To_Send)
except Exception as e:
print(e)
return 1
return 0
# return {"STATUS": "succesfully inserted to InfluxDB"}
async def fluxDataWriter(
inpDict: dict
) -> int:
"""
Insert temperature data into InfluxDB
:inputQueue: Asyncio Queue that has what is needed to be sent
"""
# inputQueue have multiple such Dicts
# {
# "source": "IDRAC",
# "value": returnObj,
# "type": "snmpPyIDRACData"
# }
if write_fluxdb_api is None:
return 2
match inpDict["source"]:
case "CISCO":
match inpDict["device"]:
case "3750X":
# Prep InfluxDB data
inflxdb_3750X_Data_To_Send = (
influxdb_client.Point(INFXLUXDB_MEASUEREMENT)
.tag("SOURCE", inpDict["source"])
.tag("DEVICE", inpDict["device"])
.tag("TYPE", inpDict["type"])
.tag("HOSTNAME", inpDict["value"].hostname)
.field("systemStatus", inpDict["value"].systemStatus)
.field("systemTemp", inpDict["value"].systemTemp)
.field("last5SecUsage", inpDict["value"].last5SecUsage)
.field("last1MinUsage", inpDict["value"].last1MinUsage)
.field("last5MinUsage", inpDict["value"].last5MinUsage)
.field("uptimeS", inpDict["value"].uptimeS)
)
try:
write_fluxdb_api.write(bucket=INFLUXBCKT, org=INFLUXORG, record=inflxdb_3750X_Data_To_Send)
except Exception as e:
print(e)
return 1
case "NEXUS":
...
return 0
case _:
print(f"{inpDict['device']} is not supported.\nOnly '3750X' and 'Nexus' are.")
return 1
case "IDRAC":
# Prep InfluxDB data
inflxdb_IDRAC_Data_To_Send = (
influxdb_client.Point(INFXLUXDB_MEASUEREMENT)
.tag("SOURCE", inpDict["source"])
.tag("TYPE", inpDict["type"])
.tag("HOSTNAME", inpDict["value"].hostname)
.field("PowerDrawPSU1", inpDict["value"].powerDrawPSU1)
.field("PowerDrawPSU2", inpDict["value"].powerDrawPSU2)
.field("TotalBoardPower", inpDict["value"].powerDrawBoard)
.field("VoltagePSU1", inpDict["value"].voltagePSU1)
.field("VoltagePSU2", inpDict["value"].voltagePSU2)
.field("InletTemperature", inpDict["value"].inletTemp)
.field("ExhaustTemperature", inpDict["value"].exhaustTemp)
.field("TemperatureCPU1", inpDict["value"].cpu1Temp)
.field("TemperatureCPU2", inpDict["value"].cpu2Temp)
.field("UptimeInSeconds", inpDict["value"].uptimeS)
.field("UptimeInHours", inpDict["value"].uptimeH)
.field("UptimeInDays", inpDict["value"].uptimeD)
)
try:
write_fluxdb_api.write(bucket=INFLUXBCKT, org=INFLUXORG, record=inflxdb_IDRAC_Data_To_Send)
except Exception as e:
print(e)
return 1
case "FANS":
await eng.execute(
# changeMeLater
t1.insert(), [{"name": "some name 1"}, {"name": "some name 2"}]
)
case _:
print(f"No such source of data as {inpDict['source']}")
return 1
return 0
# asyncio.sleep(5)

103
cont/db/MariaDB.py Normal file
View File

@@ -0,0 +1,103 @@
import sqlalchemy, os, asyncio
from sqlalchemy.ext.asyncio import create_async_engine
from typing import Annotated, Final
from dataclasses import asdict
from models.idracModel import snmpPyIDRACData
from models.ciscoModel import C3750XciscoData
from models.sqlTable import idracMeasurement, C3750XMeasurement
# SQL ENV-------------------------------------------
USESQL: Final[int] = int(os.getenv("USESQL", 0))
if USESQL:
DBENGINE: Final[str] = os.getenv("DBENGINE", "mysql+asyncmy")
DBADDR: Final[str] = os.getenv("DBADDR", "127.0.0.1")
DBUSR: Final[str] = os.getenv("DBUSR", "root")
DBPWD: Final[str] = os.getenv("DBPWD", "6767")
DBNAME: Final[str] = os.getenv("DBNAME", "TEMP_SENSR")
# SQL
if USESQL:
engine = create_async_engine(
f"{DBENGINE}://{DBUSR}:{DBPWD}@{DBADDR}/{DBNAME}",
# pool_pre_ping=True, # Check connection liveness before using and if needed, recconect
# pool_recycle=3600, # recycle connections older than N (3600 in this case) seconds
echo=True,
)
else:
engine = None
async def sqlDataWriter(
inpDict: dict
) -> int:
# inputQueue have multiple such Dicts
# {
# "source": "IDRAC",
# "value": returnObj,
# "type": "snmpPyIDRACData"
# }
if engine is None:
return 3
try:
async with engine.begin() as eng:
match inpDict["source"]:
case "CISCO":
match inpDict["device"]:
case "3750X":
payload = inpDict["value"] # snmpPyIDRACData
row = asdict(payload)
await eng.execute(
C3750XMeasurement.insert(),
[row]
)
return 0
case "NEXUS":
payload = inpDict["value"] # snmpPyIDRACData
row = asdict(payload)
await eng.execute(
NexusMeasurement.insert(),
[row]
)
return 0
case _:
print(f"{inpDict['device']} is not supported.\nOnly '3750X' and 'Nexus' are.")
return 1
# await eng.execute(
# # changeMeLater
# t1.insert(), [{"name": "some name 1"}, {"name": "some name 2"}]
# )
case "IDRAC":
payload = inpDict["value"] # snmpPyIDRACData
row = asdict(payload)
await eng.execute(
idracMeasurement.insert(),
[row]
)
return 0
case "FANS":
await eng.execute(
# changeMeLater
t1.insert(), [{"name": "some name 1"}, {"name": "some name 2"}]
)
case _:
print(f"No such source of data as {inpDict['source']}")
return 1
except Exception as e:
print(e)
return 2
return 0
# asyncio.sleep(5)

0
cont/db/__init__.py Normal file
View File

View File

@@ -0,0 +1,40 @@
import asyncio, os
from typing import Annotated, Final
from db.Influx import fluxDataWriter
from db.MariaDB import sqlDataWriter
USEINFLUX: Final[int] = int(os.getenv("USEINFLUX", 1))
USESQL: Final[int] = int(os.getenv("USESQL", 0))
async def ALLdbIDRACWriter(inputQueue: asyncio.Queue) -> None:
while True:
# print(inputQueue)
qu = await inputQueue.get()
try:
if USESQL:
sqlResult = await sqlDataWriter(qu)
match sqlResult:
case 1:
print("Wrong source")
case 2:
print("Error inserting to database")
case 3:
print("No engine defined")
case _:
print("Inserted in SQL")
if USEINFLUX:
fluxResult = await fluxDataWriter(qu)
match fluxResult:
case 1:
print("could not insert")
case 2:
print("Error with initializing Inxlux variables")
case _:
print("Inserted to InfluxDB")
except Exception as e:
print(e)
finally:
inputQueue.task_done()

View File

@@ -0,0 +1,70 @@
---
services:
snmpcollector:
container_name: snmpython-dev
user: 1600:1600
image: shupotea/yuruc3/snmpython:dev
build:
dockerfile: ./Dockerfile
environment:
- USEINFLUX=1
- INFLXDBTOKEN=67676767
- INFLUXBCKT=pyusr-DEV
- INFLUXORG=staging
- INFLXDBURL=http://influxdb:8086
- INFXLUXDB_MEASUEREMENT=dev-pycollector
- USESQL=1
- DBADDR=mariadb
- DBUSR=root
- DBPWD=67676767
- DBNAME=pyCollector
- SNMPUSER=SNMPusrRO
- SNMPPRIVKEY=67676767
- SNMPAUTHKEY=67676767
- IDRAC_HOST_LIST=192.168.0.1;192.168.0.1;192.168.0.1;192.168.0.1
- CISCO_HOST_LIST=192.168.0.1;192.168.0.1;192.168.0.1;192.168.0.1
# - HOST_LIST=192.168.20.7
# restart: unless-stopped
restart: no
influxdb:
image: influxdb:2.7
container_name: influxdb-test
ports:
- "8086:8086"
environment:
- DOCKER_INFLUXDB_INIT_MODE=setup
- DOCKER_INFLUXDB_INIT_USERNAME=yuru
- DOCKER_INFLUXDB_INIT_PASSWORD=67676767
- DOCKER_INFLUXDB_INIT_ORG=staging
- DOCKER_INFLUXDB_INIT_BUCKET=pyusr-DEV
- DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=67676767
volumes:
- influx:/var/lib/influxdb2
restart: unless-stopped
mariadb:
container_name: uberAPI-mdb
image: mariadb:latest
restart: always
volumes:
- mdb:/var/lib/mysql
environment:
MARIADB_ROOT_PASSWORD: "67676767"
ports:
- "3306:3306"
adminer:
image: adminer
container_name: adminer
restart: always
ports:
- "8180:8080"
volumes:
influx:
mdb:

31
cont/docker-compose.yaml Normal file
View File

@@ -0,0 +1,31 @@
---
services:
snmpcollector:
container_name: snmpython
user: 1600:1600
image: shupotea/yuruc3/snmpython:latest
environment:
- USEINFLUX=1
- INFLXDBTOKEN=67676767
- INFLUXBCKT=pyusr-DEV
- INFLUXORG=staging
- INFLXDBURL=http://influxdb:8086
- INFXLUXDB_MEASUEREMENT=dev-pycollector
- USESQL=1
- DBADDR=mariadb
- DBUSR=root
- DBPWD=SomeDBPassword
- DBNAME=SomeDBName
- SNMPUSER=SNMPusr
- SNMPPRIVKEY=SomePassword
- SNMPAUTHKEY=SomePassword
- IDRAC78_HOST_LIST=192.168.20.7;192.168.20.10;192.168.20.8;192.168.20.12
- IDRAC9_HOST_LIST=192.168.20.4
- CISCO_3750_HOST_LIST=192.168.20.16
- CISCO_NEXUS_HOST_LIST=192.168.0.1;192.168.0.1;192.168.0.1;192.168.0.1
restart: unless-stopped

View File

@@ -2,8 +2,8 @@
set -e
# Default to 1600 if not provided
PUID="${PUID:-1000}"
PGID="${PGID:-1000}"
PUID="${PUID:-1600}"
PGID="${PGID:-1600}"
USER="${USER:-pythusr}"
GROUP="${USER:-pythusr}"
CHOWNPATH="/app/snmpython"

0
cont/idrac/__init__.py Normal file
View File

62
cont/idrac/fans.py Normal file
View File

@@ -0,0 +1,62 @@
import os, asyncio
from models.fanModel import fanModel
from snmp.walker import walk_column_v3
from typing import Annotated, Final
# SNMP
from pysnmp.hlapi.v3arch.asyncio import *
# SNMP ENV-------------------------------------------
ROUND_PREC: Final[int] = int(os.getenv("ROUND_PREC", 2))
SNMPUSER: Final[str] = os.getenv("SNMPUSER", None)
SNMPPRIVKEY: Final[str] = os.getenv("SNMPPRIVKEY", None)
SNMPAUTHKEY: Final[str] = os.getenv("SNMPAUTHKEY", None)
# Right now I'll only use SHA
# SNMPAUTHPROTO: Final[str] = os.getenv("SNMPAUTHPROTO", "SHA")
# SNMPPRIVPROTO: Final[str] = os.getenv("SNMPPRIVPROTO", "SHA")
SNMPORT: Final[int] = int(os.getenv("SNMPORT", 161))
# Check if SNMP ENV are empty
if not SNMPUSER or not SNMPPRIVKEY or not SNMPAUTHKEY:
raise Exception("No SNMP user or/and PrivAuth passed")
# SNMP
USMUSRDATA = UsmUserData(
userName=SNMPUSER,
authKey=SNMPAUTHKEY,
privKey=SNMPPRIVKEY,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmAesCfb128Protocol,
)
async def idracPoolRemoteFAN_v3(remoteIP: str, queueToInsrt: asyncio.Queue) -> dict:
snmpEngine = SnmpEngine()
rpm_oid = "1.3.6.1.4.1.674.10892.5.4.700.12.1.6"
name_oid = "1.3.6.1.4.1.674.10892.5.4.700.12.1.19"
rpms, names = await asyncio.gather(
walk_column_v3(snmpEngine, remoteIP, rpm_oid),
walk_column_v3(snmpEngine, remoteIP, name_oid),
)
snmpEngine.close_dispatcher()
# merge by index
out = {}
for idx, rpm in rpms.items():
out[idx] = {"rpm": rpm, "name": names.get(idx).split(".")[2]}
returnDict = {
"source": "FANS",
"value": out,
"type": "Dict"
}
await queueToInsrt.put(returnDict)

208
cont/idrac/idrac78.py Normal file
View File

@@ -0,0 +1,208 @@
import os, asyncio
from models.idracModel import snmpPyIDRACData
from snmp.walker import walk_column_v3
from typing import Annotated, Final
# SNMP
from pysnmp.hlapi.v3arch.asyncio import *
# IDRAC78 get data for snmpPyIDRACData class
async def idrac7_8PoolRemote_v3(
remoteIP: str,
queueToInsrt: asyncio.Queue,
SNMPUSER: str,
SNMPAUTHKEY: str,
SNMPPRIVKEY: str,
ROUND_PREC: int = 2,
SNMPORT: int = 161
):
print("starting work on ", remoteIP)
# SNMPUSRDATA
USMUSRDATA = UsmUserData(
userName=SNMPUSER,
authKey=SNMPAUTHKEY,
privKey=SNMPPRIVKEY,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmAesCfb128Protocol,
)
snmpEngine = SnmpEngine()
# try CPU2 as CPU1 is always there
mainIterator = 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
# Uptime in seconds
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.2.5.0")), #1
)
errorIndication, errorStatus, errorIndex, mainVarBinds = await mainIterator
if errorIndication:
if errorIndication == "No SNMP response received before timeout":
print(f"Host {remoteIP} timed out.\nContinuing...")
snmpEngine.close_dispatcher()
return 1
print(errorIndication)
elif errorStatus:
print(
"{} at {}".format(
errorStatus.prettyPrint(),
errorIndex and mainVarBinds[int(errorIndex) - 1][0] or "?",
)
)
else:
for oid, val in mainVarBinds:
print(f"{oid.prettyPrint()} = {val.prettyPrint()}")
try:
# Get PSU1 current, [PSU2 current], total board power draw in watts
currentsCurrentResults = await walk_column_v3(snmpEngine, remoteIP, ".1.3.6.1.4.1.674.10892.5.4.600.30.1.6", SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY)
# PSU1 OID name, [PSU2 OID name], system board power draw
currentsNamesResults = await walk_column_v3(snmpEngine, remoteIP, ".1.3.6.1.4.1.674.10892.5.4.600.30.1.8", SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY)
# Inlet OID name .1, [Exhaust OID name], CPU1 temp OID name, [CPU2 tempOID name]
sensorNamesResults = await walk_column_v3(snmpEngine, remoteIP, ".1.3.6.1.4.1.674.10892.5.4.700.20.1.8", SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY)
# Inlet temp, [Exhaust temp], CPU1 temp, [CPU2 temp]
sensorValuesResults = await walk_column_v3(snmpEngine, remoteIP, ".1.3.6.1.4.1.674.10892.5.4.700.20.1.6", SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY)
# .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", SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY)
hasExhaust = any("exhaust" in str(thing).lower() for thing in sensorNamesResults.values())
hasCPU2 = any("cpu2" in str(thing).lower() for thing in sensorNamesResults.values())
hasPSU2 = any("ps2" in str(thing).lower() for thing in currentsNamesResults.values())
except SNMPTimeoutError as e:
print(f"{e}\nContinuing regardless...")
return 1
except RuntimeError as e:
print(f"SNMP error on {remoteIP}: {e}")
return 1
snmpEngine.close_dispatcher()
# Exhaust CPU1 and CPU2 temp
exhaustTemp = None
cpu2Temp = None
if len(currentsCurrentResults) == 1 and int(mainVarBinds[1][-1]) == 0:
exhaustTemp = int(sensorValuesResults[2]) / 10
cpu1Temp = None
elif hasExhaust and hasCPU2:
exhaustTemp = int(sensorValuesResults[2]) / 10
cpu1Temp = int(sensorValuesResults[3]) / 10
cpu2Temp = int(sensorValuesResults[4]) / 10
elif hasExhaust and not hasCPU2:
exhaustTemp = int(sensorValuesResults[2]) / 10
cpu1Temp = int(sensorValuesResults[3]) / 10
elif hasCPU2 and not hasExhaust:
cpu1Temp = int(sensorValuesResults[3]) / 10
cpu2Temp = int(sensorValuesResults[4]) / 10
else:
cpu1Temp = int(sensorValuesResults[2]) / 10
# If power draw is 0; I.E. if server is turned off
if len(currentsCurrentResults) == 1 and int(mainVarBinds[1][-1]) == 0:
returnObj = snmpPyIDRACData(
hostname=mainVarBinds[0][-1],
# Hostname
powerDrawPSU1=None,
powerDrawPSU2=None,
# PSU1 and PSU2 power draw in Watts
# board power draw
powerDrawBoard=None,
voltagePSU1=None,
voltagePSU2=None,
# PSU1 and PSU2 voltages
inletTemp=(int(sensorValuesResults[1]) / 10),
exhaustTemp=exhaustTemp,
# Inlet and Exhaust temp
cpu1Temp=None,
cpu2Temp=None,
# CPU1 and CPU2 temp
uptimeS=int(mainVarBinds[1][-1]),
# seconds
uptimeH=round(((int(mainVarBinds[1][-1]) / 60) / 60), ROUND_PREC),
# seconds->minutes->hours
uptimeD=round((((int(mainVarBinds[1][-1]) / 60) / 60) / 24), ROUND_PREC)
# seconds->minutes->hours->days
)
returnDict = {
"source": "IDRAC",
"value": returnObj,
"type": "snmpPyIDRACData"
}
# return returnObj
await queueToInsrt.put(returnDict)
return 1
voltList = []
for thing in voltResult:
voltList.append(voltResult[thing])
# PSU2 values and board power
psu2PowerDraw = None
psu2Amperage = None
psu2Voltage = None
if hasPSU2:
psu2PowerDraw = round((int(currentsCurrentResults[2]) / 10) * (int(voltList[1]) / 1000), ROUND_PREC)
psu2Amperage = int(currentsCurrentResults[2]) / 10
psu2Voltage = round((int(voltList[1]) / 1000), ROUND_PREC)
boardPowerDraw = int(currentsCurrentResults[3])
else:
boardPowerDraw = int(currentsCurrentResults[2])
returnObj = snmpPyIDRACData(
hostname=mainVarBinds[0][-1],
# Hostname
powerDrawPSU1=round((int(currentsCurrentResults[1]) / 10) * (int(voltList[0]) / 1000), ROUND_PREC),
powerDrawPSU2=psu2PowerDraw,
# PSU1 and PSU2 power draw in Watts
# board power draw
powerDrawBoard=boardPowerDraw,
voltagePSU1=round((int(voltList[0]) / 1000), ROUND_PREC),
voltagePSU2=psu2Voltage,
# PSU1 and PSU2 voltages
inletTemp=(int(sensorValuesResults[1]) / 10),
exhaustTemp=exhaustTemp,
# Inlet and Exhaust temp
cpu1Temp=cpu1Temp,
cpu2Temp=cpu2Temp,
# CPU1 and CPU2 temp
uptimeS=int(mainVarBinds[1][-1]),
# seconds
uptimeH=round(((int(mainVarBinds[1][-1]) / 60) / 60), ROUND_PREC),
# seconds->minutes->hours
uptimeD=round((((int(mainVarBinds[1][-1]) / 60) / 60) / 24), ROUND_PREC)
# seconds->minutes->hours->days
)
returnDict = {
"source": "IDRAC",
"value": returnObj,
"type": "snmpPyIDRACData"
}
# return returnObj
await queueToInsrt.put(returnDict)

218
cont/idrac/idrac9.py Normal file
View File

@@ -0,0 +1,218 @@
import os, asyncio
from models.idracModel import snmpPyIDRACData
from models.snmpTimeOut import SNMPTimeoutError
from snmp.walker import walk_column_v3
from typing import Annotated, Final
# SNMP
from pysnmp.hlapi.v3arch.asyncio import *
# IDRAC get data for snmpPyIDRACData class
async def idrac9PoolRemote_v3(
remoteIP: str,
queueToInsrt: asyncio.Queue,
SNMPUSER: str,
SNMPAUTHKEY: str,
SNMPPRIVKEY: str,
ROUND_PREC: int = 2,
SNMPORT: int = 161
):
print("starting work on ", remoteIP)
# SNMP
USMUSRDATA = UsmUserData(
userName=SNMPUSER,
authKey=SNMPAUTHKEY,
privKey=SNMPPRIVKEY,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmAesCfb128Protocol,
)
snmpEngine = SnmpEngine()
iterator = get_cmd(
snmpEngine,
USMUSRDATA,
await UdpTransportTarget.create((remoteIP, SNMPORT)),
ContextData(),
# Hostname (sysName.0)
ObjectType(ObjectIdentity(".1.3.6.1.2.1.1.5.0")), #0
# Uptime in seconds
ObjectType(ObjectIdentity(".1.3.6.1.4.1.674.10892.5.2.5.0")), #1
)
errorIndication, errorStatus, errorIndex, mainVarBinds = await iterator
if errorIndication:
if errorIndication == "No SNMP response received before timeout":
print(f"Host {remoteIP} timed out.\nContinuing...")
snmpEngine.close_dispatcher()
return 1
print(errorIndication)
elif errorStatus:
print(
"{} at {}".format(
errorStatus.prettyPrint(),
errorIndex and mainVarBinds[int(errorIndex) - 1][0] or "?",
)
)
else:
for oid, val in mainVarBinds:
print(f"{oid.prettyPrint()} = {val.prettyPrint()}")
try:
# Get PSU1 current, [PSU2 current], total board power draw in watts
currentsCurrentResults = await walk_column_v3(snmpEngine, remoteIP, ".1.3.6.1.4.1.674.10892.5.4.600.30.1.6", SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY)
# PSU1 OID name, [PSU2 OID name], system board power draw
currentsNamesResults = await walk_column_v3(snmpEngine, remoteIP, ".1.3.6.1.4.1.674.10892.5.4.600.30.1.8", SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY)
# CPU1 OID name .1, [CPU2 OID name], Inlet temp OID name, [Exhaust temp OID name]
sensorNamesResults = await walk_column_v3(snmpEngine, remoteIP, ".1.3.6.1.4.1.674.10892.5.4.700.20.1.8", SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY)
# CPU1 temp, [CPU2 temp], Inlet temp, [Exhaust temp]
sensorValuesResults = await walk_column_v3(snmpEngine, remoteIP, ".1.3.6.1.4.1.674.10892.5.4.700.20.1.6", SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY)
# .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", SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY)
except SNMPTimeoutError as e:
print(f"{e}\nContinuing regardless...")
return 1
except RuntimeError as e:
print(f"SNMP error on {remoteIP}: {e}")
return 1
snmpEngine.close_dispatcher()
hasExhaust = any("exhaust" in str(thing).lower() for thing in sensorNamesResults.values())
hasCPU2 = any("cpu2" in str(thing).lower() for thing in sensorNamesResults.values())
hasPSU2 = any("ps2" in str(thing).lower() for thing in currentsNamesResults.values())
# Exhaust CPU1 and CPU2 temp
exhaustTemp = None
inletTemp = None
cpu2Temp = None
if len(currentsCurrentResults) == 1 and int(mainVarBinds[1][-1]) == 0:
exhaustTemp = int(sensorValuesResults[2]) / 10
inletTemp = int(sensorValuesResults[1]) / 10
cpu1Temp = None
elif hasExhaust and hasCPU2:
exhaustTemp = int(sensorValuesResults[4]) / 10
inletTemp = int(sensorValuesResults[3]) / 10
cpu2Temp = int(sensorValuesResults[2]) / 10
elif hasExhaust and not hasCPU2:
exhaustTemp = int(sensorValuesResults[3]) / 10
inletTemp = int(sensorValuesResults[2]) / 10
elif hasCPU2 and not hasExhaust:
inletTemp = int(sensorValuesResults[3]) / 10
cpu2Temp = int(sensorValuesResults[2]) / 10
else:
inletTemp = int(sensorValuesResults[2]) / 10
if len(currentsCurrentResults) == 1 and int(mainVarBinds[1][-1]) == 0:
returnObj = snmpPyIDRACData(
hostname=mainVarBinds[0][-1],
# Hostname
powerDrawPSU1=None,
powerDrawPSU2=None,
# PSU1 and PSU2 power draw in Watts
# board power draw
powerDrawBoard=None,
voltagePSU1=None,
voltagePSU2=None,
# PSU1 and PSU2 voltages
inletTemp=inletTemp,
exhaustTemp=exhaustTemp,
# Inlet and Exhaust temp
cpu1Temp=None,
cpu2Temp=None,
# CPU1 and CPU2 temp
uptimeS=int(mainVarBinds[1][-1]),
# seconds
uptimeH=round(((int(mainVarBinds[1][-1]) / 60) / 60), ROUND_PREC),
# seconds->minutes->hours
uptimeD=round((((int(mainVarBinds[1][-1]) / 60) / 60) / 24), ROUND_PREC)
# seconds->minutes->hours->days
)
returnDict = {
"source": "IDRAC",
"value": returnObj,
"type": "snmpPyIDRACData"
}
# return returnObj
await queueToInsrt.put(returnDict)
return 1
voltList = []
for thing in voltResult:
voltList.append(voltResult[thing])
# PSU2 values and board power
psu2PowerDraw = None
psu2Amperage = None
psu2Voltage = None
psu1PowerDraw = None
psu1Amperage = None
psu1Voltage = None
if hasPSU2:
psu2PowerDraw = round((int(currentsCurrentResults[2]) / 10) * (int(voltList[1]) / 1000), ROUND_PREC)
psu2Amperage = int(currentsCurrentResults[2]) / 10
psu2Voltage = round((int(voltList[1]) / 1000), ROUND_PREC)
boardPowerDraw = int(currentsCurrentResults[3])
else:
boardPowerDraw = int(currentsCurrentResults[2])
returnObj = snmpPyIDRACData(
hostname=mainVarBinds[0][-1],
# Hostname
powerDrawPSU1=round((int(currentsCurrentResults[1]) / 10) * (int(voltList[0]) / 1000), ROUND_PREC),
powerDrawPSU2=psu2PowerDraw,
# PSU1 and PSU2 power draw in Watts
# board power draw
powerDrawBoard=boardPowerDraw,
voltagePSU1=round((int(voltList[0]) / 1000), ROUND_PREC),
voltagePSU2=psu2Voltage,
# PSU1 and PSU2 voltages
inletTemp=inletTemp,
exhaustTemp=exhaustTemp,
# Inlet and Exhaust temp
cpu1Temp=int(sensorValuesResults[1]) / 10,
cpu2Temp=cpu2Temp,
# CPU1 and CPU2 temp
uptimeS=int(mainVarBinds[1][-1]),
# seconds
uptimeH=round(((int(mainVarBinds[1][-1]) / 60) / 60), ROUND_PREC),
# seconds->minutes->hours
uptimeD=round((((int(mainVarBinds[1][-1]) / 60) / 60) / 24), ROUND_PREC)
# seconds->minutes->hours->days
)
returnDict = {
"source": "IDRAC",
"value": returnObj,
"type": "snmpPyIDRACData"
}
# return returnObj
await queueToInsrt.put(returnDict)

139
cont/mainRE.py Normal file
View File

@@ -0,0 +1,139 @@
import ipaddress, os, re, time, asyncio
# SNMP
from pysnmp.hlapi.v3arch.asyncio import *
# QoL
from typing import Annotated, Final
# functions
from db.collectiveWriter import ALLdbIDRACWriter
# idrac SNMP
from idrac import idrac78, idrac9
# Cisco hosts
from cisco import lob, nyater
# SNMP ENV-------------------------------------------
ROUND_PREC: Final[int] = int(os.getenv("ROUND_PREC", 2))
SNMPUSER: Final[str] = os.getenv("SNMPUSER", None)
SNMPPRIVKEY: Final[str] = os.getenv("SNMPPRIVKEY", None)
SNMPAUTHKEY: Final[str] = os.getenv("SNMPAUTHKEY", None)
# Right now I'll only use SHA
# SNMPAUTHPROTO: Final[str] = os.getenv("SNMPAUTHPROTO", "SHA")
# SNMPPRIVPROTO: Final[str] = os.getenv("SNMPPRIVPROTO", "SHA")
SNMPORT: Final[int] = int(os.getenv("SNMPORT", 161))
# Check if SNMP ENV are empty
if not SNMPUSER or not SNMPPRIVKEY or not SNMPAUTHKEY:
raise Exception("No SNMP user or/and PrivAuth passed")
# Program ENV---------------------------------------
try:
IDRAC78_HOST_LIST: Final[list] = os.getenv("IDRAC78_HOST_LIST", []).split(";")
IDRAC9_HOST_LIST: Final[list] = os.getenv("IDRAC9_HOST_LIST", []).split(";")
except:
raise Exception("No IDRAC hosts variable")
try:
CISCO_3750_HOST_LIST: Final[list] = os.getenv("CISCO_3750_HOST_LIST", []).split(";")
except:
print("No 3750-X devices passed.\nContinuing with what is available.\n")
CISCO_3750_HOST_LIST = []
try:
CISCO_NEXUS_HOST_LIST: Final[list] = os.getenv("CISCO_NEXUS_HOST_LIST", []).split(";")
except:
print("No Nexus devices passed.\nContinuing with what is available.\n")
CISCO_NEXUS_HOST_LIST = []
GET_INTERVAL: Final[int] = int(os.getenv("GET_INTERVAL", 60))
# Flightchecks-------------------------------------------
# if HOST_LIST empty, raise Exception No hosts passed
# if not IDRAC78_HOST_LIST and not IDRAC9_HOST_LIST:
# raise Exception("No IDRAC hosts passed\nExiting...")
# for host in HOST_LIST check if valid IP and create a list with strings
try:
for idrac78Host in IDRAC78_HOST_LIST:
try:
ip = str(ipaddress.IPv4Address(idrac78Host))
except ValueError:
raise Exception(f" IP {ip} for IDRAC7/8 devices is invalid.\nExiting...")
except:
IDRAC78_HOST_LIST = []
for cisco3750Host in CISCO_3750_HOST_LIST:
try:
ip = str(ipaddress.IPv4Address(cisco3750Host))
except ValueError:
raise Exception(f" IP {ip} for Cisco devices is invalid.\nExiting...")
for ciscoNexusHost in CISCO_NEXUS_HOST_LIST:
try:
ip = str(ipaddress.IPv4Address(ciscoNexusHost))
except ValueError:
raise Exception(f" IP {ip} for Cisco devices is invalid.\nExiting...")
try:
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...")
except:
IDRAC9_HOST_LIST = []
# Done under "Program ENV" on line ~18
# Code-------------------------------------------
async def main():
mainQueue = asyncio.Queue(maxsize=225)
asyncio.create_task(ALLdbIDRACWriter(mainQueue))
print("Starting tasks")
while True:
# IDRAC part
async with asyncio.TaskGroup() as tg:
for Idrac78IP in IDRAC78_HOST_LIST:
tg.create_task(idrac78.idrac7_8PoolRemote_v3(
Idrac78IP, mainQueue,
SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY, ROUND_PREC, SNMPORT))
for Idrac9IP in IDRAC9_HOST_LIST:
tg.create_task(idrac9.idrac9PoolRemote_v3(
Idrac9IP, mainQueue,
SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY, ROUND_PREC, SNMPORT))
# FANS
# tg.create_task(idracPoolRemoteFAN_v3(IdracIP, mainQueue))
# CISCO devices
for cisco3750Xdev in CISCO_3750_HOST_LIST:
tg.create_task(lob.lobGetRemote(
cisco3750Xdev, mainQueue,
SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY, ROUND_PREC, SNMPORT))
# for ciscoNexusDev in CISCO_NEXUS_HOST_LIST:
# tg.create_task(nyater(
# ciscoNexusDev, mainQueue,
# SNMPUSER, SNMPAUTHKEY, SNMPPRIVKEY, ROUND_PREC, SNMPORT))
await asyncio.sleep(GET_INTERVAL)
if __name__ == "__main__":
# qu = asyncio.Queue()
# asyncio.run(idracPoolRemote_v3("192.168.20.7", qu))
print("Starting")
asyncio.run(main())
# loop = asyncio.get_event_loop()
# task = loop.create_task(main())

0
cont/models/__init__.py Normal file
View File

66
cont/models/ciscoModel.py Normal file
View File

@@ -0,0 +1,66 @@
from dataclasses import dataclass
from typing import Optional, Annotated, Dict
@dataclass
class C3750XciscoData():
"""Dataclass for snmp data"""
hostname: str
systemStatus: str
systemTemp: int
last5SecUsage: int
last1MinUsage: int
last5MinUsage: int
uptimeS: int
# uptimeH: float
def __str__(self):
return (
f"{self.hostname}\n"
f"System status: {self.systemStatus} \n"
f"System temp: {self.systemTemp} C\n"
f"Last 5 second CPU usage: {self.last5SecUsage} %\n"
f"Last 1 minute CPU usage: {self.last1MinUsage} %\n"
f"Last 5 minutes CPU usage: {self.last5MinUsage} %\n"
f"Uptime: {self.uptimeS} Seconds\n"
)
@dataclass
class snmpPyCiscoData():
"""Dataclass for snmp data"""
hostname: str
powerDrawPSU1: int
voltagePSU1: int
inletTemp: float
exhaustTemp: float
cpu1Temp: float
uptimeS: int
uptimeH: float
# optional fields
uptimeD: float | None = None
cpu2Temp: float | None = None
powerDrawPSU2: int | None = None
voltagePSU2: int | None = None
def __str__(self):
return (
f"{self.hostname}\n"
f"PSU1 {self.powerDrawPSU1} Watts\n"
f"PSU2 {self.powerDrawPSU2} Watts\n"
f"PSU1 {self.voltagePSU1} Volts\n"
f"PSU2 {self.voltagePSU2} Volts\n"
f"Inlet {self.inletTemp} C\n"
f"Exhaust {self.exhaustTemp} C\n"
f"CPU1 {self.cpu1Temp} C\n"
f"CPU2 {self.cpu2Temp} C\n"
f"Uptime {self.uptimeS} Seconds\n"
f"Uptime {self.uptimeH} Hours\n"
f"Uptime {self.uptimeD} Days"
)

7
cont/models/fanModel.py Normal file
View File

@@ -0,0 +1,7 @@
from dataclasses import dataclass
from typing import Optional, Annotated, Dict
@dataclass
class idracFanStatus:
fans: Dict[str, int] | None = Dict[None, None] # name/index -> rpm

43
cont/models/idracModel.py Normal file
View File

@@ -0,0 +1,43 @@
from dataclasses import dataclass
from typing import Optional, Annotated, Dict
@dataclass
class snmpPyIDRACData():
"""Dataclass for snmp data"""
hostname: str
powerDrawPSU1: int
voltagePSU1: int
inletTemp: float
exhaustTemp: float
cpu1Temp: float
uptimeS: int
uptimeH: float
# optional fields
uptimeD: float | None = None
cpu2Temp: float | None = None
powerDrawPSU2: int | None = None
powerDrawBoard: int | None = None
voltagePSU2: int | None = None
def __str__(self):
return (
f"{self.hostname}\n"
f"PSU1 {self.powerDrawPSU1} Watts\n"
f"PSU2 {self.powerDrawPSU2} Watts\n"
f"Board {self.powerDrawBoard} Watts\n"
f"PSU1 {self.voltagePSU1} Volts\n"
f"PSU2 {self.voltagePSU2} Volts\n"
f"Inlet {self.inletTemp} C\n"
f"Exhaust {self.exhaustTemp} C\n"
f"CPU1 {self.cpu1Temp} C\n"
f"CPU2 {self.cpu2Temp} C\n"
f"Uptime {self.uptimeH} Hours\n"
f"Uptime {self.uptimeD} Days"
)

View File

@@ -0,0 +1,2 @@
class SNMPTimeoutError(Exception):
pass

62
cont/models/sqlTable.py Normal file
View File

@@ -0,0 +1,62 @@
from sqlalchemy import Table, Column, MetaData, Integer, String, TIMESTAMP, BigInteger, Numeric, text
metadata = MetaData()
idracMeasurement = Table(
"IDRACmeasurement",
metadata,
Column("id", Integer, primary_key=True, autoincrement=True),
Column("time_stamp", TIMESTAMP, server_default=text("CURRENT_TIMESTAMP"), nullable=False),
Column("hostname", String(64), nullable=False),
Column("powerDrawPSU1", Numeric(7, 2)),
Column("powerDrawPSU2", Numeric(7, 2)),
Column("powerDrawBoard", Numeric(7, 2)),
Column("voltagePSU1", Numeric(7, 2)),
Column("voltagePSU2", Numeric(7, 2)),
Column("inletTemp", Numeric(5, 2)),
Column("exhaustTemp", Numeric(5, 2)),
Column("cpu1Temp", Numeric(5, 2)),
Column("cpu2Temp", Numeric(5, 2)),
Column("uptimeS", BigInteger),
Column("uptimeH", Numeric(8, 3)),
Column("uptimeD", Numeric(8, 3)),
)
C3750XMeasurement = Table(
"C3750XMeasurement",
metadata,
Column("id", Integer, primary_key=True, autoincrement=True),
Column("time_stamp", TIMESTAMP, server_default=text("CURRENT_TIMESTAMP"), nullable=False),
Column("hostname", String(64), nullable=False),
Column("systemStatus", String(64)),
Column("systemTemp", Numeric(7, 2)),
Column("last5SecUsage", Numeric(7, 2)),
Column("last1MinUsage", Numeric(7, 2)),
Column("last5MinUsage", Numeric(7, 2)),
Column("uptimeS", BigInteger),
)
NexusMeasurement = Table(
"NexusMeasurement",
metadata,
Column("id", Integer, primary_key=True, autoincrement=True),
Column("time_stamp", TIMESTAMP, server_default=text("CURRENT_TIMESTAMP"), nullable=False),
Column("hostname", String(64), nullable=False),
Column("systemStatus", String(64)),
Column("systemTemp", Numeric(7, 2)),
Column("last5SecUsage", Numeric(7, 2)),
Column("last1MinUsage", Numeric(7, 2)),
Column("last5MinUsage", Numeric(7, 2)),
Column("uptimeS", BigInteger),
)

14
cont/requirements.txt Normal file
View File

@@ -0,0 +1,14 @@
# mysql-connector-python==9.2.0
PyMySQL==1.1.2
sqlmodel==0.0.37
sqlalchemy[asyncio]==2.0.47
asyncmy==0.2.11
pydantic==2.12.5
pydantic_core==2.41.5
annotated-types==0.7.0
typing==3.7.4.3
pysnmp==7.1.22
cryptography==46.0.5
# requests==2.32.5
influxdb-client==1.50.0
# influxdb3-python==0.18.0

0
cont/snmp/__init__.py Normal file
View File

117
cont/snmp/walker.py Normal file
View File

@@ -0,0 +1,117 @@
import asyncio, os
from typing import Annotated, Final
# SNMP
from pysnmp.hlapi.v3arch.asyncio import *
from models.snmpTimeOut import SNMPTimeoutError
# SNMP ENV-------------------------------------------
ROUND_PREC: Final[int] = int(os.getenv("ROUND_PREC", 2))
SNMPUSER: Final[str] = os.getenv("SNMPUSER", None)
SNMPPRIVKEY: Final[str] = os.getenv("SNMPPRIVKEY", None)
SNMPAUTHKEY: Final[str] = os.getenv("SNMPAUTHKEY", None)
# Right now I'll only use SHA
# SNMPAUTHPROTO: Final[str] = os.getenv("SNMPAUTHPROTO", "SHA")
# SNMPPRIVPROTO: Final[str] = os.getenv("SNMPPRIVPROTO", "SHA")
SNMPORT: Final[int] = int(os.getenv("SNMPORT", 161))
# Check if SNMP ENV are empty
if not SNMPUSER or not SNMPPRIVKEY or not SNMPAUTHKEY:
raise Exception("No SNMP user or/and PrivAuth passed")
# Return a following object
# {
# 1: "SomeObject",
# 2: "someOtherObject"
# }
# The value is always a string so it needs to be converted to int if needed
async def walk_column_v3(
snmpEngine,
remoteIP: str,
base_oid: str,
SNMPUSER: str,
SNMPAUTHKEY: str,
SNMPPRIVKEY: str
) -> dict:
# SNMP
USMUSRDATA = UsmUserData(
userName=SNMPUSER,
authKey=SNMPAUTHKEY,
privKey=SNMPPRIVKEY,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmAesCfb128Protocol,
)
rows = {}
async for errInd, errStat, errIdx, varBinds in walk_cmd(
snmpEngine,
USMUSRDATA,
await UdpTransportTarget.create((remoteIP, SNMPORT)),
ContextData(),
ObjectType(ObjectIdentity(base_oid)),
lexicographicMode=False,
):
if errInd:
print(f"\n\n{errInd}\n\n")
if "No SNMP response received before timeout" in str(errInd):
raise SNMPTimeoutError(f"Host {remoteIP} timed out while walking {base_oid}")
raise RuntimeError(errInd)
if errStat:
raise RuntimeError(errStat.prettyPrint())
for oid, val in varBinds:
# index is typically the last sub-identifier
idx = int(oid.prettyPrint().split(".")[-1])
rows[idx] = val.prettyPrint()
return rows
async def walk_lobotomy_column_v3(
snmpEngine,
remoteIP: str,
base_oid: str,
SNMPUSER: str,
SNMPAUTHKEY: str,
SNMPPRIVKEY: str
) -> list:
# SNMP
USMUSRDATA = UsmUserData(
userName=SNMPUSER,
authKey=SNMPAUTHKEY,
privKey=SNMPPRIVKEY,
authProtocol=usmHMACSHAAuthProtocol,
privProtocol=usmAesCfb128Protocol,
)
elements = []
async for errInd, errStat, errIdx, varBinds in walk_cmd(
snmpEngine,
USMUSRDATA,
await UdpTransportTarget.create((remoteIP, SNMPORT)),
ContextData(),
ObjectType(ObjectIdentity(base_oid)),
lexicographicMode=False,
):
if errInd:
print(f"\n\n{errInd}\n\n")
if "No SNMP response received before timeout" in str(errInd):
raise SNMPTimeoutError(f"Host {remoteIP} timed out while walking {base_oid}")
raise RuntimeError(errInd)
if errStat:
raise RuntimeError(errStat.prettyPrint())
for oid, val in varBinds:
elements.append(val.prettyPrint())
return elements

View File

@@ -22,22 +22,75 @@ SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
SET NAMES utf8mb4;
-- Event for deleting rows older than 3 days
-- Enable scheduler
SET GLOBAL event_scheduler = ON;
-- Event that runs every hour and deletes old rows
-- Delete 2,5k rows at the time to avoid long locks
CREATE EVENT IF NOT EXISTS evPurgeOldIdracRows
ON SCHEDULE EVERY 1 HOUR
DO
DELETE FROM IDRACmeasurement
WHERE time_stamp < (NOW() - INTERVAL 3 DAY)
LIMIT 2500;
-- New universal table type
CREATE TABLE newUniversalSensorTable (
CREATE TABLE IDRACmeasurement (
id MEDIUMINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
time_stamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
hostname VARCHAR(64) NOT NULL,
powerDrawPSU1 DECIMAL(7,2),
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),
voltagePSU2 DECIMAL(7,2) NULL,
inletTemp DECIMAL(5,2),
exhaustTemp DECIMAL(5,2),
cpu1Temp DECIMAL(5,2),
cpu2Temp DECIMAL(5,2) NULL,
uptimeS BIGINT,
uptimeH DECIMAL(8,3),
uptimeD DECIMAL(8,3) NULL,
INDEX idx_time (time_stamp),
INDEX idx_host_time (hostname, time_stamp)
);
-- New universal table type
CREATE TABLE C3750XMeasurement (
id MEDIUMINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
time_stamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
hostname VARCHAR(64) NOT NULL,
systemStatus VARCHAR(64) NOT NULL,
systemTemp DECIMAL(7,2) NOT NULL,
last5SecUsage DECIMAL(7,2) NOT NULL,
last1MinUsage DECIMAL(7,2) NOT NULL,
last5MinUsage DECIMAL(7,2) NOT NULL,
uptimeS BIGINT,
INDEX idx_time (time_stamp),
INDEX idx_host_time (hostname, time_stamp)
);
CREATE TABLE fansHOSTNAMEHERE (
id MEDIUMINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
time_stamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
hostname TEXT(30),
powerDrawPSU1 TINYINT(100),
powerDrawPSU2 TINYINT(100) NULL,
voltagePSU1 TINYINT(100),
voltagePSU2 TINYINT(100) NULL,
inletTemp FLOAT,
exhaustTemp FLOAT,
uptime INT NULL,
pressure FLOAT NULL,
altitude FLOAT NULL
FANxxxRPM INT
);
CREATE TABLE ciscoHOSTNAMEHERE (
id MEDIUMINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
time_stamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
hostname TEXT(30),
something INT
);