testcontainers_modules/zookeeper/
mod.rs

1use std::borrow::Cow;
2
3use testcontainers::{core::WaitFor, Image};
4
5const NAME: &str = "bitnami/zookeeper";
6const TAG: &str = "3.9.0";
7
8/// # [Apache ZooKeeper] image for [testcontainers](https://crates.io/crates/testcontainers).
9///
10/// This image is based on the [`bitnami/zookeeper` docker image].
11/// By default, anonymous logins are allowed.
12/// See the [Zookeeper documentation] for additional options.
13///
14/// # Example
15///
16/// ```
17/// use testcontainers_modules::{testcontainers::runners::AsyncRunner, zookeeper};
18/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
19/// let node = zookeeper::Zookeeper::default().start().await.unwrap();
20/// let zk_url = format!(
21///     "{}:{}",
22///     node.get_host().await.unwrap(),
23///     node.get_host_port_ipv4(2181).await.unwrap(),
24/// );
25/// let client = zookeeper_client::Client::connect(&zk_url)
26///     .await
27///     .expect("connect to Zookeeper");
28///
29/// let path = "/test";
30/// let (_, stat_watcher) = client
31///     .check_and_watch_stat(path)
32///     .await
33///     .expect("stat watcher created");
34/// # })
35/// ```
36///
37///
38/// [Apache ZooKeeper]: https://zookeeper.apache.org/
39/// [`bitnami/zookeeper` docker image]: https://hub.docker.com/r/bitnami/openldap
40/// [Zookeeper documentation]: https://zookeeper.apache.org/documentation.html
41#[derive(Debug, Default, Clone)]
42pub struct Zookeeper {
43    /// (remove if there is another variable)
44    /// Field is included to prevent this struct to be a unit struct.
45    /// This allows extending functionality (and thus further variables) without breaking changes
46    _priv: (),
47}
48
49impl Image for Zookeeper {
50    fn name(&self) -> &str {
51        NAME
52    }
53
54    fn tag(&self) -> &str {
55        TAG
56    }
57
58    fn ready_conditions(&self) -> Vec<WaitFor> {
59        vec![
60            WaitFor::message_on_stdout("Started AdminServer"),
61            WaitFor::message_on_stdout("PrepRequestProcessor (sid:0) started"),
62        ]
63    }
64
65    fn env_vars(
66        &self,
67    ) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
68        [("ALLOW_ANONYMOUS_LOGIN", "yes")]
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use rustls::crypto::CryptoProvider;
75    use zookeeper_client::{Acls, Client, CreateMode, EventType};
76
77    use crate::{testcontainers::runners::AsyncRunner, zookeeper::Zookeeper as ZookeeperImage};
78
79    #[tokio::test]
80    async fn zookeeper_check_directories_existence(
81    ) -> Result<(), Box<dyn std::error::Error + 'static>> {
82        let _ = pretty_env_logger::try_init();
83        if CryptoProvider::get_default().is_none() {
84            rustls::crypto::ring::default_provider()
85                .install_default()
86                .expect("Error initializing rustls provider");
87        }
88
89        let node = ZookeeperImage::default().start().await?;
90
91        let host_ip = node.get_host().await?;
92        let host_port = node.get_host_port_ipv4(2181).await?;
93        let zk_url = format!("{host_ip}:{host_port}");
94        let client = Client::connect(&zk_url)
95            .await
96            .expect("connect to Zookeeper");
97
98        let path = "/test";
99        let (_, stat_watcher) = client
100            .check_and_watch_stat(path)
101            .await
102            .expect("stat watcher created");
103
104        let create_options = CreateMode::Ephemeral.with_acls(Acls::anyone_all());
105        let (_, _) = client
106            .create(path, &[1, 2], &create_options)
107            .await
108            .expect("create a node");
109
110        let event = stat_watcher.changed().await;
111        assert_eq!(event.event_type, EventType::NodeCreated);
112        assert_eq!(event.path, path);
113        Ok(())
114    }
115}