Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions module/sources/check_redfish/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ def __init__(self):
is then stored in the 'system_serial' custom field""",
default_value=False),

ConfigOption("collect_hardware_asset_tag",
bool,
description="""define if the asset tag reported by the BMC (system asset tag,
falling back to the chassis asset tag) is set as the NetBox device asset tag.
Placeholder values are ignored, as is an asset tag which another device in
NetBox already has, since asset tags are unique in NetBox""",
default_value=False),

ConfigOption("model_components_as_modules",
bool,
description="""model discovered hardware components (CPUs, memory, drives,
Expand Down
34 changes: 34 additions & 0 deletions module/sources/check_redfish/import_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ class CheckRedfish(SourceBase):
# minimum check_redfish inventory version
minimum_check_redfish_version = "2.0.0"

# values a BMC reports in place of an asset tag which was never set
unset_asset_tags = ["Default string", "NA", "N/A", "None", "Null", "Unknown", "Not Specified",
"To be filled by O.E.M."]

dependent_netbox_objects = [
NBTag,
NBManufacturer,
Expand Down Expand Up @@ -282,6 +286,25 @@ def get_service_tag(self):

return get_string_or_none(grab(self.inventory_file_content, "inventory.chassis.0.sku"))

def get_asset_tag(self):
"""
Return the asset tag of the system, falling back to the one of the chassis. Placeholder
values which a BMC reports for an asset tag that was never set are ignored.

Returns
-------
(str, None): the asset tag
"""

unset_asset_tags = [x.lower() for x in self.unset_asset_tags]

for path in ["inventory.system.0.asset_tag", "inventory.chassis.0.asset_tag"]:
asset_tag = get_string_or_none(grab(self.inventory_file_content, path))
if asset_tag is not None and asset_tag.lower() not in unset_asset_tags:
return asset_tag

return None

def update_device(self):

system = grab(self.inventory_file_content, "inventory.system.0")
Expand Down Expand Up @@ -351,6 +374,17 @@ def update_device(self):
if serial is not None:
device_data["serial"] = serial

if self.settings.collect_hardware_asset_tag is True:
asset_tag = self.get_asset_tag()
if asset_tag is not None:
# NetBox rejects an asset tag which another device already has, and with it the whole update
other_device = self.inventory.get_by_data(NBDevice, data={"asset_tag": asset_tag})
if other_device is not None and other_device is not self.device_object:
log.warning(f"Asset tag '{asset_tag}' of '{self.device_object.get_display_name()}' is already "
f"used by device '{other_device.get_display_name()}'. Not setting the asset tag.")
else:
device_data["asset_tag"] = asset_tag

self.device_object.update(data=device_data, source=self)

def update_power_supply(self):
Expand Down
6 changes: 6 additions & 0 deletions settings-example.ini
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,12 @@ inventory_file_path = /full/path/to/inventory/files
; serial number (the Dell PPID) is then stored in the 'system_serial' custom field
;dell_serial_from_service_tag = False

; define if the asset tag reported by the BMC (system asset tag, falling back to the
; chassis asset tag) is set as the NetBox device asset tag. Placeholder values are
; ignored, as is an asset tag which another device in NetBox already has, since asset tags
; are unique in NetBox
;collect_hardware_asset_tag = False

; model discovered hardware components (CPUs, memory, drives, controllers, NICs, ...) as
; NetBox modules instead of the deprecated inventory items. Requires NetBox >= 4.3, on
; older versions inventory items are used
Expand Down
106 changes: 106 additions & 0 deletions tests/test_check_redfish_asset_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020 - 2026 netbox-sync team. All rights reserved.
#
# netbox-sync.py
#
# This work is licensed under the terms of the MIT license.
# For a copy, see file LICENSE.txt included in this
# repository or visit: <https://opensource.org/licenses/MIT>.

"""The asset tag a BMC reports can be set as the NetBox device asset tag.

Drives the real update_device() against real NBDevice objects.
"""

from module.netbox.object_classes import NBDevice

ASSET_TAG = "AS00174157"


def system(system_asset_tag=ASSET_TAG, chassis_asset_tag=None):
"""A system as check_redfish reports it, with the asset tag on system and chassis."""

return {"inventory": {
"system": [{"id": "1", "name": "System", "manufacturer": "HPE", "model": "ProLiant DL380 Gen10",
"serial": "CZJ0000001", "host_name": "server01", "asset_tag": system_asset_tag,
"health_status": "OK", "power_state": "On"}],
"chassis": [{"id": "1", "asset_tag": chassis_asset_tag}],
}}


def run_update_device(context, content):
context.source.inventory_file_content = content
context.source.update_device()
return context.device


def test_asset_tag_is_not_synced_by_default(check_redfish_source):
"""Existing behaviour with the option off, which must not change."""

device = run_update_device(check_redfish_source(), system())

assert device.data.get("asset_tag") is None


def test_option_sets_the_system_asset_tag(check_redfish_source):
device = run_update_device(check_redfish_source(collect_hardware_asset_tag=True),
system(chassis_asset_tag="CHASSIS-TAG"))

assert device.data["asset_tag"] == ASSET_TAG


def test_chassis_asset_tag_is_the_fallback(check_redfish_source):
"""Some BMCs report the asset tag only on the chassis."""

device = run_update_device(check_redfish_source(collect_hardware_asset_tag=True),
system(system_asset_tag=None, chassis_asset_tag=ASSET_TAG))

assert device.data["asset_tag"] == ASSET_TAG


def test_padded_asset_tag_is_stripped(check_redfish_source):
device = run_update_device(check_redfish_source(collect_hardware_asset_tag=True),
system(system_asset_tag=f" {ASSET_TAG} "))

assert device.data["asset_tag"] == ASSET_TAG


def test_placeholder_asset_tags_are_ignored(check_redfish_source):
"""A placeholder on the system must not hide a real chassis asset tag, nor be synced itself."""

context = check_redfish_source(collect_hardware_asset_tag=True)

device = run_update_device(context, system(system_asset_tag="Unknown", chassis_asset_tag=ASSET_TAG))
assert device.data["asset_tag"] == ASSET_TAG

other = context.inventory.add_object(NBDevice, data={"name": "server02"}, source=context.source)
context.source.device_object = other
context.source.inventory_file_content = system(system_asset_tag="To Be Filled By O.E.M.",
chassis_asset_tag=" ")
context.source.update_device()

assert other.data.get("asset_tag") is None


def test_asset_tag_of_another_device_is_not_set(check_redfish_source):
"""NetBox rejects a duplicate asset tag and with it every other change of the update."""

context = check_redfish_source(collect_hardware_asset_tag=True)
context.inventory.add_object(NBDevice, data={"name": "other", "asset_tag": ASSET_TAG}, source=context.source)

device = run_update_device(context, system())

assert device.data.get("asset_tag") is None
assert device.data["serial"] == "CZJ0000001"


def test_own_asset_tag_is_kept(check_redfish_source):
"""The device which already has the asset tag is not a conflict with itself."""

context = check_redfish_source(collect_hardware_asset_tag=True)
context.device.data["asset_tag"] = ASSET_TAG

device = run_update_device(context, system())

assert device.data["asset_tag"] == ASSET_TAG
assert "asset_tag" not in device.updated_items
Loading