Skip to content

Commit 58888cc

Browse files
authored
Test oximeter metrics end to end (#855)
Other tests in Propolis validate that the Oximeter translation of kstats for a VM are coherent but do so from stubbed kstat readings. Those tests are fine (and quick!), but don't cover the risk of a translation error in Propolis reading from a real system, nor the possibility of reading kstats incorrectly in the first place. ... good news: test doesn't catch anything interesting! The bug I was looking at is somewhere else.
1 parent 5b73261 commit 58888cc

File tree

15 files changed

+851
-67
lines changed

15 files changed

+851
-67
lines changed

Cargo.lock

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

phd-tests/framework/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ bhyve_api.workspace = true
1616
camino = { workspace = true, features = ["serde1"] }
1717
cfg-if.workspace = true
1818
cpuid_utils.workspace = true
19+
dropshot.workspace = true
1920
errno.workspace = true
2021
fatfs.workspace = true
2122
futures.workspace = true
2223
flate2.workspace = true
2324
hex.workspace = true
2425
libc.workspace = true
2526
newtype_derive.workspace = true
27+
omicron-common.workspace = true
28+
oximeter.workspace = true
2629
propolis-client.workspace = true
2730
reqwest = { workspace = true, features = ["blocking"] }
2831
ring.workspace = true
@@ -31,6 +34,7 @@ serde_derive.workspace = true
3134
serde_json.workspace = true
3235
slog.workspace = true
3336
slog-async.workspace = true
37+
slog-bunyan.workspace = true
3438
slog-term.workspace = true
3539
tar.workspace = true
3640
termwiz.workspace = true

phd-tests/framework/src/disk/crucible.rs

+10-7
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ use tracing::{error, info};
2121
use uuid::Uuid;
2222

2323
use super::BlockSize;
24-
use crate::{
25-
disk::DeviceName, guest_os::GuestOsKind, server_log_mode::ServerLogMode,
26-
};
24+
use crate::{disk::DeviceName, guest_os::GuestOsKind, log_config::LogConfig};
2725

2826
/// An RAII wrapper around a directory containing Crucible data files. Deletes
2927
/// the directory and its contents when dropped.
@@ -93,7 +91,7 @@ impl CrucibleDisk {
9391
data_dir_root: &impl AsRef<Path>,
9492
read_only_parent: Option<&impl AsRef<Path>>,
9593
guest_os: Option<GuestOsKind>,
96-
log_mode: ServerLogMode,
94+
log_config: LogConfig,
9795
) -> anyhow::Result<Self> {
9896
Ok(Self {
9997
device_name,
@@ -106,7 +104,7 @@ impl CrucibleDisk {
106104
downstairs_ports,
107105
data_dir_root,
108106
read_only_parent,
109-
log_mode,
107+
log_config,
110108
)?),
111109
})
112110
}
@@ -210,7 +208,7 @@ impl Inner {
210208
downstairs_ports: &[u16],
211209
data_dir_root: &impl AsRef<Path>,
212210
read_only_parent: Option<&impl AsRef<Path>>,
213-
log_mode: ServerLogMode,
211+
log_config: LogConfig,
214212
) -> anyhow::Result<Self> {
215213
// To create a region, Crucible requires a block size, an extent size
216214
// given as a number of blocks, and an extent count. Compute the latter
@@ -335,7 +333,12 @@ impl Inner {
335333
dir_arg.as_ref(),
336334
];
337335

338-
let (stdout, stderr) = log_mode.get_handles(
336+
// NOTE: `log_format` is ignored here because Crucible determines
337+
// Bunyan or plain formatting based on `atty::is()`. In practice
338+
// this is fine, and matches what we want right now, but it might be
339+
// nice to connect this more directly to the output desire expressed
340+
// by the test runner.
341+
let (stdout, stderr) = log_config.output_mode.get_handles(
339342
data_dir_root,
340343
&format!("crucible_{}_{}", disk_uuid, port),
341344
)?;

phd-tests/framework/src/disk/mod.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ use thiserror::Error;
1919
use crate::{
2020
artifacts::ArtifactStore,
2121
guest_os::GuestOsKind,
22+
log_config::LogConfig,
2223
port_allocator::{PortAllocator, PortAllocatorError},
23-
server_log_mode::ServerLogMode,
2424
};
2525

2626
use self::{crucible::CrucibleDisk, file::FileBackedDisk};
@@ -190,7 +190,7 @@ pub(crate) struct DiskFactory {
190190
port_allocator: Arc<PortAllocator>,
191191

192192
/// The logging discipline to use for Crucible server processes.
193-
log_mode: ServerLogMode,
193+
log_config: LogConfig,
194194
}
195195

196196
impl DiskFactory {
@@ -201,13 +201,13 @@ impl DiskFactory {
201201
storage_dir: &impl AsRef<Utf8Path>,
202202
artifact_store: Arc<ArtifactStore>,
203203
port_allocator: Arc<PortAllocator>,
204-
log_mode: ServerLogMode,
204+
log_config: LogConfig,
205205
) -> Self {
206206
Self {
207207
storage_dir: storage_dir.as_ref().to_path_buf(),
208208
artifact_store,
209209
port_allocator,
210-
log_mode,
210+
log_config,
211211
}
212212
}
213213
}
@@ -325,7 +325,7 @@ impl DiskFactory {
325325
&self.storage_dir,
326326
artifact_path.as_ref(),
327327
guest_os,
328-
self.log_mode,
328+
self.log_config,
329329
)
330330
.map(Arc::new)
331331
.map_err(Into::into)

phd-tests/framework/src/lib.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ use camino::Utf8PathBuf;
3636
use disk::DiskFactory;
3737
use futures::{stream::FuturesUnordered, StreamExt};
3838
use guest_os::GuestOsKind;
39+
use log_config::LogConfig;
3940
use port_allocator::PortAllocator;
40-
use server_log_mode::ServerLogMode;
4141
pub use test_vm::TestVm;
4242
use test_vm::{
4343
environment::EnvironmentSpec, spec::VmSpec, VmConfig, VmLocation,
@@ -52,16 +52,16 @@ pub mod disk;
5252
pub mod guest_os;
5353
pub mod host_api;
5454
pub mod lifecycle;
55+
pub mod log_config;
5556
mod port_allocator;
5657
mod serial;
57-
pub mod server_log_mode;
5858
pub mod test_vm;
5959
pub(crate) mod zfs;
6060

6161
/// An instance of the PHD test framework.
6262
pub struct Framework {
6363
pub(crate) tmp_directory: Utf8PathBuf,
64-
pub(crate) server_log_mode: ServerLogMode,
64+
pub(crate) log_config: LogConfig,
6565

6666
pub(crate) default_guest_cpus: u8,
6767
pub(crate) default_guest_memory_mib: u64,
@@ -99,7 +99,7 @@ pub struct FrameworkParameters<'a> {
9999
pub tmp_directory: Utf8PathBuf,
100100
pub artifact_directory: Utf8PathBuf,
101101
pub artifact_toml: Utf8PathBuf,
102-
pub server_log_mode: ServerLogMode,
102+
pub log_config: LogConfig,
103103

104104
pub default_guest_cpus: u8,
105105
pub default_guest_memory_mib: u64,
@@ -187,14 +187,14 @@ impl Framework {
187187
&params.tmp_directory,
188188
artifact_store.clone(),
189189
port_allocator.clone(),
190-
params.server_log_mode,
190+
params.log_config,
191191
);
192192

193193
let (cleanup_task_tx, cleanup_task_rx) =
194194
tokio::sync::mpsc::unbounded_channel();
195195
Ok(Self {
196196
tmp_directory: params.tmp_directory,
197-
server_log_mode: params.server_log_mode,
197+
log_config: params.log_config,
198198
default_guest_cpus: params.default_guest_cpus,
199199
default_guest_memory_mib: params.default_guest_memory_mib,
200200
default_guest_os_artifact: params.default_guest_os_artifact,

phd-tests/framework/src/server_log_mode.rs phd-tests/framework/src/log_config.rs

+30-12
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,23 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
//! Types and helpers specifying where a server process's stdout/stderr should
6-
//! be recorded.
5+
//! Types and helpers specifying how logs should be formatted and where they
6+
//! should be directed.
77
88
use std::{path::Path, process::Stdio, str::FromStr};
99

1010
use tracing::info;
1111

12-
/// Specifies where a process's output should be written.
12+
/// Specifies how a test's logging should be managed.
1313
#[derive(Debug, Clone, Copy)]
14-
pub enum ServerLogMode {
14+
pub struct LogConfig {
15+
pub output_mode: OutputMode,
16+
pub log_format: LogFormat,
17+
}
18+
19+
/// Specifies where a output for a test's processes should be written.
20+
#[derive(Debug, Clone, Copy)]
21+
pub enum OutputMode {
1522
/// Write to files in the server's factory's temporary directory.
1623
TmpFile,
1724

@@ -22,14 +29,14 @@ pub enum ServerLogMode {
2229
Null,
2330
}
2431

25-
impl FromStr for ServerLogMode {
32+
impl FromStr for OutputMode {
2633
type Err = std::io::Error;
2734

2835
fn from_str(s: &str) -> Result<Self, Self::Err> {
2936
match s.to_lowercase().as_str() {
30-
"file" | "tmpfile" => Ok(ServerLogMode::TmpFile),
31-
"stdio" => Ok(ServerLogMode::Stdio),
32-
"null" => Ok(ServerLogMode::Null),
37+
"file" | "tmpfile" => Ok(OutputMode::TmpFile),
38+
"stdio" => Ok(OutputMode::Stdio),
39+
"null" => Ok(OutputMode::Null),
3340
_ => Err(std::io::Error::new(
3441
std::io::ErrorKind::InvalidData,
3542
s.to_string(),
@@ -38,7 +45,7 @@ impl FromStr for ServerLogMode {
3845
}
3946
}
4047

41-
impl ServerLogMode {
48+
impl OutputMode {
4249
/// Returns the stdout/stderr handles to pass to processes using the
4350
/// specified logging mode.
4451
///
@@ -54,7 +61,7 @@ impl ServerLogMode {
5461
file_prefix: &str,
5562
) -> anyhow::Result<(Stdio, Stdio)> {
5663
match self {
57-
ServerLogMode::TmpFile => {
64+
OutputMode::TmpFile => {
5865
let mut stdout_path = directory.as_ref().to_path_buf();
5966
stdout_path.push(format!("{}.stdout.log", file_prefix));
6067

@@ -67,8 +74,19 @@ impl ServerLogMode {
6774
std::fs::File::create(stderr_path)?.into(),
6875
))
6976
}
70-
ServerLogMode::Stdio => Ok((Stdio::inherit(), Stdio::inherit())),
71-
ServerLogMode::Null => Ok((Stdio::null(), Stdio::null())),
77+
OutputMode::Stdio => Ok((Stdio::inherit(), Stdio::inherit())),
78+
OutputMode::Null => Ok((Stdio::null(), Stdio::null())),
7279
}
7380
}
7481
}
82+
83+
/// Specifies how output for a test's processes should be structured.
84+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85+
pub enum LogFormat {
86+
/// Format logs as plain hopefully human-readable output.
87+
Plain,
88+
89+
/// Format logs as Bunyan output, more suitable for machine processing (such
90+
/// as in CI).
91+
Bunyan,
92+
}

phd-tests/framework/src/test_vm/environment.rs

+36-2
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,31 @@ pub enum VmLocation {
1616
// TODO: Support remote VMs.
1717
}
1818

19+
/// Specifies where test VMs should report metrics to, if anywhere.
20+
#[derive(Clone, Copy, Debug)]
21+
pub enum MetricsLocation {
22+
/// Oximeter metrics should be reported to a server colocated with the test
23+
/// VM to be started.
24+
Local,
25+
// When the time comes to support remote VMs, it will presumably be useful
26+
// to have local and (perhaps multiple) remote VMs report metrics to the
27+
// same server. But we don't support remote VMs yet.
28+
}
29+
1930
#[derive(Clone, Debug)]
2031
pub struct EnvironmentSpec {
2132
pub(crate) location: VmLocation,
2233
pub(crate) propolis_artifact: String,
34+
pub(crate) metrics: Option<MetricsLocation>,
2335
}
2436

2537
impl EnvironmentSpec {
2638
pub(crate) fn new(location: VmLocation, propolis_artifact: &str) -> Self {
27-
Self { location, propolis_artifact: propolis_artifact.to_owned() }
39+
Self {
40+
location,
41+
propolis_artifact: propolis_artifact.to_owned(),
42+
metrics: None,
43+
}
2844
}
2945

3046
pub fn location(&mut self, location: VmLocation) -> &mut Self {
@@ -37,6 +53,11 @@ impl EnvironmentSpec {
3753
self
3854
}
3955

56+
pub fn metrics(&mut self, metrics: Option<MetricsLocation>) -> &mut Self {
57+
self.metrics = metrics;
58+
self
59+
}
60+
4061
pub(crate) async fn build<'a>(
4162
&self,
4263
framework: &'a Framework,
@@ -75,18 +96,31 @@ impl<'a> Environment<'a> {
7596
.port_allocator
7697
.next()
7798
.context("getting VNC server port")?;
99+
let metrics_addr = builder.metrics.and_then(|m| match m {
100+
MetricsLocation::Local => {
101+
// If the test requests metrics are local, we'll start
102+
// an Oximeter stand-in for this VM when setting up this
103+
// environment later. `start_local_vm` will patch in the
104+
// actual server address when it has created one.
105+
//
106+
// If the VM is to be started remotely but requests
107+
// "Local" metrics, that's probably an error.
108+
None
109+
}
110+
});
78111
let params = ServerProcessParameters {
79112
server_path: propolis_server,
80113
data_dir: framework.tmp_directory.as_path(),
81114
server_addr: SocketAddrV4::new(
82115
Ipv4Addr::new(127, 0, 0, 1),
83116
server_port,
84117
),
118+
metrics_addr,
85119
vnc_addr: SocketAddrV4::new(
86120
Ipv4Addr::new(127, 0, 0, 1),
87121
vnc_port,
88122
),
89-
log_mode: framework.server_log_mode,
123+
log_config: framework.log_config,
90124
};
91125
Ok(Self::Local(params))
92126
}

0 commit comments

Comments
 (0)