agent: unify listening and dialing with directed mutual measurements
Replace Probe with Agent across node runtime, Fleet contracts, backend state, API and storage. Report incoming sessions and outgoing link health through each Agent uplink, and retain directed link controls and retry. Update protocol fixtures and end-to-end coverage for the unified model; accept requested cancellation during the Fleet timeout test cleanup.
This commit is contained in:
1 parent
d5ebf29e57
commit
ad5efc62ff
206 files changed
+4202
-4964
No files matched your search
@@ -95,7 +95,7 @@ public sealed class EnrollmentAndTaskSetTests : IClassFixture<BackendFixture>
|
||||
[Fact]
|
||||
public async Task Enrollment_RejectsATlsPublicKeyThatDoesNotMatchItsFingerprint_AndAnUnknownRole()
|
||||
{
|
||||
string token = await _backend.CreateJoinTokenAsync(role: "probe");
|
||||
string token = await _backend.CreateJoinTokenAsync(role: "agent");
|
||||
using var mine = Fleet.Identity.NodeIdentity.Generate();
|
||||
using var tls = Fleet.Identity.NodeIdentity.Generate();
|
||||
using var impostor = Fleet.Identity.NodeIdentity.Generate();
|
||||
@@ -352,55 +352,41 @@ public sealed class EnrollmentAndTaskSetTests : IClassFixture<BackendFixture>
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProbeLifecycle_EnrollRenameRevokeDelete_IsReflectedInEveryAgentsPeerList()
|
||||
public async Task DialOnlyNode_EnrollDisableDelete_IsReflectedInTheListenersPeerList()
|
||||
{
|
||||
await using AgentSimulator agent = await _backend.ConnectAgentAsync();
|
||||
await agent.WaitForTaskSetAsync(s => s.Tasks.Count == Defaults.TaskCount);
|
||||
await using AgentSimulator listener = await _backend.ConnectAgentAsync();
|
||||
await listener.WaitForTaskSetAsync(s => s.Tasks.Count == Defaults.TaskCount);
|
||||
|
||||
// 创建 Probe 就是创建一个 probe 角色的加入令牌;密钥由节点自己生成,Backend 从不接触私钥
|
||||
string name = _backend.UniqueName("home");
|
||||
using EnrolledProbe probe = await _backend.CreateProbeAsync(name);
|
||||
string id = probe.ProbeId;
|
||||
Assert.StartsWith(ProbeId.Prefix, id, StringComparison.Ordinal);
|
||||
string peerKey = Convert.ToBase64String(probe.Identity.PeerIdentity.PublicKeySpki);
|
||||
string backendKey = Convert.ToBase64String(probe.Identity.Identity.PublicKeySpki);
|
||||
Assert.NotEqual(backendKey, peerKey); // 连面板的和连 Agent 的是两把独立的密钥
|
||||
using EnrolledDialer dialer = await _backend.CreateDialerAsync(name);
|
||||
string id = dialer.DialerId;
|
||||
Assert.StartsWith(AgentIds.Prefix, id, StringComparison.Ordinal);
|
||||
string peerKey = Convert.ToBase64String(dialer.Identity.PeerIdentity.PublicKeySpki);
|
||||
string backendKey = Convert.ToBase64String(dialer.Identity.Identity.PublicKeySpki);
|
||||
Assert.NotEqual(backendKey, peerKey); // 连面板的和连监听方的是两把独立的密钥
|
||||
|
||||
// Agent 的名单里放的是对等面的公钥,不是连面板的那把
|
||||
TaskSet withProbe = await agent.WaitForTaskSetAsync(s => s.Peers.Any(p => p.ProbeId.Value == id && p.PublicKey == peerKey));
|
||||
Assert.DoesNotContain(withProbe.Peers, p => p.PublicKey == backendKey);
|
||||
// 监听方的名单里放的是对等面的公钥,不是连面板的那把
|
||||
TaskSet withDialer = await listener.WaitForTaskSetAsync(s => s.Peers.Any(p => p.NodeId.Value == id && p.PublicKey == peerKey));
|
||||
Assert.DoesNotContain(withDialer.Peers, p => p.PublicKey == backendKey);
|
||||
|
||||
// 令牌带角色;面板能列出这个 Probe;重名的注册被拒绝
|
||||
JsonNode tokens = await _backend.GetJsonAsync(_backend.Admin, "/api/admin/join-tokens");
|
||||
Assert.Contains(tokens["items"]!.AsArray(), t => t!["role"]!.GetValue<string>() == "probe");
|
||||
JsonNode listed = await _backend.GetJsonAsync(_backend.Admin, "/api/admin/probes");
|
||||
Assert.Contains(listed["items"]!.AsArray(), p => p!["id"]!.GetValue<string>() == id && !p["revoked"]!.GetValue<bool>());
|
||||
string duplicateToken = await _backend.CreateJoinTokenAsync(role: "probe");
|
||||
// 重名节点进入待审批状态,尚未获得任务
|
||||
string duplicateToken = await _backend.CreateJoinTokenAsync(role: "agent");
|
||||
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(15) };
|
||||
Fleet.Core.Result<Fleet.Enrollment.Client.EnrollmentResult> duplicate = await Fleet.Enrollment.Client.EnrollmentClient.EnrollAsync(
|
||||
http, _backend.BaseAddress, duplicateToken, name, Path.Combine(Path.GetTempPath(), "moshiro-dup-" + Guid.NewGuid().ToString("N")), "0.0.0", null, TimeProvider.System, default);
|
||||
Assert.False(duplicate.IsSuccess);
|
||||
Assert.Equal("fleet_name_conflict", duplicate.Error.Code.Value);
|
||||
Assert.True(duplicate.IsSuccess);
|
||||
JsonNode pending = await AgentAsync(duplicate.Value.NodeId);
|
||||
Assert.Equal("pending", pending["status"]!.GetValue<string>());
|
||||
|
||||
// 创建身份和轮换密钥的接口已经没有了:换密钥就是吊销之后重新注册
|
||||
using HttpResponseMessage oldCreate = await _backend.Admin.PostAsync("/api/admin/probes", BackendFixture.Json(new JsonObject { ["name"] = "x" }));
|
||||
Assert.True(oldCreate.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.MethodNotAllowed);
|
||||
using HttpResponseMessage oldRotate = await _backend.Admin.PostAsync($"/api/admin/probes/{id}/rotate", null);
|
||||
Assert.True(oldRotate.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.MethodNotAllowed);
|
||||
|
||||
// 改名
|
||||
using HttpResponseMessage renamed = await _backend.Admin.PatchAsync("/api/admin/probes/" + id, BackendFixture.Json(new JsonObject { ["name"] = name + "-b" }));
|
||||
Assert.Equal(name + "-b", JsonNode.Parse(await renamed.Content.ReadAsStringAsync())!["name"]!.GetValue<string>());
|
||||
|
||||
// 吊销:从名单里消失,面板里留着记录
|
||||
using HttpResponseMessage revoked = await _backend.Admin.PostAsync($"/api/admin/probes/{id}/revoke", null);
|
||||
Assert.True(JsonNode.Parse(await revoked.Content.ReadAsStringAsync())!["revoked"]!.GetValue<bool>());
|
||||
await agent.WaitForTaskSetAsync(s => s.Peers.All(p => p.ProbeId.Value != id));
|
||||
// 停用:从名单里消失,记录还在
|
||||
using HttpResponseMessage disabled = await _backend.Admin.PostAsync($"/api/admin/agents/{id}/disable", BackendFixture.Json(new JsonObject()));
|
||||
Assert.True(disabled.IsSuccessStatusCode);
|
||||
await listener.WaitForTaskSetAsync(s => s.Peers.All(p => p.NodeId.Value != id));
|
||||
|
||||
// 删除
|
||||
using HttpResponseMessage deleted = await _backend.Admin.DeleteAsync("/api/admin/probes/" + id);
|
||||
using HttpResponseMessage deleted = await _backend.Admin.DeleteAsync("/api/admin/agents/" + id);
|
||||
Assert.Equal(HttpStatusCode.NoContent, deleted.StatusCode);
|
||||
using HttpResponseMessage gone = await _backend.Admin.GetAsync("/api/admin/probes/" + id);
|
||||
using HttpResponseMessage gone = await _backend.Admin.GetAsync("/api/admin/agents/" + id);
|
||||
Assert.Equal(HttpStatusCode.NotFound, gone.StatusCode);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user