Skip to content

Commit a253772

Browse files
authored
Merge pull request #1233 from muzarski/ccm-tls-support
ccm: Add simple support for `updateconf` and setting up TLS
2 parents c839847 + 3bc9362 commit a253772

File tree

9 files changed

+307
-701
lines changed

9 files changed

+307
-701
lines changed

.github/workflows/rust.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ jobs:
4747
run: cargo check --all-targets --manifest-path "scylla/Cargo.toml" --features "full-serialization"
4848
- name: Cargo check with all features
4949
run: cargo check --all-targets --manifest-path "scylla/Cargo.toml" --all-features
50+
- name: Cargo check with ssl feature
51+
run: cargo check --all-targets --manifest-path "scylla/Cargo.toml" --features "ssl"
5052
- name: Cargo check with secrecy-08 feature
5153
run: cargo check --all-targets --manifest-path "scylla/Cargo.toml" --features "secrecy-08"
5254
- name: Cargo check with chrono-04 feature

.github/workflows/tls.yml

Lines changed: 0 additions & 44 deletions
This file was deleted.

scylla/tests/ccm_integration/ccm/cluster.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::ccm::{IP_ALLOCATOR, ROOT_CCM_DIR};
22

33
use super::ip_allocator::NetPrefix;
44
use super::logged_cmd::{LoggedCmd, RunOptions};
5+
use super::{DB_TLS_CERT_PATH, DB_TLS_KEY_PATH};
56
use anyhow::{Context, Error};
67
use scylla::client::session_builder::SessionBuilder;
78
use std::collections::HashMap;
@@ -264,6 +265,68 @@ impl Node {
264265
env
265266
}
266267

268+
/// Executes `ccm updateconf` and applies it for this node.
269+
/// It accepts the key-value pairs to update the configuration.
270+
///
271+
/// ### Example
272+
/// ```
273+
/// # use crate::ccm::cluster::Node;
274+
/// # async fn check_only_compiles(node: &Node) -> Result<(), Box<dyn Error>> {
275+
/// let args = [
276+
/// ("client_encryption_options.enabled", "true"),
277+
/// ("client_encryption_options.certificate", "db.cert"),
278+
/// ("client_encryption_options.keyfile", "db.key"),
279+
/// ];
280+
///
281+
/// node.updateconf(args).await?
282+
/// # Ok(())
283+
/// # }
284+
/// ```
285+
///
286+
/// The code above is equivalent to the following scylla.yaml:
287+
/// ```yaml
288+
/// client_encryption_options:
289+
/// enabled: true
290+
/// certificate: db.cert
291+
/// keyfile: db.key
292+
/// ```
293+
pub(crate) async fn updateconf<K, V>(
294+
&self,
295+
key_values: impl IntoIterator<Item = (K, V)>,
296+
) -> Result<(), Error>
297+
where
298+
K: AsRef<str>,
299+
V: AsRef<str>,
300+
{
301+
let config_dir = &self.config_dir;
302+
let mut args: Vec<String> = vec![
303+
self.opts.name(),
304+
"updateconf".to_string(),
305+
"--config-dir".to_string(),
306+
config_dir.to_string_lossy().into_owned(),
307+
];
308+
for (k, v) in key_values.into_iter() {
309+
args.push(format!("{}:{}", k.as_ref(), v.as_ref()));
310+
}
311+
312+
self.logged_cmd
313+
.run_command("ccm", &args, RunOptions::new())
314+
.await?;
315+
Ok(())
316+
}
317+
318+
/// Configures TLS based on the paths provided in the environment variables `DB_TLS_CERT_PATH` and `DB_TLS_KEY_PATH`.
319+
/// If the paths are not provided, the default certificate and key are taken from `./test/tls/db.crt` and `./test/tls/db.key`.
320+
pub(crate) async fn configure_tls(&self) -> Result<(), Error> {
321+
let args = [
322+
("client_encryption_options.enabled", "true"),
323+
("client_encryption_options.certificate", &DB_TLS_CERT_PATH),
324+
("client_encryption_options.keyfile", &DB_TLS_KEY_PATH),
325+
];
326+
327+
self.updateconf(args).await
328+
}
329+
267330
/// This method starts the node. User can provide optional [`NodeStartOptions`] to control the behavior of the node start.
268331
/// If `None` is provided, the default options are used (see the implementation of Default for [`NodeStartOptions`]).
269332
pub(crate) async fn start(&mut self, opts: Option<NodeStartOptions>) -> Result<(), Error> {
@@ -581,6 +644,67 @@ impl Cluster {
581644
Ok(())
582645
}
583646

647+
/// Executes `ccm updateconf` and applies it for all nodes in the cluster.
648+
/// It accepts the key-value pairs to update the configuration.
649+
///
650+
/// ### Example
651+
/// ```
652+
/// # use crate::ccm::cluster::Cluster;
653+
/// # async fn check_only_compiles(cluster: &Cluster) -> Result<(), Box<dyn Error>> {
654+
/// let args = [
655+
/// ("client_encryption_options.enabled", "true"),
656+
/// ("client_encryption_options.certificate", "db.cert"),
657+
/// ("client_encryption_options.keyfile", "db.key"),
658+
/// ];
659+
///
660+
/// cluster.updateconf(args).await?
661+
/// # Ok(())
662+
/// # }
663+
/// ```
664+
///
665+
/// The code above is equivalent to the following scylla.yaml:
666+
/// ```yaml
667+
/// client_encryption_options:
668+
/// enabled: true
669+
/// certificate: db.cert
670+
/// keyfile: db.key
671+
/// ```
672+
pub(crate) async fn updateconf<K, V>(
673+
&self,
674+
key_values: impl IntoIterator<Item = (K, V)>,
675+
) -> Result<(), Error>
676+
where
677+
K: AsRef<str>,
678+
V: AsRef<str>,
679+
{
680+
let config_dir = self.config_dir();
681+
let mut args: Vec<String> = vec![
682+
"updateconf".to_string(),
683+
"--config-dir".to_string(),
684+
config_dir.to_string_lossy().into_owned(),
685+
];
686+
for (k, v) in key_values.into_iter() {
687+
args.push(format!("{}:{}", k.as_ref(), v.as_ref()));
688+
}
689+
690+
self.logged_cmd
691+
.run_command("ccm", &args, RunOptions::new())
692+
.await?;
693+
Ok(())
694+
}
695+
696+
/// Configures TLS based on the paths provided in the environment variables `DB_TLS_CERT_PATH` and `DB_TLS_KEY_PATH`.
697+
/// If the paths are not provided, the default certificate and key are taken from `./test/tls/db.crt` and `./test/tls/db.key`.
698+
pub(crate) async fn configure_tls(&self) -> Result<(), Error> {
699+
let args = [
700+
("client_encryption_options.enabled", "true"),
701+
("client_encryption_options.certificate", &DB_TLS_CERT_PATH),
702+
("client_encryption_options.keyfile", &DB_TLS_KEY_PATH),
703+
];
704+
705+
self.updateconf(args).await
706+
}
707+
584708
fn get_ccm_env(&self) -> HashMap<String, String> {
585709
let mut env: HashMap<String, String> = HashMap::new();
586710
env.insert(

scylla/tests/ccm_integration/ccm/mod.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,126 @@ static ROOT_CCM_DIR: LazyLock<String> = LazyLock::new(|| {
5555
ccm_root_dir
5656
});
5757

58+
pub(crate) static DB_TLS_CERT_PATH: LazyLock<String> = LazyLock::new(|| {
59+
let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR");
60+
let db_cert_path_env = std::env::var("DB_TLS_CERT_PATH");
61+
let db_cert_path = match db_cert_path_env {
62+
Ok(x) => x,
63+
Err(e) => {
64+
info!(
65+
"DB_TLS_CERT_PATH env malformed or not present: {}. Using {}/../test/tls/db.crt for db cert.",
66+
e, cargo_manifest_dir
67+
);
68+
cargo_manifest_dir.to_string() + "/../test/tls/db.crt"
69+
}
70+
};
71+
72+
let path = PathBuf::from(&db_cert_path);
73+
if !path.try_exists().unwrap() {
74+
panic!("DB cert file {:?} not found", path);
75+
}
76+
77+
db_cert_path
78+
});
79+
80+
pub(crate) static DB_TLS_KEY_PATH: LazyLock<String> = LazyLock::new(|| {
81+
let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR");
82+
let db_key_path_env = std::env::var("DB_TLS_KEY_PATH");
83+
let db_key_path = match db_key_path_env {
84+
Ok(x) => x,
85+
Err(e) => {
86+
info!(
87+
"DB_TLS_KEY_PATH env malformed or not present: {}. Using {}/../test/tls/db.key for db key.",
88+
e, cargo_manifest_dir
89+
);
90+
cargo_manifest_dir.to_string() + "/../test/tls/db.key"
91+
}
92+
};
93+
94+
let path = PathBuf::from(&db_key_path);
95+
if !path.try_exists().unwrap() {
96+
panic!("DB key file {:?} not found", path);
97+
}
98+
99+
db_key_path
100+
});
101+
102+
#[cfg(feature = "ssl")]
103+
pub(crate) static CA_TLS_CERT_PATH: LazyLock<String> = LazyLock::new(|| {
104+
let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR");
105+
let ca_cert_path_env = std::env::var("CA_TLS_CERT_PATH");
106+
let ca_cert_path = match ca_cert_path_env {
107+
Ok(x) => x,
108+
Err(e) => {
109+
info!(
110+
"CA_TLS_CERT_PATH env malformed or not present: {}. Using {}/../test/tls/ca.crt for ca cert.",
111+
e, cargo_manifest_dir
112+
);
113+
cargo_manifest_dir.to_string() + "/../test/tls/ca.crt"
114+
}
115+
};
116+
117+
let path = PathBuf::from(&ca_cert_path);
118+
if !path.try_exists().unwrap() {
119+
panic!("DB cert file {:?} not found", path);
120+
}
121+
122+
ca_cert_path
123+
});
124+
58125
pub(crate) async fn run_ccm_test<C, T, Fut>(make_cluster_options: C, test_body: T)
59126
where
60127
C: FnOnce() -> ClusterOptions,
61128
T: FnOnce(Arc<tokio::sync::Mutex<Cluster>>) -> Fut,
62129
Fut: Future<Output = ()>,
130+
{
131+
run_ccm_test_with_configuration(
132+
make_cluster_options,
133+
|cluster| async move { cluster },
134+
test_body,
135+
)
136+
.await
137+
}
138+
139+
/// Run a CCM test with a custom configuration logic before the cluster starts.
140+
///
141+
/// ### Example
142+
/// ```
143+
/// # use crate::ccm::cluster::Cluster;
144+
/// # use crate::ccm::run_ccm_test_with_configuration;
145+
/// # use std::sync::{Arc, Mutex};
146+
/// async fn configure_cluster(cluster: Cluster) -> Cluster {
147+
/// // Do some configuration here
148+
/// cluster.updateconf([("foo", "bar")]).await.expect("failed to update conf");
149+
/// cluster
150+
/// }
151+
///
152+
/// async fn test(cluster: Arc<Mutex<Cluster>>) {
153+
/// let cluster = cluster.lock().await;
154+
/// let session = cluster.make_session_builder().await.build().await.unwrap();
155+
///
156+
/// println!("Succesfully connected to the cluster!");
157+
/// }
158+
///
159+
/// run_ccm_test_with_configuration(ClusterOptions::default, configure_cluster, test).await;
160+
/// ```
161+
pub(crate) async fn run_ccm_test_with_configuration<C, Conf, ConfFut, T, TFut>(
162+
make_cluster_options: C,
163+
configure: Conf,
164+
test_body: T,
165+
) where
166+
C: FnOnce() -> ClusterOptions,
167+
Conf: FnOnce(Cluster) -> ConfFut,
168+
ConfFut: Future<Output = Cluster>,
169+
T: FnOnce(Arc<tokio::sync::Mutex<Cluster>>) -> TFut,
170+
TFut: Future<Output = ()>,
63171
{
64172
let cluster_options = make_cluster_options();
65173
let mut cluster = Cluster::new(cluster_options)
66174
.await
67175
.expect("Failed to create cluster");
68176
cluster.init().await.expect("failed to initialize cluster");
177+
cluster = configure(cluster).await;
69178
cluster.start(None).await.expect("failed to start cluster");
70179

71180
struct ClusterWrapper(Arc<tokio::sync::Mutex<Cluster>>);

scylla/tests/ccm_integration/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ mod common;
33

44
pub(crate) mod ccm;
55
mod test_example;
6+
#[cfg(feature = "ssl")]
7+
mod tls;

0 commit comments

Comments
 (0)