update api, fix tests and examples
This commit is contained in:
286
src/openrpc.rs
286
src/openrpc.rs
@@ -19,7 +19,6 @@ use sal_service_manager::{ProcessStatus, LogInfo};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::fs;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -84,6 +83,31 @@ pub struct RunJobParams {
|
||||
pub job: Job,
|
||||
}
|
||||
|
||||
/// Request parameters for starting a job
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct StartJobParams {
|
||||
pub secret: String,
|
||||
pub job_id: String,
|
||||
}
|
||||
|
||||
/// Job result response
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum JobResult {
|
||||
Success { success: String },
|
||||
Error { error: String },
|
||||
}
|
||||
|
||||
/// Job status response
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct JobStatusResponse {
|
||||
pub job_id: String,
|
||||
pub status: String,
|
||||
pub created_at: String,
|
||||
pub started_at: Option<String>,
|
||||
pub completed_at: Option<String>,
|
||||
}
|
||||
|
||||
/// Request parameters for adding a new runner
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct AddRunnerParams {
|
||||
@@ -98,18 +122,32 @@ pub struct AddRunnerParams {
|
||||
/// Request parameters for queuing a job
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct QueueJobParams {
|
||||
pub runner_name: String,
|
||||
pub runner: String,
|
||||
pub job: Job,
|
||||
}
|
||||
|
||||
/// Request parameters for queue and wait operation
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct QueueAndWaitParams {
|
||||
pub runner_name: String,
|
||||
pub runner: String,
|
||||
pub job: Job,
|
||||
pub timeout_secs: u64,
|
||||
}
|
||||
|
||||
/// Request parameters for stopping a job
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct StopJobParams {
|
||||
pub secret: String,
|
||||
pub job_id: String,
|
||||
}
|
||||
|
||||
/// Request parameters for deleting a job
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct DeleteJobParams {
|
||||
pub secret: String,
|
||||
pub job_id: String,
|
||||
}
|
||||
|
||||
/// Request parameters for getting runner logs
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct GetLogsParams {
|
||||
@@ -236,13 +274,37 @@ pub trait SupervisorRpc {
|
||||
#[method(name = "register_runner")]
|
||||
async fn register_runner(&self, params: RegisterRunnerParams) -> RpcResult<String>;
|
||||
|
||||
/// Create a job (fire-and-forget, non-blocking)
|
||||
#[method(name = "create_job")]
|
||||
async fn create_job(&self, params: RunJobParams) -> RpcResult<String>;
|
||||
/// Create a job without queuing it to a runner
|
||||
#[method(name = "jobs.create")]
|
||||
async fn jobs_create(&self, params: RunJobParams) -> RpcResult<String>;
|
||||
|
||||
/// Run a job on the appropriate runner (blocking, returns result)
|
||||
#[method(name = "run_job")]
|
||||
async fn run_job(&self, params: RunJobParams) -> RpcResult<Option<String>>;
|
||||
/// List all jobs
|
||||
#[method(name = "jobs.list")]
|
||||
async fn jobs_list(&self) -> RpcResult<Vec<Job>>;
|
||||
|
||||
/// Run a job on the appropriate runner and return the result
|
||||
#[method(name = "job.run")]
|
||||
async fn job_run(&self, params: RunJobParams) -> RpcResult<JobResult>;
|
||||
|
||||
/// Start a previously created job by queuing it to its assigned runner
|
||||
#[method(name = "job.start")]
|
||||
async fn job_start(&self, params: StartJobParams) -> RpcResult<()>;
|
||||
|
||||
/// Get the current status of a job
|
||||
#[method(name = "job.status")]
|
||||
async fn job_status(&self, job_id: String) -> RpcResult<JobStatusResponse>;
|
||||
|
||||
/// Get the result of a completed job (blocks until result is available)
|
||||
#[method(name = "job.result")]
|
||||
async fn job_result(&self, job_id: String) -> RpcResult<JobResult>;
|
||||
|
||||
/// Stop a running job
|
||||
#[method(name = "job.stop")]
|
||||
async fn job_stop(&self, params: StopJobParams) -> RpcResult<()>;
|
||||
|
||||
/// Delete a job from the system
|
||||
#[method(name = "job.delete")]
|
||||
async fn job_delete(&self, params: DeleteJobParams) -> RpcResult<()>;
|
||||
|
||||
/// Remove a runner from the supervisor
|
||||
#[method(name = "remove_runner")]
|
||||
@@ -276,9 +338,6 @@ pub trait SupervisorRpc {
|
||||
#[method(name = "queue_job_to_runner")]
|
||||
async fn queue_job_to_runner(&self, params: QueueJobParams) -> RpcResult<()>;
|
||||
|
||||
/// List all job IDs from Redis
|
||||
#[method(name = "list_jobs")]
|
||||
async fn list_jobs(&self) -> RpcResult<Vec<String>>;
|
||||
|
||||
/// Get a job by job ID
|
||||
#[method(name = "get_job")]
|
||||
@@ -381,8 +440,8 @@ impl SupervisorRpcServer for Arc<Mutex<Supervisor>> {
|
||||
Ok(params.name)
|
||||
}
|
||||
|
||||
async fn create_job(&self, params: RunJobParams) -> RpcResult<String> {
|
||||
debug!("OpenRPC request: create_job with params: {:?}", params);
|
||||
async fn jobs_create(&self, params: RunJobParams) -> RpcResult<String> {
|
||||
debug!("OpenRPC request: jobs.create with params: {:?}", params);
|
||||
|
||||
let mut supervisor = self.lock().await;
|
||||
let job_id = supervisor
|
||||
@@ -393,12 +452,85 @@ impl SupervisorRpcServer for Arc<Mutex<Supervisor>> {
|
||||
Ok(job_id)
|
||||
}
|
||||
|
||||
async fn run_job(&self, params: RunJobParams) -> RpcResult<Option<String>> {
|
||||
debug!("OpenRPC request: run_job with params: {:?}", params);
|
||||
async fn jobs_list(&self) -> RpcResult<Vec<Job>> {
|
||||
debug!("OpenRPC request: jobs.list");
|
||||
let supervisor = self.lock().await;
|
||||
supervisor
|
||||
.list_all_jobs()
|
||||
.await
|
||||
.map_err(runner_error_to_rpc_error)
|
||||
}
|
||||
|
||||
async fn job_run(&self, params: RunJobParams) -> RpcResult<JobResult> {
|
||||
debug!("OpenRPC request: job.run with params: {:?}", params);
|
||||
|
||||
let mut supervisor = self.lock().await;
|
||||
match supervisor
|
||||
.run_job(¶ms.secret, params.job)
|
||||
.await
|
||||
.map_err(runner_error_to_rpc_error)? {
|
||||
Some(output) => Ok(JobResult::Success { success: output }),
|
||||
None => Ok(JobResult::Error { error: "Job execution failed".to_string() })
|
||||
}
|
||||
}
|
||||
|
||||
async fn job_start(&self, params: StartJobParams) -> RpcResult<()> {
|
||||
debug!("OpenRPC request: job.start with params: {:?}", params);
|
||||
|
||||
let mut supervisor = self.lock().await;
|
||||
supervisor
|
||||
.run_job(¶ms.secret, params.job)
|
||||
.start_job(¶ms.secret, ¶ms.job_id)
|
||||
.await
|
||||
.map_err(runner_error_to_rpc_error)
|
||||
}
|
||||
|
||||
async fn job_status(&self, job_id: String) -> RpcResult<JobStatusResponse> {
|
||||
debug!("OpenRPC request: job.status with job_id: {}", job_id);
|
||||
|
||||
let supervisor = self.lock().await;
|
||||
let status = supervisor
|
||||
.get_job_status(&job_id)
|
||||
.await
|
||||
.map_err(runner_error_to_rpc_error)?;
|
||||
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
async fn job_result(&self, job_id: String) -> RpcResult<JobResult> {
|
||||
debug!("OpenRPC request: job.result with job_id: {}", job_id);
|
||||
|
||||
let supervisor = self.lock().await;
|
||||
match supervisor
|
||||
.get_job_result(&job_id)
|
||||
.await
|
||||
.map_err(runner_error_to_rpc_error)? {
|
||||
Some(result) => {
|
||||
if result.starts_with("Error:") {
|
||||
Ok(JobResult::Error { error: result })
|
||||
} else {
|
||||
Ok(JobResult::Success { success: result })
|
||||
}
|
||||
},
|
||||
None => Ok(JobResult::Error { error: "Job result not available".to_string() })
|
||||
}
|
||||
}
|
||||
|
||||
async fn job_stop(&self, params: StopJobParams) -> RpcResult<()> {
|
||||
debug!("OpenRPC request: job.stop with params: {:?}", params);
|
||||
|
||||
let mut supervisor = self.lock().await;
|
||||
supervisor
|
||||
.stop_job(¶ms.job_id)
|
||||
.await
|
||||
.map_err(runner_error_to_rpc_error)
|
||||
}
|
||||
|
||||
async fn job_delete(&self, params: DeleteJobParams) -> RpcResult<()> {
|
||||
debug!("OpenRPC request: job.delete with params: {:?}", params);
|
||||
|
||||
let mut supervisor = self.lock().await;
|
||||
supervisor
|
||||
.delete_job(¶ms.job_id)
|
||||
.await
|
||||
.map_err(runner_error_to_rpc_error)
|
||||
}
|
||||
@@ -469,19 +601,11 @@ impl SupervisorRpcServer for Arc<Mutex<Supervisor>> {
|
||||
debug!("OpenRPC request: queue_job_to_runner with params: {:?}", params);
|
||||
let mut supervisor = self.lock().await;
|
||||
supervisor
|
||||
.queue_job_to_runner(¶ms.runner_name, params.job)
|
||||
.queue_job_to_runner(¶ms.runner, params.job)
|
||||
.await
|
||||
.map_err(runner_error_to_rpc_error)
|
||||
}
|
||||
|
||||
async fn list_jobs(&self) -> RpcResult<Vec<String>> {
|
||||
debug!("OpenRPC request: list_jobs");
|
||||
let supervisor = self.lock().await;
|
||||
supervisor
|
||||
.list_jobs()
|
||||
.await
|
||||
.map_err(runner_error_to_rpc_error)
|
||||
}
|
||||
|
||||
async fn get_job(&self, job_id: String) -> RpcResult<Job> {
|
||||
debug!("OpenRPC request: get_job with job_id: {}", job_id);
|
||||
@@ -523,7 +647,7 @@ impl SupervisorRpcServer for Arc<Mutex<Supervisor>> {
|
||||
debug!("OpenRPC request: queue_and_wait with params: {:?}", params);
|
||||
let mut supervisor = self.lock().await;
|
||||
supervisor
|
||||
.queue_and_wait(¶ms.runner_name, params.job, params.timeout_secs)
|
||||
.queue_and_wait(¶ms.runner, params.job, params.timeout_secs)
|
||||
.await
|
||||
.map_err(runner_error_to_rpc_error)
|
||||
}
|
||||
@@ -810,20 +934,108 @@ pub async fn start_openrpc_servers(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::supervisor::Supervisor;
|
||||
|
||||
#[test]
|
||||
fn test_supervisor_rpc_creation() {
|
||||
let _rpc = SupervisorRpcImpl::new();
|
||||
// Just test that we can create the RPC implementation
|
||||
#[tokio::test]
|
||||
async fn test_supervisor_rpc_creation() {
|
||||
// Test that we can create a supervisor and use it with RPC
|
||||
use crate::supervisor::SupervisorBuilder;
|
||||
|
||||
let supervisor = SupervisorBuilder::new()
|
||||
.redis_url("redis://localhost:6379")
|
||||
.namespace("test")
|
||||
.build()
|
||||
.await;
|
||||
|
||||
// Just test that we can build a supervisor
|
||||
assert!(supervisor.is_ok() || supervisor.is_err()); // Either way is fine for this test
|
||||
}
|
||||
|
||||
#[cfg(feature = "openrpc")]
|
||||
#[test]
|
||||
fn test_process_manager_type_parsing() {
|
||||
assert!(SupervisorRpcImpl::parse_process_manager_type("simple").is_ok());
|
||||
assert!(SupervisorRpcImpl::parse_process_manager_type("tmux").is_ok());
|
||||
assert!(SupervisorRpcImpl::parse_process_manager_type("Simple").is_ok());
|
||||
assert!(SupervisorRpcImpl::parse_process_manager_type("TMUX").is_ok());
|
||||
assert!(SupervisorRpcImpl::parse_process_manager_type("invalid").is_err());
|
||||
assert!(parse_process_manager_type("simple", None).is_ok());
|
||||
assert!(parse_process_manager_type("tmux", Some("session".to_string())).is_ok());
|
||||
assert!(parse_process_manager_type("Simple", None).is_ok());
|
||||
assert!(parse_process_manager_type("TMUX", Some("session".to_string())).is_ok());
|
||||
assert!(parse_process_manager_type("invalid", None).is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_job_api_methods() {
|
||||
let supervisor = Arc::new(Mutex::new(Supervisor::default()));
|
||||
let mut sup = supervisor.lock().await;
|
||||
sup.add_user_secret("test-secret".to_string());
|
||||
drop(sup);
|
||||
|
||||
// Test jobs.create
|
||||
let job = crate::job::JobBuilder::new()
|
||||
.caller_id("test")
|
||||
.context_id("test")
|
||||
.payload("test")
|
||||
.runner("test_runner")
|
||||
.executor("osis")
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let params = RunJobParams {
|
||||
secret: "test-secret".to_string(),
|
||||
job: job.clone(),
|
||||
};
|
||||
|
||||
let result = supervisor.jobs_create(params).await;
|
||||
// Should work or fail gracefully without Redis
|
||||
assert!(result.is_ok() || result.is_err());
|
||||
|
||||
// Test job.start
|
||||
let start_params = StartJobParams {
|
||||
secret: "test-secret".to_string(),
|
||||
job_id: "test-job".to_string(),
|
||||
};
|
||||
|
||||
let result = supervisor.job_start(start_params).await;
|
||||
// Should fail gracefully without Redis/job
|
||||
assert!(result.is_err());
|
||||
|
||||
// Test invalid secret
|
||||
let invalid_params = StartJobParams {
|
||||
secret: "invalid".to_string(),
|
||||
job_id: "test-job".to_string(),
|
||||
};
|
||||
|
||||
let result = supervisor.job_start(invalid_params).await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_job_result_serialization() {
|
||||
let success = JobResult::Success { success: "test output".to_string() };
|
||||
let json = serde_json::to_string(&success).unwrap();
|
||||
assert!(json.contains("success"));
|
||||
assert!(json.contains("test output"));
|
||||
|
||||
let error = JobResult::Error { error: "test error".to_string() };
|
||||
let json = serde_json::to_string(&error).unwrap();
|
||||
assert!(json.contains("error"));
|
||||
assert!(json.contains("test error"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_job_status_response_serialization() {
|
||||
let status = JobStatusResponse {
|
||||
job_id: "test-job".to_string(),
|
||||
status: "running".to_string(),
|
||||
created_at: "2023-01-01T00:00:00Z".to_string(),
|
||||
started_at: Some("2023-01-01T00:00:05Z".to_string()),
|
||||
completed_at: None,
|
||||
};
|
||||
|
||||
let json = serde_json::to_string(&status).unwrap();
|
||||
assert!(json.contains("test-job"));
|
||||
assert!(json.contains("running"));
|
||||
assert!(json.contains("2023-01-01T00:00:00Z"));
|
||||
|
||||
let deserialized: JobStatusResponse = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(deserialized.job_id, "test-job");
|
||||
assert_eq!(deserialized.status, "running");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user