Merge branch 'main' into dependabot/cargo/paste-1.0.6
commit
6e8517cb1f
|
@ -3506,16 +3506,23 @@ dependencies = [
|
||||||
name = "router"
|
name = "router"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"async-trait",
|
||||||
|
"cache_loader_async",
|
||||||
"data_types",
|
"data_types",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
|
"influxdb_iox_client",
|
||||||
"metric",
|
"metric",
|
||||||
"mutable_batch",
|
"mutable_batch",
|
||||||
"mutable_batch_lp",
|
"mutable_batch_lp",
|
||||||
|
"mutable_batch_pb",
|
||||||
|
"observability_deps",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"regex",
|
"regex",
|
||||||
"snafu",
|
"snafu",
|
||||||
"time 0.1.0",
|
"time 0.1.0",
|
||||||
|
"tokio",
|
||||||
"trace",
|
"trace",
|
||||||
|
"write_buffer",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{collections::HashMap, num::NonZeroU32};
|
use std::{collections::BTreeMap, num::NonZeroU32};
|
||||||
|
|
||||||
/// If the buffer is used for reading or writing.
|
/// If the buffer is used for reading or writing.
|
||||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||||
pub enum WriteBufferDirection {
|
pub enum WriteBufferDirection {
|
||||||
/// Writes into the buffer aka "producer".
|
/// Writes into the buffer aka "producer".
|
||||||
Write,
|
Write,
|
||||||
|
@ -13,7 +13,7 @@ pub enum WriteBufferDirection {
|
||||||
pub const DEFAULT_N_SEQUENCERS: u32 = 1;
|
pub const DEFAULT_N_SEQUENCERS: u32 = 1;
|
||||||
|
|
||||||
/// Configures the use of a write buffer.
|
/// Configures the use of a write buffer.
|
||||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||||
pub struct WriteBufferConnection {
|
pub struct WriteBufferConnection {
|
||||||
/// If the buffer is used for reading or writing.
|
/// If the buffer is used for reading or writing.
|
||||||
pub direction: WriteBufferDirection,
|
pub direction: WriteBufferDirection,
|
||||||
|
@ -27,7 +27,9 @@ pub struct WriteBufferConnection {
|
||||||
/// Special configs to be applied when establishing the connection.
|
/// Special configs to be applied when establishing the connection.
|
||||||
///
|
///
|
||||||
/// This depends on [`type_`](Self::type_) and can configure aspects like timeouts.
|
/// This depends on [`type_`](Self::type_) and can configure aspects like timeouts.
|
||||||
pub connection_config: HashMap<String, String>,
|
///
|
||||||
|
/// Note: This config should be a [`BTreeMap`] to ensure that a stable hash.
|
||||||
|
pub connection_config: BTreeMap<String, String>,
|
||||||
|
|
||||||
/// Specifies if the sequencers (e.g. for Kafka in form of a topic) should be automatically created if they do not
|
/// Specifies if the sequencers (e.g. for Kafka in form of a topic) should be automatically created if they do not
|
||||||
/// existing prior to reading or writing.
|
/// existing prior to reading or writing.
|
||||||
|
@ -50,7 +52,7 @@ impl Default for WriteBufferConnection {
|
||||||
///
|
///
|
||||||
/// What that means depends on the used write buffer, e.g. for Kafka this will create a new topic w/
|
/// What that means depends on the used write buffer, e.g. for Kafka this will create a new topic w/
|
||||||
/// [`n_sequencers`](Self::n_sequencers) partitions.
|
/// [`n_sequencers`](Self::n_sequencers) partitions.
|
||||||
#[derive(Debug, Eq, PartialEq, Clone)]
|
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
|
||||||
pub struct WriteBufferCreationConfig {
|
pub struct WriteBufferCreationConfig {
|
||||||
/// Number of sequencers.
|
/// Number of sequencers.
|
||||||
///
|
///
|
||||||
|
@ -61,7 +63,9 @@ pub struct WriteBufferCreationConfig {
|
||||||
/// Special configs to by applied when sequencers are created.
|
/// Special configs to by applied when sequencers are created.
|
||||||
///
|
///
|
||||||
/// This depends on [type](WriteBufferConnection::type_) and can setup parameters like retention policy.
|
/// This depends on [type](WriteBufferConnection::type_) and can setup parameters like retention policy.
|
||||||
pub options: HashMap<String, String>,
|
///
|
||||||
|
/// Note: This config should be a [`BTreeMap`] to ensure that a stable hash.
|
||||||
|
pub options: BTreeMap<String, String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WriteBufferCreationConfig {
|
impl Default for WriteBufferCreationConfig {
|
||||||
|
|
|
@ -17,7 +17,7 @@ impl From<WriteBufferConnection> for write_buffer::WriteBufferConnection {
|
||||||
direction: direction.into(),
|
direction: direction.into(),
|
||||||
r#type: v.type_,
|
r#type: v.type_,
|
||||||
connection: v.connection,
|
connection: v.connection,
|
||||||
connection_config: v.connection_config,
|
connection_config: v.connection_config.into_iter().collect(),
|
||||||
creation_config: v.creation_config.map(|x| x.into()),
|
creation_config: v.creation_config.map(|x| x.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ impl From<WriteBufferCreationConfig> for write_buffer::WriteBufferCreationConfig
|
||||||
fn from(v: WriteBufferCreationConfig) -> Self {
|
fn from(v: WriteBufferCreationConfig) -> Self {
|
||||||
Self {
|
Self {
|
||||||
n_sequencers: v.n_sequencers.get(),
|
n_sequencers: v.n_sequencers.get(),
|
||||||
options: v.options,
|
options: v.options.into_iter().collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ impl TryFrom<write_buffer::WriteBufferConnection> for WriteBufferConnection {
|
||||||
direction: direction.try_into()?,
|
direction: direction.try_into()?,
|
||||||
type_: proto.r#type,
|
type_: proto.r#type,
|
||||||
connection: proto.connection,
|
connection: proto.connection,
|
||||||
connection_config: proto.connection_config,
|
connection_config: proto.connection_config.into_iter().collect(),
|
||||||
creation_config: proto.creation_config.optional("creation_config")?,
|
creation_config: proto.creation_config.optional("creation_config")?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -86,7 +86,7 @@ impl TryFrom<write_buffer::WriteBufferCreationConfig> for WriteBufferCreationCon
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
n_sequencers: NonZeroU32::try_from(proto.n_sequencers)
|
n_sequencers: NonZeroU32::try_from(proto.n_sequencers)
|
||||||
.unwrap_or_else(|_| NonZeroU32::try_from(DEFAULT_N_SEQUENCERS).unwrap()),
|
.unwrap_or_else(|_| NonZeroU32::try_from(DEFAULT_N_SEQUENCERS).unwrap()),
|
||||||
options: proto.options,
|
options: proto.options.into_iter().collect(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,22 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
async-trait = "0.1"
|
||||||
|
cache_loader_async = "0.1.2"
|
||||||
data_types = { path = "../data_types" }
|
data_types = { path = "../data_types" }
|
||||||
hashbrown = "0.11"
|
hashbrown = "0.11"
|
||||||
|
influxdb_iox_client = { path = "../influxdb_iox_client" }
|
||||||
metric = { path = "../metric" }
|
metric = { path = "../metric" }
|
||||||
mutable_batch = { path = "../mutable_batch" }
|
mutable_batch = { path = "../mutable_batch" }
|
||||||
|
mutable_batch_pb = { path = "../mutable_batch_pb" }
|
||||||
|
observability_deps = { path = "../observability_deps" }
|
||||||
trace = { path = "../trace" }
|
trace = { path = "../trace" }
|
||||||
parking_lot = "0.11.2"
|
parking_lot = "0.11.2"
|
||||||
snafu = "0.6"
|
snafu = "0.6"
|
||||||
|
time = { path = "../time" }
|
||||||
|
write_buffer = { path = "../write_buffer" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
mutable_batch_lp = { path = "../mutable_batch_lp" }
|
mutable_batch_lp = { path = "../mutable_batch_lp" }
|
||||||
regex = "1.4"
|
regex = "1.4"
|
||||||
time = { path = "../time" }
|
tokio = { version = "1.13", features = ["macros", "time"] }
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use cache_loader_async::cache_api::LoadingCache;
|
||||||
|
use data_types::write_buffer::WriteBufferConnection;
|
||||||
|
use observability_deps::tracing::debug;
|
||||||
|
use write_buffer::{
|
||||||
|
config::WriteBufferConfigFactory,
|
||||||
|
core::{WriteBufferError, WriteBufferWriting},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::grpc_client::GrpcClient;
|
||||||
|
|
||||||
|
type KeyWriteBufferProducer = (String, WriteBufferConnection);
|
||||||
|
pub type ConnectionError = Arc<dyn std::error::Error + Send + Sync + 'static>;
|
||||||
|
|
||||||
|
/// Stupid hack to fit the `Box<dyn ...>` in `WriteBufferError` into an `Arc`
|
||||||
|
struct EWrapper(WriteBufferError);
|
||||||
|
|
||||||
|
impl std::fmt::Debug for EWrapper {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for EWrapper {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for EWrapper {}
|
||||||
|
|
||||||
|
/// Connection pool for the entire routing server.
|
||||||
|
///
|
||||||
|
/// This avoids:
|
||||||
|
/// 1. That every [`Router`](crate::router::Router) uses their own connections
|
||||||
|
/// 2. That we open too many connections in total.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ConnectionPool {
|
||||||
|
grpc_clients: LoadingCache<String, Arc<dyn GrpcClient>, ConnectionError>,
|
||||||
|
write_buffer_producers:
|
||||||
|
LoadingCache<KeyWriteBufferProducer, Arc<dyn WriteBufferWriting>, ConnectionError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ConnectionPool {
|
||||||
|
/// Create new connection pool.
|
||||||
|
///
|
||||||
|
/// If `use_mock_grpc` is set only mock gRPC clients are created.
|
||||||
|
pub async fn new(use_mock_grpc: bool, wb_factory: WriteBufferConfigFactory) -> Self {
|
||||||
|
// Note: this function is async even though it does not contain any `.await` calls because `LoadingCache::new`
|
||||||
|
// requires tokio to be running and even if documented people will forget about this.
|
||||||
|
|
||||||
|
let grpc_clients = if use_mock_grpc {
|
||||||
|
LoadingCache::new(|_connection_string: String| async move {
|
||||||
|
use crate::grpc_client::MockClient;
|
||||||
|
|
||||||
|
Ok(Arc::new(MockClient::default()) as Arc<dyn GrpcClient>)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
LoadingCache::new(|connection_string: String| async move {
|
||||||
|
use crate::grpc_client::RealClient;
|
||||||
|
use influxdb_iox_client::connection::Builder;
|
||||||
|
|
||||||
|
let connection = Builder::default()
|
||||||
|
.build(&connection_string)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Arc::new(e) as ConnectionError)?;
|
||||||
|
Ok(Arc::new(RealClient::new(connection)) as Arc<dyn GrpcClient>)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let wb_factory = Arc::new(wb_factory);
|
||||||
|
let write_buffer_producers = LoadingCache::new(move |key: KeyWriteBufferProducer| {
|
||||||
|
let wb_factory = Arc::clone(&wb_factory);
|
||||||
|
async move {
|
||||||
|
wb_factory
|
||||||
|
.new_config_write(&key.0, &key.1)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Arc::new(EWrapper(e)) as ConnectionError)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
grpc_clients,
|
||||||
|
write_buffer_producers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create new connection factory for testing purposes.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub async fn new_testing() -> Self {
|
||||||
|
use time::SystemProvider;
|
||||||
|
|
||||||
|
let time_provider = Arc::new(SystemProvider::new());
|
||||||
|
Self::new(true, WriteBufferConfigFactory::new(time_provider)).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get gRPC client given a connection string.
|
||||||
|
pub async fn grpc_client(
|
||||||
|
&self,
|
||||||
|
connection_string: &str,
|
||||||
|
) -> Result<Arc<dyn GrpcClient>, ConnectionError> {
|
||||||
|
let res = self
|
||||||
|
.grpc_clients
|
||||||
|
.get_with_meta(connection_string.to_string())
|
||||||
|
.await
|
||||||
|
.map_err(|e| Arc::new(e) as ConnectionError)?;
|
||||||
|
debug!(was_cached=%res.cached, %connection_string, "getting IOx client");
|
||||||
|
Ok(res.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get write buffer producer given a DB name and config.
|
||||||
|
pub async fn write_buffer_producer(
|
||||||
|
&self,
|
||||||
|
db_name: &str,
|
||||||
|
cfg: &WriteBufferConnection,
|
||||||
|
) -> Result<Arc<dyn WriteBufferWriting>, ConnectionError> {
|
||||||
|
let res = self
|
||||||
|
.write_buffer_producers
|
||||||
|
.get_with_meta((db_name.to_string(), cfg.clone()))
|
||||||
|
.await
|
||||||
|
.map_err(|e| Arc::new(e) as ConnectionError)?;
|
||||||
|
debug!(was_cached=%res.cached, %db_name, "getting write buffer");
|
||||||
|
Ok(res.result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use time::{SystemProvider, TimeProvider};
|
||||||
|
|
||||||
|
use crate::grpc_client::MockClient;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_grpc_mocking() {
|
||||||
|
let time_provider: Arc<dyn TimeProvider> = Arc::new(SystemProvider::new());
|
||||||
|
|
||||||
|
let pool1 = ConnectionPool::new(
|
||||||
|
false,
|
||||||
|
WriteBufferConfigFactory::new(Arc::clone(&time_provider)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
// connection will fail
|
||||||
|
pool1.grpc_client("foo").await.unwrap_err();
|
||||||
|
|
||||||
|
let pool2 = ConnectionPool::new(true, WriteBufferConfigFactory::new(time_provider)).await;
|
||||||
|
let client2 = pool2.grpc_client("foo").await.unwrap();
|
||||||
|
client2.as_any().downcast_ref::<MockClient>().unwrap();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,226 @@
|
||||||
|
//! gRPC clients abastraction.
|
||||||
|
//!
|
||||||
|
//! This abstraction was created for easier testing.
|
||||||
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
sync::atomic::{AtomicBool, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use mutable_batch::DbWrite;
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
|
/// Generic write error.
|
||||||
|
pub type WriteError = Box<dyn std::error::Error + Send + Sync>;
|
||||||
|
|
||||||
|
/// An abstract IOx gRPC client.
|
||||||
|
#[async_trait]
|
||||||
|
pub trait GrpcClient: Sync + Send + std::fmt::Debug + 'static {
|
||||||
|
/// Write data to the given database.
|
||||||
|
async fn write(&self, db_name: &str, write: &DbWrite) -> Result<(), WriteError>;
|
||||||
|
|
||||||
|
/// Cast client to [`Any`], useful for downcasting.
|
||||||
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A real, network-driven gRPC client.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RealClient {
|
||||||
|
/// Write client for IOx.
|
||||||
|
write_client: influxdb_iox_client::write::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RealClient {
|
||||||
|
/// Create new client from established connection.
|
||||||
|
pub fn new(connection: influxdb_iox_client::connection::Connection) -> Self {
|
||||||
|
Self {
|
||||||
|
write_client: influxdb_iox_client::write::Client::new(connection),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl GrpcClient for RealClient {
|
||||||
|
async fn write(&self, db_name: &str, write: &DbWrite) -> Result<(), WriteError> {
|
||||||
|
use influxdb_iox_client::write::generated_types::WriteRequest;
|
||||||
|
use mutable_batch_pb::encode::encode_write;
|
||||||
|
|
||||||
|
let write_request = WriteRequest {
|
||||||
|
database_batch: Some(encode_write(db_name, write)),
|
||||||
|
};
|
||||||
|
|
||||||
|
// cheap, see https://docs.rs/tonic/0.4.2/tonic/client/index.html#concurrent-usage
|
||||||
|
let mut client = self.write_client.clone();
|
||||||
|
|
||||||
|
client
|
||||||
|
.write_pb(write_request)
|
||||||
|
.await
|
||||||
|
.map_err(|e| Box::new(e) as _)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mock client for testing.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct MockClient {
|
||||||
|
/// All writes recorded by this client.
|
||||||
|
writes: RwLock<Vec<(String, DbWrite)>>,
|
||||||
|
|
||||||
|
/// Poisen pill.
|
||||||
|
///
|
||||||
|
/// If set to `true` all writes will fail.
|
||||||
|
poisoned: AtomicBool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MockClient {
|
||||||
|
/// Take poison pill.
|
||||||
|
///
|
||||||
|
/// All subsequent writes will fail.
|
||||||
|
pub fn poison(&self) {
|
||||||
|
self.poisoned.store(true, Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a copy of all recorded writes.
|
||||||
|
pub fn writes(&self) -> Vec<(String, DbWrite)> {
|
||||||
|
self.writes.read().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assert that writes are as expected.
|
||||||
|
pub fn assert_writes(&self, expected: &[(String, DbWrite)]) {
|
||||||
|
use mutable_batch::test_util::assert_writes_eq;
|
||||||
|
|
||||||
|
let actual = self.writes();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
actual.len(),
|
||||||
|
expected.len(),
|
||||||
|
"number of writes differ ({} VS {})",
|
||||||
|
actual.len(),
|
||||||
|
expected.len()
|
||||||
|
);
|
||||||
|
|
||||||
|
for ((actual_db, actual_write), (expected_db, expected_write)) in
|
||||||
|
actual.iter().zip(expected)
|
||||||
|
{
|
||||||
|
assert_eq!(
|
||||||
|
actual_db, expected_db,
|
||||||
|
"database names differ (\"{}\" VS \"{}\")",
|
||||||
|
actual_db, expected_db
|
||||||
|
);
|
||||||
|
assert_writes_eq(actual_write, expected_write);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl GrpcClient for MockClient {
|
||||||
|
async fn write(&self, db_name: &str, write: &DbWrite) -> Result<(), WriteError> {
|
||||||
|
if self.poisoned.load(Ordering::SeqCst) {
|
||||||
|
return Err("poisened".to_string().into());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.writes
|
||||||
|
.write()
|
||||||
|
.push((db_name.to_string(), write.clone()));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use mutable_batch_lp::lines_to_batches;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_mock() {
|
||||||
|
let client = MockClient::default();
|
||||||
|
|
||||||
|
let write1 = DbWrite::new(
|
||||||
|
lines_to_batches("foo x=1 1", 0).unwrap(),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
|
let write2 = DbWrite::new(
|
||||||
|
lines_to_batches("foo x=2 2", 0).unwrap(),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
|
let write3 = DbWrite::new(
|
||||||
|
lines_to_batches("foo x=3 3", 0).unwrap(),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
client.write("db1", &write1).await.unwrap();
|
||||||
|
client.write("db2", &write1).await.unwrap();
|
||||||
|
client.write("db1", &write2).await.unwrap();
|
||||||
|
|
||||||
|
let expected_writes = vec![
|
||||||
|
(String::from("db1"), write1.clone()),
|
||||||
|
(String::from("db2"), write1.clone()),
|
||||||
|
(String::from("db1"), write2.clone()),
|
||||||
|
];
|
||||||
|
client.assert_writes(&expected_writes);
|
||||||
|
|
||||||
|
client.poison();
|
||||||
|
client.write("db1", &write3).await.unwrap_err();
|
||||||
|
client.assert_writes(&expected_writes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[should_panic(expected = "number of writes differ (1 VS 0)")]
|
||||||
|
async fn test_assert_writes_fail_count() {
|
||||||
|
let client = MockClient::default();
|
||||||
|
|
||||||
|
let write1 = DbWrite::new(
|
||||||
|
lines_to_batches("foo x=1 1", 0).unwrap(),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
client.write("db1", &write1).await.unwrap();
|
||||||
|
|
||||||
|
let expected_writes = [];
|
||||||
|
client.assert_writes(&expected_writes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[should_panic(expected = "database names differ (\"db1\" VS \"db2\")")]
|
||||||
|
async fn test_assert_writes_fail_db_name() {
|
||||||
|
let client = MockClient::default();
|
||||||
|
|
||||||
|
let write = DbWrite::new(
|
||||||
|
lines_to_batches("foo x=1 1", 0).unwrap(),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
client.write("db1", &write).await.unwrap();
|
||||||
|
|
||||||
|
let expected_writes = vec![(String::from("db2"), write)];
|
||||||
|
client.assert_writes(&expected_writes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[should_panic(expected = "batches for table \"foo\" differ")]
|
||||||
|
async fn test_assert_writes_fail_batch() {
|
||||||
|
let client = MockClient::default();
|
||||||
|
|
||||||
|
let write1 = DbWrite::new(
|
||||||
|
lines_to_batches("foo x=1 1", 0).unwrap(),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
|
let write2 = DbWrite::new(
|
||||||
|
lines_to_batches("foo x=2 2", 0).unwrap(),
|
||||||
|
Default::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
client.write("db1", &write1).await.unwrap();
|
||||||
|
|
||||||
|
let expected_writes = vec![(String::from("db1"), write2)];
|
||||||
|
client.assert_writes(&expected_writes);
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,8 @@
|
||||||
clippy::clone_on_ref_ptr
|
clippy::clone_on_ref_ptr
|
||||||
)]
|
)]
|
||||||
|
|
||||||
|
pub mod connection_pool;
|
||||||
|
pub mod grpc_client;
|
||||||
pub mod resolver;
|
pub mod resolver;
|
||||||
pub mod router;
|
pub mod router;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeMap, BTreeSet, HashMap},
|
collections::{BTreeMap, BTreeSet},
|
||||||
convert::{TryFrom, TryInto},
|
convert::{TryFrom, TryInto},
|
||||||
num::NonZeroU32,
|
num::NonZeroU32,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -129,7 +129,7 @@ impl KafkaBufferProducer {
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
conn: impl Into<String> + Send,
|
conn: impl Into<String> + Send,
|
||||||
database_name: impl Into<String> + Send,
|
database_name: impl Into<String> + Send,
|
||||||
connection_config: &HashMap<String, String>,
|
connection_config: &BTreeMap<String, String>,
|
||||||
creation_config: Option<&WriteBufferCreationConfig>,
|
creation_config: Option<&WriteBufferCreationConfig>,
|
||||||
time_provider: Arc<dyn TimeProvider>,
|
time_provider: Arc<dyn TimeProvider>,
|
||||||
) -> Result<Self, WriteBufferError> {
|
) -> Result<Self, WriteBufferError> {
|
||||||
|
@ -313,7 +313,7 @@ impl KafkaBufferConsumer {
|
||||||
conn: impl Into<String> + Send + Sync,
|
conn: impl Into<String> + Send + Sync,
|
||||||
server_id: ServerId,
|
server_id: ServerId,
|
||||||
database_name: impl Into<String> + Send + Sync,
|
database_name: impl Into<String> + Send + Sync,
|
||||||
connection_config: &HashMap<String, String>,
|
connection_config: &BTreeMap<String, String>,
|
||||||
creation_config: Option<&WriteBufferCreationConfig>,
|
creation_config: Option<&WriteBufferCreationConfig>,
|
||||||
// `trace_collector` has to be a reference due to https://github.com/rust-lang/rust/issues/63033
|
// `trace_collector` has to be a reference due to https://github.com/rust-lang/rust/issues/63033
|
||||||
trace_collector: Option<&Arc<dyn TraceCollector>>,
|
trace_collector: Option<&Arc<dyn TraceCollector>>,
|
||||||
|
@ -426,7 +426,7 @@ async fn create_kafka_topic(
|
||||||
kafka_connection: &str,
|
kafka_connection: &str,
|
||||||
database_name: &str,
|
database_name: &str,
|
||||||
n_sequencers: NonZeroU32,
|
n_sequencers: NonZeroU32,
|
||||||
cfg: &HashMap<String, String>,
|
cfg: &BTreeMap<String, String>,
|
||||||
) -> Result<(), WriteBufferError> {
|
) -> Result<(), WriteBufferError> {
|
||||||
let admin = admin_client(kafka_connection)?;
|
let admin = admin_client(kafka_connection)?;
|
||||||
|
|
||||||
|
@ -489,7 +489,7 @@ async fn maybe_auto_create_topics(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod test_utils {
|
pub mod test_utils {
|
||||||
use std::{collections::HashMap, time::Duration};
|
use std::{collections::BTreeMap, time::Duration};
|
||||||
|
|
||||||
use rdkafka::admin::{AdminOptions, AlterConfig, ResourceSpecifier};
|
use rdkafka::admin::{AdminOptions, AlterConfig, ResourceSpecifier};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -544,12 +544,12 @@ pub mod test_utils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create topic creation config that is ideal for testing and works with [`purge_kafka_topic`]
|
/// Create topic creation config that is ideal for testing and works with [`purge_kafka_topic`]
|
||||||
pub fn kafka_sequencer_options() -> HashMap<String, String> {
|
pub fn kafka_sequencer_options() -> BTreeMap<String, String> {
|
||||||
let mut cfg: HashMap<String, String> = Default::default();
|
BTreeMap::from([
|
||||||
cfg.insert("cleanup.policy".to_string(), "delete".to_string());
|
("cleanup.policy".to_string(), "delete".to_string()),
|
||||||
cfg.insert("retention.ms".to_string(), "-1".to_string());
|
("retention.ms".to_string(), "-1".to_string()),
|
||||||
cfg.insert("segment.ms".to_string(), "10".to_string());
|
("segment.ms".to_string(), "10".to_string()),
|
||||||
cfg
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Purge all records from given topic.
|
/// Purge all records from given topic.
|
||||||
|
|
Loading…
Reference in New Issue