Skip to content

Commit 6bbf434

Browse files
authored
Merge pull request #2757 from fermyon/handle-state-dir
Handle State Directory
2 parents 527c2e3 + 79caa0b commit 6bbf434

File tree

13 files changed

+370
-228
lines changed

13 files changed

+370
-228
lines changed

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

Lines changed: 51 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,79 @@ use std::{
33
path::{Path, PathBuf},
44
};
55

6-
use anyhow::Context;
6+
use anyhow::{bail, Context};
77
use serde::{Deserialize, Serialize};
88
use spin_factor_key_value::runtime_config::spin::MakeKeyValueStore;
99
use spin_key_value_sqlite::{DatabaseLocation, KeyValueSqlite};
1010

1111
/// A key-value store that uses SQLite as the backend.
1212
pub struct SpinKeyValueStore {
1313
/// The base path or directory for the SQLite database file.
14-
base_path: PathBuf,
14+
base_path: Option<PathBuf>,
1515
}
1616

1717
impl SpinKeyValueStore {
1818
/// Create a new SpinKeyValueStore with the given base path.
19-
pub fn new(base_path: PathBuf) -> Self {
19+
///
20+
/// If the database directory is None, the database will always be in-memory.
21+
/// If it's `Some`, the database will be stored at the combined `base_path` and
22+
/// the `path` specified in the runtime configuration.
23+
pub fn new(base_path: Option<PathBuf>) -> Self {
2024
Self { base_path }
2125
}
2226
}
2327

24-
/// Runtime configuration for the SQLite key-value store.
28+
impl MakeKeyValueStore for SpinKeyValueStore {
29+
const RUNTIME_CONFIG_TYPE: &'static str = "spin";
30+
31+
type RuntimeConfig = SpinKeyValueRuntimeConfig;
32+
33+
type StoreManager = KeyValueSqlite;
34+
35+
fn make_store(
36+
&self,
37+
runtime_config: Self::RuntimeConfig,
38+
) -> anyhow::Result<Self::StoreManager> {
39+
let location = match (&self.base_path, &runtime_config.path) {
40+
// If both the base path and the path are specified, resolve the path against the base path
41+
(Some(base_path), Some(path)) => {
42+
let path = resolve_relative_path(path, base_path);
43+
DatabaseLocation::Path(path)
44+
}
45+
// If the base path is `None` but path is an absolute path, use the absolute path
46+
(None, Some(path)) if path.is_absolute() => DatabaseLocation::Path(path.clone()),
47+
// If the base path is `None` but path is a relative path, error out
48+
(None, Some(path)) => {
49+
bail!(
50+
"key-value store path '{}' is relative, but no base path is set",
51+
path.display()
52+
)
53+
}
54+
// Otherwise, use an in-memory database
55+
(None | Some(_), None) => DatabaseLocation::InMemory,
56+
};
57+
if let DatabaseLocation::Path(path) = &location {
58+
// Create the store's parent directory if necessary
59+
if let Some(parent) = path.parent().filter(|p| !p.exists()) {
60+
fs::create_dir_all(parent)
61+
.context("Failed to create key value store's parent directory")?;
62+
}
63+
}
64+
Ok(KeyValueSqlite::new(location))
65+
}
66+
}
67+
68+
/// The serialized runtime configuration for the SQLite key-value store.
2569
#[derive(Deserialize, Serialize)]
2670
pub struct SpinKeyValueRuntimeConfig {
2771
/// The path to the SQLite database file.
2872
path: Option<PathBuf>,
2973
}
3074

3175
impl SpinKeyValueRuntimeConfig {
32-
/// The default filename for the SQLite database.
33-
const DEFAULT_SPIN_STORE_FILENAME: &'static str = "sqlite_key_value.db";
34-
35-
/// Create a new runtime configuration with the given directory.
36-
///
37-
/// If the database directory is None, the database is in-memory.
38-
/// If the database directory is Some, the database is stored in a file in the given directory.
39-
pub fn default(default_database_dir: Option<PathBuf>) -> Self {
40-
let path = default_database_dir.map(|dir| dir.join(Self::DEFAULT_SPIN_STORE_FILENAME));
76+
/// Create a new SpinKeyValueRuntimeConfig with the given parent directory
77+
/// where the key-value store will live.
78+
pub fn new(path: Option<PathBuf>) -> Self {
4179
Self { path }
4280
}
4381
}
@@ -51,28 +89,3 @@ fn resolve_relative_path(path: &Path, base_dir: &Path) -> PathBuf {
5189
}
5290
base_dir.join(path)
5391
}
54-
55-
impl MakeKeyValueStore for SpinKeyValueStore {
56-
const RUNTIME_CONFIG_TYPE: &'static str = "spin";
57-
58-
type RuntimeConfig = SpinKeyValueRuntimeConfig;
59-
60-
type StoreManager = KeyValueSqlite;
61-
62-
fn make_store(
63-
&self,
64-
runtime_config: Self::RuntimeConfig,
65-
) -> anyhow::Result<Self::StoreManager> {
66-
let location = match runtime_config.path {
67-
Some(path) => {
68-
let path = resolve_relative_path(&path, &self.base_path);
69-
// Create the store's parent directory if necessary
70-
fs::create_dir_all(path.parent().unwrap())
71-
.context("Failed to create key value store")?;
72-
DatabaseLocation::Path(path)
73-
}
74-
None => DatabaseLocation::InMemory,
75-
};
76-
Ok(KeyValueSqlite::new(location))
77-
}
78-
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ pub struct RuntimeConfig {
1313

1414
impl RuntimeConfig {
1515
/// Adds a store manager for the store with the given label to the runtime configuration.
16+
///
17+
/// If a store manager already exists for the given label, it will be replaced.
1618
pub fn add_store_manager(&mut self, label: String, store_manager: Arc<dyn StoreManager>) {
1719
self.store_managers.insert(label, store_manager);
1820
}

crates/factor-key-value/src/runtime_config/spin.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ type StoreFromToml =
2828
/// Creates a `StoreFromToml` function from a `MakeKeyValueStore` implementation.
2929
fn store_from_toml_fn<T: MakeKeyValueStore>(provider_type: T) -> StoreFromToml {
3030
Arc::new(move |table| {
31-
let runtime_config: T::RuntimeConfig =
32-
table.try_into().context("could not parse runtime config")?;
31+
let runtime_config: T::RuntimeConfig = table
32+
.try_into()
33+
.context("could not parse key-value runtime config")?;
3334
let provider = provider_type
3435
.make_store(runtime_config)
35-
.context("could not make store")?;
36+
.context("could not make key-value store from runtime config")?;
3637
Ok(Arc::new(provider))
3738
})
3839
}
@@ -62,8 +63,20 @@ impl RuntimeConfigResolver {
6263
///
6364
/// Users must ensure that the store type for `config` has been registered with
6465
/// the resolver using [`Self::register_store_type`].
65-
pub fn add_default_store(&mut self, label: &'static str, config: StoreConfig) {
66-
self.defaults.insert(label, config);
66+
pub fn add_default_store<T>(
67+
&mut self,
68+
label: &'static str,
69+
config: T::RuntimeConfig,
70+
) -> anyhow::Result<()>
71+
where
72+
T: MakeKeyValueStore,
73+
T::RuntimeConfig: Serialize,
74+
{
75+
self.defaults.insert(
76+
label,
77+
StoreConfig::new(T::RUNTIME_CONFIG_TYPE.to_owned(), config)?,
78+
);
79+
Ok(())
6780
}
6881

6982
/// Registers a store type to the resolver.
@@ -96,7 +109,9 @@ impl RuntimeConfigResolver {
96109

97110
let mut runtime_config = RuntimeConfig::default();
98111
for (label, config) in table {
99-
let store_manager = self.store_manager_from_config(config)?;
112+
let store_manager = self.store_manager_from_config(config).with_context(|| {
113+
format!("could not configure key-value store with label '{label}'")
114+
})?;
100115
runtime_config.add_store_manager(label.clone(), store_manager);
101116
}
102117
Ok(Some(runtime_config))
@@ -121,6 +136,8 @@ impl RuntimeConfigResolver {
121136
impl DefaultLabelResolver for RuntimeConfigResolver {
122137
fn default(&self, label: &str) -> Option<Arc<dyn StoreManager>> {
123138
let config = self.defaults.get(label)?;
139+
// TODO(rylev): The unwrap here is not ideal. We should return a Result instead.
140+
// Piping that through `DefaultLabelResolver` is a bit awkward, though.
124141
Some(self.store_manager_from_config(config.clone()).unwrap())
125142
}
126143
}

0 commit comments

Comments
 (0)