//! Container runtime abstraction trait use async_trait::async_trait; use chrono::{DateTime, Utc}; use shared_types::{ContainerBasicInfo, ServiceResourceLimits, ServiceType}; use thiserror::Error; /// Container runtime errors #[derive(Error, Debug)] pub enum ContainerRuntimeError { #[error("Connection error: {0}")] ConnectionError(String), #[error("Container creation failed: {0}")] ContainerCreationError(String), #[error("Container start failed: {0}")] ContainerStartError(String), #[error("Container stop failed: {0}")] ContainerStopError(String), #[error("Container not found: {0}")] ContainerNotFound(String), #[error("Configuration error: {0}")] ConfigurationError(String), #[error("Timeout: {0}")] Timeout(String), #[error("Kubernetes error: {0}")] K8sError(String), #[error("Docker error: {0}")] DockerError(String), } /// Result type for container operations pub type ContainerRuntimeResult = Result; /// Container runtime status #[derive(Debug, Clone, PartialEq)] pub enum ContainerRuntimeStatus { Pending, Running, Succeeded, Failed, Unknown(String), } /// Basic container info returned by runtime #[derive(Debug, Clone)] pub struct RuntimeContainerInfo { pub container_id: String, pub container_name: String, pub container_ip: String, pub status: ContainerRuntimeStatus, pub created_at: DateTime, } /// 已被移除的容器信息(用于清理关联资源) #[derive(Debug, Clone)] pub struct RemovedContainerInfo { /// 容器名称(稳定标识符) pub container_name: String, /// 容器 IP pub container_ip: String, /// 容器标识符(project_id 或 user_id) pub identifier: String, /// 服务类型 pub service_type: ServiceType, } impl From for String { fn from(status: ContainerRuntimeStatus) -> Self { match status { ContainerRuntimeStatus::Pending => "pending".to_string(), ContainerRuntimeStatus::Running => "running".to_string(), ContainerRuntimeStatus::Succeeded => "succeeded".to_string(), ContainerRuntimeStatus::Failed => "failed".to_string(), ContainerRuntimeStatus::Unknown(s) => s, } } } /// Parameters for creating a container /// /// Bundles all parameters needed for container creation to avoid /// long parameter lists that hurt code readability and maintainability. #[derive(Debug, Clone)] pub struct ContainerCreateParams { /// Project identifier (used as container name base for RCoder service) pub project_id: Option, /// User identifier (used as container name base for ComputerAgentRunner) pub user_id: Option, /// Workspace path on host pub host_workspace_path: String, /// Service type determining container purpose pub service_type: ServiceType, /// Optional resource constraints pub resource_limits: Option, /// Pod identifier for container reuse (for multi-tenant scenarios) pub pod_id: Option, /// Isolation type: tenant|space|project (for multi-tenant scenarios) pub isolation_type: Option, /// Tenant identifier (for multi-tenant scenarios) pub tenant_id: Option, /// Space identifier (for multi-tenant scenarios) pub space_id: Option, } impl ContainerCreateParams { /// Create a new builder for container create params pub fn builder() -> ContainerCreateParamsBuilder { ContainerCreateParamsBuilder::default() } } #[derive(Debug, Clone, Default)] pub struct ContainerCreateParamsBuilder { project_id: Option, user_id: Option, host_workspace_path: Option, service_type: Option, resource_limits: Option, pod_id: Option, isolation_type: Option, tenant_id: Option, space_id: Option, } impl ContainerCreateParamsBuilder { pub fn project_id(mut self, project_id: impl Into) -> Self { self.project_id = Some(project_id.into()); self } pub fn user_id(mut self, user_id: impl Into) -> Self { self.user_id = Some(user_id.into()); self } pub fn host_workspace_path(mut self, host_workspace_path: impl Into) -> Self { self.host_workspace_path = Some(host_workspace_path.into()); self } pub fn service_type(mut self, service_type: ServiceType) -> Self { self.service_type = Some(service_type); self } pub fn resource_limits(mut self, resource_limits: ServiceResourceLimits) -> Self { self.resource_limits = Some(resource_limits); self } pub fn pod_id(mut self, pod_id: impl Into) -> Self { self.pod_id = Some(pod_id.into()); self } pub fn isolation_type(mut self, isolation_type: impl Into) -> Self { self.isolation_type = Some(isolation_type.into()); self } pub fn tenant_id(mut self, tenant_id: impl Into) -> Self { self.tenant_id = Some(tenant_id.into()); self } pub fn space_id(mut self, space_id: impl Into) -> Self { self.space_id = Some(space_id.into()); self } pub fn build(self) -> ContainerCreateParams { ContainerCreateParams { project_id: self.project_id, user_id: self.user_id, host_workspace_path: self .host_workspace_path .unwrap_or_else(|| String::new()), service_type: self.service_type.unwrap_or(ServiceType::RCoder), resource_limits: self.resource_limits, pod_id: self.pod_id, isolation_type: self.isolation_type, tenant_id: self.tenant_id, space_id: self.space_id, } } } /// Abstraction trait for container runtimes (Docker, Kubernetes, etc.) /// /// This trait follows the Interface Segregation Principle - it provides /// a lean interface with only the methods that callers actually need. #[async_trait] pub trait ContainerRuntime: Send + Sync { /// Create and start a container async fn create_container( &self, params: ContainerCreateParams, ) -> ContainerRuntimeResult; /// Get container information by project_id async fn get_container_info( &self, project_id: &str, ) -> ContainerRuntimeResult>; /// Get container information by identifier + service type. /// /// `identifier` means: /// - RCoder: `project_id` /// - ComputerAgentRunner: `user_id` async fn get_container_info_by_identifier( &self, identifier: &str, service_type: &ServiceType, ) -> ContainerRuntimeResult> { if matches!(service_type, ServiceType::RCoder) { return self.get_container_info(identifier).await; } let info = self.find_container(identifier, service_type).await?; Ok(info.map(|pod| ContainerBasicInfo { container_id: pod.container_id, container_name: pod.container_name, container_ip: pod.container_ip.clone(), internal_port: shared_types::GRPC_DEFAULT_PORT, external_port: 0, project_id: identifier.to_string(), status: String::from(pod.status), created_at: pod.created_at, service_url: format!( "http://{}:{}", pod.container_ip, shared_types::GRPC_DEFAULT_PORT ), })) } /// Find container by project_id (returns None if not running) async fn find_container( &self, project_id: &str, service_type: &ServiceType, ) -> ContainerRuntimeResult>; /// Stop and remove container async fn stop_container(&self, project_id: &str) -> ContainerRuntimeResult<()>; /// Stop and remove container by identifier + service type. /// /// `identifier` means: /// - RCoder: `project_id` /// - ComputerAgentRunner: `user_id` async fn stop_container_by_identifier( &self, identifier: &str, _service_type: &ServiceType, ) -> ContainerRuntimeResult<()> { self.stop_container(identifier).await } /// Get container status async fn is_container_running(&self, project_id: &str) -> ContainerRuntimeResult; /// Get container status by identifier + service type. async fn is_container_running_by_identifier( &self, identifier: &str, service_type: &ServiceType, ) -> ContainerRuntimeResult { Ok(self .find_container(identifier, service_type) .await? .map(|c| c.status == ContainerRuntimeStatus::Running) .unwrap_or(false)) } /// List all containers managed by this runtime async fn list_containers(&self) -> ContainerRuntimeResult>; /// 同步缓存状态,清理失效的容器记录 /// /// 对于 Docker:遍历 ContainerStateActor 缓存,通过 Docker API 验证容器是否仍存在 /// 对于 K8s:遍历 pod_cache,通过 K8s API 验证 Pod 是否仍存在 /// /// # Returns /// 返回元组 (已检查数量, 已移除容器信息列表) async fn sync_states(&self) -> ContainerRuntimeResult<(u32, Vec)> { // 默认实现:不做任何事(向后兼容) Ok((0, Vec::new())) } /// Cleanup all containers (used on shutdown) async fn cleanup_all(&self) -> ContainerRuntimeResult<()>; /// Health check - verify runtime is accessible async fn health_check(&self) -> ContainerRuntimeResult<()>; }