feat(inkless): add controller guards for classic-to-diskless switch [POD-2464]#634
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds controller-side safety checks to prevent switching an existing (“classic”) topic to diskless (diskless.enable=true) unless key replication/availability preconditions are satisfied, and adds tests to validate rejection behavior.
Changes:
- Add controller validation for classic-to-diskless transitions (online partitions, no reassignment in progress, healthy ISR/ELR state).
- Apply the validation in the controller’s
incrementalAlterConfigshandling path. - Add unit/integration tests asserting that unhealthy partitions cause the switch to be rejected.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| metadata/src/main/java/org/apache/kafka/controller/ReplicationControlManager.java | Adds classic→diskless precondition validation and refactors topic filtering for switch-start record emission. |
| metadata/src/main/java/org/apache/kafka/controller/QuorumController.java | Invokes the new precondition validation before applying incremental config changes. |
| metadata/src/test/java/org/apache/kafka/controller/ReplicationControlManagerTest.java | Adds unit tests for precondition failures and pending-switch interactions. |
| core/src/test/java/kafka/server/InklessTopicTypeSwitcherClusterTest.java | Adds an integration test ensuring under-replicated topics cannot be switched to diskless via incremental alter configs. |
Comments suppressed due to low confidence (1)
metadata/src/main/java/org/apache/kafka/controller/QuorumController.java:1967
configChangesis mutated viaconfigChanges.keySet().removeAll(...). Callers can (and do) pass immutable maps (e.g.,Map.of(...)inQuorumControllerTest), which will throwUnsupportedOperationExceptioneven whenpreconditionErrorsis empty. Avoid mutating the input map and instead operate on a mutable copy / filtered map for the subsequent config write and migration record generation.
Map<ConfigResource, ApiError> preconditionErrors =
replicationControl.validateClassicToDisklessSwitchPreconditions(configChanges);
configChanges.keySet().removeAll(preconditionErrors.keySet());
ControllerResult<Map<ConfigResource, ApiError>> result =
configurationControl.incrementalAlterConfigs(configChanges, false);
result.response().putAll(preconditionErrors);
if (validateOnly) {
return result.withoutRecords();
} else {
List<ApiMessageAndVersion> migrationRecords =
replicationControl.markClassicToDisklessSwitchStarted(configChanges, result.response());
if (!migrationRecords.isEmpty()) {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
6738471 to
f086555
Compare
| Map<ConfigResource, ApiError> preconditionErrors = | ||
| replicationControl.validateClassicToDisklessSwitchPreconditions(configChanges); | ||
| configChanges.keySet().removeAll(preconditionErrors.keySet()); | ||
|
|
||
| ControllerResult<Map<ConfigResource, ApiError>> result = | ||
| configurationControl.incrementalAlterConfigs(configChanges, false); | ||
| result.response().putAll(preconditionErrors); |
There was a problem hiding this comment.
Even though it logically makes sense to first validate and then apply incrementalAlterConfigs logic, it would break the error messages for some particular cases, like this one:
e.g. diskless.allow.from.classic.enable=false and the topic is under-replicated => this code will classify the topic as "enabling diskless”" return the under-replicated precondition error, and skip ConfigurationControlManager, which would have returned “It is invalid to enable diskless on an already existing topic.”
So the correct order here would be:
- Run config validation first (
configurationControl.incrementalAlterConfigs) - Only for resources whose config change is otherwise valid, run the replication-state preconditions
- If preconditions fail, do not emit that resource’s ConfigRecord.
There was a problem hiding this comment.
Thanks for the explanation 👍 addressed
82898dd to
513d861
Compare
Enforce the following guards when altering the topic config to use diskless.enable=true: - Partitions are online - No reassignment in progress - Enough ISRs [POD-2464]
513d861 to
3bf9745
Compare
…ing diskless switch
viktorsomogyi
left a comment
There was a problem hiding this comment.
LGTM on my part but let's wait for Giuseppe's verdict as he requested some changes earlier.
giuseppelillo
left a comment
There was a problem hiding this comment.
Overall LGTM, I like the approach.
However I think there's a latent bug in the Legacy AlterConfigs (which is present also in the already existing markClassicToDisklessSwitchStartedForLegacyAlterConfigs):
Legacy AlterConfigs deletes existing configs that are missing from the request. If a classic topic has explicit diskless.enable=false and the broker default is log.diskless.enable=true, a legacy request that only changes another config and omits diskless.enable can effectively switch the topic to diskless, but it won't be caught.
Could you add a test about this and verify that is a real bug, and if so fix it?
The legacy AlterConfigs API will implicitly switch to diskless in the following conditions: - Broker level config `log.diskless.enable` is `true` - The topic currently has `diskless.enable` set to `false` - The altered topic config does not include `diskless.enable` This commit enforces the same partition validations for those implicitly diskless topics as for explicit ones.
|
@giuseppelillo good catch, it was indeed an issue 👍 I added the fix and tests e92e01c |
…POD-2464] (#634) * feat(inkless): add controller guards for classic-to-diskless switch Enforce the following guards when altering the topic config to use diskless.enable=true: - Partitions are online - No reassignment in progress - Enough ISRs [POD-2464] * fix(inkless): block unclean election via electLeaders API during pending diskless switch * fix(inkless): detect implicit diskless switch in legacy AlterConfigs The legacy AlterConfigs API will implicitly switch to diskless in the following conditions: - Broker level config `log.diskless.enable` is `true` - The topic currently has `diskless.enable` set to `false` - The altered topic config does not include `diskless.enable` This commit enforces the same partition validations for those implicitly diskless topics as for explicit ones.
Enforce the following guards when altering the topic config to use diskless.enable=true: