From c93e01b0051971499d375ae6e8591eb5a9c62f3c Mon Sep 17 00:00:00 2001 From: KennyMcCormick Date: Tue, 26 Nov 2024 20:33:40 +0800 Subject: [PATCH] support docker runtime in dfinit with ut (#868) Signed-off-by: cormick --- Cargo.lock | 1 + Cargo.toml | 1 + dragonfly-client-init/Cargo.toml | 1 + .../src/container_runtime/docker.rs | 206 +++++++++++++++++- dragonfly-client/Cargo.toml | 2 +- 5 files changed, 206 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee2c907c..ca3544ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1016,6 +1016,7 @@ dependencies = [ "dragonfly-client", "dragonfly-client-config", "dragonfly-client-core", + "serde_json", "tempfile", "tokio", "toml", diff --git a/Cargo.toml b/Cargo.toml index 98fa9600..95e46bf5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,7 @@ bytesize-serde = "0.2.1" percent-encoding = "2.3.1" tempfile = "3.14.0" tokio-rustls = "0.25.0-alpha.4" +serde_json = "1.0.132" [profile.release] opt-level = "z" diff --git a/dragonfly-client-init/Cargo.toml b/dragonfly-client-init/Cargo.toml index 31cefc69..59a9f208 100644 --- a/dragonfly-client-init/Cargo.toml +++ b/dragonfly-client-init/Cargo.toml @@ -26,3 +26,4 @@ toml_edit.workspace = true toml.workspace = true url.workspace = true tempfile.workspace = true +serde_json.workspace = true diff --git a/dragonfly-client-init/src/container_runtime/docker.rs b/dragonfly-client-init/src/container_runtime/docker.rs index 2c2a884d..50607615 100644 --- a/dragonfly-client-init/src/container_runtime/docker.rs +++ b/dragonfly-client-init/src/container_runtime/docker.rs @@ -15,8 +15,14 @@ */ use dragonfly_client_config::dfinit; -use dragonfly_client_core::{Error, Result}; +use dragonfly_client_core::{ + error::{ErrorType, OrErr}, + Error, Result, +}; +use serde_json::{json, Value}; +use tokio::{self, fs}; use tracing::{info, instrument}; +use url::Url; /// Docker represents the docker runtime manager. #[derive(Debug, Clone)] @@ -40,8 +46,6 @@ impl Docker { } } - /// TODO: Implement the run method for Docker. - /// /// run runs the docker runtime to initialize /// runtime environment for the dfdaemon. #[instrument(skip_all)] @@ -50,6 +54,200 @@ impl Docker { "docker feature is enabled, proxy_addr: {}, config_path: {:?}", self.proxy_config.addr, self.config.config_path, ); - Err(Error::Unimplemented) + + // Parse proxy address to get host and port. + let proxy_url = Url::parse(&self.proxy_config.addr).or_err(ErrorType::ParseError)?; + let proxy_host = proxy_url + .host_str() + .ok_or(Error::Unknown("host not found".to_string()))?; + let proxy_port = proxy_url + .port_or_known_default() + .ok_or(Error::Unknown("port not found".to_string()))?; + let proxy_location = format!("{}:{}", proxy_host, proxy_port); + + // Prepare proxies configuration. + let mut proxies_map = serde_json::Map::new(); + proxies_map.insert( + "http-proxy".to_string(), + json!(format!("http://{}", proxy_location)), + ); + proxies_map.insert( + "https-proxy".to_string(), + json!(format!("http://{}", proxy_location)), + ); + + let config_path = &self.config.config_path; + let mut docker_config: serde_json::Map = if config_path.exists() { + let contents = fs::read_to_string(config_path).await?; + if contents.trim().is_empty() { + serde_json::Map::new() + } else { + serde_json::from_str(&contents).or_err(ErrorType::ParseError)? + } + } else { + serde_json::Map::new() + }; + + // Insert or update proxies configuration. + docker_config.insert("proxies".to_string(), Value::Object(proxies_map)); + + // Create config directory if it doesn't exist. + let config_dir = config_path + .parent() + .ok_or(Error::Unknown("invalid config path".to_string()))?; + fs::create_dir_all(config_dir).await?; + + // Write configuration to file. + fs::write( + config_path, + serde_json::to_string_pretty(&Value::Object(docker_config)) + .or_err(ErrorType::SerializeError)?, + ) + .await?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::NamedTempFile; + use tokio::fs; + + #[tokio::test] + async fn test_docker_config_empty() { + let docker_config_file = NamedTempFile::new().unwrap(); + let docker = Docker::new( + dfinit::Docker { + config_path: docker_config_file.path().to_path_buf(), + }, + dfinit::Proxy { + addr: "http://127.0.0.1:5000".into(), + }, + ); + + let result = docker.run().await; + println!("{:?}", result); + assert!(result.is_ok()); + + // Read and verify configuration. + let contents = fs::read_to_string(docker_config_file.path()).await.unwrap(); + let config: serde_json::Value = serde_json::from_str(&contents).unwrap(); + + // Verify proxies configuration. + assert_eq!(config["proxies"]["http-proxy"], "http://127.0.0.1:5000"); + assert_eq!(config["proxies"]["https-proxy"], "http://127.0.0.1:5000"); + } + + #[tokio::test] + async fn test_docker_config_existing() { + let docker_config_file = NamedTempFile::new().unwrap(); + let initial_config = r#" + { + "log-driver": "json-file", + "experimental": true + } + "#; + fs::write(docker_config_file.path(), initial_config) + .await + .unwrap(); + + let docker = Docker::new( + dfinit::Docker { + config_path: docker_config_file.path().to_path_buf(), + }, + dfinit::Proxy { + addr: "http://127.0.0.1:5000".into(), + }, + ); + + let result = docker.run().await; + assert!(result.is_ok()); + + // Read and verify configuration. + let contents = fs::read_to_string(docker_config_file.path()).await.unwrap(); + let config: serde_json::Value = serde_json::from_str(&contents).unwrap(); + + // Verify existing configurations. + assert_eq!(config["log-driver"], "json-file"); + assert_eq!(config["experimental"], true); + + // Verify proxies configuration. + assert_eq!(config["proxies"]["http-proxy"], "http://127.0.0.1:5000"); + assert_eq!(config["proxies"]["https-proxy"], "http://127.0.0.1:5000"); + } + + #[tokio::test] + async fn test_docker_config_invalid_json() { + let docker_config_file = NamedTempFile::new().unwrap(); + let invalid_config = r#" + { + "log-driver": "json-file", + "experimental": true, + } + "#; + fs::write(docker_config_file.path(), invalid_config) + .await + .unwrap(); + + let docker = Docker::new( + dfinit::Docker { + config_path: docker_config_file.path().to_path_buf(), + }, + dfinit::Proxy { + addr: "http://127.0.0.1:5000".into(), + }, + ); + + let result = docker.run().await; + assert!(result.is_err()); + if let Err(e) = result { + assert_eq!( + format!("{}", e), + "ParseError cause: trailing comma at line 5 column 9" + ); + } + } + + #[tokio::test] + async fn test_docker_config_proxies_existing() { + let docker_config_file = NamedTempFile::new().unwrap(); + let existing_proxies = r#" + { + "proxies": { + "http-proxy": "http://old-proxy:3128", + "https-proxy": "https://old-proxy:3129", + "no-proxy": "old-no-proxy" + }, + "log-driver": "json-file" + } + "#; + fs::write(docker_config_file.path(), existing_proxies) + .await + .unwrap(); + + let docker = Docker::new( + dfinit::Docker { + config_path: docker_config_file.path().to_path_buf(), + }, + dfinit::Proxy { + addr: "http://127.0.0.1:5000".into(), + }, + ); + + let result = docker.run().await; + assert!(result.is_ok()); + + // Read and verify configuration. + let contents = fs::read_to_string(docker_config_file.path()).await.unwrap(); + let config: serde_json::Value = serde_json::from_str(&contents).unwrap(); + + // Verify existing configurations. + assert_eq!(config["log-driver"], "json-file"); + + // Verify proxies configuration. + assert_eq!(config["proxies"]["http-proxy"], "http://127.0.0.1:5000"); + assert_eq!(config["proxies"]["https-proxy"], "http://127.0.0.1:5000"); } } diff --git a/dragonfly-client/Cargo.toml b/dragonfly-client/Cargo.toml index cdd73a7a..5dc7e659 100644 --- a/dragonfly-client/Cargo.toml +++ b/dragonfly-client/Cargo.toml @@ -62,8 +62,8 @@ bytesize.workspace = true uuid.workspace = true percent-encoding.workspace = true tokio-rustls.workspace = true +serde_json.workspace = true lazy_static = "1.5" -serde_json = "1.0" tracing-log = "0.2" tracing-subscriber = { version = "0.3", features = ["env-filter", "time", "chrono"] } tracing-appender = "0.2.3"