Updates
This commit is contained in:
		@@ -19,10 +19,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
 | 
			
		||||
clap = { version = "4.0", features = ["derive"] }
 | 
			
		||||
 | 
			
		||||
# JSON-RPC dependencies
 | 
			
		||||
jsonrpsee = { version = "0.21", features = [
 | 
			
		||||
    "server",
 | 
			
		||||
    "macros"
 | 
			
		||||
] }
 | 
			
		||||
jsonrpsee = { version = "0.21", features = ["server", "macros"] }
 | 
			
		||||
jsonrpsee-types = "0.21"
 | 
			
		||||
uuid = { version = "1.6", features = ["v4", "serde"] }
 | 
			
		||||
chrono = { version = "0.4", features = ["serde"] }
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ use tracing_subscriber;
 | 
			
		||||
 | 
			
		||||
#[derive(Parser)]
 | 
			
		||||
#[command(name = "hero-openrpc-server")]
 | 
			
		||||
#[command(about = "Hero OpenRPC Server - WebSocket and Unix socket JSON-RPC server")]
 | 
			
		||||
#[command(about = "Hero OpenRPC Server - JSON-RPC over HTTP/WS")]
 | 
			
		||||
struct Cli {
 | 
			
		||||
    #[command(subcommand)]
 | 
			
		||||
    command: Commands,
 | 
			
		||||
@@ -34,12 +34,6 @@ enum Commands {
 | 
			
		||||
        #[arg(long, default_value = "127.0.0.1:9944")]
 | 
			
		||||
        addr: SocketAddr,
 | 
			
		||||
    },
 | 
			
		||||
    /// Start Unix socket server
 | 
			
		||||
    Unix {
 | 
			
		||||
        /// Unix socket path
 | 
			
		||||
        #[arg(long, default_value = "/tmp/hero-openrpc.sock")]
 | 
			
		||||
        socket_path: PathBuf,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
@@ -65,14 +59,6 @@ async fn main() -> Result<()> {
 | 
			
		||||
            info!("Starting WebSocket server on {}", addr);
 | 
			
		||||
            Transport::WebSocket(addr)
 | 
			
		||||
        }
 | 
			
		||||
        Commands::Unix { socket_path } => {
 | 
			
		||||
            info!("Starting Unix socket server on {:?}", socket_path);
 | 
			
		||||
            // Remove existing socket file if it exists
 | 
			
		||||
            if socket_path.exists() {
 | 
			
		||||
                std::fs::remove_file(&socket_path)?;
 | 
			
		||||
            }
 | 
			
		||||
            Transport::Unix(socket_path)
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let config = OpenRpcServerConfig {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use hero_job::{Job, JobBuilder, JobStatus, ScriptType};
 | 
			
		||||
use hero_supervisor::{Supervisor, SupervisorBuilder};
 | 
			
		||||
use hero_supervisor::{Supervisor, SupervisorBuilder, SupervisorError};
 | 
			
		||||
use jsonrpsee::core::async_trait;
 | 
			
		||||
use jsonrpsee::proc_macros::rpc;
 | 
			
		||||
use jsonrpsee::server::{ServerBuilder, ServerHandle};
 | 
			
		||||
@@ -12,17 +12,24 @@ use std::sync::Arc;
 | 
			
		||||
use tokio::sync::RwLock;
 | 
			
		||||
use tracing::error;
 | 
			
		||||
 | 
			
		||||
fn map_sup_error_to_rpc(e: &SupervisorError) -> ErrorCode {
 | 
			
		||||
    match e {
 | 
			
		||||
        SupervisorError::InvalidInput(_) | SupervisorError::JobError(_) => ErrorCode::InvalidParams,
 | 
			
		||||
        SupervisorError::Timeout(_) => ErrorCode::ServerError(-32002),
 | 
			
		||||
        _ => ErrorCode::InternalError,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
mod auth;
 | 
			
		||||
pub mod types;
 | 
			
		||||
 | 
			
		||||
pub use auth::*;
 | 
			
		||||
pub use types::*;
 | 
			
		||||
 | 
			
		||||
/// Transport type for the OpenRPC server
 | 
			
		||||
/** Transport type for the OpenRPC server */
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub enum Transport {
 | 
			
		||||
    WebSocket(SocketAddr),
 | 
			
		||||
    Unix(PathBuf),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// OpenRPC server configuration
 | 
			
		||||
@@ -82,7 +89,7 @@ pub trait OpenRpcApi {
 | 
			
		||||
    async fn get_job_logs(&self, job_id: String) -> Result<JobLogsResult, ErrorCode>;
 | 
			
		||||
 | 
			
		||||
    #[method(name = "list_jobs")]
 | 
			
		||||
    async fn list_jobs(&self) -> Result<Vec<Job>, ErrorCode>;
 | 
			
		||||
    async fn list_jobs(&self) -> Result<Vec<String>, ErrorCode>;
 | 
			
		||||
 | 
			
		||||
    #[method(name = "stop_job")]
 | 
			
		||||
    async fn stop_job(&self, job_id: String) -> Result<(), ErrorCode>;
 | 
			
		||||
@@ -114,8 +121,8 @@ impl OpenRpcServer {
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Start the OpenRPC server
 | 
			
		||||
    pub async fn start(self, config: OpenRpcServerConfig) -> Result<ServerHandle> {
 | 
			
		||||
    /// Start the OpenRPC server on the given SocketAddr (HTTP/WS only)
 | 
			
		||||
    pub async fn start_on(self, addr: SocketAddr) -> Result<ServerHandle> {
 | 
			
		||||
        let mut module = RpcModule::new(());
 | 
			
		||||
        
 | 
			
		||||
        // Register all the RPC methods
 | 
			
		||||
@@ -244,18 +251,17 @@ impl OpenRpcServer {
 | 
			
		||||
            }
 | 
			
		||||
        })?;
 | 
			
		||||
 | 
			
		||||
        let server = ServerBuilder::default()
 | 
			
		||||
            .build(addr)
 | 
			
		||||
            .await?;
 | 
			
		||||
        let handle = server.start(module);
 | 
			
		||||
        Ok(handle)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Start the OpenRPC server (config wrapper)
 | 
			
		||||
    pub async fn start(self, config: OpenRpcServerConfig) -> Result<ServerHandle> {
 | 
			
		||||
        match config.transport {
 | 
			
		||||
            Transport::WebSocket(addr) => {
 | 
			
		||||
                let server = ServerBuilder::default()
 | 
			
		||||
                    .build(addr)
 | 
			
		||||
                    .await?;
 | 
			
		||||
                let handle = server.start(module);
 | 
			
		||||
                Ok(handle)
 | 
			
		||||
            }
 | 
			
		||||
            Transport::Unix(_path) => {
 | 
			
		||||
                // Unix socket transport not yet implemented in jsonrpsee 0.21
 | 
			
		||||
                return Err(anyhow::anyhow!("Unix socket transport not yet supported").into());
 | 
			
		||||
            }
 | 
			
		||||
            Transport::WebSocket(addr) => self.start_on(addr).await,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -295,12 +301,8 @@ impl OpenRpcApiServer for OpenRpcServer {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn play(&self, script: String) -> Result<PlayResult, ErrorCode> {
 | 
			
		||||
        let _supervisor = self.supervisor.read().await;
 | 
			
		||||
        
 | 
			
		||||
        // For now, return a simple result since we need to implement execute_script method
 | 
			
		||||
        Ok(PlayResult { 
 | 
			
		||||
            output: format!("Script executed: {}", script) 
 | 
			
		||||
        })
 | 
			
		||||
        let output = self.run_job(script, ScriptType::SAL, None).await?;
 | 
			
		||||
        Ok(PlayResult { output })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn create_job(&self, job_params: JobParams) -> Result<String, ErrorCode> {
 | 
			
		||||
@@ -360,10 +362,37 @@ impl OpenRpcApiServer for OpenRpcServer {
 | 
			
		||||
        &self,
 | 
			
		||||
        script: String,
 | 
			
		||||
        script_type: ScriptType,
 | 
			
		||||
        _prerequisites: Option<Vec<String>>,
 | 
			
		||||
        prerequisites: Option<Vec<String>>,
 | 
			
		||||
    ) -> Result<String, ErrorCode> {
 | 
			
		||||
        // For now, return a simple result
 | 
			
		||||
        Ok(format!("Job executed with script: {} (type: {:?})", script, script_type))
 | 
			
		||||
        let supervisor = self.supervisor.read().await;
 | 
			
		||||
 | 
			
		||||
        // Build job with defaults and optional prerequisites
 | 
			
		||||
        let mut builder = JobBuilder::new()
 | 
			
		||||
            .caller_id("rpc-caller")
 | 
			
		||||
            .context_id("rpc-context")
 | 
			
		||||
            .script(&script)
 | 
			
		||||
            .script_type(script_type)
 | 
			
		||||
            .timeout(std::time::Duration::from_secs(30));
 | 
			
		||||
 | 
			
		||||
        if let Some(prs) = prerequisites {
 | 
			
		||||
            builder = builder.prerequisites(prs);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let job = match builder.build() {
 | 
			
		||||
            Ok(j) => j,
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                error!("Failed to build job in run_job: {}", e);
 | 
			
		||||
                return Err(ErrorCode::InvalidParams);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        match supervisor.run_job_and_await_result(&job).await {
 | 
			
		||||
            Ok(output) => Ok(output),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                error!("run_job failed: {}", e);
 | 
			
		||||
                Err(map_sup_error_to_rpc(&e))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn get_job_status(&self, job_id: String) -> Result<JobStatus, ErrorCode> {
 | 
			
		||||
@@ -373,7 +402,7 @@ impl OpenRpcApiServer for OpenRpcServer {
 | 
			
		||||
            Ok(status) => Ok(status),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                error!("Failed to get job status for {}: {}", job_id, e);
 | 
			
		||||
                Err(ErrorCode::InvalidParams)
 | 
			
		||||
                Err(map_sup_error_to_rpc(&e))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -385,50 +414,29 @@ impl OpenRpcApiServer for OpenRpcServer {
 | 
			
		||||
            Ok(output) => Ok(output.unwrap_or_else(|| "No output available".to_string())),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                error!("Failed to get job output for {}: {}", job_id, e);
 | 
			
		||||
                Err(ErrorCode::InvalidParams)
 | 
			
		||||
                Err(map_sup_error_to_rpc(&e))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn get_job_logs(&self, job_id: String) -> Result<JobLogsResult, ErrorCode> {
 | 
			
		||||
        // For now, return mock logs
 | 
			
		||||
        Ok(JobLogsResult {
 | 
			
		||||
            logs: format!("Logs for job {}", job_id),
 | 
			
		||||
        })
 | 
			
		||||
        let supervisor = self.supervisor.read().await;
 | 
			
		||||
        match supervisor.get_job_logs(&job_id).await {
 | 
			
		||||
            Ok(logs_opt) => Ok(JobLogsResult { logs: logs_opt }),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                error!("Failed to get job logs for {}: {}", job_id, e);
 | 
			
		||||
                Err(map_sup_error_to_rpc(&e))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn list_jobs(&self) -> Result<Vec<Job>, ErrorCode> {
 | 
			
		||||
    async fn list_jobs(&self) -> Result<Vec<String>, ErrorCode> {
 | 
			
		||||
        let supervisor = self.supervisor.read().await;
 | 
			
		||||
        
 | 
			
		||||
        match supervisor.list_jobs().await {
 | 
			
		||||
            Ok(job_ids) => {
 | 
			
		||||
                // For now, create minimal Job objects with just the IDs
 | 
			
		||||
                // In a real implementation, we'd need a supervisor.get_job() method
 | 
			
		||||
                let jobs: Vec<Job> = job_ids.into_iter().map(|job_id| {
 | 
			
		||||
                    // Create a minimal job object - this is a temporary solution
 | 
			
		||||
                    // until supervisor.get_job() is implemented
 | 
			
		||||
                    Job {
 | 
			
		||||
                        id: job_id,
 | 
			
		||||
                        caller_id: "unknown".to_string(),
 | 
			
		||||
                        context_id: "unknown".to_string(),
 | 
			
		||||
                        script: "unknown".to_string(),
 | 
			
		||||
                        script_type: ScriptType::OSIS,
 | 
			
		||||
                        timeout: std::time::Duration::from_secs(30),
 | 
			
		||||
                        retries: 0,
 | 
			
		||||
                        concurrent: false,
 | 
			
		||||
                        log_path: None,
 | 
			
		||||
                        env_vars: std::collections::HashMap::new(),
 | 
			
		||||
                        prerequisites: Vec::new(),
 | 
			
		||||
                        dependents: Vec::new(),
 | 
			
		||||
                        created_at: chrono::Utc::now(),
 | 
			
		||||
                        updated_at: chrono::Utc::now(),
 | 
			
		||||
                    }
 | 
			
		||||
                }).collect();
 | 
			
		||||
                Ok(jobs)
 | 
			
		||||
            },
 | 
			
		||||
            Ok(job_ids) => Ok(job_ids),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                error!("Failed to list jobs: {}", e);
 | 
			
		||||
                Err(ErrorCode::InternalError)
 | 
			
		||||
                Err(map_sup_error_to_rpc(&e))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -440,7 +448,7 @@ impl OpenRpcApiServer for OpenRpcServer {
 | 
			
		||||
            Ok(_) => Ok(()),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                error!("Failed to stop job {}: {}", job_id, e);
 | 
			
		||||
                Err(ErrorCode::InvalidParams)
 | 
			
		||||
                Err(map_sup_error_to_rpc(&e))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -452,7 +460,7 @@ impl OpenRpcApiServer for OpenRpcServer {
 | 
			
		||||
            Ok(_) => Ok(()),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                error!("Failed to delete job {}: {}", job_id, e);
 | 
			
		||||
                Err(ErrorCode::InvalidParams)
 | 
			
		||||
                Err(map_sup_error_to_rpc(&e))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -464,7 +472,7 @@ impl OpenRpcApiServer for OpenRpcServer {
 | 
			
		||||
            Ok(_) => Ok(()),
 | 
			
		||||
            Err(e) => {
 | 
			
		||||
                error!("Failed to clear all jobs: {}", e);
 | 
			
		||||
                Err(ErrorCode::InternalError)
 | 
			
		||||
                Err(map_sup_error_to_rpc(&e))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -24,8 +24,8 @@ pub struct StartJobResult {
 | 
			
		||||
    pub success: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Result of getting job logs
 | 
			
		||||
/** Result of getting job logs */
 | 
			
		||||
#[derive(Debug, Serialize, Deserialize, Clone)]
 | 
			
		||||
pub struct JobLogsResult {
 | 
			
		||||
    pub logs: String,
 | 
			
		||||
    pub logs: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -204,13 +204,13 @@ async fn test_list_jobs() {
 | 
			
		||||
    let result = server.list_jobs().await;
 | 
			
		||||
    assert!(result.is_ok());
 | 
			
		||||
    
 | 
			
		||||
    let jobs = result.unwrap();
 | 
			
		||||
    assert!(jobs.len() >= 3); // Should have at least the 3 jobs we created
 | 
			
		||||
    let job_ids = result.unwrap();
 | 
			
		||||
    assert!(job_ids.len() >= 3); // Should have at least the 3 jobs we created
 | 
			
		||||
    
 | 
			
		||||
    // Verify job structure
 | 
			
		||||
    for job in jobs {
 | 
			
		||||
        assert!(!job.id.is_empty());
 | 
			
		||||
        assert!(uuid::Uuid::parse_str(&job.id).is_ok());
 | 
			
		||||
    // Verify job IDs are valid UUIDs
 | 
			
		||||
    for id in job_ids {
 | 
			
		||||
        assert!(!id.is_empty());
 | 
			
		||||
        assert!(uuid::Uuid::parse_str(&id).is_ok());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -337,7 +337,10 @@ async fn test_get_job_logs() {
 | 
			
		||||
    assert!(result.is_ok());
 | 
			
		||||
    
 | 
			
		||||
    let logs_result = result.unwrap();
 | 
			
		||||
    assert!(!logs_result.logs.is_empty());
 | 
			
		||||
    match logs_result.logs {
 | 
			
		||||
        Some(ref logs) => assert!(!logs.is_empty()),
 | 
			
		||||
        None => {} // acceptable when no logs are available
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[tokio::test]
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user