Skip to content

Commit 69f71ba

Browse files
committed
Tests: Add util functions to perform DDL
Scylla doesn't like executing multiple DDLs from multiple nodes. To avoid it we will perform DDLs on a single node from all tests. Functions added in this commit will help do just that.
1 parent be14812 commit 69f71ba

File tree

2 files changed

+153
-2
lines changed

2 files changed

+153
-2
lines changed

scylla/src/utils/test_utils.rs

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1+
use crate::load_balancing::{FallbackPlan, LoadBalancingPolicy, RoutingInfo};
2+
use crate::query::Query;
3+
use crate::routing::Shard;
4+
use crate::transport::connection::Connection;
5+
use crate::transport::errors::QueryError;
16
use crate::transport::session_builder::{GenericSessionBuilder, SessionBuilderKind};
2-
use crate::Session;
7+
use crate::transport::{ClusterData, NodeRef};
8+
use crate::{CachingSession, ExecutionProfile, Session};
9+
use std::sync::Arc;
310
use std::{num::NonZeroU32, time::Duration};
411
use std::{
512
sync::atomic::{AtomicUsize, Ordering},
@@ -100,3 +107,81 @@ pub(crate) fn setup_tracing() {
100107
.with_writer(tracing_subscriber::fmt::TestWriter::new())
101108
.try_init();
102109
}
110+
111+
// This LBP produces a predictable query plan - it order the nodes
112+
// by position in the ring.
113+
// This is to make sure that all DDL queries land on the same node,
114+
// to prevent errors from concurrent DDL queries executed on different nodes.
115+
#[derive(Debug)]
116+
struct SchemaQueriesLBP;
117+
118+
impl LoadBalancingPolicy for SchemaQueriesLBP {
119+
fn pick<'a>(
120+
&'a self,
121+
_query: &'a RoutingInfo,
122+
cluster: &'a ClusterData,
123+
) -> Option<(NodeRef<'a>, Option<Shard>)> {
124+
// I'm not sure if Scylla can handle concurrent DDL queries to different shard,
125+
// in other words if its local lock is per-node or per shard.
126+
// Just to be safe, let's use explicit shard.
127+
cluster.get_nodes_info().first().map(|node| (node, Some(0)))
128+
}
129+
130+
fn fallback<'a>(
131+
&'a self,
132+
_query: &'a RoutingInfo,
133+
cluster: &'a ClusterData,
134+
) -> FallbackPlan<'a> {
135+
Box::new(cluster.get_nodes_info().iter().map(|node| (node, Some(0))))
136+
}
137+
138+
fn name(&self) -> String {
139+
"SchemaQueriesLBP".to_owned()
140+
}
141+
}
142+
143+
fn apply_ddl_lbp(query: &mut Query) {
144+
let policy = query
145+
.get_execution_profile_handle()
146+
.map(|profile| profile.pointee_to_builder())
147+
.unwrap_or(ExecutionProfile::builder())
148+
.load_balancing_policy(Arc::new(SchemaQueriesLBP))
149+
.build();
150+
query.set_execution_profile_handle(Some(policy.into_handle()));
151+
}
152+
153+
// This is just to make it easier to call the above function:
154+
// we'll be able to do session.ddl(...) instead of perform_ddl(&session, ...)
155+
// or something like that.
156+
#[allow(unused)]
157+
#[async_trait::async_trait]
158+
pub(crate) trait PerformDDL {
159+
async fn ddl(&self, query: impl Into<Query> + Send) -> Result<(), QueryError>;
160+
}
161+
162+
#[async_trait::async_trait]
163+
impl PerformDDL for Session {
164+
async fn ddl(&self, query: impl Into<Query> + Send) -> Result<(), QueryError> {
165+
let mut query = query.into();
166+
apply_ddl_lbp(&mut query);
167+
self.query_unpaged(query, &[]).await.map(|_| ())
168+
}
169+
}
170+
171+
#[async_trait::async_trait]
172+
impl PerformDDL for CachingSession {
173+
async fn ddl(&self, query: impl Into<Query> + Send) -> Result<(), QueryError> {
174+
let mut query = query.into();
175+
apply_ddl_lbp(&mut query);
176+
self.execute_unpaged(query, &[]).await.map(|_| ())
177+
}
178+
}
179+
180+
#[async_trait::async_trait]
181+
impl PerformDDL for Connection {
182+
async fn ddl(&self, query: impl Into<Query> + Send) -> Result<(), QueryError> {
183+
let mut query = query.into();
184+
apply_ddl_lbp(&mut query);
185+
self.query_unpaged(query).await.map(|_| ())
186+
}
187+
}

scylla/tests/integration/utils.rs

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
use futures::Future;
22
use scylla::deserialize::DeserializeValue;
3+
use scylla::load_balancing::{FallbackPlan, LoadBalancingPolicy, RoutingInfo};
4+
use scylla::query::Query;
5+
use scylla::routing::Shard;
6+
use scylla::transport::errors::QueryError;
37
use scylla::transport::session_builder::{GenericSessionBuilder, SessionBuilderKind};
4-
use scylla::Session;
8+
use scylla::transport::{ClusterData, NodeRef};
9+
use scylla::{ExecutionProfile, Session};
510
use std::collections::HashMap;
611
use std::env;
712
use std::net::SocketAddr;
813
use std::num::NonZeroU32;
914
use std::str::FromStr;
1015
use std::sync::atomic::{AtomicUsize, Ordering};
16+
use std::sync::Arc;
1117
use std::time::{Duration, SystemTime, UNIX_EPOCH};
1218

1319
use scylla_proxy::{Node, Proxy, ProxyError, RunningProxy, ShardAwareness};
@@ -167,3 +173,63 @@ impl<T> DeserializeOwnedValue for T where
167173
T: for<'frame, 'metadata> DeserializeValue<'frame, 'metadata>
168174
{
169175
}
176+
177+
// This LBP produces a predictable query plan - it order the nodes
178+
// by position in the ring.
179+
// This is to make sure that all DDL queries land on the same node,
180+
// to prevent errors from concurrent DDL queries executed on different nodes.
181+
#[derive(Debug)]
182+
struct SchemaQueriesLBP;
183+
184+
impl LoadBalancingPolicy for SchemaQueriesLBP {
185+
fn pick<'a>(
186+
&'a self,
187+
_query: &'a RoutingInfo,
188+
cluster: &'a ClusterData,
189+
) -> Option<(NodeRef<'a>, Option<Shard>)> {
190+
// I'm not sure if Scylla can handle concurrent DDL queries to different shard,
191+
// in other words if its local lock is per-node or per shard.
192+
// Just to be safe, let's use explicit shard.
193+
cluster.get_nodes_info().first().map(|node| (node, Some(0)))
194+
}
195+
196+
fn fallback<'a>(
197+
&'a self,
198+
_query: &'a RoutingInfo,
199+
cluster: &'a ClusterData,
200+
) -> FallbackPlan<'a> {
201+
Box::new(cluster.get_nodes_info().iter().map(|node| (node, Some(0))))
202+
}
203+
204+
fn name(&self) -> String {
205+
"SchemaQueriesLBP".to_owned()
206+
}
207+
}
208+
209+
fn apply_ddl_lbp(query: &mut Query) {
210+
let policy = query
211+
.get_execution_profile_handle()
212+
.map(|profile| profile.pointee_to_builder())
213+
.unwrap_or(ExecutionProfile::builder())
214+
.load_balancing_policy(Arc::new(SchemaQueriesLBP))
215+
.build();
216+
query.set_execution_profile_handle(Some(policy.into_handle()));
217+
}
218+
219+
// This is just to make it easier to call the above function:
220+
// we'll be able to do session.ddl(...) instead of perform_ddl(&session, ...)
221+
// or something like that.
222+
#[allow(unused)]
223+
#[async_trait::async_trait]
224+
pub(crate) trait PerformDDL {
225+
async fn ddl(&self, query: impl Into<Query> + Send) -> Result<(), QueryError>;
226+
}
227+
228+
#[async_trait::async_trait]
229+
impl PerformDDL for Session {
230+
async fn ddl(&self, query: impl Into<Query> + Send) -> Result<(), QueryError> {
231+
let mut query = query.into();
232+
apply_ddl_lbp(&mut query);
233+
self.query_unpaged(query, &[]).await.map(|_| ())
234+
}
235+
}

0 commit comments

Comments
 (0)