1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
//! `WaitFor` implementations regarding status changes.

use crate::container::{OperationalContainer, PendingContainer};
use crate::docker::ContainerState;
use crate::waitfor::{async_trait, WaitFor};
use crate::DockerTestError;
use tokio::time::{interval, Duration};

/// The RunningWait `WaitFor` implementation for containers.
/// This variant will wait until the docker daemon reports the container as running.
#[derive(Clone, Debug)]
pub struct RunningWait {
    /// How many seconds shall there be between each check for running state.
    pub check_interval: u64,
    /// The number of checks to perform before erroring out.
    pub max_checks: u64,
}

/// The ExitedWait `WaitFor` implementation for containers.
/// This variant will wait until the docker daemon reports that the container has exited.
#[derive(Clone, Debug)]
pub struct ExitedWait {
    /// How many seconds shall there be between each check for running state.
    pub check_interval: u64,
    /// The number of checks to perform before erroring out.
    pub max_checks: u64,
}

#[async_trait]
impl WaitFor for RunningWait {
    async fn wait_for_ready(
        &self,
        container: PendingContainer,
    ) -> Result<OperationalContainer, DockerTestError> {
        wait_for_container_state(container, self.check_interval, self.max_checks, |state| {
            state == ContainerState::Running
        })
        .await
    }
}

#[async_trait]
impl WaitFor for ExitedWait {
    async fn wait_for_ready(
        &self,
        container: PendingContainer,
    ) -> Result<OperationalContainer, DockerTestError> {
        wait_for_container_state(container, self.check_interval, self.max_checks, |state| {
            state == ContainerState::Exited
        })
        .await
    }

    fn expected_state(&self) -> ContainerState {
        ContainerState::Exited
    }
}

async fn wait_for_container_state(
    container: PendingContainer,
    check_interval: u64,
    max_checks: u64,
    container_state_compare: fn(ContainerState) -> bool,
) -> Result<OperationalContainer, DockerTestError> {
    let client = &container.client;

    let mut started = false;
    let mut num_checks = 0;

    // Periodically check container state in an interval.
    // At one point in the future, this check will time out with an error.
    // Once the desired state has been fulfilled within the time out period,
    // the operation returns successfully.

    let mut interval = interval(Duration::from_secs(check_interval));
    loop {
        if num_checks >= max_checks {
            break;
        }

        started = if let Ok(c) = client.container_state(&container.name).await {
            container_state_compare(c)
        } else {
            false
        };

        if started {
            break;
        }

        num_checks += 1;
        interval.tick().await;
    }

    match started {
        false => Err(DockerTestError::Startup(
            "status waitfor is not triggered".to_string(),
        )),
        true => Ok(container.into()),
    }
}