Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
themylogin committed Feb 20, 2024
1 parent 61917d9 commit d17c8ce
Show file tree
Hide file tree
Showing 14 changed files with 650 additions and 0 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/flake8.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: flake8

on:
pull_request:
types:
- 'synchronize'
- 'opened'

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v1
with:
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8
- name: Run flake8
run: flake8 .
26 changes: 26 additions & 0 deletions .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: unit_tests

on:
pull_request:
types:
- 'synchronize'
- 'opened'

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v1
with:
python-version: 3.9
- name: Install pytest
run: |
python -m pip install --upgrade pip
pip install pytest
- name: Install package
run: python setup.py install
- name: Run tests
run: pytest -v tests
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.idea/*
build/*
dist/*
ixhardware.egg*
__pycache__
*.pyc
5 changes: 5 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ixhardware (1.0-0~truenas+1) electriceel-truenas-unstable; urgency=medium

* Initial release

-- Vladimir Vinogradenko <[email protected]> Tue, 20 Feb 2024 12:33:00 +0200
19 changes: 19 additions & 0 deletions debian/control
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Source: ixhardware
Section: contrib/python
Priority: optional
Maintainer: Vladimir Vinogradenko <[email protected]>
Build-Depends: debhelper-compat (= 12),
dh-python,
python3-dev,
python3-setuptools
Standards-Version: 4.4.1
Homepage: https://github.com/truenas/ixhardware
Testsuite: autopkgtest-pkg-python

Package: python3-ixhardware
Architecture: any
Depends: ${shlibs:Depends},
${misc:Depends},
${python3:Depends}
Description: Detect iXsystems hardware.
This package detects iXsystems hardware.
9 changes: 9 additions & 0 deletions debian/rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/make -f
export DH_VERBOSE = 1

export PYBUILD_NAME=ixhardware

%:
dh $@ --with python3 --buildsystem=pybuild

override_dh_auto_test:
1 change: 1 addition & 0 deletions debian/source/format
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.0 (quilt)
1 change: 1 addition & 0 deletions debian/source/options
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
extend-diff-ignore = "^[^/]*[.]egg-info/"
4 changes: 4 additions & 0 deletions ixhardware/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .chassis import PLATFORM_PREFIXES, TRUENAS_UNKNOWN, get_chassis_hardware
from .dmi import DMIInfo, DMIParser, parse_dmi

__all__ = ["PLATFORM_PREFIXES", "TRUENAS_UNKNOWN", "get_chassis_hardware", "DMIInfo", "DMIParser", "parse_dmi"]
32 changes: 32 additions & 0 deletions ixhardware/chassis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from .dmi import DMIInfo

__all__ = ["PLATFORM_PREFIXES", "TRUENAS_UNKNOWN", "get_chassis_hardware"]


# We tag SMBIOS with relevant strings for each platform
# before we ship to customer. These are the various prefixes
# that represent each hardware platform.
# ("TRUENAS-X10", "TRUENAS-M50", "TRUENAS-MINI-X+", "FREENAS-MINI-X", etc)
PLATFORM_PREFIXES = (
"TRUENAS-Z", # z-series
"TRUENAS-X", # x-series
"TRUENAS-M", # m-series AND current mini platforms
"TRUENAS-F", # f-series (F60, F100, F130)
"TRUENAS-H", # h-series (H10, H20)
"TRUENAS-R", # freenas certified replacement
"FREENAS-MINI", # minis tagged with legacy information
)
TRUENAS_UNKNOWN = "TRUENAS-UNKNOWN"


def get_chassis_hardware(dmi: DMIInfo):
if dmi.system_product_name.startswith(PLATFORM_PREFIXES):
return dmi.system_product_name

if dmi.baseboard_product_name == "iXsystems TrueNAS X10":
# could be that production didn"t burn in the correct x-series
# model information so let"s check the motherboard model as a
# last resort
return "TRUENAS-X"

return TRUENAS_UNKNOWN
99 changes: 99 additions & 0 deletions ixhardware/dmi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from dataclasses import dataclass
from datetime import date, datetime
import logging
import subprocess
from typing import Optional

logger = logging.getLogger(__name__)

__all__ = ["DMIInfo", "DMIParser", "parse_dmi"]


@dataclass
class DMIInfo:
bios_release_date: Optional[date] = None
ecc_memory: bool = False
baseboard_manufacturer: str = ""
baseboard_product_name: str = ""
system_manufacturer: str = ""
system_product_name: str = ""
system_serial_number: str = ""
system_version: str = ""
has_ipmi: bool = False


class DMIParser:
command = ["dmidecode", "-t", "0,1,2,16,38"]

def parse(self, output: str) -> DMIInfo:
return self._parse_dmi(output.splitlines())

def _parse_dmi(self, lines: [str]) -> DMIInfo:
info = DMIInfo()

_type = None
for line in lines:
if "DMI type 0," in line:
_type = "RELEASE_DATE"
if "DMI type 1," in line:
_type = "SYSINFO"
if "DMI type 2," in line:
_type = "BBINFO"
if "DMI type 38," in line:
_type = "IPMI"

if not line or ":" not in line:
# "sections" are separated by the category name and then
# a newline so ignore those lines
continue

sect, val = [i.strip() for i in line.split(":", 1)]
if sect == "Release Date":
info.bios_release_date = self._parse_bios_release_date(val)
elif sect == "Manufacturer":
if _type == "SYSINFO":
info.system_manufacturer = val
else:
info.baseboard_manufacturer = val
elif sect == "Product Name":
if _type == "SYSINFO":
info.system_product_name = val
else:
info.baseboard_product_name = val
elif sect == "Serial Number" and _type == "SYSINFO":
info.system_serial_number = val
elif sect == "Version" and _type == "SYSINFO":
info.system_version = val
elif sect == "I2C Slave Address":
info.has_ipmi = True
elif sect == "Error Correction Type":
info.ecc_memory = "ECC" in val
# we break the for loop here since "16" is the last section
# that gets processed
break

return info

def _parse_bios_release_date(self, string):
parts = string.strip().split("/")
if len(parts) < 3:
# Don"t know what the BIOS is reporting so assume it"s invalid
return

# Give the best effort to convert to a date object.
# Searched hundreds of debugs that have been provided
# via end-users and 99% all reported the same date
# format, however, there are a couple that had a
# 2 digit year instead of a 4 digit year...gross
formatter = "%m/%d/%Y" if len(parts[-1]) == 4 else "%m/%d/%y"
try:
return datetime.strptime(string, formatter).date()
except Exception as e:
logger.warning(f"Failed to format BIOS release date to datetime object: {e!r}")


def parse_dmi() -> DMIInfo:
return DMIParser().parse(
subprocess.run(DMIParser.command, check=False, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
encoding="utf-8", errors="ignore").stdout
)
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[flake8]
max-line-length=120
13 changes: 13 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from distutils.core import setup
from setuptools import find_packages


setup(
name="ixhardware",
description="Detect iXsystems hardware",
version="1.0",
include_package_data=True,
packages=find_packages(),
license="GNU3",
platforms="any",
)
Loading

0 comments on commit d17c8ce

Please sign in to comment.