fix(node): restore Linux network inventory and expose read failures
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user