A probe now links to the backend like an agent (probe.json carries the server address). The backend sends the agent list as desired state and the probe reports its platform and build in its own hello. The agent no longer relays the list or the probe platform. Existing probes must be re-created.
130 lines
8.1 KiB
C#
130 lines
8.1 KiB
C#
using System.Text.Json.Nodes;
|
||
|
||
namespace Monitor.Backend.Tests;
|
||
|
||
/// <summary>真实的后端 + 真实的 Agent + 真实的 Probe(都在进程内,回环网络)。</summary>
|
||
[Collection("probe-e2e")]
|
||
public sealed class ProbeEndToEndTests
|
||
{
|
||
private readonly BackendFixture _backend;
|
||
|
||
public ProbeEndToEndTests(BackendFixture backend)
|
||
{
|
||
_backend = backend;
|
||
}
|
||
|
||
private async Task<JsonNode> AgentViewAsync(AgentHarness agent) => await _backend.GetJsonAsync(_backend.Admin, "/api/admin/agents/" + agent.Id);
|
||
|
||
private async Task<int> ChunkCountAsync(AgentHarness agent, string kind) =>
|
||
(await _backend.GetJsonAsync(_backend.Admin, $"/api/admin/raw/chunks?agent_id={agent.Id}"))["items"]!.AsArray().Count(c => c!["kind"]!.GetValue<string>() == kind);
|
||
|
||
[Fact]
|
||
public async Task Probe_GetsTheAgentListFromThePanel_RunsTheLineTasks_AndTheBackendStoresMergedChunks()
|
||
{
|
||
await using AgentHarness agent = await AgentHarness.StartAsync(_backend);
|
||
await agent.OverrideLineTasksAsync(rateHz: 30);
|
||
await using ProbeHarness probe = await ProbeHarness.StartAsync(_backend);
|
||
|
||
await BackendFixture.EventuallyAsync(() => Task.FromResult(probe.App.IsConnected(agent.Id)), because: "the panel sends the agent list to the probe, which then connects");
|
||
await BackendFixture.EventuallyAsync(async () => (await AgentViewAsync(agent))["probe_connected"]!.GetValue<bool>());
|
||
await BackendFixture.EventuallyAsync(() => Task.FromResult(probe.App.Connection(agent.Id)!.RoleCount == 2), because: "udp_echo and tcp_echo");
|
||
|
||
// 每 10 秒切一块,回显类再等 2 秒宽限
|
||
await BackendFixture.EventuallyAsync(async () => await ChunkCountAsync(agent, "udp_echo") >= 1 && await ChunkCountAsync(agent, "tcp_echo") >= 1, TimeSpan.FromSeconds(40));
|
||
Assert.True(agent.App.Counters.Fragments >= 2);
|
||
Assert.Equal(0, agent.App.Counters.FragmentsDiscarded);
|
||
|
||
// 时钟偏移已经估计出来:进程内两端共用同一个单调时钟,所以偏移应当接近 0(真实线路上是两台机器各自的时钟原点之差)
|
||
Assert.InRange(probe.App.Connection(agent.Id)!.ClockOffsetUs, -20_000, 20_000);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Probe_IntroducesItselfToThePanel_AndARevokedProbeIsLockedOut()
|
||
{
|
||
await _backend.SiteMustBePublicAsync();
|
||
await using ProbeHarness probe = await ProbeHarness.StartAsync(_backend);
|
||
string path = "/api/admin/probes/" + probe.ProbeId;
|
||
|
||
// 直连面板:在线,hello 里的系统、架构和版本都被记下来了
|
||
await BackendFixture.EventuallyAsync(async () => (await _backend.GetJsonAsync(_backend.Admin, path))["online"]!.GetValue<bool>(), because: "the probe links to the panel");
|
||
await BackendFixture.EventuallyAsync(
|
||
async () => (await _backend.GetJsonAsync(_backend.Anonymous, "/api/public/nodes"))["items"]!.AsArray().Any(n => n!["id"]!.GetValue<string>() == probe.ProbeId && n["os"] is not null && n["arch"] is not null),
|
||
because: "the platform comes from the probe's own hello");
|
||
|
||
// 吊销:面板立刻断开它,之后连不上
|
||
using HttpResponseMessage revoked = await _backend.Admin.PostAsync(path + "/revoke", null);
|
||
revoked.EnsureSuccessStatusCode();
|
||
await BackendFixture.EventuallyAsync(async () => !(await _backend.GetJsonAsync(_backend.Admin, path))["online"]!.GetValue<bool>(), because: "a revoked probe is disconnected and cannot come back");
|
||
Assert.False(probe.App.Link.IsConnected);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Probe_FollowsTheAgentListThePanelKnows_AndSavesItForTheNextStart()
|
||
{
|
||
await using AgentHarness first = await AgentHarness.StartAsync(_backend);
|
||
await using ProbeHarness probe = await ProbeHarness.StartAsync(_backend);
|
||
await BackendFixture.EventuallyAsync(() => Task.FromResult(probe.App.IsConnected(first.Id)));
|
||
|
||
await using AgentHarness second = await AgentHarness.StartAsync(_backend);
|
||
await BackendFixture.EventuallyAsync(() => Task.FromResult(probe.App.IsConnected(second.Id)), TimeSpan.FromSeconds(30), "a new agent should be picked up through agents_changed: " + string.Join(";", probe.App.Directory.Agents.Select(a => $"{a.AgentId}@{a.Host}:{a.TcpPort}")) + " v" + probe.App.Directory.Version + " want " + second.Id);
|
||
Assert.Contains(probe.App.Directory.Agents, a => a.AgentId == second.Id);
|
||
Assert.True(File.Exists(Path.Combine(probe.Directory, "agents.json")));
|
||
|
||
// 停用一个 Agent:Probe 断开它
|
||
using HttpResponseMessage disabled = await _backend.Admin.PostAsync($"/api/admin/agents/{second.Id}/disable", BackendFixture.Json(new JsonObject()));
|
||
Assert.True(disabled.IsSuccessStatusCode, await disabled.Content.ReadAsStringAsync());
|
||
await BackendFixture.EventuallyAsync(() => Task.FromResult(!probe.App.IsConnected(second.Id)), TimeSpan.FromSeconds(30));
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Hy2Ladder_WithTheRealProbe_Succeeds_AndEveryFragmentIsStored()
|
||
{
|
||
await using AgentHarness agent = await AgentHarness.StartAsync(_backend);
|
||
await agent.OverrideLineTasksAsync();
|
||
await using ProbeHarness probe = await ProbeHarness.StartAsync(_backend);
|
||
await BackendFixture.EventuallyAsync(async () => (await AgentViewAsync(agent))["probe_connected"]!.GetValue<bool>());
|
||
|
||
using HttpResponseMessage accepted = await _backend.Admin.PostAsync($"/api/admin/lines/{agent.Id}~{probe.ProbeId}/tests", BackendFixture.Json(new JsonObject { ["plan"] = "hy2_ladder", ["params"] = new JsonObject { ["size"] = 1000, ["duration_s"] = 5 } }));
|
||
Assert.True(accepted.StatusCode == System.Net.HttpStatusCode.Accepted, await accepted.Content.ReadAsStringAsync());
|
||
string testId = JsonNode.Parse(await accepted.Content.ReadAsStringAsync())!["id"]!.GetValue<string>();
|
||
|
||
JsonNode test = null!;
|
||
await BackendFixture.EventuallyAsync(
|
||
async () =>
|
||
{
|
||
test = await _backend.GetJsonAsync(_backend.Admin, "/api/public/tests/" + testId);
|
||
return test["status"]!.GetValue<string>() is "succeeded" or "failed" or "aborted";
|
||
},
|
||
TimeSpan.FromSeconds(90));
|
||
Assert.True(test["status"]!.GetValue<string>() == "succeeded", test.ToJsonString());
|
||
|
||
await BackendFixture.EventuallyAsync(async () => await ChunkCountAsync(agent, "udp_train") >= 5, because: "the 100 Mbit/s phase (5 s of 1000 B packets, over 50000 records) is split into two fragments");
|
||
Assert.Equal(0, agent.App.Counters.FragmentsDiscarded);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task RealitySession_WithTheRealProbe_RunsTcpBulkAndStoresBytesAndKernelRows()
|
||
{
|
||
await using AgentHarness agent = await AgentHarness.StartAsync(_backend);
|
||
await agent.OverrideLineTasksAsync();
|
||
await using ProbeHarness probe = await ProbeHarness.StartAsync(_backend);
|
||
await BackendFixture.EventuallyAsync(async () => (await AgentViewAsync(agent))["probe_connected"]!.GetValue<bool>());
|
||
|
||
using HttpResponseMessage accepted = await _backend.Admin.PostAsync($"/api/admin/lines/{agent.Id}~{probe.ProbeId}/tests", BackendFixture.Json(new JsonObject { ["plan"] = "reality_session" }));
|
||
Assert.True(accepted.StatusCode == System.Net.HttpStatusCode.Accepted, await accepted.Content.ReadAsStringAsync());
|
||
string testId = JsonNode.Parse(await accepted.Content.ReadAsStringAsync())!["id"]!.GetValue<string>();
|
||
|
||
JsonNode test = null!;
|
||
await BackendFixture.EventuallyAsync(
|
||
async () =>
|
||
{
|
||
test = await _backend.GetJsonAsync(_backend.Admin, "/api/public/tests/" + testId);
|
||
return test["status"]!.GetValue<string>() is "succeeded" or "failed" or "aborted";
|
||
},
|
||
TimeSpan.FromSeconds(150));
|
||
Assert.True(test["status"]!.GetValue<string>() == "succeeded", test.ToJsonString());
|
||
await BackendFixture.EventuallyAsync(async () => await ChunkCountAsync(agent, "tcp_bulk") >= 1, because: "the bulk phase stores its fragments");
|
||
Assert.Equal(0, agent.App.Counters.FragmentsDiscarded);
|
||
}
|
||
}
|