runk: add ps sub-command

ps command supprot two formats, `json` and `table`. `json` format just
outputs pids in the container. `table` format will use `ps` utilty in
the host, search and output all processes in the container. Add a struct
`container` to represent a spawned container. Move the `kill`
implemention from kill.rs as a method of `container`.

Fixes: #4361

Signed-off-by: Chen Yiyang <cyyzero@qq.com>
This commit is contained in:
Chen Yiyang 2022-07-28 15:51:59 +08:00
parent 7503bdab6e
commit 230a229052
No known key found for this signature in database
GPG Key ID: 67736A13C4B91126
6 changed files with 122 additions and 41 deletions

View File

@ -503,11 +503,7 @@ mod tests {
let root = tempdir().unwrap(); let root = tempdir().unwrap();
// let bundle = temp // let bundle = temp
let id = "test".to_string(); let id = "test".to_string();
create_activated_dirs( create_activated_dirs(root.path(), &id, bundle_dir.path());
&root.path().to_path_buf(),
&id,
&bundle_dir.path().to_path_buf(),
);
let pid = getpid().as_raw(); let pid = getpid().as_raw();
let mut spec = create_dummy_spec(); let mut spec = create_dummy_spec();
@ -517,7 +513,7 @@ mod tests {
.to_string_lossy() .to_string_lossy()
.to_string(); .to_string();
let status = create_dummy_status(&id, pid, &root.path().to_path_buf(), &spec); let status = create_dummy_status(&id, pid, root.path(), &spec);
status.save().unwrap(); status.save().unwrap();
let result = ActivatedContainerBuilder::default() let result = ActivatedContainerBuilder::default()
@ -600,13 +596,9 @@ mod tests {
.join(TEST_ROOTFS_PATH) .join(TEST_ROOTFS_PATH)
.to_string_lossy() .to_string_lossy()
.to_string(); .to_string();
create_activated_dirs( create_activated_dirs(root.path(), &id, bundle_dir.path());
&root.path().to_path_buf(),
&id,
&bundle_dir.path().to_path_buf(),
);
let status = create_dummy_status(&id, pid, &root.path().to_path_buf(), &spec); let status = create_dummy_status(&id, pid, root.path(), &spec);
status.save().unwrap(); status.save().unwrap();
let launcher = ActivatedContainerBuilder::default() let launcher = ActivatedContainerBuilder::default()
.id(id) .id(id)

View File

@ -3,9 +3,14 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// //
use crate::status::Status; use crate::status::{self, get_all_pid, get_current_container_state, Status};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use nix::unistd::{chdir, unlink}; use nix::sys::signal::kill;
use nix::{
sys::signal::Signal,
unistd::{chdir, unlink, Pid},
};
use oci::ContainerState;
use rustjail::{ use rustjail::{
container::{BaseContainer, LinuxContainer, EXEC_FIFO_FILENAME}, container::{BaseContainer, LinuxContainer, EXEC_FIFO_FILENAME},
process::{Process, ProcessOperations}, process::{Process, ProcessOperations},
@ -26,6 +31,47 @@ pub enum ContainerAction {
Run, Run,
} }
#[derive(Debug)]
pub struct Container {
pub status: Status,
pub state: ContainerState,
}
impl Container {
pub fn load(state_root: &Path, id: &str) -> Result<Self> {
let status = Status::load(state_root, id)?;
let state = get_current_container_state(&status)?;
Ok(Self { status, state })
}
pub fn processes(&self) -> Result<Vec<Pid>> {
get_all_pid(&self.status.cgroup_manager)
}
pub fn kill(&self, signal: Signal, all: bool) -> Result<()> {
if all {
let pids = self.processes()?;
for pid in pids {
if !status::is_process_running(pid)? {
continue;
}
kill(pid, signal)?;
}
} else {
if self.state == ContainerState::Stopped {
return Err(anyhow!("container {} not running", self.status.id));
}
let pid = Pid::from_raw(self.status.pid);
if status::is_process_running(pid)? {
kill(pid, signal)?;
}
}
Ok(())
}
// TODO: add pause and resume
}
/// Used to run a process. If init is set, it will create a container and run the process in it. /// Used to run a process. If init is set, it will create a container and run the process in it.
/// If init is not set, it will run the process in an existing container. /// If init is not set, it will run the process in an existing container.
#[derive(Debug)] #[derive(Debug)]

View File

@ -4,44 +4,22 @@
// //
use crate::Kill; use crate::Kill;
use anyhow::{anyhow, Result}; use anyhow::Result;
use libcontainer::status::{self, get_current_container_state, Status}; use libcontainer::container::Container;
use nix::{ use nix::sys::signal::Signal;
sys::signal::{kill, Signal},
unistd::Pid,
};
use oci::ContainerState;
use slog::{info, Logger}; use slog::{info, Logger};
use std::{convert::TryFrom, path::Path, str::FromStr}; use std::{convert::TryFrom, path::Path, str::FromStr};
pub fn run(opts: Kill, state_root: &Path, logger: &Logger) -> Result<()> { pub fn run(opts: Kill, state_root: &Path, logger: &Logger) -> Result<()> {
let container_id = &opts.container_id; let container_id = &opts.container_id;
let status = Status::load(state_root, container_id)?; let container = Container::load(state_root, container_id)?;
let current_state = get_current_container_state(&status)?;
let sig = parse_signal(&opts.signal)?; let sig = parse_signal(&opts.signal)?;
// TODO: liboci-cli does not support --all option for kill command. // TODO: liboci-cli does not support --all option for kill command.
// After liboci-cli supports the option, we will change the following code. // After liboci-cli supports the option, we will change the following code.
// as a workaround we use a custom Kill command. // as a workaround we use a custom Kill command.
let all = opts.all; let all = opts.all;
if all { container.kill(sig, all)?;
let pids = status::get_all_pid(&status.cgroup_manager)?;
for pid in pids {
if !status::is_process_running(pid)? {
continue;
}
kill(pid, sig)?;
}
} else {
if current_state == ContainerState::Stopped {
return Err(anyhow!("container {} not running", container_id));
}
let p = Pid::from_raw(status.pid);
if status::is_process_running(p)? {
kill(p, sig)?;
}
}
info!(&logger, "kill command finished successfully"); info!(&logger, "kill command finished successfully");

View File

@ -8,6 +8,7 @@ pub mod delete;
pub mod exec; pub mod exec;
pub mod kill; pub mod kill;
pub mod list; pub mod list;
pub mod ps;
pub mod run; pub mod run;
pub mod spec; pub mod spec;
pub mod start; pub mod start;

View File

@ -0,0 +1,63 @@
// Copyright 2021-2022 Kata Contributors
//
// SPDX-License-Identifier: Apache-2.0
//
use anyhow::anyhow;
use anyhow::Result;
use libcontainer::container::Container;
use liboci_cli::Ps;
use slog::{info, Logger};
use std::path::Path;
use std::process::Command;
use std::str;
pub fn run(opts: Ps, root: &Path, logger: &Logger) -> Result<()> {
let container = Container::load(root, opts.container_id.as_str())?;
let pids = container
.processes()?
.iter()
.map(|pid| pid.as_raw())
.collect::<Vec<_>>();
match opts.format.as_str() {
"json" => println!("{}", serde_json::to_string(&pids)?),
"table" => {
let ps_options = if opts.ps_options.is_empty() {
vec!["-ef".to_string()]
} else {
opts.ps_options
};
let output = Command::new("ps").args(ps_options).output()?;
if !output.status.success() {
return Err(anyhow!("{}", std::str::from_utf8(&output.stderr)?));
}
let lines = str::from_utf8(&output.stdout)?.lines().collect::<Vec<_>>();
if lines.is_empty() {
return Err(anyhow!("no processes found"));
}
let pid_index = lines[0]
.split_whitespace()
.position(|field| field == "PID")
.ok_or_else(|| anyhow!("could't find PID field in ps output"))?;
println!("{}", lines[0]);
for &line in &lines[1..] {
if line.is_empty() {
continue;
}
let fields = line.split_whitespace().collect::<Vec<_>>();
if pid_index >= fields.len() {
continue;
}
let pid: i32 = fields[pid_index].parse()?;
if pids.contains(&pid) {
println!("{}", line);
}
}
}
_ => return Err(anyhow!("unknown format: {}", opts.format)),
}
info!(&logger, "ps command finished successfully");
Ok(())
}

View File

@ -80,6 +80,7 @@ async fn cmd_run(subcmd: SubCommand, root_path: &Path, logger: &Logger) -> Resul
CommonCmd::Spec(spec) => commands::spec::run(spec, logger), CommonCmd::Spec(spec) => commands::spec::run(spec, logger),
CommonCmd::List(list) => commands::list::run(list, root_path, logger), CommonCmd::List(list) => commands::list::run(list, root_path, logger),
CommonCmd::Exec(exec) => commands::exec::run(exec, root_path, logger).await, CommonCmd::Exec(exec) => commands::exec::run(exec, root_path, logger).await,
CommonCmd::Ps(ps) => commands::ps::run(ps, root_path, logger),
_ => { _ => {
return Err(anyhow!("command is not implemented yet")); return Err(anyhow!("command is not implemented yet"));
} }