diff --git a/Cargo.lock b/Cargo.lock index ac7396a3..408fda60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -484,6 +484,7 @@ name = "controller_lib" version = "0.1.0" dependencies = [ "actix-web", + "etcd-client", "log", "proto", "serde", @@ -636,6 +637,22 @@ dependencies = [ "termcolor", ] +[[package]] +name = "etcd-client" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8664f6ea68aba5503d42dd1be786b0f1bd9b7972e7f40208c83ef74db91bf" +dependencies = [ + "http", + "prost", + "tokio", + "tokio-stream", + "tonic", + "tonic-build", + "tower", + "tower-service", +] + [[package]] name = "fastrand" version = "1.8.0" diff --git a/controller/lib/Cargo.toml b/controller/lib/Cargo.toml index 95d88a39..34d726bb 100644 --- a/controller/lib/Cargo.toml +++ b/controller/lib/Cargo.toml @@ -6,9 +6,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +etcd-client = "0.9" actix-web = "4.1.0" serde = { version = "1.0.139", features = ["derive"] } tonic = "0.7.2" proto = { path = "../../proto" } log = "0.4.0" -tokio = { version = "1.20.0", features = ["rt-multi-thread"] } \ No newline at end of file +tokio = { version = "1.20.0", features = ["rt-multi-thread", "macros"] } diff --git a/controller/lib/src/etcd_client/interface.rs b/controller/lib/src/etcd_client/interface.rs new file mode 100644 index 00000000..b154297d --- /dev/null +++ b/controller/lib/src/etcd_client/interface.rs @@ -0,0 +1,131 @@ +use etcd_client::{Client, DeleteResponse, Error, GetOptions, PutResponse}; +use log::{debug, error, info}; + +pub struct EtcdInterface { + client: Client, +} + +impl EtcdInterface { + pub async fn new(address: String) -> Self { + info!("Starting ETCD client on {}", address); + EtcdInterface { + client: Client::connect([address], None).await.unwrap(), + } + } + + pub async fn get(&mut self, key: &str) -> Result { + if let Some(kv) = self.client.get(key, None).await?.kvs().first() { + let res = kv.value_str(); + info!( + "Retrieving value in ETCD : Key \"{}\" is associated with value \"{}\"", + key, + res.as_ref().unwrap() + ); + res.map(|s| s.to_string()) + } else { + Err(Error::from(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Key not found", + ))) + } + } + pub async fn put(&mut self, key: &str, value: &str) -> Result { + info!( + "Inserting value in ETCD : Key \"{}\" associated with value \"{}\"", + key, value + ); + self.client.put(key, value, None).await + } + pub async fn patch(&mut self, key: &str, value: &str) -> Result { + match self.get(key).await { + Ok(_) => { + info!( + "Updating value in ETCD : Key \"{}\" associated with new value \"{}\"", + key, value + ); + self.client.put(key, value, None).await + } + Err(err) => Err(err), + } + } + pub async fn delete(&mut self, key: &str) -> Result { + if let Some(_) = self.client.get(key, None).await?.kvs().first() { + info!("Deleting value in ETCD : Key \"{}\" ", key); + self.client.delete(key, None).await + } else { + Err(Error::from(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Key not found", + ))) + } + } + + pub async fn get_all(&mut self) -> Result, Error> { + info!("Retrieving all keys in ETCD"); + let resp = self + .client + .get("", Some(GetOptions::new().with_all_keys())) + .await?; + + let mut values: Vec = vec![]; + for kv in resp.kvs() { + let value = kv.value_str()?; + values.push(value.to_string()) + } + Ok(values) + } +} + +#[cfg(test)] +mod tests { + + use crate::internal_etcd::interface::EtcdInterface; + use etcd_client::Error; + + #[tokio::test] + async fn test_value_insertion() -> Result<(), Error> { + let mut etcd_client = EtcdInterface::new("localhost:2379".to_string()).await; + let _res = etcd_client.put("foo", "bar").await?; + let resp = etcd_client.get("foo").await?; + assert_eq!(resp, "bar"); + Ok(()) + } + + #[tokio::test] + async fn test_value_modification() -> Result<(), Error> { + let mut etcd_client = EtcdInterface::new("localhost:2379".to_string()).await; + etcd_client.put("foo", "bar").await?; + let _res = etcd_client.patch("foo", "baz").await?; + let resp = etcd_client.get("foo").await?; + assert_eq!(resp, "baz"); + Ok(()) + } + #[tokio::test] + async fn test_value_deletion() -> Result<(), Error> { + let mut etcd_client = EtcdInterface::new("localhost:2379".to_string()).await; + let _res = etcd_client.put("foo", "bar").await?; + let _res = etcd_client.delete("foo").await?; + let err = etcd_client.get("foo").await; + assert!(err.is_err()); + Ok(()) + } + #[tokio::test] + async fn test_value_deletion_doesnt_exists() -> Result<(), Error> { + let mut etcd_client = EtcdInterface::new("localhost:2379".to_string()).await; + let _res = etcd_client.put("foo", "bar").await?; + let err = etcd_client.delete("foo2").await; + assert!(err.is_err()); + Ok(()) + } + + #[tokio::test] + async fn test_function_get_all() -> Result<(), Error> { + let mut etcd_client = EtcdInterface::new("localhost:2379".to_string()).await; + let _res = etcd_client.put("bar", "foo").await; + let _res = etcd_client.put("hello", "world").await; + let values = etcd_client.get_all().await?; + assert_eq!(values[0], "foo"); + assert_eq!(values[1], "world"); + Ok(()) + } +} diff --git a/controller/lib/src/etcd_client/mod.rs b/controller/lib/src/etcd_client/mod.rs new file mode 100644 index 00000000..9a5ca34c --- /dev/null +++ b/controller/lib/src/etcd_client/mod.rs @@ -0,0 +1 @@ +pub mod interface; \ No newline at end of file diff --git a/controller/lib/src/lib.rs b/controller/lib/src/lib.rs index 91866edd..4433e411 100644 --- a/controller/lib/src/lib.rs +++ b/controller/lib/src/lib.rs @@ -1,2 +1,3 @@ +pub mod etcd_client; pub mod external_api; pub mod internal_api;