776 lines
No EOL
29 KiB
Rust
776 lines
No EOL
29 KiB
Rust
use super::*;
|
|
use crate::event_log::{current_timestamp_nanos, create_build_event};
|
|
use axum::{
|
|
extract::{Path, State},
|
|
http::StatusCode,
|
|
};
|
|
use axum_jsonschema::Json;
|
|
use log::{error, info};
|
|
use serde::Deserialize;
|
|
use schemars::JsonSchema;
|
|
use std::process::Command;
|
|
use std::env;
|
|
|
|
pub async fn submit_build_request(
|
|
State(service): State<ServiceState>,
|
|
Json(request): Json<BuildRequest>,
|
|
) -> Result<Json<BuildRequestResponse>, (StatusCode, Json<ErrorResponse>)> {
|
|
let build_request_id = BuildGraphService::generate_build_request_id();
|
|
let timestamp = current_timestamp_nanos();
|
|
|
|
info!("Received build request {} for partitions: {:?}", build_request_id, request.partitions);
|
|
|
|
// Create build request state
|
|
let build_state = BuildRequestState {
|
|
build_request_id: build_request_id.clone(),
|
|
status: BuildRequestStatus::BuildRequestReceived,
|
|
requested_partitions: request.partitions.clone(),
|
|
created_at: timestamp,
|
|
updated_at: timestamp,
|
|
};
|
|
|
|
// Store in active builds
|
|
{
|
|
let mut active_builds = service.active_builds.write().await;
|
|
active_builds.insert(build_request_id.clone(), build_state);
|
|
}
|
|
|
|
// Log build request received event
|
|
let event = create_build_event(
|
|
build_request_id.clone(),
|
|
crate::build_event::EventType::BuildRequestEvent(BuildRequestEvent {
|
|
status: BuildRequestStatus::BuildRequestReceived as i32,
|
|
requested_partitions: request.partitions.iter()
|
|
.map(|p| PartitionRef { str: p.clone() })
|
|
.collect(),
|
|
message: "Build request received".to_string(),
|
|
}),
|
|
);
|
|
|
|
if let Err(e) = service.event_log.append_event(event).await {
|
|
error!("Failed to log build request received event: {}", e);
|
|
return Err((
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Json(ErrorResponse {
|
|
error: format!("Failed to log build request: {}", e),
|
|
}),
|
|
));
|
|
}
|
|
|
|
// Start build execution in background
|
|
let service_clone = service.clone();
|
|
let build_request_id_clone = build_request_id.clone();
|
|
let partitions_clone = request.partitions.clone();
|
|
|
|
tokio::spawn(async move {
|
|
if let Err(e) = execute_build_request(
|
|
service_clone,
|
|
build_request_id_clone,
|
|
partitions_clone,
|
|
).await {
|
|
error!("Build request execution failed: {}", e);
|
|
}
|
|
});
|
|
|
|
Ok(Json(BuildRequestResponse { build_request_id }))
|
|
}
|
|
|
|
#[derive(Deserialize, JsonSchema)]
|
|
pub struct BuildStatusRequest {
|
|
pub build_request_id: String,
|
|
}
|
|
|
|
pub async fn get_build_status(
|
|
State(service): State<ServiceState>,
|
|
Path(BuildStatusRequest { build_request_id }): Path<BuildStatusRequest>,
|
|
) -> Result<Json<BuildStatusResponse>, (StatusCode, Json<ErrorResponse>)> {
|
|
// Get events for this build request from the event log (source of truth)
|
|
let events = match service.event_log.get_build_request_events(&build_request_id, None).await {
|
|
Ok(events) => events,
|
|
Err(e) => {
|
|
error!("Failed to get build request events: {}", e);
|
|
return Err((
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Json(ErrorResponse {
|
|
error: format!("Failed to query build request events: {}", e),
|
|
}),
|
|
));
|
|
}
|
|
};
|
|
|
|
info!("Build request {}: Found {} events", build_request_id, events.len());
|
|
|
|
// Check if build request exists by looking for any events
|
|
if events.is_empty() {
|
|
return Err((
|
|
StatusCode::NOT_FOUND,
|
|
Json(ErrorResponse {
|
|
error: "Build request not found".to_string(),
|
|
}),
|
|
));
|
|
}
|
|
|
|
// Reconstruct build state from events - fail if no valid build request events found
|
|
let mut status: Option<BuildRequestStatus> = None;
|
|
let mut requested_partitions = Vec::new();
|
|
let mut created_at = 0i64;
|
|
let mut updated_at = 0i64;
|
|
let mut partitions_set = false;
|
|
|
|
// Sort events by timestamp to process in chronological order
|
|
let mut sorted_events = events.clone();
|
|
sorted_events.sort_by_key(|e| e.timestamp);
|
|
|
|
for event in &sorted_events {
|
|
if event.timestamp > updated_at {
|
|
updated_at = event.timestamp;
|
|
}
|
|
if created_at == 0 || event.timestamp < created_at {
|
|
created_at = event.timestamp;
|
|
}
|
|
|
|
// Extract information from build request events
|
|
if let Some(crate::build_event::EventType::BuildRequestEvent(req_event)) = &event.event_type {
|
|
info!("Processing BuildRequestEvent: status={}, message='{}'", req_event.status, req_event.message);
|
|
|
|
// Update status with the latest event - convert from i32 to enum
|
|
status = Some(match req_event.status {
|
|
0 => BuildRequestStatus::BuildRequestUnknown, // Default protobuf value - should not happen in production
|
|
1 => BuildRequestStatus::BuildRequestReceived,
|
|
2 => BuildRequestStatus::BuildRequestPlanning,
|
|
3 => BuildRequestStatus::BuildRequestExecuting,
|
|
4 => BuildRequestStatus::BuildRequestCompleted,
|
|
5 => BuildRequestStatus::BuildRequestFailed,
|
|
6 => BuildRequestStatus::BuildRequestCancelled,
|
|
unknown_status => {
|
|
error!("Invalid BuildRequestStatus value: {} in build request {}", unknown_status, build_request_id);
|
|
return Err((
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Json(ErrorResponse {
|
|
error: format!("Invalid build request status value: {}", unknown_status),
|
|
}),
|
|
));
|
|
},
|
|
});
|
|
|
|
// Use partitions from the first event that has them (typically the "received" event)
|
|
if !partitions_set && !req_event.requested_partitions.is_empty() {
|
|
info!("Setting requested partitions from event: {:?}", req_event.requested_partitions);
|
|
requested_partitions = req_event.requested_partitions.iter()
|
|
.map(|p| p.str.clone())
|
|
.collect();
|
|
partitions_set = true;
|
|
}
|
|
} else {
|
|
info!("Event is not a BuildRequestEvent: {:?}", event.event_type.as_ref().map(|t| std::mem::discriminant(t)));
|
|
}
|
|
}
|
|
|
|
// Ensure we found at least one valid BuildRequestEvent
|
|
let final_status = status.ok_or_else(|| {
|
|
error!("No valid BuildRequestEvent found in {} events for build request {}", events.len(), build_request_id);
|
|
(
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Json(ErrorResponse {
|
|
error: format!("No valid build request events found - data corruption detected"),
|
|
}),
|
|
)
|
|
})?;
|
|
|
|
// Convert events to summary format for response
|
|
let event_summaries: Vec<BuildEventSummary> = events.into_iter().map(|e| {
|
|
let (job_label, partition_ref, delegated_build_id) = extract_navigation_data(&e.event_type);
|
|
BuildEventSummary {
|
|
event_id: e.event_id,
|
|
timestamp: e.timestamp,
|
|
event_type: event_type_to_string(&e.event_type),
|
|
message: event_to_message(&e.event_type),
|
|
job_label,
|
|
partition_ref,
|
|
delegated_build_id,
|
|
}
|
|
}).collect();
|
|
|
|
let final_status_string = BuildGraphService::status_to_string(final_status);
|
|
info!("Build request {}: Final status={}, partitions={:?}", build_request_id, final_status_string, requested_partitions);
|
|
|
|
Ok(Json(BuildStatusResponse {
|
|
build_request_id,
|
|
status: final_status_string,
|
|
requested_partitions,
|
|
created_at,
|
|
updated_at,
|
|
events: event_summaries,
|
|
}))
|
|
}
|
|
|
|
#[derive(Deserialize, JsonSchema)]
|
|
pub struct CancelBuildRequest {
|
|
pub build_request_id: String,
|
|
}
|
|
|
|
pub async fn cancel_build_request(
|
|
State(service): State<ServiceState>,
|
|
Path(CancelBuildRequest { build_request_id }): Path<CancelBuildRequest>,
|
|
) -> Result<Json<serde_json::Value>, (StatusCode, Json<ErrorResponse>)> {
|
|
// Update build request state
|
|
{
|
|
let mut active_builds = service.active_builds.write().await;
|
|
if let Some(build_state) = active_builds.get_mut(&build_request_id) {
|
|
build_state.status = BuildRequestStatus::BuildRequestCancelled;
|
|
build_state.updated_at = current_timestamp_nanos();
|
|
} else {
|
|
return Err((
|
|
StatusCode::NOT_FOUND,
|
|
Json(ErrorResponse {
|
|
error: "Build request not found".to_string(),
|
|
}),
|
|
));
|
|
}
|
|
}
|
|
|
|
// Log cancellation event
|
|
let event = create_build_event(
|
|
build_request_id.clone(),
|
|
crate::build_event::EventType::BuildRequestEvent(BuildRequestEvent {
|
|
status: BuildRequestStatus::BuildRequestCancelled as i32,
|
|
requested_partitions: vec![],
|
|
message: "Build request cancelled".to_string(),
|
|
}),
|
|
);
|
|
|
|
if let Err(e) = service.event_log.append_event(event).await {
|
|
error!("Failed to log build request cancelled event: {}", e);
|
|
}
|
|
|
|
info!("Build request {} cancelled", build_request_id);
|
|
|
|
Ok(Json(serde_json::json!({
|
|
"cancelled": true,
|
|
"build_request_id": build_request_id
|
|
})))
|
|
}
|
|
|
|
#[derive(Deserialize, JsonSchema)]
|
|
pub struct PartitionStatusRequest {
|
|
#[serde(rename = "ref")]
|
|
pub ref_param: String,
|
|
}
|
|
|
|
pub async fn get_partition_status(
|
|
State(service): State<ServiceState>,
|
|
Path(partition_ref): Path<PartitionStatusRequest>,
|
|
) -> Result<Json<PartitionStatusResponse>, (StatusCode, Json<ErrorResponse>)> {
|
|
// Get latest partition status
|
|
let (status, last_updated) = match service.event_log.get_latest_partition_status(&partition_ref.ref_param).await {
|
|
Ok(Some((status, timestamp))) => (status, Some(timestamp)),
|
|
Ok(None) => {
|
|
// No partition events found - this is a legitimate 404
|
|
return Err((
|
|
StatusCode::NOT_FOUND,
|
|
Json(ErrorResponse {
|
|
error: format!("Partition not found: {}", partition_ref.ref_param),
|
|
}),
|
|
));
|
|
},
|
|
Err(e) => {
|
|
error!("Failed to get partition status: {}", e);
|
|
return Err((
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Json(ErrorResponse {
|
|
error: format!("Failed to get partition status: {}", e),
|
|
}),
|
|
));
|
|
}
|
|
};
|
|
|
|
// Get active builds for this partition
|
|
let build_requests = match service.event_log.get_active_builds_for_partition(&partition_ref.ref_param).await {
|
|
Ok(builds) => builds,
|
|
Err(e) => {
|
|
error!("Failed to get active builds for partition: {}", e);
|
|
return Err((
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Json(ErrorResponse {
|
|
error: format!("Failed to get active builds for partition: {}", e),
|
|
}),
|
|
));
|
|
}
|
|
};
|
|
|
|
Ok(Json(PartitionStatusResponse {
|
|
partition_ref: partition_ref.ref_param,
|
|
status: BuildGraphService::partition_status_to_string(status),
|
|
last_updated,
|
|
build_requests,
|
|
}))
|
|
}
|
|
|
|
#[derive(Deserialize, JsonSchema)]
|
|
pub struct PartitionEventsRequest {
|
|
#[serde(rename = "ref")]
|
|
pub ref_param: String,
|
|
}
|
|
|
|
pub async fn get_partition_events(
|
|
State(service): State<ServiceState>,
|
|
Path(request): Path<PartitionEventsRequest>,
|
|
) -> Result<Json<PartitionEventsResponse>, (StatusCode, Json<ErrorResponse>)> {
|
|
let events = match service.event_log.get_partition_events(&request.ref_param, None).await {
|
|
Ok(events) => events.into_iter().map(|e| {
|
|
let (job_label, partition_ref, delegated_build_id) = extract_navigation_data(&e.event_type);
|
|
BuildEventSummary {
|
|
event_id: e.event_id,
|
|
timestamp: e.timestamp,
|
|
event_type: event_type_to_string(&e.event_type),
|
|
message: event_to_message(&e.event_type),
|
|
job_label,
|
|
partition_ref,
|
|
delegated_build_id,
|
|
}
|
|
}).collect(),
|
|
Err(e) => {
|
|
error!("Failed to get partition events: {}", e);
|
|
return Err((
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Json(ErrorResponse {
|
|
error: format!("Failed to get partition events: {}", e),
|
|
}),
|
|
));
|
|
}
|
|
};
|
|
|
|
Ok(Json(PartitionEventsResponse {
|
|
partition_ref: request.ref_param,
|
|
events,
|
|
}))
|
|
}
|
|
|
|
pub async fn analyze_build_graph(
|
|
State(service): State<ServiceState>,
|
|
Json(request): Json<AnalyzeRequest>,
|
|
) -> Result<Json<AnalyzeResponse>, (StatusCode, Json<ErrorResponse>)> {
|
|
// Call the analyze command (use temporary ID for analyze-only requests)
|
|
let temp_build_request_id = BuildGraphService::generate_build_request_id();
|
|
let analyze_result = run_analyze_command(&service, &temp_build_request_id, &request.partitions).await;
|
|
|
|
match analyze_result {
|
|
Ok(job_graph) => {
|
|
let job_graph_json = match serde_json::to_value(&job_graph) {
|
|
Ok(json) => json,
|
|
Err(e) => {
|
|
error!("Failed to serialize job graph: {}", e);
|
|
return Err((
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Json(ErrorResponse {
|
|
error: format!("Failed to serialize job graph: {}", e),
|
|
}),
|
|
));
|
|
}
|
|
};
|
|
|
|
Ok(Json(AnalyzeResponse {
|
|
job_graph: job_graph_json,
|
|
}))
|
|
}
|
|
Err(e) => {
|
|
error!("Failed to analyze build graph: {}", e);
|
|
Err((
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Json(ErrorResponse {
|
|
error: format!("Failed to analyze build graph: {}", e),
|
|
}),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn execute_build_request(
|
|
service: ServiceState,
|
|
build_request_id: String,
|
|
partitions: Vec<String>,
|
|
) -> Result<(), String> {
|
|
info!("Starting build execution for request {}", build_request_id);
|
|
|
|
// Update status to planning
|
|
update_build_request_status(&service, &build_request_id, BuildRequestStatus::BuildRequestPlanning).await;
|
|
|
|
// Log planning event
|
|
let event = create_build_event(
|
|
build_request_id.clone(),
|
|
crate::build_event::EventType::BuildRequestEvent(BuildRequestEvent {
|
|
status: BuildRequestStatus::BuildRequestPlanning as i32,
|
|
requested_partitions: partitions.iter()
|
|
.map(|p| PartitionRef { str: p.clone() })
|
|
.collect(),
|
|
message: "Starting build planning".to_string(),
|
|
}),
|
|
);
|
|
|
|
if let Err(e) = service.event_log.append_event(event).await {
|
|
error!("Failed to log planning event: {}", e);
|
|
}
|
|
|
|
// Analyze the build graph
|
|
let job_graph = match run_analyze_command(&service, &build_request_id, &partitions).await {
|
|
Ok(graph) => graph,
|
|
Err(e) => {
|
|
error!("Failed to analyze build graph: {}", e);
|
|
update_build_request_status(&service, &build_request_id, BuildRequestStatus::BuildRequestFailed).await;
|
|
return Err(e);
|
|
}
|
|
};
|
|
|
|
// Update status to executing
|
|
update_build_request_status(&service, &build_request_id, BuildRequestStatus::BuildRequestExecuting).await;
|
|
|
|
// Log executing event
|
|
let event = create_build_event(
|
|
build_request_id.clone(),
|
|
crate::build_event::EventType::BuildRequestEvent(BuildRequestEvent {
|
|
status: BuildRequestStatus::BuildRequestExecuting as i32,
|
|
requested_partitions: partitions.iter()
|
|
.map(|p| PartitionRef { str: p.clone() })
|
|
.collect(),
|
|
message: "Starting build execution".to_string(),
|
|
}),
|
|
);
|
|
|
|
if let Err(e) = service.event_log.append_event(event).await {
|
|
error!("Failed to log executing event: {}", e);
|
|
}
|
|
|
|
// Execute the build graph
|
|
match run_execute_command(&service, &build_request_id, &job_graph).await {
|
|
Ok(_) => {
|
|
info!("Build request {} completed successfully", build_request_id);
|
|
update_build_request_status(&service, &build_request_id, BuildRequestStatus::BuildRequestCompleted).await;
|
|
|
|
// Log completion event
|
|
let event = create_build_event(
|
|
build_request_id.clone(),
|
|
crate::build_event::EventType::BuildRequestEvent(BuildRequestEvent {
|
|
status: BuildRequestStatus::BuildRequestCompleted as i32,
|
|
requested_partitions: partitions.iter()
|
|
.map(|p| PartitionRef { str: p.clone() })
|
|
.collect(),
|
|
message: "Build request completed successfully".to_string(),
|
|
}),
|
|
);
|
|
|
|
if let Err(e) = service.event_log.append_event(event).await {
|
|
error!("Failed to log completion event: {}", e);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
Err(e) => {
|
|
error!("Build request {} failed: {}", build_request_id, e);
|
|
update_build_request_status(&service, &build_request_id, BuildRequestStatus::BuildRequestFailed).await;
|
|
|
|
// Log failure event
|
|
let event = create_build_event(
|
|
build_request_id.clone(),
|
|
crate::build_event::EventType::BuildRequestEvent(BuildRequestEvent {
|
|
status: BuildRequestStatus::BuildRequestFailed as i32,
|
|
requested_partitions: partitions.iter()
|
|
.map(|p| PartitionRef { str: p.clone() })
|
|
.collect(),
|
|
message: format!("Build request failed: {}", e),
|
|
}),
|
|
);
|
|
|
|
if let Err(e) = service.event_log.append_event(event).await {
|
|
error!("Failed to log failure event: {}", e);
|
|
}
|
|
|
|
Err(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn update_build_request_status(
|
|
service: &ServiceState,
|
|
build_request_id: &str,
|
|
status: BuildRequestStatus,
|
|
) {
|
|
let mut active_builds = service.active_builds.write().await;
|
|
if let Some(build_state) = active_builds.get_mut(build_request_id) {
|
|
build_state.status = status;
|
|
build_state.updated_at = current_timestamp_nanos();
|
|
}
|
|
}
|
|
|
|
async fn run_analyze_command(
|
|
service: &ServiceState,
|
|
build_request_id: &str,
|
|
partitions: &[String],
|
|
) -> Result<JobGraph, String> {
|
|
// Run analyze command
|
|
let analyze_binary = env::var("DATABUILD_ANALYZE_BINARY")
|
|
.unwrap_or_else(|_| "databuild_analyze".to_string());
|
|
|
|
let output = Command::new(&analyze_binary)
|
|
.args(partitions)
|
|
.env("DATABUILD_JOB_LOOKUP_PATH", &service.job_lookup_path)
|
|
.env("DATABUILD_CANDIDATE_JOBS", serde_json::to_string(&service.candidate_jobs).unwrap())
|
|
.env("DATABUILD_BUILD_EVENT_LOG", &service.event_log_uri)
|
|
.env("DATABUILD_BUILD_REQUEST_ID", build_request_id)
|
|
.output()
|
|
.map_err(|e| format!("Failed to execute analyze command: {}", e))?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
return Err(format!("Analyze command failed: {}", stderr));
|
|
}
|
|
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let job_graph: JobGraph = serde_json::from_str(&stdout)
|
|
.map_err(|e| format!("Failed to parse analyze result: {}", e))?;
|
|
|
|
Ok(job_graph)
|
|
}
|
|
|
|
async fn run_execute_command(
|
|
service: &ServiceState,
|
|
build_request_id: &str,
|
|
job_graph: &JobGraph,
|
|
) -> Result<(), String> {
|
|
// Serialize job graph
|
|
let job_graph_json = serde_json::to_string(job_graph)
|
|
.map_err(|e| format!("Failed to serialize job graph: {}", e))?;
|
|
|
|
// Run execute command
|
|
let execute_binary = env::var("DATABUILD_EXECUTE_BINARY")
|
|
.unwrap_or_else(|_| "databuild_execute".to_string());
|
|
|
|
let mut child = Command::new(&execute_binary)
|
|
.env("DATABUILD_JOB_LOOKUP_PATH", &service.job_lookup_path)
|
|
.env("DATABUILD_CANDIDATE_JOBS", serde_json::to_string(&service.candidate_jobs).unwrap())
|
|
.env("DATABUILD_BUILD_EVENT_LOG", &service.event_log_uri)
|
|
.env("DATABUILD_BUILD_REQUEST_ID", build_request_id)
|
|
.stdin(std::process::Stdio::piped())
|
|
.stdout(std::process::Stdio::piped())
|
|
.stderr(std::process::Stdio::piped())
|
|
.spawn()
|
|
.map_err(|e| format!("Failed to spawn execute command: {}", e))?;
|
|
|
|
// Write job graph to stdin
|
|
if let Some(stdin) = child.stdin.take() {
|
|
use std::io::Write;
|
|
let mut stdin = stdin;
|
|
stdin.write_all(job_graph_json.as_bytes())
|
|
.map_err(|e| format!("Failed to write job graph to stdin: {}", e))?;
|
|
}
|
|
|
|
// Wait for completion
|
|
let output = child.wait_with_output()
|
|
.map_err(|e| format!("Failed to wait for execute command: {}", e))?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
return Err(format!("Execute command failed: {}", stderr));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn event_type_to_string(event_type: &Option<crate::build_event::EventType>) -> String {
|
|
match event_type {
|
|
Some(crate::build_event::EventType::BuildRequestEvent(_)) => "build_request".to_string(),
|
|
Some(crate::build_event::EventType::PartitionEvent(_)) => "partition".to_string(),
|
|
Some(crate::build_event::EventType::JobEvent(_)) => "job".to_string(),
|
|
Some(crate::build_event::EventType::DelegationEvent(_)) => "delegation".to_string(),
|
|
None => "INVALID_EVENT_TYPE".to_string(), // Make this obvious rather than hiding it
|
|
}
|
|
}
|
|
|
|
fn event_to_message(event_type: &Option<crate::build_event::EventType>) -> String {
|
|
match event_type {
|
|
Some(crate::build_event::EventType::BuildRequestEvent(event)) => event.message.clone(),
|
|
Some(crate::build_event::EventType::PartitionEvent(event)) => event.message.clone(),
|
|
Some(crate::build_event::EventType::JobEvent(event)) => event.message.clone(),
|
|
Some(crate::build_event::EventType::DelegationEvent(event)) => event.message.clone(),
|
|
None => "INVALID_EVENT_NO_MESSAGE".to_string(), // Make this obvious
|
|
}
|
|
}
|
|
|
|
fn extract_navigation_data(event_type: &Option<crate::build_event::EventType>) -> (Option<String>, Option<String>, Option<String>) {
|
|
match event_type {
|
|
Some(crate::build_event::EventType::JobEvent(event)) => {
|
|
let job_label = event.job_label.as_ref().map(|l| l.label.clone());
|
|
(job_label, None, None)
|
|
},
|
|
Some(crate::build_event::EventType::PartitionEvent(event)) => {
|
|
let partition_ref = event.partition_ref.as_ref().map(|r| r.str.clone());
|
|
(None, partition_ref, None)
|
|
},
|
|
Some(crate::build_event::EventType::DelegationEvent(event)) => {
|
|
let delegated_build_id = Some(event.delegated_to_build_request_id.clone());
|
|
(None, None, delegated_build_id)
|
|
},
|
|
Some(crate::build_event::EventType::BuildRequestEvent(_)) => {
|
|
// Build request events don't need navigation links (self-referential)
|
|
(None, None, None)
|
|
},
|
|
None => (None, None, None),
|
|
}
|
|
}
|
|
|
|
// New handlers for list endpoints
|
|
use axum::extract::Query;
|
|
use std::collections::HashMap;
|
|
|
|
pub async fn list_build_requests(
|
|
State(service): State<ServiceState>,
|
|
Query(params): Query<HashMap<String, String>>,
|
|
) -> Result<Json<BuildsListResponse>, (StatusCode, Json<ErrorResponse>)> {
|
|
let limit = params.get("limit")
|
|
.and_then(|s| s.parse::<u32>().ok())
|
|
.unwrap_or(20)
|
|
.min(100); // Cap at 100
|
|
|
|
let offset = params.get("offset")
|
|
.and_then(|s| s.parse::<u32>().ok())
|
|
.unwrap_or(0);
|
|
|
|
let status_filter = params.get("status")
|
|
.and_then(|s| match s.as_str() {
|
|
"received" => Some(BuildRequestStatus::BuildRequestReceived),
|
|
"planning" => Some(BuildRequestStatus::BuildRequestPlanning),
|
|
"executing" => Some(BuildRequestStatus::BuildRequestExecuting),
|
|
"completed" => Some(BuildRequestStatus::BuildRequestCompleted),
|
|
"failed" => Some(BuildRequestStatus::BuildRequestFailed),
|
|
"cancelled" => Some(BuildRequestStatus::BuildRequestCancelled),
|
|
_ => None,
|
|
});
|
|
|
|
match service.event_log.list_build_requests(limit, offset, status_filter).await {
|
|
Ok((summaries, total_count)) => {
|
|
let builds: Vec<BuildSummary> = summaries.into_iter().map(|s| BuildSummary {
|
|
build_request_id: s.build_request_id,
|
|
status: format!("{:?}", s.status),
|
|
requested_partitions: s.requested_partitions,
|
|
created_at: s.created_at,
|
|
updated_at: s.updated_at,
|
|
}).collect();
|
|
|
|
let has_more = (offset + limit) < total_count;
|
|
|
|
Ok(Json(BuildsListResponse {
|
|
builds,
|
|
total_count,
|
|
has_more,
|
|
}))
|
|
}
|
|
Err(e) => {
|
|
error!("Failed to list build requests: {}", e);
|
|
Err((
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Json(ErrorResponse {
|
|
error: format!("Failed to list build requests: {}", e),
|
|
}),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn list_partitions(
|
|
State(service): State<ServiceState>,
|
|
Query(params): Query<HashMap<String, String>>,
|
|
) -> Result<Json<PartitionsListResponse>, (StatusCode, Json<ErrorResponse>)> {
|
|
let limit = params.get("limit")
|
|
.and_then(|s| s.parse::<u32>().ok())
|
|
.unwrap_or(20)
|
|
.min(100); // Cap at 100
|
|
|
|
let offset = params.get("offset")
|
|
.and_then(|s| s.parse::<u32>().ok())
|
|
.unwrap_or(0);
|
|
|
|
let status_filter = params.get("status")
|
|
.and_then(|s| match s.as_str() {
|
|
"requested" => Some(PartitionStatus::PartitionRequested),
|
|
"scheduled" => Some(PartitionStatus::PartitionScheduled),
|
|
"building" => Some(PartitionStatus::PartitionBuilding),
|
|
"available" => Some(PartitionStatus::PartitionAvailable),
|
|
"failed" => Some(PartitionStatus::PartitionFailed),
|
|
"delegated" => Some(PartitionStatus::PartitionDelegated),
|
|
_ => None,
|
|
});
|
|
|
|
match service.event_log.list_recent_partitions(limit, offset, status_filter).await {
|
|
Ok((summaries, total_count)) => {
|
|
let partitions: Vec<PartitionSummary> = summaries.into_iter().map(|s| PartitionSummary {
|
|
partition_ref: s.partition_ref,
|
|
status: format!("{:?}", s.status),
|
|
updated_at: s.updated_at,
|
|
build_request_id: s.build_request_id,
|
|
}).collect();
|
|
|
|
let has_more = (offset + limit) < total_count;
|
|
|
|
Ok(Json(PartitionsListResponse {
|
|
partitions,
|
|
total_count,
|
|
has_more,
|
|
}))
|
|
}
|
|
Err(e) => {
|
|
error!("Failed to list partitions: {}", e);
|
|
Err((
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Json(ErrorResponse {
|
|
error: format!("Failed to list partitions: {}", e),
|
|
}),
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn get_activity_summary(
|
|
State(service): State<ServiceState>,
|
|
) -> Result<Json<ActivityResponse>, (StatusCode, Json<ErrorResponse>)> {
|
|
match service.event_log.get_activity_summary().await {
|
|
Ok(summary) => {
|
|
let recent_builds: Vec<BuildSummary> = summary.recent_builds.into_iter().map(|s| BuildSummary {
|
|
build_request_id: s.build_request_id,
|
|
status: format!("{:?}", s.status),
|
|
requested_partitions: s.requested_partitions,
|
|
created_at: s.created_at,
|
|
updated_at: s.updated_at,
|
|
}).collect();
|
|
|
|
let recent_partitions: Vec<PartitionSummary> = summary.recent_partitions.into_iter().map(|s| PartitionSummary {
|
|
partition_ref: s.partition_ref,
|
|
status: format!("{:?}", s.status),
|
|
updated_at: s.updated_at,
|
|
build_request_id: s.build_request_id,
|
|
}).collect();
|
|
|
|
// Simple system status logic
|
|
let system_status = if summary.active_builds_count > 10 {
|
|
"degraded".to_string()
|
|
} else {
|
|
"healthy".to_string()
|
|
};
|
|
|
|
Ok(Json(ActivityResponse {
|
|
active_builds_count: summary.active_builds_count,
|
|
recent_builds,
|
|
recent_partitions,
|
|
total_partitions_count: summary.total_partitions_count,
|
|
system_status,
|
|
}))
|
|
}
|
|
Err(e) => {
|
|
error!("Failed to get activity summary: {}", e);
|
|
Err((
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
Json(ErrorResponse {
|
|
error: format!("Failed to get activity summary: {}", e),
|
|
}),
|
|
))
|
|
}
|
|
}
|
|
} |