blob: d673e9746705b8f1359fe464bb918fe8307b3298 [file]
/* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use crate::binary::handlers::messages::poll_messages_handler::IggyPollMetadata;
use crate::streaming::segments::{IggyIndexesMut, IggyMessagesBatchMut, IggyMessagesBatchSet};
use crate::streaming::session::Session;
use crate::streaming::systems::system::System;
use crate::streaming::systems::COMPONENT;
use crate::streaming::utils::PooledBuffer;
use error_set::ErrContext;
use iggy::confirmation::Confirmation;
use iggy::consumer::Consumer;
use iggy::prelude::*;
use iggy::utils::crypto::EncryptorKind;
use iggy::{error::IggyError, identifier::Identifier};
use tracing::{error, trace};
impl System {
pub async fn poll_messages(
&self,
session: &Session,
consumer: &Consumer,
stream_id: &Identifier,
topic_id: &Identifier,
partition_id: Option<u32>,
args: PollingArgs,
) -> Result<(IggyPollMetadata, IggyMessagesBatchSet), IggyError> {
self.ensure_authenticated(session)?;
if args.count == 0 {
return Err(IggyError::InvalidMessagesCount);
}
let topic = self.find_topic(session, stream_id, topic_id).with_error_context(|error| format!("{COMPONENT} (error: {error}) - topic not found for stream ID: {stream_id}, topic_id: {topic_id}"))?;
self.permissioner
.poll_messages(session.get_user_id(), topic.stream_id, topic.topic_id)
.with_error_context(|error| format!(
"{COMPONENT} (error: {error}) - permission denied to poll messages for user {} on stream ID: {}, topic ID: {}",
session.get_user_id(),
topic.stream_id,
topic.topic_id
))?;
if !topic.has_partitions() {
return Err(IggyError::NoPartitions(topic.topic_id, topic.stream_id));
}
// There might be no partition assigned, if it's the consumer group member without any partitions.
let Some((polling_consumer, partition_id)) = topic
.resolve_consumer_with_partition_id(consumer, session.client_id, partition_id, true)
.await
.with_error_context(|error| format!("{COMPONENT} (error: {error}) - failed to resolve consumer with partition id, consumer: {consumer}, client ID: {}, partition ID: {:?}", session.client_id, partition_id))? else {
return Ok((IggyPollMetadata::new(0, 0), IggyMessagesBatchSet::empty()));
};
let (metadata, batch_set) = topic
.get_messages(polling_consumer, partition_id, args.strategy, args.count)
.await?;
if args.auto_commit && !batch_set.is_empty() {
let offset = batch_set
.last_offset()
.expect("Batch set should have at least one batch");
trace!("Last offset: {} will be automatically stored for {}, stream: {}, topic: {}, partition: {}", offset, consumer, stream_id, topic_id, partition_id);
topic
.store_consumer_offset_internal(polling_consumer, offset, partition_id)
.await
.with_error_context(|error| format!("{COMPONENT} (error: {error}) - failed to store consumer offset internal, polling consumer: {}, offset: {}, partition ID: {}", polling_consumer, offset, partition_id)) ?;
}
let batch_set = if let Some(encryptor) = &self.encryptor {
self.decrypt_messages(batch_set, encryptor.as_ref()).await?
} else {
batch_set
};
Ok((metadata, batch_set))
}
pub async fn append_messages(
&self,
session: &Session,
stream_id: &Identifier,
topic_id: &Identifier,
partitioning: &Partitioning,
messages: IggyMessagesBatchMut,
confirmation: Option<Confirmation>,
) -> Result<(), IggyError> {
self.ensure_authenticated(session)?;
let topic = self.find_topic(session, stream_id, topic_id).with_error_context(|error| format!("{COMPONENT} (error: {error}) - topic not found for stream_id: {stream_id}, topic_id: {topic_id}"))?;
self.permissioner.append_messages(
session.get_user_id(),
topic.stream_id,
topic.topic_id
).with_error_context(|error| format!(
"{COMPONENT} (error: {error}) - permission denied to append messages for user {} on stream ID: {}, topic ID: {}",
session.get_user_id(),
topic.stream_id,
topic.topic_id
))?;
let messages_count = messages.count();
// Encrypt messages if encryptor is configured
let messages = if let Some(encryptor) = &self.encryptor {
self.encrypt_messages(messages, encryptor.as_ref())?
} else {
messages
};
topic
.append_messages(partitioning, messages, confirmation)
.await?;
self.metrics.increment_messages(messages_count as u64);
Ok(())
}
pub async fn flush_unsaved_buffer(
&self,
session: &Session,
stream_id: Identifier,
topic_id: Identifier,
partition_id: u32,
fsync: bool,
) -> Result<(), IggyError> {
self.ensure_authenticated(session)?;
let topic = self.find_topic(session, &stream_id, &topic_id).with_error_context(|error| format!("{COMPONENT} (error: {error}) - topic not found for stream ID: {stream_id}, topic_id: {topic_id}"))?;
self.permissioner.append_messages(
session.get_user_id(),
topic.stream_id,
topic.topic_id
).with_error_context(|error| format!(
"{COMPONENT} (error: {error}) - permission denied to append messages for user {} on stream ID: {}, topic ID: {}",
session.get_user_id(),
topic.stream_id,
topic.topic_id
))?;
topic.flush_unsaved_buffer(partition_id, fsync).await?;
Ok(())
}
async fn decrypt_messages(
&self,
batches: IggyMessagesBatchSet,
encryptor: &EncryptorKind,
) -> Result<IggyMessagesBatchSet, IggyError> {
let mut decrypted_batches = Vec::with_capacity(batches.containers_count());
for batch in batches.iter() {
let count = batch.count();
let mut indexes = IggyIndexesMut::with_capacity(batch.count() as usize, 0);
let mut decrypted_messages = PooledBuffer::with_capacity(batch.size() as usize);
let mut position = 0;
for message in batch.iter() {
let payload = encryptor.decrypt(message.payload());
match payload {
Ok(payload) => {
message.header().write_to_buffer(&mut decrypted_messages);
decrypted_messages.extend_from_slice(&payload);
if let Some(user_headers) = message.user_headers() {
decrypted_messages.extend_from_slice(user_headers);
}
indexes.insert(0, position as u32, 0);
position += message.size();
}
Err(error) => {
error!("Cannot decrypt the message. Error: {}", error);
continue;
}
}
}
let decrypted_batch =
IggyMessagesBatchMut::from_indexes_and_messages(count, indexes, decrypted_messages);
decrypted_batches.push(decrypted_batch);
}
Ok(IggyMessagesBatchSet::from_vec(decrypted_batches))
}
fn encrypt_messages(
&self,
batch: IggyMessagesBatchMut,
encryptor: &EncryptorKind,
) -> Result<IggyMessagesBatchMut, IggyError> {
let mut encrypted_messages = PooledBuffer::with_capacity(batch.size() as usize * 2);
let count = batch.count();
let mut indexes = IggyIndexesMut::with_capacity(batch.count() as usize, 0);
let mut position = 0;
for message in batch.iter() {
let header = message.header();
let payload_length = header.payload_length();
let user_headers_length = header.user_headers_length();
let payload_bytes = message.payload();
let user_headers_bytes = message.user_headers();
let encrypted_payload = encryptor.encrypt(payload_bytes);
match encrypted_payload {
Ok(encrypted_payload) => {
encrypted_messages.extend_from_slice(&header.to_bytes());
encrypted_messages.extend_from_slice(&encrypted_payload);
if let Some(user_headers_bytes) = user_headers_bytes {
encrypted_messages.extend_from_slice(user_headers_bytes);
}
indexes.insert(0, position as u32, 0);
position += IGGY_MESSAGE_HEADER_SIZE + payload_length + user_headers_length;
}
Err(error) => {
error!("Cannot encrypt the message. Error: {}", error);
continue;
}
}
}
Ok(IggyMessagesBatchMut::from_indexes_and_messages(
count,
indexes,
encrypted_messages,
))
}
}
#[derive(Debug)]
pub struct PollingArgs {
pub strategy: PollingStrategy,
pub count: u32,
pub auto_commit: bool,
}
impl PollingArgs {
pub fn new(strategy: PollingStrategy, count: u32, auto_commit: bool) -> Self {
Self {
strategy,
count,
auto_commit,
}
}
}