Skip to main content

shardline_server/
runtime_check.rs

1use crate::{
2    ServerConfig, ServerError, backend::ServerBackend,
3    reconstruction_cache::ReconstructionCacheService,
4};
5
6/// Result of validating a Shardline runtime configuration.
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct ConfigCheckReport {
9    /// Whether validation succeeded.
10    pub status: String,
11    /// Selected runtime role.
12    pub server_role: String,
13    /// Enabled runtime protocol frontends.
14    pub server_frontends: Vec<String>,
15    /// Selected metadata backend.
16    pub metadata_backend: String,
17    /// Selected immutable object-storage backend.
18    pub object_backend: String,
19    /// Selected reconstruction-cache backend.
20    pub cache_backend: String,
21    /// Whether signed CAS bearer tokens are enabled.
22    pub auth_enabled: bool,
23    /// Whether provider token issuance is enabled.
24    pub provider_tokens_enabled: bool,
25}
26
27/// Validates the selected runtime configuration and backend reachability.
28///
29/// # Errors
30///
31/// Returns [`ServerError`] when the configured backend cannot initialize or fails its
32/// readiness checks.
33pub async fn run_config_check(config: ServerConfig) -> Result<ConfigCheckReport, ServerError> {
34    config.validate_runtime_requirements()?;
35    let auth_enabled = config.token_signing_key().is_some();
36    let provider_tokens_enabled = config.server_role().serves_api()
37        && config.provider_config_path().is_some()
38        && config.provider_api_key().is_some();
39    let backend = ServerBackend::from_config(&config).await?;
40    let reconstruction_cache = if config.server_role().uses_reconstruction_cache() {
41        ReconstructionCacheService::from_config(&config)?
42    } else {
43        ReconstructionCacheService::disabled()
44    };
45    backend.ready().await?;
46    reconstruction_cache.ready().await?;
47
48    Ok(ConfigCheckReport {
49        status: "ok".to_owned(),
50        server_role: config.server_role().as_str().to_owned(),
51        server_frontends: config
52            .server_frontends()
53            .iter()
54            .map(|frontend| frontend.as_str().to_owned())
55            .collect(),
56        metadata_backend: backend.backend_name().to_owned(),
57        object_backend: backend.object_backend_name().to_owned(),
58        cache_backend: reconstruction_cache.backend_name().to_owned(),
59        auth_enabled,
60        provider_tokens_enabled,
61    })
62}
63
64#[cfg(test)]
65mod tests {
66    use std::{
67        net::{IpAddr, Ipv4Addr, SocketAddr},
68        num::NonZeroUsize,
69    };
70
71    use super::run_config_check;
72    use crate::{ServerConfig, ServerConfigError, ServerError, ServerRole};
73
74    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
75    async fn config_check_reports_local_backend_for_initialized_storage() {
76        let storage = shardline_test_support::TempStorage::new();
77        let config = ServerConfig::new(
78            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080),
79            "http://127.0.0.1:8080".to_owned(),
80            storage.path_buf(),
81            NonZeroUsize::MIN,
82        )
83        .with_token_signing_key(b"test-signing-key-32-bytes-long!!".to_vec())
84        .unwrap();
85
86        let report = run_config_check(config).await.unwrap();
87
88        assert_eq!(report.status, "ok");
89        assert_eq!(report.server_role, "all");
90        assert_eq!(report.server_frontends, vec!["xet".to_owned()]);
91        assert_eq!(report.metadata_backend, "local");
92        assert_eq!(report.object_backend, "local");
93        assert_eq!(report.cache_backend, "memory");
94        assert!(report.auth_enabled);
95        assert!(!report.provider_tokens_enabled);
96    }
97
98    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
99    async fn transfer_role_config_check_disables_reconstruction_cache() {
100        let storage = shardline_test_support::TempStorage::new();
101        let config = ServerConfig::new(
102            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080),
103            "http://127.0.0.1:8080".to_owned(),
104            storage.path_buf(),
105            NonZeroUsize::MIN,
106        )
107        .with_server_role(ServerRole::Transfer)
108        .with_token_signing_key(b"test-signing-key-32-bytes-long!!".to_vec())
109        .unwrap();
110
111        let report = run_config_check(config).await.unwrap();
112
113        assert_eq!(report.server_role, "transfer");
114        assert_eq!(report.server_frontends, vec!["xet".to_owned()]);
115        assert_eq!(report.cache_backend, "disabled");
116        assert!(report.auth_enabled);
117        assert!(!report.provider_tokens_enabled);
118    }
119
120    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
121    async fn config_check_rejects_missing_signing_key_for_served_routes() {
122        let storage = shardline_test_support::TempStorage::new();
123        let config = ServerConfig::new(
124            SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8080),
125            "http://127.0.0.1:8080".to_owned(),
126            storage.path_buf(),
127            NonZeroUsize::MIN,
128        );
129
130        let report = run_config_check(config).await;
131
132        assert!(matches!(
133            report,
134            Err(ServerError::Config(
135                ServerConfigError::MissingTokenSigningKeyForServedRoutes
136            ))
137        ));
138    }
139}