gglib_core/services/
server_service.rs

1//! Server service - orchestrates server lifecycle operations.
2
3use crate::ports::{CoreError, ProcessHandle, ProcessRunner, ServerConfig, ServerHealth};
4use std::sync::Arc;
5
6/// Service for managing model server processes.
7pub struct ServerService {
8    runner: Arc<dyn ProcessRunner>,
9}
10
11impl ServerService {
12    /// Create a new server service.
13    pub fn new(runner: Arc<dyn ProcessRunner>) -> Self {
14        Self { runner }
15    }
16
17    /// Start a model server.
18    pub async fn start(&self, config: ServerConfig) -> Result<ProcessHandle, CoreError> {
19        self.runner.start(config).await.map_err(CoreError::from)
20    }
21
22    /// Stop a model server.
23    pub async fn stop(&self, handle: &ProcessHandle) -> Result<(), CoreError> {
24        self.runner.stop(handle).await.map_err(CoreError::from)
25    }
26
27    /// Check if a server is still running.
28    pub async fn is_running(&self, handle: &ProcessHandle) -> bool {
29        self.runner.is_running(handle).await
30    }
31
32    /// Get health status of a server.
33    pub async fn health(&self, handle: &ProcessHandle) -> Result<ServerHealth, CoreError> {
34        self.runner.health(handle).await.map_err(CoreError::from)
35    }
36
37    /// List all running server handles.
38    pub async fn list_running(&self) -> Result<Vec<ProcessHandle>, CoreError> {
39        self.runner.list_running().await.map_err(CoreError::from)
40    }
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use crate::ports::{ProcessError, ProcessRunner};
47    use async_trait::async_trait;
48    use std::path::PathBuf;
49    use std::sync::Mutex;
50
51    struct MockRunner {
52        handles: Mutex<Vec<ProcessHandle>>,
53        next_port: Mutex<u16>,
54    }
55
56    impl MockRunner {
57        fn new(base_port: u16) -> Self {
58            Self {
59                handles: Mutex::new(vec![]),
60                next_port: Mutex::new(base_port),
61            }
62        }
63    }
64
65    #[async_trait]
66    impl ProcessRunner for MockRunner {
67        async fn start(&self, config: ServerConfig) -> Result<ProcessHandle, ProcessError> {
68            let port = {
69                let mut next = self.next_port.lock().unwrap();
70                let port = *next;
71                *next += 1;
72                port
73            };
74            let handle = ProcessHandle::new(
75                config.model_id,
76                config.model_name,
77                Some(12345),
78                port,
79                0, // started_at
80            );
81            self.handles.lock().unwrap().push(handle.clone());
82            Ok(handle)
83        }
84
85        async fn stop(&self, handle: &ProcessHandle) -> Result<(), ProcessError> {
86            self.handles
87                .lock()
88                .unwrap()
89                .retain(|h| h.port != handle.port);
90            Ok(())
91        }
92
93        async fn is_running(&self, handle: &ProcessHandle) -> bool {
94            self.handles
95                .lock()
96                .unwrap()
97                .iter()
98                .any(|h| h.port == handle.port)
99        }
100
101        async fn health(&self, handle: &ProcessHandle) -> Result<ServerHealth, ProcessError> {
102            let handles = self.handles.lock().unwrap();
103            if handles.iter().any(|h| h.port == handle.port) {
104                Ok(ServerHealth::healthy())
105            } else {
106                Err(ProcessError::NotRunning(format!("port={}", handle.port)))
107            }
108        }
109
110        async fn list_running(&self) -> Result<Vec<ProcessHandle>, ProcessError> {
111            Ok(self.handles.lock().unwrap().clone())
112        }
113    }
114
115    #[tokio::test]
116    async fn test_start_and_stop() {
117        let runner = Arc::new(MockRunner::new(9000));
118        let service = ServerService::new(runner);
119
120        let config = ServerConfig::new(
121            1,
122            "test-model".to_string(),
123            PathBuf::from("/path/to/model.gguf"),
124            9000,
125        );
126
127        let handle = service.start(config).await.unwrap();
128        assert_eq!(handle.port, 9000);
129
130        let running = service.list_running().await.unwrap();
131        assert_eq!(running.len(), 1);
132
133        service.stop(&handle).await.unwrap();
134        let running = service.list_running().await.unwrap();
135        assert!(running.is_empty());
136    }
137}