mirror of
https://github.com/kata-containers/kata-containers.git
synced 2025-06-22 05:28:25 +00:00
tools: Add agent-ctl tool
Add a low-level agent control tool that can manipulate the agent via ttRPC. Fixes: #222. Signed-off-by: James O. D. Hunt <james.o.hunt@intel.com>
This commit is contained in:
parent
2e53d237ce
commit
8a1949546c
37
tools/agent-ctl/Cargo.toml
Normal file
37
tools/agent-ctl/Cargo.toml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Copyright (c) 2020 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "kata-agent-ctl"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = ["James O. D. Hunt <james.o.hunt@intel.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
protocols = { path = "../../src/agent/protocols" }
|
||||||
|
rustjail = { path = "../../src/agent/rustjail" }
|
||||||
|
oci = { path = "../../src/agent/oci" }
|
||||||
|
|
||||||
|
clap = "2.33.0"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
anyhow = "1.0.31"
|
||||||
|
|
||||||
|
logging = { path = "../../pkg/logging" }
|
||||||
|
slog = "2.5.2"
|
||||||
|
slog-scope = "4.3.0"
|
||||||
|
rand = "0.7.3"
|
||||||
|
protobuf = "2.14.0"
|
||||||
|
|
||||||
|
nix = "0.17.0"
|
||||||
|
libc = "0.2.69"
|
||||||
|
# XXX: Must be the same as the version used by the agent
|
||||||
|
ttrpc = { git = "https://github.com/containerd/ttrpc-rust", branch="0.3.0" }
|
||||||
|
|
||||||
|
# For parsing timeouts
|
||||||
|
humantime = "2.0.0"
|
||||||
|
|
||||||
|
# For Options (state passing)
|
||||||
|
serde = { version = "1.0.110", features = ["derive"] }
|
||||||
|
serde_json = "1.0.53"
|
16
tools/agent-ctl/Makefile
Normal file
16
tools/agent-ctl/Makefile
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Copyright (c) 2020 Intel Corporation
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
default: build
|
||||||
|
|
||||||
|
build:
|
||||||
|
cargo build -v
|
||||||
|
|
||||||
|
clean:
|
||||||
|
cargo clean
|
||||||
|
|
||||||
|
.PHONY: \
|
||||||
|
build \
|
||||||
|
clean
|
39
tools/agent-ctl/README.md
Normal file
39
tools/agent-ctl/README.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Agent Control tool
|
||||||
|
|
||||||
|
* [Overview](#overview)
|
||||||
|
* [Audience and environment](#audience-and-environment)
|
||||||
|
* [Full details](#full-details)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Kata Containers agent control tool (`kata-agent-ctl`) is a low-level test
|
||||||
|
tool. It allows basic interaction with the Kata Containers agent,
|
||||||
|
`kata-agent`, that runs inside the virtual machine.
|
||||||
|
|
||||||
|
Unlike the Kata Runtime, which only ever makes sequences of correctly ordered
|
||||||
|
and valid agent API calls, this tool allows users to make arbitrary agent API
|
||||||
|
calls and to control their parameters.
|
||||||
|
|
||||||
|
## Audience and environment
|
||||||
|
|
||||||
|
> **Warning:**
|
||||||
|
>
|
||||||
|
> This tool is for *advanced* users familiar with the low-level agent API calls.
|
||||||
|
> Further, it is designed to be run on test and development systems **only**: since
|
||||||
|
> the tool can make arbitrary API calls, it is possible to easily confuse
|
||||||
|
> irrevocably other parts of the system or even kill a running container or
|
||||||
|
> sandbox.
|
||||||
|
|
||||||
|
## Full details
|
||||||
|
|
||||||
|
For a usage statement, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cargo run -- --help
|
||||||
|
```
|
||||||
|
|
||||||
|
To see some examples, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cargo run -- examples
|
||||||
|
```
|
1149
tools/agent-ctl/src/client.rs
Normal file
1149
tools/agent-ctl/src/client.rs
Normal file
File diff suppressed because it is too large
Load Diff
292
tools/agent-ctl/src/main.rs
Normal file
292
tools/agent-ctl/src/main.rs
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
// Copyright (c) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use clap::{crate_name, crate_version, App, Arg, SubCommand};
|
||||||
|
use std::io;
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
|
// Convenience macro to obtain the scope logger
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! sl {
|
||||||
|
() => {
|
||||||
|
slog_scope::logger()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mod client;
|
||||||
|
mod rpc;
|
||||||
|
mod types;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
const DEFAULT_LOG_LEVEL: slog::Level = slog::Level::Info;
|
||||||
|
|
||||||
|
const DESCRIPTION_TEXT: &str = r#"DESCRIPTION:
|
||||||
|
Low-level test tool that allows basic interaction with
|
||||||
|
the Kata Containers agent using agent API calls."#;
|
||||||
|
|
||||||
|
const ABOUT_TEXT: &str = "Kata Containers agent tool";
|
||||||
|
|
||||||
|
const WARNING_TEXT: &str = r#"WARNING:
|
||||||
|
This tool is for *advanced* users familiar with the low-level agent API calls.
|
||||||
|
Further, it is designed to be run on test and development systems **only**:
|
||||||
|
since the tool can make arbitrary API calls, it is possible to easily confuse
|
||||||
|
irrevocably other parts of the system or even kill a running container or
|
||||||
|
sandbox."#;
|
||||||
|
|
||||||
|
fn make_examples_text(program_name: &str) -> String {
|
||||||
|
let bundle = "$bundle_dir";
|
||||||
|
let cid = 3;
|
||||||
|
let container_id = "$container_id";
|
||||||
|
let config_file_uri = "file:///tmp/config.json";
|
||||||
|
let port = 1024;
|
||||||
|
let sandbox_id = "$sandbox_id";
|
||||||
|
|
||||||
|
format!(
|
||||||
|
r#"EXAMPLES:
|
||||||
|
|
||||||
|
- Check if the agent is running:
|
||||||
|
|
||||||
|
$ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd Check
|
||||||
|
|
||||||
|
- Query the agent environment:
|
||||||
|
|
||||||
|
$ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd GuestDetails
|
||||||
|
|
||||||
|
- List all available (built-in and Kata Agent API) commands:
|
||||||
|
|
||||||
|
$ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd list
|
||||||
|
|
||||||
|
- Generate a random container ID:
|
||||||
|
|
||||||
|
$ {program} generate-cid
|
||||||
|
|
||||||
|
- Generate a random sandbox ID:
|
||||||
|
|
||||||
|
$ {program} generate-sid
|
||||||
|
|
||||||
|
- Attempt to create 7 sandboxes, ignoring any errors:
|
||||||
|
|
||||||
|
$ {program} connect --vsock-cid {cid} --vsock-port {port} --repeat 7 --cmd CreateSandbox
|
||||||
|
|
||||||
|
- Query guest details forever:
|
||||||
|
|
||||||
|
$ {program} connect --vsock-cid {cid} --vsock-port {port} --repeat -1 --cmd GuestDetails
|
||||||
|
|
||||||
|
- Send a 'SIGUSR1' signal to a container process:
|
||||||
|
|
||||||
|
$ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd 'SignalProcess signal=usr1 sid={sandbox_id} cid={container_id}'
|
||||||
|
|
||||||
|
- Create a sandbox with a single container, and then destroy everything:
|
||||||
|
|
||||||
|
$ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd CreateSandbox
|
||||||
|
$ {program} connect --vsock-cid {cid} --vsock-port {port} --bundle-dir {bundle:?} --cmd CreateContainer
|
||||||
|
$ {program} connect --vsock-cid {cid} --vsock-port {port} --cmd DestroySandbox
|
||||||
|
|
||||||
|
- Create a Container using a custom configuration file:
|
||||||
|
|
||||||
|
$ {program} connect --vsock-cid {cid} --vsock-port {port} --bundle-dir {bundle:?} --cmd 'CreateContainer spec={config_file_uri}'
|
||||||
|
"#,
|
||||||
|
bundle = bundle,
|
||||||
|
cid = cid,
|
||||||
|
config_file_uri = config_file_uri,
|
||||||
|
container_id = container_id,
|
||||||
|
port = port,
|
||||||
|
program = program_name,
|
||||||
|
sandbox_id = sandbox_id,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect(name: &str, global_args: clap::ArgMatches) -> Result<()> {
|
||||||
|
let args = global_args
|
||||||
|
.subcommand_matches("connect")
|
||||||
|
.ok_or("BUG: missing sub-command arguments".to_string())
|
||||||
|
.map_err(|e| anyhow!(e))?;
|
||||||
|
|
||||||
|
let interactive = args.is_present("interactive");
|
||||||
|
let ignore_errors = args.is_present("ignore-errors");
|
||||||
|
|
||||||
|
let cid_str = args
|
||||||
|
.value_of("vsock-cid")
|
||||||
|
.ok_or("need VSOCK cid".to_string())
|
||||||
|
.map_err(|e| anyhow!(e))?;
|
||||||
|
|
||||||
|
let port_str = args
|
||||||
|
.value_of("vsock-port")
|
||||||
|
.ok_or("need VSOCK port number".to_string())
|
||||||
|
.map_err(|e| anyhow!(e))?;
|
||||||
|
|
||||||
|
let cid: u32 = cid_str
|
||||||
|
.parse::<u32>()
|
||||||
|
.map_err(|e| anyhow!(format!("invalid VSOCK CID number: {}", e.to_string())))?;
|
||||||
|
|
||||||
|
let port: u32 = port_str
|
||||||
|
.parse::<u32>()
|
||||||
|
.map_err(|e| anyhow!(format!("invalid VSOCK port number: {}", e)))?;
|
||||||
|
|
||||||
|
let mut commands: Vec<&str> = Vec::new();
|
||||||
|
|
||||||
|
if !interactive {
|
||||||
|
commands = args
|
||||||
|
.values_of("cmd")
|
||||||
|
.ok_or("need commands to send to the server".to_string())
|
||||||
|
.map_err(|e| anyhow!(e))?
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot fail as a default has been specified
|
||||||
|
let log_level_name = global_args.value_of("log-level").unwrap();
|
||||||
|
|
||||||
|
let log_level = logging::level_name_to_slog_level(log_level_name).map_err(|e| anyhow!(e))?;
|
||||||
|
|
||||||
|
let writer = io::stdout();
|
||||||
|
let logger = logging::create_logger(name, crate_name!(), log_level, writer);
|
||||||
|
|
||||||
|
let timeout_nano: i64 = match args.value_of("timeout") {
|
||||||
|
Some(t) => utils::human_time_to_ns(t).map_err(|e| e)?,
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let bundle_dir = args.value_of("bundle-dir").unwrap_or("");
|
||||||
|
|
||||||
|
let result = rpc::run(
|
||||||
|
&logger,
|
||||||
|
cid,
|
||||||
|
port,
|
||||||
|
bundle_dir,
|
||||||
|
interactive,
|
||||||
|
ignore_errors,
|
||||||
|
timeout_nano,
|
||||||
|
commands,
|
||||||
|
);
|
||||||
|
if result.is_err() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn real_main() -> Result<()> {
|
||||||
|
let name = crate_name!();
|
||||||
|
|
||||||
|
let app = App::new(name)
|
||||||
|
.version(crate_version!())
|
||||||
|
.about(ABOUT_TEXT)
|
||||||
|
.long_about(DESCRIPTION_TEXT)
|
||||||
|
.after_help(WARNING_TEXT)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("log-level")
|
||||||
|
.long("log-level")
|
||||||
|
.short("l")
|
||||||
|
.help("specific log level")
|
||||||
|
.default_value(logging::slog_level_to_level_name(DEFAULT_LOG_LEVEL).unwrap())
|
||||||
|
.possible_values(&logging::get_log_levels())
|
||||||
|
.takes_value(true)
|
||||||
|
.required(false),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("connect")
|
||||||
|
.about("Connect to agent")
|
||||||
|
.after_help(WARNING_TEXT)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("bundle-dir")
|
||||||
|
.long("bundle-dir")
|
||||||
|
.help("OCI bundle directory")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("directory"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("vsock-cid")
|
||||||
|
.long("vsock-cid")
|
||||||
|
.help("VSOCK Context ID")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("CID"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("cmd")
|
||||||
|
.long("cmd")
|
||||||
|
.short("c")
|
||||||
|
.takes_value(true)
|
||||||
|
.multiple(true)
|
||||||
|
.help("API command (with optional arguments) to send to the server"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("ignore-errors")
|
||||||
|
.long("ignore-errors")
|
||||||
|
.help("Don't exit on first error"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("interactive")
|
||||||
|
.short("i")
|
||||||
|
.long("interactive")
|
||||||
|
.help("Allow interactive client"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("vsock-port")
|
||||||
|
.long("vsock-port")
|
||||||
|
.help("VSOCK Port number")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("port-number"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("timeout")
|
||||||
|
.long("timeout")
|
||||||
|
.help("timeout value as nanoseconds or using human-readable suffixes (0 [forever], 99ns, 30us, 2ms, 5s, 7m, etc)")
|
||||||
|
.takes_value(true)
|
||||||
|
.value_name("human-time"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("generate-cid")
|
||||||
|
.about("Create a random container ID")
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("generate-sid")
|
||||||
|
.about("Create a random sandbox ID")
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
SubCommand::with_name("examples")
|
||||||
|
.about("Show usage examples")
|
||||||
|
);
|
||||||
|
|
||||||
|
let args = app.get_matches();
|
||||||
|
|
||||||
|
let subcmd = args
|
||||||
|
.subcommand_name()
|
||||||
|
.ok_or("need sub-command".to_string())
|
||||||
|
.map_err(|e| anyhow!(e))?;
|
||||||
|
|
||||||
|
match subcmd {
|
||||||
|
"generate-cid" => {
|
||||||
|
println!("{}", utils::random_container_id());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
"generate-sid" => {
|
||||||
|
println!("{}", utils::random_sandbox_id());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
"examples" => {
|
||||||
|
println!("{}", make_examples_text(name));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
"connect" => {
|
||||||
|
return connect(name, args);
|
||||||
|
}
|
||||||
|
_ => return Err(anyhow!(format!("invalid sub-command: {:?}", subcmd))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
match real_main() {
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("ERROR: {}", e);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
}
|
37
tools/agent-ctl/src/rpc.rs
Normal file
37
tools/agent-ctl/src/rpc.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
// Description: ttRPC logic entry point
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use slog::{o, Logger};
|
||||||
|
|
||||||
|
use crate::client::client;
|
||||||
|
use crate::types::Config;
|
||||||
|
|
||||||
|
pub fn run(
|
||||||
|
logger: &Logger,
|
||||||
|
cid: u32,
|
||||||
|
port: u32,
|
||||||
|
bundle_dir: &str,
|
||||||
|
interactive: bool,
|
||||||
|
ignore_errors: bool,
|
||||||
|
timeout_nano: i64,
|
||||||
|
commands: Vec<&str>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let cfg = Config {
|
||||||
|
cid: cid,
|
||||||
|
port: port,
|
||||||
|
bundle_dir: bundle_dir.to_string(),
|
||||||
|
timeout_nano: timeout_nano,
|
||||||
|
interactive: interactive,
|
||||||
|
ignore_errors: ignore_errors,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Maintain the global logger for the duration of the ttRPC comms
|
||||||
|
let _guard = slog_scope::set_global_logger(logger.new(o!("subsystem" => "rpc")));
|
||||||
|
|
||||||
|
client(&cfg, commands)
|
||||||
|
}
|
20
tools/agent-ctl/src/types.rs
Normal file
20
tools/agent-ctl/src/types.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
// Type used to pass optional state between cooperating API calls.
|
||||||
|
pub type Options = HashMap<String, String>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub cid: u32,
|
||||||
|
pub port: u32,
|
||||||
|
pub bundle_dir: String,
|
||||||
|
pub timeout_nano: i64,
|
||||||
|
pub interactive: bool,
|
||||||
|
pub ignore_errors: bool,
|
||||||
|
}
|
411
tools/agent-ctl/src/utils.rs
Normal file
411
tools/agent-ctl/src/utils.rs
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
// Copyright (c) 2020 Intel Corporation
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
//
|
||||||
|
|
||||||
|
use crate::types::{Config, Options};
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use oci::{Process as ociProcess, Root as ociRoot, Spec as ociSpec};
|
||||||
|
use protocols::oci::{
|
||||||
|
Box as grpcBox, Linux as grpcLinux, LinuxCapabilities as grpcLinuxCapabilities,
|
||||||
|
POSIXRlimit as grpcPOSIXRlimit, Process as grpcProcess, Root as grpcRoot, Spec as grpcSpec,
|
||||||
|
User as grpcUser,
|
||||||
|
};
|
||||||
|
use rand::Rng;
|
||||||
|
use slog::{debug, warn};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
// Length of a sandbox identifier
|
||||||
|
const SANDBOX_ID_LEN: u8 = 64;
|
||||||
|
|
||||||
|
const FILE_URI: &str = "file://";
|
||||||
|
|
||||||
|
// Length of the guests hostname
|
||||||
|
const MIN_HOSTNAME_LEN: u8 = 8;
|
||||||
|
|
||||||
|
// Name of the OCI configuration file found at the root of an OCI bundle.
|
||||||
|
const CONFIG_FILE: &str = "config.json";
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
// Create a mutable hash map statically
|
||||||
|
static ref SIGNALS: Arc<Mutex<HashMap<&'static str, u8>>> = {
|
||||||
|
|
||||||
|
let mut m: HashMap<&'static str, u8> = HashMap::new();
|
||||||
|
|
||||||
|
m.insert("SIGHUP", 1);
|
||||||
|
m.insert("SIGINT", 2);
|
||||||
|
m.insert("SIGQUIT", 3);
|
||||||
|
m.insert("SIGILL", 4);
|
||||||
|
m.insert("SIGTRAP", 5);
|
||||||
|
m.insert("SIGABRT", 6);
|
||||||
|
m.insert("SIGBUS", 7);
|
||||||
|
m.insert("SIGFPE", 8);
|
||||||
|
m.insert("SIGKILL", 9);
|
||||||
|
m.insert("SIGUSR1", 10);
|
||||||
|
m.insert("SIGSEGV", 11);
|
||||||
|
m.insert("SIGUSR2", 12);
|
||||||
|
m.insert("SIGPIPE", 13);
|
||||||
|
m.insert("SIGALRM", 14);
|
||||||
|
m.insert("SIGTERM", 15);
|
||||||
|
m.insert("SIGSTKFLT", 16);
|
||||||
|
|
||||||
|
// XXX:
|
||||||
|
m.insert("SIGCHLD", 17);
|
||||||
|
m.insert("SIGCLD", 17);
|
||||||
|
|
||||||
|
m.insert("SIGCONT", 18);
|
||||||
|
m.insert("SIGSTOP", 19);
|
||||||
|
m.insert("SIGTSTP", 20);
|
||||||
|
m.insert("SIGTTIN", 21);
|
||||||
|
m.insert("SIGTTOU", 22);
|
||||||
|
m.insert("SIGURG", 23);
|
||||||
|
m.insert("SIGXCPU", 24);
|
||||||
|
m.insert("SIGXFSZ", 25);
|
||||||
|
m.insert("SIGVTALRM", 26);
|
||||||
|
m.insert("SIGPROF", 27);
|
||||||
|
m.insert("SIGWINCH", 28);
|
||||||
|
m.insert("SIGIO", 29);
|
||||||
|
m.insert("SIGPWR", 30);
|
||||||
|
m.insert("SIGSYS", 31);
|
||||||
|
|
||||||
|
Arc::new(Mutex::new(m))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn signame_to_signum(name: &str) -> Result<u8> {
|
||||||
|
if name == "" {
|
||||||
|
return Err(anyhow!("invalid signal"));
|
||||||
|
}
|
||||||
|
|
||||||
|
match name.parse::<u8>() {
|
||||||
|
Ok(n) => return Ok(n),
|
||||||
|
|
||||||
|
// "fall through" on error as we assume the name is not a number, but
|
||||||
|
// a signal name.
|
||||||
|
Err(_) => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut search_term: String;
|
||||||
|
|
||||||
|
if name.starts_with("SIG") {
|
||||||
|
search_term = name.to_string();
|
||||||
|
} else {
|
||||||
|
search_term = format!("SIG{}", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
search_term = search_term.to_uppercase();
|
||||||
|
|
||||||
|
// Access the hashmap
|
||||||
|
let signals_ref = SIGNALS.clone();
|
||||||
|
let m = signals_ref.lock().unwrap();
|
||||||
|
|
||||||
|
match m.get(&*search_term) {
|
||||||
|
Some(value) => Ok(*value),
|
||||||
|
None => Err(anyhow!(format!("invalid signal name: {:?}", name))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a human time fornat (like "2s") into the equivalent number
|
||||||
|
// of nano seconds.
|
||||||
|
pub fn human_time_to_ns(human_time: &str) -> Result<i64> {
|
||||||
|
if human_time == "" || human_time == "0" {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let d: humantime::Duration = human_time
|
||||||
|
.parse::<humantime::Duration>()
|
||||||
|
.map_err(|e| anyhow!(e))?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
Ok(d.as_nanos() as i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up the specified option name and return its value.
|
||||||
|
//
|
||||||
|
// - The function looks for the appropriate option value in the specified
|
||||||
|
// 'args' first.
|
||||||
|
// - 'args' is assumed to be a space-separated set of "name=value" pairs).
|
||||||
|
// - If not found in the args, the function looks in the global options hash.
|
||||||
|
// - If found in neither location, certain well-known options are auto-generated.
|
||||||
|
// - All other options values default to an empty string.
|
||||||
|
// - All options are saved in the global hash before being returned for future
|
||||||
|
// use.
|
||||||
|
pub fn get_option(name: &str, options: &mut Options, args: &str) -> String {
|
||||||
|
let words: Vec<&str> = args.split_whitespace().collect();
|
||||||
|
|
||||||
|
for word in words {
|
||||||
|
let fields: Vec<String> = word.split("=").map(|s| s.to_string()).collect();
|
||||||
|
|
||||||
|
if fields.len() < 2 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if fields[0] == "" {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = fields[0].clone();
|
||||||
|
|
||||||
|
let mut value = fields[1..].join("=");
|
||||||
|
|
||||||
|
// Expand "spec=file:///some/where/config.json"
|
||||||
|
if key == "spec" && value.starts_with(FILE_URI) {
|
||||||
|
let spec_file = match uri_to_filename(&value) {
|
||||||
|
Ok(file) => file,
|
||||||
|
Err(e) => {
|
||||||
|
warn!(sl!(), "failed to handle spec file URI: {:}", e);
|
||||||
|
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if spec_file != "" {
|
||||||
|
value = match spec_file_to_string(spec_file) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
warn!(sl!(), "failed to load spec file: {:}", e);
|
||||||
|
|
||||||
|
"".to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command args take priority over any previous value,
|
||||||
|
// so update the global set of options for this and all
|
||||||
|
// subsequent commands.
|
||||||
|
options.insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explains briefly how the option value was determined
|
||||||
|
let mut msg = "cached";
|
||||||
|
|
||||||
|
// If the option exists in the hash, return it
|
||||||
|
if let Some(value) = options.get(name) {
|
||||||
|
debug!(sl!(), "using option {:?}={:?} ({})", name, value, msg);
|
||||||
|
|
||||||
|
return value.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = "generated";
|
||||||
|
|
||||||
|
// Handle option values that can be auto-generated
|
||||||
|
let value = match name {
|
||||||
|
"cid" => random_container_id(),
|
||||||
|
"sid" => random_sandbox_id(),
|
||||||
|
|
||||||
|
// Default to CID
|
||||||
|
"exec_id" => {
|
||||||
|
msg = "derived";
|
||||||
|
//derived = true;
|
||||||
|
|
||||||
|
match options.get("cid") {
|
||||||
|
Some(value) => value.to_string(),
|
||||||
|
None => "".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => "".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!(sl!(), "using option {:?}={:?} ({})", name, value, msg);
|
||||||
|
|
||||||
|
// Store auto-generated value
|
||||||
|
options.insert(name.to_string(), value.to_string());
|
||||||
|
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_random_hex_string(len: u32) -> String {
|
||||||
|
const CHARSET: &[u8] = b"abcdef0123456789";
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let str: String = (0..len)
|
||||||
|
.map(|_| {
|
||||||
|
let idx = rng.gen_range(0, CHARSET.len());
|
||||||
|
CHARSET[idx] as char
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
str
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_sandbox_id() -> String {
|
||||||
|
generate_random_hex_string(SANDBOX_ID_LEN as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn random_container_id() -> String {
|
||||||
|
// Containers and sandboxes have same ID types
|
||||||
|
random_sandbox_id()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn config_file_from_bundle_dir(bundle_dir: &str) -> Result<String> {
|
||||||
|
if bundle_dir == "" {
|
||||||
|
return Err(anyhow!("missing bundle directory"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let config_path = PathBuf::from(&bundle_dir).join(CONFIG_FILE);
|
||||||
|
|
||||||
|
config_path
|
||||||
|
.into_os_string()
|
||||||
|
.into_string()
|
||||||
|
.map_err(|e| anyhow!(format!("failed to construct config file path: {:?}", e)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn root_oci_to_grpc(bundle_dir: &str, root: &ociRoot) -> Result<grpcRoot> {
|
||||||
|
let root_dir = root.path.clone();
|
||||||
|
|
||||||
|
let path = if root_dir.starts_with("/") {
|
||||||
|
root_dir.clone()
|
||||||
|
} else {
|
||||||
|
// Expand the root directory into an absolute value
|
||||||
|
let abs_root_dir = PathBuf::from(&bundle_dir).join(&root_dir);
|
||||||
|
|
||||||
|
abs_root_dir
|
||||||
|
.into_os_string()
|
||||||
|
.into_string()
|
||||||
|
.map_err(|e| anyhow!(format!("failed to construct bundle path: {:?}", e)))?
|
||||||
|
};
|
||||||
|
|
||||||
|
let grpc_root = grpcRoot {
|
||||||
|
Path: path,
|
||||||
|
Readonly: root.readonly,
|
||||||
|
unknown_fields: protobuf::UnknownFields::new(),
|
||||||
|
cached_size: protobuf::CachedSize::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(grpc_root)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_oci_to_grpc(p: &ociProcess) -> grpcProcess {
|
||||||
|
let console_size = match &p.console_size {
|
||||||
|
Some(s) => {
|
||||||
|
let mut b = grpcBox::new();
|
||||||
|
|
||||||
|
b.set_Width(s.width);
|
||||||
|
b.set_Height(s.height);
|
||||||
|
|
||||||
|
protobuf::SingularPtrField::some(b)
|
||||||
|
}
|
||||||
|
None => protobuf::SingularPtrField::none(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let oom_score_adj: i64 = match p.oom_score_adj {
|
||||||
|
Some(s) => s.into(),
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut user = grpcUser::new();
|
||||||
|
user.set_UID(p.user.uid);
|
||||||
|
user.set_GID(p.user.gid);
|
||||||
|
user.set_AdditionalGids(p.user.additional_gids.clone());
|
||||||
|
|
||||||
|
// FIXME: Implement RLimits OCI spec handling (copy from p.rlimits)
|
||||||
|
//let rlimits = vec![grpcPOSIXRlimit::new()];
|
||||||
|
let rlimits = protobuf::RepeatedField::new();
|
||||||
|
|
||||||
|
// FIXME: Implement Capabilities OCI spec handling (copy from p.capabilities)
|
||||||
|
let capabilities = grpcLinuxCapabilities::new();
|
||||||
|
|
||||||
|
// FIXME: Implement Env OCI spec handling (copy from p.env)
|
||||||
|
let env = protobuf::RepeatedField::new();
|
||||||
|
|
||||||
|
grpcProcess {
|
||||||
|
Terminal: p.terminal,
|
||||||
|
ConsoleSize: console_size,
|
||||||
|
User: protobuf::SingularPtrField::some(user),
|
||||||
|
Args: protobuf::RepeatedField::from_vec(p.args.clone()),
|
||||||
|
Env: env,
|
||||||
|
Cwd: p.cwd.clone(),
|
||||||
|
Capabilities: protobuf::SingularPtrField::some(capabilities),
|
||||||
|
Rlimits: rlimits,
|
||||||
|
NoNewPrivileges: p.no_new_privileges,
|
||||||
|
ApparmorProfile: p.apparmor_profile.clone(),
|
||||||
|
OOMScoreAdj: oom_score_adj,
|
||||||
|
SelinuxLabel: p.selinux_label.clone(),
|
||||||
|
unknown_fields: protobuf::UnknownFields::new(),
|
||||||
|
cached_size: protobuf::CachedSize::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn oci_to_grpc(bundle_dir: &str, cid: &str, oci: &ociSpec) -> Result<grpcSpec> {
|
||||||
|
let process = match &oci.process {
|
||||||
|
Some(p) => protobuf::SingularPtrField::some(process_oci_to_grpc(&p)),
|
||||||
|
None => protobuf::SingularPtrField::none(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let root = match &oci.root {
|
||||||
|
Some(r) => {
|
||||||
|
let grpc_root = root_oci_to_grpc(bundle_dir, &r).map_err(|e| e)?;
|
||||||
|
|
||||||
|
protobuf::SingularPtrField::some(grpc_root)
|
||||||
|
}
|
||||||
|
None => protobuf::SingularPtrField::none(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME: Implement Linux OCI spec handling
|
||||||
|
let linux = grpcLinux::new();
|
||||||
|
|
||||||
|
if cid.len() < MIN_HOSTNAME_LEN as usize {
|
||||||
|
return Err(anyhow!("container ID too short for hostname"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: Implement setting a custom (and unique!) hostname (requires uts ns setup)
|
||||||
|
//let hostname = cid[0..MIN_HOSTNAME_LEN as usize].to_string();
|
||||||
|
let hostname = "".to_string();
|
||||||
|
|
||||||
|
let grpc_spec = grpcSpec {
|
||||||
|
Version: oci.version.clone(),
|
||||||
|
Process: process,
|
||||||
|
Root: root,
|
||||||
|
Hostname: hostname,
|
||||||
|
Mounts: protobuf::RepeatedField::new(),
|
||||||
|
Hooks: protobuf::SingularPtrField::none(),
|
||||||
|
Annotations: HashMap::new(),
|
||||||
|
Linux: protobuf::SingularPtrField::some(linux),
|
||||||
|
Solaris: protobuf::SingularPtrField::none(),
|
||||||
|
Windows: protobuf::SingularPtrField::none(),
|
||||||
|
unknown_fields: protobuf::UnknownFields::new(),
|
||||||
|
cached_size: protobuf::CachedSize::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(grpc_spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uri_to_filename(uri: &str) -> Result<String> {
|
||||||
|
if !uri.starts_with(FILE_URI) {
|
||||||
|
return Err(anyhow!(format!("invalid URI: {:?}", uri)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let fields: Vec<&str> = uri.split(FILE_URI).collect();
|
||||||
|
|
||||||
|
if fields.len() != 2 {
|
||||||
|
return Err(anyhow!(format!("invalid URI: {:?}", uri)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(fields[1].to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spec_file_to_string(spec_file: String) -> Result<String> {
|
||||||
|
let oci_spec = ociSpec::load(&spec_file).map_err(|e| anyhow!(e))?;
|
||||||
|
|
||||||
|
serde_json::to_string(&oci_spec).map_err(|e| anyhow!(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_oci_spec_json(cfg: &Config) -> Result<String> {
|
||||||
|
let spec_file = config_file_from_bundle_dir(&cfg.bundle_dir)?;
|
||||||
|
|
||||||
|
spec_file_to_string(spec_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_grpc_spec(options: &mut Options, cid: &str) -> Result<grpcSpec> {
|
||||||
|
let bundle_dir = get_option("bundle-dir", options, "");
|
||||||
|
|
||||||
|
let json_spec = get_option("spec", options, "");
|
||||||
|
assert_ne!(json_spec, "");
|
||||||
|
|
||||||
|
let oci_spec: ociSpec = serde_json::from_str(&json_spec).map_err(|e| anyhow!(e))?;
|
||||||
|
|
||||||
|
Ok(oci_to_grpc(&bundle_dir, cid, &oci_spec)?)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user