Skip to content

Commit

Permalink
feat(monitoring): two config representation types (#1168)
Browse files Browse the repository at this point in the history
  • Loading branch information
yoavGrs authored Sep 14, 2023
1 parent d07d86b commit b758224
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:

- name: Run executable
run: >
target/release/papyrus_node --base_layer.node_url ${{ secrets.CI_BASE_LAYER_NODE_URL }}
target/release/papyrus_node --base_layer.node_url ${{ secrets.CI_BASE_LAYER_NODE_URL }} --monitoring_gateway.config_representation_secret qwerty
& sleep 30 ; kill $!
test:
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions config/default_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@
"pointer_target": "collect_metrics",
"privacy": "Public"
},
"monitoring_gateway.config_representation_secret": {
"description": "A secret for representing the full general config.",
"privacy": "Private",
"required_type": "String"
},
"monitoring_gateway.server_address": {
"description": "node's monitoring server.",
"privacy": "Public",
Expand Down
1 change: 1 addition & 0 deletions crates/papyrus_monitoring_gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ serde_json = { workspace = true, features = ["arbitrary_precision"]}
thiserror.workspace = true
tokio = { workspace = true, features = ["full", "sync"] }
tracing.workspace = true
validator = { workspace = true, features = ["derive"] }

[dev-dependencies]
http-body = { version = "0.4.5" }
Expand Down
42 changes: 35 additions & 7 deletions crates/papyrus_monitoring_gateway/src/gateway_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ use tower::ServiceExt;

use crate::{app, MONITORING_PREFIX};

const TEST_CONFIG_REPRESENTATION: &str = "general_config_representation";
const TEST_CONFIG_REPRESENTATION: &str = "full_general_config_representation";
const PUBLIC_TEST_CONFIG_REPRESENTATION: &str = "public_general_config_representation";
const SECRET: &str = "abcd";
const TEST_VERSION: &str = "1.2.3-dev";

// TODO(dan): consider using a proper fixture.
Expand All @@ -24,6 +26,8 @@ fn setup_app() -> Router {
storage_reader,
TEST_VERSION,
serde_json::to_value(TEST_CONFIG_REPRESENTATION).unwrap(),
serde_json::to_value(PUBLIC_TEST_CONFIG_REPRESENTATION).unwrap(),
SECRET.to_string(),
None,
)
}
Expand Down Expand Up @@ -67,16 +71,34 @@ async fn version() {
assert_eq!(&body[..], TEST_VERSION.as_bytes());
}

#[tokio::test]
async fn node_config() {
async fn validate_response(request: &str, expected_response: &str) {
let app = setup_app();
let response = request_app(app, "nodeConfig").await;
let response = request_app(app, request).await;

assert_eq!(response.status(), StatusCode::OK);

let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
let body: Value = serde_json::from_slice(&body).unwrap();
assert_eq!(body, json!(TEST_CONFIG_REPRESENTATION));
assert_eq!(body, json!(expected_response));
}

#[tokio::test]
async fn public_node_config() {
validate_response("nodeConfig", PUBLIC_TEST_CONFIG_REPRESENTATION).await;
}

#[tokio::test]
async fn node_config_valid_secret() {
validate_response(format!("nodeConfigFull/{SECRET}").as_str(), TEST_CONFIG_REPRESENTATION)
.await;
}

#[tokio::test]
async fn node_config_invalid_secret() {
let app = setup_app();
let response = request_app(app, "nodeConfigFull/zzz".to_string().as_str()).await;

assert_eq!(response.status(), StatusCode::FORBIDDEN);
}

#[tokio::test]
Expand All @@ -102,8 +124,14 @@ async fn with_metrics() {
// Creates an app with prometheus handle.
let ((storage_reader, _), _temp_dir) = test_utils::get_test_storage();
let prometheus_handle = PrometheusBuilder::new().install_recorder().unwrap();
let app =
app(storage_reader, TEST_VERSION, serde_json::Value::default(), Some(prometheus_handle));
let app = app(
storage_reader,
TEST_VERSION,
serde_json::Value::default(),
serde_json::Value::default(),
String::new(),
Some(prometheus_handle),
);

// Register a metric.
let metric_name = "metric_name";
Expand Down
71 changes: 59 additions & 12 deletions crates/papyrus_monitoring_gateway/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,36 @@ use std::fmt::Display;
use std::net::SocketAddr;
use std::str::FromStr;

use axum::extract::Path;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::routing::get;
use axum::{Json, Router};
use metrics_exporter_prometheus::{BuildError, PrometheusBuilder, PrometheusHandle};
use metrics_process::Collector;
use papyrus_config::dumping::{ser_param, SerializeConfig};
use papyrus_config::{ParamPath, ParamPrivacyInput, SerializedParam};
use papyrus_config::dumping::{ser_param, ser_required_param, SerializeConfig};
use papyrus_config::{ParamPath, ParamPrivacyInput, SerializationType, SerializedParam};
use papyrus_storage::{DbTablesStats, StorageError, StorageReader};
use serde::{Deserialize, Serialize};
use tracing::{debug, instrument};
use validator::Validate;

const MONITORING_PREFIX: &str = "monitoring";

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Validate)]
pub struct MonitoringGatewayConfig {
pub server_address: String,
pub collect_metrics: bool,
#[validate(length(min = 1))]
pub config_representation_secret: String,
}

impl Default for MonitoringGatewayConfig {
fn default() -> Self {
MonitoringGatewayConfig {
server_address: String::from("0.0.0.0:8081"),
collect_metrics: false,
config_representation_secret: String::from("qwerty"),
}
}
}
Expand All @@ -54,6 +59,12 @@ impl SerializeConfig for MonitoringGatewayConfig {
"If true, collect and return metrics in the monitoring gateway.",
ParamPrivacyInput::Public,
),
ser_required_param(
"config_representation_secret",
SerializationType::String,
"A secret for representing the full general config.",
ParamPrivacyInput::Private,
),
])
}
}
Expand All @@ -67,7 +78,10 @@ impl Display for MonitoringGatewayConfig {

pub struct MonitoringServer {
config: MonitoringGatewayConfig,
general_config_representation: serde_json::Value,
// Nested Json representation of all the parameters in the node config.
full_general_config_representation: serde_json::Value,
// Nested Json representation of the public parameters in the node config.
public_general_config_representation: serde_json::Value,
storage_reader: StorageReader,
version: &'static str,
prometheus_handle: Option<PrometheusHandle>,
Expand All @@ -76,7 +90,8 @@ pub struct MonitoringServer {
impl MonitoringServer {
pub fn new(
config: MonitoringGatewayConfig,
general_config_representation: serde_json::Value,
full_general_config_representation: serde_json::Value,
public_general_config_representation: serde_json::Value,
storage_reader: StorageReader,
version: &'static str,
) -> Result<Self, BuildError> {
Expand All @@ -88,7 +103,8 @@ impl MonitoringServer {
Ok(MonitoringServer {
config,
storage_reader,
general_config_representation,
full_general_config_representation,
public_general_config_representation,
version,
prometheus_handle,
})
Expand All @@ -104,15 +120,19 @@ impl MonitoringServer {
fields(
version = %self.version,
config = %self.config,
general_config_representation = %self.general_config_representation),
full_general_config_representation = %self.full_general_config_representation,
public_general_config_representation = %self.public_general_config_representation,
config_representation_secret = %self.config.config_representation_secret),
level = "debug")]
async fn run_server(&self) -> std::result::Result<(), hyper::Error> {
let server_address = SocketAddr::from_str(&self.config.server_address)
.expect("Configuration value for monitor server address should be valid");
let app = app(
self.storage_reader.clone(),
self.version,
self.general_config_representation.clone(),
self.full_general_config_representation.clone(),
self.public_general_config_representation.clone(),
self.config.config_representation_secret.clone(),
self.prometheus_handle.clone(),
);
debug!("Starting monitoring gateway.");
Expand All @@ -123,7 +143,9 @@ impl MonitoringServer {
fn app(
storage_reader: StorageReader,
version: &'static str,
general_config_representation: serde_json::Value,
full_general_config_representation: serde_json::Value,
public_general_config_representation: serde_json::Value,
config_representation_secret: String,
prometheus_handle: Option<PrometheusHandle>,
) -> Router {
Router::new()
Expand All @@ -133,7 +155,18 @@ fn app(
)
.route(
format!("/{MONITORING_PREFIX}/nodeConfig").as_str(),
get(move || node_config(general_config_representation)),
get(move || node_config(public_general_config_representation)),
)
.route(
// The "*secret" captures the end of the path and stores it in "secret".
format!("/{MONITORING_PREFIX}/nodeConfigFull/*secret").as_str(),
get(move |secret| {
node_config_by_secret(
full_general_config_representation,
secret,
config_representation_secret,
)
}),
)
.route(
format!("/{MONITORING_PREFIX}/nodeVersion").as_str(),
Expand All @@ -160,9 +193,23 @@ async fn db_tables_stats(
/// Returns the node config.
#[instrument(level = "debug", ret)]
async fn node_config(
general_config_representation: serde_json::Value,
full_general_config_representation: serde_json::Value,
) -> axum::Json<serde_json::Value> {
general_config_representation.into()
full_general_config_representation.into()
}

/// Returns the node config.
#[instrument(level = "debug", ret)]
async fn node_config_by_secret(
full_general_config_representation: serde_json::Value,
given_secret: Path<String>,
expected_secret: String,
) -> Result<axum::Json<serde_json::Value>, StatusCode> {
if given_secret.to_string() == expected_secret {
Ok(node_config(full_general_config_representation).await)
} else {
Err(StatusCode::FORBIDDEN)
}
}

/// Returns prometheus metrics.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ async fn main() {
"--central.url=https://alpha4.starknet.io/".to_owned(),
"--base_layer.node_url=https://mainnet.infura.io/v3/1234".to_owned(),
format!("--storage.db_config.path_prefix={}", path.display()),
"--monitoring_gateway.config_representation_secret=abcd".to_owned(),
])
.expect("Load config");
let (storage_reader, _) = open_storage(config.storage.db_config).expect("Open storage");
Expand Down
33 changes: 30 additions & 3 deletions crates/papyrus_node/src/config/config_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ use std::collections::{BTreeMap, HashMap};
use std::env::{self, args};
use std::fs::File;
use std::io::{BufWriter, Write};
use std::ops::IndexMut;
use std::path::{Path, PathBuf};
use std::str::FromStr;

use itertools::Itertools;
use papyrus_base_layer::ethereum_base_layer_contract::EthereumBaseLayerConfig;
use papyrus_config::dumping::SerializeConfig;
use papyrus_config::SerializedParam;
use papyrus_config::representation::get_config_representation;
use papyrus_config::{SerializedContent, SerializedParam};
use papyrus_monitoring_gateway::MonitoringGatewayConfig;
use pretty_assertions::assert_eq;
use serde_json::{json, Map, Value};
use starknet_api::core::ChainId;
Expand All @@ -17,9 +21,32 @@ use validator::Validate;

use crate::config::{node_command, NodeConfig, DEFAULT_CONFIG_PATH};

// Fill here all the required params in default_config.json with the default value.
// Returns the required params in default_config.json with the default value from the config
// representation.
fn required_args() -> Vec<String> {
vec!["--base_layer.node_url".to_owned(), EthereumBaseLayerConfig::default().node_url]
let default_config = NodeConfig::default();
let mut args = Vec::new();
let mut config_representation = get_config_representation(&default_config, true).unwrap();

for (param_path, serialized_param) in default_config.dump() {
let SerializedContent::RequiredType(serialization_type) = serialized_param.content else {
continue;
};
args.push(format!("--{param_path}"));

let required_param_json_value = param_path
.split('.')
.fold(&mut config_representation, |entry, config_name| entry.index_mut(config_name));

let required_param_string_value = match serialization_type {
papyrus_config::SerializationType::String => {
required_param_json_value.as_str().unwrap().to_string()
}
_ => required_param_json_value.to_string(),
};
args.push(required_param_string_value);
}
args
}

fn get_args(additional_args: Vec<&str>) -> Vec<String> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ expression: dumped_default_config
"value": false,
"privacy": "Public"
},
"monitoring_gateway.config_representation_secret": {
"description": "A secret for representing the full general config.",
"required_type": "String",
"privacy": "Private"
},
"monitoring_gateway.server_address": {
"description": "node's monitoring server.",
"value": "0.0.0.0:8081",
Expand Down
1 change: 1 addition & 0 deletions crates/papyrus_node/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ async fn run_threads(config: NodeConfig) -> anyhow::Result<()> {
let monitoring_server = MonitoringServer::new(
config.monitoring_gateway.clone(),
get_config_representation(&config, true)?,
get_config_representation(&config, false)?,
storage_reader.clone(),
VERSION_FULL,
)?;
Expand Down

0 comments on commit b758224

Please sign in to comment.