From adb4a7e026e575a59d03184ff8df835542883bfa Mon Sep 17 00:00:00 2001 From: Kelvin Fan Date: Tue, 27 Apr 2021 17:21:51 -0400 Subject: [PATCH 1/4] strategy: add new `marker_file` strategy Add a simple, flexible strategy that checks the directory `/var/lib/zincati/admin/marker_file_strategy` for a JSON file (optionally containing an expiry timestamp key) named `allowfinalize.json` and allows update finalization if such file exists and has the proper permissions. --- Cargo.toml | 2 +- dist/tmpfiles.d/zincati.conf | 24 +++- src/strategy/marker_file.rs | 209 +++++++++++++++++++++++++++++++++++ src/strategy/mod.rs | 15 +++ 4 files changed, 243 insertions(+), 7 deletions(-) create mode 100644 src/strategy/marker_file.rs diff --git a/Cargo.toml b/Cargo.toml index 3417daba..cecec43f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ serde_json = "1.0" structopt = "0.3" tempfile = "^3.2" thiserror = "1.0" -tokio = { version = "1.10", features = ["signal", "rt", "rt-multi-thread"] } +tokio = { version = "1.10", features = ["fs", "signal", "rt", "rt-multi-thread"] } toml = "0.5" tzfile = "0.1.3" url = { version = "2.2", features = ["serde"] } diff --git a/dist/tmpfiles.d/zincati.conf b/dist/tmpfiles.d/zincati.conf index 5ef5d582..231443fc 100644 --- a/dist/tmpfiles.d/zincati.conf +++ b/dist/tmpfiles.d/zincati.conf @@ -1,14 +1,26 @@ -#Type Path Mode User Group Age Argument -d /run/zincati 0775 zincati zincati - - +#Type Path Mode User Group Age Argument +d /run/zincati 0775 zincati zincati - - # Runtime configuration fragments -d /run/zincati/config.d 0775 zincati zincati - - +d /run/zincati/config.d 0775 zincati zincati - - # Runtime state, unstable/private implementation details -d /run/zincati/private 0770 zincati zincati - - +d /run/zincati/private 0770 zincati zincati - - # Runtime public interfaces -d /run/zincati/public 0775 zincati zincati - - +d /run/zincati/public 0775 zincati zincati - - # Legacy symlink to metrics socket -L+ /run/zincati/private/metrics.promsock - - - - ../public/metrics.promsock +L+ /run/zincati/private/metrics.promsock - - - - ../public/metrics.promsock + +# Directory for Zincati's variable state information +d /var/lib/zincati 0775 zincati zincati - - + +# Directory for admin input +d /var/lib/zincati/admin 0775 zincati zincati - - + +# Directory for strategy-specific input +d /var/lib/zincati/admin/strategy 0775 zincati zincati - - + +# Directory for `marker_file` strategy input +d /var/lib/zincati/admin/strategy/marker_file 0775 zincati zincati - - diff --git a/src/strategy/marker_file.rs b/src/strategy/marker_file.rs new file mode 100644 index 00000000..68c231f8 --- /dev/null +++ b/src/strategy/marker_file.rs @@ -0,0 +1,209 @@ +//! Strategy for local marker file-based updates. + +use anyhow::{Context, Error, Result}; +use fn_error_context::context; +use futures::future; +use futures::prelude::*; +use log::trace; +use serde::{Deserialize, Serialize}; +use std::os::unix::fs::PermissionsExt; +use std::pin::Pin; +use std::time::{SystemTime, UNIX_EPOCH}; +use tokio::fs as tokio_fs; + +/// Struct to parse finalization marker file's JSON content into. +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct FinalizationMarker { + /// Unix timestamp of expiry time. + allow_until: Option, +} + +/// Strategy for immediate updates. +#[derive(Clone, Debug, Default, Serialize)] +pub(crate) struct StrategyMarkerFile {} + +impl StrategyMarkerFile { + /// Strategy label/name. + pub const LABEL: &'static str = "marker_file"; + /// Local filesystem path to finalization marker file. + pub const FINALIZATION_MARKER_FILE_PATH: &'static str = + "/var/lib/zincati/admin/strategy/marker_file/allowfinalize.json"; + + /// Check if finalization is allowed. + pub(crate) fn can_finalize(&self) -> Pin>>> { + Box::pin(Self::marker_file_allow_finalization( + Self::FINALIZATION_MARKER_FILE_PATH, + )) + } + + /// Try to report steady state. + pub(crate) fn report_steady(&self) -> Pin>>> { + trace!("marker_file strategy, report steady: {}", true); + + let res = future::ok(true); + Box::pin(res) + } + + /// Asynchronous helper function that returns a future indicating whether + /// finalization is allowed, depending on the presence of a marker file. + async fn marker_file_allow_finalization( + finalization_marker_path: &'static str, + ) -> Result { + if !verify_file_metadata(finalization_marker_path).await? { + return Ok(false); + } + + if is_expired(finalization_marker_path).await? { + return Ok(false); + } + + Ok(true) + } +} + +/// Verify that finalization marker file exists, is a regular file, +/// and has the correct permissions. +#[context("failed to verify finalization marker file metadata")] +async fn verify_file_metadata(path: &str) -> Result { + let attr = tokio_fs::metadata(path).await; + let attr = match attr { + Ok(attr) => attr, + // If `path` doesn't exist, return false early. + Err(_) => return Ok(false), + }; + + if !attr.is_file() { + anyhow::bail!("file is not regular file"); + } + + let mode = attr.permissions().mode(); + if mode & 0o2 != 0 { + anyhow::bail!("file should not be writable by other"); + } + + Ok(true) +} + +/// Check whether the finalization marker file has expired, if `allowUntil` key +/// exists. +async fn is_expired(path: &'static str) -> Result { + match parse_expiry_timestamp(path).await? { + Some(expiry_timestamp) => { + // We can `unwrap()` since we're certain `UNIX_EPOCH` is in the past. + let current_timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + if current_timestamp >= expiry_timestamp { + Ok(true) + } else { + Ok(false) + } + } + None => Ok(false), + } +} + +#[context("failed to parse expiry timestamp from marker file")] +async fn parse_expiry_timestamp(path: &'static str) -> Result> { + let marker_json: Result = tokio::task::spawn_blocking(move || { + let file = std::fs::File::open(path)?; + let reader = std::io::BufReader::new(file); + let json = serde_json::from_reader(reader).context("failed to parse JSON content")?; + Ok(json) + }) + .await?; + + Ok(marker_json?.allow_until) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + use std::fs; + use std::io::BufWriter; + use std::os::unix::fs::PermissionsExt; + use std::path::PathBuf; + use tempfile::tempdir; + use tokio::runtime as rt; + + #[test] + fn test_marker_file_allow_finalization() { + lazy_static::lazy_static! { + static ref TEMPDIR_MARKER_FILE_PATH: String = { + let p: PathBuf = tempdir().unwrap().into_path().join("allowfinalize.json"); + p.into_os_string().into_string().unwrap() + }; + } + let json = json!({}); + let f = fs::File::create(&*TEMPDIR_MARKER_FILE_PATH).unwrap(); + serde_json::to_writer(BufWriter::new(f), &json).unwrap(); + + // This should pass since default file permissions are 644 and we don't check + // for ownership by root. + let runtime = rt::Runtime::new().unwrap(); + let can_finalize = + StrategyMarkerFile::marker_file_allow_finalization(&*TEMPDIR_MARKER_FILE_PATH); + let can_finalize = runtime.block_on(can_finalize).unwrap(); + assert!(can_finalize); + + // Set permissions to writable by other; expect an error. + fs::set_permissions( + &*TEMPDIR_MARKER_FILE_PATH, + fs::Permissions::from_mode(0o777), + ) + .unwrap(); + let can_finalize = + StrategyMarkerFile::marker_file_allow_finalization(&*TEMPDIR_MARKER_FILE_PATH); + runtime + .block_on(can_finalize) + .expect_err("file with incorrect permissions unexpectedly allowed finalization"); + } + + #[test] + fn test_parse_finalization_marker() { + lazy_static::lazy_static! { + static ref TEMPDIR_MARKER_FILE_PATH: String = { + let p: PathBuf = tempdir().unwrap().into_path().join("allowfinalize.json"); + p.into_os_string().into_string().unwrap() + }; + } + // 1619640863 is Apr 28 2021 20:14:23 UTC. + // Expect this to be expired. + let json = json!({ + "allowUntil": 1619640863 + }); + let f = fs::File::create(&*TEMPDIR_MARKER_FILE_PATH).unwrap(); + serde_json::to_writer(BufWriter::new(f), &json).unwrap(); + let expired = is_expired(&*TEMPDIR_MARKER_FILE_PATH); + let runtime = rt::Runtime::new().unwrap(); + let expired = runtime.block_on(expired).unwrap(); + assert_eq!(expired, true); + + // Expect timepstamp with value `u64::MAX` to not be expired. + let json = json!({ "allowUntil": u64::MAX }); + let f = fs::File::create(&*TEMPDIR_MARKER_FILE_PATH).unwrap(); + serde_json::to_writer(BufWriter::new(f), &json).unwrap(); + let expired = is_expired(&*TEMPDIR_MARKER_FILE_PATH); + let expired = runtime.block_on(expired).unwrap(); + assert_eq!(expired, false); + + // If no `allowUntil` field, marker file should not expire. + let json = json!({}); + let f = fs::File::create(&*TEMPDIR_MARKER_FILE_PATH).unwrap(); + serde_json::to_writer(BufWriter::new(f), &json).unwrap(); + let expired = is_expired(&*TEMPDIR_MARKER_FILE_PATH); + let expired = runtime.block_on(expired).unwrap(); + assert_eq!(expired, false); + + // Improper JSON. + let json = "allowUntil=1619640863"; + fs::write(&*TEMPDIR_MARKER_FILE_PATH, json).unwrap(); + let expired = is_expired(&*TEMPDIR_MARKER_FILE_PATH); + runtime + .block_on(expired) + .expect_err("improper JSON unexpectedly parsed without error"); + } +} diff --git a/src/strategy/mod.rs b/src/strategy/mod.rs index 05444818..c4ec2ab9 100644 --- a/src/strategy/mod.rs +++ b/src/strategy/mod.rs @@ -18,6 +18,9 @@ pub(crate) use immediate::StrategyImmediate; mod periodic; pub(crate) use periodic::StrategyPeriodic; +mod marker_file; +pub(crate) use marker_file::StrategyMarkerFile; + /// Label for allow responses from querying strategy's `can_finalize` function. pub static CAN_FINALIZE_ALLOW_LABEL: &str = "allow"; @@ -51,6 +54,7 @@ pub(crate) enum UpdateStrategy { FleetLock(StrategyFleetLock), Immediate(StrategyImmediate), Periodic(StrategyPeriodic), + MarkerFile(StrategyMarkerFile), } impl UpdateStrategy { @@ -62,6 +66,7 @@ impl UpdateStrategy { StrategyFleetLock::LABEL => UpdateStrategy::new_fleet_lock(cfg, identity)?, StrategyImmediate::LABEL => UpdateStrategy::new_immediate(), StrategyPeriodic::LABEL => UpdateStrategy::new_periodic(cfg)?, + StrategyMarkerFile::LABEL => UpdateStrategy::new_marker_file(), "" => UpdateStrategy::default(), x => anyhow::bail!("unsupported strategy '{}'", x), }; @@ -97,6 +102,7 @@ impl UpdateStrategy { UpdateStrategy::FleetLock(_) => StrategyFleetLock::LABEL, UpdateStrategy::Immediate(_) => StrategyImmediate::LABEL, UpdateStrategy::Periodic(_) => StrategyPeriodic::LABEL, + UpdateStrategy::MarkerFile(_) => StrategyMarkerFile::LABEL, } } @@ -108,6 +114,7 @@ impl UpdateStrategy { UpdateStrategy::Periodic(p) => { format!("{}, {}", self.configuration_label(), p.calendar_summary(),) } + UpdateStrategy::MarkerFile(_) => self.configuration_label().to_string(), } } @@ -117,6 +124,7 @@ impl UpdateStrategy { UpdateStrategy::FleetLock(s) => s.can_finalize(), UpdateStrategy::Immediate(s) => s.can_finalize(), UpdateStrategy::Periodic(s) => s.can_finalize(), + UpdateStrategy::MarkerFile(s) => s.can_finalize(), }; async { @@ -150,6 +158,7 @@ impl UpdateStrategy { UpdateStrategy::FleetLock(s) => s.report_steady(), UpdateStrategy::Immediate(s) => s.report_steady(), UpdateStrategy::Periodic(s) => s.report_steady(), + UpdateStrategy::MarkerFile(s) => s.report_steady(), }; async { @@ -177,6 +186,12 @@ impl UpdateStrategy { let periodic = StrategyPeriodic::new(cfg)?; Ok(UpdateStrategy::Periodic(periodic)) } + + /// Build a new "filesystem" strategy. + fn new_marker_file() -> Self { + let marker_file = StrategyMarkerFile::default(); + UpdateStrategy::MarkerFile(marker_file) + } } impl Default for UpdateStrategy { From 23d8e575908909980d4e85a84d094b48cb54ccd1 Mon Sep 17 00:00:00 2001 From: Kelvin Fan Date: Fri, 30 Apr 2021 16:38:06 -0400 Subject: [PATCH 2/4] docs/update-strategy: explain `marker_file` strategy This explains how the `marker_file` strategy works and how to configure it. --- docs/usage/updates-strategy.md | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/usage/updates-strategy.md b/docs/usage/updates-strategy.md index a01b3875..9c3af155 100644 --- a/docs/usage/updates-strategy.md +++ b/docs/usage/updates-strategy.md @@ -65,6 +65,64 @@ Such an approach is only recommended where nodes are already grouped into an orc [airlock]: https://github.com/coreos/airlock [etcd3]: https://etcd.io/ +# File-based strategy +The `marker_file` strategy is a simple, low-level strategy that only allows Zincati to reboot for updates when a specific marker file exists on the local filesystem. + +Similar to the `fleet_lock` strategy, the `marker_file` strategy provides a large amount of flexibility to admins, and should be used with a central controller. +Unlike `fleet_lock`, where the central controller must be a lock-manager on the network, the central controller for the `marker_file` strategy can be a containerized agent, some central task manager able to manipulate files on machines (e.g. Ansible), or even a human via SSH. + +To indicate that a machine is allowed to finalize an update and reboot, a file with the following properties must be present on the machine's local filesystem: + - named `allowfinalize.json` + - under `/var/lib/zincati/admin/strategy/marker_file` + - is a valid JSON file + - not writable by others + +If any of the above is not satisfied in your marker file, Zincati will not allow reboots. + +`allowfinalize.json` can optionally contain an `allowUntil` key with a Unix timestamp integer as its value to indicate the expiry date and time of this marker file. If the current time timestamp is _greater than or equal to_ this timestamp, then reboots will not be allowed. +Otherwise, if the `allowUntil` key is not present, reboots will be allowed for as long as `allowfinalize.json` exists (in the right location), and it must be removed to disallow reboots. +Note that `allowfinalize.json` must still be a valid JSON file, regardless of whether the `allowUntil` key is present. + +For example, if you wish to allow reboots until the end of April 2021 UTC, create a JSON file with path `/var/lib/zincati/admin/strategy/marker_file/allowfinalize.json` (Unix timestamp 1619827200 is May 01 2021 00:00:00 UTC): + +```json +{ + "allowUntil": 1619827200 +} +``` + +The above JSON file can be created using `jq` by entering the following command: + +```bash +echo '"2021-05-01T00:00:00Z"' | jq '{allowUntil: 'fromdateiso8601'}' \ +| sudo tee /var/lib/zincati/admin/strategy/marker_file/allowfinalize.json +``` + +Warning: In `jq` versions `1.6` and lower, `jq` [may output incorrect Unix timestamps][jq_bug] for certain datetimes on machines with certain `localtime`s. + +If you wish to allow reboots for as long as the marker file is present, create an empty JSON file with path `/var/lib/zincati/admin/strategy/marker_file/allowfinalize.json`: + +```json +{} +``` + +An empty JSON file can be created by entering: + +```bash +echo '{}' | sudo tee /var/lib/zincati/admin/strategy/marker_file/allowfinalize.json +``` + +For configuration purposes, such strategy is labeled `marker_file` and takes no additional configuration parameters. + +This strategy can be enabled via a configuration snippet like the following: + +```toml +[updates] +strategy = "marker_file" +``` + +[jq_bug]: https://github.com/stedolan/jq/issues/2001 + # Periodic strategy The `periodic` strategy allows Zincati to only reboot for updates during certain timeframes, also known as "maintenance windows" or "reboot windows". From db90bcbf5f64927943ec02d3e8f3e8ba3a9f22d3 Mon Sep 17 00:00:00 2001 From: Kelvin Fan Date: Wed, 12 May 2021 15:56:06 +0000 Subject: [PATCH 3/4] kola/server: add e2e test for `marker_file` strategy --- tests/kola/server/data | 1 + .../kola/server/test-marker-file-strategy.sh | 129 ++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 120000 tests/kola/server/data create mode 100755 tests/kola/server/test-marker-file-strategy.sh diff --git a/tests/kola/server/data b/tests/kola/server/data new file mode 120000 index 00000000..60d3b0a6 --- /dev/null +++ b/tests/kola/server/data @@ -0,0 +1 @@ +../common \ No newline at end of file diff --git a/tests/kola/server/test-marker-file-strategy.sh b/tests/kola/server/test-marker-file-strategy.sh new file mode 100755 index 00000000..44ff27b4 --- /dev/null +++ b/tests/kola/server/test-marker-file-strategy.sh @@ -0,0 +1,129 @@ +#!/bin/bash + +# Test to check for correct detection of a dead-end release. + +set -xeuo pipefail + +. ${KOLA_EXT_DATA}/libtest.sh + +wait_rpm_ostree_status_has_content() { + regex=$1 + + for i in {1..24} + do + rpm-ostree status > status.txt + if grep -q "$regex" status.txt; then + break + fi + sleep 5 + done + rpm-ostree status > status.txt + assert_file_has_content status.txt "$regex" +} + +cd $(mktemp -d) + +case "${AUTOPKGTEST_REBOOT_MARK:-}" in + "") + # Prepare a graph template with two nodes. The node with the lower age index will be + # populated with the current booted deployment, and the node with the higher age index + # will be populated with a new version we want to update to. + mkdir /var/www/v1 + cat <<'EOF' > graph_template +{ + "nodes": [ + { + "version": "", + "metadata": { + "org.fedoraproject.coreos.releases.age_index": "0", + "org.fedoraproject.coreos.scheme": "checksum" + }, + "payload": "" + }, + { + "version": "", + "metadata": { + "org.fedoraproject.coreos.releases.age_index" : "1", + "org.fedoraproject.coreos.scheme": "checksum" + }, + "payload": "" + } + ], + "edges": [ + [ + 0, + 1 + ] + ] +} +EOF + + cur_version="$(/usr/bin/rpm-ostree status --json | jq '.deployments[0].version' -r)" + cur_payload="$(/usr/bin/rpm-ostree status --json | jq '.deployments[0].checksum' -r)" + + # Prepare an OSTree repo in archive mode at `/var/www` and pull the currently booted commit into it. + ostree --repo=/var/www init --mode="archive" + ostree --repo=/var/www pull-local /ostree/repo "$cur_payload" + # Create a new branch `test-branch` by creating a dummy commit. + ostree --repo=/var/www commit --branch='test-branch' --tree ref="$cur_payload" \ + --add-metadata-string version='dummy' --keep-metadata='fedora-coreos.stream' \ + --keep-metadata='coreos-assembler.basearch' --parent="$cur_payload" + # Add the OSTree repo at /var/www as a new `local` remote. + ostree remote add --no-gpg-verify local http://localhost test-branch + # Rebase onto our local OSTree repo's `test-branch`. + rpm-ostree rebase local:test-branch + # Create a new commit on `test-branch`. + next_version="$cur_version".new-update + next_payload="$(ostree --repo=/var/www commit --branch=test-branch --tree ref="$cur_payload" \ + --add-metadata-string version="$next_version" --keep-metadata='fedora-coreos.stream' \ + --keep-metadata='coreos-assembler.basearch' --parent="$cur_payload")" + + jq \ + --arg cur_version "$cur_version" \ + --arg cur_payload "$cur_payload" \ + --arg next_version "$next_version" \ + --arg next_payload "$next_payload" \ + '.nodes[0].version=$cur_version | .nodes[0].payload=$cur_payload | .nodes[1].version=$next_version | .nodes[1].payload=$next_payload' \ + graph_template > /var/www/v1/graph + + # Set strategy to `marker-file` strategy. + echo 'updates.strategy = "marker_file"' > /etc/zincati/config.d/95-marker-file-strategy.toml + + # Now let Zincati check for updates (and detect that there is a new release). + echo "updates.enabled = true" > /etc/zincati/config.d/99-test-status-updates-enabled.toml + systemctl restart zincati.service + + # Check that Zincati's status is active and stuck at "reboot pending due to update strategy". + wait_rpm_ostree_status_has_content "active;.*reboot pending due to update strategy" + ok "disallow reboot when no marker file" + + systemctl stop zincati.service + + # Place marker file with an expired timestamp. + echo '"2021-05-01T00:00:00Z"' | jq '{allowUntil: 'fromdate'}' \ + > /var/lib/zincati/admin/strategy/marker_file/allowfinalize.json + + systemctl start zincati.service + + # Check that Zincati's status is active and stuck at "reboot pending due to update strategy". + wait_rpm_ostree_status_has_content "active;.*reboot pending due to update strategy" + ok "disallow reboot with expired marker file" + + systemctl stop zincati.service + + # Place marker file with no expiry to allow update finalization. + echo '{}' > /var/lib/zincati/admin/strategy/marker_file/allowfinalize.json + + /tmp/autopkgtest-reboot-prepare rebooted + + systemctl start zincati.service + ;; + + rebooted) + rpm-ostree status > status.txt + assert_file_has_content status.txt "Version:.*new-update" + ok "allow reboot with non-expired marker file" + ;; + + *) echo "unexpected mark: ${AUTOPKGTEST_REBOOT_MARK}"; exit 1;; +esac From 469f501228164925600cd0c9bfa51831570e5bdb Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 2 Nov 2023 11:13:47 -0400 Subject: [PATCH 4/4] chore(file-based-strategy): address lint findings Refs: pr #1103 --- src/strategy/marker_file.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/strategy/marker_file.rs b/src/strategy/marker_file.rs index 68c231f8..714972c9 100644 --- a/src/strategy/marker_file.rs +++ b/src/strategy/marker_file.rs @@ -145,7 +145,7 @@ mod tests { // for ownership by root. let runtime = rt::Runtime::new().unwrap(); let can_finalize = - StrategyMarkerFile::marker_file_allow_finalization(&*TEMPDIR_MARKER_FILE_PATH); + StrategyMarkerFile::marker_file_allow_finalization(&TEMPDIR_MARKER_FILE_PATH); let can_finalize = runtime.block_on(can_finalize).unwrap(); assert!(can_finalize); @@ -156,7 +156,7 @@ mod tests { ) .unwrap(); let can_finalize = - StrategyMarkerFile::marker_file_allow_finalization(&*TEMPDIR_MARKER_FILE_PATH); + StrategyMarkerFile::marker_file_allow_finalization(&TEMPDIR_MARKER_FILE_PATH); runtime .block_on(can_finalize) .expect_err("file with incorrect permissions unexpectedly allowed finalization"); @@ -177,31 +177,31 @@ mod tests { }); let f = fs::File::create(&*TEMPDIR_MARKER_FILE_PATH).unwrap(); serde_json::to_writer(BufWriter::new(f), &json).unwrap(); - let expired = is_expired(&*TEMPDIR_MARKER_FILE_PATH); + let expired = is_expired(&TEMPDIR_MARKER_FILE_PATH); let runtime = rt::Runtime::new().unwrap(); let expired = runtime.block_on(expired).unwrap(); - assert_eq!(expired, true); + assert!(expired); // Expect timepstamp with value `u64::MAX` to not be expired. let json = json!({ "allowUntil": u64::MAX }); let f = fs::File::create(&*TEMPDIR_MARKER_FILE_PATH).unwrap(); serde_json::to_writer(BufWriter::new(f), &json).unwrap(); - let expired = is_expired(&*TEMPDIR_MARKER_FILE_PATH); + let expired = is_expired(&TEMPDIR_MARKER_FILE_PATH); let expired = runtime.block_on(expired).unwrap(); - assert_eq!(expired, false); + assert!(!expired); // If no `allowUntil` field, marker file should not expire. let json = json!({}); let f = fs::File::create(&*TEMPDIR_MARKER_FILE_PATH).unwrap(); serde_json::to_writer(BufWriter::new(f), &json).unwrap(); - let expired = is_expired(&*TEMPDIR_MARKER_FILE_PATH); + let expired = is_expired(&TEMPDIR_MARKER_FILE_PATH); let expired = runtime.block_on(expired).unwrap(); - assert_eq!(expired, false); + assert!(!expired); // Improper JSON. let json = "allowUntil=1619640863"; fs::write(&*TEMPDIR_MARKER_FILE_PATH, json).unwrap(); - let expired = is_expired(&*TEMPDIR_MARKER_FILE_PATH); + let expired = is_expired(&TEMPDIR_MARKER_FILE_PATH); runtime .block_on(expired) .expect_err("improper JSON unexpectedly parsed without error");