consolidation of some of the modules, localization fixes, product advisories work, qa work
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using StellaOps.Auth.ServerIntegration.Tenancy;
|
||||
using StellaOps.JobEngine.Core.Scheduling;
|
||||
using StellaOps.JobEngine.Infrastructure.Repositories;
|
||||
using StellaOps.JobEngine.WebService.Contracts;
|
||||
using StellaOps.JobEngine.WebService.Services;
|
||||
using static StellaOps.Localization.T;
|
||||
|
||||
namespace StellaOps.JobEngine.WebService.Endpoints;
|
||||
|
||||
/// <summary>
|
||||
/// REST API endpoints for job DAG (dependency graph).
|
||||
/// </summary>
|
||||
public static class DagEndpoints
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps DAG endpoints to the route builder.
|
||||
/// </summary>
|
||||
public static RouteGroupBuilder MapDagEndpoints(this IEndpointRouteBuilder app)
|
||||
{
|
||||
var group = app.MapGroup("/api/v1/jobengine/dag")
|
||||
.WithTags("Orchestrator DAG")
|
||||
.RequireAuthorization(JobEnginePolicies.Read)
|
||||
.RequireTenant();
|
||||
|
||||
group.MapGet("run/{runId:guid}", GetRunDag)
|
||||
.WithName("Orchestrator_GetRunDag")
|
||||
.WithDescription(_t("orchestrator.dag.get_run_description"));
|
||||
|
||||
group.MapGet("run/{runId:guid}/edges", GetRunEdges)
|
||||
.WithName("Orchestrator_GetRunEdges")
|
||||
.WithDescription(_t("orchestrator.dag.get_run_edges_description"));
|
||||
|
||||
group.MapGet("run/{runId:guid}/ready-jobs", GetReadyJobs)
|
||||
.WithName("Orchestrator_GetReadyJobs")
|
||||
.WithDescription(_t("orchestrator.dag.get_ready_jobs_description"));
|
||||
|
||||
group.MapGet("run/{runId:guid}/blocked/{jobId:guid}", GetBlockedJobs)
|
||||
.WithName("Orchestrator_GetBlockedJobs")
|
||||
.WithDescription(_t("orchestrator.dag.get_blocked_jobs_description"));
|
||||
|
||||
group.MapGet("job/{jobId:guid}/parents", GetJobParents)
|
||||
.WithName("Orchestrator_GetJobParents")
|
||||
.WithDescription(_t("orchestrator.dag.get_job_parents_description"));
|
||||
|
||||
group.MapGet("job/{jobId:guid}/children", GetJobChildren)
|
||||
.WithName("Orchestrator_GetJobChildren")
|
||||
.WithDescription(_t("orchestrator.dag.get_job_children_description"));
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetRunDag(
|
||||
HttpContext context,
|
||||
[FromRoute] Guid runId,
|
||||
[FromServices] TenantResolver tenantResolver,
|
||||
[FromServices] IRunRepository runRepository,
|
||||
[FromServices] IJobRepository jobRepository,
|
||||
[FromServices] IDagEdgeRepository dagEdgeRepository,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tenantId = tenantResolver.Resolve(context);
|
||||
|
||||
// Verify run exists
|
||||
var run = await runRepository.GetByIdAsync(tenantId, runId, cancellationToken).ConfigureAwait(false);
|
||||
if (run is null)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
// Get all edges
|
||||
var edges = await dagEdgeRepository.GetByRunIdAsync(tenantId, runId, cancellationToken).ConfigureAwait(false);
|
||||
var edgeResponses = edges.Select(DagEdgeResponse.FromDomain).ToList();
|
||||
|
||||
// Get all jobs for topological sort and critical path
|
||||
var jobs = await jobRepository.GetByRunIdAsync(tenantId, runId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Compute topological order
|
||||
IReadOnlyList<Guid> topologicalOrder;
|
||||
try
|
||||
{
|
||||
topologicalOrder = DagPlanner.TopologicalSort(jobs.Select(j => j.JobId), edges);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Cycle detected - return empty order
|
||||
topologicalOrder = [];
|
||||
}
|
||||
|
||||
// Compute critical path (using a fixed estimate for simplicity)
|
||||
var criticalPath = DagPlanner.CalculateCriticalPath(jobs, edges, _ => TimeSpan.FromMinutes(5));
|
||||
|
||||
return Results.Ok(new DagResponse(
|
||||
runId,
|
||||
edgeResponses,
|
||||
topologicalOrder,
|
||||
criticalPath.CriticalPathJobIds,
|
||||
criticalPath.TotalDuration));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetRunEdges(
|
||||
HttpContext context,
|
||||
[FromRoute] Guid runId,
|
||||
[FromServices] TenantResolver tenantResolver,
|
||||
[FromServices] IRunRepository runRepository,
|
||||
[FromServices] IDagEdgeRepository dagEdgeRepository,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tenantId = tenantResolver.Resolve(context);
|
||||
|
||||
// Verify run exists
|
||||
var run = await runRepository.GetByIdAsync(tenantId, runId, cancellationToken).ConfigureAwait(false);
|
||||
if (run is null)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
var edges = await dagEdgeRepository.GetByRunIdAsync(tenantId, runId, cancellationToken).ConfigureAwait(false);
|
||||
var responses = edges.Select(DagEdgeResponse.FromDomain).ToList();
|
||||
|
||||
return Results.Ok(new DagEdgeListResponse(responses));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetReadyJobs(
|
||||
HttpContext context,
|
||||
[FromRoute] Guid runId,
|
||||
[FromServices] TenantResolver tenantResolver,
|
||||
[FromServices] IRunRepository runRepository,
|
||||
[FromServices] IJobRepository jobRepository,
|
||||
[FromServices] IDagEdgeRepository dagEdgeRepository,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tenantId = tenantResolver.Resolve(context);
|
||||
|
||||
// Verify run exists
|
||||
var run = await runRepository.GetByIdAsync(tenantId, runId, cancellationToken).ConfigureAwait(false);
|
||||
if (run is null)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
var jobs = await jobRepository.GetByRunIdAsync(tenantId, runId, cancellationToken).ConfigureAwait(false);
|
||||
var edges = await dagEdgeRepository.GetByRunIdAsync(tenantId, runId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var readyJobs = DagPlanner.GetReadyJobs(jobs, edges);
|
||||
var responses = readyJobs.Select(JobResponse.FromDomain).ToList();
|
||||
|
||||
return Results.Ok(new JobListResponse(responses, null));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetBlockedJobs(
|
||||
HttpContext context,
|
||||
[FromRoute] Guid runId,
|
||||
[FromRoute] Guid jobId,
|
||||
[FromServices] TenantResolver tenantResolver,
|
||||
[FromServices] IRunRepository runRepository,
|
||||
[FromServices] IDagEdgeRepository dagEdgeRepository,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tenantId = tenantResolver.Resolve(context);
|
||||
|
||||
// Verify run exists
|
||||
var run = await runRepository.GetByIdAsync(tenantId, runId, cancellationToken).ConfigureAwait(false);
|
||||
if (run is null)
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
var edges = await dagEdgeRepository.GetByRunIdAsync(tenantId, runId, cancellationToken).ConfigureAwait(false);
|
||||
var blockedJobs = DagPlanner.GetBlockedJobs(jobId, edges);
|
||||
|
||||
return Results.Ok(new BlockedJobsResponse(jobId, blockedJobs.ToList()));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetJobParents(
|
||||
HttpContext context,
|
||||
[FromRoute] Guid jobId,
|
||||
[FromServices] TenantResolver tenantResolver,
|
||||
[FromServices] IDagEdgeRepository dagEdgeRepository,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tenantId = tenantResolver.Resolve(context);
|
||||
|
||||
var edges = await dagEdgeRepository.GetParentEdgesAsync(tenantId, jobId, cancellationToken).ConfigureAwait(false);
|
||||
var responses = edges.Select(DagEdgeResponse.FromDomain).ToList();
|
||||
|
||||
return Results.Ok(new DagEdgeListResponse(responses));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> GetJobChildren(
|
||||
HttpContext context,
|
||||
[FromRoute] Guid jobId,
|
||||
[FromServices] TenantResolver tenantResolver,
|
||||
[FromServices] IDagEdgeRepository dagEdgeRepository,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tenantId = tenantResolver.Resolve(context);
|
||||
|
||||
var edges = await dagEdgeRepository.GetChildEdgesAsync(tenantId, jobId, cancellationToken).ConfigureAwait(false);
|
||||
var responses = edges.Select(DagEdgeResponse.FromDomain).ToList();
|
||||
|
||||
return Results.Ok(new DagEdgeListResponse(responses));
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
return Results.BadRequest(new { error = ex.Message });
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user