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 <james.o.hunt@intel.com>
This commit is contained in:
James O. D. Hunt 2020-10-15 17:30:22 +01:00
parent 5620180302
commit edf02af1d4
4 changed files with 618 additions and 1 deletions

View File

@ -257,6 +257,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hex"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "2.0.1" version = "2.0.1"
@ -274,7 +280,9 @@ name = "kata-agent-ctl"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"byteorder",
"clap", "clap",
"hex",
"humantime", "humantime",
"lazy_static", "lazy_static",
"libc", "libc",
@ -435,7 +443,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "059a34f111a9dee2ce1ac2826a68b24601c4298cfeb1a587c3cb493d5ab46f52" checksum = "059a34f111a9dee2ce1ac2826a68b24601c4298cfeb1a587c3cb493d5ab46f52"
dependencies = [ dependencies = [
"libc", "libc",
"nix 0.17.0", "nix 0.18.0",
] ]
[[package]] [[package]]

View File

@ -17,6 +17,8 @@ oci = { path = "../../src/agent/oci" }
clap = "2.33.0" clap = "2.33.0"
lazy_static = "1.4.0" lazy_static = "1.4.0"
anyhow = "1.0.31" anyhow = "1.0.31"
hex = "0.4.2"
byteorder = "1.3.4"
logging = { path = "../../pkg/logging" } logging = { path = "../../pkg/logging" }
slog = "2.5.2" slog = "2.5.2"

View File

@ -8,6 +8,7 @@
use crate::types::{Config, Options}; use crate::types::{Config, Options};
use crate::utils; use crate::utils;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use byteorder::ByteOrder;
use nix::sys::socket::{connect, socket, AddressFamily, SockAddr, SockFlag, SockType, UnixAddr}; use nix::sys::socket::{connect, socket, AddressFamily, SockAddr, SockFlag, SockType, UnixAddr};
use protocols::agent::*; use protocols::agent::*;
use protocols::agent_ttrpc::*; use protocols::agent_ttrpc::*;
@ -75,6 +76,11 @@ const DEFAULT_PS_FORMAT: &str = "json";
const ERR_API_FAILED: &str = "API failed"; const ERR_API_FAILED: &str = "API failed";
static AGENT_CMDS: &'static [AgentCmd] = &[ static AGENT_CMDS: &'static [AgentCmd] = &[
AgentCmd {
name: "AddARPNeighbors",
st: ServiceType::Agent,
fp: agent_cmd_sandbox_add_arp_neighbors,
},
AgentCmd { AgentCmd {
name: "Check", name: "Check",
st: ServiceType::Health, st: ServiceType::Health,
@ -85,6 +91,16 @@ static AGENT_CMDS: &'static [AgentCmd] = &[
st: ServiceType::Health, st: ServiceType::Health,
fp: agent_cmd_health_version, 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 { AgentCmd {
name: "CreateContainer", name: "CreateContainer",
st: ServiceType::Agent, st: ServiceType::Agent,
@ -110,6 +126,16 @@ static AGENT_CMDS: &'static [AgentCmd] = &[
st: ServiceType::Agent, st: ServiceType::Agent,
fp: agent_cmd_sandbox_get_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 { AgentCmd {
name: "ListInterfaces", name: "ListInterfaces",
st: ServiceType::Agent, st: ServiceType::Agent,
@ -125,11 +151,36 @@ static AGENT_CMDS: &'static [AgentCmd] = &[
st: ServiceType::Agent, st: ServiceType::Agent,
fp: agent_cmd_container_list_processes, 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 { AgentCmd {
name: "PauseContainer", name: "PauseContainer",
st: ServiceType::Agent, st: ServiceType::Agent,
fp: agent_cmd_container_pause, 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 { AgentCmd {
name: "RemoveContainer", name: "RemoveContainer",
st: ServiceType::Agent, st: ServiceType::Agent,
@ -140,6 +191,11 @@ static AGENT_CMDS: &'static [AgentCmd] = &[
st: ServiceType::Agent, st: ServiceType::Agent,
fp: agent_cmd_container_resume, fp: agent_cmd_container_resume,
}, },
AgentCmd {
name: "SetGuestDateTime",
st: ServiceType::Agent,
fp: agent_cmd_sandbox_set_guest_date_time,
},
AgentCmd { AgentCmd {
name: "SignalProcess", name: "SignalProcess",
st: ServiceType::Agent, st: ServiceType::Agent,
@ -165,6 +221,16 @@ static AGENT_CMDS: &'static [AgentCmd] = &[
st: ServiceType::Agent, st: ServiceType::Agent,
fp: agent_cmd_sandbox_tracing_stop, 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 { AgentCmd {
name: "UpdateInterface", name: "UpdateInterface",
st: ServiceType::Agent, st: ServiceType::Agent,
@ -180,6 +246,11 @@ static AGENT_CMDS: &'static [AgentCmd] = &[
st: ServiceType::Agent, st: ServiceType::Agent,
fp: agent_cmd_container_wait_process, fp: agent_cmd_container_wait_process,
}, },
AgentCmd {
name: "WriteStdin",
st: ServiceType::Agent,
fp: agent_cmd_container_write_stdin,
},
]; ];
static BUILTIN_CMDS: &'static [BuiltinCmd] = &[ static BUILTIN_CMDS: &'static [BuiltinCmd] = &[
@ -1212,6 +1283,528 @@ fn agent_cmd_sandbox_list_routes(
Ok(()) 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::<u32>()
.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::<u32>()
.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::<u32>()
.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::<u32>()
.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::<i64>()
.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::<u32>()
.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::<u32>()
.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::<i32>()
.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::<i32>()
.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::<i64>()
.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::<bool>()
.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::<u32>()
.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::<bool>()
.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::<i64>()
.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::<i64>()
.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<u64> = 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<u8> {
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] #[inline]
fn builtin_cmd_repeat(_cfg: &Config, _options: &mut Options, _args: &str) -> (Result<()>, bool) { 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 // XXX: NOP implementation. Due to the way repeat has to work, providing a

View File

@ -408,3 +408,17 @@ pub fn get_grpc_spec(options: &mut Options, cid: &str) -> Result<grpcSpec> {
Ok(oci_to_grpc(&bundle_dir, cid, &oci_spec)?) Ok(oci_to_grpc(&bundle_dir, cid, &oci_spec)?)
} }
pub fn str_to_bytes(s: &str) -> Result<Vec<u8>> {
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())
}
}