runtime-rs: add oci hook support

According to the runtime OCI Spec, there can be some hook
operations in the lifecycle of the container. In these hook
operations, the runtime can execute some commands. There are different
points in time in the container lifecycle  and different hook types
can be executed.

In this commit, we are now supporting 4 types of hooks(same in
runtime-go): Prestart hook, CreateRuntime hook, Poststart hook and
Poststop hook.

Fixes: #5787

Signed-off-by: Yushuo <y-shuo@linux.alibaba.com>
This commit is contained in:
Yushuo 2022-11-18 19:25:09 +08:00
parent ecac3a9e10
commit 875f2db528
22 changed files with 192 additions and 8 deletions

View File

@ -154,12 +154,15 @@ fn hook_grpc_to_oci(h: &[grpcHook]) -> Vec<oci::Hook> {
fn hooks_grpc_to_oci(h: &grpc::Hooks) -> oci::Hooks {
let prestart = hook_grpc_to_oci(h.Prestart.as_ref());
let create_runtime = hook_grpc_to_oci(h.CreateRuntime.as_ref());
let poststart = hook_grpc_to_oci(h.Poststart.as_ref());
let poststop = hook_grpc_to_oci(h.Poststop.as_ref());
oci::Hooks {
prestart,
create_runtime,
poststart,
poststop,
}
@ -860,6 +863,7 @@ mod tests {
env: Vec::from([String::from("env1"), String::from("env2")]),
timeout: Some(10),
}]),
..Default::default()
},
},
TestData {
@ -908,6 +912,7 @@ mod tests {
env: Vec::from([String::from("env1"), String::from("env2")]),
timeout: Some(10),
}]),
..Default::default()
},
},
];

View File

@ -50,6 +50,8 @@ pub struct InstanceInfo {
pub vmm_version: String,
/// The pid of the current VMM process.
pub pid: u32,
/// The tid of the current VMM master thread.
pub master_tid: u32,
/// The state of async actions.
pub async_state: AsyncState,
/// List of tids of vcpu threads (vcpu index, tid)
@ -66,6 +68,7 @@ impl InstanceInfo {
state: InstanceState::Uninitialized,
vmm_version,
pid: std::process::id(),
master_tid: 0,
async_state: AsyncState::Uninitialized,
tids: Vec::new(),
last_instance_downtime: 0,
@ -80,6 +83,7 @@ impl Default for InstanceInfo {
state: InstanceState::Uninitialized,
vmm_version: env!("CARGO_PKG_VERSION").to_string(),
pid: std::process::id(),
master_tid: 0,
async_state: AsyncState::Uninitialized,
tids: Vec::new(),
last_instance_downtime: 0,

View File

@ -193,6 +193,8 @@ pub struct Hooks {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub prestart: Vec<Hook>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub create_runtime: Vec<Hook>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub poststart: Vec<Hook>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub poststop: Vec<Hook>,
@ -1401,6 +1403,7 @@ mod tests {
env: vec![],
timeout: None,
}],
..Default::default()
}),
annotations: [
("com.example.key1".to_string(), "value1".to_string()),

View File

@ -166,6 +166,9 @@ message Hooks {
// Poststop is a list of hooks to be run after the container process exits.
repeated Hook Poststop = 3 [(gogoproto.nullable) = false];
// Createruntime is a list of hooks to be run during the creation of runtime(sandbox).
repeated Hook CreateRuntime = 4 [(gogoproto.nullable) = false];
}
message Hook {

View File

@ -294,6 +294,7 @@ impl From<oci::Hooks> for crate::oci::Hooks {
fn from(from: Hooks) -> Self {
crate::oci::Hooks {
Prestart: from_vec(from.prestart),
CreateRuntime: from_vec(from.create_runtime),
Poststart: from_vec(from.poststart),
Poststop: from_vec(from.poststop),
unknown_fields: Default::default(),
@ -974,6 +975,10 @@ impl From<crate::oci::Hooks> for oci::Hooks {
for hook in from.take_Prestart().to_vec() {
prestart.push(hook.into())
}
let mut create_runtime = Vec::new();
for hook in from.take_CreateRuntime().to_vec() {
create_runtime.push(hook.into())
}
let mut poststart = Vec::new();
for hook in from.take_Poststart().to_vec() {
poststart.push(hook.into());
@ -984,6 +989,7 @@ impl From<crate::oci::Hooks> for oci::Hooks {
}
oci::Hooks {
prestart,
create_runtime,
poststart,
poststop,
}

View File

@ -1703,6 +1703,8 @@ dependencies = [
"bitflags",
"cfg-if 1.0.0",
"libc",
"memoffset 0.6.5",
"pin-utils",
]
[[package]]
@ -2428,12 +2430,15 @@ dependencies = [
"hyper",
"hyperlocal",
"hypervisor",
"kata-sys-util",
"kata-types",
"lazy_static",
"linux_container",
"logging",
"nix 0.25.1",
"oci",
"persist",
"serde_json",
"shim-interface",
"slog",
"slog-scope",

View File

@ -472,6 +472,10 @@ impl CloudHypervisorInner {
Ok(Vec::<u32>::new())
}
pub(crate) async fn get_vmm_master_tid(&self) -> Result<u32> {
todo!()
}
pub(crate) async fn check(&self) -> Result<()> {
Ok(())
}

View File

@ -118,6 +118,11 @@ impl Hypervisor for CloudHypervisor {
inner.get_pids().await
}
async fn get_vmm_master_tid(&self) -> Result<u32> {
let inner = self.inner.read().await;
inner.get_vmm_master_tid().await
}
async fn check(&self) -> Result<()> {
let inner = self.inner.read().await;
inner.check().await

View File

@ -127,6 +127,11 @@ impl DragonballInner {
Ok(Vec::from_iter(pids.into_iter()))
}
pub(crate) async fn get_vmm_master_tid(&self) -> Result<u32> {
let master_tid = self.vmm_instance.get_vmm_master_tid();
Ok(master_tid)
}
pub(crate) async fn check(&self) -> Result<()> {
Ok(())
}

View File

@ -117,6 +117,11 @@ impl Hypervisor for Dragonball {
inner.get_pids().await
}
async fn get_vmm_master_tid(&self) -> Result<u32> {
let inner = self.inner.read().await;
inner.get_vmm_master_tid().await
}
async fn check(&self) -> Result<()> {
let inner = self.inner.read().await;
inner.check().await

View File

@ -75,6 +75,12 @@ impl VmmInstance {
share_info_lock.write().unwrap().id = String::from(id);
}
pub fn get_vmm_master_tid(&self) -> u32 {
let info = self.vmm_shared_info.clone();
let result = info.read().unwrap().master_tid;
result
}
pub fn get_vcpu_tids(&self) -> Vec<(u8, u32)> {
let info = self.vmm_shared_info.clone();
let result = info.read().unwrap().tids.clone();
@ -103,6 +109,7 @@ impl VmmInstance {
Some(kvm.into_raw_fd()),
)
.expect("Failed to start vmm");
let vmm_shared_info = self.get_shared_info();
self.vmm_thread = Some(
thread::Builder::new()
@ -110,6 +117,9 @@ impl VmmInstance {
.spawn(move || {
|| -> Result<i32> {
debug!(sl!(), "run vmm thread start");
let cur_tid = nix::unistd::gettid().as_raw() as u32;
vmm_shared_info.write().unwrap().master_tid = cur_tid;
if let Some(netns_path) = netns {
info!(sl!(), "set netns for vmm master {}", &netns_path);
let netns_fd = File::open(&netns_path)

View File

@ -87,6 +87,7 @@ pub trait Hypervisor: Send + Sync {
async fn hypervisor_config(&self) -> HypervisorConfig;
async fn get_thread_ids(&self) -> Result<VcpuThreadIds>;
async fn get_pids(&self) -> Result<Vec<u32>>;
async fn get_vmm_master_tid(&self) -> Result<u32>;
async fn cleanup(&self) -> Result<()>;
async fn check(&self) -> Result<()>;
async fn get_jailer_root(&self) -> Result<String>;

View File

@ -89,6 +89,11 @@ impl QemuInner {
todo!()
}
pub(crate) async fn get_vmm_master_tid(&self) -> Result<u32> {
info!(sl!(), "QemuInner::get_vmm_master_tid()");
todo!()
}
pub(crate) async fn cleanup(&self) -> Result<()> {
info!(sl!(), "QemuInner::cleanup()");
todo!()

View File

@ -103,6 +103,11 @@ impl Hypervisor for Qemu {
inner.get_thread_ids().await
}
async fn get_vmm_master_tid(&self) -> Result<u32> {
let inner = self.inner.read().await;
inner.get_vmm_master_tid().await
}
async fn cleanup(&self) -> Result<()> {
let inner = self.inner.read().await;
inner.cleanup().await

View File

@ -13,9 +13,12 @@ slog-scope = "4.4.0"
tokio = { version = "1.8.0", features = ["rt-multi-thread"] }
hyper = { version = "0.14.20", features = ["stream", "server", "http1"] }
hyperlocal = "0.8"
serde_json = "1.0.88"
nix = "0.25.0"
common = { path = "./common" }
kata-types = { path = "../../../libs/kata-types" }
kata-sys-util = { path = "../../../libs/kata-sys-util" }
logging = { path = "../../../libs/logging"}
oci = { path = "../../../libs/oci" }
shim-interface = { path = "../../../libs/shim-interface" }

View File

@ -26,3 +26,4 @@ agent = { path = "../../agent" }
kata-sys-util = { path = "../../../../libs/kata-sys-util" }
kata-types = { path = "../../../../libs/kata-types" }
oci = { path = "../../../../libs/oci" }

View File

@ -17,6 +17,9 @@ pub trait Sandbox: Send + Sync {
// agent function
async fn agent_sock(&self) -> Result<String>;
// hypervisor function
async fn get_vmm_master_tid(&self) -> Result<u32>;
// utils
async fn set_iptables(&self, is_ipv6: bool, data: Vec<u8>) -> Result<Vec<u8>>;
async fn get_iptables(&self, is_ipv6: bool) -> Result<Vec<u8>>;

View File

@ -18,6 +18,8 @@ use hypervisor::Param;
use kata_types::{
annotations::Annotation, config::default::DEFAULT_GUEST_DNS_FILE, config::TomlConfig,
};
use kata_sys_util::hooks::HookStates;
#[cfg(feature = "linux")]
use linux_container::LinuxContainer;
use persist::sandbox_persist::Persist;
@ -81,7 +83,12 @@ impl RuntimeHandlerManagerInner {
Ok(())
}
async fn try_init(&mut self, spec: &oci::Spec, options: &Option<Vec<u8>>) -> Result<()> {
async fn try_init(
&mut self,
spec: &oci::Spec,
state: &oci::State,
options: &Option<Vec<u8>>,
) -> Result<()> {
// return if runtime instance has init
if self.runtime_instance.is_some() {
return Ok(());
@ -125,6 +132,35 @@ impl RuntimeHandlerManagerInner {
.await
.context("init runtime handler")?;
let mut st = state.clone();
if let Some(runtime_instance) = self.runtime_instance.clone() {
let vmm_master_tid = runtime_instance
.sandbox
.get_vmm_master_tid()
.await
.context("get vmm master tid")?;
st.pid = vmm_master_tid as i32;
}
// Prestart Hooks [DEPRECATED in newest oci spec]:
// * should be run in runtime namespace
// * should be run after vm is started, but before container is created
// if Prestart Hook and CreateRuntime Hook are both supported
// * spec details: https://github.com/opencontainers/runtime-spec/blob/c1662686cff159595277b79322d0272f5182941b/config.md#prestart
if let Some(hooks) = spec.hooks.as_ref() {
let mut prestart_hook_states = HookStates::new();
prestart_hook_states.execute_hooks(&hooks.prestart, Some(st.clone()))?
}
// CreateRuntime Hooks:
// * should be run in runtime namespace
// * should be run when creating the runtime
// * spec details: https://github.com/opencontainers/runtime-spec/blob/c1662686cff159595277b79322d0272f5182941b/config.md#createruntime-hooks
if let Some(hooks) = spec.hooks.as_ref() {
let mut create_runtime_hook_states = HookStates::new();
create_runtime_hook_states.execute_hooks(&hooks.create_runtime, Some(st.clone()))?
}
// the sandbox creation can reach here only once and the sandbox is created
// so we can safely create the shim management socket right now
// the unwrap here is safe because the runtime handler is correctly created
@ -207,10 +243,11 @@ impl RuntimeHandlerManager {
async fn try_init_runtime_instance(
&self,
spec: &oci::Spec,
state: &oci::State,
options: &Option<Vec<u8>>,
) -> Result<()> {
let mut inner = self.inner.write().await;
inner.try_init(spec, options).await
inner.try_init(spec, state, options).await
}
pub async fn handler_message(&self, req: Request) -> Result<Response> {
@ -222,8 +259,16 @@ impl RuntimeHandlerManager {
oci::OCI_SPEC_CONFIG_FILE_NAME
);
let spec = oci::Spec::load(&bundler_path).context("load spec")?;
let state = oci::State {
version: spec.version.clone(),
id: container_config.container_id.to_string(),
status: oci::ContainerState::Creating,
pid: 0,
bundle: bundler_path,
annotations: spec.annotations.clone(),
};
self.try_init_runtime_instance(&spec, &container_config.options)
self.try_init_runtime_instance(&spec, &state, &container_config.options)
.await
.context("try init runtime instance")?;
let instance = self

View File

@ -37,6 +37,7 @@ pub struct Container {
pid: u32,
pub container_id: ContainerID,
config: ContainerConfig,
spec: oci::Spec,
inner: Arc<RwLock<ContainerInner>>,
agent: Arc<dyn Agent>,
resource_manager: Arc<ResourceManager>,
@ -47,6 +48,7 @@ impl Container {
pub fn new(
pid: u32,
config: ContainerConfig,
spec: oci::Spec,
agent: Arc<dyn Agent>,
resource_manager: Arc<ResourceManager>,
) -> Result<Self> {
@ -67,6 +69,7 @@ impl Container {
pid,
container_id,
config,
spec,
inner: Arc::new(RwLock::new(ContainerInner::new(
agent.clone(),
init_process,
@ -382,6 +385,14 @@ impl Container {
.context("agent update container")?;
Ok(())
}
pub async fn config(&self) -> ContainerConfig {
self.config.clone()
}
pub async fn spec(&self) -> oci::Spec {
self.spec.clone()
}
}
fn amend_spec(spec: &mut oci::Spec, disable_guest_seccomp: bool) -> Result<()> {

View File

@ -5,11 +5,10 @@
//
use anyhow::{anyhow, Context, Result};
use async_trait::async_trait;
use std::{collections::HashMap, sync::Arc};
use agent::Agent;
use async_trait::async_trait;
use common::{
error::Error,
types::{
@ -19,10 +18,13 @@ use common::{
},
ContainerManager,
};
use hypervisor::Hypervisor;
use oci::Process as OCIProcess;
use resource::ResourceManager;
use tokio::sync::RwLock;
use kata_sys_util::hooks::HookStates;
use super::{logger_with_process, Container};
pub struct VirtContainerManager {
@ -31,6 +33,7 @@ pub struct VirtContainerManager {
containers: Arc<RwLock<HashMap<String, Container>>>,
resource_manager: Arc<ResourceManager>,
agent: Arc<dyn Agent>,
hypervisor: Arc<dyn Hypervisor>,
}
impl VirtContainerManager {
@ -38,6 +41,7 @@ impl VirtContainerManager {
sid: &str,
pid: u32,
agent: Arc<dyn Agent>,
hypervisor: Arc<dyn Hypervisor>,
resource_manager: Arc<ResourceManager>,
) -> Self {
Self {
@ -46,6 +50,7 @@ impl VirtContainerManager {
containers: Default::default(),
resource_manager,
agent,
hypervisor,
}
}
}
@ -56,6 +61,7 @@ impl ContainerManager for VirtContainerManager {
let container = Container::new(
self.pid,
config,
spec.clone(),
self.agent.clone(),
self.resource_manager.clone(),
)
@ -87,6 +93,26 @@ impl ContainerManager for VirtContainerManager {
let c = containers
.remove(container_id)
.ok_or_else(|| Error::ContainerNotFound(container_id.to_string()))?;
// Poststop Hooks:
// * should be run in runtime namespace
// * should be run after the container is deleted but before delete operation returns
// * spec details: https://github.com/opencontainers/runtime-spec/blob/c1662686cff159595277b79322d0272f5182941b/config.md#poststop
let c_spec = c.spec().await;
let vmm_master_tid = self.hypervisor.get_vmm_master_tid().await?;
let state = oci::State {
version: c_spec.version.clone(),
id: c.container_id.to_string(),
status: oci::ContainerState::Stopped,
pid: vmm_master_tid as i32,
bundle: c.config().await.bundle,
annotations: c_spec.annotations.clone(),
};
if let Some(hooks) = c_spec.hooks.as_ref() {
let mut poststop_hook_states = HookStates::new();
poststop_hook_states.execute_hooks(&hooks.poststop, Some(state))?;
}
c.state_process(process).await.context("state process")
}
ProcessType::Exec => {
@ -190,6 +216,26 @@ impl ContainerManager for VirtContainerManager {
.get(container_id)
.ok_or_else(|| Error::ContainerNotFound(container_id.clone()))?;
c.start(process).await.context("start")?;
// Poststart Hooks:
// * should be run in runtime namespace
// * should be run after user-specific command is executed but before start operation returns
// * spec details: https://github.com/opencontainers/runtime-spec/blob/c1662686cff159595277b79322d0272f5182941b/config.md#poststart
let c_spec = c.spec().await;
let vmm_master_tid = self.hypervisor.get_vmm_master_tid().await?;
let state = oci::State {
version: c_spec.version.clone(),
id: c.container_id.to_string(),
status: oci::ContainerState::Running,
pid: vmm_master_tid as i32,
bundle: c.config().await.bundle,
annotations: c_spec.annotations.clone(),
};
if let Some(hooks) = c_spec.hooks.as_ref() {
let mut poststart_hook_states = HookStates::new();
poststart_hook_states.execute_hooks(&hooks.poststart, Some(state))?;
}
Ok(PID { pid: self.pid })
}

View File

@ -86,13 +86,18 @@ impl RuntimeHandler for VirtContainer {
sid,
msg_sender,
agent.clone(),
hypervisor,
hypervisor.clone(),
resource_manager.clone(),
)
.await
.context("new virt sandbox")?;
let container_manager =
container_manager::VirtContainerManager::new(sid, pid, agent, resource_manager);
let container_manager = container_manager::VirtContainerManager::new(
sid,
pid,
agent,
hypervisor,
resource_manager,
);
Ok(RuntimeInstance {
sandbox: Arc::new(sandbox),
container_manager: Arc::new(container_manager),

View File

@ -278,6 +278,10 @@ impl Sandbox for VirtSandbox {
self.agent.agent_sock().await
}
async fn get_vmm_master_tid(&self) -> Result<u32> {
self.hypervisor.get_vmm_master_tid().await
}
async fn set_iptables(&self, is_ipv6: bool, data: Vec<u8>) -> Result<Vec<u8>> {
info!(sl!(), "sb: set_iptables invoked");
let req = SetIPTablesRequest { is_ipv6, data };