mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
Fix ntp and dns status
This commit is contained in:
@@ -247,7 +247,7 @@ func (c *NTPCollector) addSources(sourcesOut []byte) map[string]interface{} {
|
||||
}
|
||||
|
||||
stratum, err := strconv.Atoi(parts[3])
|
||||
if err != nil || stratum < 1 || stratum > 16 {
|
||||
if err != nil || stratum > 16 {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -159,9 +159,9 @@ func TestNTPSources(t *testing.T) {
|
||||
t.Fatal("missing source list in sources")
|
||||
}
|
||||
|
||||
// 5 sources minus GPS refclock (#) minus stratum-0 (10.0.0.4) = 3
|
||||
if len(sources) != 3 {
|
||||
t.Fatalf("expected 3 sources, got %d", len(sources))
|
||||
// 5 sources minus GPS refclock (#) = 4 (stratum 0 is kept)
|
||||
if len(sources) != 4 {
|
||||
t.Fatalf("expected 4 sources, got %d", len(sources))
|
||||
}
|
||||
|
||||
byAddr := make(map[string]map[string]interface{})
|
||||
@@ -211,6 +211,18 @@ func TestNTPSources(t *testing.T) {
|
||||
if s3["mode"] != "peer" {
|
||||
t.Fatalf("10.0.0.3 mode: expected peer, got %v", s3["mode"])
|
||||
}
|
||||
|
||||
// 10.0.0.4: unreachable server (stratum 0)
|
||||
s4 := byAddr["10.0.0.4"]
|
||||
if s4 == nil {
|
||||
t.Fatal("missing source 10.0.0.4")
|
||||
}
|
||||
if s4["state"] != "unusable" {
|
||||
t.Fatalf("10.0.0.4 state: expected unusable, got %v", s4["state"])
|
||||
}
|
||||
if toInt(s4["stratum"]) != 0 {
|
||||
t.Fatalf("10.0.0.4 stratum: expected 0, got %v", s4["stratum"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNTPSourcesEmpty(t *testing.T) {
|
||||
|
||||
@@ -3,9 +3,7 @@ package collector
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kernelkit/infix/src/yangerd/internal/tree"
|
||||
@@ -36,14 +34,15 @@ func (c *SystemCollector) Name() string { return "system" }
|
||||
// Interval implements Collector.
|
||||
func (c *SystemCollector) Interval() time.Duration { return c.interval }
|
||||
|
||||
// Collect implements Collector. It merges DNS and service data into
|
||||
// "ietf-system:system-state". Other system-state subtrees (platform,
|
||||
// software, users, hostname, timezone, clock, memory, load, filesystems)
|
||||
// are populated by boot-once, reactive, or on-demand providers.
|
||||
// Collect implements Collector. It merges service data into
|
||||
// "ietf-system:system-state". DNS is handled reactively by
|
||||
// fswatcher on /var/lib/misc/resolv.conf. Other system-state
|
||||
// subtrees (platform, software, users, hostname, timezone, clock,
|
||||
// memory, load, filesystems) are populated by boot-once, reactive,
|
||||
// or on-demand providers.
|
||||
func (c *SystemCollector) Collect(ctx context.Context, t *tree.Tree) error {
|
||||
state := make(map[string]interface{})
|
||||
|
||||
c.addDNS(ctx, state)
|
||||
c.addServices(ctx, state)
|
||||
|
||||
if data, err := json.Marshal(state); err == nil {
|
||||
@@ -52,78 +51,6 @@ func (c *SystemCollector) Collect(ctx context.Context, t *tree.Tree) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SystemCollector) addDNS(ctx context.Context, state map[string]interface{}) {
|
||||
dns := make(map[string]interface{})
|
||||
servers := make([]interface{}, 0)
|
||||
var search []string
|
||||
options := make(map[string]interface{})
|
||||
|
||||
headData, err := c.fs.ReadFile("/etc/resolv.conf.head")
|
||||
if err == nil {
|
||||
for _, line := range strings.Split(string(headData), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "nameserver") {
|
||||
ip := strings.TrimSpace(strings.TrimPrefix(line, "nameserver"))
|
||||
if net.ParseIP(ip) != nil {
|
||||
servers = append(servers, map[string]interface{}{
|
||||
"address": ip,
|
||||
"origin": "static",
|
||||
})
|
||||
}
|
||||
} else if strings.HasPrefix(line, "search") {
|
||||
search = append(search, strings.Fields(line)[1:]...)
|
||||
} else if strings.HasPrefix(line, "options") {
|
||||
for _, opt := range strings.Fields(line)[1:] {
|
||||
if strings.HasPrefix(opt, "timeout:") {
|
||||
if v, err := strconv.Atoi(strings.TrimPrefix(opt, "timeout:")); err == nil {
|
||||
options["timeout"] = v
|
||||
}
|
||||
} else if strings.HasPrefix(opt, "attempts:") {
|
||||
if v, err := strconv.Atoi(strings.TrimPrefix(opt, "attempts:")); err == nil {
|
||||
options["attempts"] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolvOut, err := c.cmd.Run(ctx, "/sbin/resolvconf", "-l")
|
||||
if err == nil {
|
||||
for _, line := range strings.Split(string(resolvOut), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "nameserver") {
|
||||
hashParts := strings.SplitN(line, "#", 2)
|
||||
ip := strings.TrimSpace(strings.TrimPrefix(hashParts[0], "nameserver"))
|
||||
if net.ParseIP(ip) == nil {
|
||||
continue
|
||||
}
|
||||
entry := map[string]interface{}{
|
||||
"address": ip,
|
||||
"origin": "dhcp",
|
||||
}
|
||||
if len(hashParts) > 1 {
|
||||
entry["interface"] = strings.TrimSpace(hashParts[1])
|
||||
}
|
||||
servers = append(servers, entry)
|
||||
} else if strings.HasPrefix(line, "search") {
|
||||
hashParts := strings.SplitN(line, "#", 2)
|
||||
search = append(search, strings.Fields(hashParts[0])[1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(options) > 0 {
|
||||
dns["options"] = options
|
||||
}
|
||||
dns["server"] = servers
|
||||
if len(search) > 0 {
|
||||
dns["search"] = search
|
||||
}
|
||||
|
||||
state["infix-system:dns-resolver"] = dns
|
||||
}
|
||||
|
||||
func (c *SystemCollector) addServices(ctx context.Context, state map[string]interface{}) {
|
||||
out, err := c.cmd.Run(ctx, "initctl", "-j")
|
||||
if err != nil {
|
||||
|
||||
@@ -12,17 +12,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
testResolvHead = `nameserver 8.8.8.8
|
||||
nameserver 1.1.1.1
|
||||
search example.com local.lan
|
||||
options timeout:2 attempts:3
|
||||
`
|
||||
|
||||
testResolvconfOutput = `nameserver 10.0.0.1 # eth0
|
||||
nameserver 10.0.0.2 # eth1
|
||||
search dhcp.example.com # eth0
|
||||
`
|
||||
|
||||
testInitctlJSON = `[
|
||||
{
|
||||
"identity": "sshd",
|
||||
@@ -48,16 +37,13 @@ search dhcp.example.com # eth0
|
||||
func newTestCollector() (*SystemCollector, *testutil.MockRunner, *testutil.MockFileReader) {
|
||||
runner := &testutil.MockRunner{
|
||||
Results: map[string][]byte{
|
||||
"initctl -j": []byte(testInitctlJSON),
|
||||
"/sbin/resolvconf -l": []byte(testResolvconfOutput),
|
||||
"initctl -j": []byte(testInitctlJSON),
|
||||
},
|
||||
Errors: map[string]error{},
|
||||
}
|
||||
|
||||
fs := &testutil.MockFileReader{
|
||||
Files: map[string][]byte{
|
||||
"/etc/resolv.conf.head": []byte(testResolvHead),
|
||||
},
|
||||
Files: map[string][]byte{},
|
||||
Globs: map[string][]string{},
|
||||
}
|
||||
|
||||
@@ -98,59 +84,6 @@ func TestSystemCollectorInterval(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystemCollectorDNS(t *testing.T) {
|
||||
c, _, _ := newTestCollector()
|
||||
state := collectToState(t, c)
|
||||
|
||||
dns, ok := state["infix-system:dns-resolver"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatal("missing infix-system:dns-resolver in system-state")
|
||||
}
|
||||
|
||||
servers, ok := dns["server"].([]interface{})
|
||||
if !ok {
|
||||
t.Fatal("missing server list in dns-resolver")
|
||||
}
|
||||
|
||||
// 2 static (resolv.conf.head) + 2 DHCP (resolvconf -l) = 4 total
|
||||
if len(servers) != 4 {
|
||||
t.Fatalf("expected 4 DNS servers, got %d: %v", len(servers), servers)
|
||||
}
|
||||
|
||||
s0 := servers[0].(map[string]interface{})
|
||||
if s0["address"] != "8.8.8.8" || s0["origin"] != "static" {
|
||||
t.Fatalf("server[0]: expected 8.8.8.8/static, got %v", s0)
|
||||
}
|
||||
s1 := servers[1].(map[string]interface{})
|
||||
if s1["address"] != "1.1.1.1" || s1["origin"] != "static" {
|
||||
t.Fatalf("server[1]: expected 1.1.1.1/static, got %v", s1)
|
||||
}
|
||||
|
||||
s2 := servers[2].(map[string]interface{})
|
||||
if s2["address"] != "10.0.0.1" || s2["origin"] != "dhcp" {
|
||||
t.Fatalf("server[2]: expected 10.0.0.1/dhcp, got %v", s2)
|
||||
}
|
||||
if s2["interface"] != "eth0" {
|
||||
t.Fatalf("server[2] interface: expected eth0, got %v", s2["interface"])
|
||||
}
|
||||
|
||||
search, ok := dns["search"].([]interface{})
|
||||
if !ok || len(search) < 2 {
|
||||
t.Fatalf("expected search domains, got %v", dns["search"])
|
||||
}
|
||||
|
||||
options, ok := dns["options"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatal("missing options in dns-resolver")
|
||||
}
|
||||
if int(options["timeout"].(float64)) != 2 {
|
||||
t.Fatalf("dns timeout: expected 2, got %v", options["timeout"])
|
||||
}
|
||||
if int(options["attempts"].(float64)) != 3 {
|
||||
t.Fatalf("dns attempts: expected 3, got %v", options["attempts"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystemCollectorServices(t *testing.T) {
|
||||
c, _, _ := newTestCollector()
|
||||
state := collectToState(t, c)
|
||||
@@ -189,8 +122,7 @@ func TestSystemCollectorCommandFailureGraceful(t *testing.T) {
|
||||
runner := &testutil.MockRunner{
|
||||
Results: map[string][]byte{},
|
||||
Errors: map[string]error{
|
||||
"initctl -j": fmt.Errorf("not available"),
|
||||
"/sbin/resolvconf -l": fmt.Errorf("not available"),
|
||||
"initctl -j": fmt.Errorf("not available"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -228,8 +160,7 @@ func TestSystemCollectorTreeKeys(t *testing.T) {
|
||||
func TestSystemCollectorServicesNilFields(t *testing.T) {
|
||||
runner := &testutil.MockRunner{
|
||||
Results: map[string][]byte{
|
||||
"initctl -j": []byte(`[{"identity":"minimal","pid":999,"status":"running","description":"Minimal service"}]`),
|
||||
"/sbin/resolvconf -l": []byte(""),
|
||||
"initctl -j": []byte(`[{"identity":"minimal","pid":999,"status":"running","description":"Minimal service"}]`),
|
||||
},
|
||||
Errors: map[string]error{},
|
||||
}
|
||||
@@ -261,33 +192,3 @@ func TestSystemCollectorServicesNilFields(t *testing.T) {
|
||||
t.Fatalf("nil restarts should become 0, got %v", stats["restart-count"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSystemCollectorNoDNSEmptyArray(t *testing.T) {
|
||||
runner := &testutil.MockRunner{
|
||||
Results: map[string][]byte{
|
||||
"initctl -j": []byte(testInitctlJSON),
|
||||
"/sbin/resolvconf -l": []byte(""),
|
||||
},
|
||||
Errors: map[string]error{},
|
||||
}
|
||||
|
||||
fs := &testutil.MockFileReader{
|
||||
Files: map[string][]byte{},
|
||||
Globs: map[string][]string{},
|
||||
}
|
||||
|
||||
c := NewSystemCollector(runner, fs, 60*time.Second)
|
||||
state := collectToState(t, c)
|
||||
|
||||
dns, ok := state["infix-system:dns-resolver"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatal("missing infix-system:dns-resolver")
|
||||
}
|
||||
servers, ok := dns["server"].([]interface{})
|
||||
if !ok {
|
||||
t.Fatal("dns server should be an array, not null")
|
||||
}
|
||||
if len(servers) != 0 {
|
||||
t.Fatalf("expected empty server array, got %d servers", len(servers))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user