stability fixes

This commit is contained in:
Aiden McClelland
2021-11-16 01:35:32 -07:00
committed by Aiden McClelland
parent 7696ec9a13
commit 071f6cec03
15 changed files with 183 additions and 42 deletions

View File

@@ -11,6 +11,8 @@ PATCH_DB_CLIENT_SRC = $(shell find patch-db/client -not -path patch-db/client/di
GIT_REFS := $(shell find .git/refs/heads)
TMP_FILE := $(shell mktemp)
.DELETE_ON_ERROR:
all: eos.img
gzip: eos.img

View File

@@ -3,7 +3,9 @@ use std::collections::BTreeMap;
use std::ffi::{OsStr, OsString};
use std::net::Ipv4Addr;
use std::path::PathBuf;
use std::time::Duration;
use bollard::container::StopContainerOptions;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tracing::instrument;
@@ -46,17 +48,39 @@ impl DockerAction {
volumes: &Volumes,
input: Option<I>,
allow_inject: bool,
timeout: Option<Duration>,
) -> Result<Result<O, (i32, String)>, Error> {
let mut cmd = tokio::process::Command::new("docker");
let mut timeout_fut =
futures::future::Either::Left(futures::future::pending::<Result<_, Error>>());
if self.inject && allow_inject {
cmd.arg("exec");
} else {
let container_name = Self::container_name(pkg_id, name);
cmd.arg("run")
.arg("--rm")
.arg("--network=start9")
.arg(format!("--add-host=embassy:{}", Ipv4Addr::from(HOST_IP)))
.arg("--name")
.arg(Self::container_name(pkg_id, name));
.arg(&container_name);
if let Some(timeout) = timeout {
timeout_fut = futures::future::Either::Right(async move {
tokio::time::sleep(timeout).await;
match ctx
.docker
.stop_container(&container_name, Some(StopContainerOptions { t: 30 }))
.await
{
Err(bollard::errors::Error::DockerResponseNotFoundError { .. })
| Err(bollard::errors::Error::DockerResponseConflictError { .. })
| Err(bollard::errors::Error::DockerResponseNotModifiedError { .. }) => (), // Already stopped
a => a?,
};
Ok(futures::future::pending().await)
});
}
}
cmd.args(
self.docker_args(ctx, pkg_id, pkg_version, volumes, allow_inject)
@@ -85,10 +109,13 @@ impl DockerAction {
.await
.with_kind(crate::ErrorKind::Docker)?;
}
let res = handle
let res = tokio::select! {
res = handle
.wait_with_output()
.await
.with_kind(crate::ErrorKind::Docker)?;
=> res
.with_kind(crate::ErrorKind::Docker)?,
res = timeout_fut => res?,
};
Ok(if res.status.success() || res.status.code() == Some(143) {
Ok(if let Some(format) = &self.io_format {
match format.from_slice(&res.stdout) {
@@ -125,6 +152,7 @@ impl DockerAction {
pkg_version: &Version,
volumes: &Volumes,
input: Option<I>,
timeout: Option<Duration>,
) -> Result<Result<O, (i32, String)>, Error> {
let mut cmd = tokio::process::Command::new("docker");
cmd.arg("run").arg("--rm").arg("--network=none");

View File

@@ -1,6 +1,7 @@
use std::collections::BTreeMap;
use std::path::Path;
use std::str::FromStr;
use std::time::Duration;
use clap::ArgMatches;
use color_eyre::eyre::eyre;
@@ -131,6 +132,7 @@ impl Action {
volumes,
input,
true,
None,
)
.await?
.map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::Action))
@@ -154,11 +156,21 @@ impl ActionImplementation {
volumes: &Volumes,
input: Option<I>,
allow_inject: bool,
timeout: Option<Duration>,
) -> Result<Result<O, (i32, String)>, Error> {
match self {
ActionImplementation::Docker(action) => {
action
.execute(ctx, pkg_id, pkg_version, name, volumes, input, allow_inject)
.execute(
ctx,
pkg_id,
pkg_version,
name,
volumes,
input,
allow_inject,
timeout,
)
.await
}
}
@@ -171,11 +183,12 @@ impl ActionImplementation {
pkg_version: &Version,
volumes: &Volumes,
input: Option<I>,
timeout: Option<Duration>,
) -> Result<Result<O, (i32, String)>, Error> {
match self {
ActionImplementation::Docker(action) => {
action
.sandboxed(ctx, pkg_id, pkg_version, volumes, input)
.sandboxed(ctx, pkg_id, pkg_version, volumes, input, timeout)
.await
}
}

View File

@@ -89,6 +89,7 @@ impl BackupActions {
&volumes,
None,
false,
None,
)
.await?
.map_err(|e| eyre!("{}", e.1))
@@ -167,6 +168,7 @@ impl BackupActions {
&volumes,
None,
false,
None,
)
.await?
.map_err(|e| eyre!("{}", e.1))

View File

@@ -46,6 +46,7 @@ impl ConfigActions {
volumes,
None::<()>,
false,
None,
)
.await
.and_then(|res| {
@@ -73,6 +74,7 @@ impl ConfigActions {
volumes,
Some(input),
false,
None,
)
.await
.and_then(|res| {

View File

@@ -23,9 +23,7 @@ use crate::dependencies::{
};
use crate::install::cleanup::remove_current_dependents;
use crate::s9pk::manifest::{Manifest, PackageId};
use crate::util::{
display_none, display_serializable, parse_duration, parse_stdin_deserializable, IoFormat,
};
use crate::util::{display_none, display_serializable, parse_stdin_deserializable, IoFormat};
use crate::{Error, ResultExt as _};
pub mod action;
@@ -190,11 +188,11 @@ pub fn set(
#[allow(unused_variables)]
#[arg(long = "format")]
format: Option<IoFormat>,
#[arg(long = "timeout", parse(parse_duration))] timeout: Option<Duration>,
#[arg(long = "timeout")] timeout: Option<crate::util::Duration>,
#[arg(stdin, parse(parse_stdin_deserializable))] config: Option<Config>,
#[arg(rename = "expire-id", long = "expire-id")] expire_id: Option<String>,
) -> Result<(PackageId, Option<Config>, Option<Duration>, Option<String>), Error> {
Ok((id, config, timeout, expire_id))
Ok((id, config, timeout.map(|d| *d), expire_id))
}
#[command(rename = "dry", display(display_serializable))]

View File

@@ -421,6 +421,7 @@ impl DependencyConfig {
dependent_version,
dependent_volumes,
Some(dependency_config),
None,
)
.await?
.map_err(|(_, e)| e))
@@ -440,6 +441,7 @@ impl DependencyConfig {
dependent_version,
dependent_volumes,
Some(old),
None,
)
.await?
.map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::AutoConfigure))
@@ -472,7 +474,7 @@ pub async fn configure_impl(
&mut db,
&dep_id,
Some(new_config),
&Some(Duration::from_secs(3)),
&Some(Duration::from_secs(3).into()),
false,
&mut BTreeMap::new(),
&mut BTreeMap::new(),
@@ -598,7 +600,14 @@ pub async fn configure_logic(
let new_config = dependency
.auto_configure
.sandboxed(&ctx, &pkg_id, &pkg_version, &pkg_volumes, Some(&old_config))
.sandboxed(
&ctx,
&pkg_id,
&pkg_version,
&pkg_volumes,
Some(&old_config),
None,
)
.await?
.map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::AutoConfigure))?;

View File

@@ -83,7 +83,9 @@ pub async fn get_vendor<P: AsRef<Path>>(path: P) -> Result<Option<String>, Error
.join("device")
.join("vendor"),
)
.await?;
.await?
.trim()
.to_owned();
Ok(if vendor.is_empty() {
None
} else {
@@ -104,7 +106,9 @@ pub async fn get_model<P: AsRef<Path>>(path: P) -> Result<Option<String>, Error>
.join("device")
.join("model"),
)
.await?;
.await?
.trim()
.to_owned();
Ok(if model.is_empty() { None } else { Some(model) })
}

View File

@@ -183,6 +183,7 @@ async fn run_main(
&rt_state.manifest.volumes,
None,
false,
None,
)
.await
});

View File

@@ -44,6 +44,7 @@ impl Migrations {
volumes,
Some(version),
false,
None,
)
.await?
.map_err(|e| {
@@ -78,6 +79,7 @@ impl Migrations {
volumes,
Some(version),
false,
None,
)
.await?
.map_err(|e| {

View File

@@ -92,6 +92,13 @@ impl NginxControllerInner {
if lan_port_config.ssl {
// these have already been written by the net controller
let package_path = nginx_root.join(format!("ssl/{}", package));
if tokio::fs::metadata(&package_path).await.is_err() {
tokio::fs::create_dir_all(&package_path)
.await
.with_ctx(|_| {
(ErrorKind::Filesystem, package_path.display().to_string())
})?;
}
let ssl_path_key = package_path.join(format!("{}.key.pem", id));
let ssl_path_cert = package_path.join(format!("{}.cert.pem", id));
let (key, chain) = ssl_manager.certificate_for(&meta.dns_base).await?;

View File

@@ -39,6 +39,7 @@ pub async fn fetch_properties(ctx: RpcContext, id: PackageId) -> Result<Value, E
&manifest.volumes,
None,
false,
None,
)
.await?
.map_err(|(_, e)| Error::new(eyre!("{}", e), ErrorKind::Docker))

View File

@@ -9,7 +9,7 @@ use crate::action::{ActionImplementation, NoOutput};
use crate::context::RpcContext;
use crate::id::Id;
use crate::s9pk::manifest::PackageId;
use crate::util::Version;
use crate::util::{Duration, Version};
use crate::volume::Volumes;
use crate::Error;
@@ -72,6 +72,7 @@ pub struct HealthCheck {
#[serde(flatten)]
implementation: ActionImplementation,
pub critical: bool,
pub timeout: Option<Duration>,
}
impl HealthCheck {
#[instrument(skip(ctx))]
@@ -94,6 +95,10 @@ impl HealthCheck {
volumes,
Some(Utc::now().signed_duration_since(started).num_milliseconds()),
true,
Some(
self.timeout
.map_or(std::time::Duration::from_secs(30), |d| *d),
),
)
.await?;
Ok(match res {

View File

@@ -7,7 +7,6 @@ use std::path::{Path, PathBuf};
use std::process::{exit, Stdio};
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use clap::ArgMatches;
@@ -651,30 +650,98 @@ pub fn parse_stdin_deserializable<T: for<'de> Deserialize<'de>>(
format.from_reader(stdin)
}
pub fn parse_duration(arg: &str, _: &ArgMatches<'_>) -> Result<Duration, Error> {
let units_idx = arg.find(|c: char| c.is_alphabetic()).ok_or_else(|| {
#[derive(Debug, Clone, Copy)]
pub struct Duration(std::time::Duration);
impl Deref for Duration {
type Target = std::time::Duration;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl From<std::time::Duration> for Duration {
fn from(t: std::time::Duration) -> Self {
Duration(t)
}
}
impl std::str::FromStr for Duration {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let units_idx = s.find(|c: char| c.is_alphabetic()).ok_or_else(|| {
Error::new(
eyre!("Must specify units for duration"),
crate::ErrorKind::Deserialization,
)
})?;
let (num, units) = arg.split_at(units_idx);
match units {
"d" if num.contains(".") => Ok(Duration::from_secs_f64(num.parse::<f64>()? * 86400_f64)),
"d" => Ok(Duration::from_secs(num.parse::<u64>()? * 86400)),
"h" if num.contains(".") => Ok(Duration::from_secs_f64(num.parse::<f64>()? * 3600_f64)),
"h" => Ok(Duration::from_secs(num.parse::<u64>()? * 3600)),
"m" if num.contains(".") => Ok(Duration::from_secs_f64(num.parse::<f64>()? * 60_f64)),
"m" => Ok(Duration::from_secs(num.parse::<u64>()? * 60)),
"s" if num.contains(".") => Ok(Duration::from_secs_f64(num.parse()?)),
"s" => Ok(Duration::from_secs(num.parse()?)),
"ms" => Ok(Duration::from_millis(num.parse()?)),
"us" => Ok(Duration::from_micros(num.parse()?)),
"ns" => Ok(Duration::from_nanos(num.parse()?)),
_ => Err(Error::new(
let (num, units) = s.split_at(units_idx);
use std::time::Duration;
Ok(Duration(match units {
"d" if num.contains(".") => Duration::from_secs_f64(num.parse::<f64>()? * 86_400_f64),
"d" => Duration::from_secs(num.parse::<u64>()? * 86_400),
"h" if num.contains(".") => Duration::from_secs_f64(num.parse::<f64>()? * 3_600_f64),
"h" => Duration::from_secs(num.parse::<u64>()? * 3_600),
"m" if num.contains(".") => Duration::from_secs_f64(num.parse::<f64>()? * 60_f64),
"m" => Duration::from_secs(num.parse::<u64>()? * 60),
"s" if num.contains(".") => Duration::from_secs_f64(num.parse()?),
"s" => Duration::from_secs(num.parse()?),
"ms" if num.contains(".") => Duration::from_secs_f64(num.parse::<f64>()? / 1_000_f64),
"ms" => {
let millis: u128 = num.parse()?;
Duration::new((millis / 1_000) as u64, (millis % 1_000) as u32)
}
"us" | "µs" if num.contains(".") => {
Duration::from_secs_f64(num.parse::<f64>()? / 1_000_000_f64)
}
"us" | "µs" => {
let micros: u128 = num.parse()?;
Duration::new((micros / 1_000_000) as u64, (micros % 1_000_000) as u32)
}
"ns" if num.contains(".") => {
Duration::from_secs_f64(num.parse::<f64>()? / 1_000_000_000_f64)
}
"ns" => {
let nanos: u128 = num.parse()?;
Duration::new(
(nanos / 1_000_000_000) as u64,
(nanos % 1_000_000_000) as u32,
)
}
_ => {
return Err(Error::new(
eyre!("Invalid units for duration"),
crate::ErrorKind::Deserialization,
)),
))
}
}))
}
}
impl std::fmt::Display for Duration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let nanos = self.as_nanos();
match () {
_ if nanos % 86_400_000_000_000 == 0 => write!(f, "{}d", nanos / 86_400_000_000_000),
_ if nanos % 3_600_000_000_000 == 0 => write!(f, "{}h", nanos / 3_600_000_000_000),
_ if nanos % 60_000_000_000 == 0 => write!(f, "{}m", nanos / 60_000_000_000),
_ if nanos % 1_000_000_000 == 0 => write!(f, "{}s", nanos / 1_000_000_000),
_ if nanos % 1_000_000 == 0 => write!(f, "{}ms", nanos / 1_000_000),
_ if nanos % 1_000 == 0 => write!(f, "{}µs", nanos / 1_000),
_ => write!(f, "{}ns", nanos),
}
}
}
impl<'de> Deserialize<'de> for Duration {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserialize_from_str(deserializer)
}
}
impl Serialize for Duration {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serialize_display(self, serializer)
}
}