Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
2b1ded8
Squash merge for adding KVP functionality via tracing and repo instru…
peytonr18 Oct 8, 2024
60af377
Refactoring KVP PR to move kvp.rs into main libazureinit crate, combi…
peytonr18 Nov 12, 2024
f119cc5
Update tracing setup for compatibility with OpenTelemetry 0.27.0
peytonr18 Nov 12, 2024
798ab8a
Merging in latest changes from main.
peytonr18 Nov 13, 2024
a90f155
Merging in changes from main
peytonr18 Nov 26, 2024
44e8737
Merge branch 'main' into probertson-kvp-test
peytonr18 Dec 4, 2024
c3dd388
Resolving lifetime declaration of StringVisitor clippy error, along w…
peytonr18 Dec 4, 2024
b96c33f
Add unit test to validate slice value length and replace hardcoded va…
peytonr18 Dec 6, 2024
352d4cd
Refactor module structure and rename tracing.rs to logging.rs in azur…
peytonr18 Dec 6, 2024
c87ac52
Merge main into probertson-kvp-test
peytonr18 Dec 12, 2024
f62d7b9
Merge branch 'main' into probertson-kvp-test
peytonr18 Dec 12, 2024
71cd9ff
Resolving cargofmt issue
peytonr18 Dec 12, 2024
35bd757
Merge branch 'main' into probertson-kvp-test
peytonr18 Dec 17, 2024
8b2c829
Auditing tracing::info! calls in an attempt to clean up KVP file for …
peytonr18 Dec 17, 2024
e14d8ac
Add filter
peytonr18 Dec 18, 2024
0536a3b
Refactor emit_kvp_kayer and otel_layer with improved per-layer filter…
peytonr18 Dec 20, 2024
6257a81
Updating note to reflect that both the key and the value are null-byt…
peytonr18 Jan 13, 2025
9e6820a
Refactor KVP logging to use module-based targets and update filtering…
peytonr18 Jan 30, 2025
88ae27d
Merge remote-tracking branch 'origin/main' into probertson-kvp-test
peytonr18 Jan 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions libazureinit/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ impl Config {
Figment::from(Serialized::defaults(Config::default()));

if base_path.exists() {
tracing::debug!(path=?base_path, "Loading base configuration file");
tracing::info!(path=?base_path, "Loading base configuration file");
figment = figment.merge(Toml::file(base_path));
} else {
tracing::warn!(
Expand All @@ -307,7 +307,7 @@ impl Config {
if cli_path.is_dir() {
figment = Self::merge_toml_directory(figment, cli_path)?;
} else {
tracing::debug!(
tracing::info!(
"Merging configuration file from CLI: {:?}",
cli_path
);
Expand All @@ -318,7 +318,10 @@ impl Config {
figment
.extract::<Config>()
.map(|config| {
tracing::info!("Configuration successfully loaded.");
tracing::info!(
target: "kvp",
"Configuration successfully loaded."
);
config
})
.map_err(|e| {
Expand Down Expand Up @@ -355,12 +358,12 @@ impl Config {
entries.sort();

for path_entry in entries {
tracing::debug!("Merging configuration file: {:?}", path_entry);
tracing::info!("Merging configuration file: {:?}", path_entry);
figment = figment.merge(Toml::file(path_entry));
}
Ok(figment)
} else {
tracing::debug!("Directory {:?} not found, skipping.", dir_path);
tracing::info!("Directory {:?} not found, skipping.", dir_path);
Ok(figment.clone())
}
}
Expand Down
6 changes: 3 additions & 3 deletions libazureinit/src/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ async fn request(
let now = std::time::Instant::now();
let mut attempt = 0_u32;
loop {
let span = tracing::debug_span!("request", attempt, http_status = tracing::field::Empty);
let span = tracing::info_span!("request", attempt, http_status = tracing::field::Empty);
let req = request.try_clone().expect("The request body MUST be clone-able");
match client
.execute(req)
Expand All @@ -121,12 +121,12 @@ async fn request(
let _enter = span.enter();
let statuscode = response.status();
span.record("http_status", statuscode.as_u16());
tracing::info!(url=response.url().as_str(), "HTTP response received");
tracing::info!(target: "kvp", url=response.url().as_str(), "HTTP response received");

match response.error_for_status() {
Ok(response) => {
if statuscode == StatusCode::OK {
tracing::info!("HTTP response succeeded with status {}", statuscode);
tracing::info!(target: "kvp", "HTTP response succeeded with status {}", statuscode);
return Ok((response, retry_for.saturating_sub(now.elapsed() + retry_interval)));
}
},
Expand Down
8 changes: 7 additions & 1 deletion libazureinit/src/provision/ssh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ pub(crate) fn provision_ssh(

chown(&ssh_dir, Some(user.uid), Some(user.gid))?;

tracing::info!("Using authorized_keys path: {:?}", authorized_keys_path);
tracing::info!(
target: "kvp",
"Using authorized_keys path: {:?}",
authorized_keys_path
);

let mut authorized_keys = File::create(&authorized_keys_path)?;
authorized_keys.set_permissions(Permissions::from_mode(0o600))?;
Expand Down Expand Up @@ -139,6 +143,7 @@ fn run_sshd_command(
match sshd_config_command_runner() {
Ok(output) if output.status.success() => {
info!(
target: "kvp",
stdout_length = output.stdout.len(),
"Executed sshd -G successfully",
);
Expand Down Expand Up @@ -184,6 +189,7 @@ fn extract_authorized_keys_file_path(stdout: &[u8]) -> Option<String> {
if line.starts_with("authorizedkeysfile") {
let keypath = line.split_whitespace().nth(1).map(|s| {
info!(
target: "kvp",
authorizedkeysfile = %s,
"Using sshd's authorizedkeysfile path configuration"
);
Expand Down
2 changes: 2 additions & 0 deletions libazureinit/src/provision/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,15 @@ fn user_exists(username: &str) -> Result<bool, Error> {
fn useradd(user: &User) -> Result<(), Error> {
if user_exists(&user.name)? {
tracing::info!(
target: "kvp",
"User '{}' already exists. Skipping user creation.",
user.name
);

let group_list = user.groups.join(",");

tracing::info!(
target: "kvp",
"User '{}' is being added to the following groups: {}",
user.name,
group_list
Expand Down
113 changes: 58 additions & 55 deletions src/kvp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
use std::{
fmt::{self as std_fmt, Write as std_write},
fs::{File, OpenOptions},
io::{self, Error, ErrorKind, Write},
io::{self, ErrorKind, Write},
os::unix::fs::MetadataExt,
path::Path,
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
Expand Down Expand Up @@ -81,10 +81,10 @@ impl Visit for StringVisitor<'_> {
/// * `value` - The debug value associated with the field.
fn record_debug(
&mut self,
_field: &tracing::field::Field,
field: &tracing::field::Field,
value: &dyn std_fmt::Debug,
) {
write!(self.string, "{}={:?}; ", _field.name(), value)
write!(self.string, "{}={:?}; ", field.name(), value)
.expect("Writing to a string should never fail");
}
}
Expand Down Expand Up @@ -134,7 +134,7 @@ impl EmitKVPLayer {
/// # Arguments
/// * `file_path` - The file path where the KVP data will be stored.
///
pub fn new(file_path: std::path::PathBuf) -> Result<Self, std::io::Error> {
pub fn new(file_path: std::path::PathBuf) -> Result<Self, anyhow::Error> {
truncate_guest_pool_file(&file_path)?;

let file = OpenOptions::new()
Expand Down Expand Up @@ -309,21 +309,11 @@ where
let start_time =
end_time.checked_sub(elapsed).unwrap_or(UNIX_EPOCH);

let start_time_dt = DateTime::<Utc>::from(
UNIX_EPOCH
+ start_time
.duration_since(UNIX_EPOCH)
.unwrap_or_default(),
)
.format("%Y-%m-%dT%H:%M:%S%.3fZ");
let start_time_dt = DateTime::<Utc>::from(start_time)
.format("%Y-%m-%dT%H:%M:%S%.3fZ");

let end_time_dt = DateTime::<Utc>::from(
UNIX_EPOCH
+ end_time
.duration_since(UNIX_EPOCH)
.unwrap_or_default(),
)
.format("%Y-%m-%dT%H:%M:%S%.3fZ");
let end_time_dt = DateTime::<Utc>::from(end_time)
.format("%Y-%m-%dT%H:%M:%S%.3fZ");

let event_value = format!(
"Start: {} | End: {} | Status: {}",
Expand Down Expand Up @@ -362,41 +352,51 @@ fn generate_event_key(
/// exceeds the allowed size, it is split into multiple slices for encoding.
/// This is used for logging events to a KVP file.
///
/// # Note
/// - The key must be zero-padded to `HV_KVP_EXCHANGE_MAX_KEY_SIZE` to meet Hyper-V's expected formatting.
///
/// # Arguments
/// * `key` - The key as a string slice.
/// * `value` - The value associated with the key.
fn encode_kvp_item(key: &str, value: &str) -> Vec<Vec<u8>> {
let key_bytes = key.as_bytes();
let value_bytes = value.as_bytes();

let key_len = key_bytes.len().min(HV_KVP_EXCHANGE_MAX_KEY_SIZE);
let mut key_buf = vec![0u8; HV_KVP_EXCHANGE_MAX_KEY_SIZE];
key_buf[..key_len].copy_from_slice(&key_bytes[..key_len]);

let mut kvp_slices = Vec::new();
let key_buf = key
.as_bytes()
.iter()
.take(HV_KVP_EXCHANGE_MAX_KEY_SIZE)
.chain(
vec![0_u8; HV_KVP_EXCHANGE_MAX_KEY_SIZE.saturating_sub(key.len())]
.iter(),
)
.copied()
.collect::<Vec<_>>();

debug_assert!(key_buf.len() == HV_KVP_EXCHANGE_MAX_KEY_SIZE);

let kvp_slices = value
.as_bytes()
.chunks(HV_KVP_AZURE_MAX_VALUE_SIZE)
.map(|chunk| {
let mut buffer = Vec::with_capacity(
HV_KVP_EXCHANGE_MAX_KEY_SIZE + HV_KVP_EXCHANGE_MAX_VALUE_SIZE,
);
buffer.extend_from_slice(&key_buf);
buffer.extend_from_slice(chunk);
while buffer.len()
< HV_KVP_EXCHANGE_MAX_KEY_SIZE + HV_KVP_EXCHANGE_MAX_VALUE_SIZE
{
buffer.push(0);
}

for chunk in value_bytes.chunks(HV_KVP_AZURE_MAX_VALUE_SIZE) {
let mut value_buf = vec![0u8; HV_KVP_EXCHANGE_MAX_VALUE_SIZE];
value_buf[..chunk.len()].copy_from_slice(chunk);
buffer
})
.collect::<Vec<Vec<u8>>>();

kvp_slices.push(encode_kvp_slice(key_buf.clone(), value_buf));
}
debug_assert!(kvp_slices.iter().all(|kvp| kvp.len()
== HV_KVP_EXCHANGE_MAX_KEY_SIZE + HV_KVP_EXCHANGE_MAX_VALUE_SIZE));

kvp_slices
}

/// Combines the key and value of a KVP into a single byte slice, ensuring
/// proper formatting for consumption by hv_kvp_daemon service,
/// which typically reads from /var/lib/hyperv/.kvp_pool_1.
fn encode_kvp_slice(key: Vec<u8>, value: Vec<u8>) -> Vec<u8> {
let mut buffer = Vec::with_capacity(
HV_KVP_EXCHANGE_MAX_KEY_SIZE + HV_KVP_EXCHANGE_MAX_VALUE_SIZE,
);
buffer.extend_from_slice(&key);
buffer.extend_from_slice(&value);
buffer
}

/// Decodes a KVP byte slice into its corresponding key and value strings.
/// This is useful for inspecting or logging raw KVP data.
#[cfg(test)]
Expand All @@ -412,18 +412,23 @@ pub fn decode_kvp_item(
}

let key = String::from_utf8(
record_data[0..HV_KVP_EXCHANGE_MAX_KEY_SIZE].to_vec(),
record_data
.iter()
.take(HV_KVP_EXCHANGE_MAX_KEY_SIZE)
.cloned()
.collect::<Vec<_>>(),
)
.unwrap_or_else(|_| String::new())
.trim_end_matches('\x00')
.to_string();

let value = String::from_utf8(
record_data[HV_KVP_EXCHANGE_MAX_KEY_SIZE..]
record_data
.iter()
.skip(HV_KVP_EXCHANGE_MAX_KEY_SIZE)
.take(HV_KVP_AZURE_MAX_VALUE_SIZE)
.cloned()
.collect(),
.collect::<Vec<_>>(),
)
.unwrap_or_else(|_| String::new())
.trim_end_matches('\x00')
Expand All @@ -435,12 +440,9 @@ pub fn decode_kvp_item(
/// Truncates the guest pool KVP file if it contains stale data (i.e., data
/// older than the system's boot time). Logs whether the file was truncated
/// or no action was needed.
fn truncate_guest_pool_file(kvp_file: &Path) -> Result<(), Error> {
let boot_time = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|e| Error::new(std::io::ErrorKind::Other, e))?
.as_secs()
- get_uptime()?.as_secs();
fn truncate_guest_pool_file(kvp_file: &Path) -> Result<(), anyhow::Error> {
let boot_time = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs()
- get_uptime().as_secs();

match kvp_file.metadata() {
Ok(metadata) => {
Expand All @@ -461,7 +463,8 @@ fn truncate_guest_pool_file(kvp_file: &Path) -> Result<(), Error> {
return Ok(());
}
Err(e) => {
return Err(e);
return Err(anyhow::Error::from(e)
.context("Failed to access file metadata"));
}
}

Expand All @@ -471,12 +474,12 @@ fn truncate_guest_pool_file(kvp_file: &Path) -> Result<(), Error> {
/// Retrieves the system's uptime using the `sysinfo` crate, returning the duration
/// since the system booted. This can be useful for time-based calculations or checks,
/// such as determining whether data is stale or calculating the approximate boot time.
fn get_uptime() -> Result<Duration, Error> {
fn get_uptime() -> Duration {
let mut system = System::new();
system.refresh_system();

let uptime_seconds = system.uptime();
Ok(Duration::from_secs(uptime_seconds))
Duration::from_secs(uptime_seconds)
}

#[cfg(test)]
Expand Down
13 changes: 8 additions & 5 deletions src/logging.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use opentelemetry_sdk::trace::{
};
use tracing::{event, Level};
use tracing_opentelemetry::OpenTelemetryLayer;
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::{
fmt, layer::SubscriberExt, EnvFilter, Layer, Registry,
Expand All @@ -26,15 +27,17 @@ pub fn initialize_tracing() -> sdktrace::Tracer {
pub fn setup_layers(
tracer: sdktrace::Tracer,
) -> Result<(), Box<dyn std::error::Error>> {
let otel_layer = OpenTelemetryLayer::new(tracer);
let otel_layer = OpenTelemetryLayer::new(tracer)
.with_filter(EnvFilter::from_env("AZURE_INIT_LOG"));

let kvp_filter = EnvFilter::builder()
.with_default_directive(LevelFilter::INFO.into())
.parse_lossy("azure_init=INFO,libazureinit=WARN,kvp=INFO");

let emit_kvp_layer = match EmitKVPLayer::new(std::path::PathBuf::from(
"/var/lib/hyperv/.kvp_pool_1",
)) {
Ok(layer) => {
let filter = EnvFilter::new("INFO");
Some(layer.with_filter(filter))
}
Ok(layer) => Some(layer.with_filter(kvp_filter)),
Err(e) => {
event!(Level::ERROR, "Failed to initialize EmitKVPLayer: {}. Continuing without KVP logging.", e);
None
Expand Down
5 changes: 2 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ async fn main() -> ExitCode {
}
}

#[instrument(name = "root")]
#[instrument(name = "root", skip_all)]
async fn provision(config: Config, opts: Cli) -> Result<(), anyhow::Error> {
let system = System::new();
let kernel_version = system
Expand All @@ -141,13 +141,12 @@ async fn provision(config: Config, opts: Cli) -> Result<(), anyhow::Error> {
let os_version = system
.os_version()
.unwrap_or("Unknown OS Version".to_string());
let azure_init_version = env!("CARGO_PKG_VERSION");

tracing::info!(
"Kernel Version: {}, OS Version: {}, Azure-Init Version: {}",
kernel_version,
os_version,
azure_init_version
VERSION
);

let mut default_headers = header::HeaderMap::new();
Expand Down
Loading