diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 19a1a2cd3..7c3f5ed0b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -106,7 +106,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
- args: --all-features
+ args: --all-features -- --test-threads=1
doc:
name: Documentation
diff --git a/Cargo.lock b/Cargo.lock
index 3bf8a8f5a..21202c91e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -422,9 +422,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
-version = "0.8.6"
+version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120"
+checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
dependencies = [
"cfg-if",
"lazy_static",
@@ -1225,7 +1225,7 @@ dependencies = [
[[package]]
name = "northstar"
-version = "0.6.4"
+version = "0.7.0-dev"
dependencies = [
"anyhow",
"async-stream",
@@ -1246,6 +1246,7 @@ dependencies = [
"futures",
"hex",
"humanize-rs",
+ "humantime",
"inotify",
"itertools",
"lazy_static",
@@ -1287,6 +1288,7 @@ dependencies = [
"futures",
"lazy_static",
"log",
+ "nanoid",
"nix 0.23.1",
"northstar",
"regex",
@@ -2083,6 +2085,8 @@ dependencies = [
"clap",
"env_logger 0.9.0",
"futures",
+ "humantime",
+ "itertools",
"log",
"northstar",
"rand 0.8.5",
diff --git a/README.md b/README.md
index d06ec4aca..324de824d 100644
--- a/README.md
+++ b/README.md
@@ -264,7 +264,7 @@ kernel configuration with the `CONFIG_` entries in the `check_conf.sh` script.
### Container launch sequence
-**TODO**:
+
### Manifest Format
diff --git a/doc/diagrams/container_startup.png b/doc/diagrams/container_startup.png
deleted file mode 100644
index cc4157636..000000000
Binary files a/doc/diagrams/container_startup.png and /dev/null differ
diff --git a/doc/diagrams/container_startup.puml b/doc/diagrams/container_startup.puml
deleted file mode 100644
index 504cd0c31..000000000
--- a/doc/diagrams/container_startup.puml
+++ /dev/null
@@ -1,33 +0,0 @@
-@startuml container_startup
-
-activate Runtime
-Runtime -> Runtime: Check and Mount container
-create Trampoline
-Runtime -> Trampoline: Fork
-activate Trampoline
-create Init
-Trampoline -> Init: Fork
-activate Init
-Trampoline -> Runtime: Init PID
-destroy Trampoline
-Runtime -> Runtime: Wait for Trampoline exit (waitpid)
-Init -> Init: Wait for run signal (Condition::wait)
-Runtime -> Runtime: Configure cgroups
-Runtime -> Init: Signal run (Condition::notify)
-Runtime -> Runtime: Wait for execve (Condition::wait)
-Init -> Init: Mount, Chroot, UID / GID,\ndrop privileges, file descriptors
-create Container
-Init -> Container: Fork
-activate Container
-Init -> Init: Wait for container to exit (waitpid)
-Container -> Container: Set seccomp filter
-Container -> : Execve(..)
-Runtime -> Runtime: Condition pipe closed: Container is started
-note left: Condition pipe is CLOEXEC
-Container -> Init: Exit
-destroy Container
-Init -> Runtime: Exit
-Runtime -> Runtime: Read exit status from pipe or waitpid on pid of init
-destroy Init
-
-@enduml
diff --git a/examples/console/manifest.yaml b/examples/console/manifest.yaml
index 0592ddadc..e80a4dad8 100644
--- a/examples/console/manifest.yaml
+++ b/examples/console/manifest.yaml
@@ -5,10 +5,8 @@ console: true
uid: 1000
gid: 1000
io:
- stdout:
- log:
- level: DEBUG
- tag: console
+ stdout: pipe
+ stderr: pipe
mounts:
/dev:
type: dev
diff --git a/examples/cpueater/manifest.yaml b/examples/cpueater/manifest.yaml
index 07d7e716e..eceb048f6 100644
--- a/examples/cpueater/manifest.yaml
+++ b/examples/cpueater/manifest.yaml
@@ -24,7 +24,5 @@ mounts:
type: bind
host: /system
io:
- stdout:
- log:
- level: DEBUG
- tag: cpueater
+ stdout: pipe
+ stderr: pipe
diff --git a/examples/cpueater/src/main.rs b/examples/cpueater/src/main.rs
index 3ed4fbd5e..40c0de44a 100644
--- a/examples/cpueater/src/main.rs
+++ b/examples/cpueater/src/main.rs
@@ -1,7 +1,7 @@
use std::env::var;
fn main() {
- let version = var("VERSION").expect("Failed to read VERSION");
+ let version = var("NORTHSTAR_VERSION").expect("Failed to read NORTHSTAR_VERSION");
let threads = var("THREADS")
.expect("Failed to read THREADS")
.parse::()
diff --git a/examples/crashing/manifest.yaml b/examples/crashing/manifest.yaml
index f83430406..d6b5a6c65 100644
--- a/examples/crashing/manifest.yaml
+++ b/examples/crashing/manifest.yaml
@@ -5,6 +5,9 @@ uid: 1000
gid: 1000
env:
RUST_BACKTRACE: 1
+io:
+ stdout: pipe
+ stderr: discard
mounts:
/dev:
type: dev
@@ -19,8 +22,3 @@ mounts:
/system:
type: bind
host: /system
-io:
- stdout:
- log:
- level: DEBUG
- tag: crashing
diff --git a/examples/hello-ferris/manifest.yaml b/examples/hello-ferris/manifest.yaml
index 6a249e20b..b8273d7a0 100644
--- a/examples/hello-ferris/manifest.yaml
+++ b/examples/hello-ferris/manifest.yaml
@@ -37,7 +37,5 @@ mounts:
dir: /
options: noexec,nodev,nosuid
io:
- stdout:
- log:
- level: DEBUG
- tag: ferris
+ stdout: pipe
+ stderr: pipe
diff --git a/examples/hello-resource/manifest.yaml b/examples/hello-resource/manifest.yaml
index 045168037..e4533d3c7 100644
--- a/examples/hello-resource/manifest.yaml
+++ b/examples/hello-resource/manifest.yaml
@@ -23,7 +23,5 @@ mounts:
type: bind
host: /system
io:
- stdout:
- log:
- level: DEBUG
- tag: hello
+ stdout: pipe
+ stderr: pipe
diff --git a/examples/hello-world/manifest.yaml b/examples/hello-world/manifest.yaml
index e8cfc98de..49ce0cca6 100644
--- a/examples/hello-world/manifest.yaml
+++ b/examples/hello-world/manifest.yaml
@@ -6,10 +6,8 @@ gid: 1000
env:
HELLO: northstar
io:
- stdout:
- log:
- level: DEBUG
- tag: hello
+ stdout: pipe
+ stderr: pipe
mounts:
/dev:
type: dev
diff --git a/examples/hello-world/src/main.rs b/examples/hello-world/src/main.rs
index 4de022188..ae428f8ca 100644
--- a/examples/hello-world/src/main.rs
+++ b/examples/hello-world/src/main.rs
@@ -1,13 +1,9 @@
fn main() {
- let hello = std::env::var("HELLO").unwrap_or_else(|_| "unknown".into());
- let version = std::env::var("VERSION").unwrap_or_else(|_| "unknown".into());
+ let hello = std::env::var("NORTHSTAR_CONTAINER").unwrap_or_else(|_| "unknown".into());
- println!("Hello again {} from version {}!", hello, version);
+ println!("Hello again {}!", hello);
for i in 0..u64::MAX {
- println!(
- "...and hello again #{} {} from version {}...",
- i, hello, version
- );
+ println!("...and hello again #{} {} ...", i, hello);
std::thread::sleep(std::time::Duration::from_secs(1));
}
}
diff --git a/examples/inspect/manifest.yaml b/examples/inspect/manifest.yaml
index 726a2c8b9..3307b025f 100644
--- a/examples/inspect/manifest.yaml
+++ b/examples/inspect/manifest.yaml
@@ -1,17 +1,11 @@
-name: inspect
+name: inspect
version: 0.0.1
init: /inspect
uid: 1000
gid: 1000
io:
- stdout:
- log:
- level: DEBUG
- tag: inspect
- stderr:
- log:
- level: WARN
- tag: inspect
+ stdout: pipe
+ stderr: discard
mounts:
/dev:
type: dev
diff --git a/examples/memeater/manifest.yaml b/examples/memeater/manifest.yaml
index 5e9059ce4..7b2e27c0b 100644
--- a/examples/memeater/manifest.yaml
+++ b/examples/memeater/manifest.yaml
@@ -23,7 +23,5 @@ mounts:
type: bind
host: /system
io:
- stdout:
- log:
- level: DEBUG
- tag: memeater
+ stdout: pipe
+ stderr: pipe
diff --git a/examples/persistence/manifest.yaml b/examples/persistence/manifest.yaml
index 2e5595de4..19dda8c4e 100644
--- a/examples/persistence/manifest.yaml
+++ b/examples/persistence/manifest.yaml
@@ -20,7 +20,5 @@ mounts:
type: bind
host: /system
io:
- stdout:
- log:
- level: DEBUG
- tag: persistence
+ stdout: pipe
+ stderr: pipe
diff --git a/examples/seccomp/manifest.yaml b/examples/seccomp/manifest.yaml
index 3fb514724..415624770 100644
--- a/examples/seccomp/manifest.yaml
+++ b/examples/seccomp/manifest.yaml
@@ -18,10 +18,8 @@ mounts:
type: bind
host: /system
io:
- stdout:
- log:
- level: DEBUG
- tag: seccomp
+ stdout: pipe
+ stderr: pipe
seccomp:
profile:
- default
\ No newline at end of file
+ default
diff --git a/images/container-startup.png b/images/container-startup.png
index cc4157636..e698a4616 100644
Binary files a/images/container-startup.png and b/images/container-startup.png differ
diff --git a/images/container-startup.puml b/images/container-startup.puml
index 504cd0c31..d34ec7a7c 100644
--- a/images/container-startup.puml
+++ b/images/container-startup.puml
@@ -1,33 +1,69 @@
@startuml container_startup
+create Client
+activate Client
+
+create Runtime
activate Runtime
-Runtime -> Runtime: Check and Mount container
+
+create Forker
+Runtime -> Forker: Fork
+activate Forker
+
+Client -> Runtime: Connect: Hello
+Client <- Runtime: ConnectAck
+Client -> Runtime: Start container
+Runtime -> Runtime: Check and mount container(s)
+Runtime -> Runtime: Open PTY
+
+Runtime -> Forker: Create container
+
create Trampoline
-Runtime -> Trampoline: Fork
+Forker -> Trampoline: Fork
activate Trampoline
+Trampoline -> Trampoline: Create PID namespace
+
create Init
Trampoline -> Init: Fork
activate Init
-Trampoline -> Runtime: Init PID
+Init -> Init: Mount, Chroot, UID / GID,\ndrop privileges, file descriptors
+
+Trampoline -> Forker: Forked init with PID
destroy Trampoline
-Runtime -> Runtime: Wait for Trampoline exit (waitpid)
-Init -> Init: Wait for run signal (Condition::wait)
+
+Forker -> Forker: reap Trampoline
+
+Forker -> Runtime: Created init with PID
+
Runtime -> Runtime: Configure cgroups
-Runtime -> Init: Signal run (Condition::notify)
-Runtime -> Runtime: Wait for execve (Condition::wait)
-Init -> Init: Mount, Chroot, UID / GID,\ndrop privileges, file descriptors
+Runtime -> Runtime: Configure debug
+Runtime -> Runtime: Configure PTY forward
+
+Runtime -> Forker: Exec container
+Forker -> Init: Exec Container
create Container
Init -> Container: Fork
activate Container
+Forker <- Init: Exec
+Runtime <- Forker: Exec
+Client <- Runtime: Started
+Client <- Runtime: Notification: Started
+
Init -> Init: Wait for container to exit (waitpid)
+Container -> Container: Setup PTY
Container -> Container: Set seccomp filter
Container -> : Execve(..)
-Runtime -> Runtime: Condition pipe closed: Container is started
-note left: Condition pipe is CLOEXEC
-Container -> Init: Exit
+...
+Container -> Init: SIGCHLD
destroy Container
-Init -> Runtime: Exit
-Runtime -> Runtime: Read exit status from pipe or waitpid on pid of init
+
+Init -> Init: waitpid: Exit status of container
+Init -> Forker: Container exit status
destroy Init
+Forker -> Runtime: Container exit status
+Runtime -> Runtime: Stop PTY thread
+Runtime -> Runtime: Destroy cgroups
+Client <- Runtime: Notification: Exit
+
@enduml
diff --git a/main/Cargo.toml b/main/Cargo.toml
index f321b6b7a..0070c2cb8 100644
--- a/main/Cargo.toml
+++ b/main/Cargo.toml
@@ -16,7 +16,7 @@ clap = { version = "3.1.0", features = ["derive"] }
log = "0.4.14"
nix = "0.23.0"
northstar = { path = "../northstar", features = ["runtime"] }
-tokio = { version = "1.17.0", features = ["rt", "macros", "signal"] }
+tokio = { version = "1.17.0", features = ["rt-multi-thread", "macros", "signal"] }
toml = "0.5.8"
[target.'cfg(not(target_os = "android"))'.dependencies]
diff --git a/main/src/logger.rs b/main/src/logger.rs
index a3a486779..b9ee80617 100644
--- a/main/src/logger.rs
+++ b/main/src/logger.rs
@@ -9,7 +9,7 @@ pub fn init() {
}
#[cfg(not(target_os = "android"))]
-static TAG_SIZE: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(20);
+static TAG_SIZE: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(28);
/// Initialize the logger
#[cfg(not(target_os = "android"))]
@@ -17,51 +17,63 @@ pub fn init() {
use env_logger::fmt::Color;
use std::{io::Write, sync::atomic::Ordering};
+ fn color(target: &str) -> Color {
+ // Some colors are hard to read on (at least) dark terminals
+ // and I consider some others as ugly ;-)
+ let hash = target.bytes().fold(42u8, |c, x| c ^ x);
+ Color::Ansi256(match hash {
+ c @ 0..=1 => c + 2,
+ c @ 16..=21 => c + 6,
+ c @ 52..=55 | c @ 126..=129 => c + 4,
+ c @ 163..=165 | c @ 200..=201 => c + 3,
+ c @ 207 => c + 1,
+ c @ 232..=240 => c + 9,
+ c => c,
+ })
+ }
+
let mut builder = env_logger::Builder::new();
builder.parse_filters("northstar=debug");
builder.format(|buf, record| {
- let mut style = buf.style();
+ let timestamp = buf.timestamp_millis().to_string();
+ let timestamp = timestamp.strip_suffix('Z').unwrap();
+
+ let mut level = buf.default_level_style(record.metadata().level());
+ level.set_bold(true);
+ let level = level.value(record.metadata().level().as_str());
- let timestamp = buf.timestamp_millis();
- let level = buf.default_styled_level(record.metadata().level());
+ let pid = std::process::id().to_string();
+ let mut pid_style = buf.style();
+ pid_style.set_color(color(&pid));
- if let Some(module_path) = record
- .module_path()
+ if let Some(target) = Option::from(record.target().is_empty())
+ .map(|_| record.target())
+ .or_else(|| record.module_path())
.and_then(|module_path| module_path.find(&"::").map(|p| &module_path[p + 2..]))
{
- TAG_SIZE.fetch_max(module_path.len(), Ordering::SeqCst);
+ let mut tag_style = buf.style();
+ TAG_SIZE.fetch_max(target.len(), Ordering::SeqCst);
let tag_size = TAG_SIZE.load(Ordering::SeqCst);
- fn hashed_color(i: &str) -> Color {
- // Some colors are hard to read on (at least) dark terminals
- // and I consider some others as ugly ;-)
- Color::Ansi256(match i.bytes().fold(42u8, |c, x| c ^ x) {
- c @ 0..=1 => c + 2,
- c @ 16..=21 => c + 6,
- c @ 52..=55 | c @ 126..=129 => c + 4,
- c @ 163..=165 | c @ 200..=201 => c + 3,
- c @ 207 => c + 1,
- c @ 232..=240 => c + 9,
- c => c,
- })
- }
- style.set_color(hashed_color(module_path));
+ tag_style.set_color(color(target));
writeln!(
buf,
- "{}: {:>s$} {:<5}: {}",
+ "{} {:>s$} {} {:<5}: {}",
timestamp,
- style.value(module_path),
+ tag_style.value(target),
+ pid_style.value("⬤"),
level,
record.args(),
- s = tag_size
+ s = tag_size,
)
} else {
writeln!(
buf,
- "{}: {} {:<5}: {}",
+ "{} {} {} {:<5}: {}",
timestamp,
" ".repeat(TAG_SIZE.load(Ordering::SeqCst)),
+ pid_style.value("⬤"),
level,
record.args(),
)
diff --git a/main/src/main.rs b/main/src/main.rs
index dba0c49f2..80547ae73 100644
--- a/main/src/main.rs
+++ b/main/src/main.rs
@@ -7,7 +7,7 @@ use anyhow::{anyhow, Context, Error};
use clap::Parser;
use log::{debug, info, warn};
use nix::mount::MsFlags;
-use northstar::runtime;
+use northstar::{runtime, runtime::Runtime as Northstar};
use runtime::config::Config;
use std::{
fs::{self, read_to_string},
@@ -32,16 +32,31 @@ struct Opt {
pub disable_mount_namespace: bool,
}
-#[tokio::main(flavor = "current_thread")]
-async fn main() -> Result<(), Error> {
+fn main() -> Result<(), Error> {
+ // Initialize logging
+ logger::init();
+
+ // Parse command line arguments and prepare the environment
+ let config = init()?;
+
+ // Create the runtime launcher. This must be done *before* spawning the tokio threadpool.
+ let northstar = Northstar::new(config)?;
+
+ tokio::runtime::Builder::new_multi_thread()
+ .enable_all()
+ .thread_name("northstar")
+ .build()
+ .context("Failed to create runtime")?
+ .block_on(run(northstar))
+}
+
+fn init() -> Result {
let opt = Opt::parse();
let config = read_to_string(&opt.config)
.with_context(|| format!("Failed to read configuration file {}", opt.config.display()))?;
let config: Config = toml::from_str(&config)
.with_context(|| format!("Failed to read configuration file {}", opt.config.display()))?;
- logger::init();
-
fs::create_dir_all(&config.run_dir).context("Failed to create run_dir")?;
fs::create_dir_all(&config.data_dir).context("Failed to create data_dir")?;
fs::create_dir_all(&config.log_dir).context("Failed to create log dir")?;
@@ -64,9 +79,15 @@ async fn main() -> Result<(), Error> {
debug!("Mount namespace is disabled");
}
- let mut runtime = runtime::Runtime::start(config)
+ Ok(config)
+}
+
+async fn run(northstar: Northstar) -> Result<(), Error> {
+ let mut runtime = northstar
+ .start()
.await
- .context("Failed to start runtime")?;
+ .context("Failed to start Northstar")?;
+
let mut sigint = tokio::signal::unix::signal(SignalKind::interrupt())
.context("Failed to install sigint handler")?;
let mut sigterm = tokio::signal::unix::signal(SignalKind::terminate())
@@ -87,7 +108,7 @@ async fn main() -> Result<(), Error> {
info!("Received SIGHUP. Stopping Northstar runtime");
runtime.shutdown().await
}
- status = &mut runtime => status,
+ status = runtime.stopped() => status,
};
match status {
diff --git a/northstar-tests/Cargo.toml b/northstar-tests/Cargo.toml
index c73648d94..309c2e3ac 100644
--- a/northstar-tests/Cargo.toml
+++ b/northstar-tests/Cargo.toml
@@ -11,6 +11,7 @@ env_logger = "0.9.0"
futures = "0.3.21"
lazy_static = "1.4.0"
log = "0.4.14"
+nanoid = "0.4.0"
nix = "0.23.0"
northstar = { path = "../northstar", features = ["api", "runtime"] }
regex = "1.5.4"
diff --git a/northstar-tests/src/macros.rs b/northstar-tests/src/macros.rs
index aab9a3d8d..e5e78bb5f 100644
--- a/northstar-tests/src/macros.rs
+++ b/northstar-tests/src/macros.rs
@@ -1,38 +1,3 @@
-use super::logger;
-use nix::{mount, sched};
-use sched::{unshare, CloneFlags};
-
-pub fn init() {
- logger::init();
- log::set_max_level(log::LevelFilter::Debug);
-
- // Enter a mount namespace. This needs to be done before spawning
- // the tokio threadpool.
- unshare(CloneFlags::CLONE_NEWNS).unwrap();
-
- // Set the mount propagation to private on root. This ensures that *all*
- // mounts get cleaned up upon process termination. The approach to bind
- // mount the run_dir only (this is where the mounts from northstar happen)
- // doesn't work for the tests since the run_dir is a tempdir which is a
- // random dir on every run. Checking at the beginning of the tests if
- // run_dir is bind mounted - a leftover from a previous crash - obviously
- // doesn't work. Technically, it is only necessary set the propagation of
- // the parent mount of the run_dir, but this not easy to find and the change
- // of mount propagation on root is fine for the tests which are development
- // only.
- mount::mount(
- Some("/"),
- "/",
- Option::<&str>::None,
- mount::MsFlags::MS_PRIVATE | mount::MsFlags::MS_REC,
- Option::<&'static [u8]>::None,
- )
- .expect(
- "Failed to set mount propagation to private on
- root",
- );
-}
-
/// Northstar integration test
#[macro_export]
macro_rules! test {
@@ -41,15 +6,52 @@ macro_rules! test {
#![rusty_fork(timeout_ms = 300000)]
#[test]
fn $name() {
- northstar_tests::macros::init();
- match tokio::runtime::Builder::new_current_thread()
+ crate::logger::init();
+ log::set_max_level(log::LevelFilter::Debug);
+
+ // Enter a mount namespace. This needs to be done before spawning
+ // the tokio threadpool.
+ nix::sched::unshare(nix::sched::CloneFlags::CLONE_NEWNS).unwrap();
+
+ // Set the mount propagation to private on root. This ensures that *all*
+ // mounts get cleaned up upon process termination. The approach to bind
+ // mount the run_dir only (this is where the mounts from northstar happen)
+ // doesn't work for the tests since the run_dir is a tempdir which is a
+ // random dir on every run. Checking at the beginning of the tests if
+ // run_dir is bind mounted - a leftover from a previous crash - obviously
+ // doesn't work. Technically, it is only necessary set the propagation of
+ // the parent mount of the run_dir, but this not easy to find and the change
+ // of mount propagation on root is fine for the tests which are development
+ // only.
+ nix::mount::mount(
+ Some("/"),
+ "/",
+ Option::<&str>::None,
+ nix::mount::MsFlags::MS_PRIVATE | nix::mount::MsFlags::MS_REC,
+ Option::<&'static [u8]>::None,
+ )
+ .expect(
+ "Failed to set mount propagation to private on
+ root",
+ );
+ let runtime = northstar_tests::runtime::Runtime::new().expect("Failed to start runtime");
+
+ match tokio::runtime::Builder::new_multi_thread()
+ .worker_threads(1)
.enable_all()
.thread_name(stringify!($name))
.build()
.expect("Failed to start runtime")
- .block_on(async { $e }) {
+ .block_on(async {
+ let runtime = runtime.start().await?;
+ $e
+ northstar_tests::runtime::client().shutdown().await?;
+ drop(runtime);
+ tokio::fs::remove_file(northstar_tests::runtime::console().path()).await?;
+ Ok(())
+ }) {
Ok(_) => std::process::exit(0),
- Err(e) => panic!("{}", e),
+ anyhow::Result::<()>::Err(e) => panic!("{}", e),
}
}
}
diff --git a/northstar-tests/src/runtime.rs b/northstar-tests/src/runtime.rs
index 018a4cf4c..aba9d230d 100644
--- a/northstar-tests/src/runtime.rs
+++ b/northstar-tests/src/runtime.rs
@@ -3,15 +3,16 @@
use super::{containers::*, logger};
use anyhow::{anyhow, Context, Result};
use futures::StreamExt;
+use nanoid::nanoid;
use northstar::{
api::{
- client::Client,
+ client,
model::{Container, ExitStatus, Notification},
},
common::non_null_string::NonNullString,
runtime::{
- self,
config::{self, Config, RepositoryType},
+ Runtime as Northstar,
},
};
use std::{
@@ -21,49 +22,35 @@ use std::{
use tempfile::{NamedTempFile, TempDir};
use tokio::{fs, net::UnixStream, pin, select, time};
-pub struct Northstar {
- /// Runtime configuration
- pub config: Config,
- /// Runtime console address (Unix socket)
- pub console: String,
- /// Client instance
- client: northstar::api::client::Client,
- /// Runtime instance
- runtime: runtime::Runtime,
- /// Tmpdir for NPK dumps
- tmpdir: TempDir,
-}
+pub static mut CLIENT: Option = None;
-impl std::ops::Deref for Northstar {
- type Target = Client;
+pub fn client() -> &'static mut Client {
+ unsafe { CLIENT.as_mut().unwrap() }
+}
- fn deref(&self) -> &Self::Target {
- &self.client
- }
+pub fn console() -> url::Url {
+ let console = std::env::temp_dir().join(format!("northstar-{}", std::process::id()));
+ url::Url::parse(&format!("unix://{}", console.display())).unwrap()
}
-impl std::ops::DerefMut for Northstar {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.client
- }
+pub enum Runtime {
+ Created(Northstar, TempDir),
+ Started(Northstar, TempDir),
}
-impl Northstar {
- /// Launches an instance of Northstar
- pub async fn launch() -> Result {
- let pid = std::process::id();
+impl Runtime {
+ pub fn new() -> Result {
let tmpdir = tempfile::Builder::new().prefix("northstar-").tempdir()?;
-
let run_dir = tmpdir.path().join("run");
- fs::create_dir(&run_dir).await?;
+ std::fs::create_dir(&run_dir)?;
let data_dir = tmpdir.path().join("data");
- fs::create_dir(&data_dir).await?;
+ std::fs::create_dir(&data_dir)?;
let log_dir = tmpdir.path().join("log");
- fs::create_dir(&log_dir).await?;
+ std::fs::create_dir(&log_dir)?;
let test_repository = tmpdir.path().join("test");
- fs::create_dir(&test_repository).await?;
+ std::fs::create_dir(&test_repository)?;
let example_key = tmpdir.path().join("key.pub");
- fs::write(&example_key, include_bytes!("../../examples/northstar.pub")).await?;
+ std::fs::write(&example_key, include_bytes!("../../examples/northstar.pub"))?;
let mut repositories = HashMap::new();
repositories.insert(
@@ -77,71 +64,84 @@ impl Northstar {
"test-1".into(),
config::Repository {
r#type: RepositoryType::Memory,
- key: Some(example_key.clone()),
+ key: Some(example_key),
},
);
- let console = format!(
- "{}/northstar-{}",
- tmpdir.path().display(),
- std::process::id()
- );
- let console_url = url::Url::parse(&format!("unix://{}", console))?;
-
let config = Config {
- console: Some(vec![console_url.clone()]),
+ console: Some(vec![console()]),
run_dir,
- data_dir: data_dir.clone(),
+ data_dir,
log_dir,
mount_parallel: 10,
- cgroup: NonNullString::try_from(format!("northstar-{}", pid)).unwrap(),
+ cgroup: NonNullString::try_from(format!("northstar-{}", nanoid!())).unwrap(),
repositories,
debug: None,
};
+ let b = Northstar::new(config)?;
- // Start the runtime
- let runtime = runtime::Runtime::start(config.clone())
- .await
- .context("Failed to start runtime")?;
- // Wait until the console is up and running
- super::logger::assume("Started console on", 5u64).await?;
+ Ok(Runtime::Created(b, tmpdir))
+ }
+
+ pub async fn start(self) -> Result {
+ if let Runtime::Created(launcher, tmpdir) = self {
+ let runtime = launcher.start().await?;
+ logger::assume("Runtime up and running", 10u64).await?;
+
+ unsafe {
+ CLIENT = Some(Client::new().await?);
+ }
+
+ Ok(Runtime::Started(runtime, tmpdir))
+ } else {
+ anyhow::bail!("Runtime is already started")
+ }
+ }
+}
+
+pub struct Client {
+ /// Client instance
+ client: northstar::api::client::Client,
+}
+
+impl std::ops::Deref for Client {
+ type Target = client::Client;
+ fn deref(&self) -> &Self::Target {
+ &self.client
+ }
+}
+
+impl std::ops::DerefMut for Client {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.client
+ }
+}
+
+impl Client {
+ /// Launches an instance of Northstar
+ pub async fn new() -> Result {
// Connect to the runtime
- let io = UnixStream::connect(&console)
+ let io = UnixStream::connect(console().path())
.await
.expect("Failed to connect to console");
- let client = Client::new(io, Some(1000), time::Duration::from_secs(30)).await?;
+ let client = client::Client::new(io, Some(1000), time::Duration::from_secs(30)).await?;
// Wait until a successful connection
logger::assume("Client .* connected", 5u64).await?;
- Ok(Northstar {
- config,
- console,
- client,
- runtime,
- tmpdir,
- })
+ Ok(Client { client })
}
/// Connect a new client instance to the runtime
- pub async fn client(&self) -> Result> {
- let io = UnixStream::connect(&self.console)
+ pub async fn client(&self) -> Result> {
+ let io = UnixStream::connect(console().path())
.await
.context("Failed to connect to console")?;
- Client::new(io, Some(1000), time::Duration::from_secs(30))
+ client::Client::new(io, Some(1000), time::Duration::from_secs(30))
.await
.context("Failed to create client")
}
- /// Launches an instance of Northstar with the test container and
- /// resource installed.
- pub async fn launch_install_test_container() -> Result {
- let mut runtime = Self::launch().await?;
- runtime.install_test_resource().await?;
- runtime.install_test_container().await?;
- Ok(runtime)
- }
-
pub async fn stop(&mut self, container: &str, timeout: u64) -> Result<()> {
self.client.kill(container, 15).await?;
let container: Container = container.try_into()?;
@@ -158,26 +158,15 @@ impl Northstar {
Ok(())
}
- pub async fn shutdown(self) -> Result<()> {
- // Dropping the client closes the connection to the runtime
- drop(self.client);
-
- // Stop the runtime
- self.runtime
- .shutdown()
- .await
- .context("Failed to stop the runtime")?;
-
- logger::assume("Closed listener", 5u64).await?;
-
- // Remove the tmpdir
- self.tmpdir.close().expect("Failed to remove tmpdir");
+ pub async fn shutdown(&mut self) -> Result<()> {
+ drop(self.client.shutdown().await);
+ logger::assume("Shutdown complete", 5u64).await?;
Ok(())
}
// Install a npk from a buffer
pub async fn install(&mut self, npk: &[u8], repository: &str) -> Result<()> {
- let f = NamedTempFile::new_in(self.tmpdir.path())?;
+ let f = NamedTempFile::new()?;
fs::write(&f, npk).await?;
self.client.install(f.path(), repository).await?;
Ok(())
diff --git a/northstar-tests/test-container/manifest.yaml b/northstar-tests/test-container/manifest.yaml
index c28409031..2487a077d 100644
--- a/northstar-tests/test-container/manifest.yaml
+++ b/northstar-tests/test-container/manifest.yaml
@@ -3,6 +3,9 @@ version: 0.0.1
init: /test-container
uid: 1000
gid: 1000
+io:
+ stdout: pipe
+ stderr: pipe
# cgroups:
# memory:
# limit_in_bytes: 10000000
@@ -35,15 +38,6 @@ mounts:
version: 0.0.1
dir: test
options: nosuid,nodev,noexec
-io:
- stdout:
- log:
- level: DEBUG
- tag: test-container
- stderr:
- log:
- level: DEBUG
- tag: test-container
rlimits:
nproc:
soft: 10000
diff --git a/northstar-tests/test-container/src/main.rs b/northstar-tests/test-container/src/main.rs
index 4d2e68b7b..def03d394 100644
--- a/northstar-tests/test-container/src/main.rs
+++ b/northstar-tests/test-container/src/main.rs
@@ -18,6 +18,22 @@ struct Opt {
command: Option,
}
+#[derive(Debug)]
+enum Io {
+ Stdout,
+ Stderr,
+}
+
+impl From<&str> for Io {
+ fn from(s: &str) -> Io {
+ match s {
+ "stdout" => Io::Stdout,
+ "stderr" => Io::Stderr,
+ _ => panic!("Invalid io: {}", s),
+ }
+ }
+}
+
#[derive(Debug, Parser)]
enum Command {
Cat {
@@ -25,13 +41,15 @@ enum Command {
path: PathBuf,
},
Crash,
- Echo {
- message: Vec,
- },
Exit {
code: i32,
},
Inspect,
+ Print {
+ message: String,
+ #[structopt(short, long, parse(from_str), default_value = "stdout")]
+ io: Io,
+ },
Touch {
path: PathBuf,
},
@@ -49,15 +67,15 @@ fn main() -> Result<()> {
let command = Opt::parse().command.unwrap_or(Command::Sleep);
println!("Executing \"{:?}\"", command);
match command {
+ Command::CallDeleteModule { flags } => call_delete_module(flags)?,
Command::Cat { path } => cat(&path)?,
Command::Crash => crash(),
- Command::Echo { message } => echo(&message),
Command::Exit { code } => exit(code),
Command::Inspect => inspect(),
- Command::Touch { path } => touch(&path)?,
+ Command::Print { message, io } => print(&message, &io),
Command::Sleep => (),
+ Command::Touch { path } => touch(&path)?,
Command::Write { message, path } => write(&message, path.as_path())?,
- Command::CallDeleteModule { flags } => call_delete_module(flags)?,
};
sleep();
@@ -92,8 +110,11 @@ fn crash() {
panic!("witness me!");
}
-fn echo(message: &[String]) {
- println!("{}", message.join(" "));
+fn print(message: &str, io: &Io) {
+ match io {
+ Io::Stdout => println!("{}", message),
+ Io::Stderr => eprintln!("{}", message),
+ }
}
fn exit(code: i32) {
diff --git a/northstar-tests/tests/examples.rs b/northstar-tests/tests/examples.rs
index 836445616..718917e2a 100644
--- a/northstar-tests/tests/examples.rs
+++ b/northstar-tests/tests/examples.rs
@@ -1,13 +1,12 @@
use logger::assume;
use northstar::api::model::{ExitStatus, Notification};
-use northstar_tests::{containers::*, logger, runtime::Northstar, test};
+use northstar_tests::{containers::*, logger, runtime::client, test};
// Start crashing example
test!(crashing, {
- let mut runtime = Northstar::launch().await?;
- runtime.install(EXAMPLE_CRASHING_NPK, "test-0").await?;
- runtime.start(EXAMPLE_CRASHING).await?;
- runtime
+ client().install(EXAMPLE_CRASHING_NPK, "test-0").await?;
+ client().start(EXAMPLE_CRASHING).await?;
+ client()
.assume_notification(
|n| {
matches!(
@@ -21,43 +20,39 @@ test!(crashing, {
20,
)
.await?;
- runtime.shutdown().await
});
// Start console example
test!(console, {
- let mut runtime = Northstar::launch().await?;
- runtime.install(EXAMPLE_CONSOLE_NPK, "test-0").await?;
- runtime.start(EXAMPLE_CONSOLE).await?;
+ client().install(EXAMPLE_CONSOLE_NPK, "test-0").await?;
+ client().start(EXAMPLE_CONSOLE).await?;
// The console example stop itself - so wait for it...
assume("Client console:0.0.1 connected", 5).await?;
assume("Killing console:0.0.1 with SIGTERM", 5).await?;
- runtime.shutdown().await
});
// Start cpueater example and assume log message
test!(cpueater, {
- let mut runtime = Northstar::launch().await?;
- runtime.install(EXAMPLE_CPUEATER_NPK, "test-0").await?;
- runtime.start(EXAMPLE_CPUEATER).await?;
+ client().install(EXAMPLE_CPUEATER_NPK, "test-0").await?;
+ client().start(EXAMPLE_CPUEATER).await?;
assume("Eating CPU", 5).await?;
- runtime.stop(EXAMPLE_CPUEATER, 10).await?;
- runtime.shutdown().await
+ client().stop(EXAMPLE_CPUEATER, 10).await?;
});
// Start hello-ferris example
test!(hello_ferris, {
- let mut runtime = Northstar::launch().await?;
- runtime.install(EXAMPLE_FERRIS_NPK, "test-0").await?;
- runtime.install(EXAMPLE_MESSAGE_0_0_1_NPK, "test-0").await?;
- runtime.install(EXAMPLE_HELLO_FERRIS_NPK, "test-0").await?;
- runtime.start(EXAMPLE_HELLO_FERRIS).await?;
+ client().install(EXAMPLE_FERRIS_NPK, "test-0").await?;
+ client()
+ .install(EXAMPLE_MESSAGE_0_0_1_NPK, "test-0")
+ .await?;
+ client().install(EXAMPLE_HELLO_FERRIS_NPK, "test-0").await?;
+ client().start(EXAMPLE_HELLO_FERRIS).await?;
assume("Hello once more from 0.0.1!", 5).await?;
// The hello-ferris example terminates after printing something.
- // Wait for the notification that it stopped, otherwise the runtime
+ // Wait for the notification that it stopped, otherwise the client()
// will try to shutdown the application which is already exited.
- runtime
+ client()
.assume_notification(
|n| {
matches!(
@@ -71,18 +66,17 @@ test!(hello_ferris, {
15,
)
.await?;
-
- runtime.shutdown().await
});
// Start hello-resource example
test!(hello_resource, {
- let mut runtime = Northstar::launch().await?;
- runtime.install(EXAMPLE_MESSAGE_0_0_2_NPK, "test-0").await?;
- runtime
+ client()
+ .install(EXAMPLE_MESSAGE_0_0_2_NPK, "test-0")
+ .await?;
+ client()
.install(EXAMPLE_HELLO_RESOURCE_NPK, "test-0")
.await?;
- runtime.start(EXAMPLE_HELLO_RESOURCE).await?;
+ client().start(EXAMPLE_HELLO_RESOURCE).await?;
assume(
"0: Content of /message/hello: Hello once more from v0.0.2!",
5,
@@ -93,42 +87,33 @@ test!(hello_resource, {
5,
)
.await?;
- runtime.shutdown().await
});
// Start inspect example
test!(inspect, {
- let mut runtime = Northstar::launch().await?;
- runtime.install(EXAMPLE_INSPECT_NPK, "test-0").await?;
- runtime.start(EXAMPLE_INSPECT).await?;
- runtime.stop(EXAMPLE_INSPECT, 5).await?;
- // TODO
- runtime.shutdown().await
+ client().install(EXAMPLE_INSPECT_NPK, "test-0").await?;
+ client().start(EXAMPLE_INSPECT).await?;
+ client().stop(EXAMPLE_INSPECT, 5).await?;
});
// Start memeater example
// test!(memeater, {
-// let mut runtime = Northstar::launch().await?;
-// runtime.install(&EXAMPLE_MEMEATER_NPK, "test-0").await?;
-// runtime.start(EXAMPLE_MEMEATER).await?;
-// assume("Process memeater:0.0.1 is out of memory", 20).await?;
-// runtime.shutdown().await
+// let mut client() = Northstar::launch().await?;
+// client().install(&EXAMPLE_MEMEATER_NPK, "test-0").await?;
+// client().start(EXAMPLE_MEMEATER).await?;
+// assume("Process memeater:0.0.1 is out of memory", 20).await
// });
// Start persistence example and check output
test!(persistence, {
- let mut runtime = Northstar::launch().await?;
- runtime.install(EXAMPLE_PERSISTENCE_NPK, "test-0").await?;
- runtime.start(EXAMPLE_PERSISTENCE).await?;
+ client().install(EXAMPLE_PERSISTENCE_NPK, "test-0").await?;
+ client().start(EXAMPLE_PERSISTENCE).await?;
assume("Writing Hello! to /data/file", 5).await?;
assume("Content of /data/file: Hello!", 5).await?;
- runtime.shutdown().await
});
// Start seccomp example
test!(seccomp, {
- let mut runtime = Northstar::launch().await?;
- runtime.install(EXAMPLE_SECCOMP_NPK, "test-0").await?;
- runtime.start(EXAMPLE_SECCOMP).await?;
- runtime.shutdown().await
+ client().install(EXAMPLE_SECCOMP_NPK, "test-0").await?;
+ client().start(EXAMPLE_SECCOMP).await?;
});
diff --git a/northstar-tests/tests/tests.rs b/northstar-tests/tests/tests.rs
index f47403866..505c67556 100644
--- a/northstar-tests/tests/tests.rs
+++ b/northstar-tests/tests/tests.rs
@@ -1,4 +1,5 @@
-use anyhow::Result;
+use std::path::{Path, PathBuf};
+
use futures::{SinkExt, StreamExt};
use log::debug;
use logger::assume;
@@ -6,8 +7,7 @@ use northstar::api::{
self,
model::{self, ConnectNack, ExitStatus, Notification},
};
-use northstar_tests::{containers::*, logger, runtime::Northstar, test};
-use std::path::{Path, PathBuf};
+use northstar_tests::{containers::*, logger, runtime::client, test};
use tokio::{
io::{AsyncRead, AsyncWrite},
net::UnixStream,
@@ -18,175 +18,198 @@ test!(logger_smoketest, {
debug!("Yippie");
assume("Yippie", 3).await?;
assert!(assume("Juhuuu!", 1).await.is_err());
- Result::<()>::Ok(())
-});
-
-// Smoke test the runtime startup and shutdown
-test!(runtime_launch, {
- Northstar::launch().await?.shutdown().await
});
// Install and uninstall is a loop. After a number of installation
// try to start the test container
test!(install_uninstall_test_container, {
- let mut runtime = Northstar::launch().await?;
for _ in 0u32..10 {
- runtime.install_test_container().await?;
- runtime.uninstall_test_container().await?;
+ client().install_test_container().await?;
+ client().uninstall_test_container().await?;
}
- runtime.shutdown().await
});
// Install a container that already exists with the same name and version
test!(install_duplicate, {
- let mut runtime = Northstar::launch().await?;
- runtime.install_test_container().await?;
- assert!(runtime.install_test_container().await.is_err());
- runtime.shutdown().await
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+ assert!(client().install_test_container().await.is_err());
});
// Install a container that already exists in another repository
test!(install_duplicate_other_repository, {
- let mut runtime = Northstar::launch().await?;
- runtime.install(TEST_CONTAINER_NPK, "test-0").await?;
- assert!(runtime.install(TEST_CONTAINER_NPK, "test-1").await.is_err());
- runtime.shutdown().await
+ client().install(TEST_CONTAINER_NPK, "test-0").await?;
+ assert!(client()
+ .install(TEST_CONTAINER_NPK, "test-1")
+ .await
+ .is_err());
});
// Start and stop a container multiple times
test!(start_stop, {
- let mut runtime = Northstar::launch_install_test_container().await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
for _ in 0..10u32 {
- runtime.start_with_args(TEST_CONTAINER, ["sleep"]).await?;
+ client().start_with_args(TEST_CONTAINER, ["sleep"]).await?;
assume("Sleeping", 5u64).await?;
- runtime.stop(TEST_CONTAINER, 5).await?;
+ client().stop(TEST_CONTAINER, 5).await?;
assume("Process test-container:0.0.1 exited", 5).await?;
}
+});
- runtime.shutdown().await
+// Install and uninsteall the example npks
+test!(install_uninstall_examples, {
+ client().install(EXAMPLE_CPUEATER_NPK, "test-0").await?;
+ client().install(EXAMPLE_CONSOLE_NPK, "test-0").await?;
+ client().install(EXAMPLE_CRASHING_NPK, "test-0").await?;
+ client().install(EXAMPLE_FERRIS_NPK, "test-0").await?;
+ client().install(EXAMPLE_HELLO_FERRIS_NPK, "test-0").await?;
+ client()
+ .install(EXAMPLE_HELLO_RESOURCE_NPK, "test-0")
+ .await?;
+ client().install(EXAMPLE_INSPECT_NPK, "test-0").await?;
+ client().install(EXAMPLE_MEMEATER_NPK, "test-0").await?;
+ client()
+ .install(EXAMPLE_MESSAGE_0_0_1_NPK, "test-0")
+ .await?;
+ client()
+ .install(EXAMPLE_MESSAGE_0_0_2_NPK, "test-0")
+ .await?;
+ client().install(EXAMPLE_PERSISTENCE_NPK, "test-0").await?;
+ client().install(EXAMPLE_SECCOMP_NPK, "test-0").await?;
+ client().install(TEST_CONTAINER_NPK, "test-0").await?;
+ client().install(TEST_RESOURCE_NPK, "test-0").await?;
+
+ client().uninstall(EXAMPLE_CPUEATER).await?;
+ client().uninstall(EXAMPLE_CONSOLE).await?;
+ client().uninstall(EXAMPLE_CRASHING).await?;
+ client().uninstall(EXAMPLE_FERRIS).await?;
+ client().uninstall(EXAMPLE_HELLO_FERRIS).await?;
+ client().uninstall(EXAMPLE_HELLO_RESOURCE).await?;
+ client().uninstall(EXAMPLE_INSPECT).await?;
+ client().uninstall(EXAMPLE_MEMEATER).await?;
+ client().uninstall(EXAMPLE_MESSAGE_0_0_1).await?;
+ client().uninstall(EXAMPLE_MESSAGE_0_0_2).await?;
+ client().uninstall(EXAMPLE_PERSISTENCE).await?;
+ client().uninstall(EXAMPLE_SECCOMP).await?;
+ client().uninstall(TEST_CONTAINER).await?;
+ client().uninstall(TEST_RESOURCE).await?;
});
-// Mount and umount all containers known to the runtime
+// Mount and umount all containers known to the client()
test!(mount_umount, {
- let mut runtime = Northstar::launch().await?;
- runtime.install(EXAMPLE_CPUEATER_NPK, "test-0").await?;
- runtime.install(EXAMPLE_CONSOLE_NPK, "test-0").await?;
- runtime.install(EXAMPLE_CRASHING_NPK, "test-0").await?;
- runtime.install(EXAMPLE_FERRIS_NPK, "test-0").await?;
- runtime.install(EXAMPLE_HELLO_FERRIS_NPK, "test-0").await?;
- runtime
+ client().install(EXAMPLE_CPUEATER_NPK, "test-0").await?;
+ client().install(EXAMPLE_CONSOLE_NPK, "test-0").await?;
+ client().install(EXAMPLE_CRASHING_NPK, "test-0").await?;
+ client().install(EXAMPLE_FERRIS_NPK, "test-0").await?;
+ client().install(EXAMPLE_HELLO_FERRIS_NPK, "test-0").await?;
+ client()
.install(EXAMPLE_HELLO_RESOURCE_NPK, "test-0")
.await?;
- runtime.install(EXAMPLE_INSPECT_NPK, "test-0").await?;
- runtime.install(EXAMPLE_MEMEATER_NPK, "test-0").await?;
- runtime.install(EXAMPLE_MESSAGE_0_0_1_NPK, "test-0").await?;
- runtime.install(EXAMPLE_MESSAGE_0_0_2_NPK, "test-0").await?;
- runtime.install(EXAMPLE_PERSISTENCE_NPK, "test-0").await?;
- runtime.install(EXAMPLE_SECCOMP_NPK, "test-0").await?;
- runtime.install(TEST_CONTAINER_NPK, "test-0").await?;
- runtime.install(TEST_RESOURCE_NPK, "test-0").await?;
-
- let mut containers = runtime.containers().await?;
- runtime
+ client().install(EXAMPLE_INSPECT_NPK, "test-0").await?;
+ client().install(EXAMPLE_MEMEATER_NPK, "test-0").await?;
+ client()
+ .install(EXAMPLE_MESSAGE_0_0_1_NPK, "test-0")
+ .await?;
+ client()
+ .install(EXAMPLE_MESSAGE_0_0_2_NPK, "test-0")
+ .await?;
+ client().install(EXAMPLE_PERSISTENCE_NPK, "test-0").await?;
+ client().install(EXAMPLE_SECCOMP_NPK, "test-0").await?;
+ client().install(TEST_CONTAINER_NPK, "test-0").await?;
+ client().install(TEST_RESOURCE_NPK, "test-0").await?;
+
+ let mut containers = client().containers().await?;
+ client()
.mount(containers.drain(..).map(|c| c.container))
.await?;
- let containers = &mut runtime.containers().await?;
+ let containers = &mut client().containers().await?;
for c in containers.iter().filter(|c| c.mounted) {
- runtime.umount(c.container.clone()).await?;
+ client().umount(c.container.clone()).await?;
}
-
- runtime.shutdown().await
});
// Try to stop a not started container and expect an Err
test!(try_to_stop_unknown_container, {
- let mut runtime = Northstar::launch().await?;
let container = "foo:0.0.1:default";
- assert!(runtime.stop(container, 5).await.is_err());
- runtime.shutdown().await
+ assert!(client().stop(container, 5).await.is_err());
});
// Try to start a container which is not installed/known
test!(try_to_start_unknown_container, {
- let mut runtime = Northstar::launch().await?;
let container = "unknown_application:0.0.12:asdf";
- assert!(runtime.start(container).await.is_err());
- runtime.shutdown().await
+ assert!(client().start(container).await.is_err());
});
// Try to start a container where a dependency is missing
test!(try_to_start_containter_that_misses_a_resource, {
- let mut runtime = Northstar::launch().await?;
- runtime.install_test_container().await?;
+ client().install_test_container().await?;
// The TEST_RESOURCE is not installed.
- assert!(runtime.start(TEST_CONTAINER).await.is_err());
- runtime.shutdown().await
+ assert!(client().start(TEST_CONTAINER).await.is_err());
});
// Start a container that uses a resource
test!(check_test_container_resource_usage, {
- let mut runtime = Northstar::launch_install_test_container().await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
// Start the test_container process
- runtime
+ client()
.start_with_args(TEST_CONTAINER, ["cat", "/resource/hello"])
.await?;
assume("hello from test resource", 5).await?;
// The container might have finished at this point
- runtime.stop(TEST_CONTAINER, 5).await?;
-
- runtime.uninstall_test_container().await?;
- runtime.uninstall_test_resource().await?;
+ client().stop(TEST_CONTAINER, 5).await?;
- runtime.shutdown().await
+ client().uninstall_test_container().await?;
+ client().uninstall_test_resource().await?;
});
// Try to uninstall a started container
test!(try_to_uninstall_a_started_container, {
- let mut runtime = Northstar::launch_install_test_container().await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
- runtime.start_with_args(TEST_CONTAINER, ["sleep"]).await?;
- assume("test-container: Sleeping...", 5u64).await?;
+ client().start_with_args(TEST_CONTAINER, ["sleep"]).await?;
+ assume("Sleeping...", 5u64).await?;
- let result = runtime.uninstall_test_container().await;
+ let result = client().uninstall_test_container().await;
assert!(result.is_err());
- runtime.stop(TEST_CONTAINER, 5).await?;
-
- runtime.shutdown().await
+ client().stop(TEST_CONTAINER, 5).await?;
});
test!(start_mounted_container_with_not_mounted_resource, {
- let mut runtime = Northstar::launch_install_test_container().await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
// Start a container that depends on a resource.
- runtime.start_with_args(TEST_CONTAINER, ["sleep"]).await?;
- assume("test-container: Sleeping...", 5u64).await?;
- runtime.stop(TEST_CONTAINER, 5).await?;
+ client().start_with_args(TEST_CONTAINER, ["sleep"]).await?;
+ assume("Sleeping...", 5u64).await?;
+ client().stop(TEST_CONTAINER, 5).await?;
// Umount the resource and start the container again.
- runtime.umount(TEST_RESOURCE).await?;
+ client().umount(TEST_RESOURCE).await?;
- runtime.start_with_args(TEST_CONTAINER, ["sleep"]).await?;
- assume("test-container: Sleeping...", 5u64).await?;
+ client().start_with_args(TEST_CONTAINER, ["sleep"]).await?;
+ assume("Sleeping...", 5u64).await?;
- runtime.stop(TEST_CONTAINER, 5).await?;
-
- runtime.shutdown().await
+ client().stop(TEST_CONTAINER, 5).await?;
});
// The test is flaky and needs to listen for notifications
// in order to be implemented correctly
test!(container_crash_exit, {
- let mut runtime = Northstar::launch_install_test_container().await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
for _ in 0..10 {
- runtime.start_with_args(TEST_CONTAINER, ["crash"]).await?;
- runtime
+ client().start_with_args(TEST_CONTAINER, ["crash"]).await?;
+ client()
.assume_notification(
|n| {
matches!(
@@ -202,150 +225,169 @@ test!(container_crash_exit, {
.await?;
}
- runtime.uninstall_test_container().await?;
- runtime.uninstall_test_resource().await?;
-
- runtime.shutdown().await
+ client().uninstall_test_container().await?;
+ client().uninstall_test_resource().await?;
});
// Check uid. In the manifest of the test container the uid
// is set to 1000
test!(container_uses_correct_uid, {
- let mut runtime = Northstar::launch_install_test_container().await?;
- runtime.start_with_args(TEST_CONTAINER, ["inspect"]).await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+ client()
+ .start_with_args(TEST_CONTAINER, ["inspect"])
+ .await?;
assume("getuid: 1000", 5).await?;
- runtime.stop(TEST_CONTAINER, 5).await?;
- runtime.shutdown().await
+ client().stop(TEST_CONTAINER, 5).await?;
});
// Check gid. In the manifest of the test container the gid
// is set to 1000
test!(container_uses_correct_gid, {
- let mut runtime = Northstar::launch_install_test_container().await?;
- runtime.start_with_args(TEST_CONTAINER, ["inspect"]).await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+ client()
+ .start_with_args(TEST_CONTAINER, ["inspect"])
+ .await?;
assume("getgid: 1000", 5).await?;
- runtime.stop(TEST_CONTAINER, 5).await?;
- runtime.shutdown().await
+ client().stop(TEST_CONTAINER, 5).await?;
});
// Check parent pid. Northstar starts an init process which must have pid 1.
test!(container_ppid_must_be_init, {
- let mut runtime = Northstar::launch_install_test_container().await?;
- runtime.start_with_args(TEST_CONTAINER, ["inspect"]).await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+ client()
+ .start_with_args(TEST_CONTAINER, ["inspect"])
+ .await?;
assume("getppid: 1", 5).await?;
- runtime.stop(TEST_CONTAINER, 5).await?;
- runtime.shutdown().await
+ client().stop(TEST_CONTAINER, 5).await?;
});
// Check session id which needs to be pid of init
test!(container_sid_must_be_init_or_none, {
- let mut runtime = Northstar::launch_install_test_container().await?;
- runtime.start_with_args(TEST_CONTAINER, ["inspect"]).await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+ client()
+ .start_with_args(TEST_CONTAINER, ["inspect"])
+ .await?;
assume("getsid: 1", 5).await?;
- runtime.stop(TEST_CONTAINER, 5).await?;
- runtime.shutdown().await
+ client().stop(TEST_CONTAINER, 5).await?;
});
// The test container only gets the cap_kill capability. See the manifest
test!(container_shall_only_have_configured_capabilities, {
- let mut runtime = Northstar::launch_install_test_container().await?;
- runtime.start_with_args(TEST_CONTAINER, ["inspect"]).await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+ client()
+ .start_with_args(TEST_CONTAINER, ["inspect"])
+ .await?;
assume("caps bounding: \\{\\}", 10).await?;
assume("caps effective: \\{\\}", 10).await?;
assume("caps permitted: \\{\\}", 10).await?;
- runtime.stop(TEST_CONTAINER, 5).await?;
- runtime.shutdown().await
+ client().stop(TEST_CONTAINER, 5).await?;
});
// The test container has a configured resource limit of tasks
test!(container_rlimits, {
- let mut runtime = Northstar::launch_install_test_container().await?;
- runtime.start_with_args(TEST_CONTAINER, ["inspect"]).await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+ client()
+ .start_with_args(TEST_CONTAINER, ["inspect"])
+ .await?;
assume(
"Max processes 10000 20000 processes",
10,
)
.await?;
- runtime.stop(TEST_CONTAINER, 5).await?;
- runtime.shutdown().await
+ client().stop(TEST_CONTAINER, 5).await?;
});
-// Check whether after a runtime start, container start and shutdown
+// Check whether after a client() start, container start and shutdown
// any file descriptor is leaked
-test!(
- start_stop_runtime_and_containers_shall_not_leak_file_descriptors,
- {
- /// Collect a set of files in /proc/$$/fd
- fn fds() -> Result, std::io::Error> {
- let mut links = std::fs::read_dir("/proc/self/fd")?
- .filter_map(Result::ok)
- .flat_map(|entry| entry.path().read_link())
- .collect::>();
- links.sort();
- Ok(links)
- }
- // Collect list of fds
- let before = fds()?;
-
- let mut runtime = Northstar::launch_install_test_container().await?;
-
- runtime.start_with_args(TEST_CONTAINER, ["sleep"]).await?;
- assume("test-container: Sleeping", 5).await?;
- runtime.stop(TEST_CONTAINER, 5).await?;
-
- let result = runtime.shutdown().await;
-
- // Compare the list of fds before and after the RT run.
- assert_eq!(before, fds()?);
-
- result
+test!(start_stop_and_container_shall_not_leak_file_descriptors, {
+ /// Collect a set of files in /proc/$$/fd
+ fn fds() -> Result, std::io::Error> {
+ let mut links = std::fs::read_dir("/proc/self/fd")?
+ .filter_map(Result::ok)
+ .flat_map(|entry| entry.path().read_link())
+ .collect::>();
+ links.sort();
+ Ok(links)
}
-);
+
+ let before = fds()?;
+
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+
+ client().start_with_args(TEST_CONTAINER, ["sleep"]).await?;
+ assume("Sleeping", 5).await?;
+ client().stop(TEST_CONTAINER, 5).await?;
+
+ client().uninstall_test_container().await?;
+ client().uninstall_test_resource().await?;
+
+ // Compare the list of fds before and after the RT run.
+ assert_eq!(before, fds()?);
+
+ let result = client().shutdown().await;
+
+ assert!(result.is_ok());
+});
// Check open file descriptors in the test container that should be
// stdin: /dev/null
// stdout: some pipe
// stderr: /dev/null
test!(container_shall_only_have_configured_fds, {
- let mut runtime = Northstar::launch_install_test_container().await?;
- runtime.start_with_args(TEST_CONTAINER, ["inspect"]).await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+ client()
+ .start_with_args(TEST_CONTAINER, ["inspect"])
+ .await?;
assume("/proc/self/fd/0: /dev/null", 5).await?;
- assume("/proc/self/fd/1: pipe:.*", 5).await?;
- assume("/proc/self/fd/2: pipe:.*", 5).await?;
+ assume("/proc/self/fd/1: socket", 5).await?;
+ assume("/proc/self/fd/2: socket", 5).await?;
assume("total: 3", 5).await?;
- runtime.stop(TEST_CONTAINER, 5).await?;
-
- runtime.shutdown().await
+ client().stop(TEST_CONTAINER, 5).await?;
});
// Check if /proc is mounted ro
test!(proc_is_mounted_ro, {
- let mut runtime = Northstar::launch_install_test_container().await?;
- runtime.start_with_args(TEST_CONTAINER, ["inspect"]).await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+ client()
+ .start_with_args(TEST_CONTAINER, ["inspect"])
+ .await?;
assume("proc /proc proc ro,", 5).await?;
- runtime.stop(TEST_CONTAINER, 5).await?;
- runtime.shutdown().await
+ client().stop(TEST_CONTAINER, 5).await?;
});
// Check that mount flags nosuid,nodev,noexec are properly set for bind mounts
// assumption: mount flags are always listed the same order (according mount.h)
// note: MS_REC is not explicitly listed an cannot be checked with this test
test!(mount_flags_are_set_for_bind_mounts, {
- let mut runtime = Northstar::launch_install_test_container().await?;
- runtime.start_with_args(TEST_CONTAINER, ["inspect"]).await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+ client()
+ .start_with_args(TEST_CONTAINER, ["inspect"])
+ .await?;
assume(
"/.* /resource \\w+ ro,(\\w+,)*nosuid,(\\w+,)*nodev,(\\w+,)*noexec",
5,
)
.await?;
- runtime.stop(TEST_CONTAINER, 5).await?;
- runtime.shutdown().await
+ client().stop(TEST_CONTAINER, 5).await?;
});
// The test container only gets the cap_kill capability. See the manifest
test!(selinux_mounted_squasfs_has_correct_context, {
- let mut runtime = Northstar::launch_install_test_container().await?;
- runtime.start_with_args(TEST_CONTAINER, ["inspect"]).await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+ client()
+ .start_with_args(TEST_CONTAINER, ["inspect"])
+ .await?;
// Only expect selinux context if system supports it
if Path::new("/sys/fs/selinux/enforce").exists() {
assume(
@@ -356,36 +398,36 @@ test!(selinux_mounted_squasfs_has_correct_context, {
} else {
assume("/.* squashfs (\\w+,)*", 5).await?;
}
- runtime.stop(TEST_CONTAINER, 5).await?;
- runtime.shutdown().await
+ client().stop(TEST_CONTAINER, 5).await?;
});
// Call syscall with specifically allowed argument
test!(seccomp_allowed_syscall_with_allowed_arg, {
- let mut runtime = Northstar::launch_install_test_container().await?;
- runtime
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+ client()
.start_with_args(TEST_CONTAINER, ["call-delete-module", "1"])
.await?;
assume("delete_module syscall was successful", 5).await?;
- runtime.stop(TEST_CONTAINER, 5).await?;
- runtime.shutdown().await
+ client().stop(TEST_CONTAINER, 5).await?;
});
// Call syscall with argument allowed by bitmask
test!(seccomp_allowed_syscall_with_masked_arg, {
- let mut runtime = Northstar::launch_install_test_container().await?;
- runtime
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+ client()
.start_with_args(TEST_CONTAINER, ["call-delete-module", "4"])
.await?;
assume("delete_module syscall was successful", 5).await?;
- runtime.stop(TEST_CONTAINER, 5).await?;
- runtime.shutdown().await
+ client().stop(TEST_CONTAINER, 5).await?;
});
// Call syscall with prohibited argument
test!(seccomp_allowed_syscall_with_prohibited_arg, {
- let mut runtime = Northstar::launch_install_test_container().await?;
- runtime
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+ client()
.start_with_args(TEST_CONTAINER, ["call-delete-module", "7"])
.await?;
@@ -396,15 +438,15 @@ test!(seccomp_allowed_syscall_with_prohibited_arg, {
..
} if signal == &31)
};
- runtime.assume_notification(n, 5).await?;
- runtime.shutdown().await
+ client().assume_notification(n, 5).await?;
});
// Iterate all exit codes in the u8 range
test!(exitcodes, {
- let mut runtime = Northstar::launch_install_test_container().await?;
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
for c in &[0, 1, 10, 127, 128, 255] {
- runtime
+ client()
.start_with_args(TEST_CONTAINER, ["exit".to_string(), c.to_string()])
.await?;
let n = |n: &Notification| {
@@ -413,39 +455,18 @@ test!(exitcodes, {
..
} if code == c)
};
- runtime.assume_notification(n, 5).await?;
+ client().assume_notification(n, 5).await?;
}
- runtime.shutdown().await
});
-// Open many connections to the runtime
-test!(open_many_connections_to_the_runtime_and_shutdown, {
- let runtime = Northstar::launch().await?;
-
- let mut clients = Vec::new();
- for _ in 0..500 {
- let client = runtime.client().await?;
- clients.push(client);
- }
-
- let result = runtime.shutdown().await;
-
- for client in &mut clients {
- assert!(client.containers().await.is_err());
- }
- clients.clear();
-
- result
-});
-
-// Verify that the runtime reject a version mismatch in Connect
+// Verify that the client() reject a version mismatch in Connect
test!(check_api_version_on_connect, {
- let runtime = Northstar::launch().await?;
-
trait AsyncReadWrite: AsyncRead + AsyncWrite + Unpin + Send {}
impl AsyncReadWrite for T {}
- let mut connection = api::codec::framed(UnixStream::connect(&runtime.console).await?);
+ let mut connection = api::codec::Framed::new(
+ UnixStream::connect(&northstar_tests::runtime::console().path()).await?,
+ );
// Send a connect with an version unequal to the one defined in the model
let mut version = api::model::version();
@@ -469,6 +490,20 @@ test!(check_api_version_on_connect, {
let expected_message = model::Message::new_connect(model::Connect::Nack { error });
assert_eq!(connack, expected_message);
+});
+
+// Check printing on stdout and stderr
+test!(stdout_stderr, {
+ client().install_test_container().await?;
+ client().install_test_resource().await?;
+
+ let args = ["print", "--io", "stdout", "hello stdout"];
+ client().start_with_args(TEST_CONTAINER, args).await?;
+ assume("hello stdout", 10).await?;
+ client().stop(TEST_CONTAINER, 5).await?;
- runtime.shutdown().await
+ let args = ["print", "--io", "stderr", "hello stderr"];
+ client().start_with_args(TEST_CONTAINER, args).await?;
+ assume("hello stderr", 10).await?;
+ client().stop(TEST_CONTAINER, 5).await?;
});
diff --git a/northstar/Cargo.toml b/northstar/Cargo.toml
index 2bf2659ac..a964ea1d0 100644
--- a/northstar/Cargo.toml
+++ b/northstar/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "northstar"
-version = "0.6.4"
+version = "0.7.0-dev"
authors = ["ESRLabs"]
edition = "2021"
build = "build.rs"
@@ -24,6 +24,7 @@ ed25519-dalek = { version = "1.0.1", optional = true }
futures = { version = "0.3.21", features = ["thread-pool"], optional = true }
hex = { version = "0.4.3", optional = true }
humanize-rs = { version = "0.1.5", optional = true }
+humantime = { version = "2.1.0", optional = true }
inotify = { version = "0.10.0", features = ["stream"], optional = true }
itertools = { version = "0.10.1", optional = true }
lazy_static = { version = "1.4.0", optional = true }
@@ -44,7 +45,7 @@ serde_yaml = { version = "0.8.21", optional = true }
sha2 = { version = "0.10.2", optional = true }
tempfile = { version = "3.3.0", optional = true }
thiserror = "1.0.30"
-tokio = { version = "1.17.0", features = ["fs", "io-std", "io-util", "macros", "process", "rt", "sync", "time"], optional = true }
+tokio = { version = "1.17.0", features = ["fs", "io-std", "io-util", "macros", "process", "rt-multi-thread", "sync", "time"], optional = true }
tokio-eventfd = { version = "0.2.0", optional = true }
tokio-util = { version = "0.7.0", features = ["codec", "io"], optional = true }
url = { version = "2.2.2", features = ["serde"], optional = true }
@@ -92,9 +93,11 @@ runtime = [
"caps",
"cgroups-rs",
"devicemapper",
+ "derive-new",
"ed25519-dalek",
"futures",
"hex",
+ "humantime",
"itertools",
"inotify",
"lazy_static",
@@ -123,14 +126,9 @@ seccomp = [
[dev-dependencies]
anyhow = "1.0.54"
-nix = "0.23.0"
proptest = "1.0.0"
-tempfile = "3.3.0"
-tokio = { version = "1.17.0", features = ["rt-multi-thread"] }
+serde_json = "1.0.68"
[build-dependencies]
anyhow = { version = "1.0.54", optional = true }
bindgen = { version = "0.59.1", default-features = false, features = ["runtime"], optional = true }
-nix = { version = "0.23.0", optional = true }
-tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"], optional = true }
-
diff --git a/northstar/src/api/client.rs b/northstar/src/api/client.rs
index 95b409951..4b5ca0188 100644
--- a/northstar/src/api/client.rs
+++ b/northstar/src/api/client.rs
@@ -1,5 +1,5 @@
use super::{
- codec::{self, framed},
+ codec,
model::{
self, Connect, Container, ContainerData, ContainerStats, Message, MountResult,
Notification, RepositoryId, Request, Response,
@@ -21,10 +21,13 @@ use std::{
use thiserror::Error;
use tokio::{
fs,
- io::{self, AsyncRead, AsyncWrite},
+ io::{self, AsyncRead, AsyncWrite, BufWriter},
time,
};
+/// Default buffer size for installation transfers
+const BUFFER_SIZE: usize = 1024 * 1024;
+
/// API error
#[allow(missing_docs)]
#[derive(Error, Debug)]
@@ -104,7 +107,7 @@ pub async fn connect(
notifications: Option,
timeout: time::Duration,
) -> Result, Error> {
- let mut connection = framed(io);
+ let mut connection = codec::Framed::with_capacity(io, BUFFER_SIZE);
// Send connect message
let connect = Connect::Connect {
version: model::version(),
@@ -431,7 +434,7 @@ impl<'a, T: AsyncRead + AsyncWrite + Unpin> Client {
/// ```
pub async fn install(&mut self, npk: &Path, repository: &str) -> Result<(), Error> {
self.fused()?;
- let mut file = fs::File::open(npk).await.map_err(Error::Io)?;
+ let file = fs::File::open(npk).await.map_err(Error::Io)?;
let size = file.metadata().await.unwrap().len();
let request = Request::Install {
repository: repository.into(),
@@ -443,12 +446,15 @@ impl<'a, T: AsyncRead + AsyncWrite + Unpin> Client {
Error::Stopped
})?;
- io::copy(&mut file, &mut self.connection)
- .await
- .map_err(|e| {
- self.fuse();
- Error::Io(e)
- })?;
+ self.connection.flush().await?;
+ debug_assert!(self.connection.write_buffer().is_empty());
+
+ let mut reader = io::BufReader::with_capacity(BUFFER_SIZE, file);
+ let mut writer = BufWriter::with_capacity(BUFFER_SIZE, self.connection.get_mut());
+ io::copy_buf(&mut reader, &mut writer).await.map_err(|e| {
+ self.fuse();
+ Error::Io(e)
+ })?;
loop {
match self.connection.next().await {
diff --git a/northstar/src/api/codec.rs b/northstar/src/api/codec.rs
index b7616e26b..b6a879cb0 100644
--- a/northstar/src/api/codec.rs
+++ b/northstar/src/api/codec.rs
@@ -1,43 +1,51 @@
use super::model;
-use futures::Stream;
-use std::{
- cmp::min,
- io::ErrorKind,
- pin::Pin,
- task::{self, Poll},
-};
-use task::Context;
+use std::io::ErrorKind;
use tokio::io::{self, AsyncRead, AsyncWrite};
-use tokio_util::codec::{Decoder, Encoder, FramedParts};
+use tokio_util::codec::{Decoder, Encoder};
/// Newline delimited json codec for api::Message that on top implements AsyncRead and Write
pub struct Framed {
inner: tokio_util::codec::Framed,
}
-impl Framed {
- /// Consumes the Framed, returning its underlying I/O stream, the buffer with unprocessed data, and the codec.
- pub fn into_parts(self) -> FramedParts {
- self.inner.into_parts()
+impl Framed {
+ /// Provides a [`Stream`] and [`Sink`] interface for reading and writing to this
+ /// I/O object, using [`Decoder`] and [`Encoder`] to read and write the raw data.
+ pub fn new(inner: T) -> Framed {
+ Framed {
+ inner: tokio_util::codec::Framed::new(inner, Codec::default()),
+ }
+ }
+
+ /// Provides a [`Stream`] and [`Sink`] interface for reading and writing to this
+ /// I/O object, using [`Decoder`] and [`Encoder`] to read and write the raw data,
+ /// with a specific read buffer initial capacity.
+ /// [`split`]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html#method.split
+ pub fn with_capacity(inner: T, capacity: usize) -> Framed {
+ Framed {
+ inner: tokio_util::codec::Framed::with_capacity(inner, Codec::default(), capacity),
+ }
}
+}
+
+impl std::ops::Deref for Framed {
+ type Target = tokio_util::codec::Framed;
- /// Consumes the Framed, returning its underlying I/O stream.
- pub fn into_inner(self) -> T {
- self.inner.into_inner()
+ fn deref(&self) -> &Self::Target {
+ &self.inner
}
}
-/// Constructs a new Framed with Codec from `io`
-pub fn framed(io: T) -> Framed {
- Framed {
- inner: tokio_util::codec::Framed::new(io, Codec::default()),
+impl std::ops::DerefMut for Framed {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.inner
}
}
/// Newline delimited json
#[derive(Default)]
pub struct Codec {
- lines: tokio_util::codec::LinesCodec,
+ inner: tokio_util::codec::LinesCodec,
}
impl Decoder for Codec {
@@ -45,7 +53,7 @@ impl Decoder for Codec {
type Error = io::Error;
fn decode(&mut self, src: &mut bytes::BytesMut) -> Result