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
56 changes: 56 additions & 0 deletions dev-tools/omdb/src/bin/omdb/nexus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1556,6 +1556,62 @@ fn print_task_details(bgtask: &BackgroundTask, details: &serde_json::Value) {
}
}
}
} else if name == "blueprint_loader" {
#[derive(Deserialize)]
struct BlueprintLoaderStatus {
target_id: Uuid,
time_created: DateTime<Utc>,
status: String,
enabled: bool,
}

match serde_json::from_value::<BlueprintLoaderStatus>(details.clone()) {
Err(error) => eprintln!(
"warning: failed to interpret task details: {:?}: {:?}",
error, details
),
Comment on lines +1569 to +1572
Copy link
Contributor

Choose a reason for hiding this comment

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

Wondering if there's a way we can incorporate a check that this deserializes correctly. I guess to do that we'd need a test which kicks off a blueprint execution and ensures that it deserializes correctly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We could definitely do more to reify the interface between tasks' status messages and omdb. In an ideal world they'd show up in the OpenAPI spec and you wouldn't have to do this at all. When we added background tasks the obvious way to do that was with an enum with variants for each task, but that seemed unwieldy on a bunch of levels. But yeah, the more we add here, the more annoying this is.

Copy link
Contributor

@sunshowers sunshowers Aug 27, 2024

Choose a reason for hiding this comment

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

Oh yeah I think the common layer should be serde_json::Value, not an enum. But the last and first things we do can be to work with this more specific type.

I had to do something similar for the update engine—in there, I implemented a scheme where each event report carries the name of the spec it is associated with. That worked reasonably well. (It's similar in spirit to storing a type ID in dynamic objects that you can downcast to.)

Copy link
Contributor

Choose a reason for hiding this comment

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

(If this is too hard feel free to defer this, I'm going to be making some changes in this area soon)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Now that you mention the idea of a separate name specifying the schema, I wonder if we could first-class that in the JsonSchema (i.e., "if this string property is X, then this other property is of type Y") so that on the other end Progenitor could deserialize to the specific type.

Copy link
Contributor

@sunshowers sunshowers Aug 27, 2024

Choose a reason for hiding this comment

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

That's basically a tagged enum, right? Except we're modeling it as a type rather than an enum variant. (The difference is that a tagged enum is a closed universe and a "type ID" string is an open universe)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, and maybe the answer is to have the JsonSchema for this type expose it the same way it would a tagged enum?

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 I understand what you're saying, but I guess I'm not quite following what additional support Progenitor could provide over doing this by hand. (But let's discuss this separately.)

Ok(status) => {
println!(" target blueprint: {}", status.target_id);
println!(
" execution: {}",
if status.enabled { "enabled" } else { "disabled" }
);
println!(
" created at: {}",
humantime::format_rfc3339_millis(
status.time_created.into()
)
);
println!(" status: {}", status.status);
}
}
} else if name == "blueprint_executor" {
#[derive(Deserialize)]
struct BlueprintExecutorStatus {
target_id: Uuid,
enabled: bool,
errors: Option<Vec<String>>,
}

match serde_json::from_value::<BlueprintExecutorStatus>(details.clone())
{
Err(error) => eprintln!(
"warning: failed to interpret task details: {:?}: {:?}",
error, details
),
Ok(status) => {
println!(" target blueprint: {}", status.target_id);
println!(
" execution: {}",
if status.enabled { "enabled" } else { "disabled" }
);
let errors = status.errors.as_deref().unwrap_or(&[]);
println!(" errors: {}", errors.len());
for (i, e) in errors.iter().enumerate() {
println!(" error {}: {}", i, e);
}
}
}
} else {
println!(
"warning: unknown background task: {:?} \
Expand Down
8 changes: 6 additions & 2 deletions nexus/src/app/background/tasks/blueprint_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ impl BlueprintExecutor {
"target_id" => %blueprint.id);
return json!({
"target_id": blueprint.id.to_string(),
"error": "blueprint disabled"
"enabled": false,
});
}

Expand Down Expand Up @@ -111,6 +111,7 @@ impl BlueprintExecutor {

json!({
"target_id": blueprint.id.to_string(),
"enabled": true,
"needs_saga_recovery": needs_saga_recovery,
})
}
Expand All @@ -119,6 +120,7 @@ impl BlueprintExecutor {
errors.into_iter().map(|e| format!("{:#}", e)).collect();
json!({
"target_id": blueprint.id.to_string(),
"enabled": true,
"errors": errors
})
}
Expand Down Expand Up @@ -316,6 +318,7 @@ mod test {
value,
json!({
"target_id": blueprint_id,
"enabled": true,
"needs_saga_recovery": false,
})
);
Expand Down Expand Up @@ -410,6 +413,7 @@ mod test {
value,
json!({
"target_id": blueprint.1.id.to_string(),
"enabled": true,
"needs_saga_recovery": false,
})
);
Expand All @@ -427,7 +431,7 @@ mod test {
assert_eq!(
value,
json!({
"error": "blueprint disabled",
"enabled": false,
"target_id": blueprint.1.id.to_string()
})
);
Expand Down
9 changes: 7 additions & 2 deletions nexus/src/app/background/tasks/blueprint_load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ impl BackgroundTask for TargetBlueprintLoader {
};

// Decide what to do with the new blueprint
let enabled = new_bp_target.enabled;
let Some((old_bp_target, old_blueprint)) = self.last.as_deref()
else {
// We've found a target blueprint for the first time.
Expand All @@ -97,6 +98,7 @@ impl BackgroundTask for TargetBlueprintLoader {
"time_created": time_created,
"time_found": chrono::Utc::now(),
"status": "first target blueprint",
"enabled": enabled,
});
};

Expand All @@ -116,7 +118,8 @@ impl BackgroundTask for TargetBlueprintLoader {
"target_id": target_id,
"time_created": time_created,
"time_found": chrono::Utc::now(),
"status": "target blueprint updated"
"status": "target blueprint updated",
"enabled": enabled,
})
} else {
// The new target id matches the old target id
Expand Down Expand Up @@ -159,6 +162,7 @@ impl BackgroundTask for TargetBlueprintLoader {
"time_created": time_created,
"time_found": chrono::Utc::now(),
"status": format!("target blueprint {status}"),
"enabled": enabled,
})
} else {
// We found a new target blueprint that exactly
Expand All @@ -173,7 +177,8 @@ impl BackgroundTask for TargetBlueprintLoader {
json!({
"target_id": target_id,
"time_created": time_created,
"status": "target blueprint unchanged"
"status": "target blueprint unchanged",
"enabled": enabled,
})
}
}
Expand Down