From cbe0f74507c5775787c3823016bf86e2d2057a3f Mon Sep 17 00:00:00 2001 From: Shikha Vyaghra Date: Sun, 17 Nov 2024 22:08:46 +0000 Subject: [PATCH] datastore: changes to match changes in Core kit datastore Updated the `commit_transaction` function to enable committing metadata from pending transactions. In commit transaction we will first commit metadata and then pending keys to correctly perform the check to identify if key exists or not. The strength handling among pending and committed transaction is as: If pending metadata is strong and committed metadata is weak, commit the pending setting. If pending metadata is weak, committed metadata is strong and the setting is already available, do not downgrade from strong to weak. Otherwise add the strength file with value as weak. If pending and committed metadata are the same, no action is performed. Additionally, made minor changes to metadata functions for improved access and flexibility: Introduced a `committed` field to dynamically access metadata in pending transactions. Replaced the hardcoded use of live committed metadata with this committed variable ans pass Committed::Live from previous usages. Refer commit: https://github.com/bottlerocket-os/bottlerocket-core-kit/pull/294/commits/20a435e3f06625f6c75ccf8109a4a722a055d229 Refer PR: https://github.com/bottlerocket-os/bottlerocket-core-kit/pull/294 --- sources/Cargo.lock | 1 + sources/api/datastore/Cargo.toml | 1 + .../api/datastore/src/constraints_check.rs | 52 +++++ sources/api/datastore/src/error.rs | 16 +- sources/api/datastore/src/filesystem.rs | 69 +++++-- sources/api/datastore/src/lib.rs | 140 ++++++++++++-- sources/api/datastore/src/memory.rs | 180 +++++++++++++++--- 7 files changed, 401 insertions(+), 58 deletions(-) create mode 100644 sources/api/datastore/src/constraints_check.rs diff --git a/sources/Cargo.lock b/sources/Cargo.lock index d03c8babb28..5a7b32fd11f 100644 --- a/sources/Cargo.lock +++ b/sources/Cargo.lock @@ -1046,6 +1046,7 @@ dependencies = [ "percent-encoding", "serde", "serde_json", + "serde_plain", "snafu", "toml", "walkdir", diff --git a/sources/api/datastore/Cargo.toml b/sources/api/datastore/Cargo.toml index c5e88fe9bda..c1e7b41a8d8 100644 --- a/sources/api/datastore/Cargo.toml +++ b/sources/api/datastore/Cargo.toml @@ -16,6 +16,7 @@ serde = { workspace = true, features = ["derive"] } serde_json.workspace = true snafu.workspace = true walkdir.workspace = true +serde_plain.workspace = true [build-dependencies] generate-readme.workspace = true diff --git a/sources/api/datastore/src/constraints_check.rs b/sources/api/datastore/src/constraints_check.rs new file mode 100644 index 00000000000..1a162f3508f --- /dev/null +++ b/sources/api/datastore/src/constraints_check.rs @@ -0,0 +1,52 @@ +//! The outcome of the constraint check determines whether the transaction can proceed to commit. +//! A ‘rejected’ result means that one or more constraints have not been satisfied, +//! preventing the transaction from being committed. On the other hand, an ‘approved’ +//! result confirms that all constraints are satisfied and provides the required +//! settings and metadata for the commit. +//! Constraint checks can alter the write. + +use std::collections::HashMap; + +use crate::{error, Key}; + +type RejectReason = String; + +/// Represents a successful write operation after constraints have been approved. +/// Contains the following fields: +/// - `settings`: A collection of key-value pairs representing the settings to be committed. +/// - `metadata`: A collection of metadata entries. +#[derive(PartialEq)] +pub struct ApprovedWrite { + pub settings: HashMap, + pub metadata: Vec<(Key, Key, String)>, +} + +/// Represents the result of a constraint check. +/// The result can either reject the operation or approve it with the required data. +#[derive(PartialEq)] +pub enum ConstraintCheckResult { + Reject(RejectReason), + Approve(ApprovedWrite), +} + +impl TryFrom for ApprovedWrite { + type Error = error::Error; + + fn try_from(constraint_check_result: ConstraintCheckResult) -> Result { + match constraint_check_result { + ConstraintCheckResult::Reject(err) => error::ConstraintCheckRejectSnafu { err }.fail(), + ConstraintCheckResult::Approve(approved_write) => Ok(approved_write), + } + } +} + +impl From> for ConstraintCheckResult { + fn from(approved_write: Option) -> Self { + match approved_write { + None => ConstraintCheckResult::Reject( + "The write for the given transaction is rejected".to_string(), + ), + Some(approved_write) => ConstraintCheckResult::Approve(approved_write), + } + } +} diff --git a/sources/api/datastore/src/error.rs b/sources/api/datastore/src/error.rs index d1c7207ae1a..9a326381472 100644 --- a/sources/api/datastore/src/error.rs +++ b/sources/api/datastore/src/error.rs @@ -59,6 +59,20 @@ pub enum Error { #[snafu(display("Key name beyond maximum length {}: {}", name, max))] KeyTooLong { name: String, max: usize }, + + #[snafu(display("Unable to serialize data: {}", source))] + Serialize { source: serde_json::Error }, + + #[snafu(display("Unable to run the check constraint function: {}", source))] + CheckConstraintExecution { + source: Box, + }, + + #[snafu(display( + "Check constraint function rejected the transaction. Aborting commit : {}", + err + ))] + ConstraintCheckReject { err: String }, } -pub type Result = std::result::Result; +pub type Result = std::result::Result; diff --git a/sources/api/datastore/src/filesystem.rs b/sources/api/datastore/src/filesystem.rs index bf89eed783f..2aa1c4eb7d7 100644 --- a/sources/api/datastore/src/filesystem.rs +++ b/sources/api/datastore/src/filesystem.rs @@ -13,6 +13,8 @@ use std::io; use std::path::{self, Path, PathBuf}; use walkdir::{DirEntry, WalkDir}; +use crate::constraints_check::{ApprovedWrite, ConstraintCheckResult}; + use super::key::{Key, KeyType}; use super::{error, Committed, DataStore, Result}; @@ -413,6 +415,7 @@ impl DataStore for FilesystemDataStore { fn list_populated_metadata( &self, prefix: S1, + committed: &Committed, metadata_key_name: &Option, ) -> Result>> where @@ -420,7 +423,7 @@ impl DataStore for FilesystemDataStore { S2: AsRef, { // Find metadata key paths on disk - let key_paths = find_populated_key_paths(self, KeyType::Meta, prefix, &Committed::Live)?; + let key_paths = find_populated_key_paths(self, KeyType::Meta, prefix, committed)?; // For each file on disk, check the user's conditions, and add it to our output let mut result = HashMap::new(); @@ -460,8 +463,13 @@ impl DataStore for FilesystemDataStore { self.delete_key_path(path, committed) } - fn get_metadata_raw(&self, metadata_key: &Key, data_key: &Key) -> Result> { - let path = self.metadata_path(metadata_key, data_key, &Committed::Live)?; + fn get_metadata_raw( + &self, + metadata_key: &Key, + data_key: &Key, + committed: &Committed, + ) -> Result> { + let path = self.metadata_path(metadata_key, data_key, committed)?; read_file_for_key(metadata_key, &path) } @@ -470,8 +478,9 @@ impl DataStore for FilesystemDataStore { metadata_key: &Key, data_key: &Key, value: S, + committed: &Committed, ) -> Result<()> { - let path = self.metadata_path(metadata_key, data_key, &Committed::Live)?; + let path = self.metadata_path(metadata_key, data_key, committed)?; write_file_mkdir(path, value) } @@ -482,27 +491,57 @@ impl DataStore for FilesystemDataStore { /// We commit by copying pending keys to live, then removing pending. Something smarter (lock, /// atomic flip, etc.) will be required to make the server concurrent. - fn commit_transaction(&mut self, transaction: S) -> Result> + fn commit_transaction( + &mut self, + transaction: S, + constraint_check: &C, + ) -> Result> where S: Into + AsRef, + C: Fn( + &mut Self, + &Committed, + ) -> std::result::Result< + ConstraintCheckResult, + Box, + >, { + let mut pending_keys: HashSet = Default::default(); + + let transactions = self.list_transactions()?; + if !transactions.contains(transaction.as_ref()) { + return Ok(pending_keys); + } + let pending = Committed::Pending { tx: transaction.into(), }; - // Get data for changed keys - let pending_data = self.get_prefix("settings.", &pending)?; - // Nothing to do if no keys are present in pending - if pending_data.is_empty() { - return Ok(Default::default()); + let constraints_check_result = + constraint_check(self, &pending).context(error::CheckConstraintExecutionSnafu)?; + + let approved_write = ApprovedWrite::try_from(constraints_check_result)?; + + trace!( + "commit_transaction: transaction_metadata: {:?}", + approved_write.metadata + ); + + // write the metadata. + for (metadata_key, data_key, value) in approved_write.metadata { + self.set_metadata(&metadata_key, &data_key, value, &Committed::Live)?; } - // Save Keys for return value - let pending_keys: HashSet = pending_data.keys().cloned().collect(); + let pending_data = approved_write.settings; - // Apply changes to live - debug!("Writing pending keys to live"); - self.set_keys(&pending_data, &Committed::Live)?; + if !pending_data.is_empty() { + // Save Keys for return value + pending_keys = pending_data.keys().cloned().collect(); + + // Apply changes to live + debug!("Writing pending keys to live"); + self.set_keys(&pending_data, &Committed::Live)?; + } // Remove pending debug!("Removing old pending keys"); diff --git a/sources/api/datastore/src/lib.rs b/sources/api/datastore/src/lib.rs index 9156a525a19..221a608e003 100644 --- a/sources/api/datastore/src/lib.rs +++ b/sources/api/datastore/src/lib.rs @@ -27,6 +27,7 @@ The `deserialization` module provides code to deserialize datastore-acceptable k * The `serialization` module can't handle complex types under lists; it assumes lists can be serialized as scalars. */ +pub mod constraints_check; pub mod deserialization; pub mod error; pub mod filesystem; @@ -34,6 +35,7 @@ pub mod key; pub mod memory; pub mod serialization; +use constraints_check::ConstraintCheckResult; pub use error::{Error, Result}; pub use filesystem::FilesystemDataStore; pub use key::{Key, KeyType, KEY_SEPARATOR, KEY_SEPARATOR_STR}; @@ -72,6 +74,7 @@ pub trait DataStore { fn list_populated_metadata( &self, prefix: S1, + committed: &Committed, metadata_key_name: &Option, ) -> Result>> where @@ -89,7 +92,12 @@ pub trait DataStore { /// Retrieve the value for a single metadata key from the datastore. Values will inherit from /// earlier in the tree, if more specific values are not found later. - fn get_metadata(&self, metadata_key: &Key, data_key: &Key) -> Result> { + fn get_metadata( + &self, + metadata_key: &Key, + data_key: &Key, + committed: &Committed, + ) -> Result> { let mut result = Ok(None); let mut current_path = Vec::new(); @@ -101,7 +109,7 @@ pub trait DataStore { unreachable!("Prefix of Key failed to make Key: {:?}", current_path) }); - if let Some(md) = self.get_metadata_raw(metadata_key, &data_key)? { + if let Some(md) = self.get_metadata_raw(metadata_key, &data_key, committed)? { result = Ok(Some(md)); } } @@ -110,13 +118,19 @@ pub trait DataStore { /// Retrieve the value for a single metadata key from the datastore, without taking into /// account inheritance of metadata from earlier in the tree. - fn get_metadata_raw(&self, metadata_key: &Key, data_key: &Key) -> Result>; + fn get_metadata_raw( + &self, + metadata_key: &Key, + data_key: &Key, + committed: &Committed, + ) -> Result>; /// Set the value of a single metadata key in the datastore. fn set_metadata>( &mut self, metadata_key: &Key, data_key: &Key, value: S, + committed: &Committed, ) -> Result<()>; /// Removes the given metadata key from the given data key in the datastore. If we /// succeeded, we return Ok(()); if the data or metadata key didn't exist, we also return @@ -125,9 +139,20 @@ pub trait DataStore { /// Applies pending changes from the given transaction to the live datastore. Returns the /// list of changed keys. - fn commit_transaction(&mut self, transaction: S) -> Result> + fn commit_transaction( + &mut self, + transaction: S, + constraint_check: &C, + ) -> Result> where - S: Into + AsRef; + S: Into + AsRef, + C: Fn( + &mut Self, + &Committed, + ) -> std::result::Result< + ConstraintCheckResult, + Box, + >; /// Remove the given pending transaction from the datastore. Returns the list of removed /// keys. If the transaction doesn't exist, will return Ok with an empty list. @@ -205,13 +230,14 @@ pub trait DataStore { fn get_metadata_prefix( &self, find_prefix: S1, + committed: &Committed, metadata_key_name: &Option, ) -> Result>> where S1: AsRef, S2: AsRef, { - let meta_map = self.list_populated_metadata(&find_prefix, metadata_key_name)?; + let meta_map = self.list_populated_metadata(&find_prefix, committed, metadata_key_name)?; trace!("Found populated metadata: {:?}", meta_map); if meta_map.is_empty() { return Ok(HashMap::new()); @@ -234,12 +260,12 @@ pub trait DataStore { meta_key, &data_key ); - let value = self.get_metadata(&meta_key, &data_key)?.context( - error::ListedMetaNotPresentSnafu { + let value = self + .get_metadata(&meta_key, &data_key, committed)? + .context(error::ListedMetaNotPresentSnafu { meta_key: meta_key.name(), data_key: data_key.name(), - }, - )?; + })?; // Insert a top-level map entry for the data key if we've found metadata. let data_entry = result.entry(data_key.clone()).or_insert_with(HashMap::new); @@ -336,14 +362,20 @@ mod test { let grandchild = Key::new(KeyType::Data, "a.b.c").unwrap(); // Set metadata on parent - m.set_metadata(&meta, &parent, "value").unwrap(); + m.set_metadata(&meta, &parent, "value", &Committed::Live) + .unwrap(); // Metadata shows up on grandchild... assert_eq!( - m.get_metadata(&meta, &grandchild).unwrap(), + m.get_metadata(&meta, &grandchild, &Committed::Live) + .unwrap(), Some("value".to_string()) ); // ...but only through inheritance, not directly. - assert_eq!(m.get_metadata_raw(&meta, &grandchild).unwrap(), None); + assert_eq!( + m.get_metadata_raw(&meta, &grandchild, &Committed::Live) + .unwrap(), + None + ); } #[test] @@ -379,20 +411,92 @@ mod test { let mk1 = Key::new(KeyType::Meta, "metatest1").unwrap(); let mk2 = Key::new(KeyType::Meta, "metatest2").unwrap(); let mk3 = Key::new(KeyType::Meta, "metatest3").unwrap(); - m.set_metadata(&mk1, &k1, "41").unwrap(); - m.set_metadata(&mk2, &k2, "42").unwrap(); - m.set_metadata(&mk3, &k3, "43").unwrap(); + m.set_metadata(&mk1, &k1, "41", &Committed::Live).unwrap(); + m.set_metadata(&mk2, &k2, "42", &Committed::Live).unwrap(); + m.set_metadata(&mk3, &k3, "43", &Committed::Live).unwrap(); + + // Check all metadata + assert_eq!( + m.get_metadata_prefix("x.", &Committed::Live, &None as &Option<&str>) + .unwrap(), + hashmap!(k1 => hashmap!(mk1 => "41".to_string()), + k2.clone() => hashmap!(mk2.clone() => "42".to_string())) + ); + + // Check metadata matching a given name + assert_eq!( + m.get_metadata_prefix("x.", &Committed::Live, &Some("metatest2")) + .unwrap(), + hashmap!(k2 => hashmap!(mk2 => "42".to_string())) + ); + } + + #[test] + fn get_metadata_prefix_from_pending() { + let mut m = MemoryDataStore::new(); + + // Build some data keys to which we can attach metadata; they don't actually have to be + // set in the data store. + let k1 = Key::new(KeyType::Data, "x.1").unwrap(); + let k2 = Key::new(KeyType::Data, "x.2").unwrap(); + let k3 = Key::new(KeyType::Data, "y.3").unwrap(); + + // Set some metadata to check + let mk1 = Key::new(KeyType::Meta, "metatest1").unwrap(); + let mk2 = Key::new(KeyType::Meta, "metatest2").unwrap(); + let mk3 = Key::new(KeyType::Meta, "metatest3").unwrap(); + m.set_metadata( + &mk1, + &k1, + "41", + &Committed::Pending { + tx: "test".to_owned(), + }, + ) + .unwrap(); + m.set_metadata( + &mk2, + &k2, + "42", + &Committed::Pending { + tx: "test".to_owned(), + }, + ) + .unwrap(); + m.set_metadata( + &mk3, + &k3, + "43", + &Committed::Pending { + tx: "test".to_owned(), + }, + ) + .unwrap(); // Check all metadata assert_eq!( - m.get_metadata_prefix("x.", &None as &Option<&str>).unwrap(), + m.get_metadata_prefix( + "x.", + &Committed::Pending { + tx: "test".to_owned() + }, + &None as &Option<&str> + ) + .unwrap(), hashmap!(k1 => hashmap!(mk1 => "41".to_string()), k2.clone() => hashmap!(mk2.clone() => "42".to_string())) ); // Check metadata matching a given name assert_eq!( - m.get_metadata_prefix("x.", &Some("metatest2")).unwrap(), + m.get_metadata_prefix( + "x.", + &Committed::Pending { + tx: "test".to_owned() + }, + &Some("metatest2") + ) + .unwrap(), hashmap!(k2 => hashmap!(mk2 => "42".to_string())) ); } diff --git a/sources/api/datastore/src/memory.rs b/sources/api/datastore/src/memory.rs index 4ffc397912f..a4b91fc7592 100644 --- a/sources/api/datastore/src/memory.rs +++ b/sources/api/datastore/src/memory.rs @@ -5,6 +5,8 @@ use std::collections::{HashMap, HashSet}; +use crate::constraints_check::{ApprovedWrite, ConstraintCheckResult}; + use super::{Committed, DataStore, Key, Result}; #[derive(Debug, Default)] @@ -16,6 +18,9 @@ pub struct MemoryDataStore { // Map of data keys to their metadata, which in turn is a mapping of metadata keys to // arbitrary (string/serialized) values. metadata: HashMap>, + // Map of data keys to their metadata, which in turn is a mapping of metadata keys to + // arbitrary (string/serialized) values in pending transaction + pending_metadata: HashMap>, } impl MemoryDataStore { @@ -57,14 +62,21 @@ impl DataStore for MemoryDataStore { fn list_populated_metadata( &self, prefix: S1, + committed: &Committed, metadata_key_name: &Option, ) -> Result>> where S1: AsRef, S2: AsRef, { + let metadata_to_use = match committed { + Committed::Live => &self.metadata, + Committed::Pending { .. } => &self.pending_metadata, + }; + let mut result = HashMap::new(); - for (data_key, meta_map) in self.metadata.iter() { + + for (data_key, meta_map) in metadata_to_use.iter() { // Confirm data key matches requested prefix. if !data_key.name().starts_with(prefix.as_ref()) { continue; @@ -112,8 +124,19 @@ impl DataStore for MemoryDataStore { Ok(dataset.contains_key(key)) } - fn get_metadata_raw(&self, metadata_key: &Key, data_key: &Key) -> Result> { - let metadata_for_data = self.metadata.get(data_key); + fn get_metadata_raw( + &self, + metadata_key: &Key, + data_key: &Key, + committed: &Committed, + ) -> Result> { + let metadata_to_use = match committed { + Committed::Live => &self.metadata, + Committed::Pending { .. } => &self.pending_metadata, + }; + + let metadata_for_data = metadata_to_use.get(data_key); + // If we have a metadata entry for this data key, then we can try fetching the requested // metadata key, otherwise we'll return early with Ok(None). let result = metadata_for_data.and_then(|m| m.get(metadata_key)); @@ -125,17 +148,14 @@ impl DataStore for MemoryDataStore { metadata_key: &Key, data_key: &Key, value: S, + committed: &Committed, ) -> Result<()> { - // If we don't already have a metadata entry for this data key, insert one. - let metadata_for_data = self - .metadata - // Clone data key because we want the HashMap key type to be Key, not &Key, and we - // can't pass ownership because we only have a reference from our parameters. - .entry(data_key.clone()) - .or_default(); - - metadata_for_data.insert(metadata_key.clone(), value.as_ref().to_owned()); - Ok(()) + match committed { + Committed::Live => set_metadata_raw(&mut self.metadata, metadata_key, data_key, value), + Committed::Pending { .. } => { + set_metadata_raw(&mut self.pending_metadata, metadata_key, data_key, value) + } + } } fn unset_metadata(&mut self, metadata_key: &Key, data_key: &Key) -> Result<()> { @@ -146,19 +166,45 @@ impl DataStore for MemoryDataStore { Ok(()) } - fn commit_transaction(&mut self, transaction: S) -> Result> + fn commit_transaction( + &mut self, + transaction: S, + constraint_check: &C, + ) -> Result> where S: Into + AsRef, + C: Fn( + &mut Self, + &Committed, + ) -> std::result::Result< + ConstraintCheckResult, + Box, + >, { + let tx = transaction.as_ref(); + let pending = Committed::Pending { tx: tx.into() }; + + let constraint_check_result = + constraint_check(self, &pending).unwrap_or(ConstraintCheckResult::Reject( + "Check constraint function rejected the transaction. Aborting commit".to_string(), + )); + let approved_write = ApprovedWrite::try_from(constraint_check_result)?; + + let mut pending_keys: HashSet = Default::default(); // Remove anything pending for this transaction - if let Some(pending) = self.pending.remove(transaction.as_ref()) { + + if !approved_write.settings.is_empty() { + // Save Keys for return value + pending_keys = approved_write.settings.keys().cloned().collect(); + // Apply pending changes to live - self.set_keys(&pending, &Committed::Live)?; - // Return keys that were committed - Ok(pending.keys().cloned().collect()) - } else { - Ok(HashSet::new()) + self.set_keys(&approved_write.settings, &Committed::Live)?; } + + self.pending.remove(tx); + + // Return keys that were committed + Ok(pending_keys) } fn delete_transaction(&mut self, transaction: S) -> Result> @@ -179,12 +225,74 @@ impl DataStore for MemoryDataStore { } } +fn set_metadata_raw>( + metadata_to_use: &mut HashMap>, + metadata_key: &Key, + data_key: &Key, + value: S, +) -> Result<()> { + // If we don't already have a metadata entry for this data key, insert one. + let metadata_for_data = metadata_to_use + // Clone data key because we want the HashMap key type to be Key, not &Key, and we + // can't pass ownership because we only have a reference from our parameters. + .entry(data_key.clone()) + .or_default(); + + metadata_for_data.insert(metadata_key.clone(), value.as_ref().to_owned()); + Ok(()) +} + #[cfg(test)] mod test { + use std::collections::HashMap; + use super::super::{Committed, DataStore, Key, KeyType}; use super::MemoryDataStore; + use crate::constraints_check::{ApprovedWrite, ConstraintCheckResult}; + use crate::{deserialize_scalar, serialize_scalar, ScalarError}; use maplit::hashset; + fn constraint_check( + datastore: &mut MemoryDataStore, + committed: &Committed, + ) -> super::Result> + { + let mut transaction_metadata = datastore + .get_metadata_prefix("settings.", committed, &None as &Option<&str>) + .unwrap(); + + let settings_to_commit: HashMap = match committed { + Committed::Pending { tx: transaction } => datastore + .pending + .get(transaction) + .unwrap_or(&HashMap::new()) + .clone(), + Committed::Live => HashMap::new(), + }; + + let mut metadata_to_commit: Vec<(Key, Key, String)> = Vec::new(); + + for (key, value) in transaction_metadata.iter_mut() { + for (metadata_key, metadata_value) in value { + if metadata_key.name() != "strength" { + continue; + } + + // strength in pending transaction + let pending_strength: String = + deserialize_scalar::<_, ScalarError>(&metadata_value.clone()).unwrap(); + let met_value = serialize_scalar::<_, ScalarError>(&pending_strength).unwrap(); + metadata_to_commit.push((metadata_key.clone(), key.clone(), met_value)); + } + } + let approved_write = ApprovedWrite { + settings: settings_to_commit, + metadata: metadata_to_commit, + }; + + Ok(ConstraintCheckResult::from(Some(approved_write))) + } + #[test] fn get_set_unset() { let mut m = MemoryDataStore::new(); @@ -198,14 +306,38 @@ mod test { let mdkey = Key::new(KeyType::Meta, "testmd").unwrap(); let md = "mdval"; - m.set_metadata(&mdkey, &k, md).unwrap(); + m.set_metadata(&mdkey, &k, md, &Committed::Live).unwrap(); assert_eq!( - m.get_metadata_raw(&mdkey, &k).unwrap(), + m.get_metadata_raw(&mdkey, &k, &Committed::Live).unwrap(), + Some(md.to_string()) + ); + + m.set_metadata( + &mdkey, + &k, + md, + &Committed::Pending { + tx: "test".to_owned(), + }, + ) + .unwrap(); + assert_eq!( + m.get_metadata_raw( + &mdkey, + &k, + &Committed::Pending { + tx: "test".to_owned() + } + ) + .unwrap(), Some(md.to_string()) ); m.unset_metadata(&mdkey, &k).unwrap(); - assert_eq!(m.get_metadata_raw(&mdkey, &k).unwrap(), None); + assert_eq!( + m.get_metadata_raw(&mdkey, &k, &Committed::Live).unwrap(), + None + ); m.unset_key(&k, &Committed::Live).unwrap(); assert_eq!(m.get_key(&k, &Committed::Live).unwrap(), None); @@ -242,7 +374,7 @@ mod test { assert!(m.key_populated(&k, &pending).unwrap()); assert!(!m.key_populated(&k, &Committed::Live).unwrap()); - m.commit_transaction(tx).unwrap(); + m.commit_transaction(tx, &constraint_check).unwrap(); assert!(!m.key_populated(&k, &pending).unwrap()); assert!(m.key_populated(&k, &Committed::Live).unwrap()); }