blob: 7b4e4566ee6721e0fe5a31e24c6193481a5e03cf [file]
use crate::configs::system::SystemConfig;
use crate::streaming::partitions::partition::Partition;
use crate::streaming::storage::SystemStorage;
use crate::streaming::topics::consumer_group::ConsumerGroup;
use core::fmt;
use iggy::error::IggyError;
use iggy::locking::IggySharedMut;
use iggy::utils::byte_size::IggyByteSize;
use iggy::utils::timestamp::IggyTimestamp;
use std::collections::HashMap;
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug)]
pub struct Topic {
pub stream_id: u32,
pub topic_id: u32,
pub name: String,
pub path: String,
pub partitions_path: String,
pub(crate) size_bytes: Arc<AtomicU64>,
pub(crate) size_of_parent_stream: Arc<AtomicU64>,
pub(crate) messages_count_of_parent_stream: Arc<AtomicU64>,
pub(crate) messages_count: Arc<AtomicU64>,
pub(crate) config: Arc<SystemConfig>,
pub(crate) partitions: HashMap<u32, IggySharedMut<Partition>>,
pub(crate) storage: Arc<SystemStorage>,
pub(crate) consumer_groups: HashMap<u32, RwLock<ConsumerGroup>>,
pub(crate) consumer_groups_ids: HashMap<String, u32>,
pub(crate) current_partition_id: AtomicU32,
pub message_expiry: Option<u32>,
pub max_topic_size: Option<IggyByteSize>,
pub replication_factor: u8,
pub created_at: u64,
}
impl Topic {
pub fn empty(
stream_id: u32,
topic_id: u32,
config: Arc<SystemConfig>,
storage: Arc<SystemStorage>,
) -> Topic {
Topic::create(
stream_id,
topic_id,
"",
0,
config,
storage,
Arc::new(AtomicU64::new(0)),
Arc::new(AtomicU64::new(0)),
None,
None,
1,
)
.unwrap()
}
#[allow(clippy::too_many_arguments)]
pub fn create(
stream_id: u32,
topic_id: u32,
name: &str,
partitions_count: u32,
config: Arc<SystemConfig>,
storage: Arc<SystemStorage>,
size_of_parent_stream: Arc<AtomicU64>,
messages_count_of_parent_stream: Arc<AtomicU64>,
message_expiry: Option<u32>,
max_topic_size: Option<IggyByteSize>,
replication_factor: u8,
) -> Result<Topic, IggyError> {
let path = config.get_topic_path(stream_id, topic_id);
let partitions_path = config.get_partitions_path(stream_id, topic_id);
let mut topic = Topic {
stream_id,
topic_id,
name: name.to_string(),
partitions: HashMap::new(),
path,
partitions_path,
storage,
size_bytes: Arc::new(AtomicU64::new(0)),
size_of_parent_stream,
messages_count_of_parent_stream,
messages_count: Arc::new(AtomicU64::new(0)),
consumer_groups: HashMap::new(),
consumer_groups_ids: HashMap::new(),
current_partition_id: AtomicU32::new(1),
message_expiry: match message_expiry {
Some(expiry) => match expiry {
0 => None,
_ => Some(expiry),
},
None => match config.retention_policy.message_expiry.as_secs() {
0 => None,
expiry => Some(expiry),
},
},
max_topic_size,
replication_factor,
config,
created_at: IggyTimestamp::now().to_micros(),
};
topic.add_partitions(partitions_count)?;
Ok(topic)
}
pub fn get_size(&self) -> IggyByteSize {
IggyByteSize::from(self.size_bytes.load(Ordering::SeqCst))
}
pub fn get_partitions(&self) -> Vec<IggySharedMut<Partition>> {
self.partitions.values().cloned().collect()
}
pub fn get_partition(&self, partition_id: u32) -> Result<IggySharedMut<Partition>, IggyError> {
match self.partitions.get(&partition_id) {
Some(partition_arc) => Ok(partition_arc.clone()),
None => Err(IggyError::PartitionNotFound(
partition_id,
self.topic_id,
self.stream_id,
)),
}
}
}
impl fmt::Display for Topic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
let max_topic_size = match self.max_topic_size {
Some(size) => size.as_human_string_with_zero_as_unlimited(),
None => "unlimited".to_owned(),
};
write!(f, "ID: {}, ", self.topic_id)?;
write!(f, "stream ID: {}, ", self.stream_id)?;
write!(f, "name: {}, ", self.name)?;
write!(f, "path: {}, ", self.path)?;
write!(f, "partitions count: {:?}, ", self.partitions.len())?;
write!(f, "message expiry (s): {:?}, ", self.message_expiry)?;
write!(f, "max topic size (B): {:?}, ", max_topic_size)?;
write!(f, "replication factor: {}, ", self.replication_factor)
}
}
#[cfg(test)]
mod tests {
use iggy::locking::IggySharedMutFn;
use std::str::FromStr;
use super::*;
use crate::streaming::storage::tests::get_test_system_storage;
#[tokio::test]
async fn should_be_created_given_valid_parameters() {
let storage = Arc::new(get_test_system_storage());
let stream_id = 1;
let topic_id = 2;
let name = "test";
let partitions_count = 3;
let message_expiry = 10;
let max_topic_size = IggyByteSize::from_str("2 GB").unwrap();
let replication_factor = 1;
let config = Arc::new(SystemConfig::default());
let path = config.get_topic_path(stream_id, topic_id);
let size_of_parent_stream = Arc::new(AtomicU64::new(0));
let messages_count_of_parent_stream = Arc::new(AtomicU64::new(0));
let topic = Topic::create(
stream_id,
topic_id,
name,
partitions_count,
config,
storage,
messages_count_of_parent_stream,
size_of_parent_stream,
Some(message_expiry),
Some(max_topic_size),
replication_factor,
)
.unwrap();
assert_eq!(topic.stream_id, stream_id);
assert_eq!(topic.topic_id, topic_id);
assert_eq!(topic.path, path);
assert_eq!(topic.name, name);
assert_eq!(topic.partitions.len(), partitions_count as usize);
assert_eq!(topic.message_expiry, Some(message_expiry));
for (id, partition) in topic.partitions {
let partition = partition.read().await;
assert_eq!(partition.stream_id, stream_id);
assert_eq!(partition.topic_id, topic.topic_id);
assert_eq!(partition.partition_id, id);
assert_eq!(partition.segments.len(), 1);
}
}
}