fix: correct OpenWrt API metadata and earliest sample queries [CI]
This commit is contained in:
1 parent
cd193378a6
commit
0066a947da
6 files changed
+85
-11
No files matched your search
@@ -5023,8 +5023,10 @@
|
||||
"/install/openwrt.sh": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"moshiro-backend"
|
||||
"ops"
|
||||
],
|
||||
"summary": "The static native OpenWrt Probe installer and updater",
|
||||
"operationId": "install_openwrt",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
@@ -10146,9 +10148,6 @@
|
||||
{
|
||||
"name": "ops"
|
||||
},
|
||||
{
|
||||
"name": "moshiro-backend"
|
||||
},
|
||||
{
|
||||
"name": "release"
|
||||
},
|
||||
|
||||
@@ -7,6 +7,7 @@ public sealed class PlatformDetectorTests
|
||||
[InlineData("NAME=\"OpenWrt\"|VERSION_ID=\"24.10.0\"|ID=openwrt", "OpenWrt 24.10")]
|
||||
[InlineData("NAME=\"Alpine Linux\"|ID=alpine|VERSION_ID=3.22.1", "Alpine Linux 3.22")]
|
||||
[InlineData("NAME=\"Ubuntu\"|VERSION_ID=\"24.04\"", "Ubuntu 24.04")]
|
||||
[InlineData("NAME=\"Fedora Linux\"|VERSION_ID=44|HOSTNAME=private-router|KERNEL_VERSION=6.18.1|PRETTY_NAME=\"Fedora Linux 44 (Workstation Edition)\"", "Fedora Linux 44")]
|
||||
[InlineData("ID=arch", "arch")]
|
||||
[InlineData("", "Linux")]
|
||||
public void LinuxName_IsTheDistroAndItsMajorMinorVersion(string osRelease, string expected)
|
||||
@@ -22,6 +23,7 @@ public sealed class PlatformDetectorTests
|
||||
Assert.Contains(platform.Arch, new[] { "x64", "arm64", "arm", "x86" });
|
||||
// 没有主机名、内核版本:名字很短,也不含小数点以外的版本细节
|
||||
Assert.True((platform.Name ?? string.Empty).Length < 40);
|
||||
Assert.DoesNotContain(Environment.MachineName, platform.Name ?? string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
// 主机名可能恰好就是发行版名(例如 fedora),不能用字符串重合判断泄漏。
|
||||
// 发行版字段的白名单和版本截断由上面的固定输入覆盖。
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,9 @@ public static class OpsEndpoints
|
||||
.Produces(StatusCodes.Status200OK, contentType: "text/plain").ProducesProblems(401, 403);
|
||||
// 一行安装:面板给的命令先下载这份静态脚本、核对 SHA-256,再用环境变量里的面板地址、角色和一次性代码执行。
|
||||
// 脚本不含任何一次性的值,所以不需要代码,也可以直接拿去和发布页公布的哈希核对。
|
||||
app.MapGet("/install/openwrt.sh", (HttpContext http) => Static(http, "openwrt.sh", "text/x-shellscript"));
|
||||
app.MapGet("/install/openwrt.sh", (HttpContext http) => Static(http, "openwrt.sh", "text/x-shellscript"))
|
||||
.Api("install_openwrt", "The static native OpenWrt Probe installer and updater", Tag)
|
||||
.Produces(StatusCodes.Status200OK, contentType: "text/x-shellscript");
|
||||
|
||||
app.MapGet("/install", (HttpContext http) => Static(http, InstallScripts.Sh, "text/x-shellscript"))
|
||||
.Api("install_sh", "The static one-line installer for Linux and macOS nodes (same bytes for everyone; its SHA-256 is published with each release)", Tag)
|
||||
|
||||
@@ -79,16 +79,15 @@ public sealed class RowLoader
|
||||
|
||||
if (earliest is null || at < earliest.Value + width)
|
||||
{
|
||||
earliest = earliest is null ? at : Math.Min(earliest.Value, at);
|
||||
earliest = at;
|
||||
width = level.WidthMs();
|
||||
}
|
||||
|
||||
width = level.WidthMs();
|
||||
}
|
||||
|
||||
long? hot = _hot.GetRange(agent, series, nowMs - Steps.Hour, nowMs + 10_000).Select(r => (long?)r.Key).Min();
|
||||
if (hot is { } h && (earliest is null || h < earliest.Value + width))
|
||||
{
|
||||
earliest = earliest is null ? h : Math.Min(earliest.Value, h);
|
||||
earliest = h;
|
||||
}
|
||||
|
||||
return earliest;
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed partial class OpenApiConformanceTests : IClassFixture<BackendFixtu
|
||||
foreach ((string path, _) in doc["paths"]!.AsObject())
|
||||
{
|
||||
string[] prefixes = ["/api/public/", "/api/admin/", "/api/agent/v1/"];
|
||||
string[] roots = ["/healthz", "/metrics", "/install", "/install.ps1", "/install/manifest", "/install/manifest.sig", "/install/compose/{file}", "/install/quadlet/{file}", "/api/release/offer"];
|
||||
string[] roots = ["/healthz", "/metrics", "/install", "/install.ps1", "/install/openwrt.sh", "/install/manifest", "/install/manifest.sig", "/install/compose/{file}", "/install/quadlet/{file}", "/api/release/offer"];
|
||||
Assert.True(prefixes.Any(p => path.StartsWith(p, StringComparison.Ordinal)) || roots.Contains(path), $"{path} is outside the documented layers");
|
||||
foreach (string segment in path.Split('/', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
using Fleet.Core;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using MoShiro.Backend.Analysis;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Backend.Storage.Sqlite;
|
||||
using MoShiro.Protocol;
|
||||
using MoShiro.TestSupport;
|
||||
|
||||
namespace MoShiro.Backend.Tests;
|
||||
|
||||
public sealed class RowLoaderTests : IDisposable
|
||||
{
|
||||
private static readonly NodeId s_agent = new("agt_earliest");
|
||||
|
||||
private readonly string _directory = Path.Combine(Path.GetTempPath(), "moshiro-earliest-" + Guid.NewGuid().ToString("N"));
|
||||
private readonly SqliteDatabase _database;
|
||||
private readonly RawQueryGate _gate = new();
|
||||
|
||||
public RowLoaderTests()
|
||||
{
|
||||
_database = new SqliteDatabase(_directory);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0L, 1_800_000L, 1_920_000L, 1_930_000L, 1_931_000L, 1_931_000L)]
|
||||
[InlineData(0L, 7_200_000L, null, null, 1_800_000L, 1_800_000L)]
|
||||
[InlineData(0L, 7_200_000L, 1_800_000L, null, null, 1_800_000L)]
|
||||
[InlineData(0L, 3_600_000L, 3_660_000L, 3_670_000L, null, 0L)]
|
||||
[InlineData(null, null, null, null, null, null)]
|
||||
[InlineData(null, null, null, null, 1_850_000L, 1_850_000L)]
|
||||
[InlineData(null, null, 1_800_000L, null, 1_801_000L, 1_801_000L)]
|
||||
[InlineData(null, null, 1_800_000L, null, 1_860_000L, 1_800_000L)]
|
||||
[InlineData(0L, null, 0L, null, null, 0L)]
|
||||
[InlineData(0L, null, 3_540_000L, null, null, 3_540_000L)]
|
||||
[InlineData(0L, null, 3_600_000L, null, null, 0L)]
|
||||
[InlineData(null, null, null, 1_930_000L, null, 1_930_000L)]
|
||||
public async Task EarliestSampleMs_RefinesWithinTheFirstBucket_WithoutDiscardingOlderHistory(
|
||||
long? hourStart, long? fiveMinuteStart, long? minuteStart, long? tenSecondStart, long? hotStart, long? expected)
|
||||
{
|
||||
var time = new FakeTimeProvider(DateTimeOffset.UnixEpoch.AddHours(1));
|
||||
var store = new SqliteMetricStore(_database);
|
||||
var hot = new HotLayer(time);
|
||||
const string Series = "line";
|
||||
foreach ((MetricLevel level, long? start) in new[]
|
||||
{
|
||||
(MetricLevel.H1, hourStart), (MetricLevel.M5, fiveMinuteStart),
|
||||
(MetricLevel.M1, minuteStart), (MetricLevel.S10, tenSecondStart),
|
||||
})
|
||||
{
|
||||
if (start is { } at)
|
||||
{
|
||||
var row = new MetricRow(s_agent, Series, at, false, AccumulatorCodec.Encode(new MetricSet()));
|
||||
await store.UpsertAsync(level, [row], default);
|
||||
}
|
||||
}
|
||||
|
||||
if (hotStart is { } sample)
|
||||
{
|
||||
hot.Merge(s_agent, Series, ChunkFactory.UdpEchoClean(s_agent, sample));
|
||||
}
|
||||
|
||||
var loader = new RowLoader(store, hot, new WindowComputer(new SqliteRawStore(_database)), _gate);
|
||||
Assert.Equal(expected, await loader.EarliestSampleMsAsync(s_agent, Series, time.GetUtcNow().ToUnixTimeMilliseconds(), default));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gate.Dispose();
|
||||
_database.Dispose();
|
||||
Directory.Delete(_directory, recursive: true);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user