fix(node): restore Linux network inventory and expose read failures

This commit is contained in:
DCCONSTRUCTIONS
2026-09-05 18:47:12 +03:00
parent 79911eb316
commit 15bb793e5e
10 changed files with 110 additions and 43 deletions
+45 -34
View File
@@ -12,9 +12,10 @@ import (
)
type Network struct {
Name string `json:"name"`
Up bool `json:"up"`
Addresses []string `json:"addresses"`
Name string `json:"name"`
Up bool `json:"up"`
Addresses []string `json:"addresses"`
AddressesReadable bool `json:"addresses_readable"`
}
type USB struct {
Port string `json:"port"`
@@ -24,17 +25,18 @@ type USB struct {
Speed string `json:"speed_mbps"`
}
type Inventory struct {
CollectedAt string `json:"collected_at"`
Hostname string `json:"hostname"`
OS string `json:"os"`
Architecture string `json:"architecture"`
CPUs int `json:"cpus"`
MemoryKiB *uint64 `json:"memory_kib"`
AvailableKiB *uint64 `json:"available_kib"`
Networks []Network `json:"networks"`
USB []USB `json:"usb"`
USBReadable bool `json:"usb_readable"`
Warnings []string `json:"warnings"`
CollectedAt string `json:"collected_at"`
Hostname string `json:"hostname"`
OS string `json:"os"`
Architecture string `json:"architecture"`
CPUs int `json:"cpus"`
MemoryKiB *uint64 `json:"memory_kib"`
AvailableKiB *uint64 `json:"available_kib"`
Networks []Network `json:"networks"`
NetworksReadable bool `json:"networks_readable"`
USB []USB `json:"usb"`
USBReadable bool `json:"usb_readable"`
Warnings []string `json:"warnings"`
}
// Host reads only local kernel/OS metadata. It never probes network devices,
@@ -70,26 +72,9 @@ func Host(root string) Inventory {
if v.MemoryKiB == nil {
v.Warnings = append(v.Warnings, "Сведения о памяти недоступны")
}
interfaces, err := net.Interfaces()
if err != nil {
v.Warnings = append(v.Warnings, "Не удалось прочитать сетевые интерфейсы")
}
for _, it := range interfaces {
if it.Flags&net.FlagLoopback != 0 {
continue
}
n := Network{Name: it.Name, Up: it.Flags&net.FlagUp != 0, Addresses: []string{}}
addresses, e := it.Addrs()
if e != nil {
v.Warnings = append(v.Warnings, "Адреса интерфейса "+it.Name+" недоступны")
}
for _, a := range addresses {
n.Addresses = append(n.Addresses, a.String())
}
sort.Strings(n.Addresses)
v.Networks = append(v.Networks, n)
}
sort.Slice(v.Networks, func(i, j int) bool { return v.Networks[i].Name < v.Networks[j].Name })
var warnings []string
v.Networks, v.NetworksReadable, warnings = readNetworks(net.Interfaces, func(it net.Interface) ([]net.Addr, error) { return it.Addrs() })
v.Warnings = append(v.Warnings, warnings...)
entries, err := os.ReadDir(filepath.Join(root, "sys/bus/usb/devices"))
v.USBReadable = err == nil
if err != nil {
@@ -105,3 +90,29 @@ func Host(root string) Inventory {
}
return v
}
func readNetworks(list func() ([]net.Interface, error), addrs func(net.Interface) ([]net.Addr, error)) ([]Network, bool, []string) {
result, warnings := []Network{}, []string{}
interfaces, err := list()
if err != nil {
return result, false, []string{"Не удалось прочитать сетевые интерфейсы"}
}
for _, it := range interfaces {
if it.Flags&net.FlagLoopback != 0 {
continue
}
addresses, err := addrs(it)
n := Network{Name: it.Name, Up: it.Flags&net.FlagUp != 0, Addresses: []string{}, AddressesReadable: err == nil}
if err != nil {
warnings = append(warnings, "Адреса интерфейса "+it.Name+" недоступны")
} else {
for _, a := range addresses {
n.Addresses = append(n.Addresses, a.String())
}
}
sort.Strings(n.Addresses)
result = append(result, n)
}
sort.Slice(result, func(i, j int) bool { return result[i].Name < result[j].Name })
return result, true, warnings
}
@@ -0,0 +1,31 @@
package node
import (
"errors"
"net"
"testing"
)
func TestNetworkInventoryDistinguishesFailureFromEmpty(t *testing.T) {
denied := errors.New("read denied")
for _, failure := range []error{nil, denied} {
rows, readable, warnings := readNetworks(func() ([]net.Interface, error) { return nil, failure }, func(net.Interface) ([]net.Addr, error) { t.Fatal("no interface to read"); return nil, nil })
if len(rows) != 0 || readable != (failure == nil) || (len(warnings) > 0) != (failure != nil) {
t.Fatal(rows, readable, warnings)
}
}
}
func TestNetworkAddressFailureKeepsInterfaceWithoutInventingAddresses(t *testing.T) {
rows, readable, warnings := readNetworks(func() ([]net.Interface, error) {
return []net.Interface{{Name: "loop", Flags: net.FlagLoopback}, {Name: "ethernet", Flags: net.FlagUp}}, nil
}, func(it net.Interface) ([]net.Addr, error) {
if it.Name != "ethernet" {
t.Fatal("read loopback")
}
return []net.Addr{&net.IPAddr{IP: net.ParseIP("192.0.2.1")}}, errors.New("partial result")
})
if !readable || len(rows) != 1 || rows[0].Name != "ethernet" || !rows[0].Up || rows[0].AddressesReadable || len(rows[0].Addresses) != 0 || len(warnings) != 1 {
t.Fatal(rows, readable, warnings)
}
}