Skip to content

Commit

Permalink
feat(server): Use global config from file if provided (#2458)
Browse files Browse the repository at this point in the history
Provides a way to load global config statically from a file.

* If the relay mode is in managed mode, this file will be ignored as we
want in that case to fetch from upstream.
* If it's not in managed mode, it will attempt the load the file, if it
exists it will serve it, if it doesn't exist, it will serve the default
config. If it exists but we fail to open/parse it, an error will be
logged and we will serve the default.

The location of the static config will be in the same folder as
`config.yml` and `credentials.json`.

---------

Co-authored-by: Iker Barriocanal <32816711+iker-barriocanal@users.noreply.github.com>
  • Loading branch information
TBS1996 and iker-barriocanal committed Sep 12, 2023
1 parent 153ae40 commit 5cb3b09
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 22 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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))
Expand Down
6 changes: 4 additions & 2 deletions relay-config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Expand Down
21 changes: 21 additions & 0 deletions relay-dynamic-config/src/global.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand All @@ -13,6 +17,23 @@ pub struct GlobalConfig {
pub measurements: Option<MeasurementsConfig>,
}

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<Option<Self>> {
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 {

Expand Down
5 changes: 3 additions & 2 deletions relay-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
102 changes: 85 additions & 17 deletions relay-server/src/actors/global_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -151,8 +152,9 @@ pub struct GlobalConfigService {
impl GlobalConfigService {
/// Creates a new [`GlobalConfigService`].
pub fn new(config: Arc<Config>, upstream: Addr<UpstreamRelay>) -> 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,
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -240,6 +243,7 @@ impl GlobalConfigService {
),
}

// Enable upstream requests timer for global configs.
self.schedule_fetch();
}

Expand All @@ -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(),
Expand All @@ -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;

Expand All @@ -313,14 +328,67 @@ mod tests {
});

Controller::start(Duration::from_secs(1));
let config = Arc::<Config>::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;
}
}

0 comments on commit 5cb3b09

Please sign in to comment.