use std::time::Duration;
use shep_client::shep_core::config::AppConfig;
use crate::verify;
pub(crate) const RELOAD_DEADLINE_SLACK: Duration = Duration::from_secs(5);
pub(crate) fn budget(app: &AppConfig, instances: u32) -> Duration {
let mut per_instance = app
.listen_timeout
.as_duration()
.saturating_add(app.graceful_timeout.as_duration())
.saturating_add(RELOAD_DEADLINE_SLACK);
if probes_again_after_drain(app) {
per_instance = per_instance.saturating_add(app.listen_timeout.as_duration());
}
verify::bounded(per_instance.saturating_mul(instances.max(1)))
}
fn probes_again_after_drain(app: &AppConfig) -> bool {
app.reuse_port && app.readiness_probe.is_some()
}
pub(crate) fn dwell_for(app: &AppConfig) -> Duration {
if probes_again_after_drain(app) {
verify::bounded(
app.listen_timeout
.as_duration()
.saturating_add(RELOAD_DEADLINE_SLACK),
)
.max(verify::DWELL)
} else {
verify::DWELL
}
}
#[cfg(test)]
mod tests {
use shep_client::shep_core::values::UpDuration;
use super::*;
#[test]
fn the_window_matches_the_shepherds_own_reload_deadline() {
let mut app: AppConfig =
toml::from_str("name = 'web'\nscript = './run.sh'").expect("parses");
assert_eq!(app.listen_timeout.as_duration(), Duration::from_secs(3));
assert_eq!(app.graceful_timeout.as_duration(), Duration::from_secs(8));
assert_eq!(budget(&app, 1), Duration::from_secs(16));
assert_eq!(budget(&app, 2), Duration::from_secs(32));
app.instances = 1;
assert_eq!(budget(&app, 4), Duration::from_secs(64));
app.graceful_timeout = UpDuration::from_millis(20_000);
app.listen_timeout = UpDuration::from_millis(1_000);
assert_eq!(budget(&app, 3), Duration::from_secs(78));
assert_eq!(budget(&app, 0), budget(&app, 1));
}
#[test]
fn the_window_covers_a_reuse_port_apps_post_drain_probe() {
let mut app: AppConfig = toml::from_str(
"name = 'web'\nscript = './run.sh'\n\
[readiness_probe]\nkind = 'http'\ntarget = 'http://127.0.0.1:1/'",
)
.expect("parses");
assert_eq!(budget(&app, 1), Duration::from_secs(16), "no reuse_port");
app.reuse_port = true;
assert_eq!(budget(&app, 1), Duration::from_secs(19));
assert_eq!(budget(&app, 2), Duration::from_secs(38));
app.listen_timeout = UpDuration::from_millis(10_000);
assert_eq!(budget(&app, 1), Duration::from_secs(33));
app.listen_timeout = UpDuration::from_millis(u64::MAX);
assert_eq!(budget(&app, 3), verify::bounded(Duration::MAX));
app.listen_timeout = UpDuration::from_millis(3_000);
app.readiness_probe = None;
assert_eq!(budget(&app, 1), Duration::from_secs(16));
}
#[test]
fn the_dwell_is_sized_to_the_apps_post_drain_probe() {
let mut app: AppConfig = toml::from_str(
"name = 'web'\nscript = './run.sh'\nlisten_timeout = '60s'\n\
[readiness_probe]\nkind = 'http'\ntarget = 'http://127.0.0.1:1/'",
)
.expect("parses");
assert_eq!(dwell_for(&app), verify::DWELL, "no reuse_port: plain dwell");
app.reuse_port = true;
assert_eq!(
dwell_for(&app),
Duration::from_secs(65),
"listen_timeout plus the shepherd's own slack"
);
app.listen_timeout = UpDuration::from_millis(1_000);
assert_eq!(dwell_for(&app), verify::DWELL);
app.listen_timeout = UpDuration::from_millis(u64::MAX);
assert_eq!(dwell_for(&app), verify::bounded(Duration::MAX));
app.readiness_probe = None;
assert_eq!(dwell_for(&app), verify::DWELL);
}
}