Skip to content
Merged
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
2 changes: 2 additions & 0 deletions crates/apps/node/mgr/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use clap::Parser;
use comfy_table::{Table, presets::UTF8_FULL_CONDENSED};
use pplx_space_common_core::ids::sandbox_label_or_id;
use pplx_space_common_core::types::{OneOrMany, SandboxId, SandboxSelector};
use pplx_space_execution_common::environment::RUNTIME_NEUTRAL_ENVIRONMENT_SCHEMA_VERSION as DEFINITION_SCHEMA;
use pplx_space_node_api_client::proto::GetControllerStateRequest;
use pplx_space_node_mgr_client::proto::{
AllocateRequest, AllowPortRequest, CleanupSandboxRequest, DenyPortRequest, ExposePortRequest,
Expand Down Expand Up @@ -314,6 +315,7 @@ async fn run(cli: Cli) -> Result<(), String> {
let response = client
.resolve_execution_environment(
pplx_space_node_mgr_client::proto::ResolveExecutionEnvironmentRequest {
definition_schema_version: DEFINITION_SCHEMA,
compatible: None,
reference: reference.map(|value| value.to_string()),
},
Expand Down
37 changes: 31 additions & 6 deletions crates/libs/build/mgr/src/execution_binding.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Builder-side use of node-manager's verified environment, separate from guest
//! process environment variables and from downloadable artifact catalog entries.
use pplx_space_execution_common::environment::RUNTIME_NEUTRAL_ENVIRONMENT_SCHEMA_VERSION as DEFINITION_SCHEMA;
use std::path::Path;

use pplx_space_common_core::types::{EnvironmentReference, ExecutionMetadata, TemplateMetadata};
Expand Down Expand Up @@ -138,6 +139,7 @@ impl BuildExecutionBinding {
let mut node = pplx_space_node_mgr_client::connect().await?;
let response = node
.resolve_execution_environment(ResolveExecutionEnvironmentRequest {
definition_schema_version: DEFINITION_SCHEMA,
reference: None,
compatible: Some(request.clone()),
})
Expand All @@ -153,6 +155,7 @@ impl BuildExecutionBinding {
// never an unacknowledged upgrade.
let response = node
.resolve_execution_environment(ResolveExecutionEnvironmentRequest {
definition_schema_version: DEFINITION_SCHEMA,
reference: Some(fallback_reference.to_string()),
compatible: None,
})
Expand All @@ -174,6 +177,7 @@ impl BuildExecutionBinding {
let mut node = pplx_space_node_mgr_client::connect().await?;
Ok::<_, BuildExecutionBindingError>(
node.resolve_execution_environment(ResolveExecutionEnvironmentRequest {
definition_schema_version: DEFINITION_SCHEMA,
compatible: None,
reference: reference.map(|value| value.to_string()),
})
Expand Down Expand Up @@ -230,18 +234,39 @@ impl BuildExecutionBinding {
pin: Option<&super::GuestKernelPin>,
) -> Result<(), BuildExecutionBindingError> {
use pplx_space_common_core::types::{BootProfile, ExecutionCompatibilityProfile};
let BootProfile::LinuxDirectV1 { kernel } = &self.definition.descriptor().spec().boot
use pplx_space_execution_common::environment::{
FIRECRACKER_BOOT_CONTRACT, FIRECRACKER_MACHINE_CONTRACT,
};
let spec = self.definition.descriptor().spec();
let BootProfile::ArtifactsV1 {
contract,
artifacts,
} = &spec.boot
else {
return Err(BuildExecutionBindingError::ScratchProfile);
};
if pin.is_some_and(|pin| pin.version != kernel.version || pin.digest != kernel.digest) {
return Err(BuildExecutionBindingError::KernelPin);
}
let ExecutionCompatibilityProfile::FirecrackerSnapshotV1 { state_format } =
&self.definition.descriptor().spec().compatibility
let [boot] = artifacts.as_slice() else {
return Err(BuildExecutionBindingError::ScratchProfile);
};
let ExecutionCompatibilityProfile::RuntimeV1 {
contract: machine,
state_format: Some(state_format),
} = &spec.compatibility
else {
return Err(BuildExecutionBindingError::ScratchProfile);
};
if contract.as_str() != FIRECRACKER_BOOT_CONTRACT
|| machine.as_str() != FIRECRACKER_MACHINE_CONTRACT
|| boot.role().as_str() != "boot"
|| boot.format().as_str() != "vmlinux"
|| boot.component().kind != pplx_space_common_core::types::ArtifactComponentKind::Kernel
{
return Err(BuildExecutionBindingError::ScratchProfile);
}
let kernel = boot.component();
if pin.is_some_and(|pin| pin.version != kernel.version || pin.digest != kernel.digest) {
return Err(BuildExecutionBindingError::KernelPin);
}
// Values are resolved observations, never inherited selector ENV values.
command.env("SPACE_SEED_RUNTIME", self.execution.runtime().kind.as_str());
command.env(
Expand Down
51 changes: 27 additions & 24 deletions crates/libs/build/mgr/src/execution_binding/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,20 @@ fn fixture() -> ResolveExecutionEnvironmentResponse {
"/../../../../proto/execution/v1/fixtures/e0.json"
)))
.unwrap();
let spec = definition.descriptor().spec();
let RuntimeBinding::ManagedRelease(runtime) = &spec.runtime;
let BootProfile::LinuxDirectV1 { kernel } = &spec.boot else {
panic!("expected schema-1 fixture")
};
let ExecutionCompatibilityProfile::FirecrackerSnapshotV1 { state_format } = &spec.compatibility
else {
panic!("expected schema-1 fixture")
};
let mut components = definition
.execution_requirements()
.unwrap()
.components()
.to_vec();
components[0].format = None; // Historical records need not record a format.
let requirements = definition.execution_requirements().unwrap();
let execution = definition
.bind_execution(
ExecutionMetadata::new(
runtime.runtime.clone(),
spec.guest,
Some(state_format.clone()),
vec![ArtifactComponentMetadata {
kind: kernel.kind,
role: Some("boot".parse().unwrap()),
version: Some(kernel.version.clone()),
digest: Some(kernel.digest),
format: None,
}],
requirements.runtime().clone(),
requirements.guest(),
requirements.state_format().cloned(),
components,
)
.unwrap(),
)
Expand Down Expand Up @@ -111,9 +103,14 @@ fn scratch_inputs_replace_inherited_selectors_and_reject_conflicting_recipe_pins
Some(std::ffi::OsStr::new(expected))
);
}
let BootProfile::LinuxDirectV1 { kernel } = &binding.definition.descriptor().spec().boot else {
panic!("expected schema-1 fixture")
};
let (_, kernel, _) = binding
.definition
.descriptor()
.spec()
.boot
.components()
.next()
.unwrap();
let mut pin = super::super::GuestKernelPin {
version: kernel.version.clone(),
digest: kernel.digest,
Expand Down Expand Up @@ -270,10 +267,16 @@ async fn derived_vm_requests_keep_unbound_releases_on_the_legacy_protocol() {
assert!(explicit_e0.template.is_empty());
// A different boot kernel is a different binding, even with the same VMM.
let mut spec = binding.definition.descriptor().spec().clone();
let BootProfile::LinuxDirectV1 { kernel } = &mut spec.boot else {
panic!("expected schema-1 fixture")
let BootProfile::ArtifactsV1 { artifacts, .. } = &mut spec.boot else {
panic!("expected generic fixture")
};
let mut kernel = artifacts[0].component().clone();
kernel.digest = Digest::sha256([7; 32]);
artifacts[0] = BootArtifact::new(
artifacts[0].role().clone(),
kernel,
artifacts[0].format().clone(),
);
let definition = EnvironmentDefinition::new(EnvironmentDescriptor::try_from(spec).unwrap());
let requirements = definition.execution_requirements().unwrap();
let other = BuildExecutionBinding::from_response(
Expand Down
28 changes: 23 additions & 5 deletions crates/libs/execution/common/src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use crate::{ExecutionProtoError, proto};

mod binding;
mod profiles;
mod transport;
pub use binding::{EnvironmentBindingError, EnvironmentRequirementsError};
pub use transport::{FIRECRACKER_BOOT_CONTRACT, FIRECRACKER_MACHINE_CONTRACT};

/// Original encoding; existing definitions retain these bytes and references.
pub const ENVIRONMENT_SCHEMA_VERSION: u32 = 1;
Expand All @@ -18,6 +20,8 @@ pub const RUNTIME_NEUTRAL_ENVIRONMENT_SCHEMA_VERSION: u32 = 2;
pub struct EnvironmentDefinition {
reference: EnvironmentReference,
descriptor: EnvironmentDescriptor,
// Immutable historical representation used only for identity and old peers.
legacy_descriptor: Option<EnvironmentDescriptor>,
}

#[derive(Debug, thiserror::Error)]
Expand All @@ -30,6 +34,10 @@ pub enum EnvironmentDecodeError {
UnsupportedValue { field: &'static str, value: i32 },
#[error("schema {claimed} does not describe these environment profiles (expected {expected})")]
SchemaMismatch { claimed: u32, expected: u32 },
#[error("generic profile cannot represent the requested historical identity")]
IdentityEncoding,
#[error("peer cannot decode this environment definition")]
UnsupportedPeer,
#[error("invalid execution contract: {0}")]
Contract(#[from] ExecutionContractIdParseError),
#[error("invalid boot artifact role: {0}")]
Expand Down Expand Up @@ -57,6 +65,7 @@ impl EnvironmentDefinition {
Self {
reference: EnvironmentReference::new(Digest::sha256(bytes)),
descriptor,
legacy_descriptor: None,
}
}

Expand Down Expand Up @@ -100,7 +109,7 @@ impl EnvironmentDefinition {
/// Fixed field order, ASCII keys and canonical scalar spellings, final newline.
/// Identifiers cannot contain newlines or '='. No wire/JSON bytes, observations,
/// labels, source URLs, or node-local paths participate. See docs/execution-environments.md.
pub fn canonical_descriptor(descriptor: &EnvironmentDescriptor) -> String {
fn canonical_descriptor(descriptor: &EnvironmentDescriptor) -> String {
let spec = descriptor.spec();
let RuntimeBinding::ManagedRelease(release) = &spec.runtime;
let (
Expand Down Expand Up @@ -183,6 +192,11 @@ impl From<&EnvironmentDefinition> for proto::EnvironmentDefinition {
let RuntimeBinding::ManagedRelease(release) = &spec.runtime;
Self {
schema_version: value.schema_version(),
identity_schema_version: if value.identity_schema_version() == value.schema_version() {
0
} else {
value.identity_schema_version()
},
reference: value.reference.to_string(),
descriptor: Some(proto::EnvironmentDescriptor {
host: Some(spec.host.into()),
Expand Down Expand Up @@ -264,10 +278,14 @@ impl TryFrom<&proto::EnvironmentDefinition> for EnvironmentDefinition {
compatibility: required(spec.compatibility.as_ref(), "compatibility")?.try_into()?,
}
.try_into()?;
let definition = Self::new(descriptor);
definition
.check_schema(value.schema_version)?
.check_reference(value.reference.parse()?)
let identity = if value.identity_schema_version == 0 {
value.schema_version
} else {
value.identity_schema_version
};
Self::from_representation(descriptor, value.schema_version, identity)?
.check_reference(value.reference.parse()?)?
.into_runtime_neutral()
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/libs/execution/common/src/environment/binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ impl EnvironmentDefinition {
if execution.components().len() != spec.boot.components().count() {
return Err(EnvironmentBindingError::BootComponents);
}
let strict_format = matches!(spec.boot, BootProfile::ArtifactsV1 { .. });
let strict_format = self.identity_schema_version() != ENVIRONMENT_SCHEMA_VERSION;
for (role, component, format) in required {
if !execution.components().iter().any(|observed| {
observed.kind == component.kind
Expand Down
12 changes: 9 additions & 3 deletions crates/libs/execution/common/src/environment/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,14 @@ pub enum EnvironmentDocumentError {
impl EnvironmentDefinition {
pub fn to_document(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(&Document {
schema_version: self.schema_version(),
schema_version: self.identity_schema_version(),
reference: self.reference,
descriptor: self.descriptor.spec().clone(),
descriptor: self
.legacy_descriptor
.as_ref()
.unwrap_or(&self.descriptor)
.spec()
.clone(),
})
}

Expand All @@ -59,6 +64,7 @@ impl EnvironmentDefinition {
.map_err(EnvironmentDecodeError::from)?;
Ok(Self::new(descriptor)
.check_schema(document.schema_version)?
.check_reference(document.reference)?)
.check_reference(document.reference)?
.into_runtime_neutral()?)
}
}
5 changes: 4 additions & 1 deletion crates/libs/execution/common/src/environment/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ fn golden_identity_survives_protobuf_round_trip() {
assert_eq!(definition.reference().to_string(), E0_REFERENCE);
let bytes = proto::EnvironmentDefinition::from(&definition).encode_to_vec();
let wire = proto::EnvironmentDefinition::decode(bytes.as_slice()).unwrap();
assert_eq!(EnvironmentDefinition::try_from(&wire).unwrap(), definition);
assert_eq!(
EnvironmentDefinition::try_from(&wire).unwrap(),
definition.into_runtime_neutral().unwrap()
);
}

#[test]
Expand Down
Loading
Loading