Skip to content

Add measurements to blueprints#9718

Open
labbott wants to merge 1 commit intomainfrom
labbott/measurement_blueprints
Open

Add measurements to blueprints#9718
labbott wants to merge 1 commit intomainfrom
labbott/measurement_blueprints

Conversation

@labbott
Copy link
Contributor

@labbott labbott commented Jan 25, 2026

No description provided.

@labbott labbott marked this pull request as draft January 25, 2026 18:40
@labbott labbott force-pushed the labbott/measurement_blueprints branch from 03bf716 to e6a722e Compare January 27, 2026 21:15
@labbott labbott marked this pull request as ready for review January 27, 2026 21:15
@labbott labbott changed the title WIP: Add measurements to blueprints Add measurements to blueprints Jan 27, 2026
@labbott labbott force-pushed the labbott/measurement_blueprints branch from e6a722e to 9d27ff4 Compare January 27, 2026 21:16

#[derive(Queryable, Clone, Debug, Selectable, Insertable)]
#[diesel(table_name = bp_single_measurements)]
//#[diesel(check_for_backend(diesel::pg::Pg))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover debugging?

},
hash: *self
.image_artifact_sha256
.expect("this should always be set"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the field be non-optional (and the corresponding db row have a NOT NULL constraint)?

pub id: DbTypedUuid<MeasurementKind>,

pub image_artifact_sha256: Option<ArtifactHash>,
pub prune: bool,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed in chat but noting for completeness - I think we can drop the prune field since the planner has access to both the current and previous TUF repo.

}
}

impl From<BTreeSet<BlueprintSingleMeasurement>> for BlueprintMeasurements {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These Froms seem fine-ish but surprising - could wherever we're currently using BTreeSet<BlueprintSingleMeasurement> use BlueprintMeasurements instead?

self.measurements.insert(single);
}

// An empty measurement set here corresponds to the install dataset
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this property guaranteed? Wondering if there are edge cases where we could have an empty set of measurements but that didn't imply "use the install dataset" - maybe dev/test systems? Or if this is a surprising security-related property in the future sometime if someone assumes "empty set" means "won't be able to talk" but it actually means "falls back to whatever was most recently written to the install dataset (possibly months/years ago)".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is a bit of a strange edge case. I did a first pass with this being an enum like BlueprintZoneImageSource but I'm trying to remember why doing that here was ugly. It's possible I went a layer too far in getting rid of indirection. I'll play around with this more.

That said, there eventually should be no case in the future when this is empty past the initial install. As we get further along and more confident I expect we'll want to make that a stronger assertion.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This turned out to be more doable than I remembered, perhaps now that things are matured

@labbott labbott force-pushed the labbott/measurement_blueprints branch from ae2e508 to bc950ef Compare February 3, 2026 19:37
Comment on lines +987 to +1008
match measurement {
BlueprintMeasurements::Artifacts { artifacts } => artifacts
.iter()
.map(move |d| {
BpTableRow::from_strings(
state,
vec![d.hash.to_string(), d.version.to_string()],
)
})
.collect::<Vec<BpTableRow>>()
.into_iter(),
BlueprintMeasurements::InstallDataset => {
vec![BpTableRow::from_strings(
state,
vec![
"install dataset".to_string(),
"(no version)".to_string(),
],
)]
.into_iter()
.collect::<Vec<BpTableRow>>()
.into_iter()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to fix this up to use Either

Comment on lines +1946 to +1956
match self {
BlueprintMeasurements::InstallDataset => {
std::iter::empty::<&BlueprintSingleMeasurement>()
.collect::<Vec<&BlueprintSingleMeasurement>>()
.into_iter()
}
BlueprintMeasurements::Artifacts { artifacts } => artifacts
.iter()
.collect::<Vec<&BlueprintSingleMeasurement>>()
.into_iter(),
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix this to use Either

We don't actually update the measuremenst right now, that
will come in follow on work.
);
}

let raw_measurements: Vec<(BpSingleMeasurement, Option<TufArtifact>)> = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is in the wrong place within this function, after the changes @smklein made to ensure we don't read partially-deleted blueprints (#9703). About 50 lines above this is:

        // ================================================================
        // STAGE 3: Parse and validate all loaded data
        //
        // At this point, we know the blueprint exists (wasn't deleted).
        // Any validation errors from here indicate real database
        // corruption, not a torn read from concurrent deletion.
        // ================================================================

and we're not expected to do any more reads from the DB in this stage, so I think this needs to go above into "STAGE 1". (Maybe we should break the stages up into three methods to make this mistake more difficult? But that probably has its own annoyances.)

I think this may also mean we don't need the

                // We start with measurements from the install dataset, if
                // there's anything in the database this will get overwritten

comment above - if we've already read raw_measurements before we get to that point, hopefully we can fill in the correct value immediately?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I'll see about making this change and mayyybe see about refactoring but I'm not great with diesel. Maybe I should just file a bug instead?

pub zones: IdOrdMap<BlueprintZoneConfig>,
pub remove_mupdate_override: Option<MupdateOverrideUuid>,
pub host_phase_2: BlueprintHostPhase2DesiredSlots,
#[serde(default = "BlueprintMeasurements::default")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a #[serde(default)] here? I don't think we need to parse old BlueprintSledConfigs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was something I added when I thought I had a different problem. I can remove it.

}
}

/// The [`BpTable`] schema for desired host phase 2 contents
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy/pasta typo

id UUID NOT NULL,

image_artifact_sha256 STRING(64) NOT NULL,
PRIMARY KEY (blueprint_id, id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turbo-nit - these two lines are indented differently from the columns above

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants