Skip to content

Commit 87c0cfb

Browse files
adding nautobot-sync post-inspection hook
1 parent 154def1 commit 87c0cfb

File tree

5 files changed

+453
-2
lines changed

5 files changed

+453
-2
lines changed

components/ironic/values.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,16 @@ conf:
8686
rabbit_ha_queues: true
8787
pxe:
8888
loader_file_paths: "snponly.efi:/usr/lib/ipxe/snponly.efi"
89+
redfish:
90+
# Redfish inspection hooks - run hooks for redfish-based inspection
91+
inspection_hooks: "$default_inspection_hooks,nautobot-sync"
8992
inspector:
9093
extra_kernel_params: ipa-collect-lldp=1
9194
# Agent inspection hooks - ports hook removed to prevent port manipulation during agent inspection
9295
# Default hooks include: ramdisk-error,validate-interfaces,ports,architecture
9396
# We override to exclude 'ports' from the default hooks
9497
default_hooks: "ramdisk-error,validate-interfaces,architecture"
95-
hooks: "$default_hooks,pci-devices,parse-lldp,local-link-connection,resource-class"
98+
hooks: "$default_hooks,pci-devices,parse-lldp,local-link-connection,resource-class,nautobot-sync"
9699
# enable sensors and metrics for redfish metrics - https://docs.openstack.org/ironic/latest/admin/drivers/redfish/metrics.html
97100
sensor_data:
98101
send_sensor_data: true
@@ -239,6 +242,9 @@ pod:
239242
sources:
240243
- secret:
241244
name: ironic-ks-etc
245+
- secret:
246+
name: ironic-nautobot-token
247+
optional: true
242248
ironic_api:
243249
ironic_api:
244250
volumeMounts:

python/ironic-understack/ironic_understack/conf.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,18 @@ def setup_conf():
1010
"device_types_dir",
1111
help="directory storing Device Type description YAML files",
1212
default="/var/lib/understack/device-types",
13-
)
13+
),
14+
cfg.StrOpt(
15+
"nautobot_url",
16+
help="Nautobot API URL",
17+
default=None,
18+
),
19+
cfg.StrOpt(
20+
"nautobot_token",
21+
help="Nautobot API token",
22+
secret=True,
23+
default=None,
24+
),
1425
]
1526
cfg.CONF.register_group(grp)
1627
cfg.CONF.register_opts(opts, group=grp)
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""Ironic inspection hook to sync device information to Nautobot."""
2+
3+
import pynautobot
4+
from ironic.drivers.modules.inspector.hooks import base
5+
from oslo_log import log as logging
6+
7+
from ironic_understack.conf import CONF
8+
9+
LOG = logging.getLogger(__name__)
10+
11+
12+
class NautobotSyncHook(base.InspectionHook):
13+
"""Hook to sync discovered device information to Nautobot."""
14+
15+
def __call__(self, task, inventory, plugin_data):
16+
"""Sync device inventory to Nautobot.
17+
18+
:param task: Ironic task context containing node and driver info
19+
:param inventory: Hardware inventory dict from inspection
20+
:param plugin_data: Shared data dict between hooks
21+
"""
22+
try:
23+
nautobot_url = CONF.ironic_understack.nautobot_url
24+
nautobot_token = CONF.ironic_understack.nautobot_token
25+
26+
if not nautobot_url or not nautobot_token:
27+
LOG.warning(
28+
"Nautobot URL or token not configured, skipping sync for node %s",
29+
task.node.uuid,
30+
)
31+
return
32+
33+
# Initialize Nautobot client
34+
nautobot = pynautobot.api(url=nautobot_url, token=nautobot_token)
35+
36+
# Extract device information from inventory
37+
device_data = self._extract_device_data(task, inventory)
38+
39+
# Sync to Nautobot
40+
self._sync_to_nautobot(nautobot, device_data, task.node)
41+
42+
LOG.info(
43+
"Successfully synced device information to Nautobot for node %s",
44+
task.node.uuid,
45+
)
46+
47+
except (KeyError, ValueError, TypeError) as e:
48+
msg = (
49+
f"Failed to extract device information from inventory for node "
50+
f"{task.node.uuid}: {e}"
51+
)
52+
LOG.error(msg)
53+
# Don't fail inspection, just log the error
54+
except Exception as e:
55+
msg = f"Failed to sync device to Nautobot for node {task.node.uuid}: {e}"
56+
LOG.error(msg)
57+
# Don't fail inspection, just log the error
58+
59+
def _extract_device_data(self, task, inventory):
60+
"""Extract relevant device data from inventory."""
61+
data = {
62+
"serial": inventory.get("system_vendor", {}).get("serial_number"),
63+
"manufacturer": inventory.get("system_vendor", {}).get("manufacturer"),
64+
"model": inventory.get("system_vendor", {}).get("product_name"),
65+
"uuid": task.node.uuid,
66+
"name": task.node.name or task.node.uuid,
67+
}
68+
69+
# Extract interface information
70+
interfaces = []
71+
for iface in inventory.get("interfaces", []):
72+
if iface.get("mac_address"):
73+
interfaces.append(
74+
{
75+
"name": iface.get("name"),
76+
"mac_address": iface.get("mac_address"),
77+
"ipv4_address": iface.get("ipv4_address"),
78+
}
79+
)
80+
81+
data["interfaces"] = interfaces
82+
83+
return data
84+
85+
def _sync_to_nautobot(self, nautobot, device_data, node):
86+
"""Sync device data to Nautobot."""
87+
serial = device_data.get("serial")
88+
if not serial:
89+
LOG.warning("Node %s, cannot sync to Nautobot", node.uuid)
90+
return
91+
92+
# Check if device exists in Nautobot
93+
device = self._find_device(nautobot, serial)
94+
95+
if device:
96+
LOG.info("Device %s already exists in Nautobot", serial)
97+
# Update device if needed
98+
self._update_device(nautobot, device, device_data)
99+
else:
100+
LOG.info("Device %s not found in Nautobot, would create", serial)
101+
# Note: Creation requires location/rack info
102+
# which we don't have from inspection
103+
# This would need to be configured or derived from other sources
104+
105+
def _find_device(self, nautobot, serial):
106+
"""Find device in Nautobot by serial number."""
107+
try:
108+
devices = nautobot.dcim.devices.filter(serial=serial)
109+
if devices:
110+
return devices[0]
111+
except Exception:
112+
LOG.exception("Error querying Nautobot for device with serial %s", serial)
113+
return None
114+
115+
def _update_device(self, nautobot, device, device_data):
116+
"""Update device information in Nautobot."""
117+
# Update basic device info if needed
118+
# This is a placeholder - actual update logic would depend on requirements
119+
LOG.debug("Would update device %s with data: %s", device.id, device_data)

0 commit comments

Comments
 (0)