aboutsummaryrefslogtreecommitdiff
path: root/src/garage/tests/common/garage.rs
diff options
context:
space:
mode:
authorJill <kokakiwi@deuxfleurs.fr>2022-02-02 15:35:52 +0100
committerJill <kokakiwi@deuxfleurs.fr>2022-02-02 15:35:52 +0100
commit539b25652502693fed311699829f3bd28e604aa5 (patch)
tree031fb891d26630c87beca6f7ac67d21c65f6d440 /src/garage/tests/common/garage.rs
parentf67029ce2af2f8870836d5bb908254d7fdbbe71b (diff)
downloadgarage-539b25652502693fed311699829f3bd28e604aa5.tar.gz
garage-539b25652502693fed311699829f3bd28e604aa5.zip
tests: Add garage integration tests (base)
Diffstat (limited to 'src/garage/tests/common/garage.rs')
-rw-r--r--src/garage/tests/common/garage.rs217
1 files changed, 217 insertions, 0 deletions
diff --git a/src/garage/tests/common/garage.rs b/src/garage/tests/common/garage.rs
new file mode 100644
index 00000000..e579f03c
--- /dev/null
+++ b/src/garage/tests/common/garage.rs
@@ -0,0 +1,217 @@
+use std::mem::MaybeUninit;
+use std::path::{Path, PathBuf};
+use std::process;
+use std::sync::Once;
+
+use super::ext::*;
+
+// https://xkcd.com/221/
+const DEFAULT_PORT: u16 = 49995;
+
+static GARAGE_TEST_SECRET: &str =
+ "c3ea8cb80333d04e208d136698b1a01ae370d463f0d435ab2177510b3478bf44";
+
+#[derive(Debug, Default)]
+pub struct Key {
+ pub name: String,
+ pub id: String,
+ pub secret: String,
+}
+
+pub struct Instance {
+ process: process::Child,
+ pub path: PathBuf,
+ pub key: Key,
+ pub api_port: u16,
+}
+
+impl Instance {
+ fn new() -> Instance {
+ use std::{env, fs};
+
+ let port = env::var("GARAGE_TEST_INTEGRATION_PORT")
+ .map(|value| value.parse().expect("Invalid port provided"))
+ .ok()
+ .unwrap_or(DEFAULT_PORT);
+
+ let path = env::var("GARAGE_TEST_INTEGRATION_PATH")
+ .map(PathBuf::from)
+ .ok()
+ .unwrap_or_else(|| env::temp_dir().join(format!("garage-integ-test-{}", port)));
+
+ // Clean test runtime directory
+ if path.exists() {
+ fs::remove_dir_all(&path).expect("Could not clean test runtime directory");
+ }
+ fs::create_dir(&path).expect("Could not create test runtime directory");
+
+ let config = format!(
+ r#"
+metadata_dir = "{path}/meta"
+data_dir = "{path}/data"
+
+replication_mode = "1"
+
+rpc_bind_addr = "127.0.0.1:{rpc_port}"
+rpc_public_addr = "127.0.0.1:{rpc_port}"
+rpc_secret = "{secret}"
+
+[s3_api]
+s3_region = "{region}"
+api_bind_addr = "127.0.0.1:{api_port}"
+root_domain = ".s3.garage"
+
+[s3_web]
+bind_addr = "127.0.0.1:{web_port}"
+root_domain = ".web.garage"
+index = "index.html"
+"#,
+ path = path.display(),
+ secret = GARAGE_TEST_SECRET,
+ region = super::REGION,
+ api_port = port,
+ rpc_port = port + 1,
+ web_port = port + 2,
+ );
+ fs::write(path.join("config.toml"), config).expect("Could not write garage config file");
+
+ let stdout =
+ fs::File::create(path.join("stdout.log")).expect("Could not create stdout logfile");
+ let stderr =
+ fs::File::create(path.join("stderr.log")).expect("Could not create stderr logfile");
+
+ let child = command(&path.join("config.toml"))
+ .arg("server")
+ .stdout(stdout)
+ .stderr(stderr)
+ .env("RUST_LOG", "garage=info,garage_api=debug")
+ .spawn()
+ .expect("Could not start garage");
+
+ Instance {
+ process: child,
+ path,
+ key: Key::default(),
+ api_port: port,
+ }
+ }
+
+ fn setup(&mut self) {
+ use std::{thread, time::Duration};
+
+ // Wait for node to be ready
+ thread::sleep(Duration::from_secs(2));
+
+ self.setup_layout();
+
+ self.key = self.new_key("garage_test");
+ }
+
+ fn setup_layout(&self) {
+ let node_id = self.node_id();
+ let node_short_id = &node_id[..64];
+
+ self.command()
+ .args(["layout", "assign"])
+ .arg(node_short_id)
+ .args(["-c", "1", "-z", "unzonned"])
+ .quiet()
+ .expect_success_status("Could not assign garage node layout");
+ self.command()
+ .args(["layout", "apply"])
+ .args(["--version", "1"])
+ .quiet()
+ .expect_success_status("Could not apply garage node layout");
+ }
+
+ fn terminate(&mut self) {
+ // TODO: Terminate "gracefully" the process with SIGTERM instead of directly SIGKILL it.
+ self.process
+ .kill()
+ .expect("Could not terminate garage process");
+ }
+
+ pub fn command(&self) -> process::Command {
+ command(&self.path.join("config.toml"))
+ }
+
+ pub fn node_id(&self) -> String {
+ let output = self
+ .command()
+ .args(["node", "id"])
+ .expect_success_output("Could not get node ID");
+ String::from_utf8(output.stdout).unwrap()
+ }
+
+ pub fn uri(&self) -> http::Uri {
+ format!("http://127.0.0.1:{api_port}", api_port = self.api_port)
+ .parse()
+ .expect("Could not build garage endpoint URI")
+ }
+
+ pub fn new_key(&self, name: &str) -> Key {
+ let mut key = Key::default();
+
+ let output = self
+ .command()
+ .args(["key", "new"])
+ .args(["--name", name])
+ .expect_success_output("Could not create key");
+ let stdout = String::from_utf8(output.stdout).unwrap();
+
+ for line in stdout.lines() {
+ if let Some(key_id) = line.strip_prefix("Key ID: ") {
+ key.id = key_id.to_owned();
+ continue;
+ }
+ if let Some(key_secret) = line.strip_prefix("Secret key: ") {
+ key.secret = key_secret.to_owned();
+ continue;
+ }
+ }
+ assert!(!key.id.is_empty(), "Invalid key: Key ID is empty");
+ assert!(!key.secret.is_empty(), "Invalid key: Key secret is empty");
+
+ Key {
+ name: name.to_owned(),
+ ..key
+ }
+ }
+}
+
+impl Drop for Instance {
+ fn drop(&mut self) {
+ self.terminate()
+ }
+}
+
+static mut INSTANCE: MaybeUninit<Instance> = MaybeUninit::uninit();
+static INSTANCE_INIT: Once = Once::new();
+
+#[static_init::destructor]
+extern "C" fn terminate_instance() {
+ if INSTANCE_INIT.is_completed() {
+ unsafe {
+ INSTANCE.assume_init_drop();
+ }
+ }
+}
+
+pub fn instance() -> &'static Instance {
+ INSTANCE_INIT.call_once(|| unsafe {
+ let mut instance = Instance::new();
+ instance.setup();
+
+ INSTANCE.write(instance);
+ });
+
+ unsafe { INSTANCE.assume_init_ref() }
+}
+
+pub fn command(config_path: &Path) -> process::Command {
+ let mut command = process::Command::new(env!("CARGO_BIN_EXE_garage"));
+
+ command.arg("-c").arg(config_path);
+
+ command
+}