diff --git a/tools/agent-ctl/Cargo.lock b/tools/agent-ctl/Cargo.lock index 897425ab5..1d37399fa 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 fcaa98a71..ae8a752db 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/README.md b/tools/agent-ctl/README.md index 862a0e335..47a1776ee 100644 --- a/tools/agent-ctl/README.md +++ b/tools/agent-ctl/README.md @@ -3,6 +3,11 @@ * [Overview](#overview) * [Audience and environment](#audience-and-environment) * [Full details](#full-details) +* [Code summary](#code-summary) +* [Running the tool](#running-the-tool) + * [Prerequisites](#prerequisites) + * [Connect to a real Kata Container](#connect-to-a-real-kata-container) + * [Run the tool and the agent in the same environment](#run-the-tool-and-the-agent-in-the-same-environment) ## Overview @@ -37,3 +42,80 @@ To see some examples, run: ```sh $ cargo run -- examples ``` + +## Code summary + +The table below summarises where to look to learn more about both this tool, +the agent protocol and the client and server implementations. + +| Description | File | Example RPC or function | Example summary | +|-|-|-|-| +| Protocol buffers definition of the Kata Containers Agent API protocol | [`agent.proto`](../../src/agent/protocols/protos/agent.proto) | `CreateContainer` | API to create a Kata container. | +| Agent Control (client) API calls | [`src/client.rs`](src/client.rs) | `agent_cmd_container_create()` | Agent Control tool function that calls the `CreateContainer` API. | +| Agent (server) API implementations | [`rpc.rs`](../../src/agent/src/rpc.rs) | `create_container()` | Server function that implements the `CreateContainers` API. | + +## Running the tool + +### Prerequisites + +It is necessary to create an OCI bundle to use the tool. The simplest method +is: + +```sh +$ bundle_dir="bundle" +$ rootfs_dir="$bundle_dir/rootfs" +$ image="busybox" +$ mkdir -p "$rootfs_dir" && (cd "$bundle_dir" && runc spec) +$ sudo docker export $(sudo docker create "$image") | tar -C "$rootfs_dir" -xvf - +``` + +### Connect to a real Kata Container + +1. Start a Kata Container + +1. Establish the VSOCK guest CID number for the virtual machine: + + Assuming you are running a single QEMU based Kata Container, you can look + at the program arguments to find the (randomly-generated) `guest-cid=` option + value: + + ```sh + $ guest_cid=$(ps -ef | grep qemu-system-x86_64 | egrep -o "guest-cid=[^,][^,]*" | cut -d= -f2) + ``` + +1. Run the tool to connect to the agent: + + ```sh + $ cargo run -- -l debug connect --bundle-dir "${bundle_dir}" --server-address "vsock://${guest_cid}:1024" -c Check -c GetGuestDetails + ``` + + This examples makes two API calls: + + - It runs `Check` to see if the agent's RPC server is serving. + - It then runs `GetGuestDetails` to establish some details of the + environment the agent is running in. + +### Run the tool and the agent in the same environment + +> **Warnings:** +> +> - This method is **only** for testing and development! +> - Only continue if you are using a non-critical system +> (such as a freshly installed VM environment). + +1. Start the agent, specifying a local socket for it to communicate on: + + ```sh + $ sudo KATA_AGENT_SERVER_ADDR=unix:///tmp/foo.socket target/x86_64-unknown-linux-musl/release/kata-agent + ``` + +1. Run the tool in the same environment: + + ```sh + $ cargo run -- -l debug connect --server-address "unix://@/tmp/foo.socket" --bundle-dir "$bundle_dir" -c Check -c GetGuestDetails + ``` + + > **Note:** + > + > The `@` in the server address is required - it denotes an abstract + > socket which the agent requires (see `unix(7)`). diff --git a/tools/agent-ctl/src/client.rs b/tools/agent-ctl/src/client.rs index 47e6a85bd..ac38bcb38 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, @@ -106,9 +122,19 @@ static AGENT_CMDS: &'static [AgentCmd] = &[ fp: agent_cmd_container_exec, }, AgentCmd { - name: "GuestDetails", + name: "GetGuestDetails", st: ServiceType::Agent, - fp: agent_cmd_sandbox_guest_details, + 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", @@ -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] = &[ @@ -684,6 +755,8 @@ fn agent_cmd_health_check( // value unused req.set_service("".to_string()); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = health .check(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -707,6 +780,8 @@ fn agent_cmd_health_version( // value unused req.set_service("".to_string()); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = health .version(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -729,6 +804,8 @@ fn agent_cmd_sandbox_create( let sid = utils::get_option("sid", options, args); req.set_sandbox_id(sid); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .create_sandbox(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -748,6 +825,8 @@ fn agent_cmd_sandbox_destroy( ) -> Result<()> { let req = DestroySandboxRequest::default(); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .destroy_sandbox(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -778,6 +857,8 @@ fn agent_cmd_container_create( req.set_exec_id(exec_id); req.set_OCI(grpc_spec); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .create_container(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -801,6 +882,8 @@ fn agent_cmd_container_remove( req.set_container_id(cid); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .remove_container(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -838,6 +921,8 @@ fn agent_cmd_container_exec( req.set_exec_id(exec_id); req.set_process(process); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .exec_process(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -861,6 +946,8 @@ fn agent_cmd_container_stats( req.set_container_id(cid); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .stats_container(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -884,6 +971,8 @@ fn agent_cmd_container_pause( req.set_container_id(cid); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .pause_container(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -907,6 +996,8 @@ fn agent_cmd_container_resume( req.set_container_id(cid); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .resume_container(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -930,6 +1021,8 @@ fn agent_cmd_container_start( req.set_container_id(cid); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .start_container(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -940,7 +1033,7 @@ fn agent_cmd_container_start( Ok(()) } -fn agent_cmd_sandbox_guest_details( +fn agent_cmd_sandbox_get_guest_details( cfg: &Config, client: &AgentServiceClient, _health: &HealthClient, @@ -951,6 +1044,8 @@ fn agent_cmd_sandbox_guest_details( req.set_mem_block_size(true); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .get_guest_details(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -981,6 +1076,8 @@ fn agent_cmd_container_list_processes( req.set_container_id(cid); req.set_format(list_format); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .list_processes(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -1006,6 +1103,8 @@ fn agent_cmd_container_wait_process( req.set_container_id(cid); req.set_exec_id(exec_id); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .wait_process(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -1041,6 +1140,8 @@ fn agent_cmd_container_signal_process( req.set_exec_id(exec_id); req.set_signal(signum as u32); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .signal_process(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -1060,6 +1161,8 @@ fn agent_cmd_sandbox_tracing_start( ) -> Result<()> { let req = StartTracingRequest::default(); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .start_tracing(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -1079,6 +1182,8 @@ fn agent_cmd_sandbox_tracing_stop( ) -> Result<()> { let req = StopTracingRequest::default(); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .stop_tracing(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -1098,6 +1203,7 @@ fn agent_cmd_sandbox_update_interface( ) -> Result<()> { let req = UpdateInterfaceRequest::default(); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); let reply = client .update_interface(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -1105,9 +1211,6 @@ fn agent_cmd_sandbox_update_interface( // FIXME: Implement 'UpdateInterface' fully. eprintln!("FIXME: 'UpdateInterface' not fully implemented"); - // let if = ...; - // req.set_interface(if); - info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -1123,6 +1226,8 @@ fn agent_cmd_sandbox_update_routes( ) -> Result<()> { let req = UpdateRoutesRequest::default(); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .update_routes(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -1130,9 +1235,6 @@ fn agent_cmd_sandbox_update_routes( // FIXME: Implement 'UpdateRoutes' fully. eprintln!("FIXME: 'UpdateRoutes' not fully implemented"); - // let routes = ...; - // req.set_routes(routes); - info!(sl!(), "response received"; "response" => format!("{:?}", reply)); @@ -1148,6 +1250,8 @@ fn agent_cmd_sandbox_list_interfaces( ) -> Result<()> { let req = ListInterfacesRequest::default(); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .list_interfaces(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -1167,6 +1271,8 @@ fn agent_cmd_sandbox_list_routes( ) -> Result<()> { let req = ListRoutesRequest::default(); + debug!(sl!(), "sending request"; "request" => format!("{:?}", req)); + let reply = client .list_routes(&req, cfg.timeout_nano) .map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?; @@ -1177,9 +1283,531 @@ 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 + // XXX: NOP implementation. Due to the way repeat has to work, providing a // handler like this is "too late" to be useful. However, a handler // is required as "repeat" is a valid command. // diff --git a/tools/agent-ctl/src/main.rs b/tools/agent-ctl/src/main.rs index c9514772a..e36ee2278 100644 --- a/tools/agent-ctl/src/main.rs +++ b/tools/agent-ctl/src/main.rs @@ -65,7 +65,7 @@ fn make_examples_text(program_name: &str) -> String { - Query the agent environment: - $ {program} connect --server-address "{vsock_server_address}" --cmd GuestDetails + $ {program} connect --server-address "{vsock_server_address}" --cmd GetGuestDetails - List all available (built-in and Kata Agent API) commands: @@ -85,7 +85,7 @@ fn make_examples_text(program_name: &str) -> String { - Query guest details forever: - $ {program} connect --server-address "{vsock_server_address}" --repeat -1 --cmd GuestDetails + $ {program} connect --server-address "{vsock_server_address}" --repeat -1 --cmd GetGuestDetails - Send a 'SIGUSR1' signal to a container process: diff --git a/tools/agent-ctl/src/utils.rs b/tools/agent-ctl/src/utils.rs index 7dff5d8cb..1a71fd6da 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()) + } +}