Skip to content

Commit c03e32f

Browse files
committed
factors: Add KeyValueFactor
Signed-off-by: Lann Martin <lann.martin@fermyon.com>
1 parent bcbd2c4 commit c03e32f

File tree

18 files changed

+472
-78
lines changed

18 files changed

+472
-78
lines changed

Cargo.lock

Lines changed: 37 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/factor-key-value/Cargo.toml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
name = "spin-factor-key-value"
3+
version = { workspace = true }
4+
authors = { workspace = true }
5+
edition = { workspace = true }
6+
7+
[dependencies]
8+
anyhow = "1.0"
9+
serde = { version = "1.0", features = ["rc"] }
10+
spin-factors = { path = "../factors" }
11+
# TODO: merge with this crate
12+
spin-key-value = { path = "../key-value" }
13+
spin-world = { path = "../world" }
14+
toml = "0.8"
15+
16+
[lints]
17+
workspace = true

crates/factor-key-value/src/lib.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
mod store;
2+
3+
use std::{
4+
collections::{HashMap, HashSet},
5+
sync::Arc,
6+
};
7+
8+
use anyhow::{bail, ensure};
9+
use serde::Deserialize;
10+
use spin_factors::{
11+
anyhow::{self, Context},
12+
ConfigureAppContext, Factor, FactorInstanceBuilder, FactorRuntimeConfig, InitContext,
13+
InstanceBuilders, PrepareContext, RuntimeFactors,
14+
};
15+
use spin_key_value::{
16+
CachingStoreManager, DelegatingStoreManager, KeyValueDispatch, StoreManager,
17+
KEY_VALUE_STORES_KEY,
18+
};
19+
use store::{store_from_toml_fn, StoreFromToml};
20+
21+
pub use store::MakeKeyValueStore;
22+
23+
#[derive(Default)]
24+
pub struct KeyValueFactor {
25+
store_types: HashMap<&'static str, StoreFromToml>,
26+
}
27+
28+
impl KeyValueFactor {
29+
pub fn add_store_type<T: MakeKeyValueStore>(&mut self, store_type: T) -> anyhow::Result<()> {
30+
if self
31+
.store_types
32+
.insert(T::RUNTIME_CONFIG_TYPE, store_from_toml_fn(store_type))
33+
.is_some()
34+
{
35+
bail!(
36+
"duplicate key value store type {:?}",
37+
T::RUNTIME_CONFIG_TYPE
38+
);
39+
}
40+
Ok(())
41+
}
42+
}
43+
44+
impl Factor for KeyValueFactor {
45+
type RuntimeConfig = RuntimeConfig;
46+
type AppState = AppState;
47+
type InstanceBuilder = InstanceBuilder;
48+
49+
fn init<Factors: RuntimeFactors>(
50+
&mut self,
51+
mut ctx: InitContext<Factors, Self>,
52+
) -> anyhow::Result<()> {
53+
ctx.link_bindings(spin_world::v1::key_value::add_to_linker)?;
54+
ctx.link_bindings(spin_world::v2::key_value::add_to_linker)?;
55+
Ok(())
56+
}
57+
58+
fn configure_app<T: RuntimeFactors>(
59+
&self,
60+
mut ctx: ConfigureAppContext<T, Self>,
61+
) -> anyhow::Result<Self::AppState> {
62+
// Build StoreManager from runtime config
63+
let mut stores = HashMap::new();
64+
if let Some(runtime_config) = ctx.take_runtime_config() {
65+
for (label, StoreConfig { type_, config }) in runtime_config.store_configs {
66+
let store_maker = self
67+
.store_types
68+
.get(type_.as_str())
69+
.with_context(|| format!("unknown key value store type {type_:?}"))?;
70+
let store = store_maker(config)?;
71+
stores.insert(label, store);
72+
}
73+
}
74+
let delegating_manager = DelegatingStoreManager::new(stores);
75+
let caching_manager = CachingStoreManager::new(delegating_manager);
76+
let store_manager = Arc::new(caching_manager);
77+
78+
// Build component -> allowed stores map
79+
let mut component_allowed_stores = HashMap::new();
80+
for component in ctx.app().components() {
81+
let component_id = component.id().to_string();
82+
let key_value_stores = component
83+
.get_metadata(KEY_VALUE_STORES_KEY)?
84+
.unwrap_or_default()
85+
.into_iter()
86+
.collect::<HashSet<_>>();
87+
for label in &key_value_stores {
88+
// TODO: port nicer errors from KeyValueComponent (via error type?)
89+
ensure!(
90+
store_manager.is_defined(label),
91+
"unknown key_value_stores label {label:?} for component {component_id:?}"
92+
);
93+
}
94+
component_allowed_stores.insert(component_id, key_value_stores);
95+
// TODO: warn (?) on unused store?
96+
}
97+
98+
Ok(AppState {
99+
store_manager,
100+
component_allowed_stores,
101+
})
102+
}
103+
104+
fn prepare<T: RuntimeFactors>(
105+
ctx: PrepareContext<Self>,
106+
_builders: &mut InstanceBuilders<T>,
107+
) -> anyhow::Result<InstanceBuilder> {
108+
let app_state = ctx.app_state();
109+
let allowed_stores = app_state
110+
.component_allowed_stores
111+
.get(ctx.app_component().id())
112+
.expect("component should be in component_stores")
113+
.clone();
114+
Ok(InstanceBuilder {
115+
store_manager: app_state.store_manager.clone(),
116+
allowed_stores,
117+
})
118+
}
119+
}
120+
121+
#[derive(Deserialize)]
122+
#[serde(transparent)]
123+
pub struct RuntimeConfig {
124+
store_configs: HashMap<String, StoreConfig>,
125+
}
126+
127+
impl FactorRuntimeConfig for RuntimeConfig {
128+
const KEY: &'static str = "key_value_store";
129+
}
130+
131+
#[derive(Deserialize)]
132+
struct StoreConfig {
133+
#[serde(rename = "type")]
134+
type_: String,
135+
#[serde(flatten)]
136+
config: toml::Table,
137+
}
138+
139+
type AppStoreManager = CachingStoreManager<DelegatingStoreManager>;
140+
141+
pub struct AppState {
142+
store_manager: Arc<AppStoreManager>,
143+
component_allowed_stores: HashMap<String, HashSet<String>>,
144+
}
145+
146+
pub struct InstanceBuilder {
147+
store_manager: Arc<AppStoreManager>,
148+
allowed_stores: HashSet<String>,
149+
}
150+
151+
impl FactorInstanceBuilder for InstanceBuilder {
152+
type InstanceState = KeyValueDispatch;
153+
154+
fn build(self) -> anyhow::Result<Self::InstanceState> {
155+
let Self {
156+
store_manager,
157+
allowed_stores,
158+
} = self;
159+
let mut dispatch = KeyValueDispatch::new_with_capacity(u32::MAX);
160+
dispatch.init(allowed_stores, store_manager);
161+
Ok(dispatch)
162+
}
163+
}

crates/factor-key-value/src/store.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use std::sync::Arc;
2+
3+
use serde::de::DeserializeOwned;
4+
use spin_key_value::StoreManager;
5+
6+
pub trait MakeKeyValueStore: 'static {
7+
const RUNTIME_CONFIG_TYPE: &'static str;
8+
9+
type RuntimeConfig: DeserializeOwned;
10+
type StoreManager: StoreManager;
11+
12+
fn make_store(&self, runtime_config: Self::RuntimeConfig)
13+
-> anyhow::Result<Self::StoreManager>;
14+
}
15+
16+
pub(crate) type StoreFromToml = Box<dyn Fn(toml::Table) -> anyhow::Result<Arc<dyn StoreManager>>>;
17+
18+
pub(crate) fn store_from_toml_fn<T: MakeKeyValueStore>(provider_type: T) -> StoreFromToml {
19+
Box::new(move |table| {
20+
let runtime_config: T::RuntimeConfig = table.try_into()?;
21+
let provider = provider_type.make_store(runtime_config)?;
22+
Ok(Arc::new(provider))
23+
})
24+
}

crates/factor-outbound-http/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ use spin_factors::{
77
SelfInstanceBuilder,
88
};
99
use wasmtime_wasi_http::WasiHttpCtx;
10+
11+
pub use wasi::get_wasi_http_view;
12+
1013
pub struct OutboundHttpFactor;
1114

1215
impl Factor for OutboundHttpFactor {

crates/factor-outbound-http/src/wasi.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,14 @@ impl<'a> WasiHttpView for MutStates<'a> {
3838
self.wasi.table()
3939
}
4040
}
41+
42+
// TODO: This is a little weird, organizationally
43+
pub fn get_wasi_http_view<T: RuntimeFactors>(
44+
instance_state: &mut T::InstanceState,
45+
) -> anyhow::Result<impl WasiHttpView + '_> {
46+
let wasi_and_http_getter =
47+
T::instance_state_getter2::<spin_factor_wasi::WasiFactor, crate::OutboundHttpFactor>()
48+
.context("failed to get WasiFactor")?;
49+
let (wasi, http) = wasi_and_http_getter.get_states(instance_state);
50+
Ok(MutStates { http, wasi })
51+
}

0 commit comments

Comments
 (0)