Initial commit
This commit is contained in:
@@ -0,0 +1,254 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RobotNet10.NavigationTune.Data;
|
||||
using RobotNet10.NavigationTune.Hubs;
|
||||
using RobotNet10.NavigationTune.Services;
|
||||
using RobotNet10.NavigationTune.Shared.Interfaces;
|
||||
using RobotNet10.NavigationTune.Shared.Models;
|
||||
|
||||
namespace RobotNet10.NavigationTune.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// REST API controller for test execution and control
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class TuningController(
|
||||
ITuningOrchestrator orchestrator,
|
||||
IParameterManager parameterManager,
|
||||
IScenarioRepository scenarioRepository,
|
||||
ILogger<TuningController> logger) : ControllerBase
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Execute a single test
|
||||
/// </summary>
|
||||
[HttpPost("execute")]
|
||||
public async Task<ActionResult<TestExecutionResult>> ExecuteTest(
|
||||
[FromBody] ExecuteTestRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load scenario and parameter set
|
||||
var scenario = await scenarioRepository.GetByIdAsync(request.ScenarioId);
|
||||
if (scenario == null)
|
||||
return NotFound(new { error = $"Scenario with ID {request.ScenarioId} not found" });
|
||||
|
||||
var parameterSet = await parameterManager.GetByIdAsync(request.ParameterSetId);
|
||||
if (parameterSet == null)
|
||||
return NotFound(new { error = $"Parameter set with ID {request.ParameterSetId} not found" });
|
||||
|
||||
// Get connection ID from request headers or query
|
||||
var connectionId = Request.Headers["X-Connection-Id"].FirstOrDefault()
|
||||
?? Request.Query["connectionId"].FirstOrDefault();
|
||||
|
||||
// Start test and return immediately so UI can enable Stop/Pause; completion is sent via SignalR (ReceiveTestResult)
|
||||
var result = await orchestrator.StartTestAsync(
|
||||
scenario,
|
||||
parameterSet,
|
||||
connectionId,
|
||||
request.TestRunId,
|
||||
cancellationToken);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
logger.LogWarning(ex, "Invalid operation executing test");
|
||||
return BadRequest(new { error = ex.Message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error executing test");
|
||||
return StatusCode(500, new { error = "Failed to execute test", details = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run batch tests with multiple scenarios
|
||||
/// </summary>
|
||||
[HttpPost("batch")]
|
||||
public async Task<ActionResult<BatchTestResult>> RunBatchTests(
|
||||
[FromBody] BatchTestRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load parameter set
|
||||
var parameterSet = await parameterManager.GetByIdAsync(request.ParameterSetId);
|
||||
if (parameterSet == null)
|
||||
return NotFound(new { error = $"Parameter set with ID {request.ParameterSetId} not found" });
|
||||
|
||||
// Load scenarios
|
||||
var scenarios = new List<TestScenario>();
|
||||
foreach (var scenarioId in request.ScenarioIds)
|
||||
{
|
||||
var scenario = await scenarioRepository.GetByIdAsync(scenarioId);
|
||||
if (scenario == null)
|
||||
{
|
||||
logger.LogWarning("Scenario {ScenarioId} not found, skipping", scenarioId);
|
||||
continue;
|
||||
}
|
||||
scenarios.Add(scenario);
|
||||
}
|
||||
|
||||
if (scenarios.Count == 0)
|
||||
return BadRequest(new { error = "No valid scenarios found" });
|
||||
|
||||
// Execute batch
|
||||
var result = await orchestrator.RunBatchTestsAsync(scenarios, parameterSet, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error running batch tests");
|
||||
return StatusCode(500, new { error = "Failed to run batch tests", details = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compare multiple parameter configurations
|
||||
/// </summary>
|
||||
[HttpPost("compare")]
|
||||
public async Task<ActionResult<ComparisonResult>> CompareConfigurations(
|
||||
[FromBody] CompareConfigurationsRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load scenario
|
||||
var scenario = await scenarioRepository.GetByIdAsync(request.ScenarioId);
|
||||
if (scenario == null)
|
||||
return NotFound(new { error = $"Scenario with ID {request.ScenarioId} not found" });
|
||||
|
||||
// Load parameter sets
|
||||
var parameterSets = new List<NavigationParameterSet>();
|
||||
foreach (var parameterSetId in request.ParameterSetIds)
|
||||
{
|
||||
var parameterSet = await parameterManager.GetByIdAsync(parameterSetId);
|
||||
if (parameterSet == null)
|
||||
{
|
||||
logger.LogWarning("Parameter set {ParameterSetId} not found, skipping", parameterSetId);
|
||||
continue;
|
||||
}
|
||||
parameterSets.Add(parameterSet);
|
||||
}
|
||||
|
||||
if (parameterSets.Count == 0)
|
||||
return BadRequest(new { error = "No valid parameter sets found" });
|
||||
|
||||
// Compare
|
||||
var result = await orchestrator.CompareConfigurationsAsync(parameterSets, scenario, cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error comparing configurations");
|
||||
return StatusCode(500, new { error = "Failed to compare configurations", details = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pause running test
|
||||
/// </summary>
|
||||
[HttpPost("pause/{testRunId}")]
|
||||
public ActionResult PauseTest(string testRunId)
|
||||
{
|
||||
try
|
||||
{
|
||||
orchestrator.PauseTest(testRunId);
|
||||
return Ok(new { message = "Test paused" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error pausing test {TestRunId}", testRunId);
|
||||
return StatusCode(500, new { error = "Failed to pause test" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resume paused test
|
||||
/// </summary>
|
||||
[HttpPost("resume/{testRunId}")]
|
||||
public ActionResult ResumeTest(string testRunId)
|
||||
{
|
||||
try
|
||||
{
|
||||
orchestrator.ResumeTest(testRunId);
|
||||
return Ok(new { message = "Test resumed" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error resuming test {TestRunId}", testRunId);
|
||||
return StatusCode(500, new { error = "Failed to resume test" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stop running test
|
||||
/// </summary>
|
||||
[HttpPost("stop/{testRunId}")]
|
||||
public ActionResult StopTest(string testRunId)
|
||||
{
|
||||
try
|
||||
{
|
||||
orchestrator.StopTest(testRunId);
|
||||
return Ok(new { message = "Test stopped" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error stopping test {TestRunId}", testRunId);
|
||||
return StatusCode(500, new { error = "Failed to stop test" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Emergency stop
|
||||
/// </summary>
|
||||
[HttpPost("emergency-stop/{testRunId}")]
|
||||
public ActionResult EmergencyStop(string testRunId)
|
||||
{
|
||||
try
|
||||
{
|
||||
orchestrator.EmergencyStop(testRunId);
|
||||
return Ok(new { message = "Emergency stop activated" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error emergency stopping test {TestRunId}", testRunId);
|
||||
return StatusCode(500, new { error = "Failed to emergency stop test" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request model for executing a single test.
|
||||
/// When TestRunId is provided, client should call JoinTestSession(TestRunId) before execute so real-time telemetry is received.
|
||||
/// </summary>
|
||||
public class ExecuteTestRequest
|
||||
{
|
||||
public Guid ScenarioId { get; set; }
|
||||
public Guid ParameterSetId { get; set; }
|
||||
/// <summary>Optional. If set, this Id is used for the test run and for SignalR group test_{TestRunId}.</summary>
|
||||
public Guid? TestRunId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request model for batch test execution
|
||||
/// </summary>
|
||||
public class BatchTestRequest
|
||||
{
|
||||
public List<Guid> ScenarioIds { get; set; } = new();
|
||||
public Guid ParameterSetId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request model for configuration comparison
|
||||
/// </summary>
|
||||
public class CompareConfigurationsRequest
|
||||
{
|
||||
public Guid ScenarioId { get; set; }
|
||||
public List<Guid> ParameterSetIds { get; set; } = new();
|
||||
}
|
||||
Reference in New Issue
Block a user