- Add a new service manager crate for dynamic service management - Integrate service manager with Rhai for scripting - Provide examples for circle worker management and basic usage - Add comprehensive tests for service lifecycle and error handling - Implement cross-platform support for macOS and Linux (zinit/systemd)
318 lines
12 KiB
Rust
318 lines
12 KiB
Rust
use sal_service_manager::{
|
|
ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus, ZinitServiceManager,
|
|
};
|
|
use std::collections::HashMap;
|
|
use std::time::Duration;
|
|
use tokio::time::sleep;
|
|
|
|
/// Helper function to find an available Zinit socket path
|
|
async fn get_available_socket_path() -> Option<String> {
|
|
let socket_paths = [
|
|
"/var/run/zinit.sock",
|
|
"/tmp/zinit.sock",
|
|
"/run/zinit.sock",
|
|
"./zinit.sock",
|
|
];
|
|
|
|
for path in &socket_paths {
|
|
// Try to create a ZinitServiceManager to test connectivity
|
|
if let Ok(manager) = ZinitServiceManager::new(path) {
|
|
// Test if we can list services (basic connectivity test)
|
|
if manager.list().is_ok() {
|
|
println!("✓ Found working Zinit socket at: {}", path);
|
|
return Some(path.to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
/// Helper function to clean up test services
|
|
async fn cleanup_test_service(manager: &dyn ServiceManager, service_name: &str) {
|
|
let _ = manager.stop(service_name);
|
|
let _ = manager.remove(service_name);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_zinit_service_manager_creation() {
|
|
if let Some(socket_path) = get_available_socket_path().await {
|
|
let manager = ZinitServiceManager::new(&socket_path);
|
|
assert!(
|
|
manager.is_ok(),
|
|
"Should be able to create ZinitServiceManager"
|
|
);
|
|
|
|
let manager = manager.unwrap();
|
|
|
|
// Test basic connectivity by listing services
|
|
let list_result = manager.list();
|
|
assert!(list_result.is_ok(), "Should be able to list services");
|
|
|
|
println!("✓ ZinitServiceManager created successfully");
|
|
} else {
|
|
println!("⚠ Skipping test_zinit_service_manager_creation: No Zinit socket available");
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_service_lifecycle() {
|
|
if let Some(socket_path) = get_available_socket_path().await {
|
|
let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager");
|
|
let service_name = "test-lifecycle-service";
|
|
|
|
// Clean up any existing service first
|
|
cleanup_test_service(&manager, service_name).await;
|
|
|
|
let config = ServiceConfig {
|
|
name: service_name.to_string(),
|
|
binary_path: "echo".to_string(),
|
|
args: vec!["Hello from lifecycle test".to_string()],
|
|
working_directory: Some("/tmp".to_string()),
|
|
environment: HashMap::new(),
|
|
auto_restart: false,
|
|
};
|
|
|
|
// Test service creation and start
|
|
println!("Testing service creation and start...");
|
|
let start_result = manager.start(&config);
|
|
match start_result {
|
|
Ok(_) => {
|
|
println!("✓ Service started successfully");
|
|
|
|
// Wait a bit for the service to run
|
|
sleep(Duration::from_millis(500)).await;
|
|
|
|
// Test service exists
|
|
let exists = manager.exists(service_name);
|
|
assert!(exists.is_ok(), "Should be able to check if service exists");
|
|
|
|
if let Ok(true) = exists {
|
|
println!("✓ Service exists check passed");
|
|
|
|
// Test service status
|
|
let status_result = manager.status(service_name);
|
|
match status_result {
|
|
Ok(status) => {
|
|
println!("✓ Service status: {:?}", status);
|
|
assert!(
|
|
matches!(status, ServiceStatus::Running | ServiceStatus::Stopped),
|
|
"Service should be running or stopped (for oneshot)"
|
|
);
|
|
}
|
|
Err(e) => println!("⚠ Status check failed: {}", e),
|
|
}
|
|
|
|
// Test service logs
|
|
let logs_result = manager.logs(service_name, None);
|
|
match logs_result {
|
|
Ok(logs) => {
|
|
println!("✓ Retrieved logs: {}", logs.len());
|
|
// For echo command, we should have some output
|
|
assert!(
|
|
!logs.is_empty() || logs.contains("Hello"),
|
|
"Should have log output"
|
|
);
|
|
}
|
|
Err(e) => println!("⚠ Logs retrieval failed: {}", e),
|
|
}
|
|
|
|
// Test service list
|
|
let list_result = manager.list();
|
|
match list_result {
|
|
Ok(services) => {
|
|
println!("✓ Listed {} services", services.len());
|
|
assert!(
|
|
services.contains(&service_name.to_string()),
|
|
"Service should appear in list"
|
|
);
|
|
}
|
|
Err(e) => println!("⚠ List services failed: {}", e),
|
|
}
|
|
}
|
|
|
|
// Test service stop
|
|
println!("Testing service stop...");
|
|
let stop_result = manager.stop(service_name);
|
|
match stop_result {
|
|
Ok(_) => println!("✓ Service stopped successfully"),
|
|
Err(e) => println!("⚠ Stop failed: {}", e),
|
|
}
|
|
|
|
// Test service removal
|
|
println!("Testing service removal...");
|
|
let remove_result = manager.remove(service_name);
|
|
match remove_result {
|
|
Ok(_) => println!("✓ Service removed successfully"),
|
|
Err(e) => println!("⚠ Remove failed: {}", e),
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!("⚠ Service creation/start failed: {}", e);
|
|
// This might be expected if zinit doesn't allow service creation
|
|
}
|
|
}
|
|
|
|
// Final cleanup
|
|
cleanup_test_service(&manager, service_name).await;
|
|
} else {
|
|
println!("⚠ Skipping test_service_lifecycle: No Zinit socket available");
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_service_start_and_confirm() {
|
|
if let Some(socket_path) = get_available_socket_path().await {
|
|
let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager");
|
|
let service_name = "test-start-confirm-service";
|
|
|
|
// Clean up any existing service first
|
|
cleanup_test_service(&manager, service_name).await;
|
|
|
|
let config = ServiceConfig {
|
|
name: service_name.to_string(),
|
|
binary_path: "sleep".to_string(),
|
|
args: vec!["5".to_string()], // Sleep for 5 seconds
|
|
working_directory: Some("/tmp".to_string()),
|
|
environment: HashMap::new(),
|
|
auto_restart: false,
|
|
};
|
|
|
|
// Test start_and_confirm with timeout
|
|
println!("Testing start_and_confirm with timeout...");
|
|
let start_result = manager.start_and_confirm(&config, 10);
|
|
match start_result {
|
|
Ok(_) => {
|
|
println!("✓ Service started and confirmed successfully");
|
|
|
|
// Verify it's actually running
|
|
let status = manager.status(service_name);
|
|
match status {
|
|
Ok(ServiceStatus::Running) => println!("✓ Service confirmed running"),
|
|
Ok(other_status) => {
|
|
println!("⚠ Service in unexpected state: {:?}", other_status)
|
|
}
|
|
Err(e) => println!("⚠ Status check failed: {}", e),
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!("⚠ start_and_confirm failed: {}", e);
|
|
}
|
|
}
|
|
|
|
// Test start_existing_and_confirm
|
|
println!("Testing start_existing_and_confirm...");
|
|
let start_existing_result = manager.start_existing_and_confirm(service_name, 5);
|
|
match start_existing_result {
|
|
Ok(_) => println!("✓ start_existing_and_confirm succeeded"),
|
|
Err(e) => println!("⚠ start_existing_and_confirm failed: {}", e),
|
|
}
|
|
|
|
// Cleanup
|
|
cleanup_test_service(&manager, service_name).await;
|
|
} else {
|
|
println!("⚠ Skipping test_service_start_and_confirm: No Zinit socket available");
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_service_restart() {
|
|
if let Some(socket_path) = get_available_socket_path().await {
|
|
let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager");
|
|
let service_name = "test-restart-service";
|
|
|
|
// Clean up any existing service first
|
|
cleanup_test_service(&manager, service_name).await;
|
|
|
|
let config = ServiceConfig {
|
|
name: service_name.to_string(),
|
|
binary_path: "echo".to_string(),
|
|
args: vec!["Restart test".to_string()],
|
|
working_directory: Some("/tmp".to_string()),
|
|
environment: HashMap::new(),
|
|
auto_restart: true, // Enable auto-restart for this test
|
|
};
|
|
|
|
// Start the service first
|
|
let start_result = manager.start(&config);
|
|
if start_result.is_ok() {
|
|
// Wait for service to be established
|
|
sleep(Duration::from_millis(1000)).await;
|
|
|
|
// Test restart
|
|
println!("Testing service restart...");
|
|
let restart_result = manager.restart(service_name);
|
|
match restart_result {
|
|
Ok(_) => {
|
|
println!("✓ Service restarted successfully");
|
|
|
|
// Wait and check status
|
|
sleep(Duration::from_millis(500)).await;
|
|
|
|
let status_result = manager.status(service_name);
|
|
match status_result {
|
|
Ok(status) => {
|
|
println!("✓ Service state after restart: {:?}", status);
|
|
}
|
|
Err(e) => println!("⚠ Status check after restart failed: {}", e),
|
|
}
|
|
}
|
|
Err(e) => {
|
|
println!("⚠ Restart failed: {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Cleanup
|
|
cleanup_test_service(&manager, service_name).await;
|
|
} else {
|
|
println!("⚠ Skipping test_service_restart: No Zinit socket available");
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_error_handling() {
|
|
if let Some(socket_path) = get_available_socket_path().await {
|
|
let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager");
|
|
|
|
// Test operations on non-existent service
|
|
let non_existent_service = "non-existent-service-12345";
|
|
|
|
// Test status of non-existent service
|
|
let status_result = manager.status(non_existent_service);
|
|
match status_result {
|
|
Err(ServiceManagerError::ServiceNotFound(_)) => {
|
|
println!("✓ Correctly returned ServiceNotFound for non-existent service");
|
|
}
|
|
Err(other_error) => {
|
|
println!(
|
|
"⚠ Got different error for non-existent service: {}",
|
|
other_error
|
|
);
|
|
}
|
|
Ok(_) => {
|
|
println!("⚠ Unexpectedly found non-existent service");
|
|
}
|
|
}
|
|
|
|
// Test exists for non-existent service
|
|
let exists_result = manager.exists(non_existent_service);
|
|
match exists_result {
|
|
Ok(false) => println!("✓ Correctly reported non-existent service as not existing"),
|
|
Ok(true) => println!("⚠ Incorrectly reported non-existent service as existing"),
|
|
Err(e) => println!("⚠ Error checking existence: {}", e),
|
|
}
|
|
|
|
// Test stop of non-existent service
|
|
let stop_result = manager.stop(non_existent_service);
|
|
match stop_result {
|
|
Err(_) => println!("✓ Correctly failed to stop non-existent service"),
|
|
Ok(_) => println!("⚠ Unexpectedly succeeded in stopping non-existent service"),
|
|
}
|
|
|
|
println!("✓ Error handling tests completed");
|
|
} else {
|
|
println!("⚠ Skipping test_error_handling: No Zinit socket available");
|
|
}
|
|
}
|