152 lines
7.2 KiB
C#
152 lines
7.2 KiB
C#
using Fleet.Core;
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Routing;
|
|
using MoShiro.Backend.Application;
|
|
using MoShiro.Protocol;
|
|
|
|
namespace MoShiro.Backend.Api;
|
|
|
|
/// <summary>任务(线路模板、单个 Agent 的覆盖、目标任务)与按需测试。参数按 Kind 的 ParamSpec 校验,并检查带宽预算。</summary>
|
|
public static class TaskEndpoints
|
|
{
|
|
private const string TasksTag = "admin-tasks";
|
|
private const string TestsTag = "admin-tests";
|
|
|
|
public static void Map(RouteGroupBuilder group)
|
|
{
|
|
group.MapGet("/tasks", ListAsync)
|
|
.Api("tasks_list", "Tasks: line templates, per-agent overrides and target tasks", TasksTag).Returns<PageDto<TaskDto>>().ProducesProblems(401, 403);
|
|
group.MapPost("/tasks", CreateAsync)
|
|
.Api("tasks_create", "Create a task; params are validated against the kind", TasksTag)
|
|
.Returns<TaskDto>(StatusCodes.Status201Created).ProducesProblems(400, 401, 403, 404);
|
|
group.MapGet("/tasks/{id}", GetAsync)
|
|
.Api("tasks_get", "One task", TasksTag).Returns<TaskDto>().ProducesProblems(401, 403, 404);
|
|
group.MapPatch("/tasks/{id}", PatchAsync)
|
|
.Accepts<PatchTaskRequest>("application/merge-patch+json", "application/json")
|
|
.Api("tasks_update", "Change params, schedule, target or enabled (merge-patch, If-Match supported)", TasksTag)
|
|
.Returns<TaskDto>().ProducesProblems(400, 401, 403, 404, 412);
|
|
group.MapDelete("/tasks/{id}", DeleteAsync)
|
|
.Api("tasks_delete", "Delete a task", TasksTag).Returns<object>(StatusCodes.Status204NoContent).ProducesProblems(401, 403);
|
|
|
|
group.MapPost("/lines/{line_id}/tests", StartTestAsync)
|
|
.Api("tests_start", "Start an on-demand test on a line", TestsTag)
|
|
.Returns<TestDto>(StatusCodes.Status202Accepted).ProducesProblems(400, 401, 403, 404, 409);
|
|
group.MapGet("/lines/{line_id}/tests", ListTestsAsync)
|
|
.Api("tests_list", "Recent tests of a line", TestsTag).Returns<PageDto<TestDto>>().ProducesProblems(401, 403);
|
|
group.MapPost("/tests/{id}/cancel", CancelAsync)
|
|
.Api("tests_cancel", "Cancel a running test", TestsTag).Returns<TestDto>().ProducesProblems(401, 403, 404);
|
|
}
|
|
|
|
private static async Task<IResult> ListAsync(TaskService tasks, string? agent_id, string? scope, CancellationToken ct)
|
|
{
|
|
IEnumerable<TaskRecord> all = await tasks.ListAsync(ct);
|
|
if (agent_id is not null)
|
|
{
|
|
all = all.Where(t => t.AgentId?.Value == agent_id);
|
|
}
|
|
|
|
if (scope is not null)
|
|
{
|
|
all = all.Where(t => t.Scope == scope);
|
|
}
|
|
|
|
return TypedResults.Json(new PageDto<TaskDto>([.. all.Select(Mappers.ToDto)], null), ApiJsonContext.Default.PageDtoTaskDto);
|
|
}
|
|
|
|
private static async Task<IResult> CreateAsync(HttpContext http, TaskService tasks, CreateTaskRequest request, CancellationToken ct)
|
|
{
|
|
Result<TaskRecord> result = await tasks.CreateAsync(
|
|
new TaskInput(
|
|
request.Scope ?? "", request.Kind ?? "", request.Target, request.Params, request.Schedule, request.Enabled,
|
|
request.AgentId is null ? null : new NodeId(request.AgentId), request.BaseTaskId is null ? null : new TaskId(request.BaseTaskId)),
|
|
ct);
|
|
if (!result.TryGet(out TaskRecord? task, out FleetError? error))
|
|
{
|
|
return Problems.From(http, error);
|
|
}
|
|
|
|
http.Response.Headers.Location = "/api/admin/tasks/" + task.Id.Value;
|
|
Concurrency.SetETag(http, task.RowVersion);
|
|
return TypedResults.Json(Mappers.ToDto(task), ApiJsonContext.Default.TaskDto, statusCode: StatusCodes.Status201Created);
|
|
}
|
|
|
|
private static async Task<IResult> GetAsync(HttpContext http, TaskService tasks, string id, CancellationToken ct)
|
|
{
|
|
TaskRecord? task = await tasks.GetAsync(new TaskId(id), ct);
|
|
if (task is null)
|
|
{
|
|
return Problems.Create(http, MoShiroErrors.TaskNotFound, "task not found");
|
|
}
|
|
|
|
Concurrency.SetETag(http, task.RowVersion);
|
|
return TypedResults.Json(Mappers.ToDto(task), ApiJsonContext.Default.TaskDto);
|
|
}
|
|
|
|
private static async Task<IResult> PatchAsync(HttpContext http, TaskService tasks, PatchTaskRequest request, string id, CancellationToken ct)
|
|
{
|
|
Result<TaskRecord> result = await tasks.UpdateAsync(new TaskId(id), new TaskPatch(request.Params, request.Schedule, request.Enabled, request.Target), Concurrency.ExpectedRowVersion(http), ct);
|
|
if (!result.TryGet(out TaskRecord? task, out FleetError? error))
|
|
{
|
|
return Problems.From(http, error);
|
|
}
|
|
|
|
Concurrency.SetETag(http, task.RowVersion);
|
|
return TypedResults.Json(Mappers.ToDto(task), ApiJsonContext.Default.TaskDto);
|
|
}
|
|
|
|
private static async Task<IResult> DeleteAsync(TaskService tasks, string id, CancellationToken ct)
|
|
{
|
|
await tasks.DeleteAsync(new TaskId(id), ct);
|
|
return TypedResults.NoContent();
|
|
}
|
|
|
|
private static async Task<IResult> StartTestAsync(HttpContext http, TestService tests, LineService lines, StartTestRequestDto request, string line_id, CancellationToken ct)
|
|
{
|
|
Result<LineView> found = await lines.ResolveAsync(line_id, ct);
|
|
if (!found.TryGet(out LineView? line, out FleetError? lineError))
|
|
{
|
|
return Problems.From(http, lineError);
|
|
}
|
|
|
|
Result<TestRecord> result = await tests.StartAsync(line.Key.Agent, line.Key.Probe, new StartTestRequest(request.Plan ?? "", request.Params), "api", line.IpVersion, ct);
|
|
if (!result.TryGet(out TestRecord? test, out FleetError? error))
|
|
{
|
|
return Problems.From(http, error);
|
|
}
|
|
|
|
http.Response.Headers.Location = "/api/public/tests/" + test.Id.Value;
|
|
return TypedResults.Json(Mappers.ToDto(test), ApiJsonContext.Default.TestDto, statusCode: StatusCodes.Status202Accepted);
|
|
}
|
|
|
|
private static async Task<IResult> ListTestsAsync(HttpContext http, TestService tests, LineService lines, string line_id, int? limit, CancellationToken ct)
|
|
{
|
|
// 只给 Agent id(并且有多个 Probe)时列出这个 Agent 上所有 Probe 的测试
|
|
NodeId agent;
|
|
ProbeId? probe = null;
|
|
string? ipVersion = null;
|
|
if (LineKey.TryParse(line_id, out LineKey key))
|
|
{
|
|
agent = key.Agent;
|
|
probe = key.Probe;
|
|
ipVersion = key.IpVersion;
|
|
}
|
|
else
|
|
{
|
|
agent = new NodeId(line_id);
|
|
}
|
|
|
|
return TypedResults.Json(
|
|
new PageDto<TestDto>([.. (await tests.ListAsync(agent, probe, Math.Clamp(limit ?? 50, 1, 500), ipVersion, ct)).Select(Mappers.ToDto)], null),
|
|
ApiJsonContext.Default.PageDtoTestDto);
|
|
}
|
|
|
|
private static async Task<IResult> CancelAsync(HttpContext http, TestService tests, string id, CancellationToken ct)
|
|
{
|
|
Result<TestRecord> result = await tests.CancelAsync(new TestId(id), ct);
|
|
return result.TryGet(out TestRecord? test, out FleetError? error)
|
|
? TypedResults.Json(Mappers.ToDto(test), ApiJsonContext.Default.TestDto)
|
|
: Problems.From(http, error);
|
|
}
|
|
}
|