diff --git a/CHANGELOG.md b/CHANGELOG.md index fa03d1a3db..d5b17373dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,6 @@ **Features**: -- Add `MeasurementsConfig` to `GlobalConfig` and implement merging logic with project config. ([#2415](https://github.com/getsentry/relay/pull/2415)) - Add `view_names` to `AppContext` ([#2344](https://github.com/getsentry/relay/pull/2344)) - Tag keys in error events and transaction events can now be up to `200` ASCII characters long. Before, tag keys were limited to 32 characters. ([#2453](https://github.com/getsentry/relay/pull/2453)) - The Crons monitor check-in APIs have learned to accept JSON via POST. This allows for monitor upserts by specifying the `monitor_config` in the JSON body. ([#2448](https://github.com/getsentry/relay/pull/2448)) @@ -20,6 +19,8 @@ **Internal**: +- Use static global configuration if file is provided and not in managed mode. ([#2458](https://github.com/getsentry/relay/pull/2458)) +- Add `MeasurementsConfig` to `GlobalConfig` and implement merging logic with project config. ([#2415](https://github.com/getsentry/relay/pull/2415)) - Support ingestion of custom metrics when the `organizations:custom-metrics` feature flag is enabled. ([#2443](https://github.com/getsentry/relay/pull/2443)) - Merge span metrics and standalone spans extraction options. ([#2447](https://github.com/getsentry/relay/pull/2447)) - Support parsing aggregated metric buckets directly from statsd payloads. ([#2468](https://github.com/getsentry/relay/pull/2468), [#2472](https://github.com/getsentry/relay/pull/2472)) diff --git a/relay-config/src/config.rs b/relay-config/src/config.rs index 11bfa72cf2..2d8d70a92b 100644 --- a/relay-config/src/config.rs +++ b/relay-config/src/config.rs @@ -1458,9 +1458,11 @@ impl Config { /// Regenerates the relay credentials. /// /// This also writes the credentials back to the file. - pub fn regenerate_credentials(&mut self) -> anyhow::Result<()> { + pub fn regenerate_credentials(&mut self, save: bool) -> anyhow::Result<()> { let creds = Credentials::generate(); - creds.save(&self.path)?; + if save { + creds.save(&self.path)?; + } self.credentials = Some(creds); Ok(()) } diff --git a/relay-dynamic-config/src/global.rs b/relay-dynamic-config/src/global.rs index d0f7425f58..dd43cb8f26 100644 --- a/relay-dynamic-config/src/global.rs +++ b/relay-dynamic-config/src/global.rs @@ -1,3 +1,7 @@ +use std::fs::File; +use std::io::BufReader; +use std::path::Path; + use relay_event_normalization::MeasurementsConfig; use serde::{Deserialize, Serialize}; @@ -13,6 +17,23 @@ pub struct GlobalConfig { pub measurements: Option, } +impl GlobalConfig { + /// Loads the [`GlobalConfig`] from a file if it's provided. + /// + /// The folder_path argument should be the path to the folder where the relay config and + /// credentials are stored. + pub fn load(folder_path: &Path) -> anyhow::Result> { + let path = folder_path.join("global_config.json"); + + if path.exists() { + let file = BufReader::new(File::open(path)?); + Ok(Some(serde_json::from_reader(file)?)) + } else { + Ok(None) + } + } +} + #[cfg(test)] mod tests { diff --git a/relay-server/Cargo.toml b/relay-server/Cargo.toml index 7de975845f..69276ddf17 100644 --- a/relay-server/Cargo.toml +++ b/relay-server/Cargo.toml @@ -11,12 +11,13 @@ license-file = "../LICENSE" publish = false [features] +default = [] dashboard = [ "axum/ws", "dep:rust-embed", - "dep:mime_guess" + "dep:mime_guess", + "relay-log/dashboard", ] -default = [] processing = [ "dep:minidump", "dep:symbolic-common", diff --git a/relay-server/src/actors/global_config.rs b/relay-server/src/actors/global_config.rs index e7ce1d3939..b0c9b3caca 100644 --- a/relay-server/src/actors/global_config.rs +++ b/relay-server/src/actors/global_config.rs @@ -13,6 +13,7 @@ use std::borrow::Cow; use std::sync::Arc; use relay_config::Config; +use relay_config::RelayMode; use relay_dynamic_config::GlobalConfig; use relay_statsd::metric; use relay_system::{Addr, AsyncResponse, Controller, FromMessage, Interface, Service}; @@ -151,8 +152,9 @@ pub struct GlobalConfigService { impl GlobalConfigService { /// Creates a new [`GlobalConfigService`]. pub fn new(config: Arc, upstream: Addr) -> Self { - let (global_config_watch, _) = watch::channel(Arc::default()); let (internal_tx, internal_rx) = mpsc::channel(1); + let (global_config_watch, _) = watch::channel(Arc::default()); + Self { config, global_config_watch, @@ -188,7 +190,8 @@ impl GlobalConfigService { /// /// We check if we have credentials before sending, /// otherwise we would log an [`UpstreamRequestError::NoCredentials`] error. - fn update_global_config(&mut self) { + fn request_global_config(&mut self) { + // Disable upstream requests timer until we receive result of query. self.fetch_handle.reset(); let upstream_relay = self.upstream.clone(); @@ -240,6 +243,7 @@ impl GlobalConfigService { ), } + // Enable upstream requests timer for global configs. self.schedule_fetch(); } @@ -257,23 +261,34 @@ impl Service for GlobalConfigService { let mut shutdown_handle = Controller::shutdown_handle(); relay_log::info!("global config service starting"); - - if self.config.has_credentials() { - // NOTE(iker): if this first request fails it's possible the default - // global config is forwarded. This is not ideal, but we accept it - // for now. - self.update_global_config(); + if self.config.relay_mode() == RelayMode::Managed { + relay_log::info!("serving global configs fetched from upstream"); + self.request_global_config(); } else { - // NOTE(iker): not making a request results in the sleep handler - // not being reset, so no new requests are made. - relay_log::info!("fetching global configs disabled: no credentials configured"); - } + match GlobalConfig::load(self.config.path()) { + Ok(Some(from_file)) => { + relay_log::info!("serving static global config loaded from file"); + self.global_config_watch.send(Arc::new(from_file)).ok(); + } + Ok(None) => { + relay_log::info!( + "serving default global configs due to lacking static global config file" + ); + } + Err(e) => { + relay_log::error!("failed to load global config from file: {}", e); + relay_log::info!( + "serving default global configs due to failure to load global config from file" + ); + } + } + }; loop { tokio::select! { biased; - () = &mut self.fetch_handle => self.update_global_config(), + () = &mut self.fetch_handle => self.request_global_config(), Some(result) = self.internal_rx.recv() => self.handle_result(result), Some(message) = rx.recv() => self.handle_message(message), _ = shutdown_handle.notified() => self.handle_shutdown(), @@ -291,7 +306,7 @@ mod tests { use std::sync::Arc; use std::time::Duration; - use relay_config::Config; + use relay_config::{Config, RelayMode}; use relay_system::{Controller, Service, ShutdownMode}; use relay_test::mock_service; @@ -313,14 +328,67 @@ mod tests { }); Controller::start(Duration::from_secs(1)); - let config = Arc::::default(); - let service = GlobalConfigService::new(config.clone(), upstream).start(); + let mut config = Config::default(); + config.regenerate_credentials(false).unwrap(); + let fetch_interval = config.global_config_fetch_interval(); + + let service = GlobalConfigService::new(Arc::new(config), upstream).start(); assert!(service.send(Get).await.is_ok()); Controller::shutdown(ShutdownMode::Immediate); - tokio::time::sleep(config.global_config_fetch_interval() * 2).await; + tokio::time::sleep(fetch_interval * 2).await; assert!(service.send(Get).await.is_ok()); } + + #[tokio::test] + #[should_panic] + async fn managed_relay_makes_upstream_request() { + relay_test::setup(); + tokio::time::pause(); + + let (upstream, handle) = mock_service("upstream", (), |(), _| { + panic!(); + }); + + let mut config = Config::from_json_value(serde_json::json!({ + "relay": { + "mode": RelayMode::Managed + } + })) + .unwrap(); + config.regenerate_credentials(false).unwrap(); + + let fetch_interval = config.global_config_fetch_interval(); + let service = GlobalConfigService::new(Arc::new(config), upstream).start(); + service.send(Get).await.unwrap(); + + tokio::time::sleep(fetch_interval * 2).await; + handle.await.unwrap(); + } + + #[tokio::test] + async fn proxy_relay_does_not_make_upstream_request() { + relay_test::setup(); + tokio::time::pause(); + + let (upstream, _) = mock_service("upstream", (), |(), _| { + panic!("upstream should not be called outside of managed mode"); + }); + + let config = Config::from_json_value(serde_json::json!({ + "relay": { + "mode": RelayMode::Proxy + } + })) + .unwrap(); + + let fetch_interval = config.global_config_fetch_interval(); + + let service = GlobalConfigService::new(Arc::new(config), upstream).start(); + service.send(Get).await.unwrap(); + + tokio::time::sleep(fetch_interval * 2).await; + } }