2023-08-03 18:22:39 -04:00
|
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
|
2023-10-30 12:54:59 -04:00
|
|
|
use crate::metrics::Metrics;
|
2023-08-03 18:22:39 -04:00
|
|
|
use crate::{lox_context, DbConfig};
|
2023-08-22 15:19:33 -04:00
|
|
|
use chrono::{naive::Days, DateTime, Local, NaiveDateTime, Utc};
|
2023-08-03 18:22:39 -04:00
|
|
|
use lox_library::{BridgeAuth, BridgeDb};
|
|
|
|
use sled::IVec;
|
2024-03-15 02:30:57 -04:00
|
|
|
use std::collections::HashMap;
|
2023-12-18 14:17:13 -05:00
|
|
|
use thiserror::Error;
|
2024-03-15 02:30:57 -04:00
|
|
|
use troll_patrol::bridge_info::BridgeInfo as TPBridgeInfo;
|
2023-12-18 14:17:13 -05:00
|
|
|
|
|
|
|
#[derive(Error, Debug)]
|
|
|
|
pub enum LoxDBError {
|
|
|
|
//failed to get last db entry
|
|
|
|
#[error("Failed to read last database entry")]
|
|
|
|
ReadFailure(#[from] sled::Error),
|
|
|
|
|
|
|
|
//no last db entries
|
|
|
|
#[error("No database entries stored")]
|
|
|
|
DatabaseEmpty,
|
|
|
|
}
|
2023-08-03 18:22:39 -04:00
|
|
|
|
2023-11-07 00:10:38 -05:00
|
|
|
// Database of Lox Distributor State
|
2023-09-07 16:22:06 -04:00
|
|
|
pub struct DB {
|
|
|
|
db: sled::Db,
|
2023-09-07 11:00:34 -04:00
|
|
|
}
|
2023-08-03 18:22:39 -04:00
|
|
|
|
2023-09-07 16:22:06 -04:00
|
|
|
impl DB {
|
2023-11-07 00:10:38 -05:00
|
|
|
// Writes the Lox context to the lox database with "context_%Y-%m-%d_%H:%M:%S" as the
|
|
|
|
// database key
|
2023-09-07 16:22:06 -04:00
|
|
|
pub fn write_context(&mut self, context: lox_context::LoxServerContext) {
|
|
|
|
let date = Local::now().format("context_%Y-%m-%d_%H:%M:%S").to_string();
|
2023-11-02 11:01:55 -04:00
|
|
|
/* Uncomment to generate test file for this function after making changes to lox library
|
|
|
|
let file = OpenOptions::new()
|
|
|
|
.create(true)
|
|
|
|
.write(true)
|
|
|
|
.truncate(true)
|
|
|
|
.open("db_test_file.json").unwrap();
|
|
|
|
serde_json::to_writer(&file, &context).unwrap();
|
|
|
|
*/
|
2023-09-07 16:22:06 -04:00
|
|
|
let json_result = serde_json::to_vec(&context).unwrap();
|
|
|
|
println!("Writing context to the db with key: {:?}", date);
|
|
|
|
let _new_ivec = self.db.insert(
|
|
|
|
IVec::from(date.as_bytes().to_vec()),
|
|
|
|
IVec::from(json_result.clone()),
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
self.db
|
|
|
|
.get(IVec::from(date.as_bytes().to_vec()))
|
|
|
|
.unwrap()
|
|
|
|
.unwrap(),
|
|
|
|
IVec::from(json_result)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-11-07 00:10:38 -05:00
|
|
|
// If roll_back_date is empty, opens the most recent entry in the lox database or if none exists, creates a
|
|
|
|
// new database. If roll_back_date is not empty, use the specified date to roll back to a previous lox-context
|
2023-11-09 16:09:05 -05:00
|
|
|
// either exactly the entry at the roll_back_date or within 24 hours from the roll_back_date.
|
2023-09-07 16:22:06 -04:00
|
|
|
pub fn open_new_or_existing_db(
|
|
|
|
db_config: DbConfig,
|
|
|
|
roll_back_date: Option<String>,
|
2023-10-25 13:58:24 -04:00
|
|
|
metrics: Metrics,
|
2023-09-07 16:22:06 -04:00
|
|
|
) -> Result<(DB, lox_context::LoxServerContext), sled::Error> {
|
2023-10-30 12:54:59 -04:00
|
|
|
let mut context: lox_context::LoxServerContext;
|
2023-09-07 16:22:06 -04:00
|
|
|
let (lox_db, context) = match sled::open(db_config.db_path) {
|
|
|
|
Ok(lox_db) => {
|
|
|
|
// Check if the lox_db already exists
|
2023-11-20 15:28:25 -05:00
|
|
|
if lox_db.was_recovered() && !lox_db.is_empty() {
|
2023-12-18 14:17:13 -05:00
|
|
|
context = match read_lox_context_from_db(lox_db.clone(), roll_back_date) {
|
|
|
|
Ok(ctx) => ctx,
|
2023-12-18 22:56:26 -05:00
|
|
|
Err(e) => panic!("Unable to read lox database {:?}", e),
|
2023-12-18 14:17:13 -05:00
|
|
|
};
|
2023-10-25 13:58:24 -04:00
|
|
|
context.metrics = metrics;
|
2023-09-07 16:22:06 -04:00
|
|
|
//Otherwise, create a new Lox context
|
|
|
|
} else {
|
|
|
|
let new_db = BridgeDb::new();
|
|
|
|
let new_ba = BridgeAuth::new(new_db.pubkey);
|
|
|
|
context = lox_context::LoxServerContext {
|
|
|
|
db: Arc::new(Mutex::new(new_db)),
|
|
|
|
ba: Arc::new(Mutex::new(new_ba)),
|
|
|
|
extra_bridges: Arc::new(Mutex::new(Vec::new())),
|
|
|
|
to_be_replaced_bridges: Arc::new(Mutex::new(Vec::new())),
|
2024-03-15 02:30:57 -04:00
|
|
|
tp_bridge_infos: Arc::new(Mutex::new(
|
|
|
|
HashMap::<[u8; 20], TPBridgeInfo>::new(),
|
|
|
|
)),
|
2023-10-25 13:58:24 -04:00
|
|
|
metrics,
|
2023-09-07 16:22:06 -04:00
|
|
|
};
|
|
|
|
}
|
|
|
|
(DB { db: lox_db }, context)
|
2023-08-03 18:22:39 -04:00
|
|
|
}
|
2023-09-07 16:22:06 -04:00
|
|
|
Err(e) => {
|
|
|
|
panic!("Unable to read or create lox database! {:?}", e);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Ok((lox_db, context))
|
|
|
|
}
|
2023-08-03 18:22:39 -04:00
|
|
|
}
|
|
|
|
|
2023-11-07 00:10:38 -05:00
|
|
|
// Logic for finding the correct context to open from the database
|
2023-08-21 14:17:04 -04:00
|
|
|
fn read_lox_context_from_db(
|
|
|
|
lox_db: sled::Db,
|
|
|
|
roll_back_date: Option<String>,
|
2023-12-18 14:17:13 -05:00
|
|
|
) -> Result<lox_context::LoxServerContext, LoxDBError> {
|
2023-08-21 14:17:04 -04:00
|
|
|
let context: lox_context::LoxServerContext;
|
|
|
|
// Check if there is a roll back date and try to choose the appropriate context
|
|
|
|
// to rollback to, otherwise, take the last saved context
|
|
|
|
match roll_back_date {
|
|
|
|
// If roll back date has been specified, either the exact date or range should be set
|
|
|
|
Some(roll_back_date) => {
|
|
|
|
// If the date is specified and it's in the database, use that to populate the context
|
|
|
|
if lox_db.contains_key(roll_back_date.clone()).unwrap() {
|
|
|
|
// Find date/time in db and use the context from that date.
|
2023-08-22 15:19:33 -04:00
|
|
|
let ivec_context = lox_db
|
|
|
|
.get(IVec::from(roll_back_date.as_bytes().to_vec()))
|
|
|
|
.unwrap()
|
|
|
|
.unwrap();
|
2023-08-21 14:17:04 -04:00
|
|
|
context = serde_json::from_slice(&ivec_context).unwrap();
|
|
|
|
println!("Successfully used exact key {:?}", roll_back_date);
|
|
|
|
} else {
|
|
|
|
// If the exact date is not found, compute the start of the range as 24 hours prior to the specified date
|
|
|
|
match compute_startdate_string(roll_back_date.clone()){
|
2023-08-22 15:19:33 -04:00
|
|
|
Some(start_date) => {
|
|
|
|
let start_date = start_date.format("context_%Y-%m-%d_%H:%M:%S").to_string();
|
|
|
|
let r: sled::Iter = lox_db.range(IVec::from(start_date.as_bytes().to_vec())..IVec::from(roll_back_date.as_bytes().to_vec()), );
|
|
|
|
match r.last(){
|
|
|
|
Some(entry) => {
|
|
|
|
let ivec_context = entry.unwrap();
|
|
|
|
let key: String = String::from_utf8(ivec_context.0.to_vec()).unwrap();
|
|
|
|
println!("Successfully used range and key {:?}", key);
|
|
|
|
context = serde_json::from_slice(&ivec_context.1).unwrap();
|
|
|
|
},
|
|
|
|
//If no entries are found within the 24 hours prior to the specified date, Exit
|
|
|
|
None => panic!("UNEXPECTED DATE: No entries found in the 24 hours prior to the input roll_back_date"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => panic!("UNEXPECTED DATE: Tried to use input date to calculate 24 hour range but failed. Exiting."),
|
|
|
|
}
|
2023-08-21 14:17:04 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Use the last entry to populate the Lox context if no rollback date is set (which should be most common)
|
2023-12-18 22:56:26 -05:00
|
|
|
None => context = use_last_context(lox_db)?,
|
2023-08-04 17:41:22 -04:00
|
|
|
}
|
2023-12-18 14:17:13 -05:00
|
|
|
Ok(context)
|
2023-08-21 14:17:04 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
fn compute_startdate_string(date_range_end: String) -> Option<DateTime<Utc>> {
|
|
|
|
let parsed_end =
|
2023-08-22 15:19:33 -04:00
|
|
|
NaiveDateTime::parse_from_str(&date_range_end, "context_%Y-%m-%d_%H:%M:%S").unwrap();
|
2023-09-07 16:22:06 -04:00
|
|
|
let dt = DateTime::<Utc>::from_naive_utc_and_offset(parsed_end, Utc);
|
2023-08-22 15:19:33 -04:00
|
|
|
dt.with_timezone(&Utc).checked_sub_days(Days::new(1))
|
2023-08-04 17:41:22 -04:00
|
|
|
}
|
|
|
|
|
2023-11-07 00:10:38 -05:00
|
|
|
// Use the last context that was entered into the database
|
2023-12-18 14:17:13 -05:00
|
|
|
fn use_last_context(lox_db: sled::Db) -> Result<lox_context::LoxServerContext, LoxDBError> {
|
2023-12-18 22:56:26 -05:00
|
|
|
match lox_db.last()? {
|
|
|
|
Some(ivec_context) => {
|
|
|
|
let ivec_date: String = String::from_utf8(ivec_context.0.to_vec()).unwrap();
|
|
|
|
println!("Using last context with date: {:?}", ivec_date);
|
|
|
|
Ok(serde_json::from_slice(&ivec_context.1).unwrap())
|
2023-12-18 14:17:13 -05:00
|
|
|
}
|
2023-12-18 22:56:26 -05:00
|
|
|
None => Err(LoxDBError::DatabaseEmpty),
|
|
|
|
}
|
2023-08-03 18:22:39 -04:00
|
|
|
}
|
2023-09-07 11:12:40 -04:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2023-11-02 11:01:55 -04:00
|
|
|
use std::env;
|
|
|
|
use std::fs;
|
|
|
|
|
2023-09-07 11:12:40 -04:00
|
|
|
use super::lox_context::LoxServerContext;
|
2023-10-30 12:54:59 -04:00
|
|
|
use super::DbConfig;
|
2023-10-25 13:58:24 -04:00
|
|
|
use super::Metrics;
|
|
|
|
use super::DB;
|
2023-09-07 11:12:40 -04:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_write_context() {
|
2024-03-15 02:30:57 -04:00
|
|
|
// TODO: Fix db_test_file.json
|
2023-11-02 11:01:55 -04:00
|
|
|
env::set_var("TEST_FILE_PATH", "db_test_file.json");
|
2023-10-25 13:58:24 -04:00
|
|
|
let (mut lox_db, _context) =
|
|
|
|
DB::open_new_or_existing_db(DbConfig::default(), None, Metrics::default()).unwrap();
|
2023-09-07 11:12:40 -04:00
|
|
|
assert!(
|
|
|
|
lox_db.db.is_empty(),
|
|
|
|
"db read from context that shouldn't exist"
|
|
|
|
);
|
2023-11-02 11:01:55 -04:00
|
|
|
let path = env::var("TEST_FILE_PATH").unwrap();
|
|
|
|
let contents = fs::File::open(&path).unwrap();
|
|
|
|
//let test_string = std::str::from_utf8(&contents).unwrap();
|
|
|
|
let test_context: LoxServerContext = serde_json::from_reader(contents).unwrap();
|
2023-09-07 11:12:40 -04:00
|
|
|
lox_db.write_context(test_context);
|
|
|
|
assert!(
|
|
|
|
lox_db.db.len() == 1,
|
|
|
|
"db should have written only one context"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|