Skip to content

Commit

Permalink
factors: key value tests, doc comments, and Azure factor
Browse files Browse the repository at this point in the history
Signed-off-by: Kate Goldenring <kate.goldenring@fermyon.com>
  • Loading branch information
kate-goldenring committed Jul 19, 2024
1 parent 7c6faed commit 1a4615a
Show file tree
Hide file tree
Showing 12 changed files with 414 additions and 9 deletions.
77 changes: 77 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions crates/factor-key-value-azure/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "spin-factor-key-value-azure"
version.workspace = true
authors.workspace = true
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
rust-version.workspace = true

[dependencies]
anyhow = "1.0"
serde = { version = "1.0", features = ["rc"] }
spin-factor-key-value = { path = "../factor-key-value" }
# TODO: merge with this crate
spin-key-value-azure = { path = "../key-value-azure" }

[lints]
workspace = true
35 changes: 35 additions & 0 deletions crates/factor-key-value-azure/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use serde::Deserialize;
use spin_factor_key_value::MakeKeyValueStore;
use spin_key_value_azure::KeyValueAzureCosmos;

/// A key-value store that uses Azure Cosmos as the backend.
pub struct AzureKeyValueStore;

/// Runtime configuration for the Azure Cosmos key-value store.
#[derive(Deserialize)]
pub struct AzureCosmosKeyValueRuntimeConfig {
/// The authorization token for the Azure Cosmos DB account.
key: String,
/// The Azure Cosmos DB account name.
account: String,
/// The Azure Cosmos DB database.
database: String,
/// The Azure Cosmos DB container where data is stored.
/// The CosmosDB container must be created with the default partition key, /id
container: String,
}

impl MakeKeyValueStore for AzureKeyValueStore {
const RUNTIME_CONFIG_TYPE: &'static str = "azure_cosmos";

type RuntimeConfig = AzureCosmosKeyValueRuntimeConfig;

type StoreManager = KeyValueAzureCosmos;

fn make_store(
&self,
runtime_config: Self::RuntimeConfig,
) -> anyhow::Result<Self::StoreManager> {
KeyValueAzureCosmos::new(runtime_config.key, runtime_config.account, runtime_config.database, runtime_config.container)
}
}
4 changes: 4 additions & 0 deletions crates/factor-key-value-redis/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use serde::Deserialize;
use spin_factor_key_value::MakeKeyValueStore;
use spin_key_value_redis::KeyValueRedis;

/// A key-value store that uses Redis as the backend.
pub struct RedisKeyValueStore;

/// Runtime configuration for the Redis key-value store.
#[derive(Deserialize)]
pub struct RedisKeyValueRuntimeConfig {
/// The URL of the Redis server.
url: String,
}

Expand Down
10 changes: 10 additions & 0 deletions crates/factor-key-value-spin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ use serde::{Deserialize, Serialize};
use spin_factor_key_value::MakeKeyValueStore;
use spin_key_value_sqlite::{DatabaseLocation, KeyValueSqlite};

/// A key-value store that uses SQLite as the backend.
pub struct SpinKeyValueStore {
/// The base path or directory for the SQLite database file.
base_path: PathBuf,
}

impl SpinKeyValueStore {
/// Create a new SpinKeyValueStore with the given base path.
/// If the base path is None, the current directory is used.
pub fn new(base_path: Option<PathBuf>) -> anyhow::Result<Self> {
let base_path = match base_path {
Some(path) => path,
Expand All @@ -22,14 +26,20 @@ impl SpinKeyValueStore {
}
}

/// Runtime configuration for the SQLite key-value store.
#[derive(Deserialize, Serialize)]
pub struct SpinKeyValueRuntimeConfig {
/// The path to the SQLite database file.
path: Option<PathBuf>,
}

impl SpinKeyValueRuntimeConfig {
/// The default filename for the SQLite database.
const DEFAULT_SPIN_STORE_FILENAME: &'static str = "sqlite_key_value.db";

/// Create a new runtime configuration with the given state directory.
/// If the state directory is None, the database is in-memory.
/// If the state directory is Some, the database is stored in a file in the state directory.
pub fn default(state_dir: Option<PathBuf>) -> Self {
let path = state_dir.map(|dir| dir.join(Self::DEFAULT_SPIN_STORE_FILENAME));
Self { path }
Expand Down
8 changes: 8 additions & 0 deletions crates/factor-key-value/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,13 @@ spin-key-value = { path = "../key-value" }
spin-world = { path = "../world" }
toml = "0.8"

[dev-dependencies]
spin-factors-test = { path = "../factors-test" }
tokio = { version = "1", features = ["macros", "rt"] }
spin-factor-key-value-spin = { path = "../factor-key-value-spin" }
spin-factor-key-value-redis = { path = "../factor-key-value-redis" }
tempdir = "0.3"


[lints]
workspace = true
13 changes: 11 additions & 2 deletions crates/factor-key-value/src/delegating_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,26 @@ use crate::store::{store_from_toml_fn, MakeKeyValueStore, StoreFromToml};
use spin_key_value::StoreManager;
use std::{collections::HashMap, sync::Arc};

/// A RuntimeConfigResolver that delegates to the appropriate key-value store for a given label.
/// The store types are registered with the resolver using `add_store_type`.
/// The default store for a label is registered using `add_default_store`.
#[derive(Default)]
pub struct DelegatingRuntimeConfigResolver {
/// A map of store types to a function that returns the appropriate store manager from runtime config TOML.
store_types: HashMap<&'static str, StoreFromToml>,
/// A map of default store configurations for a label.
defaults: HashMap<&'static str, StoreConfig>,
}

type StoreConfig = (&'static str, toml::value::Table);

impl DelegatingRuntimeConfigResolver {
/// Create a new DelegatingRuntimeConfigResolver.
pub fn new() -> Self {
Self::default()
}

/// Adds a default store type and configuration for a label to the resolver.
pub fn add_default_store(
&mut self,
label: &'static str,
Expand All @@ -24,9 +31,8 @@ impl DelegatingRuntimeConfigResolver {
) {
self.defaults.insert(label, (store_kind, config));
}
}

impl DelegatingRuntimeConfigResolver {
/// Adds a store type to the resolver.
pub fn add_store_type<T: MakeKeyValueStore>(&mut self, store_type: T) -> anyhow::Result<()> {
if self
.store_types
Expand All @@ -43,6 +49,7 @@ impl DelegatingRuntimeConfigResolver {
}

impl RuntimeConfigResolver for DelegatingRuntimeConfigResolver {
/// Get the store manager for the given store kind and configuration.
fn get_store(
&self,
store_kind: &str,
Expand All @@ -55,6 +62,8 @@ impl RuntimeConfigResolver for DelegatingRuntimeConfigResolver {
store_from_toml(config)
}

/// Get the default store manager for the given label.
/// Returns None if no default store is registered for the label.
fn default_store(&self, label: &str) -> Option<Arc<dyn StoreManager>> {
let (store_kind, config) = self.defaults.get(label)?;
self.get_store(store_kind, config.to_owned()).ok()
Expand Down
27 changes: 22 additions & 5 deletions crates/factor-key-value/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub mod delegating_resolver;
mod runtime_config;
mod store;
pub use delegating_resolver::DelegatingRuntimeConfigResolver;

use std::{
collections::{HashMap, HashSet},
Expand All @@ -19,11 +20,14 @@ use spin_key_value::{
};
pub use store::MakeKeyValueStore;

/// A factor that provides key-value storage.
pub struct KeyValueFactor {
/// Resolves runtime configuration into store managers.
runtime_config_resolver: Arc<dyn runtime_config::RuntimeConfigResolver>,
}

impl KeyValueFactor {
/// Create a new KeyValueFactor.
pub fn new(
runtime_config_resolver: impl runtime_config::RuntimeConfigResolver + 'static,
) -> Self {
Expand All @@ -48,7 +52,7 @@ impl Factor for KeyValueFactor {
&self,
mut ctx: ConfigureAppContext<T, Self>,
) -> anyhow::Result<Self::AppState> {
// Build StoreManager from runtime config
// Build store manager from runtime config
let mut store_managers: HashMap<String, Arc<dyn StoreManager>> = HashMap::new();
if let Some(runtime_config) = ctx.take_runtime_config() {
for (
Expand All @@ -59,10 +63,14 @@ impl Factor for KeyValueFactor {
},
) in runtime_config.store_configs
{
let store = self
.runtime_config_resolver
.get_store(&store_kind, config)?;
store_managers.insert(store_label, store);
if let std::collections::hash_map::Entry::Vacant(e) =
store_managers.entry(store_label)
{
let store = self
.runtime_config_resolver
.get_store(&store_kind, config)?;
e.insert(store);
}
}
}
let resolver_clone = self.runtime_config_resolver.clone();
Expand Down Expand Up @@ -121,12 +129,21 @@ impl Factor for KeyValueFactor {
type AppStoreManager = CachingStoreManager<DelegatingStoreManager>;

pub struct AppState {
/// The store manager for the app. This is a cache around a delegating store
/// manager. For `get` requests, first checks the cache before delegating to
/// the underlying store manager.
store_manager: Arc<AppStoreManager>,
/// The allowed stores for each component.
/// This is a map from component ID to the set of store labels that the component is allowed to use.
component_allowed_stores: HashMap<String, HashSet<String>>,
}

pub struct InstanceBuilder {
/// The store manager for the app. This is a cache around a delegating store
/// manager. For `get` requests, first checks the cache before delegating to
/// the underlying store manager.
store_manager: Arc<AppStoreManager>,
/// The allowed stores for this component instance.
allowed_stores: HashSet<String>,
}

Expand Down
Loading

0 comments on commit 1a4615a

Please sign in to comment.