From e6f7ddd9a2ba6eaeadf6b7a8fd09777bb46c1982 Mon Sep 17 00:00:00 2001 From: "James O. D. Hunt" Date: Thu, 15 Oct 2020 17:30:22 +0100 Subject: [PATCH] tools: Make agent-ctl support more APIs Added new `agent-ctl` commands to allow the following agent API calls to be made: - `AddARPNeighborsRequest` - `CloseStdinRequest` - `CopyFileRequest` - `GetMetricsRequest` - `GetOOMEventRequest` - `MemHotplugByProbeRequest` - `OnlineCPUMemRequest` - `ReadStreamRequest` - `ReseedRandomDevRequest` - `SetGuestDateTimeRequest` - `TtyWinResizeRequest` - `UpdateContainerRequest` - `WriteStreamRequest` Fixes: #969. Signed-off-by: James O. D. Hunt --- tools/agent-ctl/Cargo.lock | 10 +- tools/agent-ctl/Cargo.toml | 2 + tools/agent-ctl/src/client.rs | 593 ++++++++++++++++++++++++++++++++++ tools/agent-ctl/src/utils.rs | 14 + 4 files changed, 618 insertions(+), 1 deletion(-) diff --git a/tools/agent-ctl/Cargo.lock b/tools/agent-ctl/Cargo.lock index 897425ab5c..1d37399fa0 100644 --- a/tools/agent-ctl/Cargo.lock +++ b/tools/agent-ctl/Cargo.lock @@ -257,6 +257,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + [[package]] name = "humantime" version = "2.0.1" @@ -274,7 +280,9 @@ name = "kata-agent-ctl" version = "0.0.1" dependencies = [ "anyhow", + "byteorder", "clap", + "hex", "humantime", "lazy_static", "libc", @@ -435,7 +443,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "059a34f111a9dee2ce1ac2826a68b24601c4298cfeb1a587c3cb493d5ab46f52" dependencies = [ "libc", - "nix 0.17.0", + "nix 0.18.0", ] [[package]] diff --git a/tools/agent-ctl/Cargo.toml b/tools/agent-ctl/Cargo.toml index fcaa98a71f..ae8a752dbc 100644 --- a/tools/agent-ctl/Cargo.toml +++ b/tools/agent-ctl/Cargo.toml @@ -17,6 +17,8 @@ oci = { path = "../../src/agent/oci" } clap = "2.33.0" lazy_static = "1.4.0" anyhow = "1.0.31" +hex = "0.4.2" +byteorder = "1.3.4" logging = { path = "../../pkg/logging" } slog = "2.5.2" diff --git a/tools/agent-ctl/src/client.rs b/tools/agent-ctl/src/client.rs index e2ecb9cead..ac38bcb381 100644 --- a/tools/agent-ctl/src/client.rs +++ b/tools/agent-ctl/src/client.rs @@ -8,6 +8,7 @@ use crate::types::{Config, Options}; use crate::utils; use anyhow::{anyhow, Result}; +use byteorder::ByteOrder; use nix::sys::socket::{connect, socket, AddressFamily, SockAddr, SockFlag, SockType, UnixAddr}; use protocols::agent::*; use protocols::agent_ttrpc::*; @@ -75,6 +76,11 @@ const DEFAULT_PS_FORMAT: &str = "json"; const ERR_API_FAILED: &str = "API failed"; static AGENT_CMDS: &'static [AgentCmd] = &[ + AgentCmd { + name: "AddARPNeighbors", + st: ServiceType::Agent, + fp: agent_cmd_sandbox_add_arp_neighbors, + }, AgentCmd { name: "Check", st: ServiceType::Health, @@ -85,6 +91,16 @@ static AGENT_CMDS: &'static [AgentCmd] = &[ st: ServiceType::Health, fp: agent_cmd_health_version, }, + AgentCmd { + name: "CloseStdin", + st: ServiceType::Agent, + fp: agent_cmd_container_close_stdin, + }, + AgentCmd { + name: "CopyFile", + st: ServiceType::Agent, + fp: agent_cmd_sandbox_copy_file, + }, AgentCmd { name: "CreateContainer", st: ServiceType::Agent, @@ -110,6 +126,16 @@ static AGENT_CMDS: &'static [AgentCmd] = &[ st: ServiceType::Agent, fp: agent_cmd_sandbox_get_guest_details, }, + AgentCmd { + name: "GetMetrics", + st: ServiceType::Agent, + fp: agent_cmd_sandbox_get_metrics, + }, + AgentCmd { + name: "GetOOMEvent", + st: ServiceType::Agent, + fp: agent_cmd_sandbox_get_oom_event, + }, AgentCmd { name: "ListInterfaces", st: ServiceType::Agent, @@ -125,11 +151,36 @@ static AGENT_CMDS: &'static [AgentCmd] = &[ st: ServiceType::Agent, fp: agent_cmd_container_list_processes, }, + AgentCmd { + name: "MemHotplugByProbe", + st: ServiceType::Agent, + fp: agent_cmd_sandbox_mem_hotplug_by_probe, + }, + AgentCmd { + name: "OnlineCPUMem", + st: ServiceType::Agent, + fp: agent_cmd_sandbox_online_cpu_mem, + }, AgentCmd { name: "PauseContainer", st: ServiceType::Agent, fp: agent_cmd_container_pause, }, + AgentCmd { + name: "ReadStderr", + st: ServiceType::Agent, + fp: agent_cmd_container_read_stderr, + }, + AgentCmd { + name: "ReadStdout", + st: ServiceType::Agent, + fp: agent_cmd_container_read_stdout, + }, + AgentCmd { + name: "ReseedRandomDev", + st: ServiceType::Agent, + fp: agent_cmd_sandbox_reseed_random_dev, + }, AgentCmd { name: "RemoveContainer", st: ServiceType::Agent, @@ -140,6 +191,11 @@ static AGENT_CMDS: &'static [AgentCmd] = &[ st: ServiceType::Agent, fp: agent_cmd_container_resume, }, + AgentCmd { + name: "SetGuestDateTime", + st: ServiceType::Agent, + fp: agent_cmd_sandbox_set_guest_date_time, + }, AgentCmd { name: "SignalProcess", st: ServiceType::Agent, @@ -165,6 +221,16 @@ static AGENT_CMDS: &'static [AgentCmd] = &[ st: ServiceType::Agent, fp: agent_cmd_sandbox_tracing_stop, }, + AgentCmd { + name: "TtyWinResize", + st: ServiceType::Agent, + fp: agent_cmd_container_tty_win_resize, + }, + AgentCmd { + name: "UpdateContainer", + st: ServiceType::Agent, + fp: agent_cmd_sandbox_update_container, + }, AgentCmd { name: "UpdateInterface", st: ServiceType::Agent, @@ -180,6 +246,11 @@ static AGENT_CMDS: &'static [AgentCmd] = &[ st: ServiceType::Agent, fp: agent_cmd_container_wait_process, }, + AgentCmd { + name: "WriteStdin", + st: ServiceType::Agent, + fp: agent_cmd_container_write_stdin, + }, ]; static BUILTIN_CMDS: &'static [BuiltinCmd] = &[ @@ -1212,6 +1283,528 @@ fn agent_cmd_sandbox_list_routes( Ok(()) } +fn agent_cmd_container_tty_win_resize( + cfg: &Config, + client: &AgentServiceClient, + _health: &HealthClient, + options: &mut Options, + args: &str, +) -> Result<()> { + let mut req = TtyWinResizeRequest::default(); + + let cid = utils::get_option("cid", options, args); + let exec_id = utils::get_option("exec_id", options, args); + + req.set_container_id(cid); + req.set_exec_id(exec_id); + + let rows_str = utils::get_option("row", options, args); + + if rows_str != "" { + let rows = rows_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid row size"))?; + req.set_row(rows); + } + + let cols_str = utils::get_option("column", options, args); + + if cols_str != "" { + let cols = cols_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid column size"))?; + + req.set_column(cols); + } + + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + + let reply = client + .tty_win_resize(&req, cfg.timeout_nano) + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; + + info!(sl!(), "response received"; + "response" => format!("{:?}", reply)); + + Ok(()) +} + +fn agent_cmd_container_close_stdin( + cfg: &Config, + client: &AgentServiceClient, + _health: &HealthClient, + options: &mut Options, + args: &str, +) -> Result<()> { + let mut req = CloseStdinRequest::default(); + + let cid = utils::get_option("cid", options, args); + let exec_id = utils::get_option("exec_id", options, args); + + req.set_container_id(cid); + req.set_exec_id(exec_id); + + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + + let reply = client + .close_stdin(&req, cfg.timeout_nano) + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; + + info!(sl!(), "response received"; + "response" => format!("{:?}", reply)); + + Ok(()) +} + +fn agent_cmd_container_read_stdout( + cfg: &Config, + client: &AgentServiceClient, + _health: &HealthClient, + options: &mut Options, + args: &str, +) -> Result<()> { + let mut req = ReadStreamRequest::default(); + + let cid = utils::get_option("cid", options, args); + let exec_id = utils::get_option("exec_id", options, args); + + req.set_container_id(cid); + req.set_exec_id(exec_id); + + let length_str = utils::get_option("len", options, args); + + if length_str != "" { + let length = length_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid length"))?; + req.set_len(length); + } + + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + + let reply = client + .read_stdout(&req, cfg.timeout_nano) + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; + + info!(sl!(), "response received"; + "response" => format!("{:?}", reply)); + + Ok(()) +} + +fn agent_cmd_container_read_stderr( + cfg: &Config, + client: &AgentServiceClient, + _health: &HealthClient, + options: &mut Options, + args: &str, +) -> Result<()> { + let mut req = ReadStreamRequest::default(); + + let cid = utils::get_option("cid", options, args); + let exec_id = utils::get_option("exec_id", options, args); + + req.set_container_id(cid); + req.set_exec_id(exec_id); + + let length_str = utils::get_option("len", options, args); + + if length_str != "" { + let length = length_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid length"))?; + req.set_len(length); + } + + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + + let reply = client + .read_stderr(&req, cfg.timeout_nano) + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; + + info!(sl!(), "response received"; + "response" => format!("{:?}", reply)); + + Ok(()) +} + +fn agent_cmd_container_write_stdin( + cfg: &Config, + client: &AgentServiceClient, + _health: &HealthClient, + options: &mut Options, + args: &str, +) -> Result<()> { + let mut req = WriteStreamRequest::default(); + + let cid = utils::get_option("cid", options, args); + let exec_id = utils::get_option("exec_id", options, args); + + let str_data = utils::get_option("data", options, args); + let data = utils::str_to_bytes(&str_data)?; + + req.set_container_id(cid); + req.set_exec_id(exec_id); + req.set_data(data.to_vec()); + + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + + let reply = client + .write_stdin(&req, cfg.timeout_nano) + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; + + info!(sl!(), "response received"; + "response" => format!("{:?}", reply)); + + Ok(()) +} + +fn agent_cmd_sandbox_get_metrics( + cfg: &Config, + client: &AgentServiceClient, + _health: &HealthClient, + _options: &mut Options, + _args: &str, +) -> Result<()> { + let req = GetMetricsRequest::default(); + + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + + let reply = client + .get_metrics(&req, cfg.timeout_nano) + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; + + info!(sl!(), "response received"; + "response" => format!("{:?}", reply)); + + Ok(()) +} + +fn agent_cmd_sandbox_get_oom_event( + cfg: &Config, + client: &AgentServiceClient, + _health: &HealthClient, + _options: &mut Options, + _args: &str, +) -> Result<()> { + let req = GetOOMEventRequest::default(); + + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + + let reply = client + .get_oom_event(&req, cfg.timeout_nano) + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; + + info!(sl!(), "response received"; + "response" => format!("{:?}", reply)); + + Ok(()) +} + +fn agent_cmd_sandbox_copy_file( + cfg: &Config, + client: &AgentServiceClient, + _health: &HealthClient, + options: &mut Options, + args: &str, +) -> Result<()> { + let mut req = CopyFileRequest::default(); + + let path = utils::get_option("path", options, args); + if path != "" { + req.set_path(path); + } + + let file_size_str = utils::get_option("file_size", options, args); + + if file_size_str != "" { + let file_size = file_size_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid file_size"))?; + + req.set_file_size(file_size); + } + + let file_mode_str = utils::get_option("file_mode", options, args); + + if file_mode_str != "" { + let file_mode = file_mode_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid file_mode"))?; + + req.set_file_mode(file_mode); + } + + let dir_mode_str = utils::get_option("dir_mode", options, args); + + if dir_mode_str != "" { + let dir_mode = dir_mode_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid dir_mode"))?; + + req.set_dir_mode(dir_mode); + } + + let uid_str = utils::get_option("uid", options, args); + + if uid_str != "" { + let uid = uid_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid uid"))?; + + req.set_uid(uid); + } + + let gid_str = utils::get_option("gid", options, args); + + if gid_str != "" { + let gid = gid_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid gid"))?; + req.set_gid(gid); + } + + let offset_str = utils::get_option("offset", options, args); + + if offset_str != "" { + let offset = offset_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid offset"))?; + req.set_offset(offset); + } + + let data_str = utils::get_option("data", options, args); + if data_str != "" { + let data = utils::str_to_bytes(&data_str)?; + req.set_data(data.to_vec()); + } + + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + + let reply = client + .copy_file(&req, cfg.timeout_nano) + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; + + info!(sl!(), "response received"; + "response" => format!("{:?}", reply)); + + Ok(()) +} + +fn agent_cmd_sandbox_reseed_random_dev( + cfg: &Config, + client: &AgentServiceClient, + _health: &HealthClient, + options: &mut Options, + args: &str, +) -> Result<()> { + let mut req = ReseedRandomDevRequest::default(); + + let str_data = utils::get_option("data", options, args); + let data = utils::str_to_bytes(&str_data)?; + + req.set_data(data.to_vec()); + + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + + let reply = client + .reseed_random_dev(&req, cfg.timeout_nano) + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; + + info!(sl!(), "response received"; + "response" => format!("{:?}", reply)); + + Ok(()) +} + +fn agent_cmd_sandbox_online_cpu_mem( + cfg: &Config, + client: &AgentServiceClient, + _health: &HealthClient, + options: &mut Options, + args: &str, +) -> Result<()> { + let mut req = OnlineCPUMemRequest::default(); + + let wait_str = utils::get_option("wait", options, args); + + if wait_str != "" { + let wait = wait_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid wait bool"))?; + + req.set_wait(wait); + } + + let nb_cpus_str = utils::get_option("nb_cpus", options, args); + + if nb_cpus_str != "" { + let nb_cpus = nb_cpus_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid nb_cpus value"))?; + + req.set_nb_cpus(nb_cpus); + } + + let cpu_only_str = utils::get_option("cpu_only", options, args); + + if cpu_only_str != "" { + let cpu_only = cpu_only_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid cpu_only bool"))?; + + req.set_cpu_only(cpu_only); + } + + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + + let reply = client + .online_cpu_mem(&req, cfg.timeout_nano) + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; + + info!(sl!(), "response received"; + "response" => format!("{:?}", reply)); + + Ok(()) +} + +fn agent_cmd_sandbox_set_guest_date_time( + cfg: &Config, + client: &AgentServiceClient, + _health: &HealthClient, + options: &mut Options, + args: &str, +) -> Result<()> { + let mut req = SetGuestDateTimeRequest::default(); + + let secs_str = utils::get_option("sec", options, args); + + if secs_str != "" { + let secs = secs_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid seconds"))?; + + req.set_Sec(secs); + } + + let usecs_str = utils::get_option("usec", options, args); + + if usecs_str != "" { + let usecs = usecs_str + .parse::() + .map_err(|e| anyhow!(e).context("invalid useconds"))?; + + req.set_Usec(usecs); + } + + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + + let reply = client + .set_guest_date_time(&req, cfg.timeout_nano) + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; + + info!(sl!(), "response received"; + "response" => format!("{:?}", reply)); + + Ok(()) +} + +fn agent_cmd_sandbox_add_arp_neighbors( + cfg: &Config, + client: &AgentServiceClient, + _health: &HealthClient, + _options: &mut Options, + _args: &str, +) -> Result<()> { + let req = AddARPNeighborsRequest::default(); + + // FIXME: Implement fully. + eprintln!("FIXME: 'AddARPNeighbors' not fully implemented"); + + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + + let reply = client + .add_arp_neighbors(&req, cfg.timeout_nano) + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; + + info!(sl!(), "response received"; + "response" => format!("{:?}", reply)); + + Ok(()) +} + +fn agent_cmd_sandbox_update_container( + cfg: &Config, + client: &AgentServiceClient, + _health: &HealthClient, + options: &mut Options, + args: &str, +) -> Result<()> { + let mut req = UpdateContainerRequest::default(); + + let cid = utils::get_option("cid", options, args); + + req.set_container_id(cid); + + // FIXME: Implement fully + eprintln!("FIXME: 'UpdateContainer' not fully implemented"); + + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + + let reply = client + .update_container(&req, cfg.timeout_nano) + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; + + info!(sl!(), "response received"; + "response" => format!("{:?}", reply)); + + Ok(()) +} + +fn agent_cmd_sandbox_mem_hotplug_by_probe( + cfg: &Config, + client: &AgentServiceClient, + _health: &HealthClient, + options: &mut Options, + args: &str, +) -> Result<()> { + let mut req = MemHotplugByProbeRequest::default(); + + // Expected to be a comma separated list of hex addresses + let addr_list = utils::get_option("memHotplugProbeAddr", options, args); + + if addr_list != "" { + let addrs: Vec = addr_list + // Convert into a list of string values. + .split(",") + // Convert each string element into a u8 array of bytes, ignoring + // those elements that fail the conversion. + .filter_map(|s| hex::decode(s.trim_start_matches("0x")).ok()) + // "Stretch" the u8 byte slice into one of length 8 + // (to allow each 8 byte chunk to be converted into a u64). + .map(|mut v| -> Vec { + v.resize(8, 0x0); + v + }) + // Convert the slice of u8 bytes into a u64 + .map(|b| byteorder::LittleEndian::read_u64(&b)) + .collect(); + + req.set_memHotplugProbeAddr(addrs); + } + + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + + let reply = client + .mem_hotplug_by_probe(&req, cfg.timeout_nano) + .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; + + info!(sl!(), "response received"; + "response" => format!("{:?}", reply)); + + Ok(()) +} + #[inline] fn builtin_cmd_repeat(_cfg: &Config, _options: &mut Options, _args: &str) -> (Result<()>, bool) { // XXX: NOP implementation. Due to the way repeat has to work, providing a diff --git a/tools/agent-ctl/src/utils.rs b/tools/agent-ctl/src/utils.rs index 7dff5d8cb6..1a71fd6da3 100644 --- a/tools/agent-ctl/src/utils.rs +++ b/tools/agent-ctl/src/utils.rs @@ -408,3 +408,17 @@ pub fn get_grpc_spec(options: &mut Options, cid: &str) -> Result { Ok(oci_to_grpc(&bundle_dir, cid, &oci_spec)?) } + +pub fn str_to_bytes(s: &str) -> Result> { + let prefix = "hex:"; + + if s.starts_with(prefix) { + let hex_str = s.trim_start_matches(prefix); + + let decoded = hex::decode(hex_str).map_err(|e| anyhow!(e))?; + + Ok(decoded) + } else { + Ok(s.as_bytes().to_vec()) + } +}