Skip to content

Conversation

davepacheco
Copy link
Collaborator

@davepacheco davepacheco commented Sep 6, 2025

Implements coordinated Nexus quiesce per RFD 588.

This PR only changes the existing Nexus quiesce process to do what RFD 588 documents. There are several other things that need to happen for a successful handoff. I will create follow-on PRs for those.

Fixes #8859
Fixes #8857
Fixes #8796
Fixes #8795
Fixes #8971
Fixes #8858

@davepacheco
Copy link
Collaborator Author

davepacheco commented Sep 15, 2025

With this PR as-is as of f7c92b3, I was able to do a semi-manual handoff and write a working live test, too. However, I had to fix several other issues, some of which also overlap with #8936. I'll separate these out into separate PRs and coordinate with @smklein.

@davepacheco davepacheco self-assigned this Sep 15, 2025
@davepacheco
Copy link
Collaborator Author

This is now ready for review.

Note that there should be minimal risk / impact to existing systems by landing this PR because existing systems do not quiesce until after #8936. By the time we do that, I hope we'll have more complete testing in place (e.g., a successful live test). I need to put together a few more PRs for that.

@davepacheco davepacheco marked this pull request as ready for review September 15, 2025 22:28

let db_nexus_ids: BTreeSet<_> = nexus_ids
.iter()
.cloned()
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit - since UUIDs impl Copy, I think this could be

Suggested change
.cloned()
.copied()

(which is not different in terms of generated code but it's more obvious that this is cheap)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in e8eb204.

Ok(count)
}

/// Updates the "last_drained_blueprint_id" for the given Nexus id
Copy link
Contributor

Choose a reason for hiding this comment

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

Wrong docstring on this method?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch. Fixed in e8eb204.

}
}

pub async fn extra_datastore(&self, log: &Logger) -> Arc<DataStore> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need this, as opposed to cloning the return value of datastore()?

Copy link
Collaborator Author

@davepacheco davepacheco Sep 16, 2025

Choose a reason for hiding this comment

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

The datastore (really, the underlying pool) has in-memory state associated with being quiesced. In our test, we want independent instances of this so that they can be quiesced independently.

edit: I'll add a comment about this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, fair enough. Probably worth at least a doc comment noting how this is different? I'm tempted to suggest a more descriptive name too, but I'm not sure what. independent_datastore() maybe?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Comment added in e8eb204.

/// Channel for TUF repository artifacts to be replicated out to sleds
pub tuf_artifact_replication_rx: mpsc::Receiver<ArtifactsWithPlan>,
/// Channel for exposing the latest loaded blueprint
pub blueprint_load_tx:
Copy link
Contributor

Choose a reason for hiding this comment

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

Just making sure I understand: we have this field now because in nexus/app/mod.rs, we need to construct this channel to get a handle to the receiver before we've set up the background task system?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yup.

.map_err(|e| public_error_from_diesel(e, ErrorHandler::Server))
}

/// Updates the "last_drained_blueprint_id" for the given Nexus id
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit, should we update this to return a bool? It's always going to be 0 or 1 rows updated, right?

Alternatively - we could just return an error if the "count == 0", right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't think we want to return an error for the 0 case because that could just mean we've already updated it to this blueprint id and that's fine. The caller needs to retry errors, but not that case.

I can see the appeal of the bool. I want to log the value either way, which feels slightly more idiomatic at the caller level, so I'm going to leave it.

pub async fn database_nexus_access_update_quiesced(
&self,
nexus_id: OmicronZoneUuid,
) -> Result<usize, Error> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same comment here about returning a usize - since we're indexing on nexus_id already, seems like we'll either perform the update successfully or not, and can return that more idiomatically than a usize.

@davepacheco davepacheco enabled auto-merge (squash) September 16, 2025 18:37
@davepacheco davepacheco merged commit 2163cfa into main Sep 16, 2025
16 checks passed
@davepacheco davepacheco deleted the dap/quiesce-with-db branch September 16, 2025 22:42
charliepark pushed a commit that referenced this pull request Sep 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment