refactor!: rename the project from Monitor to MoShiro [CI]
ci/woodpecker/push/main-amd64-build Pipeline was successful

Namespaces, assemblies, solution, binaries, images, systemd units, config paths, environment variables, protocol methods and the Linux user are all renamed. Breaking: existing databases, node identities and installed units must be recreated.
This commit is contained in:
justme committed 2026-10-07 02:03:25 +08:00
1 parent 27ac7fb787
commit 2e146d4da7
309 files changed
+1565 -1572

No files matched your search

@@ -1,6 +1,6 @@
using Monitor.Backend.Analysis;
using MoShiro.Backend.Analysis;
namespace Monitor.Backend.Analysis.Tests;
namespace MoShiro.Backend.Analysis.Tests;
public sealed class AccumulatorTests
{
@@ -6,8 +6,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="xunit.v3" />
<ProjectReference Include="..\..\src\Monitor.Backend.Analysis\Monitor.Backend.Analysis.csproj" />
<ProjectReference Include="..\Monitor.TestSupport\Monitor.TestSupport.csproj" />
<ProjectReference Include="..\..\src\MoShiro.Backend.Analysis\MoShiro.Backend.Analysis.csproj" />
<ProjectReference Include="..\MoShiro.TestSupport\MoShiro.TestSupport.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
@@ -1,8 +1,8 @@
using Fleet.Core;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Analysis.Tests;
namespace MoShiro.Backend.Analysis.Tests;
public sealed class ModelAndTestEvaluatorTests
{
@@ -1,8 +1,8 @@
using Fleet.Core;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Analysis.Tests;
namespace MoShiro.Backend.Analysis.Tests;
public sealed class OpTests
{
@@ -1,9 +1,9 @@
using System.Text.Json.Nodes;
using Monitor.Measurement;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Measurement;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>真实的后端 + 真实的 Agent(进程内)+ 假 Probe:覆盖 Agent 与 Backend 的链路、Probe 与 Agent 的点对点会话和数据面。</summary>
[Collection("backend")]
@@ -3,11 +3,11 @@ using Fleet.Core;
using Fleet.Enrollment.Client;
using Fleet.Hosting;
using Fleet.Identity;
using Monitor.Agent;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Agent;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>
/// 进程内的真实 Agent:用 <see cref="EnrollmentClient"/> 注册(和 <c>fleet-enroll</c> 同一份代码)、写出身份目录、
@@ -43,7 +43,7 @@ public sealed class AgentHarness : IAsyncDisposable
public static async Task<AgentHarness> StartAsync(BackendFixture backend, Action<AgentConfig>? configure = null, string? name = null)
{
string directory = Path.Combine(Path.GetTempPath(), "monitor-agent-" + Guid.NewGuid().ToString("N"));
string directory = Path.Combine(Path.GetTempPath(), "moshiro-agent-" + Guid.NewGuid().ToString("N"));
string token = await backend.CreateJoinTokenAsync();
using var http = new HttpClient();
Result<EnrollmentResult> enrolled = await EnrollmentClient.EnrollAsync(http, backend.BaseAddress, token, name ?? backend.UniqueName("agent"), directory, AgentApp.Version, null, TimeProvider.System, CancellationToken.None);
@@ -1,24 +1,24 @@
using System.Net.Http.Headers;
using System.Text.Json.Nodes;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>
/// 对一个正在运行的 NativeAOT 发布产物跑一遍真实的 Agent 流程(注册、WebSocket、状态同步、二进制收件箱、查询、SSE、按需测试)。
/// 只有设置了 <c>MONITOR_AOT_URL</c> 才会运行:
/// <c>dotnet publish backend/src/Monitor.Backend -r win-x64 -p:PublishAot=true</c>,启动产物,再设置该环境变量后运行本测试。
/// 只有设置了 <c>MOSHIRO_AOT_URL</c> 才会运行:
/// <c>dotnet publish backend/src/MoShiro.Backend -r win-x64 -p:PublishAot=true</c>,启动产物,再设置该环境变量后运行本测试。
/// 管理员凭据与测试夹具相同(用户 admin,口令见 <see cref="BackendFixture.AdminPassword"/>)。
/// </summary>
public sealed class AotSmokeTests
{
private static string? Url => Environment.GetEnvironmentVariable("MONITOR_AOT_URL");
private static string? Url => Environment.GetEnvironmentVariable("MOSHIRO_AOT_URL");
[Fact]
public async Task TheNativeBinary_ServesTheWholeAgentWorkflow()
{
Assert.SkipUnless(Url is not null, "set MONITOR_AOT_URL to the address of a running NativeAOT build to run this test");
Assert.SkipUnless(Url is not null, "set MOSHIRO_AOT_URL to the address of a running NativeAOT build to run this test");
var baseAddress = new Uri(Url!);
using var http = new HttpClient { BaseAddress = baseAddress };
@@ -2,7 +2,7 @@ using System.Net;
using System.Net.Http.Headers;
using System.Text.Json.Nodes;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("backend")]
public sealed class ApiBasicsTests
@@ -217,8 +217,8 @@ public sealed class ApiBasicsTests
using HttpResponseMessage allowed = await _backend.Admin.GetAsync("/metrics");
string text = await allowed.Content.ReadAsStringAsync();
Assert.Equal(HttpStatusCode.OK, allowed.StatusCode);
Assert.Contains("monitor_agents_online", text);
Assert.Contains("monitor_analysis_queue_length", text);
Assert.Contains("moshiro_agents_online", text);
Assert.Contains("moshiro_analysis_queue_length", text);
}
[Fact]
@@ -1,6 +1,6 @@
using System.Xml.Linq;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>
/// 依赖规则(docs/project-layout.md 第 3 节):由测试在构建期强制,等价于文档里的 <c>eng/check-deps</c>。
@@ -17,7 +17,8 @@ public sealed class ArchitectureTests
private static string FindRoot()
{
string? dir = AppContext.BaseDirectory;
while (dir is not null && !File.Exists(Path.Combine(dir, "global.json")))
// backend/ 目录的标志:BannedSymbols.txt 只在这里(global.json 和 NuGet 版本表已经挪到仓库根目录)
while (dir is not null && !File.Exists(Path.Combine(dir, "BannedSymbols.txt")))
{
dir = Path.GetDirectoryName(dir);
}
@@ -48,17 +49,17 @@ public sealed class ArchitectureTests
private static bool IsFleet(Project p) => p.Name.StartsWith("Fleet.", StringComparison.Ordinal) && !IsTest(p);
[Fact]
public void Fleet_NeverReferencesMonitor_InProjectsOrCode()
public void Fleet_NeverReferencesMoShiro_InProjectsOrCode()
{
foreach (Project project in LoadProjects().Where(IsFleet))
{
Assert.DoesNotContain(project.ProjectReferences, r => r.StartsWith("Monitor.", StringComparison.Ordinal));
Assert.DoesNotContain(project.ProjectReferences, r => r.StartsWith("MoShiro.", StringComparison.Ordinal));
foreach (string file in Directory.EnumerateFiles(project.Directory, "*.cs", SearchOption.AllDirectories).Where(f => !f.Contains(Path.DirectorySeparatorChar + "obj" + Path.DirectorySeparatorChar, StringComparison.Ordinal)))
{
string text = File.ReadAllText(file);
Assert.False(text.Contains("using Monitor.", StringComparison.Ordinal) || text.Contains("namespace Monitor.", StringComparison.Ordinal), $"{file} refers to Monitor.*");
Assert.False(text.Contains("monitor.", StringComparison.Ordinal) && text.Contains("\"monitor.", StringComparison.Ordinal), $"{file} hard-codes a Monitor method or channel name");
Assert.False(text.Contains("7443", StringComparison.Ordinal), $"{file} hard-codes a Monitor port");
Assert.False(text.Contains("using MoShiro.", StringComparison.Ordinal) || text.Contains("namespace MoShiro.", StringComparison.Ordinal), $"{file} refers to MoShiro.*");
Assert.False(text.Contains("moshiro.", StringComparison.Ordinal) && text.Contains("\"moshiro.", StringComparison.Ordinal), $"{file} hard-codes a MoShiro method or channel name");
Assert.False(text.Contains("7443", StringComparison.Ordinal), $"{file} hard-codes a MoShiro port");
}
}
}
@@ -66,7 +67,7 @@ public sealed class ArchitectureTests
[Fact]
public void NodeSideLibraries_HaveNoRuntimePackages_AndUseTheNodeBanList()
{
string[] nodeSide = ["Fleet.Core", "Fleet.Identity", "Fleet.Rpc", "Monitor.Protocol", "Monitor.Backend.Analysis"];
string[] nodeSide = ["Fleet.Core", "Fleet.Identity", "Fleet.Rpc", "MoShiro.Protocol", "MoShiro.Backend.Analysis"];
foreach (Project project in LoadProjects().Where(p => nodeSide.Contains(p.Name)))
{
Assert.All(project.Packages, package => Assert.Contains(package, s_allowedAnalyzers));
@@ -83,8 +84,8 @@ public sealed class ArchitectureTests
public void Protocol_DependsOnlyOnFleetCoreAndRpc_AndAnalysisOnlyOnProtocol()
{
List<Project> projects = LoadProjects();
Assert.Equal(["Fleet.Core", "Fleet.Rpc"], projects.Single(p => p.Name == "Monitor.Protocol").ProjectReferences.Order().ToArray());
Assert.Equal(["Monitor.Protocol"], projects.Single(p => p.Name == "Monitor.Backend.Analysis").ProjectReferences.ToArray());
Assert.Equal(["Fleet.Core", "Fleet.Rpc"], projects.Single(p => p.Name == "MoShiro.Protocol").ProjectReferences.Order().ToArray());
Assert.Equal(["MoShiro.Protocol"], projects.Single(p => p.Name == "MoShiro.Backend.Analysis").ProjectReferences.ToArray());
}
[Fact]
@@ -94,19 +95,19 @@ public sealed class ArchitectureTests
Project Get(string name) => projects.Single(p => p.Name == name);
// Agent 是纯采集进程:不引用后端的任何层,也不引用 Fleet 的服务端库
Project agent = Get("Monitor.Agent");
Assert.DoesNotContain(agent.ProjectReferences, r => r.StartsWith("Monitor.Backend", StringComparison.Ordinal) || r.StartsWith("Fleet.Server", StringComparison.Ordinal) || r.StartsWith("Fleet.Enrollment", StringComparison.Ordinal));
Assert.Equal(["Fleet.Hosting", "Fleet.Link", "Monitor.Measurement"], agent.ProjectReferences.Order().ToArray());
Project agent = Get("MoShiro.Agent");
Assert.DoesNotContain(agent.ProjectReferences, r => r.StartsWith("MoShiro.Backend", StringComparison.Ordinal) || r.StartsWith("Fleet.Server", StringComparison.Ordinal) || r.StartsWith("Fleet.Enrollment", StringComparison.Ordinal));
Assert.Equal(["Fleet.Hosting", "Fleet.Link", "MoShiro.Measurement"], agent.ProjectReferences.Order().ToArray());
// Probe 同理,并且不认识 Backend 和 Agent
Project probe = Get("Monitor.Probe");
Assert.Equal(["Fleet.Hosting", "Fleet.Link", "Monitor.Measurement"], probe.ProjectReferences.Order().ToArray());
Project probe = Get("MoShiro.Probe");
Assert.Equal(["Fleet.Hosting", "Fleet.Link", "MoShiro.Measurement"], probe.ProjectReferences.Order().ToArray());
// 两个节点的共享库只依赖协议
Assert.Equal(["Monitor.Protocol"], Get("Monitor.Measurement").ProjectReferences.ToArray());
Assert.Equal(["MoShiro.Protocol"], Get("MoShiro.Measurement").ProjectReferences.ToArray());
// 节点一侧的库没有运行时依赖包,并使用节点禁用列表(不能用 HttpClient、Process、写文件;唯一的写文件豁免在 ConfigStore)
foreach (string name in new[] { "Fleet.Link", "Fleet.Hosting", "Monitor.Measurement", "Monitor.Agent", "Monitor.Probe" })
foreach (string name in new[] { "Fleet.Link", "Fleet.Hosting", "MoShiro.Measurement", "MoShiro.Agent", "MoShiro.Probe" })
{
Project project = Get(name);
Assert.All(project.Packages, package => Assert.Contains(package, s_allowedAnalyzers));
@@ -126,7 +127,7 @@ public sealed class ArchitectureTests
{
// 采集层(协议、积木、Agent、Probe、Fleet)只认识机制(传输、节奏、方向、时间戳)和原始列;
// REALITY、Hysteria2、Minecraft 这些业务概念以及由原语编排成的测试方案只存在于 Backend。
string[] collectionRoots = ["shared/Monitor.Protocol", "shared/Monitor.Measurement", "agent/src", "probe/src", "fleet/src"];
string[] collectionRoots = ["shared/MoShiro.Protocol", "shared/MoShiro.Measurement", "agent/src", "probe/src", "fleet/src"];
string[] forbidden = ["reality", "hysteria", "hy2", "minecraft", "brutal", "PlanSpec", "PhaseSpec"];
foreach (string root in collectionRoots)
{
@@ -142,7 +143,7 @@ public sealed class ArchitectureTests
}
// 派生量(丢包率、RTT、抖动)由 Backend 的分析层算:采集层的积木不引用分析层
Assert.DoesNotContain(LoadProjects().Single(p => p.Name == "Monitor.Measurement").ProjectReferences, r => r.Contains("Analysis", StringComparison.Ordinal));
Assert.DoesNotContain(LoadProjects().Single(p => p.Name == "MoShiro.Measurement").ProjectReferences, r => r.Contains("Analysis", StringComparison.Ordinal));
}
[Fact]
@@ -152,22 +153,22 @@ public sealed class ArchitectureTests
Project Get(string name) => projects.Single(p => p.Name == name);
// Api 不能引用 Storage(也不引用 Gateway):只调用 Application
Assert.DoesNotContain(Get("Monitor.Backend.Api").ProjectReferences, r => r.Contains("Storage", StringComparison.Ordinal) || r.Contains("Gateway", StringComparison.Ordinal));
Assert.Equal(["Monitor.Backend.Application"], Get("Monitor.Backend.Api").ProjectReferences.ToArray());
Assert.DoesNotContain(Get("MoShiro.Backend.Api").ProjectReferences, r => r.Contains("Storage", StringComparison.Ordinal) || r.Contains("Gateway", StringComparison.Ordinal));
Assert.Equal(["MoShiro.Backend.Application"], Get("MoShiro.Backend.Api").ProjectReferences.ToArray());
// Application 不依赖任何下游层
Assert.DoesNotContain(Get("Monitor.Backend.Application").ProjectReferences, r => r.Contains("Storage", StringComparison.Ordinal) || r.Contains("Api", StringComparison.Ordinal) || r.Contains("Gateway", StringComparison.Ordinal));
Assert.DoesNotContain(Get("MoShiro.Backend.Application").ProjectReferences, r => r.Contains("Storage", StringComparison.Ordinal) || r.Contains("Api", StringComparison.Ordinal) || r.Contains("Gateway", StringComparison.Ordinal));
// Gateway 只通过 Application 与 Fleet.Server 工作
Assert.DoesNotContain(Get("Monitor.Backend.Gateway").ProjectReferences, r => r.Contains("Storage", StringComparison.Ordinal) || r.Contains("Backend.Api", StringComparison.Ordinal));
Assert.DoesNotContain(Get("MoShiro.Backend.Gateway").ProjectReferences, r => r.Contains("Storage", StringComparison.Ordinal) || r.Contains("Backend.Api", StringComparison.Ordinal));
// Storage 实现 Application 的端口,不认识 Api 和 Gateway
Assert.DoesNotContain(Get("Monitor.Backend.Storage.Sqlite").ProjectReferences, r => r.Contains("Backend.Api", StringComparison.Ordinal) || r.Contains("Gateway", StringComparison.Ordinal));
Assert.DoesNotContain(Get("MoShiro.Backend.Storage.Sqlite").ProjectReferences, r => r.Contains("Backend.Api", StringComparison.Ordinal) || r.Contains("Gateway", StringComparison.Ordinal));
// 只有宿主(组合根)认识所有层
Assert.Equivalent(
new[] { "Monitor.Backend.Api", "Monitor.Backend.Gateway", "Monitor.Backend.Storage.Sqlite" },
Get("Monitor.Backend").ProjectReferences.ToArray());
new[] { "MoShiro.Backend.Api", "MoShiro.Backend.Gateway", "MoShiro.Backend.Storage.Sqlite" },
Get("MoShiro.Backend").ProjectReferences.ToArray());
}
[Fact]
@@ -175,7 +176,7 @@ public sealed class ArchitectureTests
{
string[] allowed =
[
"Microsoft.CodeAnalysis.BannedApiAnalyzers", "Microsoft.Data.Sqlite", "Microsoft.AspNetCore.OpenApi",
"Microsoft.CodeAnalysis.BannedApiAnalyzers", "Microsoft.Data.Sqlite", "SQLitePCLRaw.lib.e_sqlite3", "Microsoft.AspNetCore.OpenApi",
"Microsoft.Extensions.ApiDescription.Server", "xunit.v3", "Microsoft.Extensions.TimeProvider.Testing",
];
foreach (Project project in LoadProjects())
@@ -187,7 +188,7 @@ public sealed class ArchitectureTests
[Fact]
public void ApplicationAndApi_NeverTouchSqliteOrTheFileSystemDirectly()
{
foreach (string layer in new[] { "Monitor.Backend.Application", "Monitor.Backend.Api", "Monitor.Backend.Gateway", "Monitor.Backend.Analysis" })
foreach (string layer in new[] { "MoShiro.Backend.Application", "MoShiro.Backend.Api", "MoShiro.Backend.Gateway", "MoShiro.Backend.Analysis" })
{
string dir = Path.Combine(s_root, "src", layer);
foreach (string file in Directory.EnumerateFiles(dir, "*.cs", SearchOption.AllDirectories).Where(f => !f.Contains(Path.DirectorySeparatorChar + "obj" + Path.DirectorySeparatorChar, StringComparison.Ordinal)))
@@ -204,7 +205,7 @@ public sealed class ArchitectureTests
public void NoProductionCode_BuildsSqlByConcatenatingValues()
{
// 手写的参数化 SQL:只有表名可以来自常量;值一律通过参数
string dir = Path.Combine(s_root, "src", "Monitor.Backend.Storage.Sqlite");
string dir = Path.Combine(s_root, "src", "MoShiro.Backend.Storage.Sqlite");
foreach (string file in Directory.EnumerateFiles(dir, "*.cs", SearchOption.AllDirectories).Where(f => !f.Contains(Path.DirectorySeparatorChar + "obj" + Path.DirectorySeparatorChar, StringComparison.Ordinal)))
{
string text = File.ReadAllText(file);
@@ -5,10 +5,10 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.DependencyInjection;
using Monitor.Backend.Application;
using Monitor.TestSupport;
using MoShiro.Backend.Application;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>
/// 在真实的 Kestrel 端口上启动整个后端(真实的 SQLite 与段文件,只是用临时目录和较快的轮询),
@@ -58,7 +58,7 @@ public class BackendFixture : IAsyncLifetime
public async ValueTask InitializeAsync()
{
DataDirectory = Path.Combine(Path.GetTempPath(), "monitor-tests-" + Guid.NewGuid().ToString("N"));
DataDirectory = Path.Combine(Path.GetTempPath(), "moshiro-tests-" + Guid.NewGuid().ToString("N"));
await StartAsync();
_adminToken = await CreateAdminTokenAsync();
CreateClients();
@@ -100,7 +100,7 @@ public class BackendFixture : IAsyncLifetime
public async Task<AgentSimulator> ReconnectAsync(AgentSimulator previous)
{
AgentSimulator agent = AgentSimulator.Restore(previous.ExportedKey, previous.NodeId);
agent.ProbeForChunks = new Monitor.Protocol.ProbeId(DefaultProbeId);
agent.ProbeForChunks = new MoShiro.Protocol.ProbeId(DefaultProbeId);
_agents.Add(agent);
await agent.ConnectAsync(BaseAddress);
return agent;
@@ -129,7 +129,7 @@ public class BackendFixture : IAsyncLifetime
{
string token = await CreateJoinTokenAsync(autoApprove);
AgentSimulator agent = await AgentSimulator.EnrollAsync(Anonymous, token, name ?? UniqueName("agent"), kinds);
agent.ProbeForChunks = new Monitor.Protocol.ProbeId(DefaultProbeId);
agent.ProbeForChunks = new MoShiro.Protocol.ProbeId(DefaultProbeId);
_agents.Add(agent);
await agent.ConnectAsync(BaseAddress, hello, kinds);
return agent;
@@ -1,9 +1,9 @@
using System.Net;
using System.Text.Json.Nodes;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("backend")]
public sealed class EnrollmentAndTaskSetTests
@@ -150,7 +150,7 @@ public sealed class EnrollmentAndTaskSetTests
RawChunk chunk = ChunkFactory.UdpEchoClean(agent.NodeId, TimeProvider.System.GetUtcNow().ToUnixTimeMilliseconds() - 30_000);
chunk.Header.AgentId = agent.NodeId;
await agent.SendBinaryAsync(Fleet.Rpc.OutboxFrame.Build(MonitorChannels.RawChunk, chunk.Header.ChunkId.Value, RawChunkCodec.Encode(chunk)));
await agent.SendBinaryAsync(Fleet.Rpc.OutboxFrame.Build(MoShiroChannels.RawChunk, chunk.Header.ChunkId.Value, RawChunkCodec.Encode(chunk)));
await Task.Delay(300);
JsonNode chunks = await _backend.GetJsonAsync(_backend.Admin, "/api/admin/raw/chunks?agent_id=" + agent.NodeId.Value);
Assert.Empty(chunks["items"]!.AsArray());
@@ -1,9 +1,9 @@
using System.Text.Json.Nodes;
using Monitor.Agent;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Agent;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
public sealed class HostSamplerTests
{
@@ -2,10 +2,10 @@ using System.Net;
using System.Net.Http.Headers;
using System.Text.Json.Nodes;
using Fleet.Core;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("backend")]
public sealed class IngestAndQueryTests
@@ -120,11 +120,11 @@ public sealed class IngestAndQueryTests
var ack = new TaskCompletionSource<bool>();
byte[] corrupt = RawChunkCodec.Encode(ChunkFactory.UdpEchoClean(agent.NodeId, start));
corrupt[^1] ^= 0xFF;
await agent.SendBinaryAsync(Fleet.Rpc.OutboxFrame.Build(MonitorChannels.RawChunk, garbage.Value, corrupt));
await agent.SendBinaryAsync(Fleet.Rpc.OutboxFrame.Build(MoShiroChannels.RawChunk, garbage.Value, corrupt));
// 冒名的块:agent_id 与认证的节点不一致
RawChunk spoofed = ChunkFactory.UdpEchoClean(new NodeId("agt_someone_else"), start);
await agent.SendBinaryAsync(Fleet.Rpc.OutboxFrame.Build(MonitorChannels.RawChunk, spoofed.Header.ChunkId.Value, RawChunkCodec.Encode(spoofed)));
await agent.SendBinaryAsync(Fleet.Rpc.OutboxFrame.Build(MoShiroChannels.RawChunk, spoofed.Header.ChunkId.Value, RawChunkCodec.Encode(spoofed)));
await BackendFixture.EventuallyAsync(async () =>
{
@@ -1,8 +1,8 @@
using System.Net;
using System.Text.Json.Nodes;
using Monitor.TestSupport;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("probe-e2e")]
public sealed class LinkTests
@@ -1,10 +1,10 @@
using System.Net;
using System.Text.Json.Nodes;
using Fleet.Core;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("backend")]
public sealed class LiveTests
@@ -1,7 +1,7 @@
using System.Net;
using System.Text.Json.Nodes;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>登录限流按来源地址计,会影响同一地址上的其他登录,所以用独立的后端实例。</summary>
public sealed class LoginRateLimitTests : IClassFixture<StrictLoginFixture>
@@ -8,11 +8,11 @@
<ItemGroup>
<PackageReference Include="xunit.v3" />
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" />
<ProjectReference Include="..\..\src\Monitor.Backend\Monitor.Backend.csproj" />
<ProjectReference Include="..\Monitor.TestSupport\Monitor.TestSupport.csproj" />
<ProjectReference Include="..\..\agent\src\Monitor.Agent\Monitor.Agent.csproj" />
<ProjectReference Include="..\..\src\MoShiro.Backend\MoShiro.Backend.csproj" />
<ProjectReference Include="..\MoShiro.TestSupport\MoShiro.TestSupport.csproj" />
<ProjectReference Include="..\..\agent\src\MoShiro.Agent\MoShiro.Agent.csproj" />
<ProjectReference Include="..\..\fleet\src\Fleet.Enrollment.Client\Fleet.Enrollment.Client.csproj" />
<ProjectReference Include="..\..\probe\src\Monitor.Probe\Monitor.Probe.csproj" />
<ProjectReference Include="..\..\probe\src\MoShiro.Probe\MoShiro.Probe.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
@@ -2,10 +2,10 @@ using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using Microsoft.Extensions.Time.Testing;
using Monitor.Backend.Application;
using Monitor.Protocol;
using MoShiro.Backend.Application;
using MoShiro.Protocol;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
public sealed partial class NodeInfoTests
{
@@ -1,10 +1,10 @@
using System.Net;
using System.Text.Json.Nodes;
using Fleet.Core;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("backend")]
public sealed class OnDemandTestsTests
@@ -233,7 +233,7 @@ public sealed class OnDemandTestsTests
Assert.Equal("line:" + _backend.DefaultProbeId, item["series"]!.GetValue<string>());
using HttpResponseMessage download = await _backend.Admin.GetAsync("/api/admin/raw/chunks/" + chunk.Header.ChunkId);
Assert.Equal("application/vnd.monitor.rawchunk", download.Content.Headers.ContentType!.MediaType);
Assert.Equal("application/vnd.moshiro.rawchunk", download.Content.Headers.ContentType!.MediaType);
RawChunk decoded = RawChunkCodec.Decode(await download.Content.ReadAsByteArrayAsync()).Value;
Assert.Equal(chunk.Header.ChunkId, decoded.Header.ChunkId);
Assert.Equal(100, decoded.Find("msg")!.Count);
@@ -1,7 +1,7 @@
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>
/// 对照 docs/api-guidelines.md 检查运行时生成的 OpenAPI 文档:operationId、路径风格、错误响应格式、字段命名。
@@ -5,12 +5,12 @@ using Fleet.Core;
using Fleet.Rpc;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Monitor.Backend.Analysis;
using Monitor.Backend.Application;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Backend.Analysis;
using MoShiro.Backend.Application;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("backend")]
public sealed class OperationsAndSecurityTests
@@ -169,7 +169,7 @@ public sealed class OperationsAndSecurityTests
await agent.SendBinaryAsync(OutboxFrame.Build(99, Guid.NewGuid(), [1])); // 没有注册的通道
// 节点不能调用任何“执行”类的方法:Fleet 根本没有这样的路由
foreach (string method in new[] { "fleet.exec", "fleet.shell", "monitor.exec", "monitor.write_file", "fleet.update" })
foreach (string method in new[] { "fleet.exec", "fleet.shell", "moshiro.exec", "moshiro.write_file", "fleet.update" })
{
RpcMessage reply = await agent.RequestRawAsync(method);
Assert.Equal("fleet_unsupported", reply.Error!.Data!.Code);
@@ -1,8 +1,8 @@
using System.Text.Json.Nodes;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>重启后端:数据、身份和令牌都必须还在;热层从原始块回填;段文件尾部没有索引的数据被截断。</summary>
public sealed class PersistenceTests : IAsyncLifetime
@@ -1,8 +1,8 @@
using System.Text.Json;
using Monitor.Backend.Application;
using Monitor.Protocol;
using MoShiro.Backend.Application;
using MoShiro.Protocol;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
public sealed class PlanTests
{
@@ -1,6 +1,6 @@
using System.Text.Json.Nodes;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>真实的后端 + 真实的 Agent + 真实的 Probe(都在进程内,回环网络)。</summary>
[Collection("probe-e2e")]
@@ -1,9 +1,9 @@
using System.Text;
using System.Text.Json.Nodes;
using Fleet.Core;
using Monitor.Probe;
using MoShiro.Probe;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>进程内的真实 Probe:用后端生成的身份文件(和面板下载的 probe.json 同一份)启动 <see cref="ProbeApp"/>。</summary>
public sealed class ProbeHarness : IAsyncDisposable
@@ -31,7 +31,7 @@ public sealed class ProbeHarness : IAsyncDisposable
JsonNode created = await backend.CreateProbeAsync();
string json = created.ToJsonString();
string directory = Path.Combine(Path.GetTempPath(), "monitor-probe-" + Guid.NewGuid().ToString("N"));
string directory = Path.Combine(Path.GetTempPath(), "moshiro-probe-" + Guid.NewGuid().ToString("N"));
System.IO.Directory.CreateDirectory(directory);
string path = Path.Combine(directory, "probe.json");
await File.WriteAllTextAsync(path, json, Encoding.UTF8);
@@ -1,10 +1,10 @@
using Fleet.Core;
using Microsoft.Extensions.Time.Testing;
using Monitor.Backend.Analysis;
using Monitor.Backend.Application;
using Monitor.Protocol;
using MoShiro.Backend.Analysis;
using MoShiro.Backend.Application;
using MoShiro.Protocol;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>时间段、步长与数据源的规划:与 docs/query.md 里的表格逐行对应。</summary>
public sealed class QueryPlanningTests
@@ -127,18 +127,18 @@ public sealed class QueryPlanningTests
{
Result<QueryRange> result = Resolver.Resolve(new RangeRequest(range, null, null, null, null), s_now);
Assert.False(result.IsSuccess);
Assert.Contains(result.Error.Code, new[] { MonitorErrors.InvalidRange, MonitorErrors.RangeTooLarge });
Assert.Contains(result.Error.Code, new[] { MoShiroErrors.InvalidRange, MoShiroErrors.RangeTooLarge });
}
[Fact]
public void RangeResolver_EnforcesThePointLimit_AndSuggestsAStep()
{
Result<QueryRange> result = Resolver.Resolve(new RangeRequest("30d", null, null, "1m", null), s_now);
Assert.Equal(MonitorErrors.RangeTooLarge, result.Error.Code);
Assert.Equal(MoShiroErrors.RangeTooLarge, result.Error.Code);
Assert.Contains("step=", result.Error.Message);
Assert.Equal(MonitorErrors.InvalidStep, Resolver.Resolve(new RangeRequest("1h", null, null, "7s", null), s_now).Error.Code);
Assert.Equal(MonitorErrors.InvalidRange, Resolver.Resolve(new RangeRequest(null, DateTimeOffset.FromUnixTimeMilliseconds(s_now), DateTimeOffset.FromUnixTimeMilliseconds(s_now), null, null), s_now).Error.Code);
Assert.Equal(MonitorErrors.ValidationFailed, Resolver.Resolve(new RangeRequest("1h", DateTimeOffset.FromUnixTimeMilliseconds(s_now - Hour), null, null, null), s_now).Error.Code);
Assert.Equal(MoShiroErrors.InvalidStep, Resolver.Resolve(new RangeRequest("1h", null, null, "7s", null), s_now).Error.Code);
Assert.Equal(MoShiroErrors.InvalidRange, Resolver.Resolve(new RangeRequest(null, DateTimeOffset.FromUnixTimeMilliseconds(s_now), DateTimeOffset.FromUnixTimeMilliseconds(s_now), null, null), s_now).Error.Code);
Assert.Equal(MoShiroErrors.ValidationFailed, Resolver.Resolve(new RangeRequest("1h", DateTimeOffset.FromUnixTimeMilliseconds(s_now - Hour), null, null, null), s_now).Error.Code);
}
[Fact]
@@ -147,7 +147,7 @@ public sealed class QueryPlanningTests
Assert.True(Resolver.Resolve(new RangeRequest("1h", null, null, null, "UTC"), s_now).IsSuccess);
Assert.True(Resolver.Resolve(new RangeRequest("1h", null, null, null, null), s_now).IsSuccess);
Result<QueryRange> shanghai = Resolver.Resolve(new RangeRequest("7d", null, null, "1d", "Asia/Shanghai"), s_now);
Assert.Equal(MonitorErrors.ValidationFailed, shanghai.Error.Code);
Assert.Equal(MoShiroErrors.ValidationFailed, shanghai.Error.Code);
Assert.IsType<ValidationError>(shanghai.Error);
}
@@ -209,10 +209,10 @@ public sealed class QueryPlanningTests
Assert.Equal(selection.ExportIds.Count, selection.ExportIds.Distinct().Count());
Assert.True(selection.WantsScores);
Assert.Equal(MonitorErrors.UnknownMetric, MetricSelection.Resolve("nope", null, 100).Error.Code);
Assert.Equal(MonitorErrors.UnknownMetric, MetricSelection.Resolve("score.nope", null, 100).Error.Code);
Assert.Equal(MonitorErrors.UnknownPreset, MetricSelection.Resolve(null, "nope", 100).Error.Code);
Assert.Equal(MonitorErrors.TooManyMetrics, MetricSelection.Resolve(null, "udp", 3).Error.Code);
Assert.Equal(MoShiroErrors.UnknownMetric, MetricSelection.Resolve("nope", null, 100).Error.Code);
Assert.Equal(MoShiroErrors.UnknownMetric, MetricSelection.Resolve("score.nope", null, 100).Error.Code);
Assert.Equal(MoShiroErrors.UnknownPreset, MetricSelection.Resolve(null, "nope", 100).Error.Code);
Assert.Equal(MoShiroErrors.TooManyMetrics, MetricSelection.Resolve(null, "udp", 3).Error.Code);
}
[Fact]
@@ -1,7 +1,7 @@
using System.Collections.Concurrent;
using System.Text.Json.Nodes;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
public sealed record SseEvent(string Name, JsonNode? Data);
@@ -2,20 +2,20 @@ using System.Text;
using Fleet.Core;
using Fleet.Server;
using Microsoft.Data.Sqlite;
using Monitor.Backend.Analysis;
using Monitor.Backend.Application;
using Monitor.Backend.Storage.Sqlite;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Backend.Analysis;
using MoShiro.Backend.Application;
using MoShiro.Backend.Storage.Sqlite;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
/// <summary>存储层:段文件与索引、指标窗口、队列、节点与令牌。直接使用真实的 SQLite 和临时目录。</summary>
public sealed class StorageTests : IDisposable
{
private static readonly NodeId s_agent = new("agt_storage");
private readonly string _dir = Path.Combine(Path.GetTempPath(), "monitor-storage-" + Guid.NewGuid().ToString("N"));
private readonly string _dir = Path.Combine(Path.GetTempPath(), "moshiro-storage-" + Guid.NewGuid().ToString("N"));
private readonly SqliteDatabase _db;
public StorageTests()
@@ -269,7 +269,7 @@ public sealed class StorageTests : IDisposable
Assert.True((await ((IFleetStore)store).CreateNodeAsync(Node("agt_4", "hk", NodeStatus.Pending), default)).IsSuccess);
Result<AgentRecord> approve = await store.SetStatusAsync(new NodeId("agt_3"), NodeStatus.Active, default);
Assert.Equal(MonitorErrors.NameConflict, approve.Error.Code);
Assert.Equal(MoShiroErrors.NameConflict, approve.Error.Code);
// 接管:保留旧 ID 与历史,换成新公钥,删除待审批记录
Result<AgentRecord> takeover = await store.TakeoverAsync(new NodeId("agt_3"), default);
@@ -277,7 +277,7 @@ public sealed class StorageTests : IDisposable
Assert.Equal(new byte[] { 1, 2, 3 }, takeover.Value.PublicKey);
Assert.Null(await store.GetAsync(new NodeId("agt_3"), default));
Assert.Equal(NodeStatus.Pending, (await store.GetAsync(new NodeId("agt_4"), default))!.Status);
Assert.Equal(MonitorErrors.AgentNotFound, (await store.TakeoverAsync(new NodeId("agt_1"), default)).Error.Code); // 只有 pending 能接管别人
Assert.Equal(MoShiroErrors.AgentNotFound, (await store.TakeoverAsync(new NodeId("agt_1"), default)).Error.Code); // 只有 pending 能接管别人
}
[Fact]
@@ -288,12 +288,12 @@ public sealed class StorageTests : IDisposable
AgentRecord current = (await store.GetAsync(new NodeId("agt_1"), default))!;
Result<AgentRecord> stale = await store.PatchAsync(current.Id, new AgentPatch { Labels = ["x"] }, current.RowVersion + 5, default);
Assert.Equal(MonitorErrors.PreconditionFailed, stale.Error.Code);
Assert.Equal(MoShiroErrors.PreconditionFailed, stale.Error.Code);
Result<AgentRecord> ok = await store.PatchAsync(current.Id, new AgentPatch { Labels = ["x"] }, current.RowVersion, default);
Assert.Equal(current.RowVersion + 1, ok.Value.RowVersion);
Assert.Equal(["x"], ok.Value.Labels);
Assert.Equal("hk", ok.Value.Name); // 没写的字段不动
Assert.Equal(MonitorErrors.AgentNotFound, (await store.PatchAsync(new NodeId("agt_x"), new AgentPatch(), null, default)).Error.Code);
Assert.Equal(MoShiroErrors.AgentNotFound, (await store.PatchAsync(new NodeId("agt_x"), new AgentPatch(), null, default)).Error.Code);
}
[Fact]
@@ -368,11 +368,11 @@ public sealed class StorageTests : IDisposable
[Fact]
public void Schema_RefusesADatabaseFromANewerBuild()
{
string dir = Path.Combine(Path.GetTempPath(), "monitor-schema-" + Guid.NewGuid().ToString("N"));
string dir = Path.Combine(Path.GetTempPath(), "moshiro-schema-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(dir);
try
{
using (var connection = new SqliteConnection($"Data Source={Path.Combine(dir, "monitor.db")}"))
using (var connection = new SqliteConnection($"Data Source={Path.Combine(dir, "moshiro.db")}"))
{
connection.Open();
connection.Exec("PRAGMA user_version = 99");
@@ -1,15 +1,20 @@
using System.Net;
using System.Text.Json.Nodes;
using Monitor.Protocol;
using Monitor.TestSupport;
using MoShiro.Protocol;
using MoShiro.TestSupport;
namespace Monitor.Backend.Tests;
namespace MoShiro.Backend.Tests;
[Collection("probe-e2e")]
public sealed class UpdateTests
{
private const string Digest = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
// 每个用例取自己的 seq(单调递增):共用的后端只接受比已登记的更大的 seq,用例的先后顺序不能影响结果
private static long s_seq = TimeProvider.System.GetUtcNow().ToUnixTimeSeconds() * 1000;
private static long NextSeq() => Interlocked.Add(ref s_seq, 100);
private readonly BackendFixture _backend;
public UpdateTests(BackendFixture backend)
@@ -19,7 +24,7 @@ public sealed class UpdateTests
// 面板不验签(节点宿主机上的更新器验),所以测试里签名随便写一个格式对的
private static string Manifest(string component, long seq, string digest = Digest) =>
$$"""{"schema":1,"component":"{{component}}","channel":"dev","image":"registry.example/monitor-dev/monitor-{{component}}","digest":"{{digest}}","tag":"dev-{{seq}}","version":"0.1.1","commit":"abc","built_at":"2026-10-06T00:00:00Z","seq":{{seq}}}""";
$$"""{"schema":1,"component":"{{component}}","channel":"dev","image":"registry.example/moshiro-dev/moshiro-{{component}}","digest":"{{digest}}","tag":"dev-{{seq}}","version":"0.1.1","commit":"abc","built_at":"2026-10-06T00:00:00Z","seq":{{seq}}}""";
private async Task<HttpResponseMessage> PublishAsync(string manifest, string signature = "MEUCIQDc") =>
await _backend.Admin.PostAsync("/api/admin/releases", BackendFixture.Json(new JsonObject { ["manifest"] = manifest, ["signature"] = signature }));
@@ -27,7 +32,7 @@ public sealed class UpdateTests
[Fact]
public async Task Release_IsForwardedByteForByte_ToOnlineNodesOfThatComponent_AndToNodesThatConnectLater()
{
long seq = TimeProvider.System.GetUtcNow().ToUnixTimeSeconds() * 1000;
long seq = NextSeq();
await using AgentSimulator online = await _backend.ConnectAgentAsync();
string manifest = Manifest("agent", seq);
@@ -53,7 +58,7 @@ public sealed class UpdateTests
[Fact]
public async Task Release_Registration_RefusesBadManifests_StaleSeq_AndNeedsAnAdmin()
{
long seq = TimeProvider.System.GetUtcNow().ToUnixTimeSeconds() * 1000 + 5;
long seq = NextSeq();
Assert.Equal(HttpStatusCode.BadRequest, (await PublishAsync("{not json")).StatusCode);
Assert.Equal(HttpStatusCode.BadRequest, (await PublishAsync(Manifest("agent", seq, digest: "sha256:short"))).StatusCode);
Assert.Equal(HttpStatusCode.BadRequest, (await PublishAsync(Manifest("backend", seq))).StatusCode);
@@ -84,7 +89,7 @@ public sealed class UpdateTests
return client;
}
long seq = TimeProvider.System.GetUtcNow().ToUnixTimeSeconds() * 1000 + 50;
long seq = NextSeq();
using HttpClient release = Client(await NewTokenAsync("release"));
using HttpClient read = Client(await NewTokenAsync("read"));
var body = BackendFixture.Json(new JsonObject { ["manifest"] = Manifest("probe", seq), ["signature"] = "MEUC" });
@@ -7,9 +7,9 @@ using System.Text.Json.Nodes;
using Fleet.Core;
using Fleet.Identity;
using Fleet.Rpc;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.TestSupport;
namespace MoShiro.TestSupport;
/// <summary>
/// 一个会说 Fleet 协议的假 Agent:用加入令牌注册 → 连接 → 签名认证 → hello → 接收期望状态 → 上传原始块并等待 ACK。
@@ -48,7 +48,7 @@ public sealed class AgentSimulator : IAsyncDisposable
public ConcurrentQueue<TestRunParams> TestRuns { get; } = new();
/// <summary>面板发来的发布清单(<c>monitor.update.offer</c>)和更新请求(<c>monitor.update.apply</c>)。</summary>
/// <summary>面板发来的发布清单(<c>moshiro.update.offer</c>)和更新请求(<c>moshiro.update.apply</c>)。</summary>
public ConcurrentQueue<UpdateOfferParams> UpdateOffers { get; } = new();
private int _updateApplies;
@@ -63,7 +63,7 @@ public sealed class AgentSimulator : IAsyncDisposable
public string? CloseReason { get; private set; }
/// <summary>收到 monitor.test.run 时的应答:默认接受。</summary>
/// <summary>收到 moshiro.test.run 时的应答:默认接受。</summary>
public Func<TestRunParams, (bool Accepted, string? ErrorCode)> TestRunHandler { get; set; } = _ => (true, null);
public byte[] PublicKey => _identity.PublicKeySpki;
@@ -144,11 +144,11 @@ public sealed class AgentSimulator : IAsyncDisposable
ProbeUdpPorts = [7443],
LocalLimits = new LocalLimits { MaxTotalMbps = 200, MaxTotalPps = 20_000, MaxConcurrentTests = 1 },
},
MonitorJsonContext.Default.AgentHelloMeta);
MoShiroJsonContext.Default.AgentHelloMeta);
var hello = new HelloParams
{
NodeVersion = "0.0.1-sim",
Capabilities = [MonitorCapabilities.Base, MonitorCapabilities.KindsPrefix + string.Join(',', kinds ?? ["udp_echo", "tcp_echo", "udp_train", "connect", "hold", "tcp_bulk", "icmp_echo"])],
Capabilities = [MoShiroCapabilities.Base, MoShiroCapabilities.KindsPrefix + string.Join(',', kinds ?? ["udp_echo", "tcp_echo", "udp_train", "connect", "hold", "tcp_bulk", "icmp_echo"])],
NowUtcMs = TimeProvider.System.GetUtcNow().ToUnixTimeMilliseconds(),
Meta = meta,
};
@@ -170,7 +170,7 @@ public sealed class AgentSimulator : IAsyncDisposable
Guid id = chunk.Header.ChunkId.Value;
var ack = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
_acks[id] = ack;
await SendBinaryAsync(OutboxFrame.Build(MonitorChannels.RawChunk, id, bytes));
await SendBinaryAsync(OutboxFrame.Build(MoShiroChannels.RawChunk, id, bytes));
return await ack.Task.WaitAsync(timeout ?? TimeSpan.FromSeconds(10));
}
@@ -182,24 +182,24 @@ public sealed class AgentSimulator : IAsyncDisposable
public Task SendEventAsync(string kind, string? detail = null) =>
SendAsync(RpcMessage.Notification(
MonitorMethods.Event,
MoShiroMethods.Event,
new AgentEventParams { Kind = kind, AtUtcMs = TimeProvider.System.GetUtcNow().ToUnixTimeMilliseconds(), Detail = detail },
MonitorJsonContext.Default.AgentEventParams));
MoShiroJsonContext.Default.AgentEventParams));
public Task SendHostAsync(HostSnapshot snapshot) =>
SendAsync(RpcMessage.Notification(MonitorMethods.Host, snapshot, MonitorJsonContext.Default.HostSnapshot));
SendAsync(RpcMessage.Notification(MoShiroMethods.Host, snapshot, MoShiroJsonContext.Default.HostSnapshot));
public Task SendTestStatusAsync(TestId test, string status, int? phase = null, string? errorCode = null) =>
SendAsync(RpcMessage.Notification(
MonitorMethods.TestStatus,
MoShiroMethods.TestStatus,
new TestStatusParams { TestId = test, Status = status, Phase = phase, ErrorCode = errorCode },
MonitorJsonContext.Default.TestStatusParams));
MoShiroJsonContext.Default.TestStatusParams));
/// <summary>像节点的更新器报告的那样,随心跳上报更新状态。</summary>
public Task SendUpdateStatusAsync(UpdateStatus status) =>
SendAsync(RpcMessage.Notification(
FleetMethods.Heartbeat,
new HeartbeatParams { Extra = JsonSerializer.SerializeToElement(new AgentHeartbeatExtra { Update = status }, MonitorJsonContext.Default.AgentHeartbeatExtra) },
new HeartbeatParams { Extra = JsonSerializer.SerializeToElement(new AgentHeartbeatExtra { Update = status }, MoShiroJsonContext.Default.AgentHeartbeatExtra) },
FleetRpcContext.Default.HeartbeatParams));
public async Task<RpcMessage> RequestRawAsync(string method, JsonElement? parameters = null) => await RequestAsync(method, parameters);
@@ -383,7 +383,7 @@ public sealed class AgentSimulator : IAsyncDisposable
StateSyncParams sync = message.Params!.Value.Deserialize(FleetRpcContext.Default.StateSyncParams)!;
if (sync.Type == TaskSet.StateType)
{
TaskSets.Enqueue(sync.Doc.Deserialize(MonitorJsonContext.Default.TaskSet)!);
TaskSets.Enqueue(sync.Doc.Deserialize(MoShiroJsonContext.Default.TaskSet)!);
}
var result = new StateSyncResult { AppliedVersion = sync.Version, Rejected = [.. RejectNextState] };
@@ -403,19 +403,19 @@ public sealed class AgentSimulator : IAsyncDisposable
case FleetMethods.Revoked:
Revoked = true;
break;
case MonitorMethods.UpdateOffer:
UpdateOffers.Enqueue(message.Params!.Value.Deserialize(MonitorJsonContext.Default.UpdateOfferParams)!);
case MoShiroMethods.UpdateOffer:
UpdateOffers.Enqueue(message.Params!.Value.Deserialize(MoShiroJsonContext.Default.UpdateOfferParams)!);
break;
case MonitorMethods.UpdateApply:
case MoShiroMethods.UpdateApply:
Interlocked.Increment(ref _updateApplies);
break;
case MonitorMethods.TestRun:
TestRunParams run = message.Params!.Value.Deserialize(MonitorJsonContext.Default.TestRunParams)!;
case MoShiroMethods.TestRun:
TestRunParams run = message.Params!.Value.Deserialize(MoShiroJsonContext.Default.TestRunParams)!;
TestRuns.Enqueue(run);
(bool accepted, string? errorCode) = TestRunHandler(run);
if (accepted)
{
await SendAsync(RpcMessage.Success(message.Id!.Value, new TestRunResult { Accepted = true }, MonitorJsonContext.Default.TestRunResult));
await SendAsync(RpcMessage.Success(message.Id!.Value, new TestRunResult { Accepted = true }, MoShiroJsonContext.Default.TestRunResult));
}
else
{
@@ -423,10 +423,10 @@ public sealed class AgentSimulator : IAsyncDisposable
}
break;
case MonitorMethods.TestCancel:
TestCancelParams cancel = message.Params!.Value.Deserialize(MonitorJsonContext.Default.TestCancelParams)!;
case MoShiroMethods.TestCancel:
TestCancelParams cancel = message.Params!.Value.Deserialize(MoShiroJsonContext.Default.TestCancelParams)!;
TestCancels.Enqueue(cancel.TestId);
await SendAsync(RpcMessage.Success(message.Id!.Value, new TestRunResult { Accepted = true }, MonitorJsonContext.Default.TestRunResult));
await SendAsync(RpcMessage.Success(message.Id!.Value, new TestRunResult { Accepted = true }, MoShiroJsonContext.Default.TestRunResult));
break;
}
}
@@ -3,7 +3,7 @@ using System.Text.Json;
using Fleet.Core;
using Fleet.Rpc;
namespace Monitor.TestSupport;
namespace MoShiro.TestSupport;
public sealed record AuthAttempt(bool Ok, string? Status, string? ErrorCode, string? CloseReason);
@@ -1,8 +1,8 @@
using System.Text.Json;
using Fleet.Core;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.TestSupport;
namespace MoShiro.TestSupport;
/// <summary>每一个回显包的设定:上行或下行丢失,以及两个方向的单向时延(微秒)。</summary>
public readonly record struct EchoPacket(bool LostUp, bool LostDown, long OwdUpUs, long OwdDownUs, long ProcessUs = 50);
@@ -3,7 +3,7 @@
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\shared\Monitor.Protocol\Monitor.Protocol.csproj" />
<ProjectReference Include="..\..\shared\MoShiro.Protocol\MoShiro.Protocol.csproj" />
<ProjectReference Include="..\..\fleet\src\Fleet.Identity\Fleet.Identity.csproj" />
<ProjectReference Include="..\..\fleet\src\Fleet.Link\Fleet.Link.csproj" />
</ItemGroup>
@@ -8,9 +8,9 @@ using Fleet.Core;
using Fleet.Identity;
using Fleet.Link;
using Fleet.Rpc;
using Monitor.Protocol;
using MoShiro.Protocol;
namespace Monitor.TestSupport;
namespace MoShiro.TestSupport;
/// <summary>
/// 假 Probe:通过真实的点对点握手(TLS 固定证书 + 签名 + ECDH)连接真实的 Agent,接收 <c>line_task_set</c>,
@@ -104,7 +104,7 @@ public sealed class ProbeSimulator : IAsyncDisposable
for (int i = 0; i < rounds; i++)
{
long t0 = NowUs();
Result<ClockResult> reply = await _rpc!.RequestAsync(MonitorMethods.Clock, new ClockParams { ProbeMonoUs = t0 }, MonitorJsonContext.Default.ClockParams, MonitorJsonContext.Default.ClockResult, CancellationToken.None);
Result<ClockResult> reply = await _rpc!.RequestAsync(MoShiroMethods.Clock, new ClockParams { ProbeMonoUs = t0 }, MoShiroJsonContext.Default.ClockParams, MoShiroJsonContext.Default.ClockResult, CancellationToken.None);
long t1 = NowUs();
long roundTrip = t1 - t0;
if (reply.IsSuccess && roundTrip < bestRoundTrip)
@@ -214,7 +214,7 @@ public sealed class ProbeSimulator : IAsyncDisposable
Guid id = fragment.Header.ChunkId.Value;
var ack = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
_acks[id] = ack;
await _rpc!.SendBinaryAsync(OutboxFrame.Build(MonitorChannels.ColumnBlock, id, RawChunkCodec.Encode(fragment)), ct);
await _rpc!.SendBinaryAsync(OutboxFrame.Build(MoShiroChannels.ColumnBlock, id, RawChunkCodec.Encode(fragment)), ct);
if (waitForAck)
{
await ack.Task.WaitAsync(TimeSpan.FromSeconds(10), ct);
@@ -322,24 +322,24 @@ public sealed class ProbeSimulator : IAsyncDisposable
{
if (sync.Type == LineTaskSet.StateType)
{
probe._taskSets.Enqueue(sync.Doc.Deserialize(MonitorJsonContext.Default.LineTaskSet)!);
probe._taskSets.Enqueue(sync.Doc.Deserialize(MoShiroJsonContext.Default.LineTaskSet)!);
}
return ValueTask.FromResult(Result<StateSyncResult>.Ok(new StateSyncResult { AppliedVersion = sync.Version }));
})
.On(
MonitorMethods.TestPhase,
MonitorJsonContext.Default.TestPhaseStartParams,
MonitorJsonContext.Default.TestPhaseStartResult,
MoShiroMethods.TestPhase,
MoShiroJsonContext.Default.TestPhaseStartParams,
MoShiroJsonContext.Default.TestPhaseStartResult,
async (probe, start, ct) => Result<TestPhaseStartResult>.Ok(new TestPhaseStartResult { Accepted = await probe.StartPhaseAsync(start, ct) }))
.On(
MonitorMethods.TestPhaseEnd,
MonitorJsonContext.Default.TestPhaseEndParams,
MonitorJsonContext.Default.TestPhaseEndResult,
MoShiroMethods.TestPhaseEnd,
MoShiroJsonContext.Default.TestPhaseEndParams,
MoShiroJsonContext.Default.TestPhaseEndResult,
async (probe, end, ct) => Result<TestPhaseEndResult>.Ok(new TestPhaseEndResult { Fragments = await probe.EndPhaseAsync(end, ct) }))
.OnNotify(
MonitorMethods.TestAbort,
MonitorJsonContext.Default.TestAbortParams,
MoShiroMethods.TestAbort,
MoShiroJsonContext.Default.TestAbortParams,
(probe, abort, _) =>
{
probe.Aborts.Enqueue(abort.TestId);