use super::recoverable::RecoverableConnection;
use crate::{
error::{ErrorKind, EventHubsError, Result},
models::{EventHubPartitionProperties, EventHubProperties},
};
use azure_core_amqp::{
AmqpManagementApis, AmqpOrderedMap, AmqpSimpleValue, AmqpTimestamp, AmqpValue,
};
use std::{sync::Arc, time::SystemTime};
use tracing::warn;
pub(crate) struct ManagementInstance {
recoverable_connection: Arc<RecoverableConnection>,
}
const EVENTHUB_ENTITY_TYPE: &str = "com.microsoft:eventhub";
const PARTITION_ENTITY_TYPE: &str = "com.microsoft:partition";
const EVENTHUB_PROPERTY_PARTITION_COUNT: &str = "partition_count";
const EVENTHUB_PROPERTY_PARTITION_IDS: &str = "partition_ids";
const EVENTHUB_PROPERTY_NAME: &str = "name";
const EVENTHUB_PROPERTY_PARTITION: &str = "partition";
const EVENTHUB_PROPERTY_CREATED_AT: &str = "created_at";
const EVENTHUB_PARTITION_PROPERTIES_TYPE: &str = "type";
const EVENTHUB_PARTITION_PROPERTIES_LAST_ENQUEUED_SEQUENCE_NUMBER_EPOCH: &str =
"last_enqueued_sequence_number_epoch";
const EVENTHUB_PARTITION_PROPERTIES_BEGIN_SEQUENCE_NUMBER: &str = "begin_sequence_number";
const EVENTHUB_PARTITION_PROPERTIES_LAST_ENQUEUED_SEQUENCE_NUMBER: &str =
"last_enqueued_sequence_number";
const EVENTHUB_PARTITION_PROPERTIES_LAST_ENQUEUED_OFFSET: &str = "last_enqueued_offset";
const EVENTHUB_PARTITION_PROPERTIES_LAST_ENQUEUED_TIME_UTC: &str = "last_enqueued_time_utc";
const EVENTHUB_PARTITION_PROPERTIES_IS_EMPTY: &str = "is_partition_empty";
impl ManagementInstance {
pub fn new(recoverable_connection: Arc<RecoverableConnection>) -> Arc<Self> {
Arc::new(Self {
recoverable_connection,
})
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(
connection_id = %self.recoverable_connection.get_connection_id(),
eventhub = %eventhub,
),
err,
)]
pub async fn get_eventhub_properties(&self, eventhub: &str) -> Result<EventHubProperties> {
let mut application_properties: AmqpOrderedMap<String, AmqpSimpleValue> =
AmqpOrderedMap::new();
application_properties.insert(EVENTHUB_PROPERTY_NAME.to_string(), eventhub.into());
let response = self
.recoverable_connection
.get_management_client()
.call(EVENTHUB_ENTITY_TYPE.to_string(), application_properties)
.await?;
if !response.contains_key(EVENTHUB_PROPERTY_PARTITION_COUNT) {
warn!(
missing_key = EVENTHUB_PROPERTY_PARTITION_COUNT,
"Management response for event hub properties is missing a required key"
);
return Err(EventHubsError::from(ErrorKind::InvalidManagementResponse));
}
let name: String = response
.get(EVENTHUB_PROPERTY_NAME)
.ok_or_else(|| {
warn!(
missing_key = EVENTHUB_PROPERTY_NAME,
"Management response for event hub properties is missing a required key"
);
EventHubsError::from(ErrorKind::InvalidManagementResponse)
})?
.into();
let created_at: Option<SystemTime> = Into::<AmqpTimestamp>::into(
response
.get(EVENTHUB_PROPERTY_CREATED_AT)
.ok_or_else(|| {
warn!(
missing_key = EVENTHUB_PROPERTY_CREATED_AT,
"Management response for event hub properties is missing a required key"
);
EventHubsError::from(ErrorKind::InvalidManagementResponse)
})?
.clone(),
)
.0;
let partition_ids = response
.get(EVENTHUB_PROPERTY_PARTITION_IDS)
.ok_or_else(|| {
warn!(
missing_key = EVENTHUB_PROPERTY_PARTITION_IDS,
"Management response for event hub properties is missing a required key"
);
EventHubsError::from(ErrorKind::InvalidManagementResponse)
})?;
let partition_ids = match partition_ids {
AmqpValue::Array(partition_ids) => partition_ids
.iter()
.map(|id| match id {
AmqpValue::String(id) => Ok(id.clone()),
_ => {
warn!(
key = EVENTHUB_PROPERTY_PARTITION_IDS,
"Management response partition id entry is not a string"
);
Err(EventHubsError::from(ErrorKind::InvalidManagementResponse))
}
})
.collect::<Result<Vec<String>>>()?,
_ => {
warn!(
key = EVENTHUB_PROPERTY_PARTITION_IDS,
"Management response partition ids value is not an array"
);
return Err(EventHubsError::from(ErrorKind::InvalidManagementResponse));
}
};
Ok(EventHubProperties {
name,
created_on: created_at,
partition_ids,
})
}
#[tracing::instrument(
level = "debug",
skip_all,
fields(
connection_id = %self.recoverable_connection.get_connection_id(),
eventhub = %eventhub,
partition_id = %partition_id,
),
err,
)]
pub async fn get_eventhub_partition_properties(
&self,
eventhub: &str,
partition_id: &str,
) -> Result<EventHubPartitionProperties> {
let mut application_properties: AmqpOrderedMap<String, AmqpSimpleValue> =
AmqpOrderedMap::new();
application_properties.insert(EVENTHUB_PROPERTY_NAME.to_string(), eventhub.into());
application_properties.insert(EVENTHUB_PROPERTY_PARTITION.to_string(), partition_id.into());
let response = self
.recoverable_connection
.get_management_client()
.call(PARTITION_ENTITY_TYPE.to_string(), application_properties)
.await?;
if !response.contains_key(EVENTHUB_PARTITION_PROPERTIES_TYPE) {
warn!(
missing_key = EVENTHUB_PARTITION_PROPERTIES_TYPE,
"Management response for partition properties is missing a required key"
);
return Err(EventHubsError::from(ErrorKind::InvalidManagementResponse));
}
if !response.contains_key(EVENTHUB_PARTITION_PROPERTIES_LAST_ENQUEUED_SEQUENCE_NUMBER_EPOCH)
{
warn!(
missing_key = EVENTHUB_PARTITION_PROPERTIES_LAST_ENQUEUED_SEQUENCE_NUMBER_EPOCH,
"Management response for partition properties is missing a required key"
);
return Err(EventHubsError::from(ErrorKind::InvalidManagementResponse));
}
let missing_key = |key: &'static str| {
move || {
warn!(
missing_key = key,
"Management response for partition properties is missing a required key"
);
EventHubsError::from(ErrorKind::InvalidManagementResponse)
}
};
Ok(EventHubPartitionProperties {
beginning_sequence_number: response
.get(EVENTHUB_PARTITION_PROPERTIES_BEGIN_SEQUENCE_NUMBER)
.ok_or_else(missing_key(
EVENTHUB_PARTITION_PROPERTIES_BEGIN_SEQUENCE_NUMBER,
))?
.into(),
id: response
.get(EVENTHUB_PROPERTY_PARTITION)
.ok_or_else(missing_key(EVENTHUB_PROPERTY_PARTITION))?
.into(),
eventhub: response
.get(EVENTHUB_PROPERTY_NAME)
.ok_or_else(missing_key(EVENTHUB_PROPERTY_NAME))?
.into(),
last_enqueued_sequence_number: response
.get(EVENTHUB_PARTITION_PROPERTIES_LAST_ENQUEUED_SEQUENCE_NUMBER)
.ok_or_else(missing_key(
EVENTHUB_PARTITION_PROPERTIES_LAST_ENQUEUED_SEQUENCE_NUMBER,
))?
.into(),
last_enqueued_offset: response
.get(EVENTHUB_PARTITION_PROPERTIES_LAST_ENQUEUED_OFFSET)
.ok_or_else(missing_key(
EVENTHUB_PARTITION_PROPERTIES_LAST_ENQUEUED_OFFSET,
))?
.into(),
last_enqueued_time_utc: Into::<AmqpTimestamp>::into(
response
.get(EVENTHUB_PARTITION_PROPERTIES_LAST_ENQUEUED_TIME_UTC)
.ok_or_else(missing_key(
EVENTHUB_PARTITION_PROPERTIES_LAST_ENQUEUED_TIME_UTC,
))?,
)
.0,
is_empty: response
.get(EVENTHUB_PARTITION_PROPERTIES_IS_EMPTY)
.ok_or_else(missing_key(EVENTHUB_PARTITION_PROPERTIES_IS_EMPTY))?
.into(),
})
}
}