Device sync: v6 primary IP, DMI placeholders, CLI report
The primary IP of each family is the management address, else the first
address on the management interface (the v6 address next to a v4
management address). DMI placeholders ("Not Specified", "To Be Filled
By O.E.M.") are not written as serials. rollcall ipam sync prints the
device report.
Refs #10
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
133b5f38d9
commit
c3032683ab
2 files changed
+41
-6
No files matched your search
@@ -232,7 +232,7 @@ func (n *netBox) syncDevices(ctx context.Context, cfg DeviceSyncConfig, machines
|
||||
site = id
|
||||
}
|
||||
body := obj{"name": name, "device_type": types[tk], "role": roleID, "site": site, "status": nbStatus(m.State),
|
||||
"serial": trunc(deref(m.Serial), 50), "tags": tags, "custom_fields": obj{n.field: m.ID},
|
||||
"serial": trunc(dmiValue(deref(m.Serial)), 50), "tags": tags, "custom_fields": obj{n.field: m.ID},
|
||||
"location": nil, "rack": nil, "position": nil, "face": nil}
|
||||
if id, ok := locBy[siteName{site, strings.ToLower(m.Location.Room)}]; ok && m.Location.Room != "" {
|
||||
body["location"] = id
|
||||
@@ -376,14 +376,21 @@ func (n *netBox) syncInterfaces(ctx context.Context, dev obj, m *Machine, ips []
|
||||
}
|
||||
}
|
||||
}
|
||||
// addresses on their interfaces; the management address (else the first
|
||||
// one on the boot NIC) is the primary IP
|
||||
// addresses on their interfaces; the primary IP of each family is the
|
||||
// management address, else the first address on the management (or
|
||||
// boot) interface
|
||||
var boot string
|
||||
if b := bootNIC(nics, m.MACs); b != nil {
|
||||
boot = b.Name
|
||||
}
|
||||
mgmt := deref(m.MgmtIP)
|
||||
type cand struct {
|
||||
id, iface int64
|
||||
fam int
|
||||
}
|
||||
var cands []cand
|
||||
primary := map[int]int64{}
|
||||
mgmtIface := ifaceID[boot]
|
||||
for _, x := range ips {
|
||||
a, ok := nbIPs[x.IP]
|
||||
if !ok || n.mine(a) != m.ID {
|
||||
@@ -405,8 +412,14 @@ func (n *netBox) syncInterfaces(ctx context.Context, dev obj, m *Machine, ips []
|
||||
if strings.Contains(x.IP, ":") {
|
||||
fam = 6
|
||||
}
|
||||
if _, set := primary[fam]; x.IP == mgmt || (!set && iid == ifaceID[boot] && mgmt == "") {
|
||||
primary[fam] = nbID(a)
|
||||
if x.IP == mgmt {
|
||||
primary[fam], mgmtIface = nbID(a), iid
|
||||
}
|
||||
cands = append(cands, cand{nbID(a), iid, fam})
|
||||
}
|
||||
for _, c := range cands {
|
||||
if _, set := primary[c.fam]; !set && c.iface == mgmtIface {
|
||||
primary[c.fam] = c.id
|
||||
}
|
||||
}
|
||||
body := obj{}
|
||||
@@ -441,3 +454,12 @@ func nbIPFromObj(a obj) nbIP {
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// dmiValue drops the placeholders firmware leaves in DMI fields.
|
||||
func dmiValue(s string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(s)) {
|
||||
case "", "not specified", "to be filled by o.e.m.", "default string", "system serial number", "0123456789", "none", "n/a":
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
@@ -231,6 +231,8 @@ func TestNetBoxDeviceSync(t *testing.T) {
|
||||
tagged := []any{map[string]any{"name": "rollcall"}}
|
||||
ipID := f.put("ipam/ip-addresses/", map[string]any{"address": "10.213.77.50/24", "tags": tagged,
|
||||
"custom_fields": map[string]any{"rollcall_machine": "m-a"}})
|
||||
ip6ID := f.put("ipam/ip-addresses/", map[string]any{"address": "fd00:213:77::50/64", "tags": tagged,
|
||||
"custom_fields": map[string]any{"rollcall_machine": "m-a"}})
|
||||
foreignIP := f.put("ipam/ip-addresses/", map[string]any{"address": "10.213.77.51/24", "tags": []any{},
|
||||
"custom_fields": map[string]any{}})
|
||||
// a device someone else made with a machine's name
|
||||
@@ -241,7 +243,12 @@ func TestNetBoxDeviceSync(t *testing.T) {
|
||||
b := dcimMachine("m-b", "", "ready", Location{Building: "nowhere", Rack: "R12"}, [2]string{"eth0", "aa:bb:cc:00:00:03"})
|
||||
c := dcimMachine("m-c", "taken", "commission-failed", Location{})
|
||||
n := dcimMachine("m-n", "", "new", Location{})
|
||||
ips := []IPAddr{{IP: "10.213.77.50", MachineID: "m-a", Iface: "eno1"}, {IP: "10.213.77.51", MachineID: "m-b"}}
|
||||
ips := []IPAddr{{IP: "10.213.77.50", MachineID: "m-a", Iface: "eno1"}, {IP: "fd00:213:77::50", MachineID: "m-a", Iface: "eno1"},
|
||||
{IP: "10.213.77.51", MachineID: "m-b"}}
|
||||
mgmt := "10.213.77.50"
|
||||
a.MgmtIP = &mgmt
|
||||
ns := "Not Specified"
|
||||
b.Serial = &ns
|
||||
|
||||
rep := nb.syncDevices(ctx, cfg, []*Machine{a, b, c, n}, ips)
|
||||
if rep.Error != "" {
|
||||
@@ -283,6 +290,12 @@ func TestNetBoxDeviceSync(t *testing.T) {
|
||||
if ip["assigned_object_type"] != "dcim.interface" || ip["assigned_object_id"] != eno1["id"] || d["primary_ip4"] != float64(ipID) {
|
||||
t.Fatalf("ip %v primary %v", ip, d["primary_ip4"])
|
||||
}
|
||||
if d["primary_ip6"] != float64(ip6ID) {
|
||||
t.Fatalf("primary ip6 %v (the v6 address on the management interface)", d["primary_ip6"])
|
||||
}
|
||||
if db["serial"] != "" {
|
||||
t.Fatalf("DMI placeholder as serial: %v", db["serial"])
|
||||
}
|
||||
if f.objs["ipam/ip-addresses/"][foreignIP]["assigned_object_id"] != nil {
|
||||
t.Fatal("assigned an address that is not Rollcall's")
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user