2022-02-02 14:35:52 +00:00
|
|
|
use std::mem::MaybeUninit;
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use std::process;
|
|
|
|
use std::sync::Once;
|
|
|
|
|
|
|
|
use super::ext::*;
|
|
|
|
|
|
|
|
// https://xkcd.com/221/
|
2022-03-04 17:33:18 +00:00
|
|
|
pub const DEFAULT_PORT: u16 = 49995;
|
2022-02-02 14:35:52 +00:00
|
|
|
|
|
|
|
static GARAGE_TEST_SECRET: &str =
|
|
|
|
"c3ea8cb80333d04e208d136698b1a01ae370d463f0d435ab2177510b3478bf44";
|
|
|
|
|
2022-03-11 16:35:08 +00:00
|
|
|
#[derive(Debug, Default, Clone)]
|
2022-02-02 14:35:52 +00:00
|
|
|
pub struct Key {
|
2023-03-13 14:03:54 +00:00
|
|
|
pub name: Option<String>,
|
2022-02-02 14:35:52 +00:00
|
|
|
pub id: String,
|
|
|
|
pub secret: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Instance {
|
|
|
|
process: process::Child,
|
|
|
|
pub path: PathBuf,
|
2023-03-13 14:03:54 +00:00
|
|
|
pub default_key: Key,
|
2022-05-10 11:16:57 +00:00
|
|
|
pub s3_port: u16,
|
|
|
|
pub k2v_port: u16,
|
|
|
|
pub web_port: u16,
|
2023-01-12 17:13:03 +00:00
|
|
|
pub admin_port: u16,
|
2022-02-02 14:35:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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"
|
2023-06-09 11:23:08 +00:00
|
|
|
db_engine = "sled"
|
2022-02-02 14:35:52 +00:00
|
|
|
|
|
|
|
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}"
|
2022-05-10 11:16:57 +00:00
|
|
|
api_bind_addr = "127.0.0.1:{s3_port}"
|
2022-02-02 14:35:52 +00:00
|
|
|
root_domain = ".s3.garage"
|
|
|
|
|
2022-05-10 11:16:57 +00:00
|
|
|
[k2v_api]
|
|
|
|
api_bind_addr = "127.0.0.1:{k2v_port}"
|
|
|
|
|
2022-02-02 14:35:52 +00:00
|
|
|
[s3_web]
|
|
|
|
bind_addr = "127.0.0.1:{web_port}"
|
|
|
|
root_domain = ".web.garage"
|
|
|
|
index = "index.html"
|
2022-02-16 13:23:04 +00:00
|
|
|
|
2022-02-22 14:25:13 +00:00
|
|
|
[admin]
|
|
|
|
api_bind_addr = "127.0.0.1:{admin_port}"
|
2022-02-02 14:35:52 +00:00
|
|
|
"#,
|
|
|
|
path = path.display(),
|
|
|
|
secret = GARAGE_TEST_SECRET,
|
|
|
|
region = super::REGION,
|
2022-05-10 11:16:57 +00:00
|
|
|
s3_port = port,
|
|
|
|
k2v_port = port + 1,
|
|
|
|
rpc_port = port + 2,
|
|
|
|
web_port = port + 3,
|
|
|
|
admin_port = port + 4,
|
2022-02-02 14:35:52 +00:00
|
|
|
);
|
|
|
|
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)
|
2022-05-10 11:16:57 +00:00
|
|
|
.env("RUST_LOG", "garage=info,garage_api=trace")
|
2022-02-02 14:35:52 +00:00
|
|
|
.spawn()
|
|
|
|
.expect("Could not start garage");
|
|
|
|
|
|
|
|
Instance {
|
|
|
|
process: child,
|
|
|
|
path,
|
2023-03-13 14:03:54 +00:00
|
|
|
default_key: Key::default(),
|
2022-05-10 11:16:57 +00:00
|
|
|
s3_port: port,
|
|
|
|
k2v_port: port + 1,
|
|
|
|
web_port: port + 3,
|
2023-01-12 17:13:03 +00:00
|
|
|
admin_port: port + 4,
|
2022-02-02 14:35:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn setup(&mut self) {
|
2023-03-13 09:48:47 +00:00
|
|
|
self.wait_for_boot();
|
2022-02-02 14:35:52 +00:00
|
|
|
self.setup_layout();
|
2023-03-13 14:03:54 +00:00
|
|
|
self.default_key = self.key(Some("garage_test"));
|
2022-02-02 14:35:52 +00:00
|
|
|
}
|
|
|
|
|
2023-03-13 09:48:47 +00:00
|
|
|
fn wait_for_boot(&mut self) {
|
|
|
|
use std::{thread, time::Duration};
|
2022-02-02 14:35:52 +00:00
|
|
|
|
2023-03-13 09:48:47 +00:00
|
|
|
// 60 * 2 seconds = 120 seconds = 2min
|
|
|
|
for _ in 0..60 {
|
|
|
|
let termination = self
|
|
|
|
.command()
|
|
|
|
.args(["status"])
|
|
|
|
.quiet()
|
|
|
|
.status()
|
|
|
|
.expect("Unable to run command");
|
|
|
|
if termination.success() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
thread::sleep(Duration::from_secs(2));
|
|
|
|
}
|
2022-02-02 14:35:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2022-11-08 14:13:37 +00:00
|
|
|
.args(["-c", "1G", "-z", "unzonned"])
|
2022-02-02 14:35:52 +00:00
|
|
|
.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()
|
|
|
|
}
|
|
|
|
|
2022-05-10 11:16:57 +00:00
|
|
|
pub fn s3_uri(&self) -> http::Uri {
|
|
|
|
format!("http://127.0.0.1:{s3_port}", s3_port = self.s3_port)
|
|
|
|
.parse()
|
|
|
|
.expect("Could not build garage endpoint URI")
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn k2v_uri(&self) -> http::Uri {
|
|
|
|
format!("http://127.0.0.1:{k2v_port}", k2v_port = self.k2v_port)
|
2022-02-02 14:35:52 +00:00
|
|
|
.parse()
|
|
|
|
.expect("Could not build garage endpoint URI")
|
|
|
|
}
|
|
|
|
|
2023-03-13 14:03:54 +00:00
|
|
|
pub fn key(&self, maybe_name: Option<&str>) -> Key {
|
2022-02-02 14:35:52 +00:00
|
|
|
let mut key = Key::default();
|
|
|
|
|
2023-03-13 14:03:54 +00:00
|
|
|
let mut cmd = self.command();
|
2023-04-25 10:34:26 +00:00
|
|
|
let base = cmd.args(["key", "create"]);
|
2023-03-13 14:03:54 +00:00
|
|
|
let with_name = match maybe_name {
|
2023-04-25 10:34:26 +00:00
|
|
|
Some(name) => base.args([name]),
|
2023-03-13 14:03:54 +00:00
|
|
|
None => base,
|
|
|
|
};
|
|
|
|
|
|
|
|
let output = with_name.expect_success_output("Could not create key");
|
2022-02-02 14:35:52 +00:00
|
|
|
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 {
|
2023-03-13 14:03:54 +00:00
|
|
|
name: maybe_name.map(String::from),
|
2022-02-02 14:35:52 +00:00
|
|
|
..key
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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() {
|
2022-02-04 16:53:46 +00:00
|
|
|
// This block is sound as it depends on `INSTANCE_INIT` being completed, meaning `INSTANCE`
|
|
|
|
// is actually initialized.
|
2022-02-02 14:35:52 +00:00
|
|
|
unsafe {
|
2022-02-02 15:07:26 +00:00
|
|
|
INSTANCE.assume_init_mut().terminate();
|
2022-02-02 14:35:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn instance() -> &'static Instance {
|
|
|
|
INSTANCE_INIT.call_once(|| unsafe {
|
|
|
|
let mut instance = Instance::new();
|
|
|
|
instance.setup();
|
|
|
|
|
|
|
|
INSTANCE.write(instance);
|
|
|
|
});
|
|
|
|
|
2022-02-04 16:53:46 +00:00
|
|
|
// This block is sound as it depends on `INSTANCE_INIT` being completed by calling `call_once` (blocking),
|
|
|
|
// meaning `INSTANCE` is actually initialized.
|
2022-02-02 14:35:52 +00:00
|
|
|
unsafe { INSTANCE.assume_init_ref() }
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn command(config_path: &Path) -> process::Command {
|
2022-02-04 16:53:46 +00:00
|
|
|
use std::env;
|
|
|
|
|
2022-02-03 17:04:43 +00:00
|
|
|
let mut command = process::Command::new(
|
2022-02-04 16:53:46 +00:00
|
|
|
env::var("GARAGE_TEST_INTEGRATION_EXE")
|
|
|
|
.unwrap_or_else(|_| env!("CARGO_BIN_EXE_garage").to_owned()),
|
2022-02-03 17:04:43 +00:00
|
|
|
);
|
2022-02-02 14:35:52 +00:00
|
|
|
|
|
|
|
command.arg("-c").arg(config_path);
|
|
|
|
|
|
|
|
command
|
|
|
|
}
|