Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
72 changes: 69 additions & 3 deletions crates/libs/node/runtime/core/src/catalog/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub enum CompiledEnvironmentDefinitionError {
UnsupportedArchitecture(Arch),
#[error("invalid compiled environment descriptor: {0}")]
Descriptor(#[from] EnvironmentDescriptorError),
#[error("invalid compiled runtime ordering version: {0}")]
RuntimeVersion(#[from] semver::Error),
#[error("invalid compiled state format: {0}")]
StateFormat(#[from] StateFormatNameParseError),
#[error("invalid compiled state format version: {0}")]
Expand All @@ -27,6 +29,23 @@ pub enum ResolveEnvironmentError {
}

impl InitialEnvironment {
/// Resolve E0 independently of additional compiled bindings. An invalid
/// optional qualification must not withdraw the production default.
pub fn compiled_environment(
&self,
) -> Result<CompiledEnvironment, CompiledEnvironmentDefinitionError> {
let definition = self.definition()?;
Ok(CompiledEnvironment {
runtime_ordering_key: semver::Version::parse(self.release.release.version.as_str())?,
initial_reference: definition.reference(),
release: self.release.clone(),
kernel: self.kernel.clone(),
definition,
memory_state: true,
name: "e0",
})
}

/// Production qualification covers Linux/x86_64 E0 only. Existing artifact
/// pins for other architectures retain their installation meaning.
pub fn definition(&self) -> Result<EnvironmentDefinition, CompiledEnvironmentDefinitionError> {
Expand Down Expand Up @@ -105,6 +124,9 @@ pub struct CompiledEnvironment {
kernel: PinnedKernel,
definition: EnvironmentDefinition,
memory_state: bool,
name: &'static str,
runtime_ordering_key: semver::Version,
initial_reference: EnvironmentReference,
}

impl CompiledEnvironment {
Expand All @@ -117,10 +139,17 @@ impl CompiledEnvironment {
) -> Self {
let definition = definition_from_pins(&release, &kernel, state_version).unwrap();
Self {
runtime_ordering_key: semver::Version::parse(release.release.version.as_str()).unwrap(),
initial_reference: InitialEnvironment::for_architecture(release.architecture)
.unwrap()
.definition()
.unwrap()
.reference(),
release,
kernel,
definition,
memory_state,
name: "synthetic",
}
}

Expand All @@ -141,25 +170,44 @@ impl CompiledEnvironment {
.as_deref()
}

/// Build an owned catalog for startup validation and qualification tests.
/// Repeated execution lookups use `cached_for_architecture` instead.
pub fn for_architecture(architecture: Arch) -> Result<Vec<Self>, CatalogError> {
if architecture != Arch::X86_64 {
return Ok(Vec::new());
}
let catalog = RuntimeCatalog::compiled()?;
let initial_reference = InitialEnvironment::for_architecture(architecture)?
.definition()
.map_err(CatalogError::EnvironmentDefinition)?
.reference();
// Complete bindings are approved independently of downloadable release pins.
// Memory-state compatibility must be qualified before it is advertised.
[
(
"e0",
"1.15.1",
"6.1.155",
INITIAL_FIRECRACKER_STATE_FORMAT_VERSION,
true,
),
("1.17.0", "6.18.44", "12.0.0", true),
("1.17.0", "6.1.155", "12.0.0", true),
(
"fc-1.17.0-linux-6.18.44",
"1.17.0",
"6.18.44",
"12.0.0",
true,
),
(
"fc-1.17.0-linux-6.1.155",
"1.17.0",
"6.1.155",
"12.0.0",
true,
),
]
.into_iter()
.map(|(runtime, kernel, state_version, memory_state)| {
.map(|(name, runtime, kernel, state_version, memory_state)| {
let release = catalog
.releases(RuntimeKind::Firecracker, architecture)
.find(|pin| pin.release.version.as_str() == runtime)
Expand All @@ -172,16 +220,34 @@ impl CompiledEnvironment {
.ok_or(CatalogError::MissingEnvironmentPins { runtime, kernel })?;
let definition = definition_from_pins(release, kernel_pin, state_version)
.map_err(CatalogError::EnvironmentDefinition)?;
let runtime_ordering_key = semver::Version::parse(release.release.version.as_str())
.map_err(CompiledEnvironmentDefinitionError::RuntimeVersion)
.map_err(CatalogError::EnvironmentDefinition)?;
Ok(Self {
runtime_ordering_key,
initial_reference,
release: release.clone(),
kernel: kernel_pin.clone(),
definition,
memory_state,
name,
})
})
.collect()
}

pub fn is_initial(&self) -> bool {
self.definition.reference() == self.initial_reference
}

pub(crate) fn runtime_ordering_key(&self) -> &semver::Version {
&self.runtime_ordering_key
}

pub fn name(&self) -> &'static str {
self.name
}

pub fn release(&self) -> &PinnedRelease {
&self.release
}
Expand Down
23 changes: 23 additions & 0 deletions crates/libs/node/runtime/core/src/catalog/environment/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,26 @@ fn compiled_environments_have_complete_distinct_bindings_and_operation_support()
.is_empty()
);
}

#[test]
fn initial_identity_and_legacy_restore_do_not_depend_on_display_name() {
use crate::runtime_inventory::ExecutionSelectionMode;
use crate::selection::ExecutionPolicy;
let initial = InitialEnvironment::for_architecture(Arch::X86_64).unwrap();
let mut entry = initial.compiled_environment().unwrap();
entry.name = "renamed-initial";
let bound = entry.definition().execution_requirements().unwrap();
let historical = ExecutionMetadata::new(
bound.runtime().clone(),
bound.guest(),
bound.state_format().cloned(),
vec![],
)
.unwrap();
let policy = ExecutionPolicy::parse(Some("renamed-initial"), None, &[entry]).unwrap();
assert!(!policy.legacy_unbound_allowed());
assert_eq!(policy.default_reference(), bound.environment());
policy
.check_execution(&historical, ExecutionSelectionMode::PersistedStateRestore)
.unwrap();
}
1 change: 1 addition & 0 deletions crates/libs/node/runtime/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub mod file_delta;

pub mod launch_admission;
pub mod runtime_inventory;
pub mod selection;
pub mod verity;

pub mod snapshot_files;
Expand Down
199 changes: 199 additions & 0 deletions crates/libs/node/runtime/core/src/runtime_inventory/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1179,3 +1179,202 @@ fn bound_cold_boot_uses_the_recorded_runtime_and_never_the_node_default() {
);
}
}

#[test]
fn compatible_selection_preserves_exact_before_fallback() {
use crate::catalog::CompiledEnvironment;
use crate::selection::{self, CompatibleSelection, ExecutionPolicy};
let catalog = CompiledEnvironment::for_architecture(Arch::X86_64).unwrap();
let source = catalog[0].definition().execution_requirements().unwrap();
let all = "e0,fc-1.17.0-linux-6.1.155,fc-1.17.0-linux-6.18.44";
for exact_installed in [true, false] {
let mut inventory = inventory(Vec::new());
for entry in &catalog {
if entry.name() == "e0" && !exact_installed {
continue;
}
let execution = entry.definition().execution_requirements().unwrap();
let mut runtime = installation(
execution.runtime().version.as_str(),
execution.state_format().unwrap().version.as_str(),
);
runtime.guest = execution.guest();
let mut component = kernel(entry.kernel().version.as_str(), 0, false, false);
component.metadata = execution.components()[0].clone();
inventory.environments.insert(
entry.definition().reference(),
InstalledEnvironment {
runtime: Ok(runtime),
components: Ok(vec![component]),
},
);
}
for (allowed, default, exact_allowed) in [
(all, "e0", true),
(
"fc-1.17.0-linux-6.1.155,fc-1.17.0-linux-6.18.44",
"fc-1.17.0-linux-6.18.44",
false,
),
] {
let policy = ExecutionPolicy::parse(Some(allowed), Some(default), &catalog).unwrap();
let (_, cold) = selection::select(
&inventory,
&policy,
CompatibleSelection::ColdBoot { source: &source },
)
.unwrap();
assert_eq!(
cold.execution.runtime().version.as_str(),
if exact_installed && exact_allowed {
"1.15.1"
} else {
"1.17.0"
}
);
assert_eq!(cold.execution.components(), source.components());
let restore = selection::select(
&inventory,
&policy,
CompatibleSelection::Restore { source: &source },
);
if exact_installed && exact_allowed {
assert_eq!(
restore.unwrap().1.execution.runtime().version.as_str(),
"1.15.1"
);
} else {
restore.unwrap_err();
}
}
}
}

#[test]
fn restore_falls_back_after_revocation_without_boot_files() {
use crate::catalog::CompiledEnvironment;
use crate::selection::{self, CompatibleSelection, ExecutionPolicy};
let mut catalog = CompiledEnvironment::for_architecture(Arch::X86_64).unwrap();
let source = catalog[0].definition().execution_requirements().unwrap();
// Synthetic qualification proves a patch release is eligible because its
// declared profile matches, not because its version happens to be newer.
let mut release = catalog[0].release().clone();
release.release.version = "1.15.2".parse().unwrap();
catalog.push(CompiledEnvironment::for_test(
release,
catalog[0].kernel().clone(),
"9.0.0",
true,
));
let mut inventory = inventory(Vec::new());
inventory.components.clear();
for entry in &catalog {
let execution = entry.definition().execution_requirements().unwrap();
let mut runtime = installation(
execution.runtime().version.as_str(),
execution.state_format().unwrap().version.as_str(),
);
runtime.guest = execution.guest();
inventory.environments.insert(
entry.definition().reference(),
InstalledEnvironment {
runtime: Ok(runtime),
components: Err(std::sync::Arc::new(
RuntimeInventoryError::NoCompatibleComponent(execution.components()[0].clone()),
)),
},
);
}
for (allowed, expected) in [("e0,synthetic", "1.15.1"), ("synthetic", "1.15.2")] {
let policy = ExecutionPolicy::parse(Some(allowed), Some("synthetic"), &catalog).unwrap();
let (_, selected) = selection::select(
&inventory,
&policy,
CompatibleSelection::Restore { source: &source },
)
.unwrap();
assert_eq!(selected.execution.runtime().version.as_str(), expected);
assert_eq!(selected.execution.state_format(), source.state_format());
assert_eq!(selected.execution.components(), source.components());
assert!(selected.components.is_empty());
assert_eq!(source.runtime().version.as_str(), "1.15.1");
selection::select(
&inventory,
&policy,
CompatibleSelection::ColdBoot { source: &source },
)
.unwrap_err();
}
}

#[test]
fn transient_exact_source_failure_never_selects_another_consumer() {
use crate::catalog::CompiledEnvironment;
use crate::selection::{self, CompatibleSelection, ExecutionPolicy, SelectionError};
let mut catalog = CompiledEnvironment::for_architecture(Arch::X86_64).unwrap();
let source = catalog[0].definition().execution_requirements().unwrap();
let mut release = catalog[0].release().clone();
release.release.version = "1.15.2".parse().unwrap();
catalog.push(CompiledEnvironment::for_test(
release,
catalog[0].kernel().clone(),
"9.0.0",
true,
));
for reverse in [false, true] {
if reverse {
catalog.reverse();
}
for transient in [false, true] {
for allowed in ["e0,synthetic", "synthetic"] {
let policy =
ExecutionPolicy::parse(Some(allowed), Some("synthetic"), &catalog).unwrap();
let mut inventory = inventory(Vec::new());
for entry in &catalog {
let execution = entry.definition().execution_requirements().unwrap();
let mut runtime = installation(
execution.runtime().version.as_str(),
execution.state_format().unwrap().version.as_str(),
);
runtime.guest = execution.guest();
let mut component = kernel(entry.kernel().version.as_str(), 0, false, false);
component.metadata = execution.components()[0].clone();
inventory.environments.insert(
entry.definition().reference(),
InstalledEnvironment {
runtime: if entry.name() == "e0" {
Err(std::sync::Arc::new(RuntimeInventoryError::ReadDir {
path: "/synthetic/runtime".into(),
source: std::io::Error::from(if transient {
std::io::ErrorKind::Interrupted
} else {
std::io::ErrorKind::NotFound
}),
}))
} else {
Ok(runtime)
},
components: Ok(vec![component]),
},
);
}
for intent in [
CompatibleSelection::ColdBoot { source: &source },
CompatibleSelection::Restore { source: &source },
] {
let result = selection::select(&inventory, &policy, intent);
if transient && allowed == "e0,synthetic" {
assert!(
matches!(result, Err(SelectionError::Unavailable(ref error)) if error.is_transient())
);
} else {
assert_eq!(
result.unwrap().1.execution.runtime().version.as_str(),
"1.15.2"
);
}
}
}
}
}
}
Loading
Loading