mirror of
https://github.com/henrygd/beszel.git
synced 2026-05-06 10:51:50 +02:00
update
This commit is contained in:
@@ -217,7 +217,7 @@ func (h *SyncNetworkProbesHandler) Handle(hctx *HandlerContext) error {
|
|||||||
if err := cbor.Unmarshal(hctx.Request.Data, &req); err != nil {
|
if err := cbor.Unmarshal(hctx.Request.Data, &req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
resp, err := hctx.Agent.probeManager.ApplySync(req)
|
resp, err := hctx.Agent.probeManager.HandleSyncRequest(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,19 @@ func newProbeTask(config probe.Config) *probeTask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newProbeTaskFromExisting(config probe.Config, existing *probeTask) *probeTask {
|
||||||
|
task := newProbeTask(config)
|
||||||
|
if existing == nil {
|
||||||
|
return task
|
||||||
|
}
|
||||||
|
|
||||||
|
existing.mu.Lock()
|
||||||
|
defer existing.mu.Unlock()
|
||||||
|
task.samples = append(task.samples, existing.samples...)
|
||||||
|
task.buckets = existing.buckets
|
||||||
|
return task
|
||||||
|
}
|
||||||
|
|
||||||
// newProbeAggregate initializes an aggregate with an unset minimum value.
|
// newProbeAggregate initializes an aggregate with an unset minimum value.
|
||||||
func newProbeAggregate() probeAggregate {
|
func newProbeAggregate() probeAggregate {
|
||||||
return probeAggregate{minMs: math.MaxFloat64}
|
return probeAggregate{minMs: math.MaxFloat64}
|
||||||
@@ -193,14 +206,14 @@ func (pm *ProbeManager) SyncProbes(configs []probe.Config) {
|
|||||||
if exists {
|
if exists {
|
||||||
close(task.cancel)
|
close(task.cancel)
|
||||||
}
|
}
|
||||||
task = newProbeTask(cfg)
|
task = newProbeTaskFromExisting(cfg, task)
|
||||||
pm.probes[key] = task
|
pm.probes[key] = task
|
||||||
go pm.runProbe(task, true)
|
go pm.runProbe(task, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplySync applies a full or incremental probe sync request.
|
// HandleSyncRequest applies a full or incremental probe sync request.
|
||||||
func (pm *ProbeManager) ApplySync(req probe.SyncRequest) (probe.SyncResponse, error) {
|
func (pm *ProbeManager) HandleSyncRequest(req probe.SyncRequest) (probe.SyncResponse, error) {
|
||||||
switch req.Action {
|
switch req.Action {
|
||||||
case probe.SyncActionReplace:
|
case probe.SyncActionReplace:
|
||||||
pm.SyncProbes(req.Configs)
|
pm.SyncProbes(req.Configs)
|
||||||
@@ -216,7 +229,7 @@ func (pm *ProbeManager) ApplySync(req probe.SyncRequest) (probe.SyncResponse, er
|
|||||||
return probe.SyncResponse{Result: *result}, nil
|
return probe.SyncResponse{Result: *result}, nil
|
||||||
case probe.SyncActionDelete:
|
case probe.SyncActionDelete:
|
||||||
if req.Config.ID == "" {
|
if req.Config.ID == "" {
|
||||||
return probe.SyncResponse{}, errors.New("missing probe ID for delete action")
|
return probe.SyncResponse{}, errors.New("missing probe ID for delete")
|
||||||
}
|
}
|
||||||
pm.DeleteProbe(req.Config.ID)
|
pm.DeleteProbe(req.Config.ID)
|
||||||
return probe.SyncResponse{}, nil
|
return probe.SyncResponse{}, nil
|
||||||
@@ -244,7 +257,7 @@ func (pm *ProbeManager) UpsertProbe(config probe.Config, runNow bool) (*probe.Re
|
|||||||
if exists {
|
if exists {
|
||||||
close(task.cancel)
|
close(task.cancel)
|
||||||
}
|
}
|
||||||
task = newProbeTask(config)
|
task = newProbeTaskFromExisting(config, task)
|
||||||
pm.probes[config.ID] = task
|
pm.probes[config.ID] = task
|
||||||
startTask = true
|
startTask = true
|
||||||
pm.mu.Unlock()
|
pm.mu.Unlock()
|
||||||
@@ -309,7 +322,7 @@ func (pm *ProbeManager) Stop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// runProbe executes a single probe task in a loop.
|
// runProbe executes a single probe task in a loop.
|
||||||
func (pm *ProbeManager) runProbe(task *probeTask, runImmediately bool) {
|
func (pm *ProbeManager) runProbe(task *probeTask, runNow bool) {
|
||||||
interval := time.Duration(task.config.Interval) * time.Second
|
interval := time.Duration(task.config.Interval) * time.Second
|
||||||
if interval < time.Second {
|
if interval < time.Second {
|
||||||
interval = 10 * time.Second
|
interval = 10 * time.Second
|
||||||
@@ -317,7 +330,7 @@ func (pm *ProbeManager) runProbe(task *probeTask, runImmediately bool) {
|
|||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
if runImmediately {
|
if runNow {
|
||||||
pm.executeProbe(task)
|
pm.executeProbe(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -197,7 +197,7 @@ func TestProbeManagerApplySyncUpsertRunsImmediatelyAndReturnsResult(t *testing.T
|
|||||||
httpClient: server.Client(),
|
httpClient: server.Client(),
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := pm.ApplySync(probe.SyncRequest{
|
resp, err := pm.HandleSyncRequest(probe.SyncRequest{
|
||||||
Action: probe.SyncActionUpsert,
|
Action: probe.SyncActionUpsert,
|
||||||
Config: probe.Config{ID: "probe-1", Target: server.URL, Protocol: "http", Interval: 10},
|
Config: probe.Config{ID: "probe-1", Target: server.URL, Protocol: "http", Interval: 10},
|
||||||
RunNow: true,
|
RunNow: true,
|
||||||
@@ -216,6 +216,48 @@ func TestProbeManagerApplySyncUpsertRunsImmediatelyAndReturnsResult(t *testing.T
|
|||||||
require.Len(t, task.samples, 1)
|
require.Len(t, task.samples, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProbeManagerUpsertProbeKeepsHistoryWhenOnlyIntervalChanges(t *testing.T) {
|
||||||
|
originalCfg := probe.Config{ID: "probe-1", Target: "1.1.1.1", Protocol: "icmp", Interval: 10}
|
||||||
|
updatedCfg := probe.Config{ID: "probe-1", Target: "1.1.1.1", Protocol: "icmp", Interval: 30}
|
||||||
|
now := time.Now().UTC()
|
||||||
|
|
||||||
|
existingTask := &probeTask{config: originalCfg, cancel: make(chan struct{})}
|
||||||
|
existingTask.addSampleLocked(probeSample{responseMs: 12, timestamp: now.Add(-50 * time.Minute)})
|
||||||
|
existingTask.addSampleLocked(probeSample{responseMs: 24, timestamp: now.Add(-30 * time.Second)})
|
||||||
|
|
||||||
|
pm := &ProbeManager{
|
||||||
|
probes: map[string]*probeTask{originalCfg.ID: existingTask},
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := pm.UpsertProbe(updatedCfg, false)
|
||||||
|
defer pm.Stop()
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Nil(t, result)
|
||||||
|
|
||||||
|
updatedTask := pm.probes[updatedCfg.ID]
|
||||||
|
require.NotNil(t, updatedTask)
|
||||||
|
assert.NotSame(t, existingTask, updatedTask)
|
||||||
|
assert.Equal(t, updatedCfg, updatedTask.config)
|
||||||
|
|
||||||
|
updatedTask.mu.Lock()
|
||||||
|
defer updatedTask.mu.Unlock()
|
||||||
|
require.Len(t, updatedTask.samples, 1)
|
||||||
|
assert.Equal(t, 24.0, updatedTask.samples[0].responseMs)
|
||||||
|
|
||||||
|
agg := updatedTask.aggregateLocked(time.Hour, now)
|
||||||
|
require.True(t, agg.hasData())
|
||||||
|
assert.Equal(t, 2, agg.totalCount)
|
||||||
|
assert.Equal(t, 2, agg.successCount)
|
||||||
|
assert.Equal(t, 18.0, agg.avgResponse())
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-existingTask.cancel:
|
||||||
|
default:
|
||||||
|
t.Fatal("expected original probe task to be cancelled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestProbeManagerApplySyncDeleteRemovesTask(t *testing.T) {
|
func TestProbeManagerApplySyncDeleteRemovesTask(t *testing.T) {
|
||||||
config := probe.Config{ID: "probe-1", Target: "1.1.1.1", Protocol: "icmp", Interval: 10}
|
config := probe.Config{ID: "probe-1", Target: "1.1.1.1", Protocol: "icmp", Interval: 10}
|
||||||
task := &probeTask{config: config, cancel: make(chan struct{})}
|
task := &probeTask{config: config, cancel: make(chan struct{})}
|
||||||
@@ -223,7 +265,7 @@ func TestProbeManagerApplySyncDeleteRemovesTask(t *testing.T) {
|
|||||||
probes: map[string]*probeTask{config.ID: task},
|
probes: map[string]*probeTask{config.ID: task},
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := pm.ApplySync(probe.SyncRequest{
|
_, err := pm.HandleSyncRequest(probe.SyncRequest{
|
||||||
Action: probe.SyncActionDelete,
|
Action: probe.SyncActionDelete,
|
||||||
Config: probe.Config{ID: config.ID},
|
Config: probe.Config{ID: config.ID},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package hub
|
package hub
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/henrygd/beszel/internal/entities/probe"
|
"github.com/henrygd/beszel/internal/entities/probe"
|
||||||
@@ -11,7 +12,7 @@ import (
|
|||||||
|
|
||||||
// generateProbeID creates a stable hash ID for a probe based on its configuration and the system it belongs to.
|
// generateProbeID creates a stable hash ID for a probe based on its configuration and the system it belongs to.
|
||||||
func generateProbeID(systemId string, config probe.Config) string {
|
func generateProbeID(systemId string, config probe.Config) string {
|
||||||
return systems.MakeStableHashId(systemId, config.Target, config.Protocol)
|
return systems.MakeStableHashId(systemId, config.Target, config.Protocol, strconv.FormatUint(uint64(config.Port), 10))
|
||||||
}
|
}
|
||||||
|
|
||||||
// bindNetworkProbesEvents keeps probe records and agent probe state in sync.
|
// bindNetworkProbesEvents keeps probe records and agent probe state in sync.
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func TestGenerateProbeID(t *testing.T) {
|
|||||||
Port: 80,
|
Port: 80,
|
||||||
Interval: 60,
|
Interval: 60,
|
||||||
},
|
},
|
||||||
expected: "a20a5827",
|
expected: "de7b3647",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "HTTP probe on example.com with different system ID",
|
name: "HTTP probe on example.com with different system ID",
|
||||||
@@ -34,7 +34,7 @@ func TestGenerateProbeID(t *testing.T) {
|
|||||||
Port: 80,
|
Port: 80,
|
||||||
Interval: 60,
|
Interval: 60,
|
||||||
},
|
},
|
||||||
expected: "ab602ae7",
|
expected: "be9e2707",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Same probe, different interval",
|
name: "Same probe, different interval",
|
||||||
@@ -45,7 +45,7 @@ func TestGenerateProbeID(t *testing.T) {
|
|||||||
Port: 80,
|
Port: 80,
|
||||||
Interval: 120,
|
Interval: 120,
|
||||||
},
|
},
|
||||||
expected: "ab602ae7",
|
expected: "be9e2707",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ICMP probe on 1.1.1.1",
|
name: "ICMP probe on 1.1.1.1",
|
||||||
@@ -56,7 +56,7 @@ func TestGenerateProbeID(t *testing.T) {
|
|||||||
Port: 0,
|
Port: 0,
|
||||||
Interval: 10,
|
Interval: 10,
|
||||||
},
|
},
|
||||||
expected: "6d13a4a4",
|
expected: "49ec14fc",
|
||||||
}, {
|
}, {
|
||||||
name: "ICMP probe on 1.1.1.1 with different system ID",
|
name: "ICMP probe on 1.1.1.1 with different system ID",
|
||||||
systemID: "sys4567",
|
systemID: "sys4567",
|
||||||
@@ -66,7 +66,29 @@ func TestGenerateProbeID(t *testing.T) {
|
|||||||
Port: 0,
|
Port: 0,
|
||||||
Interval: 10,
|
Interval: 10,
|
||||||
},
|
},
|
||||||
expected: "ddd6c81",
|
expected: "84921aa3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TCP probe on example.com with port 443",
|
||||||
|
systemID: "sys789",
|
||||||
|
config: probe.Config{
|
||||||
|
Protocol: "tcp",
|
||||||
|
Target: "example.com",
|
||||||
|
Port: 443,
|
||||||
|
Interval: 30,
|
||||||
|
},
|
||||||
|
expected: "677b991",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "TCP probe on example.com with port 8443",
|
||||||
|
systemID: "sys789",
|
||||||
|
config: probe.Config{
|
||||||
|
Protocol: "tcp",
|
||||||
|
Target: "example.com",
|
||||||
|
Port: 8443,
|
||||||
|
Interval: 30,
|
||||||
|
},
|
||||||
|
expected: "84167969",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user