ci/woodpecker/push/main-amd64-build Pipeline was successful
Probes connect to the panel directly and get the agent list as desired state. CI signs the image digest of agent and probe releases, the panel forwards the manifest, and a host updater verifies it with a local public key, pulls by digest and rolls back when the new version is unhealthy.
86 lines
3.1 KiB
C#
86 lines
3.1 KiB
C#
using Fleet.Core;
|
|
using Fleet.Hosting;
|
|
using Monitor.Protocol;
|
|
|
|
namespace Monitor.Probe.Tests;
|
|
|
|
public sealed class ProbeTests
|
|
{
|
|
[Fact]
|
|
public void ClockEstimator_UsesTheSampleWithTheShortestRoundTrip()
|
|
{
|
|
var clock = new ClockEstimator();
|
|
Assert.False(clock.HasEstimate);
|
|
|
|
// Offset 1000 with a 50 ms round trip, offset 5000 with a 2 ms round trip (symmetry assumption is far better here)
|
|
clock.Add(sendUs: 0, agentUs: 25_000 + 1000, receiveUs: 50_000);
|
|
clock.Add(sendUs: 100_000, agentUs: 101_000 + 5000, receiveUs: 102_000);
|
|
Assert.True(clock.HasEstimate);
|
|
Assert.Equal(5000, clock.OffsetUs);
|
|
|
|
// Slow samples do not displace the good one
|
|
clock.Add(sendUs: 200_000, agentUs: 300_000, receiveUs: 400_000);
|
|
Assert.Equal(5000, clock.OffsetUs);
|
|
}
|
|
|
|
private static string NewDirectory()
|
|
{
|
|
string path = Path.Combine(Path.GetTempPath(), "probe-tests-" + Guid.NewGuid().ToString("N"));
|
|
Directory.CreateDirectory(path);
|
|
return path;
|
|
}
|
|
|
|
private static ProbeAgentList List(long version, params string[] ids) => new()
|
|
{
|
|
Version = version,
|
|
Agents = [.. ids.Select(id => new AgentEndpoint { AgentId = new NodeId(id), Host = id + ".example", TcpPort = 7443, Spki = "c3BraQ==" })],
|
|
};
|
|
|
|
[Fact]
|
|
public void AgentDirectory_StartsEmpty_IgnoresStaleLists_PersistsChanges_AndReloadsThem()
|
|
{
|
|
string directory = NewDirectory();
|
|
try
|
|
{
|
|
var directoryA = new AgentDirectory(new ConfigStore(directory), directory, NullNodeLog.Instance);
|
|
int changes = 0;
|
|
directoryA.Changed += () => changes++;
|
|
Assert.Empty(directoryA.Agents);
|
|
|
|
directoryA.Apply(List(5, "agt_1", "agt_2"));
|
|
Assert.Equal(1, changes);
|
|
Assert.Equal(["agt_1", "agt_2"], directoryA.Agents.Select(a => a.AgentId));
|
|
|
|
directoryA.Apply(List(4, "agt_old")); // out-of-order older push
|
|
directoryA.Apply(List(5, "agt_1", "agt_2")); // same version and content
|
|
Assert.Equal(1, changes);
|
|
Assert.Equal(5, directoryA.Version);
|
|
|
|
// The next start uses the saved list while the panel is not reachable yet
|
|
var directoryB = new AgentDirectory(new ConfigStore(directory), directory, NullNodeLog.Instance);
|
|
Assert.Equal(["agt_1", "agt_2"], directoryB.Agents.Select(a => a.AgentId));
|
|
Assert.Equal(5, directoryB.Version);
|
|
}
|
|
finally
|
|
{
|
|
Directory.Delete(directory, recursive: true);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void AgentDirectory_StartsEmpty_WhenTheSavedFileIsCorrupt()
|
|
{
|
|
string directory = NewDirectory();
|
|
try
|
|
{
|
|
new ConfigStore(directory).Write(AgentDirectory.FileName, "{ not json"u8.ToArray());
|
|
var loaded = new AgentDirectory(new ConfigStore(directory), directory, NullNodeLog.Instance);
|
|
Assert.Empty(loaded.Agents);
|
|
}
|
|
finally
|
|
{
|
|
Directory.Delete(directory, recursive: true);
|
|
}
|
|
}
|
|
}
|