-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-17011: Fix a bug preventing features from supporting v0 #16421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
1c1e556
6235178
1dc5f99
c0e3c7c
3e07a3b
ab1b566
234ea4b
04ecd25
890c184
6ad6749
c1ba1f5
85a827a
d6eb186
f4bd3cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,7 @@ | |
|
|
||
| import java.nio.ByteBuffer; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.Optional; | ||
| import java.util.Set; | ||
|
|
||
|
|
@@ -49,6 +50,71 @@ public class ApiVersionsResponse extends AbstractResponse { | |
|
|
||
| private final ApiVersionsResponseData data; | ||
|
|
||
| public static class Builder { | ||
| private Errors error = Errors.NONE; | ||
| private int throttleTimeMs = 0; | ||
| private ApiVersionCollection apiVersions = null; | ||
| private Features<SupportedVersionRange> supportedFeatures = null; | ||
| private Map<String, Short> finalizedFeatures = null; | ||
| private long finalizedFeaturesEpoch = 0; | ||
| private boolean zkMigrationEnabled = false; | ||
| private boolean suppressFeatureLevel0 = false; | ||
|
|
||
| public Builder setError(Errors error) { | ||
| this.error = error; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder setThrottleTimeMs(int throttleTimeMs) { | ||
| this.throttleTimeMs = throttleTimeMs; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder setApiVersions(ApiVersionCollection apiVersions) { | ||
| this.apiVersions = apiVersions; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder setSupportedFeatures(Features<SupportedVersionRange> supportedFeatures) { | ||
| this.supportedFeatures = supportedFeatures; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder setFinalizedFeatures(Map<String, Short> finalizedFeatures) { | ||
| this.finalizedFeatures = finalizedFeatures; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder setFinalizedFeaturesEpoch(long finalizedFeaturesEpoch) { | ||
| this.finalizedFeaturesEpoch = finalizedFeaturesEpoch; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder setZkMigrationEnabled(boolean zkMigrationEnabled) { | ||
| this.zkMigrationEnabled = zkMigrationEnabled; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder setSuppressFeatureLevel0(boolean suppressFeatureLevel0) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suppressFeatureLevel0 => alterFeatureLevel0 ? |
||
| this.suppressFeatureLevel0 = suppressFeatureLevel0; | ||
| return this; | ||
| } | ||
|
|
||
| public ApiVersionsResponse build() { | ||
| final ApiVersionsResponseData data = new ApiVersionsResponseData(); | ||
| data.setErrorCode(error.code()); | ||
| data.setApiKeys(Objects.requireNonNull(apiVersions)); | ||
| data.setThrottleTimeMs(throttleTimeMs); | ||
| data.setSupportedFeatures( | ||
| maybeFilterSupportedFeatureKeys(Objects.requireNonNull(supportedFeatures), suppressFeatureLevel0)); | ||
| data.setFinalizedFeatures( | ||
| createFinalizedFeatureKeys(Objects.requireNonNull(finalizedFeatures))); | ||
| data.setFinalizedFeaturesEpoch(finalizedFeaturesEpoch); | ||
| data.setZkMigrationReady(zkMigrationEnabled); | ||
| return new ApiVersionsResponse(data); | ||
| } | ||
| } | ||
|
|
||
| public ApiVersionsResponse(ApiVersionsResponseData data) { | ||
| super(ApiKeys.API_VERSIONS); | ||
| this.data = data; | ||
|
|
@@ -105,65 +171,32 @@ public static ApiVersionsResponse parse(ByteBuffer buffer, short version) { | |
| } | ||
| } | ||
|
|
||
| public static ApiVersionsResponse createApiVersionsResponse( | ||
| int throttleTimeMs, | ||
| public static ApiVersionCollection controllerApiVersions( | ||
| RecordVersion minRecordVersion, | ||
| Features<SupportedVersionRange> latestSupportedFeatures, | ||
| Map<String, Short> finalizedFeatures, | ||
| long finalizedFeaturesEpoch, | ||
| NodeApiVersions controllerApiVersions, | ||
| ListenerType listenerType, | ||
| boolean enableUnstableLastVersion, | ||
| boolean zkMigrationEnabled, | ||
| boolean clientTelemetryEnabled | ||
| ) { | ||
| ApiVersionCollection apiKeys; | ||
| if (controllerApiVersions != null) { | ||
| apiKeys = intersectForwardableApis( | ||
| listenerType, | ||
| minRecordVersion, | ||
| controllerApiVersions.allSupportedApiVersions(), | ||
| enableUnstableLastVersion, | ||
| clientTelemetryEnabled | ||
| ); | ||
| } else { | ||
| apiKeys = filterApis( | ||
| minRecordVersion, | ||
| listenerType, | ||
| enableUnstableLastVersion, | ||
| clientTelemetryEnabled | ||
| ); | ||
| } | ||
|
|
||
| return createApiVersionsResponse( | ||
| throttleTimeMs, | ||
| apiKeys, | ||
| latestSupportedFeatures, | ||
| finalizedFeatures, | ||
| finalizedFeaturesEpoch, | ||
| zkMigrationEnabled | ||
| ); | ||
| return intersectForwardableApis( | ||
| listenerType, | ||
| minRecordVersion, | ||
| controllerApiVersions.allSupportedApiVersions(), | ||
| enableUnstableLastVersion, | ||
| clientTelemetryEnabled); | ||
| } | ||
|
|
||
| public static ApiVersionsResponse createApiVersionsResponse( | ||
| int throttleTimeMs, | ||
| ApiVersionCollection apiVersions, | ||
| Features<SupportedVersionRange> latestSupportedFeatures, | ||
| Map<String, Short> finalizedFeatures, | ||
| long finalizedFeaturesEpoch, | ||
| boolean zkMigrationEnabled | ||
| public static ApiVersionCollection brokerApiVersions( | ||
| RecordVersion minRecordVersion, | ||
| ListenerType listenerType, | ||
| boolean enableUnstableLastVersion, | ||
| boolean clientTelemetryEnabled | ||
| ) { | ||
| return new ApiVersionsResponse( | ||
| createApiVersionsResponseData( | ||
| throttleTimeMs, | ||
| Errors.NONE, | ||
| apiVersions, | ||
| latestSupportedFeatures, | ||
| finalizedFeatures, | ||
| finalizedFeaturesEpoch, | ||
| zkMigrationEnabled | ||
| ) | ||
| ); | ||
| return filterApis( | ||
| minRecordVersion, | ||
| listenerType, | ||
| enableUnstableLastVersion, | ||
| clientTelemetryEnabled); | ||
| } | ||
|
|
||
| public static ApiVersionCollection filterApis( | ||
|
|
@@ -249,37 +282,24 @@ public static ApiVersionCollection intersectForwardableApis( | |
| return apiKeys; | ||
| } | ||
|
|
||
| private static ApiVersionsResponseData createApiVersionsResponseData( | ||
| final int throttleTimeMs, | ||
| final Errors error, | ||
| final ApiVersionCollection apiKeys, | ||
| final Features<SupportedVersionRange> latestSupportedFeatures, | ||
| final Map<String, Short> finalizedFeatures, | ||
| final long finalizedFeaturesEpoch, | ||
| final boolean zkMigrationEnabled | ||
| private static SupportedFeatureKeyCollection maybeFilterSupportedFeatureKeys( | ||
| Features<SupportedVersionRange> latestSupportedFeatures, | ||
|
cmccabe marked this conversation as resolved.
|
||
| boolean suppressV0 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suppressV0 => alterV0 ? |
||
| ) { | ||
| final ApiVersionsResponseData data = new ApiVersionsResponseData(); | ||
| data.setThrottleTimeMs(throttleTimeMs); | ||
| data.setErrorCode(error.code()); | ||
| data.setApiKeys(apiKeys); | ||
| data.setSupportedFeatures(createSupportedFeatureKeys(latestSupportedFeatures)); | ||
| data.setFinalizedFeatures(createFinalizedFeatureKeys(finalizedFeatures)); | ||
| data.setFinalizedFeaturesEpoch(finalizedFeaturesEpoch); | ||
| data.setZkMigrationReady(zkMigrationEnabled); | ||
|
|
||
| return data; | ||
| } | ||
|
|
||
| private static SupportedFeatureKeyCollection createSupportedFeatureKeys( | ||
| Features<SupportedVersionRange> latestSupportedFeatures) { | ||
| SupportedFeatureKeyCollection converted = new SupportedFeatureKeyCollection(); | ||
| for (Map.Entry<String, SupportedVersionRange> feature : latestSupportedFeatures.features().entrySet()) { | ||
| final SupportedFeatureKey key = new SupportedFeatureKey(); | ||
| final SupportedVersionRange versionRange = feature.getValue(); | ||
| key.setName(feature.getKey()); | ||
| key.setMinVersion(versionRange.min()); | ||
| key.setMaxVersion(versionRange.max()); | ||
| converted.add(key); | ||
| // Some older clients will have deserialization problems if a feature's | ||
| // minimum supported level is 0. Therefore, when preparing ApiVersionResponse | ||
| // at versions less than 4, we must leave those features out. See KAFKA-17011 | ||
| // for details. | ||
| if (!(suppressV0 && versionRange.min() == 0)) { | ||
| final SupportedFeatureKey key = new SupportedFeatureKey(); | ||
| key.setName(feature.getKey()); | ||
| key.setMinVersion(versionRange.min()); | ||
| key.setMaxVersion(versionRange.max()); | ||
| converted.add(key); | ||
| } | ||
| } | ||
|
|
||
| return converted; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |
| import org.apache.kafka.common.protocol.Errors; | ||
|
|
||
| import java.nio.ByteBuffer; | ||
| import java.util.Iterator; | ||
|
|
||
| public class BrokerRegistrationRequest extends AbstractRequest { | ||
|
|
||
|
|
@@ -45,7 +46,21 @@ public short oldestAllowedVersion() { | |
|
|
||
| @Override | ||
| public BrokerRegistrationRequest build(short version) { | ||
| return new BrokerRegistrationRequest(data, version); | ||
| if (version < 4) { | ||
| // Workaround for KAFKA-17011: for BrokerRegistrationRequest versions older than 4, | ||
| // exclude support version ranges that begin with 0. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we still need to finalize/agree here about omission vs. using 1 for older versions of the api. Right now we have omit for broker registration, but using version 1 for ApiVersions. We also have the option to use 1 for both or omit for both. Note: In 3.8, we omit for both requests. I think there is some benefit in not including a version with 0 to older controllers rather than 1, but can also understand that it is confusing to have different approaches for different APIs. @junrao I know you had some opinions here.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems that it's simpler for the old BrokerRegistrationRequest to be consistent with the older version of ApiVersionResponse, i.e., to include the feature but set the minSupportedVersion to 1, instead of 0.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK. Just like in |
||
| BrokerRegistrationRequestData newData = data.duplicate(); | ||
| for (Iterator<BrokerRegistrationRequestData.Feature> iter = newData.features().iterator(); | ||
|
chia7712 marked this conversation as resolved.
|
||
| iter.hasNext(); ) { | ||
| BrokerRegistrationRequestData.Feature feature = iter.next(); | ||
| if (feature.minSupportedVersion() == 0) { | ||
| iter.remove(); | ||
| } | ||
| } | ||
| return new BrokerRegistrationRequest(newData, version); | ||
| } else { | ||
| return new BrokerRegistrationRequest(data, version); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -27,7 +27,9 @@ | |
| // | ||
| // Starting from Apache Kafka 2.4 (KIP-511), ApiKeys field is populated with the supported | ||
| // versions of the ApiVersionsRequest when an UNSUPPORTED_VERSION error is returned. | ||
| "validVersions": "0-3", | ||
| // | ||
| // Version 4 fixes KAFKA-17011, which blocked SupportedFeatures.MinVersion from being 0. | ||
| "validVersions": "0-4", | ||
| "flexibleVersions": "3+", | ||
| "fields": [ | ||
| { "name": "ErrorCode", "type": "int16", "versions": "0+", | ||
|
|
@@ -45,7 +47,7 @@ | |
| "about": "The duration in milliseconds for which the request was throttled due to a quota violation, or zero if the request did not violate any quota." }, | ||
| { "name": "SupportedFeatures", "type": "[]SupportedFeatureKey", "ignorable": true, | ||
| "versions": "3+", "tag": 0, "taggedVersions": "3+", | ||
| "about": "Features supported by the broker.", | ||
| "about": "Features supported by the broker. Note: in v0-v3, features with MinSupportedVersion = 0 must be left out.", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This needs to be adjusted accordingly since we now keep the feature but returns 1 as the minSupportedVersion.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
done |
||
| "fields": [ | ||
| { "name": "Name", "type": "string", "versions": "3+", "mapKey": true, | ||
| "about": "The name of the feature." }, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,12 +18,15 @@ | |
| // Version 2 adds LogDirs for KIP-858 | ||
|
|
||
| // Version 3 adds the PreviousBrokerEpoch for the KIP-966 | ||
|
|
||
| // Version 4 fixes KAFKA-17011, which blocked SupportedFeatures.MinVersion in the response from being 0. | ||
|
|
||
| { | ||
| "apiKey":62, | ||
| "type": "request", | ||
| "listeners": ["controller"], | ||
| "name": "BrokerRegistrationRequest", | ||
| "validVersions": "0-3", | ||
| "validVersions": "0-4", | ||
|
cmccabe marked this conversation as resolved.
|
||
| "flexibleVersions": "0+", | ||
| "fields": [ | ||
| { "name": "BrokerId", "type": "int32", "versions": "0+", "entityType": "brokerId", | ||
|
|
@@ -45,7 +48,7 @@ | |
| ] | ||
| }, | ||
| { "name": "Features", "type": "[]Feature", | ||
| "about": "The features on this broker", "versions": "0+", "fields": [ | ||
| "about": "The features on this broker. Note: in v0-v3, features with MinSupportedVersion = 0 must be left out.", "versions": "0+", "fields": [ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This needs to be adjusted accordingly since we now keep the feature but returns 1 as the minSupportedVersion.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| { "name": "Name", "type": "string", "versions": "0+", "mapKey": true, | ||
| "about": "The feature name." }, | ||
| { "name": "MinSupportedVersion", "type": "int16", "versions": "0+", | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suppressFeatureLevel0 => alterFeatureLevel0 ?