From 3a16aa0a2a2e9e9a19e7c1115a2cabe7ff5353d0 Mon Sep 17 00:00:00 2001 From: Benjamin Wiegand <126627496+Benjamin-Wiegand@users.noreply.github.com> Date: Sun, 19 May 2024 04:08:43 -0700 Subject: [PATCH] initial commit --- README.md | 1 + main.py | 206 +++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 + scrape.py | 80 +++++++++++++++ snmp.py | 97 ++++++++++++++++++ snmp_groups.py | 104 ++++++++++++++++++++ targets/__init__.py | 0 targets/cpu.py | 62 ++++++++++++ targets/drive.py | 100 +++++++++++++++++++ targets/fan.py | 78 +++++++++++++++ targets/logical_drive.py | 3 + targets/memory.py | 77 +++++++++++++++ targets/power.py | 7 ++ targets/temp.py | 64 ++++++++++++ 14 files changed, 881 insertions(+) create mode 100644 README.md create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 scrape.py create mode 100644 snmp.py create mode 100644 snmp_groups.py create mode 100644 targets/__init__.py create mode 100644 targets/cpu.py create mode 100644 targets/drive.py create mode 100644 targets/fan.py create mode 100644 targets/logical_drive.py create mode 100644 targets/memory.py create mode 100644 targets/power.py create mode 100644 targets/temp.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..f5eff27 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Fast ILO Exporter \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..dd15303 --- /dev/null +++ b/main.py @@ -0,0 +1,206 @@ +from prometheus_client import start_http_server, Gauge, Counter +from prometheus_client.core import REGISTRY, GaugeMetricFamily +from prometheus_client.registry import Collector + +from pysnmp.entity.engine import SnmpEngine +from pysnmp.hlapi import CommunityData, UdpTransportTarget, ContextData + +from snmp import SnmpConfiguration, snmp_get +import scrape + +from snmp_groups import BulkValues, BulkDummyValue +from targets.temp import * +from targets.fan import * +from targets.cpu import * +from targets.drive import * +from targets.memory import * +import targets.power + +import argparse +import traceback + +NAMESPACE = 'ilo' + +arg_parser = argparse.ArgumentParser( + 'ilo_exporter', + description='A fast(er) prometheus exporter for applicable HP servers using SNMP via the ILO controller.', +) + +arg_parser.add_argument('-i', '--ilo-address', help='ILO IP address to scan.', required=True) +arg_parser.add_argument('-a', '--server-address', default='0.0.0.0', help='Address to bind for hosting the metrics endpoint.') +arg_parser.add_argument('-p', '--server-port', default=6969, help='Port to bind for the metrics endpoint.') +arg_parser.add_argument('-c', '--snmp-community', default='public', help='SNMP community to read.') +arg_parser.add_argument('--snmp-port', default=161, help='SNMP port to use.') +arg_parser.add_argument('-o', '--scan-once', action='store_true', help='Only scan for SNMP variables on init, instead of on each collection (except hard drives, see --scan-drives-once). This is a small optimizaion that can be used if your sever configuration never changes.') +arg_parser.add_argument('--scan-drives-once', action='store_true', help='When combined with --scan-once, this also prevents hard drives from being rescanned on collection. This is not recommeded.') +arg_parser.add_argument('-v', '--verbose', action='store_true', help='Increases verbosity.') +arg_parser.add_argument('-q', '--quiet', action='store_true', help='Tells the exporter to stfu under normal operation unless there is an error/warning.') + +args = arg_parser.parse_args() +if args.quiet and args.verbose: + print('stop it. (--quiet and --verbose do not mix)') + exit(1) + +SCAN_FAIL_COUNTER = Counter('exporter', 'Number of times scanning the iLO for SNMP variables has failed.', namespace=NAMESPACE, subsystem='snmp_scan_failures') + + +def noisy(*a, **kwa): + if not args.quiet: + print(*a, **kwa) + + +def verbose(*a, **kwa): + if args.verbose: + print(*a, **kwa) + + +class BulkCollector(Collector): + def __init__(self, snmp_config: SnmpConfiguration, index_oid_template: str, target_name: str, scan_on_collect: bool, *metrics_groups: tuple[str, BulkValues, list[BulkEnums]], scan_method: any = scrape.detect_things): + self._snmp_config = snmp_config + self._metrics_groups = metrics_groups + self._target_name = target_name + self._name_template = '%s_%s_' % (NAMESPACE, target_name) + '%s' + self._ids = [] + self._index_oid_template = index_oid_template + self._scan_on_collect = scan_on_collect + self._scan_method = scan_method + + if not scan_on_collect: + self.scan() + + def scan(self): + verbose('scanning target', self._target_name) + self._ids = self._scan_method(self._snmp_config, self._index_oid_template) + noisy('found', len(self._ids), 'items for target', self._target_name) + + def collect(self): + cache = {} + + if self._scan_on_collect: + try: + self.scan() + except Exception as e: + traceback.print_exception(e) + print('Failed to scan SNMP, aborting collection') + SCAN_FAIL_COUNTER.inc() + return + + for documentation, bulk_values, bulk_labels in self._metrics_groups: + metric_name = self._name_template % bulk_values.name + verbose('collecting', metric_name) + + label_names = ['id'] + label_maps = [] + + for label in bulk_labels: + # the labels are cached since they may be reused + if label.name not in cache: + cache[label.name] = label.get_values(self._snmp_config, self._ids) + label_names.append(label.name) + label_maps.append(cache[label.name]) + + metric = GaugeMetricFamily( + metric_name, + documentation, + labels=label_names + ) + + # values are not reused + value_map = bulk_values.get_values(self._snmp_config, self._ids) + + # do some fuckery (bad design, I know.) + for i in self._ids: + labels = [str(i)] # id is first + for label_map in label_maps: + label_value = label_map[i] + labels.append(str(label_value)) + + value = value_map[i] + metric.add_metric(labels, value) + + yield metric + + +def get_power_draw() -> float: + verbose('collecting ilo_server_power_draw') + val = snmp_get(config, targets.power.POWER_METER_READING) + return val + + +if __name__ == '__main__': + + config = SnmpConfiguration( + SnmpEngine(), + CommunityData(args.snmp_community), + UdpTransportTarget((args.ilo_address[0], args.snmp_port)), + ContextData(), + ) + + power = Gauge("ilo_server_power_draw", "Power draw of the server in watts") + power.set_function(get_power_draw) + + no_value = BulkDummyValue('info') + + REGISTRY.register(BulkCollector( + config, + TEMP_INDEX, + 'temperature', + not args.scan_once, + ('Temperatures readings of each temperature sensor in celsius', TEMP_CELSIUS, [TEMP_SENSOR_LOCALE, TEMP_CONDITION]), + ('Temperature thresholds for each temperature sensor in celsius', TEMP_THRESHOLD, [TEMP_SENSOR_LOCALE, TEMP_THRESHOLD_TYPE]), + )) + + REGISTRY.register(BulkCollector( + config, + FAN_INDEX, + 'fan', + not args.scan_once, + ('Information about system fans', no_value, [FAN_LOCALE, FAN_CONDITION, FAN_SPEED, FAN_PRESENT, FAN_PRESENCE_TEST]), + )) + + REGISTRY.register(BulkCollector( + config, + CPU_INDEX, + 'cpu', + not args.scan_once, + ('Information about CPUs', no_value, [CPU_NAME, CPU_STATUS, CPU_POWER_STATUS]), + ('Speed of CPUs in megahertz', CPU_SPEED, [CPU_NAME]), + ('CPU step', CPU_STEP, [CPU_NAME]), # I dunno + ('Number of enabled cores', CORES_ENABLED, [CPU_NAME]), + ('Number of available threads', THREADS_AVAILABLE, [CPU_NAME]), + )) + + # logical drives are for v2 if it ever exists (I don't use logical drives, sorry) + + REGISTRY.register(BulkCollector( + config, + DRIVE_INDEX, + 'drive', + not args.scan_drives_once, + ('Information about installed drives', no_value, [DRIVE_BOX, DRIVE_BAY, DRIVE_VENDOR, DRIVE_LOCATION, DRIVE_SERIAL, DRIVE_LINK_RATE, DRIVE_STATUS, DRIVE_CONDITION]), + ('Sizes of installed drives in megabytes', DRIVE_SIZE, [DRIVE_BOX, DRIVE_BAY, DRIVE_VENDOR, DRIVE_LOCATION, DRIVE_SERIAL]), + ('Temperatures of installed drives in celsius', DRIVE_TEMP, [DRIVE_BOX, DRIVE_BAY, DRIVE_VENDOR, DRIVE_LOCATION, DRIVE_SERIAL]), + ('Temperature thresholds of installed drives in celsius', DRIVE_TEMP_THRESHOLD, [DRIVE_BOX, DRIVE_BAY, DRIVE_VENDOR, DRIVE_LOCATION, DRIVE_SERIAL]), + ('Maximum temperatures of installed drives in celsius', DRIVE_TEMP_MAX, [DRIVE_BOX, DRIVE_BAY, DRIVE_VENDOR, DRIVE_LOCATION, DRIVE_SERIAL]), + ('Reference time of installed drives in hours', DRIVE_REFERENCE_TIME, [DRIVE_BOX, DRIVE_BAY, DRIVE_VENDOR, DRIVE_LOCATION, DRIVE_SERIAL]), + scan_method=scrape.detect_complex, + )) + + REGISTRY.register(BulkCollector( + config, + MEMORY_INDEX, + 'memory', + not args.scan_once, + ('Information about system memory', no_value, [MEMORY_LOCATION, MEMORY_MANUFACTURER, MEMORY_PART_NUMBER, MEMORY_STATUS, MEMORY_CONDITION]), + ('Sizes of system memory modules in kilobytes', MEMORY_SIZE, [MEMORY_LOCATION]), + )) + + # start metrics endpoint + addr = args.server_address + port = args.server_port + print('starting metrics server on http://%s:%s' % (addr, port)) + server, thread = start_http_server(port, addr) + print('ready!') + + thread.join() + print('thread died!') diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b3a7d35 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +prometheus_client~=0.20.0 +pysnmp~=4.4.12 \ No newline at end of file diff --git a/scrape.py b/scrape.py new file mode 100644 index 0000000..58fe029 --- /dev/null +++ b/scrape.py @@ -0,0 +1,80 @@ +from snmp import snmp_get_all, snmp_walk, SnmpConfiguration, SnmpEngine, CommunityData, UdpTransportTarget, ContextData + + +def detect_things(c: SnmpConfiguration, base_oid: str) -> list[int]: + """ Scans for things and returns a list of their ids. """ + things = [] + for _, index in snmp_walk(c, base_oid): + assert isinstance(index, int) + assert index not in things + things.append(index) + return things + + +# because of the way drive indexing works, this is the simplest way I can think to do it without over-complicating +# everything else +def detect_complex(c: SnmpConfiguration, base_oid: str) -> list[tuple[int]]: + """ Scans for things and returns a list of their oid indexes. """ + drives = [] + for oid, _ in snmp_walk(c, base_oid): + index = oid[len(base_oid) + 1:] + index = (*[int(i) for i in index.split('.')],) + assert index not in drives + drives.append(index) + return drives + + +if __name__ == '__main__': + from targets.fan import FAN_VALUES, FAN_INDEX + from targets.temp import TEMP_VALUES, TEMP_INDEX + from targets.cpu import CPU_VALUES, CPU_INDEX + from targets.memory import MEMORY_VALUES, MEMORY_INDEX + from targets.drive import DRIVE_INDEX + from targets.logical_drive import LOGICAL_DRIVES_INDEX + + config = SnmpConfiguration( + SnmpEngine(), + CommunityData('deeznuts'), + UdpTransportTarget(('192.168.100.88', 161)), + ContextData(), + ) + + print('scanning hardware...') + fans = detect_things(config, FAN_INDEX) + temp_sensors = detect_things(config, TEMP_INDEX) + cpus = detect_things(config, CPU_INDEX) + logical_drives = detect_things(config, LOGICAL_DRIVES_INDEX) + drives = detect_complex(config, DRIVE_INDEX) + memory_slots = detect_things(config, MEMORY_INDEX) + + print('\'puter has', len(fans), 'fans') + print('\'puter has', len(temp_sensors), 'temp sensors') + print('\'puter has', len(cpus), 'processors') + print('\'puter has', len(logical_drives), 'logical drives') + print('\'puter has', len(drives), 'physical drives') + print('\'puter has', len(memory_slots), 'memory slots') + + for ilo_enum in FAN_VALUES: + states = ilo_enum.get_values(config, fans) + for fan in fans: + print('fan', fan, ilo_enum.name, 'is', states[fan]) + print() + + for value in TEMP_VALUES: + states = value.get_values(config, temp_sensors) + for sensor in temp_sensors: + print('temperature', sensor, value.name, 'is', states[sensor]) + print() + + for value in CPU_VALUES: + states = value.get_values(config, cpus) + for cpu in cpus: + print('cpu', cpu, value.name, 'is', states[cpu]) + print() + + for value in MEMORY_VALUES: + states = value.get_values(config, memory_slots) + for slot in memory_slots: + print('memory slot', slot, value.name, 'is', states[slot]) + print() + diff --git a/snmp.py b/snmp.py new file mode 100644 index 0000000..41bb6e0 --- /dev/null +++ b/snmp.py @@ -0,0 +1,97 @@ +# just a highly simplified wrapper over pysnmp + +from pysnmp.hlapi import NoSuchInstance, Integer, Integer32, Counter32, OctetString, ObjectType, ObjectIdentity, getCmd, nextCmd, SnmpEngine, CommunityData, UdpTransportTarget, ContextData + +# for bulk requests. I find large requests crash the ilo (lol) +MAX_CHUNK = 64 + + +class SnmpConfiguration(object): + def __init__(self, engine: SnmpEngine, auth: CommunityData, transport: UdpTransportTarget, context: ContextData): + self.engine = engine + self.auth = auth + self.transport = transport + self.context = context + + +class AgentError(Exception): + pass + + +class EngineError(Exception): + pass + + +def process_value(var_bind) -> str | int | float | None: + val = var_bind[1] + if isinstance(val, NoSuchInstance): + return None + elif isinstance(val, Integer) or isinstance(val, Integer32) or isinstance(val, Counter32): + return int(val) + elif isinstance(val, OctetString): + return str(val) + else: + print('i dunno:', val) + print('unhandled type:', type(val)) + return val.prettyPrint() + + +def snmp_get(c: SnmpConfiguration, oid: str | tuple[int]) -> str | int | float | None: + """ gets a single oid """ + return snmp_get_all(c, oid)[0] + + +def snmp_get_all(c: SnmpConfiguration, *oid: str | tuple[int]) -> list[str | int | float | None]: + """ does a bulk request """ + if len(oid) > MAX_CHUNK: + # split it up to not break the target + results = [] + results.extend(snmp_get_all(c, *oid[:MAX_CHUNK])) + results.extend(snmp_get_all(c, *oid[MAX_CHUNK:])) + return results + + # do snmp get + it = getCmd(c.engine, c.auth, c.transport, c.context, *[ObjectType(ObjectIdentity(x)) for x in oid]) + engine_err, agent_err, agent_err_index, var_binds = next(it) + + # handle errors + if engine_err: + raise EngineError(engine_err) + elif agent_err: + raise AgentError('%s at %s' % (agent_err.prettyPrint(), var_binds[int(agent_err_index) - 1] if agent_err_index else '?')) + + # debugging + # for var_bind in var_binds: + # print('got snmp:', ' = '.join([x.prettyPrint() for x in var_bind])) + + return [process_value(vb) for vb in var_binds] + + +def snmp_walk(c: SnmpConfiguration, base_oid: str) -> list[tuple[str, str | int | float | None]]: + """ does a walk within the range of a specified base oid """ + results = [] + + # do snmp get + it = nextCmd(c.engine, c.auth, c.transport, c.context, ObjectType(ObjectIdentity(base_oid))) + within = True + while within: + engine_err, agent_err, agent_err_index, var_binds = next(it) + + # handle errors + if engine_err: + raise EngineError(engine_err) + elif agent_err: + raise AgentError('%s at %s' % (agent_err.prettyPrint(), var_binds[int(agent_err_index) - 1] if agent_err_index else '?')) + + for var_bind in var_binds: + # print(var_bind) + oid = str(var_bind[0].getOid()) + if oid.startswith(base_oid): + results.append((oid, process_value(var_bind))) + else: + within = False + + if len(var_binds) == 0: + within = False + + return results diff --git a/snmp_groups.py b/snmp_groups.py new file mode 100644 index 0000000..161249a --- /dev/null +++ b/snmp_groups.py @@ -0,0 +1,104 @@ +from snmp import SnmpConfiguration, snmp_get_all + + +class EnumMapping(object): + def __init__(self, value: int, value_map: dict[int, str]): + self._value = value + self._value_map = value_map + + def get_value(self) -> int: + return self._value + + def get_name(self) -> str | None: + if self._value not in self._value_map.keys(): + return None + return self._value_map[self._value] + + def __str__(self) -> str: + name = self.get_name() + if name is None: + return 'unknown state %i' % self._value + return name + + +class BulkValues(object): + def __init__(self, oid_template, name: str): + self._oid_template = oid_template + self._name = name + + @property + def name(self): + return self._name + + def get_values(self, c: SnmpConfiguration, indexes: list) -> dict: + oids = [self._oid_template(index) for index in indexes] + results = snmp_get_all(c, *oids) + result_dict = {} + for index in indexes: + result_dict[index] = results.pop(0) + + return result_dict + + +class BulkDummyValue(BulkValues): + def __init__(self, name: str): + super().__init__(None, name) + self._name = name + + @property + def name(self): + return self._name + + def get_values(self, _: SnmpConfiguration, indexes: list) -> dict: + result_dict = {} + for index in indexes: + result_dict[index] = 1 + + return result_dict + + +class BulkNumbers(BulkValues): + def __init__(self, oid_template, name: str): + super().__init__(oid_template, name) + + def get_values(self, c: SnmpConfiguration, indexes: list) -> dict: + result_dict = super().get_values(c, indexes) + for key in result_dict.keys(): + if not isinstance(result_dict[key], int): + result_dict[key] = -1 + print('unknown value (not an int):', result_dict[key]) + return result_dict + + +class BulkEnums(BulkNumbers): + def __init__(self, oid_template, name: str, value_map: dict): + super().__init__(oid_template, name) + self._value_map = value_map + + @property + def state_map(self): + return self._value_map + + def get_values(self, c: SnmpConfiguration, indexes: list) -> dict: + result_dict = super().get_values(c, indexes) + for key in result_dict.keys(): + value = result_dict[key] + result_dict[key] = EnumMapping(value, self._value_map) + if __debug__ and value not in self._value_map: + print('unexpected enum value from ilo for %s: %i' % (self.name, value)) + return result_dict + + +class BulkStrings(BulkValues): + def __init__(self, oid_template, name: str): + super().__init__(oid_template, name) + + def get_values(self, c: SnmpConfiguration, indexes: list) -> dict: + result_dict = super().get_values(c, indexes) + for key in result_dict.keys(): + if not isinstance(result_dict[key], str): + result_dict[key] = 'unknown value: %s' % str(result_dict[key]) + print('unknown value (not a string):', result_dict[key]) + else: + result_dict[key] = result_dict[key].strip() + return result_dict diff --git a/targets/__init__.py b/targets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/targets/cpu.py b/targets/cpu.py new file mode 100644 index 0000000..a0fb6c0 --- /dev/null +++ b/targets/cpu.py @@ -0,0 +1,62 @@ +from snmp_groups import BulkEnums, BulkNumbers, BulkStrings + +CPU_INDEX = '1.3.6.1.4.1.232.1.2.2.1.1.1' + +CPU_NAME = BulkStrings( + (lambda i: '1.3.6.1.4.1.232.1.2.2.1.1.3.%i' % i), + 'name', +) + +CPU_SPEED = BulkNumbers( + (lambda i: '1.3.6.1.4.1.232.1.2.2.1.1.4.%i' % i), + 'speed', +) + +CPU_STEP = BulkNumbers( + (lambda i: '1.3.6.1.4.1.232.1.2.2.1.1.5.%i' % i), + 'step', +) + +CPU_STATUS = BulkEnums( + (lambda i: '1.3.6.1.4.1.232.1.2.2.1.1.6.%i' % i), + 'status', + { + 1: 'unknown', + 2: 'ok', + 3: 'degraded', + 4: 'failed', + 5: 'disabled', + } +) + +CORES_ENABLED = BulkNumbers( + (lambda i: '1.3.6.1.4.1.232.1.2.2.1.1.15.%i' % i), + 'cores_enabled', +) + +THREADS_AVAILABLE = BulkNumbers( + (lambda i: '1.3.6.1.4.1.232.1.2.2.1.1.25.%i' % i), + 'threads_available', +) + +CPU_POWER_STATUS = BulkEnums( + (lambda i: '1.3.6.1.4.1.232.1.2.2.1.1.26.%i' % i), + 'power_status', + { + 1: 'unknown', + 2: 'Low Powered', + 3: 'Normal Powered', + 4: 'High Powered', + } +) + +# for debugging +CPU_VALUES = [ + CPU_NAME, + CPU_SPEED, + CPU_STEP, + CPU_STATUS, + CORES_ENABLED, + THREADS_AVAILABLE, + CPU_POWER_STATUS, +] diff --git a/targets/drive.py b/targets/drive.py new file mode 100644 index 0000000..8f7c21a --- /dev/null +++ b/targets/drive.py @@ -0,0 +1,100 @@ +from snmp_groups import BulkEnums, BulkNumbers, BulkStrings + +DRIVE_INDEX = '1.3.6.1.4.1.232.3.2.5.1.1.2' + +# controller? idk +# if anyone can show me a situation where this and drive bay are not related I'll uncomment this +# DRIVE_CONTROLLER = BulkNumbers( +# (lambda i: (1, 3, 6, 1, 4, 1, 232, 3, 2, 5, 1, 1, 1) + i), +# 'controller' +# ) + +DRIVE_BOX = BulkNumbers( + (lambda i: (1, 3, 6, 1, 4, 1, 232, 3, 2, 5, 1, 1, 63) + i), + 'box' +) + +DRIVE_BAY = BulkNumbers( + (lambda i: (1, 3, 6, 1, 4, 1, 232, 3, 2, 5, 1, 1, 5) + i), + 'bay' +) + +DRIVE_VENDOR = BulkStrings( + (lambda i: (1, 3, 6, 1, 4, 1, 232, 3, 2, 5, 1, 1, 3) + i), + 'vendor', +) + +# this may be slightly redundant +DRIVE_LOCATION = BulkStrings( + (lambda i: (1, 3, 6, 1, 4, 1, 232, 3, 2, 5, 1, 1, 64) + i), + 'location', +) + +DRIVE_SERIAL = BulkStrings( + (lambda i: (1, 3, 6, 1, 4, 1, 232, 3, 2, 5, 1, 1, 51) + i), + 'serial', +) + +DRIVE_SIZE = BulkNumbers( + (lambda i: (1, 3, 6, 1, 4, 1, 232, 3, 2, 5, 1, 1, 45) + i), + 'size', +) + +DRIVE_LINK_RATE = BulkEnums( + (lambda i: (1, 3, 6, 1, 4, 1, 232, 3, 2, 5, 1, 1, 65) + i), + 'link_rate', + { + 1: 'other', + 2: '1.5Gbps', + 3: '3.0Gbps', + 4: '6.0Gbps', + 5: '12.0Gbps', + } +) + +DRIVE_TEMP = BulkNumbers( + (lambda i: (1, 3, 6, 1, 4, 1, 232, 3, 2, 5, 1, 1, 70) + i), + 'temperature' +) + +DRIVE_TEMP_THRESHOLD = BulkNumbers( + (lambda i: (1, 3, 6, 1, 4, 1, 232, 3, 2, 5, 1, 1, 71) + i), + 'temperature_threshold' +) + +DRIVE_TEMP_MAX = BulkNumbers( + (lambda i: (1, 3, 6, 1, 4, 1, 232, 3, 2, 5, 1, 1, 72) + i), + 'temperature_maximum' +) + +DRIVE_STATUS = BulkEnums( + (lambda i: (1, 3, 6, 1, 4, 1, 232, 3, 2, 5, 1, 1, 6) + i), + 'status', + { + 1: 'Other', + 2: 'Ok', + 3: 'Failed', + 4: 'Predictive Failure', + 5: 'Erasing', + 6: 'Erase Done', + 7: 'Erase Queued', + 8: 'SSD Wear Out', + 9: 'Not Authenticated', + } +) + +DRIVE_CONDITION = BulkEnums( + (lambda i: (1, 3, 6, 1, 4, 1, 232, 3, 2, 5, 1, 1, 37) + i), + 'condition', + { + 1: 'other', + 2: 'ok', + 3: 'degraded', + 4: 'failed', + } +) + +DRIVE_REFERENCE_TIME = BulkNumbers( + (lambda i: (1, 3, 6, 1, 4, 1, 232, 3, 2, 5, 1, 1, 9) + i), + 'reference_time' +) diff --git a/targets/fan.py b/targets/fan.py new file mode 100644 index 0000000..fd5aaaa --- /dev/null +++ b/targets/fan.py @@ -0,0 +1,78 @@ +from snmp_groups import BulkEnums + +FAN_INDEX = '1.3.6.1.4.1.232.6.2.6.7.1.2.0' + +FAN_LOCALE = BulkEnums( + (lambda i: '1.3.6.1.4.1.232.6.2.6.7.1.3.0.%i' % i), + 'locale', + { + 1: 'other', + 2: 'unknown', + 3: 'system', + 4: 'systemBoard', + 5: 'ioBoard', + 6: 'cpu', + 7: 'memory', + 8: 'storage', + 9: 'removable media', + 10: 'power supply', + 11: 'ambent', + 12: 'chassis', + 13: 'bridge card', + 14: 'management board', + 15: 'backplane', + 16: 'network slot', + 17: 'blade slot', + 18: 'virtual', + } +) + +FAN_PRESENT = BulkEnums( + (lambda i: '1.3.6.1.4.1.232.6.2.6.7.1.4.0.%i' % i), + 'presence', + { + 1: 'other', + 2: 'absent', + 3: 'present', + } +) + +FAN_PRESENCE_TEST = BulkEnums( + (lambda i: '1.3.6.1.4.1.232.6.2.6.7.1.5.0.%i' % i), + 'presence_test', + { + 1: 'other', + 2: 'tachOutput', + 3: 'spinDetect', + } +) + +FAN_SPEED = BulkEnums( + (lambda i: '1.3.6.1.4.1.232.6.2.6.7.1.6.0.%i' % i), + 'speed', + { + 1: 'other', + 2: 'normal', + 3: 'high', + } +) + +FAN_CONDITION = BulkEnums( + (lambda i: '1.3.6.1.4.1.232.6.2.6.7.1.6.0.%i' % i), + 'condition', + { + 1: 'other', + 2: 'normal', + 3: 'degraded', + 4: 'failed', + } +) + +# for debugging +FAN_VALUES = [ + FAN_LOCALE, + FAN_PRESENT, + FAN_PRESENCE_TEST, + FAN_SPEED, + FAN_CONDITION, +] diff --git a/targets/logical_drive.py b/targets/logical_drive.py new file mode 100644 index 0000000..34bb520 --- /dev/null +++ b/targets/logical_drive.py @@ -0,0 +1,3 @@ +# I do not use HP's raid utility, so I cannot test this + +LOGICAL_DRIVES_INDEX = '1.3.6.1.4.1.232.3.2.3.1.1.2.0' diff --git a/targets/memory.py b/targets/memory.py new file mode 100644 index 0000000..e2688a2 --- /dev/null +++ b/targets/memory.py @@ -0,0 +1,77 @@ +from snmp_groups import BulkEnums, BulkNumbers, BulkStrings + +MEMORY_INDEX = '1.3.6.1.4.1.232.6.2.14.13.1.1' + +MEMORY_LOCATION = BulkStrings( + (lambda i: '1.3.6.1.4.1.232.6.2.14.13.1.13.%i' % i), + 'location', +) + +MEMORY_MANUFACTURER = BulkStrings( + (lambda i: '1.3.6.1.4.1.232.6.2.14.13.1.9.%i' % i), + 'manufacturer', +) + +MEMORY_PART_NUMBER = BulkStrings( + (lambda i: '1.3.6.1.4.1.232.6.2.14.13.1.10.%i' % i), + 'part_number', +) + +MEMORY_SIZE = BulkNumbers( + (lambda i: '1.3.6.1.4.1.232.6.2.14.13.1.6.%i' % i), + 'size', +) + +# this is an enum, but I don't know the mappings +# I also don't have HP smart ram for testing +# MEMORY_TECHNOLOGY = BulkNumbers( +# (lambda i: '1.3.6.1.4.1.232.6.2.14.13.1.8.%i' % i), +# 'technology', +# ) + +# this is another enum, but I don't know the mappings +# MEMORY_TYPE = BulkNumbers( +# (lambda i: '1.3.6.1.4.1.232.6.2.14.13.1.7.%i' % i), +# 'type', +# ) + +MEMORY_STATUS = BulkEnums( + (lambda i: '1.3.6.1.4.1.232.6.2.14.13.1.19.%i' % i), + 'status', + { + 1: 'other', + 2: 'notPresent', + 3: 'present', + 4: 'good', + 5: 'add', + 6: 'upgrade', + 7: 'missing', + 8: 'doesNotMatch', + 9: 'notSupported', + 10: 'badConfig', + 11: 'degraded', + 12: 'spare', + 13: 'partial', + } +) + +MEMORY_CONDITION = BulkEnums( + (lambda i: '1.3.6.1.4.1.232.6.2.14.13.1.20.%i' % i), + 'condition', + { + 1: 'other', + 2: 'ok', + 3: 'degraded', + 4: 'degradedModuleIndexUnknown', + } +) + +# for debugging +MEMORY_VALUES = [ + MEMORY_LOCATION, + MEMORY_MANUFACTURER, + MEMORY_PART_NUMBER, + MEMORY_SIZE, + MEMORY_STATUS, + MEMORY_CONDITION +] diff --git a/targets/power.py b/targets/power.py new file mode 100644 index 0000000..c3ee80e --- /dev/null +++ b/targets/power.py @@ -0,0 +1,7 @@ + +POWER_METER_READING = '1.3.6.1.4.1.232.6.2.15.3.0' + +# I have no idea what these values mean (or map to). any help would be appreciated +# POWER_METER_SUPPORT = '1.3.6.1.4.1.232.6.2.15.1' +# POWER_METER_STATUS = '1.3.6.1.4.1.232.6.2.15.2' +# POWER_METER_PREVIOUS_READING = '1.3.6.1.4.1.232.6.2.15.4' diff --git a/targets/temp.py b/targets/temp.py new file mode 100644 index 0000000..1358e16 --- /dev/null +++ b/targets/temp.py @@ -0,0 +1,64 @@ +from snmp_groups import BulkEnums, BulkNumbers + +TEMP_INDEX = '1.3.6.1.4.1.232.6.2.6.8.1.2.0' + +TEMP_CELSIUS = BulkNumbers( + (lambda i: '1.3.6.1.4.1.232.6.2.6.8.1.4.0.%i' % i), + 'celsius', +) + +TEMP_THRESHOLD = BulkNumbers( + (lambda i: '1.3.6.1.4.1.232.6.2.6.8.1.5.0.%i' % i), + 'threshold', +) + +TEMP_SENSOR_LOCALE = BulkEnums( + (lambda i: '1.3.6.1.4.1.232.6.2.6.8.1.3.0.%i' % i), + 'sensor_locale', + { + 1: 'other', + 2: 'unknown', + 3: 'system', + 4: 'systemBoard', + 5: 'ioBoard', + 6: 'cpu', + 7: 'memory', + 8: 'storage', + 9: 'removable media', + 10: 'power supply', + 11: 'ambent', + 12: 'chassis', + 13: 'bridge card', + } +) + +TEMP_THRESHOLD_TYPE = BulkEnums( + (lambda i: '1.3.6.1.4.1.232.6.2.6.8.1.7.0.%i' % i), + 'threshold_type', + { + 1: 'other', + 5: 'blowout', + 9: 'caution', + 15: 'critical', + 16: 'noreaction', + } +) + +TEMP_CONDITION = BulkEnums( + (lambda i: '1.3.6.1.4.1.232.6.2.6.8.1.6.0.%i' % i), + 'condition', + { + 1: 'other', + 2: 'normal', + 3: 'high', + } +) + +# for debugging +TEMP_VALUES = [ + TEMP_SENSOR_LOCALE, + TEMP_THRESHOLD_TYPE, + TEMP_CONDITION, + TEMP_CELSIUS, + TEMP_THRESHOLD, +]