From de653000c45204e9cc323329052dc47e105fc8ec Mon Sep 17 00:00:00 2001 From: Yaroslav Dynnikov <yaroslav.dynnikov@gmail.com> Date: Tue, 21 Jun 2022 23:33:10 +0300 Subject: [PATCH] feature: parse --failure-domain Part of https://git.picodata.io/picodata/picodata/picodata/-/issues/98 --- src/args.rs | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/args.rs b/src/args.rs index bef897b3f2..3600e217c8 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,6 +1,7 @@ use clap::Parser; use std::{ borrow::Cow, + collections::HashMap, ffi::{CStr, CString}, }; use tarantool::log::SayLevel; @@ -93,6 +94,23 @@ pub struct Run { /// Address(es) of other instance(s) pub peers: Vec<String>, + #[clap( + long = "failure-domain", + value_name = "key=value", + require_value_delimiter = true, + use_value_delimiter = true, + parse(try_from_str = try_parse_kv), + env = "PICODATA_FAILURE_DOMAIN" + )] + /// Comma-separated list describing physical location of the server. + /// Each domain is a key-value pair. Until max replicaset count is + /// reached, picodata will avoid putting two instances into the same + /// replicaset if at least one key of their failure domains has the + /// same value. Instead, new replicasets will be created. + /// Replicasets will be populated with instances from different + /// failure domains until the desired replication factor is reached. + pub failure_domains: Vec<(String, String)>, + #[clap(long, value_name = "name", env = "PICODATA_REPLICASET_ID")] /// Name of the replicaset pub replicaset_id: Option<String>, @@ -160,6 +178,15 @@ impl Run { any => Some(any.to_string()), } } + + #[allow(unused)] + pub fn failure_domains(&self) -> HashMap<&str, &str> { + let mut ret = HashMap::new(); + for (k, v) in &self.failure_domains { + ret.insert(k.as_ref(), v.as_ref()); + } + ret + } } //////////////////////////////////////////////////////////////////////////////// @@ -237,6 +264,13 @@ fn try_parse_address(text: &str) -> Result<String, ParseAddressError> { Ok(format!("{host}:{port}")) } +fn try_parse_kv(s: &str) -> Result<(String, String), String> { + let pos = s + .find('=') + .ok_or_else(|| format!("invalid KEY=value: no `=` found in `{}`", s))?; + Ok((s[..pos].into(), s[pos + 1..].into())) +} + #[cfg(test)] mod tests { use super::*; @@ -300,6 +334,7 @@ mod tests { assert_eq!(parsed.listen, "localhost:3301"); // default assert_eq!(parsed.advertise_address(), "localhost:3301"); // default assert_eq!(parsed.log_level(), SayLevel::Info); // default + assert_eq!(parsed.failure_domains(), HashMap::new()); // default let parsed = parse![Run, "--instance-id", "instance-id-from-args"]; assert_eq!( @@ -321,6 +356,9 @@ mod tests { let parsed = parse![Run, "--peer", ":3302"]; assert_eq!(parsed.peers.as_ref(), vec!["localhost:3302"]); + + let parsed = parse![Run, "--peer", "p1", "--peer", "p2,p3"]; + assert_eq!(parsed.peers.as_ref(), vec!["p1:3301", "p2:3301", "p3:3301"]); } std::env::set_var("PICODATA_INSTANCE_ID", ""); @@ -369,5 +407,32 @@ mod tests { let parsed = parse![Run, "--log-level", "warn"]; assert_eq!(parsed.log_level(), SayLevel::Warn); } + + std::env::set_var("PICODATA_FAILURE_DOMAIN", "k1=env1,k2=env2"); + { + let parsed = parse![Run,]; + assert_eq!( + parsed.failure_domains(), + HashMap::from([("k1", "env1"), ("k2", "env2")]) + ); + + let parsed = parse![Run, "--failure-domain", "k1=arg1,k1=arg1-again"]; + assert_eq!( + parsed.failure_domains(), + HashMap::from([("k1", "arg1-again")]) + ); + + let parsed = parse![ + Run, + "--failure-domain", + "k2=arg2", + "--failure-domain", + "k3=arg3,k4=arg4" + ]; + assert_eq!( + parsed.failure_domains(), + HashMap::from([("k2", "arg2"), ("k3", "arg3"), ("k4", "arg4")]) + ); + } } } -- GitLab