Add shim exit signal

Signed-off-by: Maksym Pavlenko <pavlenko.maksym@gmail.com>
This commit is contained in:
Maksym Pavlenko 2021-07-28 11:43:22 -07:00
parent 2120de8ba8
commit 9629eac9ec
3 changed files with 120 additions and 31 deletions

View File

@ -10,37 +10,46 @@ API offered by containerd's shim v2 runtime implementation written in Go.
The API is very similar to the one offered by Go version: The API is very similar to the one offered by Go version:
```rust ```rust
struct Service; struct Service {
exit: shim::ExitSignal,
}
impl shim::Shim for Service { impl shim::Shim for Service {
fn new(_id: &str, _namespace: &str, _config: &mut shim::Config) -> Self { fn new(
Service {} _id: &str,
_namespace: &str,
_publisher: shim::RemotePublisher,
_config: &mut shim::Config,
exit: shim::ExitSignal,
) -> Self {
Service { exit }
} }
fn start_shim(&mut self, opts: StartOpts) -> Result<String, Box<dyn Error>> { fn start_shim(&mut self, _opts: shim::StartOpts) -> Result<String, Box<dyn Error>> {
let address = shim::spawn(opts)?; Ok("Socket address here".into())
Ok(address)
}
fn delete_shim(&mut self) -> Result<api::DeleteResponse, Box<dyn Error>> {
todo!()
} }
} }
impl shim::Task for Service { impl shim::Task for Service {
fn create( fn create(
&self, &self,
ctx: &TtrpcContext, _ctx: &TtrpcContext,
req: api::CreateTaskRequest, _req: api::CreateTaskRequest,
) -> ::ttrpc::Result<api::CreateTaskResponse> { ) -> TtrpcResult<api::CreateTaskResponse> {
debug!("Create"); // New task nere...
Ok(api::CreateTaskResponse::default()) Ok(api::CreateTaskResponse::default())
} }
fn shutdown(&self, _ctx: &TtrpcContext, _req: api::ShutdownRequest) -> TtrpcResult<api::Empty> {
self.exit.signal(); // Signal to shutdown shim server
Ok(api::Empty::default())
}
} }
fn main() { fn main() {
shim::run::<Service>("io.containerd.empty.v1") shim::run::<Service>("io.containerd.empty.v1")
} }
``` ```
## How to use ## How to use

View File

@ -1,9 +1,12 @@
use containerd_shim as shim; use containerd_shim as shim;
use shim::{api, TtrpcContext, TtrpcResult};
use log::info; use log::info;
use shim::{api, TtrpcContext, TtrpcResult};
use std::error::Error;
struct Service; struct Service {
exit: shim::ExitSignal,
}
impl shim::Shim for Service { impl shim::Shim for Service {
fn new( fn new(
@ -11,8 +14,13 @@ impl shim::Shim for Service {
_namespace: &str, _namespace: &str,
_publisher: shim::RemotePublisher, _publisher: shim::RemotePublisher,
_config: &mut shim::Config, _config: &mut shim::Config,
exit: shim::ExitSignal,
) -> Self { ) -> Self {
Service {} Service { exit }
}
fn start_shim(&mut self, _opts: shim::StartOpts) -> Result<String, Box<dyn Error>> {
Ok("Socket address here".into())
} }
} }
@ -25,6 +33,11 @@ impl shim::Task for Service {
info!("Create"); info!("Create");
Ok(api::CreateTaskResponse::default()) Ok(api::CreateTaskResponse::default())
} }
fn shutdown(&self, _ctx: &TtrpcContext, _req: api::ShutdownRequest) -> TtrpcResult<api::Empty> {
self.exit.signal();
Ok(api::Empty::default())
}
} }
fn main() { fn main() {

View File

@ -5,11 +5,13 @@ use std::hash::Hasher;
use std::io::{self, Write}; use std::io::{self, Write};
use std::path::PathBuf; use std::path::PathBuf;
use std::process::{self, Command, Stdio}; use std::process::{self, Command, Stdio};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
use std::time; use std::time;
pub use containerd_shim_protos as protos; pub use containerd_shim_protos as protos;
use protos::protobuf::Message; use protos::protobuf::Message;
use protos::shim::{shim::DeleteResponse, shim_ttrpc::create_task}; use protos::shim::{shim::DeleteResponse, shim_ttrpc::create_task};
use protos::ttrpc::Server; use protos::ttrpc::Server;
@ -23,7 +25,11 @@ mod reap;
pub use publisher::RemotePublisher; pub use publisher::RemotePublisher;
pub use protos::shim::shim as api; pub mod api {
pub use super::protos::shim::empty::Empty;
pub use super::protos::shim::shim::*;
}
pub use protos::shim::shim_ttrpc::Task; pub use protos::shim::shim_ttrpc::Task;
pub use protos::ttrpc; pub use protos::ttrpc;
@ -39,6 +45,8 @@ pub struct Config {
pub no_sub_reaper: bool, pub no_sub_reaper: bool,
} }
/// Startup options received from containerd to start new shim instance.
/// These will be passed via [`Shim::start_shim`] to shim.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct StartOpts { pub struct StartOpts {
/// ID of the container. /// ID of the container.
@ -53,17 +61,49 @@ pub struct StartOpts {
pub namespace: String, pub namespace: String,
} }
/// Shim interface that must be implemented by clients. /// Helper structure that wraps atomic bool to signal shim server when to shutdown the TTRPC server.
pub trait Shim: Task { /// Shim implementations are responsible for calling [`Self::signal`].
fn new(id: &str, namespace: &str, publisher: RemotePublisher, config: &mut Config) -> Self; #[derive(Clone)]
pub struct ExitSignal(Arc<AtomicBool>);
/// Launch new shim. impl Default for ExitSignal {
/// See https://github.com/containerd/containerd/tree/master/runtime/v2#start fn default() -> Self {
fn start_shim(&mut self, opts: StartOpts) -> Result<String, Box<dyn error::Error>> { ExitSignal(Arc::new(AtomicBool::new(false)))
let address = spawn(opts)?; }
Ok(address) }
impl ExitSignal {
/// Set exit signal to shutdown shim server.
pub fn signal(&self) {
self.0.store(true, Ordering::Release)
} }
/// Wait for the exit signal to be set.
fn wait(&self) {
while !self.0.load(Ordering::Acquire) {
std::hint::spin_loop();
}
}
}
/// Main shim interface that must be implemented by all shims.
/// Start and delete routines will be called to handle containerd's shim lifecycle requests.
pub trait Shim: Task {
fn new(
id: &str,
namespace: &str,
publisher: RemotePublisher,
config: &mut Config,
exit: ExitSignal,
) -> Self;
/// Start shim will be called by containerd when launching new shim instance.
/// It expected to return TTRPC address containerd daemon can use to communicate with
/// the given shim instance.
/// See https://github.com/containerd/containerd/tree/master/runtime/v2#start
fn start_shim(&mut self, opts: StartOpts) -> Result<String, Box<dyn error::Error>>;
/// Delete shim will be called by containerd after shim shutdown to cleanup any leftovers.
fn delete_shim(&mut self) -> Result<DeleteResponse, Box<dyn error::Error>> { fn delete_shim(&mut self) -> Result<DeleteResponse, Box<dyn error::Error>> {
Ok(DeleteResponse::default()) Ok(DeleteResponse::default())
} }
@ -91,8 +131,15 @@ where
let publisher = publisher::RemotePublisher::new(&ttrpc_address)?; let publisher = publisher::RemotePublisher::new(&ttrpc_address)?;
// Create shim instance // Create shim instance
let exit_signal = ExitSignal::default();
let mut config = Config::default(); let mut config = Config::default();
let mut shim = T::new(id, &flags.namespace, publisher, &mut config); let mut shim = T::new(
id,
&flags.namespace,
publisher,
&mut config,
exit_signal.clone(),
);
if !config.no_sub_reaper { if !config.no_sub_reaper {
reap::set_subreaper()?; reap::set_subreaper()?;
@ -135,8 +182,7 @@ where
server.start()?; server.start()?;
// TODO: define exit criteria here. exit_signal.wait();
std::thread::sleep(std::time::Duration::from_secs(360));
server.shutdown(); server.shutdown();
@ -208,9 +254,30 @@ pub fn spawn(opts: StartOpts) -> Result<String, Error> {
]) ])
.spawn()?; .spawn()?;
// This is temp HACK. // TODO: This is hack: give TTRPC server some time to initialize. Need to pass fd instead.
// Give TTRPC server some time to initialize.
thread::sleep(time::Duration::from_secs(2)); thread::sleep(time::Duration::from_secs(2));
Ok(socket_address) Ok(socket_address)
} }
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn exit_signal() {
let signal = ExitSignal::default();
let cloned = signal.clone();
let handle = thread::spawn(move || {
cloned.signal();
});
signal.wait();
if let Err(err) = handle.join() {
panic!("{:?}", err);
}
}
}