From 5f90377a4f3923a0bea481ab88c6ac32323dc738 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Wed, 21 Aug 2024 16:49:27 -0700 Subject: [PATCH] add a simple test that metrics make it to oximeter --- gateway-test-utils/src/setup.rs | 10 ++- nexus/tests/integration_tests/metrics.rs | 96 +++++++++++++++++++++++- 2 files changed, 102 insertions(+), 4 deletions(-) diff --git a/gateway-test-utils/src/setup.rs b/gateway-test-utils/src/setup.rs index 46bc55805aa..6aff29d3de8 100644 --- a/gateway-test-utils/src/setup.rs +++ b/gateway-test-utils/src/setup.rs @@ -8,6 +8,9 @@ use camino::Utf8Path; use dropshot::test_util::ClientTestContext; use dropshot::test_util::LogContext; use gateway_messages::SpPort; +pub use omicron_gateway::metrics::{ + DevConfig as MetricsDevConfig, MetricsConfig, +}; use omicron_gateway::MgsArguments; use omicron_gateway::SpType; use omicron_gateway::SwitchPortConfig; @@ -33,6 +36,7 @@ pub struct GatewayTestContext { pub server: omicron_gateway::Server, pub simrack: SimRack, pub logctx: LogContext, + pub gateway_id: Uuid, } impl GatewayTestContext { @@ -143,8 +147,8 @@ pub async fn test_setup_with_config( // Start gateway server let rack_id = Some(Uuid::parse_str(RACK_UUID).unwrap()); - - let args = MgsArguments { id: Uuid::new_v4(), addresses, rack_id }; + let gateway_id = Uuid::new_v4(); + let args = MgsArguments { id: gateway_id, addresses, rack_id }; let server = omicron_gateway::Server::start( server_config.clone(), args, @@ -206,5 +210,5 @@ pub async fn test_setup_with_config( log.new(o!("component" => "client test context")), ); - GatewayTestContext { client, server, simrack, logctx } + GatewayTestContext { client, server, simrack, logctx, gateway_id } } diff --git a/nexus/tests/integration_tests/metrics.rs b/nexus/tests/integration_tests/metrics.rs index 3b808984ae7..8b44d670484 100644 --- a/nexus/tests/integration_tests/metrics.rs +++ b/nexus/tests/integration_tests/metrics.rs @@ -23,8 +23,10 @@ use nexus_types::external_api::views::OxqlQueryResult; use omicron_test_utils::dev::poll::{wait_for_condition, CondCheckError}; use omicron_uuid_kinds::{GenericUuid, InstanceUuid}; use oximeter::types::Datum; +use oximeter::types::FieldValue; use oximeter::types::Measurement; use oximeter::TimeseriesSchema; +use std::borrow::Borrow; use uuid::Uuid; pub async fn query_for_metrics( @@ -344,7 +346,6 @@ async fn test_instance_watcher_metrics( ); }}; } - use oximeter::types::FieldValue; const INSTANCE_ID_FIELD: &str = "instance_id"; const STATE_FIELD: &str = "state"; const STATE_STARTING: &str = "starting"; @@ -589,6 +590,99 @@ async fn test_instance_watcher_metrics( assert_gte!(ts2_running, 2); } +#[nexus_test] +async fn test_mgs_metrics( + cptestctx: &ControlPlaneTestContext, +) { + // Make a MGS + let mgs = { + let (mut mgs_config, sp_sim_config) = + gateway_test_utils::setup::load_test_config(); + // munge the already-parsed MGS config file to point it at the test + // Nexus' address. + mgs_config.metrics.dev = + Some(gateway_test_utils::setup::MetricsDevConfig { + bind_loopback: true, + nexus_address: Some(cptestctx.internal_client.bind_address), + }); + gateway_test_utils::setup::test_setup_with_config( + "test_mgs_metrics", + gateway_messages::SpPort::One, + mgs_config, + &sp_sim_config, + None, + ) + .await + }; + + // Wait until the MGS registers as a producer with Oximeter. + wait_for_producer(&cptestctx.oximeter, &mgs.gateway_id).await; + cptestctx.oximeter.force_collect().await; + + async fn get_timeseries( + cptestctx: &ControlPlaneTestContext, + name: &str, + ) -> oxql_types::Table { + let table = timeseries_query(&cptestctx, &format!("get {name}")) + .await + .into_iter() + .find(|t| t.name() == name); + match table { + Some(table) => table, + None => panic!("missing table for {name}"), + } + } + + #[track_caller] + fn check_all_serials_present(table: oxql_types::Table) { + let mut sim_gimlet_00 = 0; + let mut sim_gimlet_01 = 0; + for timeseries in table.timeseries() { + let fields = ×eries.fields; + let n_points = timeseries.points.len(); + eprintln!("found timeseries: {fields:?} ({n_points} points)"); + assert!(n_points > 0, "timeseries {fields:?} should have points"); + let serial_str = match timeseries.fields.get("chassis_serial") { + Some(FieldValue::String(s)) => s.borrow(), + Some(x) => panic!( + "`chassis_serial` field should be a string, but got: {x:?}" + ), + None => { + panic!("timeseries should have a `chassis_serial` field") + } + }; + match serial_str { + "SimGimlet00" => sim_gimlet_00 += 1, + "SimGimlet01" => sim_gimlet_01 += 1, + // if someone adds sensor readings to the fake sidecar later, + // that's okay... + _ => eprintln!("bonus simulated chassis serial {serial_str:?}"), + } + } + + assert!( + sim_gimlet_00 > 0, + "expected at least one timeseries from SimGimlet00 in {table:#?}" + ); + assert!( + sim_gimlet_01 > 0, + "expected at least one timeseries from SimGimlet01 in {table:#?}" + ); + } + + let temp_metrics = + get_timeseries(&cptestctx, "hardware_component:temperature").await; + check_all_serials_present(temp_metrics); + + let voltage_metrics = + get_timeseries(&cptestctx, "hardware_component:voltage").await; + check_all_serials_present(voltage_metrics); + + let current_metrics = + get_timeseries(&cptestctx, "hardware_component:current").await; + check_all_serials_present(current_metrics); +} + /// Wait until a producer is registered with Oximeter. /// /// This blocks until the producer is registered, for up to 60s. It panics if