agent: add configurable container pipe size cmdline option

Adds a cmdline option to configure the stdout/stderr pipe sizes.
Uses `F_SETPIPE_SZ` to resize the write side of the pipe after
creation.

Example Cmdline option: `agent.container_pipe_size=2097152`

fixes #152

Signed-off-by: Alex Price <aprice@atlassian.com>
This commit is contained in:
Alex Price 2020-03-03 15:47:55 +11:00
parent 7965445adf
commit 204edf0e51
3 changed files with 247 additions and 9 deletions

View File

@ -12,7 +12,7 @@ use std::os::unix::io::RawFd;
// use crate::cgroups::Manager as CgroupManager;
// use crate::intelrdt::Manager as RdtManager;
use nix::fcntl::OFlag;
use nix::fcntl::{fcntl, FcntlArg, OFlag};
use nix::sys::signal::{self, Signal};
use nix::sys::socket::{self, AddressFamily, SockFlag, SockType};
use nix::sys::wait::{self, WaitStatus};
@ -72,7 +72,13 @@ impl ProcessOperations for Process {
}
impl Process {
pub fn new(logger: &Logger, ocip: &OCIProcess, id: &str, init: bool) -> Result<Self> {
pub fn new(
logger: &Logger,
ocip: &OCIProcess,
id: &str,
init: bool,
pipe_size: i32,
) -> Result<Self> {
let logger = logger.new(o!("subsystem" => "process"));
let mut p = Process {
@ -128,14 +134,54 @@ impl Process {
p.parent_stdin = Some(pstdin);
p.stdin = Some(stdin);
let (pstdout, stdout) = unistd::pipe2(OFlag::O_CLOEXEC)?;
let (pstdout, stdout) = create_extended_pipe(OFlag::O_CLOEXEC, pipe_size)?;
p.parent_stdout = Some(pstdout);
p.stdout = Some(stdout);
let (pstderr, stderr) = unistd::pipe2(OFlag::O_CLOEXEC)?;
let (pstderr, stderr) = create_extended_pipe(OFlag::O_CLOEXEC, pipe_size)?;
p.parent_stderr = Some(pstderr);
p.stderr = Some(stderr);
Ok(p)
}
}
fn create_extended_pipe(flags: OFlag, pipe_size: i32) -> Result<(RawFd, RawFd)> {
let (r, w) = unistd::pipe2(flags)?;
if pipe_size > 0 {
fcntl(w, FcntlArg::F_SETPIPE_SZ(pipe_size))?;
}
Ok((r, w))
}
#[cfg(test)]
mod tests {
use crate::process::create_extended_pipe;
use nix::fcntl::{fcntl, FcntlArg, OFlag};
use std::fs;
use std::os::unix::io::RawFd;
fn get_pipe_max_size() -> i32 {
fs::read_to_string("/proc/sys/fs/pipe-max-size")
.unwrap()
.trim()
.parse::<i32>()
.unwrap()
}
fn get_pipe_size(fd: RawFd) -> i32 {
fcntl(fd, FcntlArg::F_GETPIPE_SZ).unwrap()
}
#[test]
fn test_create_extended_pipe() {
// Test the default
let (r, w) = create_extended_pipe(OFlag::O_CLOEXEC, 0).unwrap();
// Test setting to the max size
let max_size = get_pipe_max_size();
let (r, w) = create_extended_pipe(OFlag::O_CLOEXEC, max_size).unwrap();
let actual_size = get_pipe_size(w);
assert_eq!(max_size, actual_size);
}
}

View File

@ -12,9 +12,11 @@ const LOG_LEVEL_OPTION: &str = "agent.log";
const HOTPLUG_TIMOUT_OPTION: &str = "agent.hotplug_timeout";
const DEBUG_CONSOLE_VPORT_OPTION: &str = "agent.debug_console_vport";
const LOG_VPORT_OPTION: &str = "agent.log_vport";
const CONTAINER_PIPE_SIZE_OPTION: &str = "agent.container_pipe_size";
const DEFAULT_LOG_LEVEL: slog::Level = slog::Level::Info;
const DEFAULT_HOTPLUG_TIMEOUT: time::Duration = time::Duration::from_secs(3);
const DEFAULT_CONTAINER_PIPE_SIZE: i32 = 0;
// FIXME: unused
const TRACE_MODE_FLAG: &str = "agent.trace";
@ -28,6 +30,7 @@ pub struct agentConfig {
pub hotplug_timeout: time::Duration,
pub debug_console_vport: i32,
pub log_vport: i32,
pub container_pipe_size: i32,
}
impl agentConfig {
@ -39,6 +42,7 @@ impl agentConfig {
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
debug_console_vport: 0,
log_vport: 0,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
}
}
@ -80,6 +84,11 @@ impl agentConfig {
self.log_vport = port;
}
}
if param.starts_with(format!("{}=", CONTAINER_PIPE_SIZE_OPTION).as_str()) {
let container_pipe_size = get_container_pipe_size(param)?;
self.container_pipe_size = container_pipe_size
}
}
Ok(())
@ -156,6 +165,40 @@ fn get_hotplug_timeout(param: &str) -> Result<time::Duration> {
Ok(time::Duration::from_secs(value.unwrap()))
}
fn get_container_pipe_size(param: &str) -> Result<i32> {
let fields: Vec<&str> = param.split("=").collect();
if fields.len() != 2 {
return Err(
ErrorKind::ErrorCode(String::from("invalid container pipe size parameter")).into(),
);
}
let key = fields[0];
if key != CONTAINER_PIPE_SIZE_OPTION {
return Err(
ErrorKind::ErrorCode(String::from("invalid container pipe size key name")).into(),
);
}
let res = fields[1].parse::<i32>();
if res.is_err() {
return Err(
ErrorKind::ErrorCode(String::from("unable to parse container pipe size")).into(),
);
}
let value = res.unwrap();
if value < 0 {
return Err(ErrorKind::ErrorCode(String::from(
"container pipe size should not be negative",
))
.into());
}
Ok(value)
}
#[cfg(test)]
mod tests {
use super::*;
@ -172,6 +215,11 @@ mod tests {
const ERR_INVALID_HOTPLUG_TIMEOUT_PARAM: &str = "unable to parse hotplug timeout";
const ERR_INVALID_HOTPLUG_TIMEOUT_KEY: &str = "invalid hotplug timeout key name";
const ERR_INVALID_CONTAINER_PIPE_SIZE: &str = "invalid container pipe size parameter";
const ERR_INVALID_CONTAINER_PIPE_SIZE_PARAM: &str = "unable to parse container pipe size";
const ERR_INVALID_CONTAINER_PIPE_SIZE_KEY: &str = "invalid container pipe size key name";
const ERR_INVALID_CONTAINER_PIPE_NEGATIVE: &str = "container pipe size should not be negative";
// helper function to make errors less crazy-long
fn make_err(desc: &str) -> Error {
ErrorKind::ErrorCode(desc.to_string()).into()
@ -218,6 +266,7 @@ mod tests {
dev_mode: bool,
log_level: slog::Level,
hotplug_timeout: time::Duration,
container_pipe_size: i32,
}
let tests = &[
@ -227,6 +276,7 @@ mod tests {
dev_mode: true,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "agent.debug_console agent.devmodex",
@ -234,6 +284,7 @@ mod tests {
dev_mode: false,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "agent.logx=debug",
@ -241,6 +292,7 @@ mod tests {
dev_mode: false,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "agent.log=debug",
@ -248,6 +300,7 @@ mod tests {
dev_mode: false,
log_level: slog::Level::Debug,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "",
@ -255,6 +308,7 @@ mod tests {
dev_mode: false,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "foo",
@ -262,6 +316,7 @@ mod tests {
dev_mode: false,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "foo bar",
@ -269,6 +324,7 @@ mod tests {
dev_mode: false,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "foo bar",
@ -276,6 +332,7 @@ mod tests {
dev_mode: false,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "foo agent bar",
@ -283,6 +340,7 @@ mod tests {
dev_mode: false,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "foo debug_console agent bar devmode",
@ -290,6 +348,7 @@ mod tests {
dev_mode: false,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "agent.debug_console",
@ -297,6 +356,7 @@ mod tests {
dev_mode: false,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: " agent.debug_console ",
@ -304,6 +364,7 @@ mod tests {
dev_mode: false,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "agent.debug_console foo",
@ -311,6 +372,7 @@ mod tests {
dev_mode: false,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: " agent.debug_console foo",
@ -318,6 +380,7 @@ mod tests {
dev_mode: false,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "foo agent.debug_console bar",
@ -325,6 +388,7 @@ mod tests {
dev_mode: false,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "foo agent.debug_console",
@ -332,6 +396,7 @@ mod tests {
dev_mode: false,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "foo agent.debug_console ",
@ -339,6 +404,7 @@ mod tests {
dev_mode: false,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "agent.devmode",
@ -346,6 +412,7 @@ mod tests {
dev_mode: true,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: " agent.devmode ",
@ -353,6 +420,7 @@ mod tests {
dev_mode: true,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "agent.devmode foo",
@ -360,6 +428,7 @@ mod tests {
dev_mode: true,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: " agent.devmode foo",
@ -367,6 +436,7 @@ mod tests {
dev_mode: true,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "foo agent.devmode bar",
@ -374,6 +444,7 @@ mod tests {
dev_mode: true,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "foo agent.devmode",
@ -381,6 +452,7 @@ mod tests {
dev_mode: true,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "foo agent.devmode ",
@ -388,6 +460,7 @@ mod tests {
dev_mode: true,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "agent.devmode agent.debug_console",
@ -395,6 +468,7 @@ mod tests {
dev_mode: true,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "agent.devmode agent.debug_console agent.hotplug_timeout=100",
@ -402,6 +476,7 @@ mod tests {
dev_mode: true,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: time::Duration::from_secs(100),
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "agent.devmode agent.debug_console agent.hotplug_timeout=0",
@ -409,6 +484,39 @@ mod tests {
dev_mode: true,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "agent.devmode agent.debug_console agent.container_pipe_size=2097152",
debug_console: true,
dev_mode: true,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: 2097152,
},
TestData {
contents: "agent.devmode agent.debug_console agent.container_pipe_size=100",
debug_console: true,
dev_mode: true,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: 100,
},
TestData {
contents: "agent.devmode agent.debug_console agent.container_pipe_size=0",
debug_console: true,
dev_mode: true,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
TestData {
contents: "agent.devmode agent.debug_console agent.container_pip_siz=100",
debug_console: true,
dev_mode: true,
log_level: DEFAULT_LOG_LEVEL,
hotplug_timeout: DEFAULT_HOTPLUG_TIMEOUT,
container_pipe_size: DEFAULT_CONTAINER_PIPE_SIZE,
},
];
@ -438,9 +546,15 @@ mod tests {
.expect(&format!("{}: failed to write file contents", msg));
let mut config = agentConfig::new();
assert!(config.debug_console == false, msg);
assert!(config.dev_mode == false, msg);
assert!(config.hotplug_timeout == time::Duration::from_secs(3), msg);
assert_eq!(config.debug_console, false, "{}", msg);
assert_eq!(config.dev_mode, false, "{}", msg);
assert_eq!(
config.hotplug_timeout,
time::Duration::from_secs(3),
"{}",
msg
);
assert_eq!(config.container_pipe_size, 0, "{}", msg);
let result = config.parse_cmdline(filename);
assert!(result.is_ok(), "{}", msg);
@ -449,6 +563,7 @@ mod tests {
assert_eq!(d.dev_mode, config.dev_mode, "{}", msg);
assert_eq!(d.log_level, config.log_level, "{}", msg);
assert_eq!(d.hotplug_timeout, config.hotplug_timeout, "{}", msg);
assert_eq!(d.container_pipe_size, config.container_pipe_size, "{}", msg);
}
}
@ -689,4 +804,78 @@ mod tests {
assert_result!(d.result, result, format!("{}", msg));
}
}
#[test]
fn test_get_container_pipe_size() {
#[derive(Debug)]
struct TestData<'a> {
param: &'a str,
result: Result<i32>,
}
let tests = &[
TestData {
param: "",
result: Err(make_err(ERR_INVALID_CONTAINER_PIPE_SIZE)),
},
TestData {
param: "agent.container_pipe_size",
result: Err(make_err(ERR_INVALID_CONTAINER_PIPE_SIZE)),
},
TestData {
param: "foo=bar",
result: Err(make_err(ERR_INVALID_CONTAINER_PIPE_SIZE_KEY)),
},
TestData {
param: "agent.container_pip_siz=1",
result: Err(make_err(ERR_INVALID_CONTAINER_PIPE_SIZE_KEY)),
},
TestData {
param: "agent.container_pipe_size=1",
result: Ok(1),
},
TestData {
param: "agent.container_pipe_size=3",
result: Ok(3),
},
TestData {
param: "agent.container_pipe_size=2097152",
result: Ok(2097152),
},
TestData {
param: "agent.container_pipe_size=0",
result: Ok(0),
},
TestData {
param: "agent.container_pipe_size=-1",
result: Err(make_err(ERR_INVALID_CONTAINER_PIPE_NEGATIVE)),
},
TestData {
param: "agent.container_pipe_size=foobar",
result: Err(make_err(ERR_INVALID_CONTAINER_PIPE_SIZE_PARAM)),
},
TestData {
param: "agent.container_pipe_size=j",
result: Err(make_err(ERR_INVALID_CONTAINER_PIPE_SIZE_PARAM)),
},
TestData {
param: "agent.container_pipe_size=4jbsdja",
result: Err(make_err(ERR_INVALID_CONTAINER_PIPE_SIZE_PARAM)),
},
TestData {
param: "agent.container_pipe_size=4294967296",
result: Err(make_err(ERR_INVALID_CONTAINER_PIPE_SIZE_PARAM)),
},
];
for (i, d) in tests.iter().enumerate() {
let msg = format!("test[{}]: {:?}", i, d);
let result = get_container_pipe_size(d.param);
let msg = format!("{}: result: {:?}", msg, result);
assert_result!(d.result, result, format!("{}", msg));
}
}
}

View File

@ -36,6 +36,7 @@ use crate::namespace::{NSTYPEIPC, NSTYPEPID, NSTYPEUTS};
use crate::random;
use crate::sandbox::Sandbox;
use crate::version::{AGENT_VERSION, API_VERSION};
use crate::AGENT_CONFIG;
use netlink::{RtnlHandle, NETLINK_ROUTE};
use libc::{self, c_ushort, pid_t, winsize, TIOCSWINSZ};
@ -139,8 +140,9 @@ impl agentService {
let mut ctr: LinuxContainer =
LinuxContainer::new(cid.as_str(), CONTAINER_BASE, opts, &sl!())?;
let pipe_size = AGENT_CONFIG.read().unwrap().container_pipe_size;
let p = if oci.Process.is_some() {
let tp = Process::new(&sl!(), oci.get_Process(), eid.as_str(), true)?;
let tp = Process::new(&sl!(), oci.get_Process(), eid.as_str(), true, pipe_size)?;
tp
} else {
info!(sl!(), "no process configurations!");
@ -274,7 +276,8 @@ impl agentService {
return Err(ErrorKind::Nix(nix::Error::from_errno(nix::errno::Errno::EINVAL)).into());
};
let p = Process::new(&sl!(), ocip, exec_id.as_str(), false)?;
let pipe_size = AGENT_CONFIG.read().unwrap().container_pipe_size;
let p = Process::new(&sl!(), ocip, exec_id.as_str(), false, pipe_size)?;
let ctr = match sandbox.get_container(cid.as_str()) {
Some(v) => v,