diff --git a/services/backup/src/constants.rs b/services/backup/src/constants.rs --- a/services/backup/src/constants.rs +++ b/services/backup/src/constants.rs @@ -2,6 +2,7 @@ pub const AWS_REGION: &str = "us-east-2"; pub const MPSC_CHANNEL_BUFFER_CAPACITY: usize = 1; +pub const ID_SEPARATOR: &str = ":"; // Configuration defaults diff --git a/services/backup/src/database.rs b/services/backup/src/database.rs --- a/services/backup/src/database.rs +++ b/services/backup/src/database.rs @@ -29,6 +29,27 @@ pub attachment_holders: String, } +impl BackupItem { + pub fn new( + user_id: String, + backup_id: String, + compaction_holder: String, + ) -> Self { + BackupItem { + user_id, + backup_id, + compaction_holder, + created: chrono::Utc::now(), + // TODO: Recovery data is mocked with random string + recovery_data: crate::utils::generate_random_string( + 20, + &mut rand::thread_rng(), + ), + attachment_holders: String::new(), + } + } +} + #[derive(Clone, Debug)] pub struct LogItem { pub backup_id: String, diff --git a/services/backup/src/main.rs b/services/backup/src/main.rs --- a/services/backup/src/main.rs +++ b/services/backup/src/main.rs @@ -11,6 +11,7 @@ pub mod constants; pub mod database; pub mod service; +pub mod utils; // re-export this to be available as crate::CONFIG pub use config::CONFIG; diff --git a/services/backup/src/service/handlers/create_backup.rs b/services/backup/src/service/handlers/create_backup.rs --- a/services/backup/src/service/handlers/create_backup.rs +++ b/services/backup/src/service/handlers/create_backup.rs @@ -85,3 +85,12 @@ unimplemented!() } } + +/// Generates ID for a new backup +fn generate_backup_id(device_id: &str) -> String { + format!( + "{device_id}_{timestamp}", + device_id = device_id, + timestamp = chrono::Utc::now().timestamp_millis() + ) +} diff --git a/services/backup/src/service/mod.rs b/services/backup/src/service/mod.rs --- a/services/backup/src/service/mod.rs +++ b/services/backup/src/service/mod.rs @@ -1,12 +1,14 @@ +use aws_sdk_dynamodb::Error as DynamoDBError; use proto::backup_service_server::BackupService; use std::pin::Pin; use tokio::sync::mpsc; use tokio_stream::{wrappers::ReceiverStream, Stream, StreamExt}; use tonic::{Request, Response, Status}; -use tracing::{debug, error, info, instrument, trace, Instrument}; +use tracing::{debug, error, info, instrument, trace, warn, Instrument}; use crate::{ - constants::MPSC_CHANNEL_BUFFER_CAPACITY, database::DatabaseClient, + constants::MPSC_CHANNEL_BUFFER_CAPACITY, + database::{DatabaseClient, Error as DBError}, }; mod proto { @@ -19,6 +21,7 @@ pub(super) mod create_backup; // re-exports for convenient usage in handlers + pub(self) use super::handle_db_error; pub(self) use super::proto; } use self::handlers::create_backup::CreateBackupHandler; @@ -144,3 +147,21 @@ Err(Status::unimplemented("unimplemented")) } } + +/// A helper converting our Database errors into gRPC responses +fn handle_db_error(db_error: DBError) -> Status { + match db_error { + DBError::AwsSdk(DynamoDBError::InternalServerError(_)) + | DBError::AwsSdk(DynamoDBError::ProvisionedThroughputExceededException( + _, + )) + | DBError::AwsSdk(DynamoDBError::RequestLimitExceeded(_)) => { + warn!("AWS transient error occurred"); + Status::unavailable("please retry") + } + e => { + error!("Encountered an unexpected error: {}", e); + Status::failed_precondition("unexpected error") + } + } +} diff --git a/services/backup/src/utils.rs b/services/backup/src/utils.rs new file mode 100644 --- /dev/null +++ b/services/backup/src/utils.rs @@ -0,0 +1,28 @@ +use rand::{distributions::DistString, CryptoRng, Rng}; +use uuid::Uuid; + +use crate::constants::ID_SEPARATOR; + +/// Generates a blob `holder` string used to store backup/log data +/// in Blob service +pub fn generate_blob_holder( + blob_hash: &str, + backup_id: &str, + resource_id: Option<&str>, +) -> String { + format!( + "{backup_id}{sep}{resource_id}{sep}{blob_hash}{sep}{uuid}", + backup_id = backup_id, + resource_id = resource_id.unwrap_or_default(), + blob_hash = blob_hash, + sep = ID_SEPARATOR, + uuid = Uuid::new_v4() + ) +} + +pub fn generate_random_string( + length: usize, + rng: &mut (impl Rng + CryptoRng), +) -> String { + rand::distributions::Alphanumeric.sample_string(rng, length) +}