kata-containers/tools/agent-ctl/src/client.rs
bin 421439c633 API: remove ProcessListContainer/ListProcesses
This commit will remove ProcessListContainer API from VCSandbox
and ListProcesses from agent.proto.

Fixes: #1668

Signed-off-by: bin <bin@hyper.sh>
2021-04-09 17:34:25 +08:00

1921 lines
48 KiB
Rust

// Copyright (c) 2020 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
// Description: Client side of ttRPC comms
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::*;
use protocols::health::*;
use protocols::health_ttrpc::*;
use slog::{debug, info};
use std::io;
use std::io::Write; // XXX: for flush()
use std::os::unix::io::{IntoRawFd, RawFd};
use std::os::unix::net::UnixStream;
use std::thread::sleep;
use std::time::Duration;
use ttrpc;
use ttrpc::context::Context;
// Hack until the actual Context type supports this.
fn clone_context(ctx: &Context) -> Context {
Context {
metadata: ctx.metadata.clone(),
timeout_nano: ctx.timeout_nano,
}
}
// Agent command handler type
//
// Notes:
//
// - 'cmdline' is the command line (command name and optional space separate
// arguments).
// - 'options' can be read and written to, allowing commands to pass state to
// each other via well-known option names.
type AgentCmdFp = fn(
ctx: &Context,
client: &AgentServiceClient,
health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()>;
// Builtin command handler type
type BuiltinCmdFp = fn(args: &str) -> (Result<()>, bool);
enum ServiceType {
Agent,
Health,
}
// XXX: Agent command names *MUST* start with an upper-case letter.
struct AgentCmd {
name: &'static str,
st: ServiceType,
fp: AgentCmdFp,
}
// XXX: Builtin command names *MUST* start with a lower-case letter.
struct BuiltinCmd {
name: &'static str,
descr: &'static str,
fp: BuiltinCmdFp,
}
// Command that causes the agent to exit (iff tracing is enabled)
const SHUTDOWN_CMD: &'static str = "DestroySandbox";
// Command that requests this program ends
const CMD_QUIT: &'static str = "quit";
const CMD_REPEAT: &'static str = "repeat";
const DEFAULT_PROC_SIGNAL: &'static str = "SIGKILL";
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,
fp: agent_cmd_health_check,
},
AgentCmd {
name: "Version",
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,
fp: agent_cmd_container_create,
},
AgentCmd {
name: "CreateSandbox",
st: ServiceType::Agent,
fp: agent_cmd_sandbox_create,
},
AgentCmd {
name: "DestroySandbox",
st: ServiceType::Agent,
fp: agent_cmd_sandbox_destroy,
},
AgentCmd {
name: "ExecProcess",
st: ServiceType::Agent,
fp: agent_cmd_container_exec,
},
AgentCmd {
name: "GetGuestDetails",
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,
fp: agent_cmd_sandbox_list_interfaces,
},
AgentCmd {
name: "ListRoutes",
st: ServiceType::Agent,
fp: agent_cmd_sandbox_list_routes,
},
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,
fp: agent_cmd_container_remove,
},
AgentCmd {
name: "ResumeContainer",
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,
fp: agent_cmd_container_signal_process,
},
AgentCmd {
name: "StartContainer",
st: ServiceType::Agent,
fp: agent_cmd_container_start,
},
AgentCmd {
name: "StartTracing",
st: ServiceType::Agent,
fp: agent_cmd_sandbox_tracing_start,
},
AgentCmd {
name: "StatsContainer",
st: ServiceType::Agent,
fp: agent_cmd_container_stats,
},
AgentCmd {
name: "StopTracing",
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,
fp: agent_cmd_sandbox_update_interface,
},
AgentCmd {
name: "UpdateRoutes",
st: ServiceType::Agent,
fp: agent_cmd_sandbox_update_routes,
},
AgentCmd {
name: "WaitProcess",
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] = &[
BuiltinCmd {
name: "echo",
descr: "Display the arguments",
fp: builtin_cmd_echo,
},
BuiltinCmd {
name: "help",
descr: "Alias for 'list'",
fp: builtin_cmd_list,
},
BuiltinCmd {
name: "list",
descr: "List all available commands",
fp: builtin_cmd_list,
},
BuiltinCmd {
name: "repeat",
descr: "Repeat the next command 'n' times [-1 for forever]",
fp: builtin_cmd_repeat,
},
BuiltinCmd {
name: "sleep",
descr:
"Pause for specified period number of nanoseconds (supports human-readable suffixes [no floating points numbers])",
fp: builtin_cmd_sleep,
},
BuiltinCmd {
name: CMD_QUIT,
descr: "Exit this program",
fp: builtin_cmd_quit,
},
];
fn get_agent_cmd_names() -> Vec<String> {
let mut names = Vec::new();
for cmd in AGENT_CMDS {
names.push(cmd.name.to_string());
}
names
}
fn get_agent_cmd_details() -> Vec<String> {
let mut cmds = Vec::new();
for cmd in AGENT_CMDS {
let service = match cmd.st {
ServiceType::Agent => "agent",
ServiceType::Health => "health",
};
cmds.push(format!("{} ({} service)", cmd.name, service));
}
cmds
}
fn get_agent_cmd_func(name: &str) -> Result<AgentCmdFp> {
for cmd in AGENT_CMDS {
if cmd.name == name {
return Ok(cmd.fp);
}
}
Err(anyhow!("Invalid command: {:?}", name))
}
fn get_builtin_cmd_details() -> Vec<String> {
let mut cmds = Vec::new();
for cmd in BUILTIN_CMDS {
cmds.push(format!("{} ({})", cmd.name, cmd.descr));
}
cmds
}
fn get_all_cmd_details() -> Vec<String> {
let mut cmds = get_builtin_cmd_details();
cmds.append(&mut get_agent_cmd_names());
cmds
}
fn get_builtin_cmd_func(name: &str) -> Result<BuiltinCmdFp> {
for cmd in BUILTIN_CMDS {
if cmd.name == name {
return Ok(cmd.fp);
}
}
Err(anyhow!("Invalid command: {:?}", name))
}
fn client_create_vsock_fd(cid: libc::c_uint, port: u32) -> Result<RawFd> {
let fd = socket(
AddressFamily::Vsock,
SockType::Stream,
SockFlag::SOCK_CLOEXEC,
None,
)
.map_err(|e| anyhow!(e))?;
let sock_addr = SockAddr::new_vsock(cid, port);
connect(fd, &sock_addr).map_err(|e| anyhow!(e))?;
Ok(fd)
}
fn create_ttrpc_client(server_address: String) -> Result<ttrpc::Client> {
if server_address == "" {
return Err(anyhow!("server address cannot be blank"));
}
let fields: Vec<&str> = server_address.split("://").collect();
if fields.len() != 2 {
return Err(anyhow!("invalid server address URI"));
}
let scheme = fields[0].to_lowercase();
let fd: RawFd = match scheme.as_str() {
// Formats:
//
// - "unix://absolute-path" (domain socket)
// (example: "unix:///tmp/domain.socket")
//
// - "unix://@absolute-path" (abstract socket)
// (example: "unix://@/tmp/abstract.socket")
//
"unix" => {
let mut abstract_socket = false;
let mut path = fields[1].to_string();
if path.starts_with('@') {
abstract_socket = true;
// Remove the magic abstract-socket request character ('@')
// and crucially add a trailing nul terminator (required to
// interoperate with the ttrpc crate).
path = path[1..].to_string() + &"\x00".to_string();
}
if abstract_socket {
let socket_fd = match socket(
AddressFamily::Unix,
SockType::Stream,
SockFlag::empty(),
None,
) {
Ok(s) => s,
Err(e) => return Err(anyhow!(e).context("Failed to create Unix Domain socket")),
};
let unix_addr = match UnixAddr::new_abstract(path.as_bytes()) {
Ok(s) => s,
Err(e) => {
return Err(
anyhow!(e).context("Failed to create Unix Domain abstract socket")
)
}
};
let sock_addr = SockAddr::Unix(unix_addr);
connect(socket_fd, &sock_addr).map_err(|e| {
anyhow!(e).context("Failed to connect to Unix Domain abstract socket")
})?;
socket_fd
} else {
let stream = match UnixStream::connect(path) {
Ok(s) => s,
Err(e) => {
return Err(
anyhow!(e).context("failed to create named UNIX Domain stream socket")
)
}
};
stream.into_raw_fd()
}
}
// Format: "vsock://cid:port"
"vsock" => {
let addr: Vec<&str> = fields[1].split(':').collect();
if addr.len() != 2 {
return Err(anyhow!("invalid VSOCK server address URI"));
}
let cid: u32 = match addr[0] {
"-1" | "" => libc::VMADDR_CID_ANY,
_ => match addr[0].parse::<u32>() {
Ok(c) => c,
Err(e) => return Err(anyhow!(e).context("VSOCK CID is not numeric")),
},
};
let port: u32 = match addr[1].parse::<u32>() {
Ok(r) => r,
Err(e) => return Err(anyhow!(e).context("VSOCK port is not numeric")),
};
client_create_vsock_fd(cid, port).map_err(|e| {
anyhow!(e).context("failed to create VSOCK connection (check agent is running)")
})?
}
_ => {
return Err(anyhow!("invalid server address URI scheme: {:?}", scheme));
}
};
Ok(ttrpc::client::Client::new(fd))
}
fn kata_service_agent(server_address: String) -> Result<AgentServiceClient> {
let ttrpc_client = create_ttrpc_client(server_address)?;
Ok(AgentServiceClient::new(ttrpc_client))
}
fn kata_service_health(server_address: String) -> Result<HealthClient> {
let ttrpc_client = create_ttrpc_client(server_address)?;
Ok(HealthClient::new(ttrpc_client))
}
fn announce(cfg: &Config) {
info!(sl!(), "announce"; "config" => format!("{:?}", cfg));
}
pub fn client(cfg: &Config, commands: Vec<&str>) -> Result<()> {
if commands.len() == 1 && commands[0] == "list" {
println!("Built-in commands:\n");
let mut builtin_cmds = get_builtin_cmd_details();
builtin_cmds.sort();
builtin_cmds.iter().for_each(|n| println!(" {}", n));
println!();
println!("Agent API commands:\n");
let mut agent_cmds = get_agent_cmd_details();
agent_cmds.sort();
agent_cmds.iter().for_each(|n| println!(" {}", n));
println!();
return Ok(());
}
announce(cfg);
// Create separate connections for each of the services provided
// by the agent.
let client = kata_service_agent(cfg.server_address.clone())?;
let health = kata_service_health(cfg.server_address.clone())?;
let mut options = Options::new();
let ttrpc_ctx = ttrpc::context::with_timeout(cfg.timeout_nano);
// Special-case loading the OCI config file so it is accessible
// to all commands.
let oci_spec_json = utils::get_oci_spec_json(cfg)?;
options.insert("spec".to_string(), oci_spec_json);
// Convenience option
options.insert("bundle-dir".to_string(), cfg.bundle_dir.clone());
info!(sl!(), "client setup complete";
"server-address" => cfg.server_address.to_string());
if cfg.interactive {
return interactive_client_loop(&cfg, &mut options, &client, &health, &ttrpc_ctx);
}
let mut repeat_count = 1;
for cmd in commands {
if cmd.starts_with(CMD_REPEAT) {
repeat_count = get_repeat_count(cmd);
continue;
}
let (result, shutdown) = handle_cmd(
&cfg,
&client,
&health,
&ttrpc_ctx,
repeat_count,
&mut options,
&cmd,
);
if result.is_err() {
return result;
}
if shutdown {
break;
}
// Reset
repeat_count = 1;
}
Ok(())
}
// Handle internal and agent API commands.
fn handle_cmd(
cfg: &Config,
client: &AgentServiceClient,
health: &HealthClient,
ctx: &Context,
repeat_count: i64,
options: &mut Options,
cmdline: &str,
) -> (Result<()>, bool) {
let fields: Vec<&str> = cmdline.split_whitespace().collect();
let cmd = fields[0];
if cmd == "" {
// Ignore empty commands
return (Ok(()), false);
}
let first = match cmd.chars().nth(0) {
Some(c) => c,
None => return (Err(anyhow!("failed to check command name")), false),
};
let args = if fields.len() > 1 {
fields[1..].join(" ")
} else {
String::new()
};
let mut count = 0;
let mut count_msg = String::new();
if repeat_count < 0 {
count_msg = "forever".to_string();
}
let mut error_count: u64 = 0;
let mut result: (Result<()>, bool);
loop {
if repeat_count > 0 {
count_msg = format!("{} of {}", count + 1, repeat_count);
}
info!(sl!(), "Run command {:} ({})", cmd, count_msg);
if first.is_lowercase() {
result = handle_builtin_cmd(cmd, &args);
} else {
result = handle_agent_cmd(ctx, client, health, options, cmd, &args);
}
if result.0.is_err() {
if cfg.ignore_errors {
error_count += 1;
debug!(sl!(), "ignoring error for command {:}: {:?}", cmd, result.0);
} else {
return result;
}
}
info!(
sl!(),
"Command {:} ({}) returned {:?}", cmd, count_msg, result
);
if repeat_count > 0 {
count += 1;
if count == repeat_count {
break;
}
}
}
if cfg.ignore_errors {
debug!(sl!(), "Error count for command {}: {}", cmd, error_count);
(Ok(()), result.1)
} else {
result
}
}
fn handle_builtin_cmd(cmd: &str, args: &str) -> (Result<()>, bool) {
let f = match get_builtin_cmd_func(&cmd) {
Ok(fp) => fp,
Err(e) => return (Err(e), false),
};
f(&args)
}
// Execute the ttRPC specified by the first field of "line". Return a result
// along with a bool which if set means the client should shutdown.
fn handle_agent_cmd(
ctx: &Context,
client: &AgentServiceClient,
health: &HealthClient,
options: &mut Options,
cmd: &str,
args: &str,
) -> (Result<()>, bool) {
let f = match get_agent_cmd_func(&cmd) {
Ok(fp) => fp,
Err(e) => return (Err(e), false),
};
let result = f(ctx, client, health, options, &args);
if result.is_err() {
return (result, false);
}
let shutdown = cmd == SHUTDOWN_CMD;
(Ok(()), shutdown)
}
fn interactive_client_loop(
cfg: &Config,
options: &mut Options,
client: &AgentServiceClient,
health: &HealthClient,
ctx: &Context,
) -> Result<()> {
let result = builtin_cmd_list("");
if result.0.is_err() {
return result.0;
}
let mut repeat_count: i64 = 1;
loop {
let cmdline =
readline("Enter command").map_err(|e| anyhow!(e).context("failed to read line"))?;
if cmdline == "" {
continue;
}
if cmdline.starts_with(CMD_REPEAT) {
repeat_count = get_repeat_count(&cmdline);
continue;
}
let (result, shutdown) =
handle_cmd(cfg, client, health, ctx, repeat_count, options, &cmdline);
if result.is_err() {
return result;
}
if shutdown {
break;
}
// Reset
repeat_count = 1;
}
Ok(())
}
fn readline(prompt: &str) -> std::result::Result<String, String> {
print!("{}: ", prompt);
io::stdout()
.flush()
.map_err(|e| format!("failed to flush: {:?}", e))?;
let mut line = String::new();
std::io::stdin()
.read_line(&mut line)
.map_err(|e| format!("failed to read line: {:?}", e))?;
// Remove NL
Ok(line.trim_end().to_string())
}
fn agent_cmd_health_check(
ctx: &Context,
_client: &AgentServiceClient,
health: &HealthClient,
_options: &mut Options,
_args: &str,
) -> Result<()> {
let mut req = CheckRequest::default();
let ctx = clone_context(ctx);
// value unused
req.set_service("".to_string());
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = health
.check(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_health_version(
ctx: &Context,
_client: &AgentServiceClient,
health: &HealthClient,
_options: &mut Options,
_args: &str,
) -> Result<()> {
// XXX: Yes, the API is actually broken!
let mut req = CheckRequest::default();
let ctx = clone_context(ctx);
// value unused
req.set_service("".to_string());
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = health
.version(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_create(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = CreateSandboxRequest::default();
let ctx = clone_context(ctx);
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(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_destroy(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
_options: &mut Options,
_args: &str,
) -> Result<()> {
let req = DestroySandboxRequest::default();
let ctx = clone_context(ctx);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.destroy_sandbox(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_container_create(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = CreateContainerRequest::default();
let ctx = clone_context(ctx);
let cid = utils::get_option("cid", options, args);
let exec_id = utils::get_option("exec_id", options, args);
// FIXME: container create: add back "spec=file:///" support
let grpc_spec = utils::get_grpc_spec(options, &cid).map_err(|e| anyhow!(e))?;
req.set_container_id(cid);
req.set_exec_id(exec_id);
req.set_OCI(grpc_spec);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.create_container(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_container_remove(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = RemoveContainerRequest::default();
let cid = utils::get_option("cid", options, args);
let ctx = clone_context(ctx);
req.set_container_id(cid);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.remove_container(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_container_exec(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = ExecProcessRequest::default();
let ctx = clone_context(ctx);
let cid = utils::get_option("cid", options, args);
let exec_id = utils::get_option("exec_id", options, args);
let grpc_spec = utils::get_grpc_spec(options, &cid).map_err(|e| anyhow!(e))?;
let bundle_dir = options
.get("bundle-dir")
.ok_or("BUG: bundle-dir missing")
.map_err(|e| anyhow!(e))?;
let process = grpc_spec
.Process
.into_option()
.ok_or(format!(
"failed to get process from OCI spec: {}",
bundle_dir,
))
.map_err(|e| anyhow!(e))?;
req.set_container_id(cid);
req.set_exec_id(exec_id);
req.set_process(process);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.exec_process(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_container_stats(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = StatsContainerRequest::default();
let ctx = clone_context(ctx);
let cid = utils::get_option("cid", options, args);
req.set_container_id(cid);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.stats_container(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_container_pause(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = PauseContainerRequest::default();
let ctx = clone_context(ctx);
let cid = utils::get_option("cid", options, args);
req.set_container_id(cid);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.pause_container(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_container_resume(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = ResumeContainerRequest::default();
let cid = utils::get_option("cid", options, args);
let ctx = clone_context(ctx);
req.set_container_id(cid);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.resume_container(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_container_start(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = StartContainerRequest::default();
let ctx = clone_context(ctx);
let cid = utils::get_option("cid", options, args);
req.set_container_id(cid);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.start_container(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_get_guest_details(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
_options: &mut Options,
_args: &str,
) -> Result<()> {
let mut req = GuestDetailsRequest::default();
let ctx = clone_context(ctx);
req.set_mem_block_size(true);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.get_guest_details(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_container_wait_process(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = WaitProcessRequest::default();
let ctx = clone_context(ctx);
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
.wait_process(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_container_signal_process(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = SignalProcessRequest::default();
let ctx = clone_context(ctx);
let cid = utils::get_option("cid", options, args);
let exec_id = utils::get_option("exec_id", options, args);
let mut sigstr = utils::get_option("signal", options, args);
// Convert to a numeric
if sigstr == "" {
sigstr = DEFAULT_PROC_SIGNAL.to_string();
}
let signum = utils::signame_to_signum(&sigstr).map_err(|e| anyhow!(e))?;
req.set_container_id(cid);
req.set_exec_id(exec_id);
req.set_signal(signum as u32);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.signal_process(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_tracing_start(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
_options: &mut Options,
_args: &str,
) -> Result<()> {
let req = StartTracingRequest::default();
let ctx = clone_context(ctx);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.start_tracing(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_tracing_stop(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
_options: &mut Options,
_args: &str,
) -> Result<()> {
let req = StopTracingRequest::default();
let ctx = clone_context(ctx);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.stop_tracing(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_update_interface(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
_options: &mut Options,
_args: &str,
) -> Result<()> {
let req = UpdateInterfaceRequest::default();
let ctx = clone_context(ctx);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.update_interface(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
// FIXME: Implement 'UpdateInterface' fully.
eprintln!("FIXME: 'UpdateInterface' not fully implemented");
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_update_routes(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
_options: &mut Options,
_args: &str,
) -> Result<()> {
let req = UpdateRoutesRequest::default();
let ctx = clone_context(ctx);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.update_routes(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
// FIXME: Implement 'UpdateRoutes' fully.
eprintln!("FIXME: 'UpdateRoutes' not fully implemented");
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_list_interfaces(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
_options: &mut Options,
_args: &str,
) -> Result<()> {
let req = ListInterfacesRequest::default();
let ctx = clone_context(ctx);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.list_interfaces(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_list_routes(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
_options: &mut Options,
_args: &str,
) -> Result<()> {
let req = ListRoutesRequest::default();
let ctx = clone_context(ctx);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.list_routes(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_container_tty_win_resize(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = TtyWinResizeRequest::default();
let ctx = clone_context(ctx);
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(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_container_close_stdin(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = CloseStdinRequest::default();
let ctx = clone_context(ctx);
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(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_container_read_stdout(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = ReadStreamRequest::default();
let ctx = clone_context(ctx);
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(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_container_read_stderr(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = ReadStreamRequest::default();
let ctx = clone_context(ctx);
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(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_container_write_stdin(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = WriteStreamRequest::default();
let ctx = clone_context(ctx);
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(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_get_metrics(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
_options: &mut Options,
_args: &str,
) -> Result<()> {
let req = GetMetricsRequest::default();
let ctx = clone_context(ctx);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.get_metrics(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_get_oom_event(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
_options: &mut Options,
_args: &str,
) -> Result<()> {
let req = GetOOMEventRequest::default();
let ctx = clone_context(ctx);
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.get_oom_event(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_copy_file(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = CopyFileRequest::default();
let ctx = clone_context(ctx);
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(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_reseed_random_dev(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = ReseedRandomDevRequest::default();
let ctx = clone_context(ctx);
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(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_online_cpu_mem(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = OnlineCPUMemRequest::default();
let ctx = clone_context(ctx);
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(ctx, &req)
.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(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = SetGuestDateTimeRequest::default();
let ctx = clone_context(ctx);
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(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_add_arp_neighbors(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
_options: &mut Options,
_args: &str,
) -> Result<()> {
let req = AddARPNeighborsRequest::default();
let ctx = clone_context(ctx);
// FIXME: Implement fully.
eprintln!("FIXME: 'AddARPNeighbors' not fully implemented");
debug!(sl!(), "sending request"; "request" => format!("{:?}", req));
let reply = client
.add_arp_neighbors(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
fn agent_cmd_sandbox_update_container(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = UpdateContainerRequest::default();
let ctx = clone_context(ctx);
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(ctx, &req)
.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(
ctx: &Context,
client: &AgentServiceClient,
_health: &HealthClient,
options: &mut Options,
args: &str,
) -> Result<()> {
let mut req = MemHotplugByProbeRequest::default();
let ctx = clone_context(ctx);
// 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(ctx, &req)
.map_err(|e| anyhow!("{:?}", e).context(ERR_API_FAILED))?;
info!(sl!(), "response received";
"response" => format!("{:?}", reply));
Ok(())
}
#[inline]
fn builtin_cmd_repeat(_args: &str) -> (Result<()>, bool) {
// 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.
//
// A cleaner approach would be to make `AgentCmd.fp` an `Option` which for
// this command would be specified as `None`, but this is the only command
// which doesn't need an implementation, so this approach is simpler :)
(Ok(()), false)
}
fn builtin_cmd_sleep(args: &str) -> (Result<()>, bool) {
let ns = match utils::human_time_to_ns(args) {
Ok(t) => t,
Err(e) => return (Err(e), false),
};
sleep(Duration::from_nanos(ns as u64));
(Ok(()), false)
}
fn builtin_cmd_echo(args: &str) -> (Result<()>, bool) {
println!("{}", args);
(Ok(()), false)
}
fn builtin_cmd_quit(_args: &str) -> (Result<()>, bool) {
(Ok(()), true)
}
fn builtin_cmd_list(_args: &str) -> (Result<()>, bool) {
let cmds = get_all_cmd_details();
cmds.iter().for_each(|n| println!(" - {}", n));
println!("");
(Ok(()), false)
}
fn get_repeat_count(cmdline: &str) -> i64 {
let default_repeat_count: i64 = 1;
let fields: Vec<&str> = cmdline.split_whitespace().collect();
if fields.len() < 2 {
return default_repeat_count;
}
if fields[0] != CMD_REPEAT {
return default_repeat_count;
}
let count = fields[1];
match count.parse::<i64>() {
Ok(n) => return n,
Err(_) => return default_repeat_count,
}
}