Compare commits

..

No commits in common. "main" and "v0.6.0" have entirely different histories.
main ... v0.6.0

47 changed files with 1242 additions and 6162 deletions

1
.envrc
View file

@ -1 +0,0 @@
use nix

View file

@ -1,25 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Configuration file**
Paste here your netbox agent configuration file
**Environment:**
- OS: [e.g. Ubuntu 18.04]
- Netbox agent version [e.g. master, v0.6.0]
**Additional context**
Add any other context about the problem here.

3
.gitignore vendored
View file

@ -181,6 +181,3 @@ dmypy.json
# End of https://www.gitignore.io/api/emacs,python
netbox-docker
/.vscode
.direnv
.pre-commit-config.yaml

View file

@ -1 +0,0 @@
include requirements.txt

162
README.md
View file

@ -13,15 +13,14 @@ The goal is to generate an existing infrastructure on Netbox and have the abilit
* Generic ability to guess datacenters and rack location through drivers (`cmd` and `file` and custom ones)
* Update existing `Device` and `Interface`
* Handle blade moving (new slot, new chassis)
* Handle blade GPU expansions
* Automatic cabling (server's interface to switch's interface) using lldp
* Local inventory using `Inventory Item` for CPU, GPU, RAM, RAID cards, physical disks (behind raid cards)
* Local inventory using `Inventory Item` for CPU, RAM, RAID cards, physical disks (behind raid cards)
* PSUs creation and power consumption reporting (based on vendor's tools)
# Requirements
- Netbox >= 3.7
- Python >= 3.7
- Netbox >= 2.6
- Python >= 3.4
- [pynetbox](https://github.com/digitalocean/pynetbox/)
- [python3-netaddr](https://github.com/drkjam/netaddr)
- [python3-netifaces](https://github.com/al45tair/netifaces)
@ -33,112 +32,41 @@ The goal is to generate an existing infrastructure on Netbox and have the abilit
- lldpd
- lshw
## Inventory requirement
- hpassacli
- storcli
- omreport
# Known limitations
# Installation
```
# pip3 install netbox-agent
```
# Usage
The agent can be run from a shell and get its configuration from either the configuration file or environment variables.
Configuration values are overridden based on the following precedence: command line arguments (might include config file) > environment variables > default config file > defaults.
```
# netbox_agent -c /etc/netbox_agent.yaml --register
INFO:root:Creating chassis blade (serial: QTFCQ574502EF)
INFO:root:Creating blade (serial: QTFCQ574502D2) myserver on chassis QTFCQ574502EF
INFO:root:Setting device (QTFCQ574502D2) new slot on Slot 9 (Chassis QTFCQ574502EF)..
INFO:root:Interface a8:1e:84:f2:9e:6a not found, creating..
INFO:root:Creating NIC enp1s0f1 (a8:1e:84:f2:9e:6a) on myserver
INFO:root:Interface 02:42:7a:89:cf:a4 not found, creating..
INFO:root:Creating NIC br-07ea1e4a2f0e (02:42:7a:89:cf:a4) on myserver
INFO:root:Create new IP 172.19.0.1/16 on br-07ea1e4a2f0e
INFO:root:Interface a8:1e:84:f2:9e:69 not found, creating..
INFO:root:Creating NIC enp1s0f0 (a8:1e:84:f2:9e:69) on myserver
INFO:root:Create new IP 42.42.42.42/24 on enp1s0f0
INFO:root:Create new IP fe80::aa1e:84ff:fef2:9e69/64 on enp1s0f0
INFO:root:Interface a8:1e:84:cd:9d:d6 not found, creating..
INFO:root:Creating NIC IPMI (a8:1e:84:cd:9d:d6) on myserver
INFO:root:Create new IP 10.191.122.10/24 on IPMI
```
If you need, you can update only specific informations like:
* Network
* Inventory
* Location
* PSUs
```
# ip a add 42.42.42.43/24 dev enp1s0f1
# netbox_agent -c /etc/netbox_agent.yaml --update-network
INFO:root:Create new IP 42.42.42.43/24 on enp1s0f1
# netbox_agent --update-inventory
INFO:root:Creating Disk Samsung SSD 850 S2RBNX0K101698D
```
* The project is only compatible with Linux.
Since it uses `ethtool` and parses `/sys/` directory, it's not compatible with *BSD distributions.
* Netbox `>=2.6.0,<=2.6.2` has a caching problem ; if the cache lifetime is too high, the script can get stale data after modification.
We advise to set `CACHE_TIME` to `0`.
# Configuration
```
# Netbox configuration
netbox:
url: 'http://netbox.internal.company.com'
token: supersecrettoken
# uncomment to disable ssl verification
# ssl_verify: false
# uncomment to use the system's CA certificates
# ssl_ca_certs_file: /etc/ssl/certs/ca-certificates.crt
# Network configuration
network:
# Regex to ignore interfaces
ignore_interfaces: "(dummy.*|docker.*)"
# Regex to ignore IP addresses
ignore_ips: (127\.0\.0\..*)
# enable auto-cabling by parsing LLDP answers
# enable auto-cabling
lldp: true
#
# You can use these to change the Netbox roles.
# These are the defaults.
#
#device:
# chassis_role: "Server Chassis"
# blade_role: "Blade"
# server_role: "Server"
# tags: server, blade, ,just a comma,delimited,list
# custom_fields: field1=value1,field2=value2#
#
# Can use this to set the tenant
#
#tenant:
# driver: "file:/tmp/tenant"
# regex: "(.*)"
## Enable virtual machine support
# virtual:
# # not mandatory, can be guessed
# enabled: True
# # see https://netbox.company.com/virtualization/clusters/
# cluster_name: my_vm_cluster
# Enable datacenter location feature in Netbox
datacenter_location:
driver: "cmd:cat /etc/qualification | tr [A-Z] [a-z]"
regex: "datacenter: (?P<datacenter>[A-Za-z0-9]+)"
driver: "cmd:cat /etc/qualification | tr [a-z] [A-Z]"
regex: "DATACENTER: (?P<datacenter>[A-Za-z0-9]+)"
# driver: 'cmd:lldpctl'
# regex: 'SysName: .*\.([A-Za-z0-9]+)'
#
# driver: "file:/tmp/datacenter"
# regex: "(.*)"
# Enable rack location feature in Netbox
rack_location:
# driver: 'cmd:lldpctl'
# match SysName: sw-dist-a1.dc42
@ -147,7 +75,6 @@ rack_location:
# driver: "file:/tmp/datacenter"
# regex: "(.*)"
# Enable local inventory reporting
inventory: true
```
@ -162,36 +89,6 @@ The `get_blade_slot` method return the name of the `Device Bay`.
Certain vendors don't report the blade slot in `dmidecode`, so we can use the `slot_location` regex feature of the configuration file.
Some blade servers can be equipped with additional hardware using expansion blades, next to the processing blade, such as GPU expansion, or drives bay expansion. By default, the hardware from the expnasion is associated with the blade server itself, but it's possible to register the expansion as its own device using the `--expansion-as-device` command line parameter, or by setting `expansion_as_device` to `true` in the configuration file.
## Drives attributes processing
It is possible to process drives extended attributes such as the drive's physical or logical identifier, logical drive RAID type, size, consistency and so on.
Those attributes as set as `custom_fields` in Netbox, and need to be registered properly before being able to specify them during the inventory phase.
As the custom fields have to be created prior being able to register the disks extended attributes, this feature is only activated using the `--process-virtual-drives` command line parameter, or by setting `process_virtual_drives` to `true` in the configuration file.
The custom fields to create as `DCIM > inventory item` `Text` are described below.
```
NAME LABEL DESCRIPTION
mount_point Mount point Device mount point(s)
pd_identifier Physical disk identifier Physical disk identifier in the RAID controller
vd_array Virtual drive array Virtual drive array the disk is member of
vd_consistency Virtual drive consistency Virtual disk array consistency
vd_device Virtual drive device Virtual drive system device
vd_raid_type Virtual drive RAID Virtual drive array RAID type
vd_size Virtual drive size Virtual drive array size
```
In the current implementation, the disks attributes ore not updated: if a disk with the correct serial number is found, it's sufficient to consider it as up to date.
To force the reprocessing of the disks extended attributes, the `--force-disk-refresh` command line option can be used: it removes all existing disks to before populating them with the correct parsing. Unless this option is specified, the extended attributes won't be modified unless a disk is replaced.
It is possible to dump the physical/virtual disks map on the filesystem under the JSON notation to ease or automate disks management. The file path has to be provided using the `--dump-disks-map` command line parameter.
## Anycast IP
The default behavior of the agent is to assign an interface to an IP.
@ -211,7 +108,7 @@ Tested on:
* AWS
* GCP
## [Dell Inc.](https://github.com/Solvik/netbox-agent/blob/master/netbox_agent/vendors/dell.py)
## Dell Inc.
### Blades
@ -227,7 +124,7 @@ Tested on:
* DSS7500
## [HP / HPE](https://github.com/Solvik/netbox-agent/blob/master/netbox_agent/vendors/hp.py)
## HP / HPE
### Blades
@ -235,8 +132,6 @@ Tested on:
* HP ProLiant BL460c Gen8
* HP ProLiant BL460c Gen9
* HP ProLiant BL460c Gen10
* HP ProLiant BL460c Gen10 Graphics Exp its expansion HP ProLiant BL460c Graphics Expansion Blade
* HP Moonshot 1500 Enclosure (your `DeviceType` should have slots batch create with `Bay c[1-45n1]`) with HP ProLiant m750, m710x, m510 Server Cartridge
### Pizzas
@ -245,18 +140,18 @@ Tested on:
* ProLiant SL4540 Gen9
* ProLiant XL450 Gen10
## [Supermicro](https://github.com/Solvik/netbox-agent/blob/master/netbox_agent/vendors/supermicro.py)
## Supermicro
### Blades
* SBI-* and SBA-* should be supported, but I need dmidecode output example to support automatic blade location
Feel free to send me a dmidecode output for Supermicro's blade!
### Pizzas
* SSG-6028R
* SYS-6018R
## [QCT](https://github.com/Solvik/netbox-agent/blob/master/netbox_agent/vendors/qct.py)
## QCT
### Blades
@ -266,27 +161,6 @@ Tested on:
* Nothing ATM, feel free to send me a dmidecode or make a PR!
# Known limitations
# TODO
* The project is only compatible with Linux.
Since it uses `ethtool` and parses `/sys/` directory, it's not compatible with *BSD distributions.
* Netbox `>=2.6.0,<=2.6.2` has a caching problem ; if the cache lifetime is too high, the script can get stale data after modification.
We advise to set `CACHE_TIME` to `0`.
# Developing
If you want to run the agent while adding features or just for debugging purposes
```
# git clone https://github.com/Solvik/netbox-agent.git
# cd netbox-agent
# python3 -m netbox_agent.cli --register
```
On a personal note, I use the docker image from [netbox-community/netbox-docker](https://github.com/netbox-community/netbox-docker)
```
# git clone https://github.com/netbox-community/netbox-docker
# cd netbox-docker
# docker-compose pull
# docker-compose up
```
- [ ] `CustomFields` support with firmware versions for Device (BIOS), RAID Cards and disks

View file

@ -1,55 +0,0 @@
{
sources ? import ./npins,
pkgs ? import sources.nixpkgs { },
}:
let
checks = (import sources.git-hooks).run {
src = ./.;
hooks =
{
commitizen.enable = true;
}
// (pkgs.lib.genAttrs
[
"black"
"isort"
"ruff"
]
(hook: {
enable = true;
stages = [ "pre-push" ];
})
);
};
python3 = pkgs.python3.override {
packageOverrides = self: _: {
netifaces2 = self.callPackage ./nix/netifaces2.nix { };
};
};
in
{
devShell = pkgs.mkShell {
name = "netbox-agent.dev";
packages = [
(python3.withPackages (ps: [
ps.pynetbox
ps.netaddr
ps.netifaces2
ps.pyyaml
ps.jsonargparse
ps.python-slugify
ps.packaging
ps.distro
]))
] ++ checks.enabledPackages;
shellHook = ''
${checks.shellHook}
'';
};
}

View file

@ -1,8 +1,6 @@
netbox:
url: 'http://netbox.internal.company.com'
token: supersecrettoken
# uncomment to disable ssl verification
# ssl_verify: false
network:
ignore_interfaces: "(dummy.*|docker.*)"
@ -10,26 +8,9 @@ network:
# enable auto-cabling
lldp: true
#
# You can use these to change the roles.
#
#device:
# chassis_role: "Server Chassis"
# blade_role: "Blade"
# server_role: "Server"
# tags: server, blade, ,just a comma,delimited,list
# custom_fields: field1=value1,field2=value2
#
#
# Use this to set the tenant
#
#tenant:
# driver: "file:/tmp/tenant"
# regex: "(.*)"
datacenter_location:
driver: "cmd:cat /etc/qualification | tr [A-Z] [a-z]"
regex: "datacenter: (?P<datacenter>[A-Za-z0-9]+)"
driver: "cmd:cat /etc/qualification | tr [a-z] [A-Z]"
regex: "DATACENTER: (?P<datacenter>[A-Za-z0-9]+)"
# driver: 'cmd:lldpctl'
# regex: 'SysName: .*\.([A-Za-z0-9]+)'
#

View file

@ -1,7 +1,6 @@
from importlib.metadata import PackageNotFoundError
from importlib.metadata import version as _get_version
from pkg_resources import DistributionNotFound, get_distribution
try:
__version__ = _get_version(__name__)
except PackageNotFoundError:
__version__ = get_distribution(__name__).version
except DistributionNotFound:
pass

View file

@ -1,8 +1,5 @@
from packaging import version
import netbox_agent.dmidecode as dmidecode
from netbox_agent.config import config
from netbox_agent.config import netbox_instance as nb
from netbox_agent.logging import logging # NOQA
from netbox_agent.vendors.dell import DellHost
from netbox_agent.vendors.generic import GenericHost
@ -12,12 +9,12 @@ from netbox_agent.vendors.supermicro import SupermicroHost
from netbox_agent.virtualmachine import VirtualMachine, is_vm
MANUFACTURERS = {
"Dell Inc.": DellHost,
"HP": HPHost,
"HPE": HPHost,
"Supermicro": SupermicroHost,
"Quanta Cloud Technology Inc.": QCTHost,
"Generic": GenericHost,
'Dell Inc.': DellHost,
'HP': HPHost,
'HPE': HPHost,
'Supermicro': SupermicroHost,
'Quanta Cloud Technology Inc.': QCTHost,
'Generic': GenericHost,
}
@ -26,38 +23,26 @@ def run(config):
if config.virtual.enabled or is_vm(dmi):
if not config.virtual.cluster_name:
raise Exception(
"virtual.cluster_name parameter is mandatory because it's a VM"
)
raise Exception('virtual.cluster_name parameter is mandatory because it\'s a VM')
server = VirtualMachine(dmi=dmi)
else:
manufacturer = dmidecode.get_by_type(dmi, "Chassis")[0].get("Manufacturer")
manufacturer = dmidecode.get_by_type(dmi, 'Chassis')[0].get('Manufacturer')
try:
server = MANUFACTURERS[manufacturer](dmi=dmi)
except KeyError:
server = GenericHost(dmi=dmi)
if version.parse(nb.version) < version.parse("3.7"):
print("netbox-agent is not compatible with Netbox prior to version 3.7")
return False
if (
config.register
or config.update_all
or config.update_network
or config.update_location
or config.update_inventory
or config.update_psu
):
server.netbox_create_or_update(config)
if config.debug:
server.print_debug()
if config.register or config.update_all or config.update_network or config.update_location or \
config.update_inventory or config.update_psu:
server.netbox_create_or_update(config)
return True
def main():
return 0 if run(config) else 1
return run(config)
if __name__ == "__main__":
if __name__ == '__main__':
main()

View file

@ -3,184 +3,71 @@ import sys
import jsonargparse
import pynetbox
import requests
import urllib3
def get_config():
p = jsonargparse.ArgumentParser(
default_config_files=[
"/etc/netbox_agent.yaml",
"~/.config/netbox_agent.yaml",
"~/.netbox_agent.yaml",
'/etc/netbox_agent.yaml',
'~/.config/netbox_agent.yaml',
'~/.netbox_agent.yaml',
],
prog="netbox_agent",
prog='netbox_agent',
description="Netbox agent to run on your infrastructure's servers",
env_prefix="NETBOX_AGENT_",
default_env=True,
env_prefix='NETBOX_AGENT_',
default_env=True
)
p.add_argument("-c", "--config", action=jsonargparse.ActionConfigFile)
p.add_argument('-c', '--config', action=jsonargparse.ActionConfigFile)
p.add_argument(
"-r", "--register", action="store_true", help="Register server to Netbox"
)
p.add_argument(
"-u", "--update-all", action="store_true", help="Update all infos in Netbox"
)
p.add_argument("-d", "--debug", action="store_true", help="Print debug infos")
p.add_argument("--update-network", action="store_true", help="Update network")
p.add_argument("--update-inventory", action="store_true", help="Update inventory")
p.add_argument("--update-location", action="store_true", help="Update location")
p.add_argument("--update-psu", action="store_true", help="Update PSU")
p.add_argument(
"--purge-old-devices",
action="store_true",
help="Purge existing (old ?) devices having same name but different serial",
)
p.add_argument(
"--expansion-as-device",
action="store_true",
help="Manage blade expansions as external devices",
)
p.add_argument('-r', '--register', action='store_true', help='Register server to Netbox')
p.add_argument('-u', '--update-all', action='store_true', help='Update all infos in Netbox')
p.add_argument('-d', '--debug', action='store_true', help='Print debug infos')
p.add_argument('--update-network', action='store_true', help='Update network')
p.add_argument('--update-inventory', action='store_true', help='Update inventory')
p.add_argument('--update-location', action='store_true', help='Update location')
p.add_argument('--update-psu', action='store_true', help='Update PSU')
p.add_argument("--log_level", default="debug")
p.add_argument("--netbox.ssl_ca_certs_file", help="SSL CA certificates file")
p.add_argument("--netbox.url", help="Netbox URL")
p.add_argument("--netbox.token", help="Netbox API Token")
p.add_argument(
"--netbox.ssl_verify",
default=True,
action="store_true",
help="Disable SSL verification",
)
p.add_argument(
"--virtual.enabled", action="store_true", help="Is a virtual machine or not"
)
p.add_argument("--virtual.cluster_name", help="Cluster name of VM")
p.add_argument(
"--hostname_cmd",
default=None,
help="Command to output hostname, used as Device's name in netbox",
)
p.add_argument(
"--device.platform",
default=None,
help="Override device platform. Here we use OS distribution.",
)
p.add_argument("--device.tags", default=r"", help="tags to use for a host")
p.add_argument(
"--preserve-tags",
action="store_true",
help="Append new unique tags, preserve those already present",
)
p.add_argument(
"--device.custom_fields",
default=r"",
help="custom_fields to use for a host, eg: field1=v1,field2=v2",
)
p.add_argument(
"--device.blade_role", default=r"Blade", help="role to use for a blade server"
)
p.add_argument(
"--device.chassis_role",
default=r"Server Chassis",
help="role to use for a chassis",
)
p.add_argument(
"--device.server_role", default=r"Server", help="role to use for a server"
)
p.add_argument("--tenant.driver", help="tenant driver, ie cmd, file")
p.add_argument("--tenant.driver_file", help="tenant driver custom driver file path")
p.add_argument("--tenant.regex", help="tenant regex to extract Netbox tenant slug")
p.add_argument(
"--datacenter_location.driver", help="Datacenter location driver, ie: cmd, file"
)
p.add_argument(
"--datacenter_location.driver_file",
help="Datacenter location custom driver file path",
)
p.add_argument(
"--datacenter_location.regex",
help="Datacenter location regex to extract Netbox DC slug",
)
p.add_argument("--rack_location.driver", help="Rack location driver, ie: cmd, file")
p.add_argument(
"--rack_location.driver_file", help="Rack location custom driver file path"
)
p.add_argument(
"--rack_location.regex", help="Rack location regex to extract Rack name"
)
p.add_argument("--slot_location.driver", help="Slot location driver, ie: cmd, file")
p.add_argument(
"--slot_location.driver_file", help="Slot location custom driver file path"
)
p.add_argument(
"--slot_location.regex", help="Slot location regex to extract slot name"
)
p.add_argument(
"--network.ignore_interfaces",
default=r"(dummy.*|docker.*)",
help="Regex to ignore interfaces",
)
p.add_argument(
"--network.ignore_ips",
default=r"^(127\.0\.0\..*|fe80.*|::1.*)",
help="Regex to ignore IPs",
)
p.add_argument(
"--network.ipmi", default=True, help="Enable gathering IPMI information"
)
p.add_argument(
"--network.lldp", help="Enable auto-cabling feature through LLDP infos"
)
p.add_argument(
"--inventory",
action="store_true",
help="Enable HW inventory (CPU, Memory, RAID Cards, Disks) feature",
)
p.add_argument(
"--process-virtual-drives",
action="store_true",
help="Process virtual drives information from RAID "
"controllers to fill disk custom_fields",
)
p.add_argument(
"--force-disk-refresh",
action="store_true",
help="Forces disks detection reprocessing",
)
p.add_argument(
"--dump-disks-map", help="File path to dump physical/virtual disks map"
)
p.add_argument('--log_level', default='debug')
p.add_argument('--netbox.url', help='Netbox URL')
p.add_argument('--netbox.token', help='Netbox API Token')
p.add_argument('--virtual.enabled', action='store_true', help='Is a virtual machine or not')
p.add_argument('--virtual.cluster_name', help='Cluster name of VM')
p.add_argument('--hostname_cmd', default=None,
help="Command to output hostname, used as Device's name in netbox")
p.add_argument('--datacenter_location.driver',
help='Datacenter location driver, ie: cmd, file')
p.add_argument('--datacenter_location.driver_file',
help='Datacenter location custom driver file path')
p.add_argument('--datacenter_location.regex',
help='Datacenter location regex to extract Netbox DC slug')
p.add_argument('--rack_location.driver', help='Rack location driver, ie: cmd, file')
p.add_argument('--rack_location.driver_file', help='Rack location custom driver file path')
p.add_argument('--rack_location.regex', help='Rack location regex to extract Rack name')
p.add_argument('--slot_location.driver', help='Slot location driver, ie: cmd, file')
p.add_argument('--slot_location.driver_file', help='Slot location custom driver file path')
p.add_argument('--slot_location.regex', help='Slot location regex to extract slot name')
p.add_argument('--network.ignore_interfaces', default=r'(dummy.*|docker.*)',
help='Regex to ignore interfaces')
p.add_argument('--network.ignore_ips', default=r'^(127\.0\.0\..*|fe80.*|::1.*)',
help='Regex to ignore IPs')
p.add_argument('--network.lldp', help='Enable auto-cabling feature through LLDP infos')
p.add_argument('--inventory', action='store_true',
help='Enable HW inventory (CPU, Memory, RAID Cards, Disks) feature')
options = p.parse_args()
return options
config = get_config()
def get_netbox_instance():
config = get_config()
if config.netbox.url is None or config.netbox.token is None:
logging.error("Netbox URL and token are mandatory")
logging.error('Netbox URL and token are mandatory')
sys.exit(1)
nb = pynetbox.api(
return pynetbox.api(
url=get_config().netbox.url,
token=get_config().netbox.token,
)
ca_certs_file = config.netbox.ssl_ca_certs_file
if ca_certs_file is not None:
session = requests.Session()
session.verify = ca_certs_file
nb.http_session = session
elif config.netbox.ssl_verify is False:
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
session = requests.Session()
session.verify = False
nb.http_session = session
return nb
config = get_config()
netbox_instance = get_netbox_instance()

View file

@ -5,57 +5,55 @@ import sys
from netbox_agent.misc import is_tool
_handle_re = _re.compile(
"^Handle\\s+(.+),\\s+DMI\\s+type\\s+(\\d+),\\s+(\\d+)\\s+bytes$"
)
_in_block_re = _re.compile("^\\t\\t(.+)$")
_record_re = _re.compile("\\t(.+):\\s+(.+)$")
_record2_re = _re.compile("\\t(.+):$")
_handle_re = _re.compile('^Handle\\s+(.+),\\s+DMI\\s+type\\s+(\\d+),\\s+(\\d+)\\s+bytes$')
_in_block_re = _re.compile('^\\t\\t(.+)$')
_record_re = _re.compile('\\t(.+):\\s+(.+)$')
_record2_re = _re.compile('\\t(.+):$')
_type2str = {
0: "BIOS",
1: "System",
2: "Baseboard",
3: "Chassis",
4: "Processor",
5: "Memory Controller",
6: "Memory Module",
7: "Cache",
8: "Port Connector",
9: "System Slots",
10: " On Board Devices",
11: " OEM Strings",
12: " System Configuration Options",
13: " BIOS Language",
14: " Group Associations",
15: " System Event Log",
16: " Physical Memory Array",
17: " Memory Device",
18: " 32-bit Memory Error",
19: " Memory Array Mapped Address",
20: " Memory Device Mapped Address",
21: " Built-in Pointing Device",
22: " Portable Battery",
23: " System Reset",
24: " Hardware Security",
25: " System Power Controls",
26: " Voltage Probe",
27: " Cooling Device",
28: " Temperature Probe",
29: " Electrical Current Probe",
30: " Out-of-band Remote Access",
31: " Boot Integrity Services",
32: " System Boot",
33: " 64-bit Memory Error",
34: " Management Device",
35: " Management Device Component",
36: " Management Device Threshold Data",
37: " Memory Channel",
38: " IPMI Device",
39: " Power Supply",
40: " Additional Information",
41: " Onboard Devices Extended Information",
42: " Management Controller Host Interface",
0: 'BIOS',
1: 'System',
2: 'Baseboard',
3: 'Chassis',
4: 'Processor',
5: 'Memory Controller',
6: 'Memory Module',
7: 'Cache',
8: 'Port Connector',
9: 'System Slots',
10: ' On Board Devices',
11: ' OEM Strings',
12: ' System Configuration Options',
13: ' BIOS Language',
14: ' Group Associations',
15: ' System Event Log',
16: ' Physical Memory Array',
17: ' Memory Device',
18: ' 32-bit Memory Error',
19: ' Memory Array Mapped Address',
20: ' Memory Device Mapped Address',
21: ' Built-in Pointing Device',
22: ' Portable Battery',
23: ' System Reset',
24: ' Hardware Security',
25: ' System Power Controls',
26: ' Voltage Probe',
27: ' Cooling Device',
28: ' Temperature Probe',
29: ' Electrical Current Probe',
30: ' Out-of-band Remote Access',
31: ' Boot Integrity Services',
32: ' System Boot',
33: ' 64-bit Memory Error',
34: ' Management Device',
35: ' Management Device Component',
36: ' Management Device Threshold Data',
37: ' Memory Channel',
38: ' IPMI Device',
39: ' Power Supply',
40: ' Additional Information',
41: ' Onboard Devices Extended Information',
42: ' Management Controller Host Interface'
}
_str2type = {}
for type_id, type_str in _type2str.items():
@ -72,7 +70,7 @@ def parse(output=None):
else:
buffer = _execute_cmd()
if isinstance(buffer, bytes):
buffer = buffer.decode("utf-8")
buffer = buffer.decode('utf-8')
_data = _parse(buffer)
return _data
@ -131,31 +129,24 @@ def get_by_type(data, type_id):
result = []
for entry in data.values():
if entry["DMIType"] == type_id:
if entry['DMIType'] == type_id:
result.append(entry)
return result
def _execute_cmd():
if not is_tool("dmidecode"):
logging.error(
"Dmidecode does not seem to be present on your system. Add it your path or "
"check the compatibility of this project with your distro."
)
if not is_tool('dmidecode'):
logging.error('Dmidecode does not seem to be present on your system. Add it your path or '
'check the compatibility of this project with your distro.')
sys.exit(1)
return _subprocess.check_output(
[
"dmidecode",
],
stderr=_subprocess.PIPE,
)
return _subprocess.check_output(['dmidecode', ], stderr=_subprocess.PIPE)
def _parse(buffer):
output_data = {}
# Each record is separated by double newlines
split_output = buffer.split("\n\n")
split_output = buffer.split('\n\n')
for record in split_output:
record_element = record.splitlines()
@ -173,21 +164,21 @@ def _parse(buffer):
dmi_handle = handle_data[0]
output_data[dmi_handle] = {}
output_data[dmi_handle]["DMIType"] = int(handle_data[1])
output_data[dmi_handle]["DMISize"] = int(handle_data[2])
output_data[dmi_handle]['DMIType'] = int(handle_data[1])
output_data[dmi_handle]['DMISize'] = int(handle_data[2])
# Okay, we know 2nd line == name
output_data[dmi_handle]["DMIName"] = record_element[1]
output_data[dmi_handle]['DMIName'] = record_element[1]
in_block_elemet = ""
in_block_list = ""
in_block_elemet = ''
in_block_list = ''
# Loop over the rest of the record, gathering values
for i in range(2, len(record_element), 1):
if i >= len(record_element):
break
# Check whether we are inside a \t\t block
if in_block_elemet != "":
if in_block_elemet != '':
in_block_data = _in_block_re.findall(record_element[i])
if in_block_data:
@ -201,7 +192,7 @@ def _parse(buffer):
else:
# We are out of the \t\t block; reset it again, and let
# the parsing continue
in_block_elemet = ""
in_block_elemet = ''
record_data = _record_re.findall(record_element[i])
@ -217,7 +208,7 @@ def _parse(buffer):
# This is an array of data - let the loop know we are inside
# an array block
in_block_elemet = record_data2[0]
in_block_list = ""
in_block_list = ''
continue

View file

@ -2,7 +2,7 @@ import re
def get(value, regex):
for line in open(value, "r"):
for line in open(value, 'r'):
r = re.search(regex, line)
if r and len(r.groups()) > 0:
return r.groups()[0]

View file

@ -6,16 +6,16 @@ from shutil import which
# mapping fields from ethtool output to simple names
field_map = {
"Supported ports": "ports",
"Supported link modes": "sup_link_modes",
"Supports auto-negotiation": "sup_autoneg",
"Advertised link modes": "adv_link_modes",
"Advertised auto-negotiation": "adv_autoneg",
"Speed": "speed",
"Duplex": "duplex",
"Port": "port",
"Auto-negotiation": "autoneg",
"Link detected": "link",
'Supported ports': 'ports',
'Supported link modes': 'sup_link_modes',
'Supports auto-negotiation': 'sup_autoneg',
'Advertised link modes': 'adv_link_modes',
'Advertised auto-negotiation': 'adv_autoneg',
'Speed': 'speed',
'Duplex': 'duplex',
'Port': 'port',
'Auto-negotiation': 'autoneg',
'Link detected': 'link',
}
@ -25,7 +25,7 @@ def merge_two_dicts(x, y):
return z
class Ethtool:
class Ethtool():
"""
This class aims to parse ethtool output
There is several bindings to have something proper, but it requires
@ -40,40 +40,39 @@ class Ethtool:
parse ethtool output
"""
output = subprocess.getoutput("ethtool {}".format(self.interface))
output = subprocess.getoutput('ethtool {}'.format(self.interface))
fields = {}
field = ""
fields["speed"] = "-"
fields["link"] = "-"
fields["duplex"] = "-"
for line in output.split("\n")[1:]:
field = ''
fields['speed'] = '-'
fields['link'] = '-'
fields['duplex'] = '-'
for line in output.split('\n')[1:]:
line = line.rstrip()
r = line.find(":")
r = line.find(':')
if r > 0:
field = line[:r].strip()
if field not in field_map:
continue
field = field_map[field]
output = line[r + 1 :].strip()
output = line[r + 1:].strip()
fields[field] = output
else:
if len(field) > 0 and field in field_map:
fields[field] += " " + line.strip()
if len(field) > 0 and \
field in field_map:
fields[field] += ' ' + line.strip()
return fields
def _parse_ethtool_module_output(self):
status, output = subprocess.getstatusoutput(
"ethtool -m {}".format(self.interface)
)
status, output = subprocess.getstatusoutput('ethtool -m {}'.format(self.interface))
if status == 0:
r = re.search(r"Identifier.*\((\w+)\)", output)
r = re.search(r'Identifier.*\((\w+)\)', output)
if r and len(r.groups()) > 0:
return {"form_factor": r.groups()[0]}
return {'form_factor': r.groups()[0]}
return {}
def parse(self):
if which("ethtool") is None:
if which('ethtool') is None:
return None
output = self._parse_ethtool_output()
output.update(self._parse_ethtool_module_output())

View file

@ -1,8 +1,5 @@
import json
import logging
import re
import sys
import traceback
import pynetbox
@ -15,17 +12,16 @@ from netbox_agent.raid.omreport import OmreportRaid
from netbox_agent.raid.storcli import StorcliRaid
INVENTORY_TAG = {
"cpu": {"name": "hw:cpu", "slug": "hw-cpu"},
"gpu": {"name": "hw:gpu", "slug": "hw-gpu"},
"disk": {"name": "hw:disk", "slug": "hw-disk"},
"interface": {"name": "hw:interface", "slug": "hw-interface"},
"memory": {"name": "hw:memory", "slug": "hw-memory"},
"motherboard": {"name": "hw:motherboard", "slug": "hw-motherboard"},
"raid_card": {"name": "hw:raid_card", "slug": "hw-raid-card"},
'cpu': {'name': 'hw:cpu', 'slug': 'hw-cpu'},
'disk': {'name': 'hw:disk', 'slug': 'hw-disk'},
'interface': {'name': 'hw:interface', 'slug': 'hw-interface'},
'memory': {'name': 'hw:memory', 'slug': 'hw-memory'},
'motherboard': {'name': 'hw:motherboard', 'slug': 'hw-motherboard'},
'raid_card': {'name': 'hw:raid_card', 'slug': 'hw-raid-card'},
}
class Inventory:
class Inventory():
"""
Better Inventory items coming, see:
- https://github.com/netbox-community/netbox/issues/3087
@ -36,7 +32,6 @@ class Inventory:
* cpu
* raid cards
* disks
* gpus
methods that:
* get local item
@ -49,11 +44,10 @@ class Inventory:
- no scan of NVMe devices
"""
def __init__(self, server, update_expansion=False):
def __init__(self, server):
self.create_netbox_tags()
self.server = server
self.update_expansion = update_expansion
netbox_server = self.server.get_netbox_server(update_expansion)
netbox_server = self.server.get_netbox_server()
self.device_id = netbox_server.id if netbox_server else None
self.raid = None
@ -62,17 +56,16 @@ class Inventory:
self.lshw = LSHW()
def create_netbox_tags(self):
ret = []
for key, tag in INVENTORY_TAG.items():
nb_tag = nb.extras.tags.get(name=tag["name"])
nb_tag = nb.extras.tags.get(
name=tag['name']
)
if not nb_tag:
nb_tag = nb.extras.tags.create(
name=tag["name"],
slug=tag["slug"],
comments=tag["name"],
name=tag['name'],
slug=tag['slug'],
comments=tag['name'],
)
ret.append(nb_tag)
return ret
def find_or_create_manufacturer(self, name):
if name is None:
@ -82,28 +75,29 @@ class Inventory:
name=name,
)
if not manufacturer:
logging.info("Creating missing manufacturer {name}".format(name=name))
logging.info('Creating missing manufacturer {name}'.format(name=name))
manufacturer = nb.dcim.manufacturers.create(
name=name,
slug=re.sub("[^A-Za-z0-9]+", "-", name).lower(),
slug=re.sub('[^A-Za-z0-9]+', '-', name).lower(),
)
logging.info("Creating missing manufacturer {name}".format(name=name))
logging.info('Creating missing manufacturer {name}'.format(name=name))
return manufacturer
def get_netbox_inventory(self, device_id, tag):
try:
items = nb.dcim.inventory_items.filter(device_id=device_id, tag=tag)
items = nb.dcim.inventory_items.filter(
device_id=device_id,
tag=tag
)
except pynetbox.core.query.RequestError:
logging.info("Tag {tag} is missing, returning empty array.".format(tag=tag))
logging.info('Tag {tag} is missing, returning empty array.'.format(tag=tag))
items = []
return list(items)
return items
def create_netbox_inventory_item(
self, device_id, tags, vendor, name, serial, description
):
def create_netbox_inventory_item(self, device_id, tags, vendor, name, serial, description):
manufacturer = self.find_or_create_manufacturer(vendor)
_ = nb.dcim.inventory_items.create(
@ -111,25 +105,26 @@ class Inventory:
manufacturer=manufacturer.id,
discovered=True,
tags=tags,
name="{}".format(name),
serial="{}".format(serial),
description=description,
name='{}'.format(name),
serial='{}'.format(serial),
description=description
)
logging.info(
"Creating inventory item {} {}/{} {} ".format(
vendor, name, serial, description
)
logging.info('Creating inventory item {} {}/{} {} '.format(
vendor,
name,
serial,
description)
)
def get_hw_motherboards(self):
motherboards = []
m = {}
m["serial"] = self.lshw.motherboard_serial
m["vendor"] = self.lshw.vendor
m["name"] = "{} {}".format(self.lshw.vendor, self.lshw.motherboard)
m["description"] = "{} Motherboard".format(self.lshw.motherboard)
m['serial'] = self.lshw.motherboard_serial
m['vendor'] = self.lshw.vendor
m['name'] = '{} {}'.format(self.lshw.vendor, self.lshw.motherboard)
m['description'] = '{} Motherboard'.format(self.lshw.motherboard)
motherboards.append(m)
@ -139,29 +134,27 @@ class Inventory:
motherboards = self.get_hw_motherboards()
nb_motherboards = self.get_netbox_inventory(
device_id=self.device_id, tag=INVENTORY_TAG["motherboard"]["slug"]
)
device_id=self.device_id,
tag=INVENTORY_TAG['motherboard']['slug'])
for nb_motherboard in nb_motherboards:
if nb_motherboard.serial not in [x["serial"] for x in motherboards]:
logging.info(
"Deleting unknown motherboard {motherboard}/{serial}".format(
if nb_motherboard.serial not in [x['serial'] for x in motherboards]:
logging.info('Deleting unknown motherboard {vendor} {motherboard}/{serial}'.format(
motherboard=self.lshw.motherboard,
serial=nb_motherboard.serial,
)
)
))
nb_motherboard.delete()
# create interfaces that are not in netbox
for motherboard in motherboards:
if motherboard.get("serial") not in [x.serial for x in nb_motherboards]:
if motherboard.get('serial') not in [x.serial for x in nb_motherboards]:
self.create_netbox_inventory_item(
device_id=self.device_id,
tags=[{"name": INVENTORY_TAG["motherboard"]["name"]}],
vendor="{}".format(motherboard.get("vendor", "N/A")),
serial="{}".format(motherboard.get("serial", "No SN")),
name="{}".format(motherboard.get("name")),
description="{}".format(motherboard.get("description")),
tags=[INVENTORY_TAG['motherboard']['name']],
vendor='{}'.format(motherboard.get('vendor', 'N/A')),
serial='{}'.format(motherboard.get('serial', 'No SN')),
name='{}'.format(motherboard.get('name')),
description='{}'.format(motherboard.get('description'))
)
def create_netbox_interface(self, iface):
@ -170,93 +163,84 @@ class Inventory:
device=self.device_id,
manufacturer=manufacturer.id,
discovered=True,
tags=[{"name": INVENTORY_TAG["interface"]["name"]}],
name="{}".format(iface["product"]),
serial="{}".format(iface["serial"]),
description="{} {}".format(iface["description"], iface["name"]),
tags=[INVENTORY_TAG['interface']['name']],
name="{}".format(iface['product']),
serial='{}'.format(iface['serial']),
description='{} {}'.format(iface['description'], iface['name'])
)
def do_netbox_interfaces(self):
nb_interfaces = self.get_netbox_inventory(
device_id=self.device_id, tag=INVENTORY_TAG["interface"]["slug"]
)
device_id=self.device_id,
tag=INVENTORY_TAG['interface']['slug'])
interfaces = self.lshw.interfaces
# delete interfaces that are in netbox but not locally
# use the serial_number has the comparison element
for nb_interface in nb_interfaces:
if nb_interface.serial not in [x["serial"] for x in interfaces]:
logging.info(
"Deleting unknown interface {serial}".format(
if nb_interface.serial not in [x['serial'] for x in interfaces]:
logging.info('Deleting unknown interface {serial}'.format(
serial=nb_interface.serial,
)
)
))
nb_interface.delete()
# create interfaces that are not in netbox
for iface in interfaces:
if iface.get("serial") not in [x.serial for x in nb_interfaces]:
if iface.get('serial') not in [x.serial for x in nb_interfaces]:
self.create_netbox_interface(iface)
def create_netbox_cpus(self):
for cpu in self.lshw.get_hw_linux("cpu"):
for cpu in self.lshw.get_hw_linux('cpu'):
manufacturer = self.find_or_create_manufacturer(cpu["vendor"])
_ = nb.dcim.inventory_items.create(
device=self.device_id,
manufacturer=manufacturer.id,
discovered=True,
tags=[{"name": INVENTORY_TAG["cpu"]["name"]}],
name=cpu["product"],
description="CPU {}".format(cpu["location"]),
tags=[INVENTORY_TAG['cpu']['name']],
name=cpu['product'],
description='CPU {}'.format(cpu['location']),
# asset_tag=cpu['location']
)
logging.info("Creating CPU model {}".format(cpu["product"]))
logging.info('Creating CPU model {}'.format(cpu['product']))
def do_netbox_cpus(self):
cpus = self.lshw.get_hw_linux("cpu")
cpus = self.lshw.get_hw_linux('cpu')
nb_cpus = self.get_netbox_inventory(
device_id=self.device_id,
tag=INVENTORY_TAG["cpu"]["slug"],
tag=INVENTORY_TAG['cpu']['slug'],
)
if not len(nb_cpus) or len(nb_cpus) and len(cpus) != len(nb_cpus):
if not len(nb_cpus) or \
len(nb_cpus) and len(cpus) != len(nb_cpus):
for x in nb_cpus:
x.delete()
self.create_netbox_cpus()
def get_raid_cards(self, filter_cards=False):
def get_raid_cards(self):
raid_class = None
if self.server.manufacturer in ("Dell", "Huawei"):
if is_tool("omreport"):
if self.server.manufacturer == 'Dell':
if is_tool('omreport'):
raid_class = OmreportRaid
if is_tool("storcli"):
if is_tool('storcli'):
raid_class = StorcliRaid
elif self.server.manufacturer in ("HP", "HPE"):
if is_tool("ssacli"):
elif self.server.manufacturer == 'HP':
if is_tool('ssacli'):
raid_class = HPRaid
if not raid_class:
return []
self.raid = raid_class()
if (
filter_cards
and config.expansion_as_device
and self.server.own_expansion_slot()
):
return [
c
for c in self.raid.get_controllers()
if c.is_external() is self.update_expansion
]
else:
return self.raid.get_controllers()
controllers = self.raid.get_controllers()
if len(self.raid.get_controllers()):
return controllers
def create_netbox_raid_card(self, raid_card):
manufacturer = self.find_or_create_manufacturer(raid_card.get_manufacturer())
manufacturer = self.find_or_create_manufacturer(
raid_card.get_manufacturer()
)
name = raid_card.get_product_name()
serial = raid_card.get_serial_number()
@ -264,17 +248,15 @@ class Inventory:
device=self.device_id,
discovered=True,
manufacturer=manufacturer.id if manufacturer else None,
tags=[{"name": INVENTORY_TAG["raid_card"]["name"]}],
name="{}".format(name),
serial="{}".format(serial),
description="RAID Card",
tags=[INVENTORY_TAG['raid_card']['name']],
name='{}'.format(name),
serial='{}'.format(serial),
description='RAID Card',
)
logging.info(
"Creating RAID Card {name} (SN: {serial})".format(
logging.info('Creating RAID Card {name} (SN: {serial})'.format(
name=name,
serial=serial,
)
)
))
return nb_raid_card
def do_netbox_raid_cards(self):
@ -289,19 +271,18 @@ class Inventory:
"""
nb_raid_cards = self.get_netbox_inventory(
device_id=self.device_id, tag=[INVENTORY_TAG["raid_card"]["slug"]]
device_id=self.device_id,
tag=[INVENTORY_TAG['raid_card']['slug']]
)
raid_cards = self.get_raid_cards(filter_cards=True)
raid_cards = self.get_raid_cards()
# delete cards that are in netbox but not locally
# use the serial_number has the comparison element
for nb_raid_card in nb_raid_cards:
if nb_raid_card.serial not in [x.get_serial_number() for x in raid_cards]:
logging.info(
"Deleting unknown locally RAID Card {serial}".format(
logging.info('Deleting unknown locally RAID Card {serial}'.format(
serial=nb_raid_card.serial,
)
)
))
nb_raid_card.delete()
# create card that are not in netbox
@ -309,70 +290,56 @@ class Inventory:
if raid_card.get_serial_number() not in [x.serial for x in nb_raid_cards]:
self.create_netbox_raid_card(raid_card)
def is_virtual_disk(self, disk, raid_devices):
disk_type = disk.get("type")
logicalname = disk.get("logicalname")
description = disk.get("description")
size = disk.get("size")
product = disk.get("product")
if (
logicalname in raid_devices
or disk_type is None
or product is None
or description is None
):
return True
def is_virtual_disk(self, disk):
logicalname = disk.get('logicalname')
description = disk.get('description')
size = disk.get('size')
product = disk.get('product')
non_raid_disks = [
"MR9361-8i",
'MR9361-8i',
]
if (
logicalname in raid_devices
or product in non_raid_disks
or "virtual" in product.lower()
or "logical" in product.lower()
or "volume" in description.lower()
or "dvd-ram" in description.lower()
or description == "SCSI Enclosure"
or (size is None and logicalname is None)
):
if size is None and logicalname is None or \
'virtual' in product.lower() or 'logical' in product.lower() or \
product in non_raid_disks or \
description == 'SCSI Enclosure' or \
'volume' in description.lower():
return True
return False
def get_hw_disks(self):
disks = []
for raid_card in self.get_raid_cards(filter_cards=True):
disks.extend(raid_card.get_physical_disks())
raid_devices = [
d.get("custom_fields", {}).get("vd_device")
for d in disks
if d.get("custom_fields", {}).get("vd_device")
]
for disk in self.lshw.get_hw_linux("storage"):
if self.is_virtual_disk(disk, raid_devices):
if self.is_virtual_disk(disk):
continue
size = round(int(disk.get("size", 0)) / 1073741824, 1)
d = {
"name": "",
"Size": "{} GB".format(size),
"logicalname": disk.get("logicalname"),
"description": disk.get("description"),
"SN": disk.get("serial"),
"Model": disk.get("product"),
"Type": disk.get("type"),
}
if disk.get("vendor"):
d["Vendor"] = disk["vendor"]
logicalname = disk.get('logicalname')
description = disk.get('description')
size = disk.get('size', 0)
product = disk.get('product')
serial = disk.get('serial')
d = {}
d["name"] = ""
d['Size'] = '{} GB'.format(int(size / 1024 / 1024 / 1024))
d['logicalname'] = logicalname
d['description'] = description
d['SN'] = serial
d['Model'] = product
if disk.get('vendor'):
d['Vendor'] = disk['vendor']
else:
d["Vendor"] = get_vendor(disk["product"])
d['Vendor'] = get_vendor(disk['product'])
disks.append(d)
for raid_card in self.get_raid_cards():
disks += raid_card.get_physical_disks()
# remove duplicate serials
seen = set()
uniq = [x for x in disks if x["SN"] not in seen and not seen.add(x["SN"])]
uniq = [x for x in disks if x['SN'] not in seen and not seen.add(x['SN'])]
return uniq
def create_netbox_disk(self, disk):
@ -380,184 +347,104 @@ class Inventory:
if "Vendor" in disk:
manufacturer = self.find_or_create_manufacturer(disk["Vendor"])
name = "{} ({})".format(disk["Model"], disk["Size"])
description = disk["Type"]
sn = disk.get("SN", "unknown")
parms = {
"device": self.device_id,
"discovered": True,
"tags": [{"name": INVENTORY_TAG["disk"]["name"]}],
"name": name,
"serial": sn,
"part_id": disk["Model"],
"description": description,
"manufacturer": getattr(manufacturer, "id", None),
}
if config.process_virtual_drives:
parms["custom_fields"] = disk.get("custom_fields", {})
_ = nb.dcim.inventory_items.create(**parms)
logging.info(
"Creating Disk {model} {serial}".format(
model=disk["Model"],
serial=sn,
)
)
def dump_disks_map(self, disks):
disk_map = [d["custom_fields"] for d in disks if "custom_fields" in d]
if config.dump_disks_map == "-":
f = sys.stdout
logicalname = disk.get('logicalname')
desc = disk.get('description')
# nonraid disk
if logicalname and desc:
if type(logicalname) is list:
logicalname = logicalname[0]
name = '{} - {} ({})'.format(
desc,
logicalname,
disk.get('Size', 0))
description = 'Device {}'.format(disk.get('logicalname', 'Unknown'))
else:
f = open(config.dump_disks_map, "w")
f.write(json.dumps(disk_map, separators=(",", ":"), indent=4, sort_keys=True))
if config.dump_disks_map != "-":
f.close()
name = '{} ({})'.format(disk['Model'], disk['Size'])
description = '{}'.format(disk['Type'])
_ = nb.dcim.inventory_items.create(
device=self.device_id,
discovered=True,
tags=[INVENTORY_TAG['disk']['name']],
name=name,
serial=disk['SN'],
part_id=disk['Model'],
description=description,
manufacturer=manufacturer.id if manufacturer else None
)
logging.info('Creating Disk {model} {serial}'.format(
model=disk['Model'],
serial=disk['SN'],
))
def do_netbox_disks(self):
nb_disks = self.get_netbox_inventory(
device_id=self.device_id, tag=INVENTORY_TAG["disk"]["slug"]
)
device_id=self.device_id,
tag=INVENTORY_TAG['disk']['slug'])
disks = self.get_hw_disks()
if config.dump_disks_map:
try:
self.dump_disks_map(disks)
except Exception as e:
logging.error("Failed to dump disks map: {}".format(e))
logging.debug(traceback.format_exc())
disk_serials = [d["SN"] for d in disks if "SN" in d]
# delete disks that are in netbox but not locally
# use the serial_number has the comparison element
for nb_disk in nb_disks:
if nb_disk.serial not in disk_serials or config.force_disk_refresh:
logging.info(
"Deleting unknown locally Disk {serial}".format(
if nb_disk.serial not in [x['SN'] for x in disks if x.get('SN')]:
logging.info('Deleting unknown locally Disk {serial}'.format(
serial=nb_disk.serial,
)
)
))
nb_disk.delete()
if config.force_disk_refresh:
nb_disks = self.get_netbox_inventory(
device_id=self.device_id, tag=INVENTORY_TAG["disk"]["slug"]
)
# create disks that are not in netbox
for disk in disks:
if disk.get("SN") not in [d.serial for d in nb_disks]:
if disk.get('SN') not in [x.serial for x in nb_disks]:
self.create_netbox_disk(disk)
def create_netbox_memory(self, memory):
manufacturer = self.find_or_create_manufacturer(memory["vendor"])
name = "Slot {} ({}GB)".format(memory["slot"], memory["size"])
manufacturer = self.find_or_create_manufacturer(memory['vendor'])
name = 'Slot {} ({}GB)'.format(memory['slot'], memory['size'])
nb_memory = nb.dcim.inventory_items.create(
device=self.device_id,
discovered=True,
manufacturer=manufacturer.id,
tags=[{"name": INVENTORY_TAG["memory"]["name"]}],
tags=[INVENTORY_TAG['memory']['name']],
name=name,
part_id=memory["product"],
serial=memory["serial"],
description=memory["description"],
part_id=memory['product'],
serial=memory['serial'],
description=memory['description'],
)
logging.info(
"Creating Memory {location} {type} {size}GB".format(
location=memory["slot"],
type=memory["product"],
size=memory["size"],
)
)
logging.info('Creating Memory {location} {type} {size}GB'.format(
location=memory['slot'],
type=memory['product'],
size=memory['size'],
))
return nb_memory
def do_netbox_memories(self):
memories = self.lshw.memories
nb_memories = self.get_netbox_inventory(
device_id=self.device_id, tag=INVENTORY_TAG["memory"]["slug"]
device_id=self.device_id,
tag=INVENTORY_TAG['memory']['slug']
)
for nb_memory in nb_memories:
if nb_memory.serial not in [x["serial"] for x in memories]:
logging.info(
"Deleting unknown locally Memory {serial}".format(
if nb_memory.serial not in [x['serial'] for x in memories]:
logging.info('Deleting unknown locally Memory {serial}'.format(
serial=nb_memory.serial,
)
)
))
nb_memory.delete()
for memory in memories:
if memory.get("serial") not in [x.serial for x in nb_memories]:
if memory.get('serial') not in [x.serial for x in nb_memories]:
self.create_netbox_memory(memory)
def create_netbox_gpus(self, gpus):
for gpu in gpus:
if "product" in gpu and len(gpu["product"]) > 50:
gpu["product"] = gpu["product"][:48] + ".."
manufacturer = self.find_or_create_manufacturer(gpu["vendor"])
_ = nb.dcim.inventory_items.create(
device=self.device_id,
manufacturer=manufacturer.id,
discovered=True,
tags=[{"name": INVENTORY_TAG["gpu"]["name"]}],
name=gpu["product"],
description=gpu["description"],
)
logging.info("Creating GPU model {}".format(gpu["product"]))
def is_external_gpu(self, gpu):
is_3d_gpu = gpu["description"].startswith("3D")
return (
self.server.is_blade()
and self.server.own_gpu_expansion_slot()
and is_3d_gpu
)
def do_netbox_gpus(self):
gpus = []
gpu_models = {}
for gpu in self.lshw.get_hw_linux("gpu"):
# Filters GPU if an expansion bay is detected:
# The internal (VGA) GPU only goes into the blade inventory,
# the external (3D) GPU goes into the expansion blade.
if (
config.expansion_as_device
and self.update_expansion ^ self.is_external_gpu(gpu)
):
continue
gpus.append(gpu)
gpu_models.setdefault(gpu["product"], 0)
gpu_models[gpu["product"]] += 1
nb_gpus = self.get_netbox_inventory(
device_id=self.device_id,
tag=INVENTORY_TAG["gpu"]["slug"],
)
nb_gpu_models = {}
for gpu in nb_gpus:
nb_gpu_models.setdefault(str(gpu), 0)
nb_gpu_models[str(gpu)] += 1
up_to_date = set(gpu_models) == set(nb_gpu_models)
if not gpus or not up_to_date:
for x in nb_gpus:
x.delete()
if gpus and not up_to_date:
self.create_netbox_gpus(gpus)
def create_or_update(self):
if config.inventory is None or config.update_inventory is None:
return False
if self.update_expansion is False:
self.do_netbox_cpus()
self.do_netbox_memories()
self.do_netbox_raid_cards()
self.do_netbox_disks()
self.do_netbox_interfaces()
self.do_netbox_motherboard()
self.do_netbox_gpus()
self.do_netbox_disks()
self.do_netbox_raid_cards()
return True

View file

@ -4,7 +4,7 @@ import subprocess
from netaddr import IPNetwork
class IPMI:
class IPMI():
"""
Parse IPMI output
ie:
@ -37,9 +37,9 @@ class IPMI:
"""
def __init__(self):
self.ret, self.output = subprocess.getstatusoutput("ipmitool lan print")
self.ret, self.output = subprocess.getstatusoutput('ipmitool lan print')
if self.ret != 0:
logging.error("Cannot get ipmi info: {}".format(self.output))
logging.error('Cannot get ipmi info: {}'.format(self.output))
def parse(self):
_ipmi = {}
@ -47,31 +47,22 @@ class IPMI:
return _ipmi
for line in self.output.splitlines():
key = line.split(":")[0].strip()
if key not in [
"802.1q VLAN ID",
"IP Address",
"Subnet Mask",
"MAC Address",
]:
key = line.split(':')[0].strip()
if key not in ['802.1q VLAN ID', 'IP Address', 'Subnet Mask', 'MAC Address']:
continue
value = ":".join(line.split(":")[1:]).strip()
value = ':'.join(line.split(':')[1:]).strip()
_ipmi[key] = value
ret = {}
ret["name"] = "IPMI"
ret["mtu"] = 1500
ret["bonding"] = False
ret["mac"] = _ipmi["MAC Address"]
ret["vlan"] = (
int(_ipmi["802.1q VLAN ID"])
if _ipmi["802.1q VLAN ID"] != "Disabled"
else None
)
ip = _ipmi["IP Address"]
netmask = _ipmi["Subnet Mask"]
address = str(IPNetwork("{}/{}".format(ip, netmask)))
ret['name'] = 'IPMI'
ret['bonding'] = False
ret['mac'] = _ipmi['MAC Address']
ret['vlan'] = int(_ipmi['802.1q VLAN ID']) \
if _ipmi['802.1q VLAN ID'] != 'Disabled' else None
ip = _ipmi['IP Address']
netmask = _ipmi['Subnet Mask']
address = str(IPNetwork('{}/{}'.format(ip, netmask)))
ret["ip"] = [address]
ret["ipmi"] = True
ret['ip'] = [address]
ret['ipmi'] = True
return ret

View file

@ -1,17 +1,12 @@
import logging
import subprocess
from netbox_agent.misc import is_tool
class LLDP:
class LLDP():
def __init__(self, output=None):
if not is_tool("lldpctl"):
logging.debug("lldpd package seems to be missing or daemon not running.")
if output:
self.output = output
else:
self.output = subprocess.getoutput("lldpctl -f keyvalue")
self.output = subprocess.getoutput('lldpctl -f keyvalue')
self.data = self.parse()
def parse(self):
@ -19,7 +14,7 @@ class LLDP:
vlans = {}
vid = None
for entry in self.output.splitlines():
if "=" not in entry:
if '=' not in entry:
continue
path, value = entry.strip().split("=", 1)
split_path = path.split(".")
@ -31,41 +26,38 @@ class LLDP:
vlans[interface] = {}
for path_component in path_components:
if not isinstance(current_dict.get(path_component), dict):
current_dict[path_component] = {}
current_dict = current_dict.get(path_component)
if "vlan-id" in path:
current_dict[path_component] = current_dict.get(path_component, {})
current_dict = current_dict[path_component]
if 'vlan-id' in path:
vid = value
vlans[interface][value] = vlans[interface].get(vid, {})
elif path.endswith("vlan"):
vid = value.replace("vlan-", "")
elif path.endswith('vlan'):
vid = value.replace('vlan-', '')
vlans[interface][vid] = vlans[interface].get(vid, {})
elif "pvid" in path:
vlans[interface][vid]["pvid"] = True
if "vlan" not in path:
elif 'pvid' in path:
vlans[interface][vid]['pvid'] = True
if 'vlan' not in path:
current_dict[final] = value
for interface, vlan in vlans.items():
output_dict["lldp"][interface]["vlan"] = vlan
if not output_dict:
logging.debug("No LLDP output, please check your network config.")
output_dict['lldp'][interface]['vlan'] = vlan
return output_dict
def get_switch_ip(self, interface):
# lldp.eth0.chassis.mgmt-ip=100.66.7.222
if self.data["lldp"].get(interface) is None:
if self.data['lldp'].get(interface) is None:
return None
return self.data["lldp"][interface]["chassis"].get("mgmt-ip")
return self.data['lldp'][interface]['chassis']['mgmt-ip']
def get_switch_port(self, interface):
# lldp.eth0.port.descr=GigabitEthernet1/0/1
if self.data["lldp"].get(interface) is None:
if self.data['lldp'].get(interface) is None:
return None
if self.data["lldp"][interface]["port"].get("ifname"):
return self.data["lldp"][interface]["port"]["ifname"]
return self.data["lldp"][interface]["port"]["descr"]
if self.data['lldp'][interface]['port'].get('ifname'):
return self.data['lldp'][interface]['port']['ifname']
return self.data['lldp'][interface]['port']['descr']
def get_switch_vlan(self, interface):
# lldp.eth0.vlan.vlan-id=296
if self.data["lldp"].get(interface) is None:
if self.data['lldp'].get(interface) is None:
return None
return self.data["lldp"][interface]["vlan"]
return self.data['lldp'][interface]['vlan']

View file

@ -4,7 +4,7 @@ import importlib.machinery
from netbox_agent.config import config
class LocationBase:
class LocationBase():
"""
This class is used to guess the location in order to push the information
in Netbox for a `Device`
@ -27,19 +27,15 @@ class LocationBase:
if self.driver_file:
try:
# FIXME: Works with Python 3.3+, support older version?
loader = importlib.machinery.SourceFileLoader(
"driver_file", self.driver_file
)
loader = importlib.machinery.SourceFileLoader('driver_file', self.driver_file)
self.driver = loader.load_module()
except ImportError:
raise ImportError(
"Couldn't import {} as a module".format(self.driver_file)
)
raise ImportError("Couldn't import {} as a module".format(self.driver_file))
else:
if self.driver:
try:
self.driver = importlib.import_module(
"netbox_agent.drivers.{}".format(self.driver)
'netbox_agent.drivers.{}'.format(self.driver)
)
except ImportError:
raise ImportError("Driver {} doesn't exists".format(self.driver))
@ -47,40 +43,19 @@ class LocationBase:
def get(self):
if self.driver is None:
return None
if not hasattr(self.driver, "get"):
if not hasattr(self.driver, 'get'):
raise Exception(
"Your driver {} doesn't have a get() function, please fix it".format(
self.driver
"Your driver {} doesn't have a get() function, please fix it".format(self.driver)
)
)
return getattr(self.driver, "get")(self.driver_value, self.regex)
class Tenant(LocationBase):
def __init__(self):
driver = config.tenant.driver.split(":")[0] if config.tenant.driver else None
driver_value = (
":".join(config.tenant.driver.split(":")[1:])
if config.tenant.driver
else None
)
driver_file = config.tenant.driver_file
regex = config.tenant.regex
super().__init__(driver, driver_value, driver_file, regex)
return getattr(self.driver, 'get')(self.driver_value, self.regex)
class Datacenter(LocationBase):
def __init__(self):
driver = (
config.datacenter_location.driver.split(":")[0]
if config.datacenter_location.driver
else None
)
driver_value = (
":".join(config.datacenter_location.driver.split(":")[1:])
if config.datacenter_location.driver
else None
)
driver = config.datacenter_location.driver.split(':')[0] if \
config.datacenter_location.driver else None
driver_value = ':'.join(config.datacenter_location.driver.split(':')[1:]) if \
config.datacenter_location.driver else None
driver_file = config.datacenter_location.driver_file
regex = config.datacenter_location.regex
super().__init__(driver, driver_value, driver_file, regex)
@ -88,16 +63,10 @@ class Datacenter(LocationBase):
class Rack(LocationBase):
def __init__(self):
driver = (
config.rack_location.driver.split(":")[0]
if config.rack_location.driver
else None
)
driver_value = (
":".join(config.rack_location.driver.split(":")[1:])
if config.rack_location.driver
else None
)
driver = config.rack_location.driver.split(':')[0] if \
config.rack_location.driver else None
driver_value = ':'.join(config.rack_location.driver.split(':')[1:]) if \
config.rack_location.driver else None
driver_file = config.rack_location.driver_file
regex = config.rack_location.regex
super().__init__(driver, driver_value, driver_file, regex)
@ -105,16 +74,10 @@ class Rack(LocationBase):
class Slot(LocationBase):
def __init__(self):
driver = (
config.slot_location.driver.split(":")[0]
if config.slot_location.driver
else None
)
driver_value = (
":".join(config.slot_location.driver.split(":")[1:])
if config.slot_location.driver
else None
)
driver = config.slot_location.driver.split(':')[0] if \
config.slot_location.driver else None
driver_value = ':'.join(config.slot_location.driver.split(':')[1:]) if \
config.slot_location.driver else None
driver_file = config.slot_location.driver_file
regex = config.slot_location.regex
super().__init__(driver, driver_value, driver_file, regex)

View file

@ -3,7 +3,7 @@ import logging
from netbox_agent.config import config
logger = logging.getLogger()
if config.log_level.lower() == "debug":
if config.log_level.lower() == 'debug':
logger.setLevel(logging.DEBUG)
else:
logger.setLevel(logging.INFO)

View file

@ -6,27 +6,22 @@ import sys
from netbox_agent.misc import is_tool
class LSHW:
class LSHW():
def __init__(self):
if not is_tool("lshw"):
logging.error("lshw does not seem to be installed")
if not is_tool('lshw'):
logging.error('lshw does not seem to be installed')
sys.exit(1)
data = subprocess.getoutput("lshw -quiet -json")
json_data = json.loads(data)
# Starting from version 02.18, `lshw -json` wraps its result in a list
# rather than returning directly a dictionary
if isinstance(json_data, list):
self.hw_info = json_data[0]
else:
self.hw_info = json_data
data = subprocess.getoutput(
'lshw -quiet -json'
)
self.hw_info = json.loads(data)
self.info = {}
self.memories = []
self.interfaces = []
self.cpus = []
self.power = []
self.disks = []
self.gpus = []
self.vendor = self.hw_info["vendor"]
self.product = self.hw_info["product"]
self.chassis_serial = self.hw_info["serial"]
@ -58,93 +53,64 @@ class LSHW:
def get_hw_linux(self, hwclass):
if hwclass == "cpu":
return self.cpus
if hwclass == "gpu":
return self.gpus
if hwclass == "network":
return self.interfaces
if hwclass == "storage":
if hwclass == 'storage':
return self.disks
if hwclass == "memory":
if hwclass == 'memory':
return self.memories
def find_network(self, obj):
# Some interfaces do not have device (logical) name (eth0, for
# instance), such as not connected network mezzanine cards in blade
# servers. In such situations, the card will be named `unknown[0-9]`.
unkn_intfs = []
for i in self.interfaces:
# newer versions of lshw can return a list of names, see issue #227
if not isinstance(i["name"], list):
if i["name"].startswith("unknown"):
unkn_intfs.append(i)
else:
for j in i["name"]:
if j.startswith("unknown"):
unkn_intfs.append(j)
d = {}
d["name"] = obj["logicalname"]
d["macaddress"] = obj["serial"]
d["serial"] = obj["serial"]
d["product"] = obj["product"]
d["vendor"] = obj["vendor"]
d["description"] = obj["description"]
unkn_name = "unknown{}".format(len(unkn_intfs))
self.interfaces.append(
{
"name": obj.get("logicalname", unkn_name),
"macaddress": obj.get("serial", ""),
"serial": obj.get("serial", ""),
"product": obj.get("product", "Unknown NIC"),
"vendor": obj.get("vendor", "Unknown"),
"description": obj.get("description", ""),
}
)
self.interfaces.append(d)
def find_storage(self, obj):
if "children" in obj:
for device in obj["children"]:
self.disks.append(
{
"logicalname": device.get("logicalname"),
"product": device.get("product"),
"serial": device.get("serial"),
"version": device.get("version"),
"size": device.get("size"),
"description": device.get("description"),
"type": device.get("description"),
}
)
elif "nvme" in obj["configuration"]["driver"]:
if not is_tool("nvme"):
logging.error("nvme-cli >= 1.0 does not seem to be installed")
return
try:
nvme = json.loads(
subprocess.check_output(
["nvme", "-list", "-o", "json"], encoding="utf8"
)
)
for device in nvme["Devices"]:
d = {
"logicalname": device["DevicePath"],
"product": device["ModelNumber"],
"serial": device["SerialNumber"],
"version": device["Firmware"],
"description": "NVME",
"type": "NVME",
}
if "UsedSize" in device:
d["size"] = device["UsedSize"]
if "UsedBytes" in device:
d["size"] = device["UsedBytes"]
d = {}
d["logicalname"] = device.get("logicalname")
d["product"] = device.get("product")
d["serial"] = device.get("serial")
d["version"] = device.get("version")
d["size"] = device.get("size")
d["description"] = device.get("description")
self.disks.append(d)
elif "nvme" in obj["configuration"]["driver"]:
nvme = json.loads(
subprocess.check_output(["nvme", '-list', '-o', 'json'],
encoding='utf8')) # noqa: E128
d = {}
d["vendor"] = obj["vendor"]
d["version"] = obj["version"]
d["product"] = obj["product"]
d['description'] = "NVME Disk"
d['product'] = nvme["Devices"][0]["ModelNumber"]
d['size'] = nvme["Devices"][0]["PhysicalSize"]
d['serial'] = nvme["Devices"][0]["SerialNumber"]
d['logicalname'] = nvme["Devices"][0]["DevicePath"]
self.disks.append(d)
except Exception:
pass
def find_cpus(self, obj):
if "product" in obj:
self.cpus.append(
{
"product": obj.get("product", "Unknown CPU"),
"vendor": obj.get("vendor", "Unknown vendor"),
"description": obj.get("description", ""),
"location": obj.get("slot", ""),
}
)
c = {}
c["product"] = obj["product"]
c["vendor"] = obj["vendor"]
c["description"] = obj["description"]
c["location"] = obj["slot"]
self.cpus.append(c)
def find_memories(self, obj):
if "children" not in obj:
@ -155,26 +121,16 @@ class LSHW:
if "empty" in dimm["description"]:
continue
self.memories.append(
{
"slot": dimm.get("slot"),
"description": dimm.get("description"),
"id": dimm.get("id"),
"serial": dimm.get("serial", "N/A"),
"vendor": dimm.get("vendor", "N/A"),
"product": dimm.get("product", "N/A"),
"size": dimm.get("size", 0) / 2**20 / 1024,
}
)
d = {}
d["slot"] = dimm.get("slot")
d["description"] = dimm.get("description")
d["id"] = dimm.get("id")
d["serial"] = dimm.get("serial", 'N/A')
d["vendor"] = dimm.get("vendor", 'N/A')
d["product"] = dimm.get("product")
d["size"] = dimm.get("size", 0) / 2 ** 20 / 1024
def find_gpus(self, obj):
if "product" in obj:
infos = {
"product": obj.get("product", "Unknown GPU"),
"vendor": obj.get("vendor", "Unknown"),
"description": obj.get("description", ""),
}
self.gpus.append(infos)
self.memories.append(d)
def walk_bridge(self, obj):
if "children" not in obj:
@ -183,8 +139,6 @@ class LSHW:
for bus in obj["children"]:
if bus["class"] == "storage":
self.find_storage(bus)
if bus["class"] == "display":
self.find_gpus(bus)
if "children" in bus:
for b in bus["children"]:
@ -192,8 +146,6 @@ class LSHW:
self.find_storage(b)
if b["class"] == "network":
self.find_network(b)
if b["class"] == "display":
self.find_gpus(b)
if __name__ == "__main__":

View file

@ -1,78 +1,31 @@
import re
import socket
import subprocess
from shutil import which
from slugify import slugify
from netbox_agent.config import netbox_instance as nb
def is_tool(name):
"""Check whether `name` is on PATH and marked as executable."""
'''Check whether `name` is on PATH and marked as executable.'''
return which(name) is not None
def get_device_role(role):
device_role = nb.dcim.device_roles.get(name=role)
if device_role is None:
raise Exception('DeviceRole "{}" does not exist, please create it'.format(role))
return device_role
def get_device_type(type):
device_type = nb.dcim.device_types.get(model=type)
if device_type is None:
raise Exception('DeviceType "{}" does not exist, please create it'.format(type))
return device_type
def get_device_platform(device_platform):
if device_platform is None:
try:
# Python 3.8+ moved linux_distribution() to distro
try:
import distro
linux_distribution = " ".join(distro.linux_distribution())
except ImportError:
import platform
linux_distribution = " ".join(platform.linux_distribution())
if not linux_distribution:
return None
except (ModuleNotFoundError, NameError, AttributeError):
return None
else:
linux_distribution = device_platform
device_platform = nb.dcim.platforms.get(name=linux_distribution)
if device_platform is None:
device_platform = nb.dcim.platforms.create(
name=linux_distribution, slug=slugify(linux_distribution)
)
return device_platform
def get_vendor(name):
vendors = {
"PERC": "Dell",
"SANDISK": "SanDisk",
"DELL": "Dell",
"ST": "Seagate",
"CRUCIAL": "Crucial",
"MICRON": "Micron",
"INTEL": "Intel",
"SAMSUNG": "Samsung",
"EH0": "HP",
"HGST": "HGST",
"HUH": "HGST",
"MB": "Toshiba",
"MC": "Toshiba",
"MD": "Toshiba",
"MG": "Toshiba",
"WD": "WDC",
'PERC': 'Dell',
'SANDISK': 'SanDisk',
'DELL': 'Dell',
'ST': 'Seagate',
'CRUCIAL': 'Crucial',
'MICRON': 'Micron',
'INTEL': 'Intel',
'SAMSUNG': 'Samsung',
'EH0': 'HP',
'HGST': 'HGST',
'HUH': 'HGST',
'MB': 'Toshiba',
'MC': 'Toshiba',
'MD': 'Toshiba',
'MG': 'Toshiba',
'WD': 'WDC'
}
for key, value in vendors.items():
if name.upper().startswith(key):
@ -82,32 +35,5 @@ def get_vendor(name):
def get_hostname(config):
if config.hostname_cmd is None:
return "{}".format(socket.gethostname())
return '{}'.format(socket.gethostname())
return subprocess.getoutput(config.hostname_cmd)
def create_netbox_tags(tags):
ret = []
for tag in tags:
nb_tag = nb.extras.tags.get(name=tag)
if not nb_tag:
nb_tag = nb.extras.tags.create(
name=tag,
slug=slugify(tag),
)
ret.append(nb_tag)
return ret
def get_mount_points():
mount_points = {}
output = subprocess.getoutput("mount")
for r in output.split("\n"):
if not r.startswith("/dev/"):
continue
mount_info = r.split()
device = mount_info[0]
device = re.sub(r"\d+$", "", device)
mp = mount_info[2]
mount_points.setdefault(device, []).append(mp)
return mount_points

View file

@ -1,7 +1,7 @@
import logging
import os
import re
from itertools import chain, islice
from itertools import chain
import netifaces
from netaddr import IPAddress
@ -17,55 +17,42 @@ class Network(object):
def __init__(self, server, *args, **kwargs):
self.nics = []
self.server = server
self.tenant = self.server.get_netbox_tenant()
self.lldp = LLDP() if config.network.lldp else None
self.nics = self.scan()
self.ipmi = None
self.dcim_choices = {}
dcim_c = nb.dcim.interfaces.choices()
for _choice_type in dcim_c:
key = "interface:{}".format(_choice_type)
self.dcim_choices[key] = {}
for choice in dcim_c[_choice_type]:
self.dcim_choices[key][choice["display_name"]] = choice["value"]
dcim_c = nb.dcim.choices()
for choice in dcim_c:
self.dcim_choices[choice] = {}
for c in dcim_c[choice]:
self.dcim_choices[choice][c['label']] = c['value']
self.ipam_choices = {}
ipam_c = nb.ipam.ip_addresses.choices()
for _choice_type in ipam_c:
key = "ip-address:{}".format(_choice_type)
self.ipam_choices[key] = {}
for choice in ipam_c[_choice_type]:
self.ipam_choices[key][choice["display_name"]] = choice["value"]
ipam_c = nb.ipam.choices()
for choice in ipam_c:
self.ipam_choices[choice] = {}
for c in ipam_c[choice]:
self.ipam_choices[choice][c['label']] = c['value']
def get_network_type():
return NotImplementedError
def scan(self):
nics = []
for interface in os.listdir("/sys/class/net/"):
for interface in os.listdir('/sys/class/net/'):
# ignore if it's not a link (ie: bonding_masters etc)
if not os.path.islink("/sys/class/net/{}".format(interface)):
if not os.path.islink('/sys/class/net/{}'.format(interface)):
continue
if config.network.ignore_interfaces and re.match(
config.network.ignore_interfaces, interface
):
logging.debug(
"Ignore interface {interface}".format(interface=interface)
)
if config.network.ignore_interfaces and \
re.match(config.network.ignore_interfaces, interface):
logging.debug('Ignore interface {interface}'.format(interface=interface))
continue
ip_addr = netifaces.ifaddresses(interface).get(netifaces.AF_INET, [])
ip6_addr = netifaces.ifaddresses(interface).get(netifaces.AF_INET6, [])
if config.network.ignore_ips:
for i, ip in enumerate(ip_addr):
if re.match(config.network.ignore_ips, ip["addr"]):
ip_addr.pop(i)
for i, ip in enumerate(ip6_addr):
if re.match(config.network.ignore_ips, ip["addr"]):
ip6_addr.pop(i)
# netifaces returns a ipv6 netmask that netaddr does not understand.
# this strips the netmask down to the correct format for netaddr,
@ -83,72 +70,58 @@ class Network(object):
# }
#
for addr in ip6_addr:
addr["addr"] = addr["addr"].replace("%{}".format(interface), "")
addr["mask"] = addr["mask"].split("/")[0]
addr["addr"] = addr["addr"].replace('%{}'.format(interface), '')
addr["netmask"] = addr["netmask"].split('/')[0]
ip_addr.append(addr)
mac = (
open("/sys/class/net/{}/address".format(interface), "r").read().strip()
)
mtu = int(
open("/sys/class/net/{}/mtu".format(interface), "r").read().strip()
)
vlan = None
if len(interface.split(".")) > 1:
vlan = int(interface.split(".")[1])
if config.network.ignore_ips and ip_addr:
for i, ip in enumerate(ip_addr):
if re.match(config.network.ignore_ips, ip['addr']):
ip_addr.pop(i)
mac = open('/sys/class/net/{}/address'.format(interface), 'r').read().strip()
vlan = None
if len(interface.split('.')) > 1:
vlan = int(interface.split('.')[1])
bonding = False
bonding_slaves = []
if os.path.isdir("/sys/class/net/{}/bonding".format(interface)):
if os.path.isdir('/sys/class/net/{}/bonding'.format(interface)):
bonding = True
bonding_slaves = (
open("/sys/class/net/{}/bonding/slaves".format(interface))
.read()
.split()
)
# Tun and TAP support
virtual = os.path.isfile("/sys/class/net/{}/tun_flags".format(interface))
bonding_slaves = open(
'/sys/class/net/{}/bonding/slaves'.format(interface)
).read().split()
nic = {
"name": interface,
"mac": mac if mac != "00:00:00:00:00:00" else None,
"ip": (
[
"{}/{}".format(x["addr"], IPAddress(x["mask"]).netmask_bits())
for x in ip_addr
]
if ip_addr
else None
), # FIXME: handle IPv6 addresses
"ethtool": Ethtool(interface).parse(),
"virtual": virtual,
"vlan": vlan,
"mtu": mtu,
"bonding": bonding,
"bonding_slaves": bonding_slaves,
'name': interface,
'mac': mac if mac != '00:00:00:00:00:00' else None,
'ip': [
'{}/{}'.format(
x['addr'],
IPAddress(x['netmask']).netmask_bits()
) for x in ip_addr
] if ip_addr else None, # FIXME: handle IPv6 addresses
'ethtool': Ethtool(interface).parse(),
'vlan': vlan,
'bonding': bonding,
'bonding_slaves': bonding_slaves,
}
nics.append(nic)
return nics
def _set_bonding_interfaces(self):
bonding_nics = (x for x in self.nics if x["bonding"])
bonding_nics = (x for x in self.nics if x['bonding'])
for nic in bonding_nics:
bond_int = self.get_netbox_network_card(nic)
logging.debug(
"Setting slave interface for {name}".format(name=bond_int.name)
)
logging.debug('Setting slave interface for {name}'.format(
name=bond_int.name
))
for slave_int in (
self.get_netbox_network_card(slave_nic)
for slave_nic in self.nics
if slave_nic["name"] in nic["bonding_slaves"]
):
if slave_nic['name'] in nic['bonding_slaves']):
if slave_int.lag is None or slave_int.lag.id != bond_int.id:
logging.debug(
"Settting interface {name} as slave of {master}".format(
logging.debug('Settting interface {name} as slave of {master}'.format(
name=slave_int.name, master=bond_int.name
)
)
))
slave_int.lag = bond_int
slave_int.save()
else:
@ -159,50 +132,46 @@ class Network(object):
return self.nics
def get_netbox_network_card(self, nic):
if nic["mac"] is None:
if nic['mac'] is None:
interface = self.nb_net.interfaces.get(
name=nic["name"], **self.custom_arg_id
name=nic['name'],
**self.custom_arg_id,
)
else:
interface = self.nb_net.interfaces.get(
mac_address=nic["mac"], name=nic["name"], **self.custom_arg_id
mac_address=nic['mac'],
name=nic['name'],
**self.custom_arg_id,
)
return interface
def get_netbox_network_cards(self):
return self.nb_net.interfaces.filter(**self.custom_arg_id)
return self.nb_net.interfaces.filter(
**self.custom_arg_id,
)
def get_netbox_type_for_nic(self, nic):
if self.get_network_type() == "virtual":
return self.dcim_choices["interface:type"]["Virtual"]
if self.get_network_type() == 'virtual':
return self.dcim_choices['interface:type']['Virtual']
if nic.get("bonding"):
return self.dcim_choices["interface:type"]["Link Aggregation Group (LAG)"]
if nic.get('bonding'):
return self.dcim_choices['interface:type']['Link Aggregation Group (LAG)']
if nic.get("bonding"):
return self.dcim_choices["interface:type"]["Link Aggregation Group (LAG)"]
if nic.get('bonding'):
return self.dcim_choices['interface:type']['Link Aggregation Group (LAG)']
if nic.get('ethtool') is None:
return self.dcim_choices['interface:type']['Other']
if nic.get("virtual"):
return self.dcim_choices["interface:type"]["Virtual"]
if nic['ethtool']['speed'] == '10000Mb/s':
if nic['ethtool']['port'] == 'FIBRE':
return self.dcim_choices['interface:type']['SFP+ (10GE)']
return self.dcim_choices['interface:type']['10GBASE-T (10GE)']
if nic.get("ethtool") is None:
return self.dcim_choices["interface:type"]["Other"]
if nic["ethtool"]["speed"] == "10000Mb/s":
if nic["ethtool"]["port"] in ("FIBRE", "Direct Attach Copper"):
return self.dcim_choices["interface:type"]["SFP+ (10GE)"]
return self.dcim_choices["interface:type"]["10GBASE-T (10GE)"]
elif nic["ethtool"]["speed"] == "25000Mb/s":
if nic["ethtool"]["port"] in ("FIBRE", "Direct Attach Copper"):
return self.dcim_choices["interface:type"]["SFP28 (25GE)"]
elif nic["ethtool"]["speed"] == "1000Mb/s":
if nic["ethtool"]["port"] in ("FIBRE", "Direct Attach Copper"):
return self.dcim_choices["interface:type"]["SFP (1GE)"]
return self.dcim_choices["interface:type"]["1000BASE-T (1GE)"]
return self.dcim_choices["interface:type"]["Other"]
elif nic['ethtool']['speed'] == '1000Mb/s':
if nic['ethtool']['port'] == 'FIBRE':
return self.dcim_choices['interface:type']['SFP (1GE)']
return self.dcim_choices['interface:type']['1000BASE-T (1GE)']
return self.dcim_choices['interface:type']['Other']
def get_or_create_vlan(self, vlan_id):
# FIXME: we may need to specify the datacenter
@ -212,129 +181,89 @@ class Network(object):
)
if vlan is None:
vlan = nb.ipam.vlans.create(
name="VLAN {}".format(vlan_id),
name='VLAN {}'.format(vlan_id),
vid=vlan_id,
)
return vlan
def reset_vlan_on_interface(self, nic, interface):
update = False
vlan_id = nic["vlan"]
lldp_vlan = (
self.lldp.get_switch_vlan(nic["name"])
if config.network.lldp and isinstance(self, ServerNetwork)
else None
)
# For strange reason, we need to get the object from scratch
# The object returned by pynetbox's save isn't always working (since pynetbox 6)
interface = self.nb_net.interfaces.get(id=interface.id)
vlan_id = nic['vlan']
lldp_vlan = self.lldp.get_switch_vlan(nic['name']) if config.network.lldp else None
# Handle the case were the local interface isn't an interface vlan as reported by Netbox
# and that LLDP doesn't report a vlan-id
if (
vlan_id is None
and lldp_vlan is None
and (interface.mode is not None or len(interface.tagged_vlans) > 0)
):
logging.info(
"Interface {interface} is not tagged, reseting mode".format(
interface=interface
)
)
# if local interface isn't a interface vlan or lldp doesn't report a vlan-id
if vlan_id is None and lldp_vlan is None and \
(interface.mode is not None or len(interface.tagged_vlans) > 0):
logging.info('Interface {interface} is not tagged, reseting mode'.format(
interface=interface))
update = True
interface.mode = None
interface.tagged_vlans = []
interface.untagged_vlan = None
# if the local interface is configured with a vlan, it's supposed to be taggued
# if mode is either not set or not correctly configured or vlan are not
# correctly configured, we reset the vlan
# if it's a vlan interface
elif vlan_id and (
interface.mode is None
or not isinstance(interface.mode, int)
and (
hasattr(interface.mode, "value")
and interface.mode.value
== self.dcim_choices["interface:mode"]["Access"]
or len(interface.tagged_vlans) != 1
or int(interface.tagged_vlans[0].vid) != int(vlan_id)
)
):
logging.info(
"Resetting tagged VLAN(s) on interface {interface}".format(
interface=interface
)
)
interface.mode is None or
type(interface.mode) is not int and (
interface.mode.value == self.dcim_choices['interface:mode']['Access'] or
len(interface.tagged_vlans) != 1 or
interface.tagged_vlans[0].vid != vlan_id)):
logging.info('Resetting tagged VLAN(s) on interface {interface}'.format(
interface=interface))
update = True
nb_vlan = self.get_or_create_vlan(vlan_id)
interface.mode = self.dcim_choices["interface:mode"]["Tagged"]
interface.mode = self.dcim_choices['interface:mode']['Tagged']
interface.tagged_vlans = [nb_vlan] if nb_vlan else []
interface.untagged_vlan = None
# Finally if LLDP reports a vlan-id with the pvid attribute
# if lldp reports a vlan-id with pvid
elif lldp_vlan:
pvid_vlan = [key for (key, value) in lldp_vlan.items() if value["pvid"]]
pvid_vlan = [key for (key, value) in lldp_vlan.items() if value['pvid']]
if len(pvid_vlan) > 0 and (
interface.mode is None
or interface.mode.value != self.dcim_choices["interface:mode"]["Access"]
or interface.untagged_vlan is None
or interface.untagged_vlan.vid != int(pvid_vlan[0])
):
logging.info(
"Resetting access VLAN on interface {interface}".format(
interface=interface
)
)
interface.mode is None or
interface.mode.value != self.dcim_choices['interface:mode']['Access'] or
interface.untagged_vlan is None or
interface.untagged_vlan.vid != int(pvid_vlan[0])):
logging.info('Resetting access VLAN on interface {interface}'.format(
interface=interface))
update = True
nb_vlan = self.get_or_create_vlan(pvid_vlan[0])
interface.mode = self.dcim_choices["interface:mode"]["Access"]
interface.mode = self.dcim_choices['interface:mode']['Access']
interface.untagged_vlan = nb_vlan.id
return update, interface
def create_netbox_nic(self, nic, mgmt=False):
# TODO: add Optic Vendor, PN and Serial
nic_type = self.get_netbox_type_for_nic(nic)
logging.info(
"Creating NIC {name} ({mac}) on {device}".format(
name=nic["name"], mac=nic["mac"], device=self.device.name
)
)
type = self.get_netbox_type_for_nic(nic)
logging.info('Creating NIC {name} ({mac}) on {device}'.format(
name=nic['name'], mac=nic['mac'], device=self.device.name))
nb_vlan = None
params = dict(self.custom_arg)
params.update(
{
"name": nic["name"],
"type": nic_type,
"mgmt_only": mgmt,
}
interface = self.nb_net.interfaces.create(
name=nic['name'],
mac_address=nic['mac'],
type=type,
mgmt_only=mgmt,
**self.custom_arg,
)
if nic["mac"]:
params["mac_address"] = nic["mac"]
if nic["mtu"]:
params["mtu"] = nic["mtu"]
interface = self.nb_net.interfaces.create(**params)
if nic["vlan"]:
nb_vlan = self.get_or_create_vlan(nic["vlan"])
interface.mode = self.dcim_choices["interface:mode"]["Tagged"]
if nic['vlan']:
nb_vlan = self.get_or_create_vlan(nic['vlan'])
interface.mode = 200
interface.tagged_vlans = [nb_vlan.id]
interface.save()
elif config.network.lldp and self.lldp.get_switch_vlan(nic["name"]) is not None:
elif config.network.lldp and self.lldp.get_switch_vlan(nic['name']) is not None:
# if lldp reports a vlan on an interface, tag the interface in access and set the vlan
# report only the interface which has `pvid=yes` (ie: lldp.eth3.vlan.pvid=yes)
# if pvid is not present, it'll be processed as a vlan tagged interface
vlans = self.lldp.get_switch_vlan(nic["name"])
vlans = self.lldp.get_switch_vlan(nic['name'])
for vid, vlan_infos in vlans.items():
nb_vlan = self.get_or_create_vlan(vid)
if vlan_infos.get("vid"):
interface.mode = self.dcim_choices["interface:mode"]["Access"]
if vlan_infos.get('vid'):
interface.mode = self.dcim_choices['interface:mode']['Access']
interface.untagged_vlan = nb_vlan.id
interface.save()
# cable the interface
if config.network.lldp and isinstance(self, ServerNetwork):
if config.network.lldp:
switch_ip = self.lldp.get_switch_ip(interface.name)
switch_interface = self.lldp.get_switch_port(interface.name)
@ -347,7 +276,7 @@ class Network(object):
return interface
def create_or_update_netbox_ip_on_interface(self, ip, interface):
"""
'''
Two behaviors:
- Anycast IP
* If IP exists and is in Anycast, create a new Anycast one
@ -358,180 +287,129 @@ class Network(object):
* If IP doesn't exist, create it
* If IP exists and isn't assigned, take it
* If IP exists and interface is wrong, change interface
"""
'''
netbox_ips = nb.ipam.ip_addresses.filter(
address=ip,
)
if not netbox_ips:
logging.info(
"Create new IP {ip} on {interface}".format(ip=ip, interface=interface)
if not len(netbox_ips):
logging.info('Create new IP {ip} on {interface}'.format(
ip=ip, interface=interface))
netbox_ip = nb.ipam.ip_addresses.create(
address=ip,
interface=interface.id,
status=1,
)
query_params = {
"address": ip,
"status": "active",
"assigned_object_type": self.assigned_object_type,
"assigned_object_id": interface.id,
}
netbox_ip = nb.ipam.ip_addresses.create(**query_params)
return netbox_ip
netbox_ip = list(netbox_ips)[0]
else:
netbox_ip = netbox_ips[0]
# If IP exists in anycast
if netbox_ip.role and netbox_ip.role.label == "Anycast":
logging.debug("IP {} is Anycast..".format(ip))
if netbox_ip.role and netbox_ip.role.label == 'Anycast':
logging.debug('IP {} is Anycast..'.format(ip))
unassigned_anycast_ip = [x for x in netbox_ips if x.interface is None]
assigned_anycast_ip = [
x for x in netbox_ips if x.interface and x.interface.id == interface.id
]
assigned_anycast_ip = [x for x in netbox_ips if
x.interface and x.interface.id == interface.id]
# use the first available anycast ip
if len(unassigned_anycast_ip):
logging.info("Assigning existing Anycast IP {} to interface".format(ip))
logging.info('Assigning existing Anycast IP {} to interface'.format(ip))
netbox_ip = unassigned_anycast_ip[0]
netbox_ip.interface = interface
netbox_ip.save()
# or if everything is assigned to other servers
elif not len(assigned_anycast_ip):
logging.info(
"Creating Anycast IP {} and assigning it to interface".format(ip)
logging.info('Creating Anycast IP {} and assigning it to interface'.format(ip))
netbox_ip = nb.ipam.ip_addresses.create(
address=ip,
interface=interface.id,
status=1,
role=self.ipam_choices['ip-address:role']['Anycast'],
)
query_params = {
"address": ip,
"status": "active",
"role": self.ipam_choices["ip-address:role"]["Anycast"],
"tenant": self.tenant.id if self.tenant else None,
"assigned_object_type": self.assigned_object_type,
"assigned_object_id": interface.id,
}
netbox_ip = nb.ipam.ip_addresses.create(**query_params)
return netbox_ip
else:
ip_interface = getattr(netbox_ip, "interface", None)
assigned_object = getattr(netbox_ip, "assigned_object", None)
if not ip_interface or not assigned_object:
if netbox_ip.interface is None:
logging.info('Assigning existing IP {ip} to {interface}'.format(
ip=ip, interface=interface))
elif netbox_ip.interface.id != interface.id:
logging.info(
"Assigning existing IP {ip} to {interface}".format(
ip=ip, interface=interface
)
)
elif (ip_interface and ip_interface.id != interface.id) or (
assigned_object and assigned_object.id != interface.id
):
old_interface = getattr(netbox_ip, "assigned_object", "n/a")
logging.info(
"Detected interface change for ip {ip}: old interface is "
"{old_interface} (id: {old_id}), new interface is {new_interface} "
" (id: {new_id})".format(
old_interface=old_interface,
new_interface=interface,
old_id=netbox_ip.id,
new_id=interface.id,
ip=netbox_ip.address,
)
)
'Detected interface change for ip {ip}: old interface is '
'{old_interface} (id: {old_id}), new interface is {new_interface} '
' (id: {new_id})'
.format(
old_interface=netbox_ip.interface, new_interface=interface,
old_id=netbox_ip.id, new_id=interface.id, ip=netbox_ip.address
))
else:
return netbox_ip
netbox_ip.assigned_object_type = self.assigned_object_type
netbox_ip.assigned_object_id = interface.id
netbox_ip.interface = interface
netbox_ip.save()
return netbox_ip
def create_or_update_netbox_network_cards(self):
if config.update_all is None or config.update_network is None:
return None
logging.debug("Creating/Updating NIC...")
logging.debug('Creating/Updating NIC...')
# delete unknown interface
nb_nics = list(self.get_netbox_network_cards())
local_nics = [x["name"] for x in self.nics]
for nic in nb_nics:
nb_nics = self.get_netbox_network_cards()
local_nics = [x['name'] for x in self.nics]
for nic in nb_nics[:]:
if nic.name not in local_nics:
logging.info(
"Deleting netbox interface {name} because not present locally".format(
logging.info('Deleting netbox interface {name} because not present locally'.format(
name=nic.name
)
)
))
nb_nics.remove(nic)
nic.delete()
# delete IP on netbox that are not known on this server
if len(nb_nics):
def batched(it, n):
while batch := tuple(islice(it, n)):
yield batch
netbox_ips = []
for ids in batched((x.id for x in nb_nics), 25):
netbox_ips += list(nb.ipam.ip_addresses.filter(**{self.intf_type: ids}))
all_local_ips = list(
chain.from_iterable([x["ip"] for x in self.nics if x["ip"] is not None])
netbox_ips = nb.ipam.ip_addresses.filter(
interface_id=[x.id for x in nb_nics],
)
all_local_ips = list(chain.from_iterable([
x['ip'] for x in self.nics if x['ip'] is not None
]))
for netbox_ip in netbox_ips:
if netbox_ip.address not in all_local_ips:
logging.info(
"Unassigning IP {ip} from {interface}".format(
ip=netbox_ip.address, interface=netbox_ip.assigned_object
)
)
netbox_ip.assigned_object_type = None
netbox_ip.ssigned_object_id = None
logging.info('Unassigning IP {ip} from {interface}'.format(
ip=netbox_ip.address, interface=netbox_ip.interface))
netbox_ip.interface = None
netbox_ip.save()
# update each nic
for nic in self.nics:
interface = self.get_netbox_network_card(nic)
if not interface:
logging.info(
"Interface {mac_address} not found, creating..".format(
mac_address=nic["mac"]
)
logging.info('Interface {mac_address} not found, creating..'.format(
mac_address=nic['mac'])
)
interface = self.create_netbox_nic(nic)
nic_update = 0
if nic["name"] != interface.name:
logging.info(
"Updating interface {interface} name to: {name}".format(
interface=interface, name=nic["name"]
)
)
interface.name = nic["name"]
if nic['name'] != interface.name:
logging.info('Updating interface {interface} name to: {name}'.format(
interface=interface, name=nic['name']))
interface.name = nic['name']
nic_update += 1
ret, interface = self.reset_vlan_on_interface(nic, interface)
nic_update += ret
if hasattr(interface, "mtu"):
if nic["mtu"] != interface.mtu:
logging.info(
"Interface mtu is wrong, updating to: {mtu}".format(
mtu=nic["mtu"]
)
)
interface.mtu = nic["mtu"]
nic_update += 1
if hasattr(interface, "type"):
_type = self.get_netbox_type_for_nic(nic)
if not interface.type or _type != interface.type.value:
logging.info("Interface type is wrong, resetting")
if not interface.type or \
_type != interface.type.value:
logging.info('Interface type is wrong, resetting')
interface.type = _type
nic_update += 1
if hasattr(interface, "lag") and interface.lag is not None:
if hasattr(interface, 'lag') and interface.lag is not None:
local_lag_int = next(
item for item in self.nics if item["name"] == interface.lag.name
item for item in self.nics if item['name'] == interface.lag.name
)
if nic["name"] not in local_lag_int["bonding_slaves"]:
logging.info("Interface has no LAG, resetting")
if nic['name'] not in local_lag_int['bonding_slaves']:
logging.info('Interface has no LAG, resetting')
nic_update += 1
interface.lag = None
# cable the interface
if config.network.lldp and isinstance(self, ServerNetwork):
if config.network.lldp:
switch_ip = self.lldp.get_switch_ip(interface.name)
switch_interface = self.lldp.get_switch_port(interface.name)
if switch_ip and switch_interface:
@ -540,66 +418,56 @@ class Network(object):
)
nic_update += ret
if nic["ip"]:
if nic['ip']:
# sync local IPs
for ip in nic["ip"]:
for ip in nic['ip']:
self.create_or_update_netbox_ip_on_interface(ip, interface)
if nic_update > 0:
interface.save()
self._set_bonding_interfaces()
logging.debug("Finished updating NIC!")
logging.debug('Finished updating NIC!')
class ServerNetwork(Network):
def __init__(self, server, *args, **kwargs):
super(ServerNetwork, self).__init__(server, args, kwargs)
if config.network.ipmi:
self.ipmi = self.get_ipmi()
if self.ipmi:
self.nics.append(self.ipmi)
self.server = server
self.device = self.server.get_netbox_server()
self.nb_net = nb.dcim
self.custom_arg = {"device": getattr(self.device, "id", None)}
self.custom_arg_id = {"device_id": getattr(self.device, "id", None)}
self.intf_type = "interface_id"
self.assigned_object_type = "dcim.interface"
self.custom_arg = {'device': self.device.id}
self.custom_arg_id = {'device_id': self.device.id}
def get_network_type(self):
return "server"
return 'server'
def get_ipmi(self):
ipmi = IPMI().parse()
return ipmi
def connect_interface_to_switch(
self, switch_ip, switch_interface, nb_server_interface
):
logging.info(
"Interface {} is not connected to switch, trying to connect..".format(
def connect_interface_to_switch(self, switch_ip, switch_interface, nb_server_interface):
logging.info('Interface {} is not connected to switch, trying to connect..'.format(
nb_server_interface.name
)
)
))
nb_mgmt_ip = nb.ipam.ip_addresses.get(
address=switch_ip,
)
if not nb_mgmt_ip:
logging.error("Switch IP {} cannot be found in Netbox".format(switch_ip))
logging.error('Switch IP {} cannot be found in Netbox'.format(switch_ip))
return nb_server_interface
try:
nb_switch = nb_mgmt_ip.assigned_object.device
logging.info(
"Found a switch in Netbox based on LLDP infos: {} (id: {})".format(
switch_ip, nb_switch.id
)
)
nb_switch = nb_mgmt_ip.interface.device
logging.info('Found a switch in Netbox based on LLDP infos: {} (id: {})'.format(
switch_ip,
nb_switch.id
))
except KeyError:
logging.error(
"Switch IP {} is found but not associated to a Netbox Switch Device".format(
'Switch IP {} is found but not associated to a Netbox Switch Device'.format(
switch_ip
)
)
@ -607,32 +475,26 @@ class ServerNetwork(Network):
switch_interface = self.lldp.get_switch_port(nb_server_interface.name)
nb_switch_interface = nb.dcim.interfaces.get(
device_id=nb_switch.id,
device=nb_switch,
name=switch_interface,
)
if nb_switch_interface is None:
logging.error(
"Switch interface {} cannot be found".format(switch_interface)
)
logging.error('Switch interface {} cannot be found'.format(switch_interface))
return nb_server_interface
logging.info(
"Found interface {} on switch {}".format(
logging.info('Found interface {} on switch {}'.format(
switch_interface,
switch_ip,
)
)
))
cable = nb.dcim.cables.create(
a_terminations=[
{"object_type": "dcim.interface", "object_id": nb_server_interface.id},
],
b_terminations=[
{"object_type": "dcim.interface", "object_id": nb_switch_interface.id},
],
termination_a_id=nb_server_interface.id,
termination_a_type="dcim.interface",
termination_b_id=nb_switch_interface.id,
termination_b_type="dcim.interface",
)
nb_server_interface.cable = cable
logging.info(
"Connected interface {interface} with {switch_interface} of {switch_ip}".format(
'Connected interface {interface} with {switch_interface} of {switch_ip}'.format(
interface=nb_server_interface.name,
switch_interface=switch_interface,
switch_ip=switch_ip,
@ -648,32 +510,40 @@ class ServerNetwork(Network):
switch_ip, switch_interface, nb_server_interface
)
else:
nb_sw_int = nb_server_interface.cable.b_terminations[0]
nb_sw_int = nb_server_interface.cable.termination_b
nb_sw = nb_sw_int.device
nb_mgmt_int = nb.dcim.interfaces.get(device_id=nb_sw.id, mgmt_only=True)
nb_mgmt_ip = nb.ipam.ip_addresses.get(interface_id=nb_mgmt_int.id)
nb_mgmt_int = nb.dcim.interfaces.get(
device_id=nb_sw.id,
mgmt_only=True
)
nb_mgmt_ip = nb.ipam.ip_addresses.get(
interface_id=nb_mgmt_int.id
)
if nb_mgmt_ip is None:
logging.error(
"Switch {switch_ip} does not have IP on its management interface".format(
'Switch {switch_ip} does not have IP on its management interface'.format(
switch_ip=switch_ip,
)
)
return update, nb_server_interface
# Netbox IP is always IP/Netmask
nb_mgmt_ip = nb_mgmt_ip.address.split("/")[0]
if nb_mgmt_ip != switch_ip or nb_sw_int.name != switch_interface:
logging.info("Netbox cable is not connected to correct ports, fixing..")
nb_mgmt_ip = nb_mgmt_ip.address.split('/')[0]
if nb_mgmt_ip != switch_ip or \
nb_sw_int.name != switch_interface:
logging.info('Netbox cable is not connected to correct ports, fixing..')
logging.info(
"Deleting cable {cable_id} from {interface} to {switch_interface} of "
"{switch_ip}".format(
'Deleting cable {cable_id} from {interface} to {switch_interface} of '
'{switch_ip}'.format(
cable_id=nb_server_interface.cable.id,
interface=nb_server_interface.name,
switch_interface=nb_sw_int.name,
switch_ip=nb_mgmt_ip,
)
)
cable = nb.dcim.cables.get(nb_server_interface.cable.id)
cable = nb.dcim.cables.get(
nb_server_interface.cable.id
)
cable.delete()
update = True
nb_server_interface = self.connect_interface_to_switch(
@ -688,17 +558,15 @@ class VirtualNetwork(Network):
self.server = server
self.device = self.server.get_netbox_vm()
self.nb_net = nb.virtualization
self.custom_arg = {"virtual_machine": getattr(self.device, "id", None)}
self.custom_arg_id = {"virtual_machine_id": getattr(self.device, "id", None)}
self.intf_type = "vminterface_id"
self.assigned_object_type = "virtualization.vminterface"
self.custom_arg = {'virtual_machine': self.device.id}
self.custom_arg_id = {'virtual_machine_id': self.device.id}
dcim_c = nb.virtualization.interfaces.choices()
for _choice_type in dcim_c:
key = "interface:{}".format(_choice_type)
self.dcim_choices[key] = {}
for choice in dcim_c[_choice_type]:
self.dcim_choices[key][choice["display_name"]] = choice["value"]
dcim_c = nb.virtualization.choices()
for choice in dcim_c:
self.dcim_choices[choice] = {}
for c in dcim_c[choice]:
self.dcim_choices[choice][c['label']] = c['value']
def get_network_type(self):
return "virtual"
return 'virtual'

View file

@ -6,63 +6,54 @@ from netbox_agent.config import netbox_instance as nb
PSU_DMI_TYPE = 39
class PowerSupply:
class PowerSupply():
def __init__(self, server=None):
self.server = server
self.netbox_server = self.server.get_netbox_server()
if self.server.is_blade():
self.device_id = (
self.netbox_server.parent_device.id if self.netbox_server else None
)
self.device_id = self.netbox_server.parent_device.id if self.netbox_server else None
else:
self.device_id = self.netbox_server.id if self.netbox_server else None
def get_power_supply(self):
power_supply = []
for psu in dmidecode.get_by_type(self.server.dmi, PSU_DMI_TYPE):
if "Present" not in psu["Status"] or psu["Status"] == "Not Present":
if 'Present' not in psu['Status'] or psu['Status'] == 'Not Present':
continue
try:
max_power = int(psu.get("Max Power Capacity").split()[0])
max_power = int(psu.get('Max Power Capacity').split()[0])
except ValueError:
max_power = None
desc = "{} - {}".format(
psu.get("Manufacturer", "No Manufacturer").strip(),
psu.get("Name", "No name").strip(),
)
sn = psu.get("Serial Number", "").strip()
# Let's assume that if no serial and no power reported we skip it
if sn == "" and max_power is None:
continue
if sn == "":
sn = "N/A"
power_supply.append(
{
"name": sn,
"description": desc,
"allocated_draw": None,
"maximum_draw": max_power,
"device": self.device_id,
}
desc = '{} - {}'.format(
psu.get('Manufacturer', 'No Manufacturer').strip(),
psu.get('Name', 'No name').strip(),
)
power_supply.append({
'name': psu.get('Serial Number', 'No S/N').strip(),
'description': desc,
'allocated_draw': None,
'maximum_draw': max_power,
'device': self.device_id,
})
return power_supply
def get_netbox_power_supply(self):
return nb.dcim.power_ports.filter(device_id=self.device_id)
return nb.dcim.power_ports.filter(
device_id=self.device_id
)
def create_or_update_power_supply(self):
nb_psus = list(self.get_netbox_power_supply())
nb_psus = self.get_netbox_power_supply()
psus = self.get_power_supply()
# Delete unknown PSU
delete = False
for nb_psu in nb_psus:
if nb_psu.name not in [x["name"] for x in psus]:
logging.info(
"Deleting unknown locally PSU {name}".format(name=nb_psu.name)
)
if nb_psu.name not in [x['name'] for x in psus]:
logging.info('Deleting unknown locally PSU {name}'.format(
name=nb_psu.name
))
nb_psu.delete()
delete = True
@ -71,23 +62,27 @@ class PowerSupply:
# sync existing Netbox PSU with local infos
for nb_psu in nb_psus:
local_psu = next(item for item in psus if item["name"] == nb_psu.name)
local_psu = next(
item for item in psus if item['name'] == nb_psu.name
)
update = False
if nb_psu.description != local_psu["description"]:
if nb_psu.description != local_psu['description']:
update = True
nb_psu.description = local_psu["description"]
if nb_psu.maximum_draw != local_psu["maximum_draw"]:
nb_psu.description = local_psu['description']
if nb_psu.maximum_draw != local_psu['maximum_draw']:
update = True
nb_psu.maximum_draw = local_psu["maximum_draw"]
nb_psu.maximum_draw = local_psu['maximum_draw']
if update:
nb_psu.save()
for psu in psus:
if psu["name"] not in [x.name for x in nb_psus]:
logging.info(
"Creating PSU {name} ({description}), {maximum_draw}W".format(**psu)
if psu['name'] not in [x.name for x in nb_psus]:
logging.info('Creating PSU {name} ({description}), {maximum_draw}W'.format(
**psu
))
nb_psu = nb.dcim.power_ports.create(
**psu
)
nb_psu = nb.dcim.power_ports.create(**psu)
return True
@ -95,7 +90,7 @@ class PowerSupply:
try:
psu_cons = self.server.get_power_consumption()
except NotImplementedError:
logging.error("Cannot report power consumption for this vendor")
logging.error('Cannot report power consumption for this vendor')
return False
nb_psus = self.get_netbox_power_supply()
@ -103,27 +98,22 @@ class PowerSupply:
return False
# find power feeds for rack or dc
voltage = None
pwr_feeds = None
if self.netbox_server.rack:
pwr_feeds = nb.dcim.power_feeds.filter(rack=self.netbox_server.rack.id)
if pwr_feeds:
voltage = [p["voltage"] for p in pwr_feeds]
else:
logging.info("Could not find power feeds for Rack, defaulting value to 230")
voltage = [230 for _ in nb_psus]
pwr_feeds = nb.dcim.power_feeds.filter(
rack=self.netbox_server.rack.id
)
if pwr_feeds is None or not len(pwr_feeds):
logging.info('Could not find power feeds for Rack, defaulting value to 230')
voltage = 230
for i, nb_psu in enumerate(nb_psus):
nb_psu.allocated_draw = int(float(psu_cons[i]) * voltage[i])
if nb_psu.allocated_draw < 1:
logging.info("PSU is not connected or in standby mode")
continue
nb_psu.allocated_draw = float(psu_cons[i]) * voltage
nb_psu.save()
logging.info(
"Updated power consumption for PSU {}: {}W".format(
logging.info('Updated power consumption for PSU {}: {}W'.format(
nb_psu.name,
nb_psu.allocated_draw,
)
)
))
return True

View file

@ -1,4 +1,4 @@
class RaidController:
class RaidController():
def get_product_name(self):
raise NotImplementedError
@ -15,10 +15,7 @@ class RaidController:
def get_physical_disks(self):
raise NotImplementedError
def is_external(self):
return False
class Raid:
class Raid():
def get_controllers(self):
raise NotImplementedError

View file

@ -1,240 +1,166 @@
import logging
import re
import subprocess
from netbox_agent.misc import get_vendor
from netbox_agent.raid.base import Raid, RaidController
REGEXP_CONTROLLER_HP = re.compile(r"Smart Array ([a-zA-Z0-9- ]+) in Slot ([0-9]+)")
REGEXP_CONTROLLER_HP = re.compile(r'Smart Array ([a-zA-Z0-9- ]+) in Slot ([0-9]+)')
class HPRaidControllerError(Exception):
pass
def _get_indentation(string):
"""Return the number of spaces before the current line."""
return len(string) - len(string.lstrip(' '))
def ssacli(sub_command):
command = ["ssacli"]
command.extend(sub_command.split())
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, stderr = p.communicate()
stdout = stdout.decode("utf-8")
if p.returncode != 0:
mesg = "Failed to execute command '{}':\n{}".format(" ".join(command), stdout)
raise HPRaidControllerError(mesg)
if "does not have any physical" in stdout:
return list()
def _get_key_value(string):
"""Return the (key, value) as a tuple from a string."""
# Normally all properties look like this:
# Unique Identifier: 600508B1001CE4ACF473EE9C826230FF
# Disk Name: /dev/sda
# Mount Points: None
key = ''
value = ''
try:
key, value = string.split(':')
except ValueError:
# This handles the case when the property of a logical drive
# returned is as follows. Here we cannot split by ':' because
# the disk id has colon in it. So if this is about disk,
# then strip it accordingly.
# Mirror Group 0: physicaldrive 6I:1:5
string = string.lstrip(' ')
if string.startswith('physicaldrive'):
fields = string.split(' ')
key = fields[0]
value = fields[1]
else:
lines = stdout.split("\n")
lines = list(filter(None, lines))
return lines
# TODO(rameshg87): Check if this ever occurs.
return None, None
return key.lstrip(' ').rstrip(' '), value.lstrip(' ').rstrip(' ')
def _test_if_valid_line(line):
ignore_patterns = [
"Note:",
"Error:",
"is not loaded",
"README",
" failure",
" cache",
]
for pattern in ignore_patterns:
if not line or pattern in line:
return None
return line
def _get_dict(lines, start_index, indentation):
"""Recursive function for parsing hpssacli/ssacli output."""
info = {}
current_item = None
def _parse_ctrl_output(lines):
controllers = {}
current_ctrl = None
for line in lines:
line = line.strip()
line = _test_if_valid_line(line)
if line is None:
continue
ctrl = REGEXP_CONTROLLER_HP.search(line)
if ctrl is not None:
slot = ctrl.group(2)
current_ctrl = "{} - Slot {}".format(ctrl.group(1), slot)
controllers[current_ctrl] = {"Slot": slot}
if "Embedded" not in line:
controllers[current_ctrl]["External"] = True
continue
if ": " not in line:
i = start_index
while i < len(lines):
current_line = lines[i]
if current_line.startswith('Note:'):
i = i + 1
continue
attr, val = line.split(": ", 1)
attr = attr.strip()
val = val.strip()
controllers[current_ctrl][attr] = val
return controllers
current_line_indentation = _get_indentation(current_line)
if current_line_indentation == indentation:
current_item = current_line.lstrip(' ')
info[current_item] = {}
i = i + 1
continue
def _parse_pd_output(lines):
drives = {}
current_array = None
current_drv = None
if i >= len(lines) - 1:
key, value = _get_key_value(current_line)
# If this is some unparsable information, then
# just skip it.
if key:
info[current_item][key] = value
return info, i
for line in lines:
line = line.strip()
line = _test_if_valid_line(line)
if line is None:
continue
# Parses the Array the drives are in
if line.startswith("Array"):
current_array = line.split(None, 1)[1]
# Detects new physical drive
if line.startswith("physicaldrive"):
current_drv = line.split(None, 1)[1]
drives[current_drv] = {}
if current_array is not None:
drives[current_drv]["Array"] = current_array
continue
if ": " not in line:
continue
attr, val = line.split(": ", 1)
attr = attr.strip()
val = val.strip()
drives.setdefault(current_drv, {})[attr] = val
return drives
next_line = lines[i + 1]
next_line_indentation = _get_indentation(next_line)
if current_line_indentation == next_line_indentation:
key, value = _get_key_value(current_line)
if key:
info[current_item][key] = value
i = i + 1
elif next_line_indentation > current_line_indentation:
ret_dict, j = _get_dict(lines, i, current_line_indentation)
info[current_item].update(ret_dict)
i = j + 1
elif next_line_indentation < current_line_indentation:
key, value = _get_key_value(current_line)
if key:
info[current_item][key] = value
return info, i
def _parse_ld_output(lines):
drives = {}
current_array = None
current_drv = None
for line in lines:
line = line.strip()
line = _test_if_valid_line(line)
if line is None:
continue
# Parses the Array the drives are in
if line.startswith("Array"):
current_array = line.split(None, 1)[1]
drives[current_array] = {}
# Detects new physical drive
if line.startswith("Logical Drive"):
current_drv = line.split(": ", 1)[1]
drives.setdefault(current_array, {})["LogicalDrive"] = current_drv
continue
if ": " not in line:
continue
attr, val = line.split(": ", 1)
drives.setdefault(current_array, {})[attr] = val
return drives
return info, i
class HPRaidController(RaidController):
def __init__(self, controller_name, data):
self.controller_name = controller_name
self.data = data
self.pdrives = self._get_physical_disks()
arrays = [d["Array"] for d in self.pdrives.values() if d.get("Array")]
if arrays:
self.ldrives = self._get_logical_drives()
self._get_virtual_drives_map()
def get_product_name(self):
return self.controller_name
def get_manufacturer(self):
return "HP"
return 'HP'
def get_serial_number(self):
return self.data["Serial Number"]
return self.data['Serial Number']
def get_firmware_version(self):
return self.data["Firmware Version"]
return self.data['Firmware Version']
def is_external(self):
return self.data.get("External", False)
def get_physical_disks(self):
ret = []
output = subprocess.getoutput(
'ssacli ctrl slot={slot} pd all show detail'.format(slot=self.data['Slot'])
)
lines = output.split('\n')
lines = list(filter(None, lines))
j = -1
while j < len(lines):
info_dict, j = _get_dict(lines, j + 1, 0)
def _get_physical_disks(self):
lines = ssacli("ctrl slot={} pd all show detail".format(self.data["Slot"]))
pdrives = _parse_pd_output(lines)
ret = {}
for name, attrs in pdrives.items():
array = attrs.get("Array", "")
model = attrs.get("Model", "").strip()
key = next(iter(info_dict))
for array, physical_disk in info_dict[key].items():
for _, pd_attr in physical_disk.items():
model = pd_attr.get('Model', '').strip()
vendor = None
if model.startswith("HP"):
vendor = "HP"
if model.startswith('HP'):
vendor = 'HP'
elif len(model.split()) > 1:
vendor = get_vendor(model.split()[1])
else:
vendor = get_vendor(model)
ret[name] = {
"Array": array,
"Model": model,
"Vendor": vendor,
"SN": attrs.get("Serial Number", "").strip(),
"Size": attrs.get("Size", "").strip(),
"Type": (
"SSD"
if attrs.get("Interface Type") == "Solid State SATA"
else "HDD"
),
"_src": self.__class__.__name__,
"custom_fields": {
"pd_identifier": name,
"mount_point": attrs.get("Mount Points", "").strip(),
"vd_device": attrs.get("Disk Name", "").strip(),
"vd_size": attrs.get("Size", "").strip(),
},
}
ret.append({
'Model': model,
'Vendor': vendor,
'SN': pd_attr.get('Serial Number', '').strip(),
'Size': pd_attr.get('Size', '').strip(),
'Type': 'SSD' if pd_attr.get('Interface Type') == 'Solid State SATA'
else 'HDD',
'_src': self.__class__.__name__,
})
return ret
def _get_logical_drives(self):
lines = ssacli("ctrl slot={} ld all show detail".format(self.data["Slot"]))
ldrives = _parse_ld_output(lines)
ret = {}
for array, attrs in ldrives.items():
ret[array] = {
"vd_array": array,
"vd_size": attrs.get("Size", "").strip(),
"vd_consistency": attrs.get("Status", "").strip(),
"vd_raid_type": "RAID {}".format(
attrs.get("Fault Tolerance", "N/A").strip()
),
"vd_device": attrs.get("LogicalDrive", "").strip(),
"mount_point": attrs.get("Mount Points", "").strip(),
}
return ret
def _get_virtual_drives_map(self):
for name, attrs in self.pdrives.items():
array = attrs["Array"]
ld = self.ldrives.get(array)
if ld is None:
logging.error(
"Failed to find array information for physical drive {}."
" Ignoring.".format(name)
)
continue
attrs["custom_fields"].update(ld)
def get_physical_disks(self):
return list(self.pdrives.values())
class HPRaid(Raid):
def __init__(self):
self.output = subprocess.getoutput("ssacli ctrl all show detail")
self.output = subprocess.getoutput('ssacli ctrl all show detail')
self.controllers = []
self.convert_to_dict()
def convert_to_dict(self):
lines = self.output.split("\n")
lines = self.output.split('\n')
lines = list(filter(None, lines))
controllers = _parse_ctrl_output(lines)
for controller, attrs in controllers.items():
self.controllers.append(HPRaidController(controller, attrs))
j = -1
while j < len(lines):
info_dict, j = _get_dict(lines, j + 1, 0)
if len(info_dict.keys()):
_product_name = list(info_dict.keys())[0]
product_name = REGEXP_CONTROLLER_HP.search(_product_name)
if product_name:
self.controllers.append(
HPRaidController(product_name.group(1), info_dict[_product_name])
)
def get_controllers(self):
return self.controllers

View file

@ -1,43 +1,25 @@
import logging
import re
import subprocess
import xml.etree.ElementTree as ET # NOQA
from netbox_agent.misc import get_mount_points, get_vendor
from netbox_agent.misc import get_vendor
from netbox_agent.raid.base import Raid, RaidController
class OmreportControllerError(Exception):
pass
# Inspiration from https://github.com/asciiphil/perc-status/blob/master/perc-status
def omreport(sub_command):
command = ["omreport"]
command.extend(sub_command.split())
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
p.wait()
stdout = p.stdout.read().decode("utf-8")
if p.returncode != 0:
mesg = "Failed to execute command '{}':\n{}".format(" ".join(command), stdout)
raise OmreportControllerError(mesg)
res = {}
section_re = re.compile("^[A-Z]")
current_section = None
current_obj = None
for line in stdout.split("\n"):
if ": " in line:
attr, value = line.split(": ", 1)
attr = attr.strip()
value = value.strip()
if attr == "ID":
obj = {}
res.setdefault(current_section, []).append(obj)
current_obj = obj
current_obj[attr] = value
elif section_re.search(line) is not None:
current_section = line.strip()
return res
def get_field(obj, fieldname):
f = obj.find(fieldname)
if f is None:
return None
if f.attrib['type'] in ['u32', 'u64']:
if re.search('Mask$', fieldname):
return int(f.text, 2)
else:
return int(f.text)
if f.attrib['type'] == 'astring':
return f.text
return f.text
class OmreportController(RaidController):
@ -46,82 +28,50 @@ class OmreportController(RaidController):
self.controller_index = controller_index
def get_product_name(self):
return self.data["Name"]
return get_field(self.data, 'Name')
def get_manufacturer(self):
return None
def get_serial_number(self):
return self.data.get("DeviceSerialNumber")
return get_field(self.data, 'DeviceSerialNumber')
def get_firmware_version(self):
return self.data.get("Firmware Version")
def _get_physical_disks(self):
pds = {}
res = omreport("storage pdisk controller={}".format(self.controller_index))
for pdisk in [d for d in list(res.values())[0]]:
disk_id = pdisk["ID"]
size = re.sub("B .*$", "B", pdisk["Capacity"])
pds[disk_id] = {
"Vendor": get_vendor(pdisk["Vendor ID"]),
"Model": pdisk["Product ID"],
"SN": pdisk["Serial No."],
"Size": size,
"Type": pdisk["Media"],
"_src": self.__class__.__name__,
}
return pds
def _get_virtual_drives_map(self):
pds = {}
res = omreport("storage vdisk controller={}".format(self.controller_index))
for vdisk in [d for d in list(res.values())[0]]:
vdisk_id = vdisk["ID"]
device = vdisk["Device Name"]
mount_points = get_mount_points()
mp = mount_points.get(device, "n/a")
size = re.sub("B .*$", "B", vdisk["Size"])
vd = {
"vd_array": vdisk_id,
"vd_size": size,
"vd_consistency": vdisk["State"],
"vd_raid_type": vdisk["Layout"],
"vd_device": vdisk["Device Name"],
"mount_point": ", ".join(sorted(mp)),
}
drives_res = omreport(
"storage pdisk controller={} vdisk={}".format(
self.controller_index, vdisk_id
)
)
for pdisk in [d for d in list(drives_res.values())[0]]:
pds[pdisk["ID"]] = vd
return pds
return get_field(self.data, 'Firmware Version')
def get_physical_disks(self):
pds = self._get_physical_disks()
vds = self._get_virtual_drives_map()
for pd_identifier, vd in vds.items():
if pd_identifier not in pds:
logging.error(
"Physical drive {} listed in virtual drive {} not "
"found in drives list".format(pd_identifier, vd["vd_array"])
ret = []
output = subprocess.getoutput(
'omreport storage controller controller={} -fmt xml'.format(self.controller_index)
)
continue
pds[pd_identifier].setdefault("custom_fields", {}).update(vd)
pds[pd_identifier]["custom_fields"]["pd_identifier"] = pd_identifier
return list(pds.values())
root = ET.fromstring(output)
et_array_disks = root.find('ArrayDisks')
if et_array_disks is not None:
for obj in et_array_disks.findall('DCStorageObject'):
ret.append({
'Vendor': get_vendor(get_field(obj, 'Vendor')),
'Model': get_field(obj, 'ProductID'),
'SN': get_field(obj, 'DeviceSerialNumber'),
'Size': '{:.0f}GB'.format(
int(get_field(obj, 'Length')) / 1024 / 1024 / 1024
),
'Type': 'HDD' if int(get_field(obj, 'MediaType')) == 1 else 'SSD',
'_src': self.__class__.__name__,
})
return ret
class OmreportRaid(Raid):
def __init__(self):
output = subprocess.getoutput('omreport storage controller -fmt xml')
controller_xml = ET.fromstring(output)
self.controllers = []
res = omreport("storage controller")
for controller in res["Controller"]:
ctrl_index = controller["ID"]
self.controllers.append(OmreportController(ctrl_index, controller))
for obj in controller_xml.find('Controllers').findall('DCStorageObject'):
ctrl_index = get_field(obj, 'ControllerNum')
self.controllers.append(
OmreportController(ctrl_index, obj)
)
def get_controllers(self):
return self.controllers

View file

@ -1,153 +1,73 @@
import json
import logging
import os
import re
import subprocess
from netbox_agent.config import config
from netbox_agent.misc import get_mount_points, get_vendor
from netbox_agent.misc import get_vendor
from netbox_agent.raid.base import Raid, RaidController
class StorcliControllerError(Exception):
pass
def storecli(sub_command):
command = ["storcli"]
command.extend(sub_command.split())
command.append("J")
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, stderr = p.communicate()
if stderr:
mesg = "Failed to execute command '{}':\n{}".format(" ".join(command), stdout)
raise StorcliControllerError(mesg)
stdout = stdout.decode("utf-8")
data = json.loads(stdout)
controllers = dict(
[
(c["Command Status"]["Controller"], c["Response Data"])
for c in data["Controllers"]
if c["Command Status"]["Status"] == "Success"
]
)
if not controllers:
logging.error(
"Failed to execute command '{}'. "
"Ignoring data.".format(" ".join(command))
)
return {}
return controllers
class StorcliController(RaidController):
def __init__(self, controller_index, data):
self.data = data
self.controller_index = controller_index
def get_product_name(self):
return self.data["Product Name"]
return self.data['Product Name']
def get_manufacturer(self):
return None
def get_serial_number(self):
return self.data["Serial Number"]
return self.data['Serial Number']
def get_firmware_version(self):
return self.data["FW Package Build"]
def _get_physical_disks(self):
pds = {}
cmd = "/c{}/eall/sall show all".format(self.controller_index)
controllers = storecli(cmd)
pd_info = controllers[self.controller_index]
pd_re = re.compile(r"^Drive (/c\d+/e\d+/s\d+)$")
for section, attrs in pd_info.items():
reg = pd_re.search(section)
if reg is None:
continue
pd_name = reg.group(1)
pd_attr = attrs[0]
pd_identifier = pd_attr["EID:Slt"]
size = pd_attr.get("Size", "").strip()
media_type = pd_attr.get("Med", "").strip()
pd_details = pd_info["{} - Detailed Information".format(section)]
pd_dev_attr = pd_details["{} Device attributes".format(section)]
model = pd_dev_attr.get("Model Number", "").strip()
pd = {
"Model": model,
"Vendor": get_vendor(model),
"SN": pd_dev_attr.get("SN", "").strip(),
"Size": size,
"Type": media_type,
"_src": self.__class__.__name__,
}
if config.process_virtual_drives:
pd.setdefault("custom_fields", {})["pd_identifier"] = pd_name
pds[pd_identifier] = pd
return pds
def _get_virtual_drives_map(self):
vds = {}
cmd = "/c{}/vall show all".format(self.controller_index)
controllers = storecli(cmd)
vd_info = controllers[self.controller_index]
mount_points = get_mount_points()
for vd_identifier, vd_attrs in vd_info.items():
if not vd_identifier.startswith("/c{}/v".format(self.controller_index)):
continue
volume = vd_identifier.split("/")[-1].lstrip("v")
vd_attr = vd_attrs[0]
vd_pd_identifier = "PDs for VD {}".format(volume)
vd_pds = vd_info[vd_pd_identifier]
vd_prop_identifier = "VD{} Properties".format(volume)
vd_properties = vd_info[vd_prop_identifier]
for pd in vd_pds:
pd_identifier = pd["EID:Slt"]
wwn = vd_properties["SCSI NAA Id"]
wwn_path = "/dev/disk/by-id/wwn-0x{}".format(wwn)
device = os.path.realpath(wwn_path)
mp = mount_points.get(device, "n/a")
vds[pd_identifier] = {
"vd_array": vd_identifier,
"vd_size": vd_attr["Size"],
"vd_consistency": vd_attr["Consist"],
"vd_raid_type": vd_attr["TYPE"],
"vd_device": device,
"mount_point": ", ".join(sorted(mp)),
}
return vds
return self.data['FW Package Build']
def get_physical_disks(self):
# Parses physical disks information
pds = self._get_physical_disks()
# Parses virtual drives information and maps them to physical disks
vds = self._get_virtual_drives_map()
for pd_identifier, vd in vds.items():
if pd_identifier not in pds:
logging.error(
"Physical drive {} listed in virtual drive {} not "
"found in drives list".format(pd_identifier, vd["vd_array"])
ret = []
output = subprocess.getoutput(
'storcli /c{}/eall/sall show all J'.format(self.controller_index)
)
continue
pds[pd_identifier].setdefault("custom_fields", {}).update(vd)
drive_infos = json.loads(output)['Controllers'][self.controller_index]['Response Data']
return list(pds.values())
for physical_drive in self.data['PD LIST']:
enclosure = physical_drive.get('EID:Slt').split(':')[0]
slot = physical_drive.get('EID:Slt').split(':')[1]
size = physical_drive.get('Size').strip()
media_type = physical_drive.get('Med').strip()
drive_identifier = 'Drive /c{}/e{}/s{}'.format(
str(self.controller_index), str(enclosure), str(slot)
)
drive_attr = drive_infos['{} - Detailed Information'.format(drive_identifier)][
'{} Device attributes'.format(drive_identifier)]
model = drive_attr.get('Model Number', '').strip()
ret.append({
'Model': model,
'Vendor': get_vendor(model),
'SN': drive_attr.get('SN', '').strip(),
'Size': size,
'Type': media_type,
'_src': self.__class__.__name__,
})
return ret
class StorcliRaid(Raid):
def __init__(self):
self.output = subprocess.getoutput('storcli /call show J')
self.data = json.loads(self.output)
self.controllers = []
controllers = storecli("/call show")
for controller_id, controller_data in controllers.items():
self.controllers.append(StorcliController(controller_id, controller_data))
if len([
x for x in self.data['Controllers']
if x['Command Status']['Status'] == 'Success'
]) > 0:
for controller in self.data['Controllers']:
self.controllers.append(
StorcliController(
controller['Command Status']['Controller'],
controller['Response Data']
)
)
def get_controllers(self):
return self.controllers

View file

@ -1,111 +1,79 @@
import logging
import socket
import subprocess
import sys
from pprint import pprint
import netbox_agent.dmidecode as dmidecode
from netbox_agent.config import config
from netbox_agent.config import netbox_instance as nb
from netbox_agent.inventory import Inventory
from netbox_agent.location import Datacenter, Rack, Tenant
from netbox_agent.misc import (
create_netbox_tags,
get_device_platform,
get_device_role,
get_device_type,
)
from netbox_agent.location import Datacenter, Rack
from netbox_agent.network import ServerNetwork
from netbox_agent.power import PowerSupply
class ServerBase:
def get_device_role(role):
device_role = nb.dcim.device_roles.get(
name=role
)
if device_role is None:
raise Exception('DeviceRole "{}" does not exist, please create it'.format(role))
return device_role
def get_device_type(type):
device_type = nb.dcim.device_types.get(
model=type
)
if device_type is None:
raise Exception('DeviceType "{}" does not exist, please create it'.format(type))
return device_type
class ServerBase():
def __init__(self, dmi=None):
if dmi:
self.dmi = dmi
else:
self.dmi = dmidecode.parse()
self.baseboard = dmidecode.get_by_type(self.dmi, "Baseboard")
self.bios = dmidecode.get_by_type(self.dmi, "BIOS")
self.chassis = dmidecode.get_by_type(self.dmi, "Chassis")
self.system = dmidecode.get_by_type(self.dmi, "System")
self.device_platform = get_device_platform(config.device.platform)
self.baseboard = dmidecode.get_by_type(self.dmi, 'Baseboard')
self.bios = dmidecode.get_by_type(self.dmi, 'BIOS')
self.chassis = dmidecode.get_by_type(self.dmi, 'Chassis')
self.system = dmidecode.get_by_type(self.dmi, 'System')
self.network = None
self.tags = (
list(set([x.strip() for x in config.device.tags.split(",") if x.strip()]))
if config.device.tags
else []
)
self.nb_tags = list(create_netbox_tags(self.tags))
config_cf = set(
[f.strip() for f in config.device.custom_fields.split(",") if f.strip()]
)
self.custom_fields = {}
self.custom_fields.update(
dict(
[
(k.strip(), v.strip())
for k, v in [f.split("=", 1) for f in config_cf]
]
)
)
def get_tenant(self):
tenant = Tenant()
return tenant.get()
def get_netbox_tenant(self):
tenant = self.get_tenant()
if tenant is None:
return None
nb_tenant = nb.tenancy.tenants.get(slug=self.get_tenant())
return nb_tenant
def get_datacenter(self):
dc = Datacenter()
return dc.get()
def get_netbox_datacenter(self):
dc = self.get_datacenter()
if dc is None:
logging.error("Specifying a datacenter (Site) is mandatory in Netbox")
sys.exit(1)
nb_dc = nb.dcim.sites.get(
slug=dc,
datacenter = nb.dcim.sites.get(
slug=self.get_datacenter()
)
if nb_dc is None:
logging.error("Site (slug: {}) has not been found".format(dc))
sys.exit(1)
return nb_dc
return datacenter
def update_netbox_location(self, server):
dc = self.get_datacenter()
rack = self.get_rack()
nb_rack = self.get_netbox_rack()
nb_dc = self.get_netbox_datacenter()
update = False
if dc and server.site and server.site.slug != nb_dc.slug:
logging.info(
"Datacenter location has changed from {} to {}, updating".format(
logging.info('Datacenter location has changed from {} to {}, updating'.format(
server.site.slug,
nb_dc.slug,
)
)
))
update = True
server.site = nb_dc.id
if server.rack and nb_rack and server.rack.id != nb_rack.id:
logging.info(
"Rack location has changed from {} to {}, updating".format(
if rack and server.rack and server.rack.id != nb_rack.id:
logging.info('Rack location has changed from {} to {}, updating'.format(
server.rack,
nb_rack,
)
)
))
update = True
server.rack = nb_rack
if nb_rack is None:
@ -113,59 +81,32 @@ class ServerBase:
server.position = None
return update, server
def update_netbox_expansion_location(self, server, expansion):
update = False
if expansion.tenant != server.tenant:
expansion.tenant = server.tenant
update = True
if expansion.site != server.site:
expansion.site = server.site
update = True
if expansion.rack != server.rack:
expansion.rack = server.rack
update = True
return update
def get_rack(self):
rack = Rack()
return rack.get()
def get_netbox_rack(self):
rack = self.get_rack()
datacenter = self.get_netbox_datacenter()
if not rack:
return None
if rack and not datacenter:
logging.error("Can't get rack if no datacenter is configured or found")
sys.exit(1)
return nb.dcim.racks.get(
name=rack,
site_id=datacenter.id,
rack = nb.dcim.racks.get(
name=self.get_rack(),
site_id=self.get_netbox_datacenter().id,
)
return rack
def get_product_name(self):
"""
Return the Chassis Name from dmidecode info
"""
return self.system[0]["Product Name"].strip()
return self.system[0]['Product Name'].strip()
def get_service_tag(self):
"""
Return the Service Tag from dmidecode info
"""
return self.system[0]["Serial Number"].strip()
def get_expansion_service_tag(self):
"""
Return the virtual Service Tag from dmidecode info host
with 'expansion'
"""
return self.system[0]["Serial Number"].strip() + " expansion"
return self.system[0]['Serial Number'].strip()
def get_hostname(self):
if config.hostname_cmd is None:
return "{}".format(socket.gethostname())
return '{}'.format(socket.gethostname())
return subprocess.getoutput(config.hostname_cmd)
def is_blade(self):
@ -195,199 +136,96 @@ class ServerBase:
def get_power_consumption(self):
raise NotImplementedError
def get_expansion_product(self):
raise NotImplementedError
def _netbox_create_chassis(self, datacenter, tenant, rack):
def _netbox_create_chassis(self, datacenter, rack):
device_type = get_device_type(self.get_chassis())
device_role = get_device_role(config.device.chassis_role)
device_role = get_device_role('Server Chassis')
serial = self.get_chassis_service_tag()
logging.info("Creating chassis blade (serial: {serial})".format(serial=serial))
logging.info('Creating chassis blade (serial: {serial})'.format(
serial=serial))
new_chassis = nb.dcim.devices.create(
name=self.get_chassis_name(),
device_type=device_type.id,
serial=serial,
role=device_role.id,
device_role=device_role.id,
site=datacenter.id if datacenter else None,
tenant=tenant.id if tenant else None,
rack=rack.id if rack else None,
tags=[{"name": x} for x in self.tags],
custom_fields=self.custom_fields,
)
return new_chassis
def _netbox_create_blade(self, chassis, datacenter, tenant, rack):
device_role = get_device_role(config.device.blade_role)
def _netbox_create_blade(self, chassis, datacenter, rack):
device_role = get_device_role('Blade')
device_type = get_device_type(self.get_product_name())
serial = self.get_service_tag()
hostname = self.get_hostname()
logging.info(
"Creating blade (serial: {serial}) {hostname} on chassis {chassis_serial}".format(
'Creating blade (serial: {serial}) {hostname} on chassis {chassis_serial}'.format(
serial=serial, hostname=hostname, chassis_serial=chassis.serial
)
)
))
new_blade = nb.dcim.devices.create(
name=hostname,
serial=serial,
role=device_role.id,
device_role=device_role.id,
device_type=device_type.id,
parent_device=chassis.id,
site=datacenter.id if datacenter else None,
tenant=tenant.id if tenant else None,
rack=rack.id if rack else None,
tags=[{"name": x} for x in self.tags],
custom_fields=self.custom_fields,
)
return new_blade
def _netbox_create_blade_expansion(self, chassis, datacenter, tenant, rack):
device_role = get_device_role(config.device.blade_role)
device_type = get_device_type(self.get_expansion_product())
serial = self.get_expansion_service_tag()
hostname = self.get_hostname() + " expansion"
logging.info(
"Creating expansion (serial: {serial}) {hostname} on chassis {chassis_serial}".format(
serial=serial, hostname=hostname, chassis_serial=chassis.serial
)
)
new_blade = nb.dcim.devices.create(
name=hostname,
serial=serial,
role=device_role.id,
device_type=device_type.id,
parent_device=chassis.id,
site=datacenter.id if datacenter else None,
tenant=tenant.id if tenant else None,
rack=rack.id if rack else None,
tags=[{"name": x} for x in self.tags],
)
return new_blade
def _netbox_deduplicate_server(self):
serial = self.get_service_tag()
hostname = self.get_hostname()
server = nb.dcim.devices.get(name=hostname)
if server and server.serial != serial:
server.delete()
def _netbox_create_server(self, datacenter, tenant, rack):
device_role = get_device_role(config.device.server_role)
def _netbox_create_server(self, datacenter, rack):
device_role = get_device_role('Server')
device_type = get_device_type(self.get_product_name())
if not device_type:
raise Exception('Chassis "{}" doesn\'t exist'.format(self.get_chassis()))
serial = self.get_service_tag()
hostname = self.get_hostname()
logging.info(
"Creating server (serial: {serial}) {hostname}".format(
serial=serial, hostname=hostname
)
)
logging.info('Creating server (serial: {serial}) {hostname}'.format(
serial=serial, hostname=hostname))
new_server = nb.dcim.devices.create(
name=hostname,
serial=serial,
role=device_role.id,
device_role=device_role.id,
device_type=device_type.id,
platform=self.device_platform.id,
site=datacenter.id if datacenter else None,
tenant=tenant.id if tenant else None,
rack=rack.id if rack else None,
tags=[{"name": x} for x in self.tags],
)
return new_server
def get_netbox_server(self, expansion=False):
if expansion is False:
def get_netbox_server(self):
return nb.dcim.devices.get(serial=self.get_service_tag())
else:
return nb.dcim.devices.get(serial=self.get_expansion_service_tag())
def _netbox_set_or_update_blade_slot(self, server, chassis, datacenter):
# before everything check if right chassis
actual_device_bay = (
server.parent_device.device_bay if server.parent_device else None
)
actual_device_bay = server.parent_device.device_bay if server.parent_device else None
actual_chassis = actual_device_bay.device if actual_device_bay else None
slot = self.get_blade_slot()
if (
actual_chassis
and actual_chassis.serial == chassis.serial
and actual_device_bay.name == slot
):
if actual_chassis and \
actual_chassis.serial == chassis.serial and \
actual_device_bay.name == slot:
return
real_device_bays = nb.dcim.device_bays.filter(
device_id=chassis.id,
name=slot,
)
real_device_bays = nb.dcim.device_bays.filter(
device_id=chassis.id,
name=slot,
)
if real_device_bays:
if len(real_device_bays) > 0:
logging.info(
"Setting device ({serial}) new slot on {slot} "
"(Chassis {chassis_serial})..".format(
'Setting device ({serial}) new slot on {slot} '
'(Chassis {chassis_serial})..'.format(
serial=server.serial, slot=slot, chassis_serial=chassis.serial
)
)
))
# reset actual device bay if set
if actual_device_bay:
# Forces the evaluation of the installed_device attribute to
# workaround a bug probably due to lazy loading optimization
# that prevents the value change detection
actual_device_bay.installed_device
actual_device_bay.installed_device = None
actual_device_bay.save()
# setup new device bay
real_device_bay = next(real_device_bays)
real_device_bay = real_device_bays[0]
real_device_bay.installed_device = server
real_device_bay.save()
else:
logging.error("Could not find slot {slot} for chassis".format(slot=slot))
def _netbox_set_or_update_blade_expansion_slot(
self, expansion, chassis, datacenter
):
# before everything check if right chassis
actual_device_bay = (
expansion.parent_device.device_bay if expansion.parent_device else None
)
actual_chassis = actual_device_bay.device if actual_device_bay else None
slot = self.get_blade_expansion_slot()
if (
actual_chassis
and actual_chassis.serial == chassis.serial
and actual_device_bay.name == slot
):
return
real_device_bays = nb.dcim.device_bays.filter(
device_id=chassis.id,
name=slot,
)
if not real_device_bays:
logging.error(
"Could not find slot {slot} expansion for chassis".format(slot=slot)
)
return
logging.info(
"Setting device expansion ({serial}) new slot on {slot} "
"(Chassis {chassis_serial})..".format(
serial=expansion.serial, slot=slot, chassis_serial=chassis.serial
)
)
# reset actual device bay if set
if actual_device_bay:
# Forces the evaluation of the installed_device attribute to
# workaround a bug probably due to lazy loading optimization
# that prevents the value change detection
actual_device_bay.installed_device
actual_device_bay.installed_device = None
actual_device_bay.save()
# setup new device bay
real_device_bay = next(real_device_bays)
real_device_bay.installed_device = expansion
real_device_bay.save()
logging.error('Could not find slot {slot} for chassis'.format(
slot=slot
))
def netbox_create_or_update(self, config):
"""
@ -403,39 +241,34 @@ class ServerBase:
"""
datacenter = self.get_netbox_datacenter()
rack = self.get_netbox_rack()
tenant = self.get_netbox_tenant()
if config.purge_old_devices:
self._netbox_deduplicate_server()
if self.is_blade():
chassis = nb.dcim.devices.get(serial=self.get_chassis_service_tag())
chassis = nb.dcim.devices.get(
serial=self.get_chassis_service_tag()
)
# Chassis does not exist
if not chassis:
chassis = self._netbox_create_chassis(datacenter, tenant, rack)
chassis = self._netbox_create_chassis(datacenter, rack)
server = nb.dcim.devices.get(serial=self.get_service_tag())
if not server:
server = self._netbox_create_blade(chassis, datacenter, tenant, rack)
server = self._netbox_create_blade(chassis, datacenter, rack)
# Set slot for blade
self._netbox_set_or_update_blade_slot(server, chassis, datacenter)
else:
server = nb.dcim.devices.get(serial=self.get_service_tag())
if not server:
server = self._netbox_create_server(datacenter, tenant, rack)
self._netbox_create_server(datacenter, rack)
logging.debug("Updating Server...")
logging.debug('Updating Server...')
# check network cards
if config.register or config.update_all or config.update_network:
self.network = ServerNetwork(server=self)
self.network.create_or_update_netbox_network_cards()
update_inventory = config.inventory and (
config.register or config.update_all or config.update_inventory
)
# update inventory if feature is enabled
if config.inventory and (config.register or config.update_all or config.update_inventory):
self.inventory = Inventory(server=self)
if update_inventory:
self.inventory.create_or_update()
# update psu
if config.register or config.update_all or config.update_psu:
@ -443,104 +276,32 @@ class ServerBase:
self.power.create_or_update_power_supply()
self.power.report_power_consumption()
expansion = nb.dcim.devices.get(serial=self.get_expansion_service_tag())
if self.own_expansion_slot() and config.expansion_as_device:
logging.debug("Update Server expansion...")
if not expansion:
expansion = self._netbox_create_blade_expansion(
chassis, datacenter, tenant, rack
)
# set slot for blade expansion
self._netbox_set_or_update_blade_expansion_slot(
expansion, chassis, datacenter
)
if update_inventory:
# Updates expansion inventory
inventory = Inventory(server=self, update_expansion=True)
inventory.create_or_update()
elif self.own_expansion_slot() and expansion:
expansion.delete()
expansion = None
update = 0
# for every other specs
# check hostname
if server.name != self.get_hostname():
update += 1
server.name = self.get_hostname()
update += 1
server_tags = sorted(set([x.name for x in server.tags]))
tags = sorted(set(self.tags))
if server_tags != tags:
new_tags_ids = [x.id for x in self.nb_tags]
if not config.preserve_tags:
server.tags = new_tags_ids
else:
server_tags_ids = [x.id for x in server.tags]
server.tags = sorted(set(new_tags_ids + server_tags_ids))
update += 1
if server.custom_fields != self.custom_fields:
server.custom_fields = self.custom_fields
update += 1
if config.update_all or config.update_location:
ret, server = self.update_netbox_location(server)
update += ret
if server.platform != self.device_platform:
server.platform = self.device_platform
update += 1
if update:
server.save()
if expansion:
update = 0
expansion_name = server.name + " expansion"
if expansion.name != expansion_name:
expansion.name = expansion_name
update += 1
if self.update_netbox_expansion_location(server, expansion):
update += 1
if update:
expansion.save()
logging.debug("Finished updating Server!")
logging.debug('Finished updating Server!')
def print_debug(self):
self.network = ServerNetwork(server=self)
print("Datacenter:", self.get_datacenter())
print("Netbox Datacenter:", self.get_netbox_datacenter())
print("Rack:", self.get_rack())
print("Netbox Rack:", self.get_netbox_rack())
print("Is blade:", self.is_blade())
print("Got expansion:", self.own_expansion_slot())
print("Product Name:", self.get_product_name())
print("Platform:", self.device_platform)
print("Chassis:", self.get_chassis())
print("Chassis service tag:", self.get_chassis_service_tag())
print("Service tag:", self.get_service_tag())
print(
"NIC:",
)
print('Datacenter:', self.get_datacenter())
print('Netbox Datacenter:', self.get_netbox_datacenter())
print('Rack:', self.get_rack())
print('Netbox Rack:', self.get_netbox_rack())
print('Is blade:', self.is_blade())
print('Product Name:', self.get_product_name())
print('Chassis:', self.get_chassis())
print('Chassis service tag:', self.get_chassis_service_tag())
print('Service tag:', self.get_service_tag())
print('NIC:',)
pprint(self.network.get_network_cards())
pass
def own_expansion_slot(self):
"""
Indicates if the device hosts an expansion card
"""
return False
def own_gpu_expansion_slot(self):
"""
Indicates if the device hosts a GPU expansion card
"""
return False
def own_drive_expansion_slot(self):
"""
Indicates if the device hosts a drive expansion bay
"""
return False

View file

@ -8,10 +8,10 @@ from netbox_agent.server import ServerBase
class DellHost(ServerBase):
def __init__(self, *args, **kwargs):
super(DellHost, self).__init__(*args, **kwargs)
self.manufacturer = "Dell"
self.manufacturer = 'Dell'
def is_blade(self):
return self.get_product_name().startswith("PowerEdge M")
return self.get_product_name().startswith('PowerEdge M')
def get_blade_slot(self):
"""
@ -20,69 +20,50 @@ class DellHost(ServerBase):
` Location In Chassis: Slot 03`
"""
if self.is_blade():
return self.baseboard[0].get("Location In Chassis").strip()
return self.baseboard[0].get('Location In Chassis').strip()
return None
def get_chassis_name(self):
if not self.is_blade():
return None
return "Chassis {}".format(self.get_service_tag())
return 'Chassis {}'.format(self.get_service_tag())
def get_chassis(self):
if self.is_blade():
return self.chassis[0]["Version"].strip()
return self.chassis[0]['Version'].strip()
return self.get_product_name()
def get_chassis_service_tag(self):
if self.is_blade():
return self.chassis[0]["Serial Number"].strip()
return self.chassis[0]['Serial Number'].strip()
return self.get_service_tag()
def get_power_consumption(self):
"""
'''
Parse omreport output like this
Amperage
PS1 Current 1 : 1.8 A
PS2 Current 2 : 1.4 A
"""
'''
value = []
if not is_tool("omreport"):
logging.error("omreport does not seem to be installed, please debug")
if not is_tool('omreport'):
logging.error('omreport does not seem to be installed, please debug')
return value
data = subprocess.getoutput("omreport chassis pwrmonitoring")
data = subprocess.getoutput('omreport chassis pwrmonitoring')
amperage = False
for line in data.splitlines():
if line.startswith("Amperage"):
if line.startswith('Amperage'):
amperage = True
continue
if amperage:
if line.startswith("PS"):
amp_value = line.split(":")[1].split()[0]
if line.startswith('PS'):
amp_value = line.split(':')[1].split()[0]
value.append(amp_value)
else:
break
return value
def get_expansion_product(self):
"""
Get the extension slot that is on a pair slot number
next to the compute slot that is on an odd slot number
"""
raise NotImplementedError
def is_expansion_slot(self, server):
"""
Return True if its an extension slot
"""
raise NotImplementedError
def get_blade_expansion_slot(self):
"""
Expansion slot are always the compute bay number + 1
"""
raise NotImplementedError

View file

@ -5,12 +5,10 @@ from netbox_agent.server import ServerBase
class GenericHost(ServerBase):
def __init__(self, *args, **kwargs):
super(GenericHost, self).__init__(*args, **kwargs)
self.manufacturer = dmidecode.get_by_type(self.dmi, "Baseboard")[0].get(
"Manufacturer"
)
self.manufacturer = dmidecode.get_by_type(self.dmi, 'Baseboard')[0].get('Manufacturer')
def is_blade(self):
return False
return None
def get_blade_slot(self):
return None

View file

@ -1,22 +1,16 @@
import netbox_agent.dmidecode as dmidecode
from netbox_agent.inventory import Inventory
from netbox_agent.server import ServerBase
class HPHost(ServerBase):
def __init__(self, *args, **kwargs):
super(HPHost, self).__init__(*args, **kwargs)
self.manufacturer = "HP"
self.product = self.get_product_name()
if self.is_blade():
self.hp_rack_locator = self._find_rack_locator()
self.manufacturer = 'HP'
def is_blade(self):
blade = self.product.startswith("ProLiant BL")
blade |= self.product.startswith("ProLiant m") and self.product.endswith(
"Server Cartridge"
)
return blade
return self.get_product_name().startswith('ProLiant BL')
def _find_rack_locator(self):
"""
@ -27,101 +21,34 @@ class HPHost(ServerBase):
# FIXME: make a dmidecode function get_by_dminame() ?
if self.is_blade():
locator = dmidecode.get_by_type(self.dmi, 204)
if self.product.startswith("ProLiant BL460c Gen10"):
locator = locator[0]["Strings"]
if self.get_product_name() == 'ProLiant BL460c Gen10':
locator = locator[0]['Strings']
return {
"Enclosure Model": locator[2].strip(),
"Enclosure Name": locator[0].strip(),
"Server Bay": locator[3].strip(),
"Enclosure Serial": locator[4].strip(),
'Enclosure Model': locator[2].strip(),
'Enclosure Name': locator[0].strip(),
'Server Bay': locator[3].strip(),
'Enclosure Serial': locator[4].strip(),
}
# HP ProLiant m750, m710x, m510 Server Cartridge
if self.product.startswith("ProLiant m") and self.product.endswith(
"Server Cartridge"
):
locator = dmidecode.get_by_type(self.dmi, 2)
chassis = dmidecode.get_by_type(self.dmi, 3)
return {
"Enclosure Model": "Moonshot 1500 Chassis",
"Enclosure Name": "Unknown",
"Server Bay": locator[0]["Location In Chassis"].strip(),
"Enclosure Serial": chassis[0]["Serial Number"].strip(),
}
return locator[0]
def get_blade_slot(self):
if self.is_blade():
return "Bay {}".format(str(self.hp_rack_locator["Server Bay"].strip()))
return 'Bay {}'.format(
int(self.hp_rack_locator['Server Bay'].strip())
)
return None
def get_chassis(self):
if self.is_blade():
return self.hp_rack_locator["Enclosure Model"].strip()
return self.hp_rack_locator['Enclosure Model'].strip()
return self.get_product_name()
def get_chassis_name(self):
if not self.is_blade():
return None
return self.hp_rack_locator["Enclosure Name"].strip()
return self.hp_rack_locator['Enclosure Name'].strip()
def get_chassis_service_tag(self):
if self.is_blade():
return self.hp_rack_locator["Enclosure Serial"].strip()
return self.hp_rack_locator['Enclosure Serial'].strip()
return self.get_service_tag()
def get_blade_expansion_slot(self):
"""
Expansion slot are always the compute bay number + 1
"""
if (
self.is_blade()
and self.own_gpu_expansion_slot()
or self.own_disk_expansion_slot()
or True
):
return "Bay {}".format(
str(int(self.hp_rack_locator["Server Bay"].strip()) + 1)
)
return None
def get_expansion_product(self):
"""
Get the extension slot that is on a pair slot number
next to the compute slot that is on an odd slot number
I only know on model of slot GPU extension card that.
"""
if self.own_gpu_expansion_slot():
return "ProLiant BL460c Graphics Expansion Blade"
elif self.own_disk_expansion_slot():
return "ProLiant BL460c Disk Expansion Blade"
return None
def own_expansion_slot(self):
"""
Indicates if the device hosts an expension card
"""
return self.own_gpu_expansion_slot() or self.own_disk_expansion_slot()
def own_gpu_expansion_slot(self):
"""
Indicates if the device hosts a GPU expansion card based
on the product name
"""
return self.get_product_name().endswith("Graphics Exp")
def own_disk_expansion_slot(self):
"""
Indicates if the device hosts a drive expansion card based
on raid card attributes.
"""
# Uses already parsed inventory if available
# parses it otherwise
inventory = getattr(self, "inventory", None)
if inventory is None:
inventory = Inventory(self)
for raid_card in inventory.get_raid_cards():
if self.is_blade() and raid_card.is_external():
return True
return False

View file

@ -4,29 +4,29 @@ from netbox_agent.server import ServerBase
class QCTHost(ServerBase):
def __init__(self, *args, **kwargs):
super(QCTHost, self).__init__(*args, **kwargs)
self.manufacturer = "QCT"
self.manufacturer = 'QCT'
def is_blade(self):
return "Location In Chassis" in self.baseboard[0].keys()
return 'Location In Chassis' in self.baseboard[0].keys()
def get_blade_slot(self):
if self.is_blade():
return "Slot {}".format(
self.baseboard[0].get("Location In Chassis").strip()
return 'Slot {}'.format(
self.baseboard[0].get('Location In Chassis').strip()
)
return None
def get_chassis_name(self):
if not self.is_blade():
return None
return "Chassis {}".format(self.get_service_tag())
return 'Chassis {}'.format(self.get_service_tag())
def get_chassis(self):
if self.is_blade():
return self.chassis[0]["Version"].strip()
return self.chassis[0]['Version'].strip()
return self.get_product_name()
def get_chassis_service_tag(self):
if self.is_blade():
return self.chassis[0]["Serial Number"].strip()
return self.chassis[0]['Serial Number'].strip()
return self.get_service_tag()

View file

@ -1,9 +1,9 @@
from netbox_agent.location import Slot
from netbox_agent.server import ServerBase
class SupermicroHost(ServerBase):
"""
"""
Supermicro DMI can be messed up. They depend on the vendor
to set the correct values. The endusers cannot
change them without buying a license from Supermicro.
@ -14,26 +14,17 @@ class SupermicroHost(ServerBase):
2) Baseboard - this is used for the blade.
3) Chassis - this is ignored.
"""
"""
class SupermicroHost(ServerBase):
def __init__(self, *args, **kwargs):
super(SupermicroHost, self).__init__(*args, **kwargs)
self.manufacturer = "Supermicro"
self.manufacturer = 'Supermicro'
def is_blade(self):
product_name = self.system[0]["Product Name"].strip()
# Blades
blade = product_name.startswith("SBI")
blade |= product_name.startswith("SBA")
# Twin
blade |= "TR-" in product_name
# TwinPro
blade |= "TP-" in product_name
# BigTwin
blade |= "BT-" in product_name
# Microcloud
blade |= product_name.startswith("SYS-5039")
blade |= product_name.startswith("SYS-5038")
blade = self.system[0]['Product Name'].startswith('SBI')
blade |= self.system[0]['Product Name'].startswith('SYS')
return blade
def get_blade_slot(self):
@ -46,34 +37,24 @@ class SupermicroHost(ServerBase):
return None
def get_service_tag(self):
if self.is_blade():
return self.baseboard[0]["Serial Number"].strip()
return self.system[0]["Serial Number"].strip()
return self.baseboard[0]['Serial Number'].strip()
def get_product_name(self):
if self.is_blade():
return self.baseboard[0]["Product Name"].strip()
return self.system[0]["Product Name"].strip()
return self.baseboard[0]['Product Name'].strip()
return self.system[0]['Product Name'].strip()
def get_chassis(self):
if self.is_blade():
return self.system[0]["Product Name"].strip()
return self.system[0]['Product Name'].strip()
return self.get_product_name()
def get_chassis_service_tag(self):
if self.is_blade():
return self.system[0]["Serial Number"].strip()
return self.system[0]['Serial Number'].strip()
return self.get_service_tag()
def get_chassis_name(self):
if not self.is_blade():
return None
return "Chassis {}".format(self.get_chassis_service_tag())
def get_expansion_product(self):
"""
Get the extension slot that is on a pair slot number
next to the compute slot that is on an odd slot number
I only know on model of slot GPU extension card that.
"""
raise NotImplementedError
return 'Chassis {}'.format(self.get_chassis_service_tag())

View file

@ -1,33 +1,24 @@
import os
from pprint import pprint
import netbox_agent.dmidecode as dmidecode
from netbox_agent.config import config
from netbox_agent.config import netbox_instance as nb
from netbox_agent.location import Tenant
from netbox_agent.logging import logging # NOQA
from netbox_agent.misc import create_netbox_tags, get_device_platform, get_hostname
from netbox_agent.misc import get_hostname
from netbox_agent.network import VirtualNetwork
def is_vm(dmi):
bios = dmidecode.get_by_type(dmi, "BIOS")[0]
system = dmidecode.get_by_type(dmi, "System")[0]
bios = dmidecode.get_by_type(dmi, 'BIOS')
system = dmidecode.get_by_type(dmi, 'System')
return (
"Hyper-V" in bios["Version"]
or "Xen" in bios["Version"]
or "Google Compute Engine" in system["Product Name"]
) or (
(
"Amazon EC2" in system["Manufacturer"]
and not system["Product Name"].endswith(".metal")
)
or "RHEV Hypervisor" in system["Product Name"]
or "QEMU" in system["Manufacturer"]
or "VirtualBox" in bios["Version"]
or "VMware" in system["Manufacturer"]
)
if 'Hyper-V' in bios[0]['Version'] or \
'Xen' in bios[0]['Version'] or \
'Google Compute Engine' in system[0]['Product Name'] or \
'VirtualBox' in bios[0]['Version'] or \
'VMware' in system[0]['Manufacturer']:
return True
return False
class VirtualMachine(object):
@ -37,18 +28,10 @@ class VirtualMachine(object):
else:
self.dmi = dmidecode.parse()
self.network = None
self.device_platform = get_device_platform(config.device.platform)
self.tags = (
list(set(config.device.tags.split(","))) if config.device.tags else []
)
self.nb_tags = create_netbox_tags(self.tags)
def get_memory(self):
mem_bytes = os.sysconf("SC_PAGE_SIZE") * os.sysconf(
"SC_PHYS_PAGES"
) # e.g. 4015976448
mem_gib = mem_bytes / (1024.0**2) # e.g. 3.74
mem_bytes = os.sysconf('SC_PAGE_SIZE') * os.sysconf('SC_PHYS_PAGES') # e.g. 4015976448
mem_gib = mem_bytes / (1024.**2) # e.g. 3.74
return int(mem_gib)
def get_vcpus(self):
@ -56,7 +39,9 @@ class VirtualMachine(object):
def get_netbox_vm(self):
hostname = get_hostname(config)
vm = nb.virtualization.virtual_machines.get(name=hostname)
vm = nb.virtualization.virtual_machines.get(
name=hostname
)
return vm
def get_netbox_cluster(self, name):
@ -65,25 +50,8 @@ class VirtualMachine(object):
)
return cluster
def get_netbox_datacenter(self, name):
cluster = self.get_netbox_cluster()
if cluster.datacenter:
return cluster.datacenter
return None
def get_tenant(self):
tenant = Tenant()
return tenant.get()
def get_netbox_tenant(self):
tenant = self.get_tenant()
if tenant is None:
return None
nb_tenant = nb.tenancy.tenants.get(slug=self.get_tenant())
return nb_tenant
def netbox_create_or_update(self, config):
logging.debug("It's a virtual machine")
logging.debug('It\'s a virtual machine')
created = False
updated = 0
@ -92,60 +60,27 @@ class VirtualMachine(object):
vcpus = self.get_vcpus()
memory = self.get_memory()
tenant = self.get_netbox_tenant()
if not vm:
logging.debug("Creating Virtual machine..")
logging.debug('Creating Virtual machine..')
cluster = self.get_netbox_cluster(config.virtual.cluster_name)
vm = nb.virtualization.virtual_machines.create(
name=hostname,
cluster=cluster.id,
platform=self.device_platform.id,
vcpus=vcpus,
memory=memory,
tenant=tenant.id if tenant else None,
tags=[{"name": x} for x in self.tags],
)
created = True
self.network = VirtualNetwork(server=self)
self.network.create_or_update_netbox_network_cards()
if not created:
if vm.vcpus != vcpus:
if not created and vm.vcpus != vcpus:
vm.vcpus = vcpus
updated += 1
if vm.memory != memory:
elif not created and vm.memory != memory:
vm.memory = memory
updated += 1
vm_tags = sorted(set([x.name for x in vm.tags]))
tags = sorted(set(self.tags))
if vm_tags != tags:
new_tags_ids = [x.id for x in self.nb_tags]
if not config.preserve_tags:
vm.tags = new_tags_ids
else:
vm_tags_ids = [x.id for x in vm.tags]
vm.tags = sorted(set(new_tags_ids + vm_tags_ids))
updated += 1
if vm.platform != self.device_platform:
vm.platform = self.device_platform
updated += 1
if updated:
vm.save()
def print_debug(self):
self.network = VirtualNetwork(server=self)
print("Cluster:", self.get_netbox_cluster(config.virtual.cluster_name))
print("Platform:", self.device_platform)
print("VM:", self.get_netbox_vm())
print("vCPU:", self.get_vcpus())
print("Memory:", f"{self.get_memory()} MB")
print(
"NIC:",
)
pprint(self.network.get_network_cards())
pass

View file

@ -1,46 +0,0 @@
{
lib,
buildPythonPackage,
fetchFromGitHub,
cargo,
rustPlatform,
rustc,
typing-extensions,
}:
buildPythonPackage rec {
pname = "netifaces-2";
version = "0.0.22";
pyproject = true;
src = fetchFromGitHub {
owner = "SamuelYvon";
repo = "netifaces-2";
rev = "V${version}";
hash = "sha256-XO3HWq8FOVzvpbK8mIBOup6hFMnhDpqOK/5bPziPZQ8=";
};
cargoDeps = rustPlatform.fetchCargoTarball {
inherit src;
name = "${pname}-${version}";
hash = "sha256-uoUa6DSBuIV3RrE7svT1TVLxPHdx8BFu/C6mbpRmor0=";
};
build-system = [
cargo
rustPlatform.cargoSetupHook
rustPlatform.maturinBuildHook
rustc
];
dependencies = [ typing-extensions ];
pythonImportsCheck = [ "netifaces" ];
meta = {
description = "Netifaces reborn";
homepage = "https://github.com/SamuelYvon/netifaces-2.git";
license = lib.licenses.mit;
maintainers = with lib.maintainers; [ ];
};
}

View file

@ -1,80 +0,0 @@
# Generated by npins. Do not modify; will be overwritten regularly
let
data = builtins.fromJSON (builtins.readFile ./sources.json);
version = data.version;
mkSource =
spec:
assert spec ? type;
let
path =
if spec.type == "Git" then
mkGitSource spec
else if spec.type == "GitRelease" then
mkGitSource spec
else if spec.type == "PyPi" then
mkPyPiSource spec
else if spec.type == "Channel" then
mkChannelSource spec
else
builtins.throw "Unknown source type ${spec.type}";
in
spec // { outPath = path; };
mkGitSource =
{
repository,
revision,
url ? null,
hash,
branch ? null,
...
}:
assert repository ? type;
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
# In the latter case, there we will always be an url to the tarball
if url != null then
(builtins.fetchTarball {
inherit url;
sha256 = hash; # FIXME: check nix version & use SRI hashes
})
else
assert repository.type == "Git";
let
urlToName =
url: rev:
let
matched = builtins.match "^.*/([^/]*)(\\.git)?$" repository.url;
short = builtins.substring 0 7 rev;
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
in
"${if matched == null then "source" else builtins.head matched}${appendShort}";
name = urlToName repository.url revision;
in
builtins.fetchGit {
url = repository.url;
rev = revision;
inherit name;
# hash = hash;
};
mkPyPiSource =
{ url, hash, ... }:
builtins.fetchurl {
inherit url;
sha256 = hash;
};
mkChannelSource =
{ url, hash, ... }:
builtins.fetchTarball {
inherit url;
sha256 = hash;
};
in
if version == 3 then
builtins.mapAttrs (_: mkSource) data.pins
else
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"

View file

@ -1,23 +0,0 @@
{
"pins": {
"git-hooks": {
"type": "Git",
"repository": {
"type": "GitHub",
"owner": "cachix",
"repo": "git-hooks.nix"
},
"branch": "master",
"revision": "3c3e88f0f544d6bb54329832616af7eb971b6be6",
"url": "https://github.com/cachix/git-hooks.nix/archive/3c3e88f0f544d6bb54329832616af7eb971b6be6.tar.gz",
"hash": "04pwjz423iq2nkazkys905gvsm5j39722ngavrnx42b8msr5k555"
},
"nixpkgs": {
"type": "Channel",
"name": "nixpkgs-unstable",
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-24.11pre694416.ccc0c2126893/nixexprs.tar.xz",
"hash": "0cn1z4wzps8nfqxzr6l5mbn81adcqy2cy2ic70z13fhzicmxfsbx"
}
},
"version": 3
}

View file

@ -1,2 +0,0 @@
[tool.isort]
profile = "black"

View file

@ -1,8 +1,5 @@
pynetbox==7.3.4
netaddr==1.3.0
netifaces2==0.0.22
pyyaml==6.0.1
jsonargparse==4.32.0
python-slugify==8.0.4
packaging==23.2
distro==1.9.0
pynetbox==4.3.1
netaddr==0.7.19
netifaces==0.10.9
pyyaml==5.3.1
jsonargparse==2.27.0

View file

@ -1,27 +0,0 @@
{
"extensions": {
"enabled": ["python_venv", "blocks"]
},
"core": {
"group": "Application/System",
"license": "Apache2",
"name": "netbox-agent",
"summary": "NetBox agent for server",
"url": "https://github.com/Solvik/netbox-agent",
"version": "0.7.0",
"requires": ["lshw"]
},
"python_venv": {
"python": "python3.6",
"requirements": ["requirements.txt"],
"name": "netbox-agent",
"path": "/opt/"
},
"blocks": {
"post": ["ln -sf /opt/netbox-agent/bin/netbox_agent /usr/bin/netbox_agent"],
"desc": [
"This project aims to create hardware automatically into Netbox based on standard tools (dmidecode, lldpd, parsing /sys/, etc).",
"The goal is to generate an existing infrastructure on Netbox and have the ability to update it regularly by executing the agent."
]
}
}

View file

@ -1,38 +1,33 @@
import os
from setuptools import find_packages, setup
def get_requirements():
reqs_path = os.path.join(os.path.dirname(__file__), "requirements.txt")
with open(reqs_path, "r") as f:
reqs = [r.strip() for r in f if r.strip()]
return reqs
setup(
name="netbox_agent",
version="0.7.1",
description="NetBox agent for server",
long_description=open("README.md", encoding="utf-8").read(),
long_description_content_type="text/markdown",
url="https://github.com/solvik/netbox_agent",
author="Solvik Blum",
author_email="solvik@solvik.fr",
license="Apache2",
name='netbox_agent',
version='0.6.0',
description='NetBox agent for server',
long_description=open('README.md', encoding="utf-8").read(),
url='https://github.com/solvik/netbox_agent',
author='Solvik Blum',
author_email='solvik@solvik.fr',
license='Apache2',
include_package_data=True,
packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]),
use_scm_version=True,
install_requires=get_requirements(),
install_requires=[
'pynetbox==4.3.1',
'netaddr==0.7.19',
'netifaces==0.10.9',
'pyyaml==5.3.1',
'jsonargparse==2.27.0',
],
zip_safe=False,
keywords=["netbox"],
keywords=['netbox'],
classifiers=[
"Intended Audience :: Developers",
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
'Intended Audience :: Developers',
'Development Status :: 5 - Production/Stable',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.6',
],
entry_points={
"console_scripts": ["netbox_agent=netbox_agent.cli:main"],
},
'console_scripts': ['netbox_agent=netbox_agent.cli:main'],
}
)

View file

@ -1 +0,0 @@
(import ./. { }).devShell

View file

@ -14,15 +14,14 @@ def get_fixture_paths(path):
return fixture_paths
def parametrize_with_fixtures(
path, base_path="tests/fixtures", argname="fixture", only_filenames=None
):
def parametrize_with_fixtures(path, base_path='tests/fixtures',
argname='fixture', only_filenames=None):
path = os.path.join(base_path, path)
fixture_paths = get_fixture_paths(path)
argvalues = []
for path in fixture_paths:
with open(path, "r") as f:
content = "".join(f.readlines())
with open(path, 'r') as f:
content = ''.join(f.readlines())
filename = os.path.basename(path)
if only_filenames and filename not in only_filenames:
continue
@ -31,5 +30,4 @@ def parametrize_with_fixtures(
def _decorator(test_function):
return pytest.mark.parametrize(argname, argvalues)(test_function)
return _decorator

File diff suppressed because it is too large Load diff

View file

@ -1,921 +0,0 @@
# dmidecode 3.1
Getting SMBIOS data from sysfs.
SMBIOS 2.8 present.
93 structures occupying 4311 bytes.
Table at 0x766AD000.
Handle 0x0000, DMI type 7, 19 bytes
Cache Information
Socket Designation: L1-Cache
Configuration: Enabled, Not Socketed, Level 1
Operational Mode: Write Back
Location: Internal
Installed Size: 256 kB
Maximum Size: 256 kB
Supported SRAM Types:
Synchronous
Installed SRAM Type: Synchronous
Speed: Unknown
Error Correction Type: Single-bit ECC
System Type: Unified
Associativity: 8-way Set-associative
Handle 0x0001, DMI type 7, 19 bytes
Cache Information
Socket Designation: L2-Cache
Configuration: Enabled, Not Socketed, Level 2
Operational Mode: Varies With Memory Address
Location: Internal
Installed Size: 1024 kB
Maximum Size: 1024 kB
Supported SRAM Types:
Synchronous
Installed SRAM Type: Synchronous
Speed: Unknown
Error Correction Type: Single-bit ECC
System Type: Unified
Associativity: 4-way Set-associative
Handle 0x0002, DMI type 7, 19 bytes
Cache Information
Socket Designation: L3-Cache
Configuration: Enabled, Not Socketed, Level 3
Operational Mode: Varies With Memory Address
Location: Internal
Installed Size: 8192 kB
Maximum Size: 8192 kB
Supported SRAM Types:
Synchronous
Installed SRAM Type: Synchronous
Speed: Unknown
Error Correction Type: Single-bit ECC
System Type: Unified
Associativity: 16-way Set-associative
Handle 0x0003, DMI type 4, 48 bytes
Processor Information
Socket Designation: Proc 1
Type: Central Processor
Family: Xeon
Manufacturer: Intel(R) Corporation
ID: E3 06 05 00 FF FB EB BF
Signature: Type 0, Family 6, Model 94, Stepping 3
Flags:
FPU (Floating-point unit on-chip)
VME (Virtual mode extension)
DE (Debugging extension)
PSE (Page size extension)
TSC (Time stamp counter)
MSR (Model specific registers)
PAE (Physical address extension)
MCE (Machine check exception)
CX8 (CMPXCHG8 instruction supported)
APIC (On-chip APIC hardware supported)
SEP (Fast system call)
MTRR (Memory type range registers)
PGE (Page global enable)
MCA (Machine check architecture)
CMOV (Conditional move instruction supported)
PAT (Page attribute table)
PSE-36 (36-bit page size extension)
CLFSH (CLFLUSH instruction supported)
DS (Debug store)
ACPI (ACPI supported)
MMX (MMX technology supported)
FXSR (FXSAVE and FXSTOR instructions supported)
SSE (Streaming SIMD extensions)
SSE2 (Streaming SIMD extensions 2)
SS (Self-snoop)
HTT (Multi-threading)
TM (Thermal monitor supported)
PBE (Pending break enabled)
Version: Intel(R) Xeon(R) CPU E3-1585L v5 @ 3.00GHz
Voltage: 1.0 V
External Clock: 100 MHz
Max Speed: 3700 MHz
Current Speed: 3000 MHz
Status: Populated, Enabled
Upgrade: Other
L1 Cache Handle: 0x0000
L2 Cache Handle: 0x0001
L3 Cache Handle: 0x0002
Serial Number: To Be Filled By O.E.M.
Asset Tag: To Be Filled By O.E.M.
Part Number: To Be Filled By O.E.M.
Core Count: 4
Core Enabled: 4
Thread Count: 8
Characteristics:
64-bit capable
Multi-Core
Hardware Thread
Execute Protection
Enhanced Virtualization
Power/Performance Control
Handle 0x0004, DMI type 0, 24 bytes
BIOS Information
Vendor: HP
Version: H07
Release Date: 05/23/2016
Address: 0xF0000
Runtime Size: 64 kB
ROM Size: 16 MB
Characteristics:
PCI is supported
PNP is supported
BIOS is upgradeable
BIOS shadowing is allowed
ESCD support is available
Boot from CD is supported
Selectable boot is supported
EDD is supported
5.25"/360 kB floppy services are supported (int 13h)
5.25"/1.2 MB floppy services are supported (int 13h)
3.5"/720 kB floppy services are supported (int 13h)
Print screen service is supported (int 5h)
8042 keyboard services are supported (int 9h)
Serial services are supported (int 14h)
Printer services are supported (int 17h)
CGA/mono video services are supported (int 10h)
ACPI is supported
USB legacy is supported
BIOS boot specification is supported
Function key-initiated network boot is supported
Targeted content distribution is supported
UEFI is supported
BIOS Revision: 1.0
Firmware Revision: 2.40
Handle 0x0005, DMI type 1, 27 bytes
System Information
Manufacturer: HP
Product Name: ProLiant m710x Server Cartridge
Version: Not Specified
Serial Number: CN66480BLA
UUID: CF6D6DE0-A5AB-512C-BCD4-5CF40EC490C1
Wake-up Type: Power Switch
SKU Number: 833105-B21
Family: ProLiant
Handle 0x0006, DMI type 16, 23 bytes
Physical Memory Array
Location: System Board Or Motherboard
Use: System Memory
Error Correction Type: Multi-bit ECC
Maximum Capacity: 64 GB
Error Information Handle: Not Provided
Number Of Devices: 4
Handle 0x0007, DMI type 17, 40 bytes
Memory Device
Array Handle: 0x0006
Error Information Handle: Not Provided
Total Width: 72 bits
Data Width: 64 bits
Size: 16384 MB
Form Factor: DIMM
Set: None
Locator: PROC 1 DIMM 1
Bank Locator: Not Specified
Type: DDR4
Type Detail: Synchronous
Speed: 2400 MT/s
Manufacturer: HP
Serial Number: Not Specified
Asset Tag: Not Specified
Part Number: 853289-091
Rank: 2
Configured Clock Speed: 2133 MT/s
Minimum Voltage: 1.2 V
Maximum Voltage: 1.2 V
Configured Voltage: 1.2 V
Handle 0x0008, DMI type 17, 40 bytes
Memory Device
Array Handle: 0x0006
Error Information Handle: Not Provided
Total Width: 72 bits
Data Width: 64 bits
Size: 16384 MB
Form Factor: DIMM
Set: 1
Locator: PROC 1 DIMM 2
Bank Locator: Not Specified
Type: DDR4
Type Detail: Synchronous
Speed: 2400 MT/s
Manufacturer: HP
Serial Number: Not Specified
Asset Tag: Not Specified
Part Number: 853289-091
Rank: 2
Configured Clock Speed: 2133 MT/s
Minimum Voltage: 1.2 V
Maximum Voltage: 1.2 V
Configured Voltage: 1.2 V
Handle 0x0009, DMI type 17, 40 bytes
Memory Device
Array Handle: 0x0006
Error Information Handle: Not Provided
Total Width: 72 bits
Data Width: 64 bits
Size: 16384 MB
Form Factor: DIMM
Set: 2
Locator: PROC 1 DIMM 3
Bank Locator: Not Specified
Type: DDR4
Type Detail: Synchronous
Speed: 2400 MT/s
Manufacturer: HP
Serial Number: Not Specified
Asset Tag: Not Specified
Part Number: 853289-091
Rank: 2
Configured Clock Speed: 2133 MT/s
Minimum Voltage: 1.2 V
Maximum Voltage: 1.2 V
Configured Voltage: 1.2 V
Handle 0x000A, DMI type 17, 40 bytes
Memory Device
Array Handle: 0x0006
Error Information Handle: Not Provided
Total Width: 72 bits
Data Width: 64 bits
Size: 16384 MB
Form Factor: DIMM
Set: 3
Locator: PROC 1 DIMM 4
Bank Locator: Not Specified
Type: DDR4
Type Detail: Synchronous
Speed: 2400 MT/s
Manufacturer: HP
Serial Number: Not Specified
Asset Tag: Not Specified
Part Number: 853289-091
Rank: 2
Configured Clock Speed: 2133 MT/s
Minimum Voltage: 1.2 V
Maximum Voltage: 1.2 V
Configured Voltage: 1.2 V
Handle 0x000B, DMI type 19, 31 bytes
Memory Array Mapped Address
Starting Address: 0x00000000000
Ending Address: 0x0007E7FFFFF
Range Size: 2024 MB
Physical Array Handle: 0x0006
Partition Width: 1
Handle 0x000C, DMI type 19, 31 bytes
Memory Array Mapped Address
Starting Address: 0x0000000100000000k
Ending Address: 0x00000010817FFFFFk
Range Size: 63512 MB
Physical Array Handle: 0x0006
Partition Width: 1
Handle 0x000D, DMI type 211, 7 bytes
OEM-specific Type
Header and Data:
D3 07 0D 00 03 00 00
Handle 0x000E, DMI type 2, 17 bytes
Base Board Information
Manufacturer: HP
Product Name: ProLiant m710x Server Cartridge
Version: Not Specified
Serial Number: CN66480ANY
Asset Tag:
Features:
Board is a hosting board
Board is removable
Board is replaceable
Location In Chassis: c2n1
Chassis Handle: 0x0000
Type: Motherboard
Contained Object Handles: 0
Handle 0x000F, DMI type 3, 21 bytes
Chassis Information
Manufacturer: HP
Type: Multi-system
Lock: Not Present
Version: Not Specified
Serial Number: CZ3702MD5K
Asset Tag:
Boot-up State: Safe
Power Supply State: Safe
Thermal State: Safe
Security Status: Unknown
OEM Information: 0x00000000
Height: 5 U
Number Of Power Cords: Unspecified
Contained Elements: 0
Handle 0x0010, DMI type 11, 5 bytes
OEM Strings
String 1: PSF:
String 2: Product ID: 833105-B21
String 3: CPN: HP Moonshot 1500 Chassis
String 4: OEM String:
Handle 0x0011, DMI type 38, 18 bytes
IPMI Device Information
Interface Type: KCS (Keyboard Control Style)
Specification Version: 2.0
I2C Slave Address: 0x10
NV Storage Device: Not Present
Base Address: 0x0000000000000CA2 (I/O)
Register Spacing: Successive Byte Boundaries
Handle 0x0012, DMI type 193, 9 bytes
OEM-specific Type
Header and Data:
C1 09 12 00 00 01 00 02 03
Strings:
0/0000
Handle 0x0013, DMI type 194, 5 bytes
OEM-specific Type
Header and Data:
C2 05 13 00 10
Handle 0x0014, DMI type 195, 7 bytes
OEM-specific Type
Header and Data:
C3 07 14 00 01 12 01
Strings:
$0E110855
Handle 0x0015, DMI type 197, 16 bytes
OEM-specific Type
Header and Data:
C5 10 15 00 03 00 00 01 FF 01 2D 00 00 00 00 00
Handle 0x0016, DMI type 198, 14 bytes
OEM-specific Type
Header and Data:
C6 0E 16 00 17 00 00 00 00 00 01 0A FF FF
Handle 0x0017, DMI type 199, 28 bytes
OEM-specific Type
Header and Data:
C7 1C 17 00 2C 00 00 00 15 20 01 07 E2 06 05 00
88 00 00 00 16 20 16 03 E3 06 05 00
Handle 0x0018, DMI type 201, 16 bytes
OEM-specific Type
Header and Data:
C9 10 18 00 10 02 00 00 40 0D 01 00 0E 00 00 80
Handle 0x0019, DMI type 215, 6 bytes
OEM-specific Type
Header and Data:
D7 06 19 00 00 05
Handle 0x001A, DMI type 216, 23 bytes
OEM-specific Type
Header and Data:
D8 17 1A 00 01 00 01 02 07 01 00 05 17 E0 07 00
00 00 00 00 00 00 00
Strings:
System ROM
v1.00 (05/23/2016)
Handle 0x001B, DMI type 216, 23 bytes
OEM-specific Type
Header and Data:
D8 17 1B 00 04 00 01 02 04 00 00 00 00 00 00 00
00 00 00 00 00 00 00
Strings:
Power Management Controller Firmware
0.0
Handle 0x001C, DMI type 216, 23 bytes
OEM-specific Type
Header and Data:
D8 17 1C 00 05 00 01 02 02 60 00 00 00 00 00 00
00 00 00 00 00 00 00
Strings:
Power Management Controller FW Bootloader
6.0
Handle 0x001D, DMI type 216, 23 bytes
OEM-specific Type
Header and Data:
D8 17 1D 00 08 00 01 00 01 09 09 00 00 00 00 00
00 00 00 00 00 00 00
Strings:
System Programmable Logic Device
Handle 0x001E, DMI type 216, 23 bytes
OEM-specific Type
Header and Data:
D8 17 1E 00 09 00 01 00 03 04 00 00 00 03 00 96
00 0A 00 00 00 01 00
Strings:
Server Platform Services (SPS) Firmware
Handle 0x001F, DMI type 219, 32 bytes
HP ProLiant Information
Power Features: 0x000000df
Omega Features: 0x00000000
Misc. Features: 0x00009001
iCRU: Yes
UEFI: No
Handle 0x0020, DMI type 223, 11 bytes
OEM-specific Type
Header and Data:
DF 0B 20 00 66 46 70 00 00 00 00
Handle 0x0021, DMI type 224, 10 bytes
OEM-specific Type
Header and Data:
E0 0A 21 00 00 00 02 03 FE FF
Handle 0x0022, DMI type 226, 21 bytes
OEM-specific Type
Header and Data:
E2 15 22 00 38 33 33 31 30 35 43 4E 36 36 34 38
30 41 4E 59 01
Strings:
CN66480ANY
Handle 0x0023, DMI type 227, 22 bytes
OEM-specific Type
Header and Data:
E3 16 23 00 03 00 07 00 00 A0 01 00 FF FF FF FF
00 00 00 00 00 00
Handle 0x0024, DMI type 227, 22 bytes
OEM-specific Type
Header and Data:
E3 16 24 00 03 00 08 00 00 A2 01 00 FF FF FF FF
00 00 00 00 00 00
Handle 0x0025, DMI type 227, 22 bytes
OEM-specific Type
Header and Data:
E3 16 25 00 03 00 09 00 00 A4 01 00 FF FF FF FF
01 00 00 00 00 00
Handle 0x0026, DMI type 227, 22 bytes
OEM-specific Type
Header and Data:
E3 16 26 00 03 00 0A 00 00 A6 01 00 FF FF FF FF
01 00 00 00 00 00
Handle 0x0027, DMI type 228, 14 bytes
OEM-specific Type
Header and Data:
E4 0E 27 00 00 00 00 00 00 00 00 FF 00 00
Handle 0x0028, DMI type 228, 14 bytes
OEM-specific Type
Header and Data:
E4 0E 28 00 01 00 00 00 00 00 00 FF 00 00
Handle 0x0029, DMI type 228, 14 bytes
OEM-specific Type
Header and Data:
E4 0E 29 00 02 00 00 00 00 00 00 FF 00 00
Handle 0x002A, DMI type 228, 14 bytes
OEM-specific Type
Header and Data:
E4 0E 2A 00 03 00 00 00 00 00 00 FF 00 00
Handle 0x002B, DMI type 228, 14 bytes
OEM-specific Type
Header and Data:
E4 0E 2B 00 04 00 00 00 00 00 00 FF 00 00
Handle 0x002C, DMI type 228, 14 bytes
OEM-specific Type
Header and Data:
E4 0E 2C 00 05 00 00 00 00 00 00 FF 00 00
Handle 0x002D, DMI type 228, 14 bytes
OEM-specific Type
Header and Data:
E4 0E 2D 00 06 00 00 00 00 00 00 FF 00 00
Handle 0x002E, DMI type 228, 14 bytes
OEM-specific Type
Header and Data:
E4 0E 2E 00 07 00 00 00 00 00 00 FF 00 00
Handle 0x002F, DMI type 228, 14 bytes
OEM-specific Type
Header and Data:
E4 0E 2F 00 08 06 00 00 00 00 00 FF 01 00
Handle 0x0030, DMI type 229, 100 bytes
OEM-specific Type
Header and Data:
E5 64 30 00 24 4F 43 53 00 B0 F1 77 00 00 00 00
00 40 00 00 24 4F 43 42 00 50 F0 77 00 00 00 00
00 60 01 00 24 48 44 44 00 30 F0 77 00 00 00 00
00 20 00 00 24 57 48 45 00 20 EF 77 00 00 00 00
00 10 00 00 24 53 4D 56 98 2E F2 77 00 00 00 00
08 00 00 00 24 5A 58 54 00 10 EF 77 00 00 00 00
3B 00 00 00
Handle 0x0031, DMI type 232, 14 bytes
OEM-specific Type
Header and Data:
E8 0E 31 00 07 00 11 00 00 00 B0 04 B0 04
Handle 0x0032, DMI type 232, 14 bytes
OEM-specific Type
Header and Data:
E8 0E 32 00 08 00 11 00 00 00 B0 04 B0 04
Handle 0x0033, DMI type 232, 14 bytes
OEM-specific Type
Header and Data:
E8 0E 33 00 09 00 11 00 00 00 B0 04 B0 04
Handle 0x0034, DMI type 232, 14 bytes
OEM-specific Type
Header and Data:
E8 0E 34 00 0A 00 11 00 00 00 B0 04 B0 04
Handle 0x0035, DMI type 237, 9 bytes
OEM-specific Type
Header and Data:
ED 09 35 00 07 00 01 02 03
Strings:
Hynix
HMA82GS7AFR8N-UH
31F9E4B8
Handle 0x0036, DMI type 237, 9 bytes
OEM-specific Type
Header and Data:
ED 09 36 00 08 00 01 02 03
Strings:
Hynix
HMA82GS7AFR8N-UH
31F9E4B6
Handle 0x0037, DMI type 237, 9 bytes
OEM-specific Type
Header and Data:
ED 09 37 00 09 00 01 02 03
Strings:
Hynix
HMA82GS7AFR8N-UH
31F9E96B
Handle 0x0038, DMI type 237, 9 bytes
OEM-specific Type
Header and Data:
ED 09 38 00 0A 00 01 02 03
Strings:
Hynix
HMA82GS7AFR8N-UH
31F9E4B9
Handle 0x0039, DMI type 9, 17 bytes
System Slot Information
Designation: PCI-E Slot 2
Type: x4 PCI Express 2
Current Usage: Available
Length: Other
ID: 2
Characteristics:
3.3 V is provided
PME signal is supported
Bus Address: 0000:02:00.0
Handle 0x003A, DMI type 9, 17 bytes
System Slot Information
Designation: PCI-E Slot 1
Type: x1 PCI Express 2
Current Usage: Available
Length: Other
ID: 1
Characteristics:
3.3 V is provided
PME signal is supported
Bus Address: 0000:05:00.0
Handle 0x003B, DMI type 9, 17 bytes
System Slot Information
Designation: PCI-E Slot 5
Type: x2 PCI Express 2
Current Usage: Available
Length: Other
ID: 5
Characteristics:
3.3 V is provided
PME signal is supported
Bus Address: 0000:08:00.0
Handle 0x003C, DMI type 9, 17 bytes
System Slot Information
Designation: PCI-E Slot 0
Type: x1 PCI Express 3 x4
Current Usage: Available
Length: Unknown
ID: 0
Characteristics:
3.3 V is provided
PME signal is supported
Bus Address: 0000:fe:00.0
Handle 0x003D, DMI type 9, 17 bytes
System Slot Information
Designation: PCI-E Slot 0
Type: x1 PCI Express 3 x4
Current Usage: Available
Length: Unknown
ID: 0
Characteristics:
3.3 V is provided
PME signal is supported
Bus Address: 0000:fe:00.0
Handle 0x003E, DMI type 9, 17 bytes
System Slot Information
Designation: PCI-E Slot 4
Type: x4 PCI Express 2
Current Usage: Available
Length: Unknown
ID: 4
Characteristics:
3.3 V is provided
PME signal is supported
Bus Address: 0000:0b:00.0
Handle 0x003F, DMI type 9, 17 bytes
System Slot Information
Designation: PCI-E Slot 3
Type: x4 PCI Express 2
Current Usage: In Use
Length: Other
ID: 3
Characteristics:
3.3 V is provided
PME signal is supported
Bus Address: 0000:0e:00.0
Handle 0x0040, DMI type 9, 17 bytes
System Slot Information
Designation: PCI-E Slot 1
Type: x4 PCI Express 3 x4
Current Usage: Available
Length: Unknown
ID: 1
Characteristics:
3.3 V is provided
PME signal is supported
Bus Address: 0000:fe:00.0
Handle 0x0041, DMI type 9, 17 bytes
System Slot Information
Designation: PCI-E Slot 1
Type: x4 PCI Express 3 x4
Current Usage: Available
Length: Unknown
ID: 1
Characteristics:
3.3 V is provided
PME signal is supported
Bus Address: 0000:fe:00.0
Handle 0x0042, DMI type 233, 41 bytes
HP BIOS PXE NIC PCI and MAC Information
NIC 1: PCI device 11:00.0, MAC address 00:FD:45:50:6C:01
Handle 0x0043, DMI type 233, 41 bytes
HP BIOS PXE NIC PCI and MAC Information
NIC 2: PCI device 11:00.0, MAC address 00:FD:45:50:6C:02
Handle 0x0044, DMI type 32, 11 bytes
System Boot Information
Status: No errors detected
Handle 0x0045, DMI type 196, 15 bytes
OEM-specific Type
Header and Data:
C4 0F 45 00 00 00 00 00 00 00 02 01 00 01 02
Handle 0x0046, DMI type 41, 11 bytes
Onboard Device
Reference Designation: Embedded LOM 1 Port 1
Type: Ethernet
Status: Enabled
Type Instance: 1
Bus Address: 0000:11:00.0
Handle 0x0047, DMI type 41, 11 bytes
Onboard Device
Reference Designation: Embedded LOM 1 Port 2
Type: Ethernet
Status: Enabled
Type Instance: 2
Bus Address: 0000:11:00.0
Handle 0x0048, DMI type 41, 11 bytes
Onboard Device
Reference Designation: Embedded SATA Controller #1
Type: SATA Controller
Status: Enabled
Type Instance: 1
Bus Address: 0000:00:17.0
Handle 0x0049, DMI type 202, 13 bytes
OEM-specific Type
Header and Data:
CA 0D 49 00 07 00 FF 01 01 01 00 00 00
Handle 0x004A, DMI type 202, 13 bytes
OEM-specific Type
Header and Data:
CA 0D 4A 00 08 00 FF 02 01 02 00 00 00
Handle 0x004B, DMI type 202, 13 bytes
OEM-specific Type
Header and Data:
CA 0D 4B 00 09 00 FF 03 01 03 00 00 00
Handle 0x004C, DMI type 202, 13 bytes
OEM-specific Type
Header and Data:
CA 0D 4C 00 0A 00 FF 04 01 04 00 00 00
Handle 0x004D, DMI type 203, 34 bytes
OEM-specific Type
Header and Data:
CB 22 4D 00 39 00 FE FF FF FF FF FF FF FF FF FF
FF FF FE FF 00 00 09 0A 02 01 FF FF 01 02 03 04
FE FF
Strings:
PciRoot(0x0)/Pci(0x1D,0x0)/Pci(0x0,0x0)
PCI.Slot.2.1
Empty slot 2
Slot 2
Handle 0x004E, DMI type 203, 34 bytes
OEM-specific Type
Header and Data:
CB 22 4E 00 3A 00 FE FF FF FF FF FF FF FF FF FF
FF FF FE FF 00 00 09 0A 01 01 FF FF 01 02 03 04
FE FF
Strings:
PciRoot(0x0)/Pci(0x1D,0x5)/Pci(0x0,0x0)
PCI.Slot.1.1
Empty slot 1
Slot 1
Handle 0x004F, DMI type 203, 34 bytes
OEM-specific Type
Header and Data:
CB 22 4F 00 3B 00 FE FF FF FF FF FF FF FF FF FF
FF FF FE FF 00 00 09 0A 05 01 FF FF 01 02 03 04
FE FF
Strings:
PciRoot(0x0)/Pci(0x1C,0x0)/Pci(0x0,0x0)
PCI.Slot.5.1
Empty slot 5
Slot 5
Handle 0x0050, DMI type 203, 34 bytes
OEM-specific Type
Header and Data:
CB 22 50 00 3C 00 FE FF FF FF FF FF FF FF FF FF
FF FF FE FF 00 00 09 0A 01 01 FF FF 01 02 03 04
FE FF
Strings:
PciRoot(0x0)/Pci(0x1C,0x2)/Pci(0x0,0x0)
PCI.Slot.1.1
Unknown
Slot 0
Handle 0x0051, DMI type 203, 34 bytes
OEM-specific Type
Header and Data:
CB 22 51 00 3D 00 FE FF FF FF FF FF FF FF FF FF
FF FF FE FF 00 00 09 0A 01 01 FF FF 01 02 03 04
FE FF
Strings:
PciRoot(0x0)/Pci(0x1C,0x2)/Pci(0x0,0x0)
PCI.Slot.1.1
Unknown
Slot 0
Handle 0x0052, DMI type 203, 34 bytes
OEM-specific Type
Header and Data:
CB 22 52 00 3E 00 FE FF FF FF FF FF FF FF FF FF
FF FF FE FF 00 00 09 0A 04 01 FF FF 01 02 03 04
FE FF
Strings:
PciRoot(0x0)/Pci(0x1C,0x4)/Pci(0x0,0x0)
PCI.Slot.4.1
Empty slot 4
Slot 4
Handle 0x0053, DMI type 203, 34 bytes
OEM-specific Type
Header and Data:
CB 22 53 00 3F 00 FE FF 79 11 0F 01 79 11 01 00
01 08 FE FF 00 00 10 0A 03 01 FF FF 01 02 03 04
FE FF
Strings:
PciRoot(0x0)/Pci(0x1B,0x0)/Pci(0x0,0x0)
NVMe.Slot.3.1
NVM Express Controller
Slot 3
Handle 0x0054, DMI type 203, 34 bytes
OEM-specific Type
Header and Data:
CB 22 54 00 40 00 FE FF FF FF FF FF FF FF FF FF
FF FF FE FF 00 00 09 0A 01 01 FF FF 01 02 03 04
FE FF
Strings:
PciRoot(0x0)/Pci(0x1C,0x2)/Pci(0x0,0x0)
PCI.Slot.1.1
Empty slot 1
Slot 0
Handle 0x0055, DMI type 203, 34 bytes
OEM-specific Type
Header and Data:
CB 22 55 00 41 00 FE FF FF FF FF FF FF FF FF FF
FF FF FE FF 00 00 09 0A 01 01 FF FF 01 02 03 04
FE FF
Strings:
PciRoot(0x0)/Pci(0x1C,0x2)/Pci(0x0,0x0)
PCI.Slot.1.1
Empty slot 1
Slot 0
Handle 0x0056, DMI type 203, 34 bytes
OEM-specific Type
Header and Data:
CB 22 56 00 46 00 FE FF B3 15 07 10 90 15 04 22
02 00 FE FF 00 00 04 01 01 01 FF FF 01 02 03 04
FE FF
Strings:
PciRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x0)/Ctrl(0x1)
NIC.LOM.1.1
Port 1 - Mellanox Network Adapter
Embedded LOM 1
Handle 0x0057, DMI type 203, 34 bytes
OEM-specific Type
Header and Data:
CB 22 57 00 47 00 FE FF B3 15 07 10 90 15 04 22
02 00 FE FF 00 00 04 01 01 02 FF FF 01 02 03 04
FE FF
Strings:
PciRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x0)/Ctrl(0x2)
NIC.LOM.1.2
Port 2 - Mellanox Network Adapter
Embedded LOM 1
Handle 0x0058, DMI type 203, 34 bytes
OEM-specific Type
Header and Data:
CB 22 58 00 48 00 FE FF 86 80 02 A1 3C 10 65 81
01 06 FE FF 00 00 06 08 01 01 FF FF 01 02 03 04
FE FF
Strings:
PciRoot(0x0)/Pci(0x17,0x0)
SATA.Emb.1.1
Embedded SATA Controller #1
Embedded SATA Controller #1
Handle 0x0059, DMI type 234, 16 bytes
OEM-specific Type
Header and Data:
EA 10 59 00 FE FF C0 00 01 A0 00 00 00 00 00 00
Handle 0x005A, DMI type 234, 12 bytes
OEM-specific Type
Header and Data:
EA 0C 5A 00 27 00 60 03 01 01 00 00
Handle 0x005B, DMI type 240, 39 bytes
OEM-specific Type
Header and Data:
F0 27 5B 00 56 00 18 1E 24 00 01 08 48 0D 00 00
00 00 00 0F 00 00 00 00 00 00 00 0B 00 00 00 00
00 00 00 02 00 00 00
Strings:
02.36.70.00
Handle 0xFEFF, DMI type 127, 4 bytes
End Of Table

View file

@ -1,536 +0,0 @@
# dmidecode 3.1
Getting SMBIOS data from sysfs.
SMBIOS 3.0 present.
32 structures occupying 2366 bytes.
Table at 0x6FB76000.
Handle 0x0000, DMI type 0, 24 bytes
BIOS Information
Vendor: American Megatrends Inc.
Version: 2.2
Release Date: 05/16/2018
Address: 0xF0000
Runtime Size: 64 kB
ROM Size: 16 MB
Characteristics:
PCI is supported
BIOS is upgradeable
BIOS shadowing is allowed
Boot from CD is supported
Selectable boot is supported
BIOS ROM is socketed
EDD is supported
5.25"/1.2 MB floppy services are supported (int 13h)
3.5"/720 kB floppy services are supported (int 13h)
3.5"/2.88 MB floppy services are supported (int 13h)
Print screen service is supported (int 5h)
Serial services are supported (int 14h)
Printer services are supported (int 17h)
ACPI is supported
USB legacy is supported
BIOS boot specification is supported
Targeted content distribution is supported
UEFI is supported
BIOS Revision: 5.11
Handle 0x0001, DMI type 1, 27 bytes
System Information
Manufacturer: Supermicro
Product Name: SYS-5039MS-H12TRF-OS012
Version: 0123456789
Serial Number: E235735X6B01665
UUID: 00000000-0000-0000-0000-0CC47AE14338
Wake-up Type: Power Switch
SKU Number: To be filled by O.E.M.
Family: To be filled by O.E.M.
Handle 0x0002, DMI type 2, 15 bytes
Base Board Information
Manufacturer: Supermicro
Product Name: X11SSE-F
Version: 1.01
Serial Number: ZM169S040205
Asset Tag: To be filled by O.E.M.
Features:
Board is a hosting board
Board is replaceable
Location In Chassis: To be filled by O.E.M.
Chassis Handle: 0x0003
Type: Motherboard
Contained Object Handles: 0
Handle 0x0003, DMI type 3, 22 bytes
Chassis Information
Manufacturer: Supermicro
Type: Other
Lock: Not Present
Version: 0123456789
Serial Number: C9390AF40A20098
Asset Tag: To be filled by O.E.M.
Boot-up State: Safe
Power Supply State: Safe
Thermal State: Safe
Security Status: None
OEM Information: 0x00000000
Height: Unspecified
Number Of Power Cords: 1
Contained Elements: 0
SKU Number: To be filled by O.E.M.
Handle 0x0004, DMI type 9, 17 bytes
System Slot Information
Designation: CPU MICRO-LP PCI-E 3.0 X8
Type: x8 PCI Express 3 x8
Current Usage: In Use
Length: Short
ID: 1
Characteristics:
3.3 V is provided
Opening is shared
PME signal is supported
Bus Address: 0000:01:00.0
Handle 0x0005, DMI type 11, 5 bytes
OEM Strings
String 1: Intel Skylake-S/Skylake-H/Greenlow
String 2: Supermicro motherboard-X11 Series
Handle 0x0006, DMI type 32, 20 bytes
System Boot Information
Status: No errors detected
Handle 0x0007, DMI type 39, 22 bytes
System Power Supply
Power Unit Group: 1
Location: PSU1
Name: PWS-2K04F-1R
Manufacturer: SUPERMICRO
Serial Number: P2K4FCG37KT0851
Asset Tag: N/A
Model Part Number: PWS-2K04F-1R
Revision: 1.0
Max Power Capacity: 2000 W
Status: Present, OK
Type: Switching
Input Voltage Range Switching: Auto-switch
Plugged: Yes
Hot Replaceable: No
Handle 0x0008, DMI type 39, 22 bytes
System Power Supply
Power Unit Group: 2
Location: PSU2
Name: PWS-2K04F-1R
Manufacturer: SUPERMICRO
Serial Number: P2K4FCG37KT0852
Asset Tag: N/A
Model Part Number: PWS-2K04F-1R
Revision: 1.0
Max Power Capacity: 2000 W
Status: Present, OK
Type: Switching
Input Voltage Range Switching: Auto-switch
Plugged: Yes
Hot Replaceable: No
Handle 0x0009, DMI type 41, 11 bytes
Onboard Device
Reference Designation: ASPEED Video AST2400
Type: Video
Status: Enabled
Type Instance: 1
Bus Address: 0000:04:00.0
Handle 0x000A, DMI type 38, 18 bytes
IPMI Device Information
Interface Type: KCS (Keyboard Control Style)
Specification Version: 2.0
I2C Slave Address: 0x10
NV Storage Device: Not Present
Base Address: 0x0000000000000CA2 (I/O)
Register Spacing: Successive Byte Boundaries
Handle 0x000B, DMI type 7, 19 bytes
Cache Information
Socket Designation: L1 Cache
Configuration: Enabled, Not Socketed, Level 1
Operational Mode: Write Back
Location: Internal
Installed Size: 128 kB
Maximum Size: 128 kB
Supported SRAM Types:
Synchronous
Installed SRAM Type: Synchronous
Speed: Unknown
Error Correction Type: Parity
System Type: Data
Associativity: 8-way Set-associative
Handle 0x000C, DMI type 7, 19 bytes
Cache Information
Socket Designation: L1 Cache
Configuration: Enabled, Not Socketed, Level 1
Operational Mode: Write Back
Location: Internal
Installed Size: 128 kB
Maximum Size: 128 kB
Supported SRAM Types:
Synchronous
Installed SRAM Type: Synchronous
Speed: Unknown
Error Correction Type: Parity
System Type: Instruction
Associativity: 8-way Set-associative
Handle 0x000D, DMI type 7, 19 bytes
Cache Information
Socket Designation: L2 Cache
Configuration: Enabled, Not Socketed, Level 2
Operational Mode: Write Back
Location: Internal
Installed Size: 1024 kB
Maximum Size: 1024 kB
Supported SRAM Types:
Synchronous
Installed SRAM Type: Synchronous
Speed: Unknown
Error Correction Type: Single-bit ECC
System Type: Unified
Associativity: 4-way Set-associative
Handle 0x000E, DMI type 7, 19 bytes
Cache Information
Socket Designation: L3 Cache
Configuration: Enabled, Not Socketed, Level 3
Operational Mode: Write Back
Location: Internal
Installed Size: 8192 kB
Maximum Size: 8192 kB
Supported SRAM Types:
Synchronous
Installed SRAM Type: Synchronous
Speed: Unknown
Error Correction Type: Multi-bit ECC
System Type: Unified
Associativity: 16-way Set-associative
Handle 0x000F, DMI type 4, 48 bytes
Processor Information
Socket Designation: CPU
Type: Central Processor
Family: Xeon
Manufacturer: Intel(R) Corporation
ID: E3 06 05 00 FF FB EB BF
Signature: Type 0, Family 6, Model 94, Stepping 3
Flags:
FPU (Floating-point unit on-chip)
VME (Virtual mode extension)
DE (Debugging extension)
PSE (Page size extension)
TSC (Time stamp counter)
MSR (Model specific registers)
PAE (Physical address extension)
MCE (Machine check exception)
CX8 (CMPXCHG8 instruction supported)
APIC (On-chip APIC hardware supported)
SEP (Fast system call)
MTRR (Memory type range registers)
PGE (Page global enable)
MCA (Machine check architecture)
CMOV (Conditional move instruction supported)
PAT (Page attribute table)
PSE-36 (36-bit page size extension)
CLFSH (CLFLUSH instruction supported)
DS (Debug store)
ACPI (ACPI supported)
MMX (MMX technology supported)
FXSR (FXSAVE and FXSTOR instructions supported)
SSE (Streaming SIMD extensions)
SSE2 (Streaming SIMD extensions 2)
SS (Self-snoop)
HTT (Multi-threading)
TM (Thermal monitor supported)
PBE (Pending break enabled)
Version: Intel(R) Xeon(R) CPU E3-1245 v5 @ 3.50GHz
Voltage: 1.1 V
External Clock: 100 MHz
Max Speed: 3900 MHz
Current Speed: 3500 MHz
Status: Populated, Enabled
Upgrade: Other
L1 Cache Handle: 0x000C
L2 Cache Handle: 0x000D
L3 Cache Handle: 0x000E
Serial Number: To Be Filled By O.E.M.
Asset Tag: To Be Filled By O.E.M.
Part Number: To Be Filled By O.E.M.
Core Count: 4
Core Enabled: 4
Thread Count: 8
Characteristics:
64-bit capable
Multi-Core
Hardware Thread
Execute Protection
Enhanced Virtualization
Power/Performance Control
Handle 0x0010, DMI type 16, 23 bytes
Physical Memory Array
Location: System Board Or Motherboard
Use: System Memory
Error Correction Type: Single-bit ECC
Maximum Capacity: 64 GB
Error Information Handle: Not Provided
Number Of Devices: 4
Handle 0x0011, DMI type 17, 40 bytes
Memory Device
Array Handle: 0x0010
Error Information Handle: Not Provided
Total Width: Unknown
Data Width: Unknown
Size: No Module Installed
Form Factor: Unknown
Set: None
Locator: DIMMA1
Bank Locator: P0_Node0_Channel0_Dimm0
Type: Unknown
Type Detail: None
Speed: Unknown
Manufacturer: Not Specified
Serial Number: Not Specified
Asset Tag: Not Specified
Part Number: Not Specified
Rank: Unknown
Configured Clock Speed: Unknown
Minimum Voltage: Unknown
Maximum Voltage: Unknown
Configured Voltage: Unknown
Handle 0x0012, DMI type 17, 40 bytes
Memory Device
Array Handle: 0x0010
Error Information Handle: Not Provided
Total Width: 72 bits
Data Width: 64 bits
Size: 16384 MB
Form Factor: DIMM
Set: None
Locator: DIMMA2
Bank Locator: P0_Node0_Channel0_Dimm1
Type: DDR4
Type Detail: Synchronous
Speed: 2400 MT/s
Manufacturer: Micron
Serial Number: 13DA8BB7
Asset Tag: DIMMA2_AssetTag(16/36)
Part Number: 18ADF2G72AZ-2G3B1
Rank: 2
Configured Clock Speed: 2133 MT/s
Minimum Voltage: 1.2 V
Maximum Voltage: 1.2 V
Configured Voltage: 1.2 V
Handle 0x0013, DMI type 17, 40 bytes
Memory Device
Array Handle: 0x0010
Error Information Handle: Not Provided
Total Width: Unknown
Data Width: Unknown
Size: No Module Installed
Form Factor: Unknown
Set: None
Locator: DIMMB1
Bank Locator: P0_Node0_Channel1_Dimm0
Type: Unknown
Type Detail: None
Speed: Unknown
Manufacturer: Not Specified
Serial Number: Not Specified
Asset Tag: Not Specified
Part Number: Not Specified
Rank: Unknown
Configured Clock Speed: Unknown
Minimum Voltage: Unknown
Maximum Voltage: Unknown
Configured Voltage: Unknown
Handle 0x0014, DMI type 17, 40 bytes
Memory Device
Array Handle: 0x0010
Error Information Handle: Not Provided
Total Width: 72 bits
Data Width: 64 bits
Size: 16384 MB
Form Factor: DIMM
Set: None
Locator: DIMMB2
Bank Locator: P0_Node0_Channel1_Dimm1
Type: DDR4
Type Detail: Synchronous
Speed: 2400 MT/s
Manufacturer: Micron
Serial Number: 13DA8C08
Asset Tag: DIMMB2_AssetTag(16/36)
Part Number: 18ADF2G72AZ-2G3B1
Rank: 2
Configured Clock Speed: 2133 MT/s
Minimum Voltage: 1.2 V
Maximum Voltage: 1.2 V
Configured Voltage: 1.2 V
Handle 0x0015, DMI type 19, 31 bytes
Memory Array Mapped Address
Starting Address: 0x00000000000
Ending Address: 0x007FFFFFFFF
Range Size: 32 GB
Physical Array Handle: 0x0010
Partition Width: 2
Handle 0x0016, DMI type 15, 73 bytes
System Event Log
Area Length: 65535 bytes
Header Start Offset: 0x0000
Header Length: 16 bytes
Data Start Offset: 0x0010
Access Method: Memory-mapped physical 32-bit address
Access Address: 0xFF610000
Status: Valid, Not Full
Change Token: 0x00000001
Header Format: Type 1
Supported Log Type Descriptors: 25
Descriptor 1: Single-bit ECC memory error
Data Format 1: Multiple-event handle
Descriptor 2: Multi-bit ECC memory error
Data Format 2: Multiple-event handle
Descriptor 3: Parity memory error
Data Format 3: None
Descriptor 4: Bus timeout
Data Format 4: None
Descriptor 5: I/O channel block
Data Format 5: None
Descriptor 6: Software NMI
Data Format 6: None
Descriptor 7: POST memory resize
Data Format 7: None
Descriptor 8: POST error
Data Format 8: POST results bitmap
Descriptor 9: PCI parity error
Data Format 9: Multiple-event handle
Descriptor 10: PCI system error
Data Format 10: Multiple-event handle
Descriptor 11: CPU failure
Data Format 11: None
Descriptor 12: EISA failsafe timer timeout
Data Format 12: None
Descriptor 13: Correctable memory log disabled
Data Format 13: None
Descriptor 14: Logging disabled
Data Format 14: None
Descriptor 15: System limit exceeded
Data Format 15: None
Descriptor 16: Asynchronous hardware timer expired
Data Format 16: None
Descriptor 17: System configuration information
Data Format 17: None
Descriptor 18: Hard disk information
Data Format 18: None
Descriptor 19: System reconfigured
Data Format 19: None
Descriptor 20: Uncorrectable CPU-complex error
Data Format 20: None
Descriptor 21: Log area reset/cleared
Data Format 21: None
Descriptor 22: System boot
Data Format 22: None
Descriptor 23: End of log
Data Format 23: None
Descriptor 24: OEM-specific
Data Format 24: OEM-specific
Descriptor 25: OEM-specific
Data Format 25: OEM-specific
Handle 0x0017, DMI type 20, 35 bytes
Memory Device Mapped Address
Starting Address: 0x00000000000
Ending Address: 0x003FFFFFFFF
Range Size: 16 GB
Physical Device Handle: 0x0012
Memory Array Mapped Address Handle: 0x0015
Partition Row Position: Unknown
Interleave Position: 1
Interleaved Data Depth: 2
Handle 0x0018, DMI type 20, 35 bytes
Memory Device Mapped Address
Starting Address: 0x00400000000
Ending Address: 0x007FFFFFFFF
Range Size: 16 GB
Physical Device Handle: 0x0014
Memory Array Mapped Address Handle: 0x0015
Partition Row Position: Unknown
Interleave Position: 2
Interleaved Data Depth: 2
Handle 0x0019, DMI type 221, 26 bytes
OEM-specific Type
Header and Data:
DD 1A 19 00 03 01 00 04 01 00 08 00 02 00 00 00
00 C6 00 03 00 00 05 00 00 00
Strings:
Reference Code - CPU
uCode Version
TXT ACM version
Handle 0x001A, DMI type 221, 68 bytes
OEM-specific Type
Header and Data:
DD 44 1A 00 09 01 00 04 01 00 08 00 02 03 FF FF
FF FF FF 04 00 FF FF FF 31 00 05 00 FF FF FF 31
00 06 00 FF FF FF FF FF 07 00 3E 00 00 00 00 08
00 34 00 00 00 00 09 00 3E 00 00 00 00 0A 00 34
00 00 00 00
Strings:
Reference Code - SKL PCH
PCH-CRID Status
Disabled
PCH-CRID Original Value
PCH-CRID New Value
OPROM - RST - RAID
SKL PCH H Bx Hsio Version
SKL PCH H Dx Hsio Version
SKL PCH LP Bx Hsio Version
SKL PCH LP Cx Hsio Version
Handle 0x001B, DMI type 221, 54 bytes
OEM-specific Type
Header and Data:
DD 36 1B 00 07 01 00 04 01 00 08 00 02 00 02 01
00 00 00 03 00 04 01 00 00 00 04 05 FF FF FF FF
FF 06 00 FF FF FF 07 00 07 00 FF FF FF 07 00 08
00 FF FF FF 00 00
Strings:
Reference Code - SA - System Agent
Reference Code - MRC
SA - PCIe Version
SA-CRID Status
Disabled
SA-CRID Original Value
SA-CRID New Value
OPROM - VBIOS
Handle 0x001C, DMI type 40, 27 bytes
Additional Information 1
Handle 0x001D, DMI type 40, 27 bytes
Additional Information 1
Handle 0x001E, DMI type 136, 6 bytes
OEM-specific Type
Header and Data:
88 06 1E 00 00 00
Handle 0x001F, DMI type 127, 4 bytes
End Of Table

View file

@ -3,22 +3,18 @@ from tests.conftest import parametrize_with_fixtures
@parametrize_with_fixtures(
"lldp/",
only_filenames=[
"dedibox1.txt",
],
)
'lldp/', only_filenames=[
'dedibox1.txt',
])
def test_lldp_parse_with_port_desc(fixture):
lldp = LLDP(fixture)
assert lldp.get_switch_port("enp1s0f0") == "RJ-9"
assert lldp.get_switch_port('enp1s0f0') == 'RJ-9'
@parametrize_with_fixtures(
"lldp/",
only_filenames=[
"qfx.txt",
],
)
'lldp/', only_filenames=[
'qfx.txt',
])
def test_lldp_parse_without_ifname(fixture):
lldp = LLDP(fixture)
assert lldp.get_switch_port("eth0") == "xe-0/0/1"
assert lldp.get_switch_port('eth0') == 'xe-0/0/1'

View file

@ -1,12 +1,9 @@
from netbox_agent.dmidecode import parse
from netbox_agent.server import ServerBase
from netbox_agent.vendors.hp import HPHost
from netbox_agent.vendors.qct import QCTHost
from netbox_agent.vendors.supermicro import SupermicroHost
from tests.conftest import parametrize_with_fixtures
@parametrize_with_fixtures("dmidecode/")
@parametrize_with_fixtures('dmidecode/')
def test_init(fixture):
dmi = parse(fixture)
server = ServerBase(dmi)
@ -14,78 +11,33 @@ def test_init(fixture):
@parametrize_with_fixtures(
"dmidecode/",
only_filenames=[
"HP_SL4540_Gen8",
"HP_BL460c_Gen9",
"HP_DL380p_Gen8",
"HP_SL4540_Gen8" "HP_ProLiant_BL460c_Gen10_Graphics_Exp",
],
)
'dmidecode/', only_filenames=[
'HP_SL4540_Gen8',
'HP_BL460c_Gen9',
'HP_DL380p_Gen8',
'HP_SL4540_Gen8'
])
def test_hp_service_tag(fixture):
dmi = parse(fixture)
server = HPHost(dmi)
assert server.get_service_tag() == "4242"
@parametrize_with_fixtures("dmidecode/", only_filenames=["HP_ProLiant_m710x"])
def test_moonshot_blade(fixture):
dmi = parse(fixture)
server = HPHost(dmi)
assert server.get_service_tag() == "CN66480BLA"
assert server.get_chassis_service_tag() == "CZ3702MD5K"
assert server.is_blade() is True
assert server.own_expansion_slot() is False
@parametrize_with_fixtures("dmidecode/", only_filenames=["SYS-5039MS-H12TRF-OS012.txt"])
def test_supermicro_blade(fixture):
dmi = parse(fixture)
server = SupermicroHost(dmi)
assert server.get_service_tag() == "E235735X6B01665"
assert server.get_chassis_service_tag() == "C9390AF40A20098"
assert server.get_chassis() == "SYS-5039MS-H12TRF-OS012"
assert server.is_blade() is True
@parametrize_with_fixtures("dmidecode/", only_filenames=["SM_SYS-6018R"])
def test_supermicro_pizza(fixture):
dmi = parse(fixture)
server = SupermicroHost(dmi)
assert server.get_service_tag() == "A177950X7709591"
assert server.get_chassis() == "SYS-6018R-TDTPR"
assert server.is_blade() is False
@parametrize_with_fixtures("dmidecode/", only_filenames=["QCT_X10E-9N"])
def test_qct_x10(fixture):
dmi = parse(fixture)
server = QCTHost(dmi)
assert server.get_service_tag() == "QTFCQ57140285"
@parametrize_with_fixtures("dmidecode/", only_filenames=["unknown.txt"])
def test_generic_host_service_tag(fixture):
dmi = parse(fixture)
server = ServerBase(dmi)
assert server.get_service_tag() == "42"
@parametrize_with_fixtures("dmidecode/", only_filenames=["unknown.txt"])
def test_generic_host_product_name(fixture):
dmi = parse(fixture)
server = ServerBase(dmi)
assert server.get_product_name() == "SR"
assert server.get_service_tag() == '4242'
@parametrize_with_fixtures(
"dmidecode/", only_filenames=["HP_ProLiant_BL460c_Gen10_Graphics_Exp"]
)
def test_hp_blade_with_gpu_expansion(fixture):
'dmidecode/', only_filenames=[
'unknown.txt'
])
def test_generic_host_service_tag(fixture):
dmi = parse(fixture)
server = HPHost(dmi)
assert server.get_service_tag() == "4242"
assert server.get_chassis_service_tag() == "4343"
assert server.is_blade() is True
assert server.own_expansion_slot() is True
assert server.get_expansion_service_tag() == "4242 expansion"
server = ServerBase(dmi)
assert server.get_service_tag() == '42'
@parametrize_with_fixtures(
'dmidecode/', only_filenames=[
'unknown.txt'
])
def test_generic_host_product_name(fixture):
dmi = parse(fixture)
server = ServerBase(dmi)
assert server.get_product_name() == 'SR'