-
Notifications
You must be signed in to change notification settings - Fork 14.9k
KAFKA-19931 Align broker and controller behavior for the Admin.incrementalAlterConfigs API #21005
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
base: trunk
Are you sure you want to change the base?
Changes from 7 commits
1fcfe35
cc33873
f5f78e7
59d3be2
c8d913f
f8d01df
a165eb5
48184c2
1ff2a4c
da21c8b
fdbb81c
a0198d9
d6ae031
138e8e8
72e787b
bfd3860
2a4c9ea
d388063
6df68ba
bb8c33a
16ca216
ca12d22
ac4b124
b07102e
500b7ae
8d1cf15
d896bfb
a690de7
e7251e2
374c301
59264ff
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 |
|---|---|---|
|
|
@@ -721,45 +721,40 @@ class ControllerApis( | |
| util.Map[String, Entry[AlterConfigOp.OpType, String]]]() | ||
| val brokerLoggerResponses = new util.ArrayList[AlterConfigsResourceResponse](1) | ||
| alterConfigsRequest.data.resources.forEach { resource => | ||
| val configResource = new ConfigResource( | ||
| ConfigResource.Type.forId(resource.resourceType), resource.resourceName()) | ||
| if (configResource.`type`().equals(ConfigResource.Type.BROKER_LOGGER)) { | ||
| val apiError = try { | ||
| runtimeLoggerManager.applyChangesForResource( | ||
| authHelper.authorize(request.context, CLUSTER_ACTION, CLUSTER, CLUSTER_NAME), | ||
| alterConfigsRequest.data().validateOnly(), | ||
| resource) | ||
| ApiError.NONE | ||
| } catch { | ||
| case t: Throwable => ApiError.fromThrowable(t) | ||
| } | ||
| brokerLoggerResponses.add(new AlterConfigsResourceResponse(). | ||
| setResourceName(resource.resourceName()). | ||
| setResourceType(resource.resourceType()). | ||
| setErrorCode(apiError.error().code()). | ||
| setErrorMessage(if (apiError.isFailure) apiError.messageWithFallback() else null)) | ||
| } else if (configResource.`type`().equals(ConfigResource.Type.UNKNOWN)) { | ||
| response.responses().add(new AlterConfigsResourceResponse(). | ||
| setErrorCode(UNSUPPORTED_VERSION.code()). | ||
| setErrorMessage("Unknown resource type " + resource.resourceType() + "."). | ||
| setResourceName(resource.resourceName()). | ||
| setResourceType(resource.resourceType())) | ||
| } else if (!duplicateResources.contains(configResource)) { | ||
| val altersByName = new util.HashMap[String, Entry[AlterConfigOp.OpType, String]]() | ||
| resource.configs.forEach { config => | ||
| altersByName.put(config.name, new util.AbstractMap.SimpleEntry[AlterConfigOp.OpType, String]( | ||
| AlterConfigOp.OpType.forId(config.configOperation), config.value)) | ||
| } | ||
| if (configChanges.put(configResource, altersByName) != null) { | ||
| duplicateResources.add(configResource) | ||
| configChanges.remove(configResource) | ||
| response.responses().add(new AlterConfigsResourceResponse(). | ||
| setErrorCode(INVALID_REQUEST.code()). | ||
| setErrorMessage("Duplicate resource."). | ||
| setResourceName(resource.resourceName()). | ||
| setResourceType(resource.resourceType())) | ||
| ConfigAdminManager.processConfigResource( | ||
| resource, | ||
| onBrokerLogger = () => { | ||
| val apiError = try { | ||
| runtimeLoggerManager.applyChangesForResource( | ||
| authHelper.authorize(request.context, CLUSTER_ACTION, CLUSTER, CLUSTER_NAME), | ||
| alterConfigsRequest.data().validateOnly(), | ||
| resource) | ||
| ApiError.NONE | ||
| } catch { | ||
| case t: Throwable => ApiError.fromThrowable(t) | ||
| } | ||
| brokerLoggerResponses.add(new AlterConfigsResourceResponse() | ||
| .setResourceName(resource.resourceName()) | ||
| .setResourceType(resource.resourceType()) | ||
| .setErrorCode(apiError.error().code()) | ||
| .setErrorMessage(if (apiError.isFailure) apiError.messageWithFallback() else null)) | ||
| }, | ||
| configResource => { | ||
| addConfigChangesOrHandleDuplicate(configResource, resource, duplicateResources, configChanges, response) | ||
| }, | ||
| configResource => { | ||
| addConfigChangesOrHandleDuplicate(configResource, resource, duplicateResources, configChanges, response) | ||
| }, | ||
| onError = (_, t) => { | ||
| val err = ApiError.fromThrowable(t) | ||
| error(s"Error on processing incrementalAlterConfigs request on ${resource.resourceName()}", t) | ||
| response.responses().add(new AlterConfigsResourceResponse() | ||
| .setErrorCode(err.error().code()) | ||
| .setErrorMessage(err.messageWithFallback()) | ||
| .setResourceName(resource.resourceName()) | ||
| .setResourceType(resource.resourceType())) | ||
| } | ||
| } | ||
| ) | ||
| } | ||
| val iterator = configChanges.keySet().iterator() | ||
| while (iterator.hasNext) { | ||
|
|
@@ -792,6 +787,32 @@ class ControllerApis( | |
| } | ||
| } | ||
|
|
||
| private def addConfigChangesOrHandleDuplicate( | ||
| configResource: ConfigResource, | ||
| resource: IncrementalAlterConfigsRequestData.AlterConfigsResource, | ||
| duplicateResources: util.HashSet[ConfigResource], | ||
| configChanges: util.HashMap[ConfigResource, util.Map[String, Entry[AlterConfigOp.OpType, String]]], | ||
| response: IncrementalAlterConfigsResponseData | ||
| ): Unit = { | ||
| if (!duplicateResources.contains(configResource)) { | ||
|
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. Could we use
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 meant that the
Collaborator
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. The old logic worked in three steps:
Given this behavior, I think we cannot rely solely on a map to implement this logic. |
||
| val altersByName = new util.HashMap[String, Entry[AlterConfigOp.OpType, String]]() | ||
| resource.configs.forEach { config => | ||
| altersByName.put(config.name, new util.AbstractMap.SimpleEntry[AlterConfigOp.OpType, String]( | ||
| AlterConfigOp.OpType.forId(config.configOperation), config.value)) | ||
| } | ||
|
|
||
| if (configChanges.put(configResource, altersByName) != null) { | ||
| duplicateResources.add(configResource) | ||
| configChanges.remove(configResource) | ||
| response.responses().add(new AlterConfigsResourceResponse() | ||
| .setErrorCode(INVALID_REQUEST.code()) | ||
| .setErrorMessage("Duplicate resource.") | ||
| .setResourceName(resource.resourceName()) | ||
| .setResourceType(resource.resourceType())) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private def handleCreatePartitions(request: RequestChannel.Request): CompletableFuture[Unit] = { | ||
| def filterAlterAuthorizedTopics(topics: Iterable[String]): Set[String] = { | ||
| authHelper.filterByAuthorized(request.context, ALTER, TOPIC, topics)(n => n) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -652,7 +652,7 @@ class ControllerApisTest { | |
| setResourceName("3"). | ||
| setResourceType(ConfigResource.Type.BROKER.id()), | ||
| new AlterConfigsResourceResponse(). | ||
| setErrorCode(UNSUPPORTED_VERSION.code()). | ||
| setErrorCode(INVALID_REQUEST.code()). | ||
|
Collaborator
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. The controller error code should match the broker error code. |
||
| setErrorMessage("Unknown resource type 124."). | ||
| setResourceName("foo"). | ||
| setResourceType(124.toByte), | ||
|
|
@@ -679,6 +679,38 @@ class ControllerApisTest { | |
| response.data().responses().asScala.toSet) | ||
| } | ||
|
|
||
| @Test | ||
| def testInvalidIncrementalAlterConfigsWithNullResources(): Unit = { | ||
| val requestData = new IncrementalAlterConfigsRequestData().setResources( | ||
| new AlterConfigsResourceCollection(util.Arrays.asList( | ||
| new AlterConfigsResource(). | ||
| setResourceName("3"). | ||
| setResourceType(ConfigResource.Type.BROKER.id()). | ||
| setConfigs(new AlterableConfigCollection(util.Arrays.asList(new AlterableConfig(). | ||
| setName("my.custom.config"). | ||
| setValue(null). | ||
| setConfigOperation(AlterConfigOp.OpType.SET.id())).iterator())) | ||
| ).iterator())) | ||
| val request = buildRequest(new IncrementalAlterConfigsRequest.Builder(requestData).build(0)) | ||
| controllerApis = createControllerApis(None, new MockController.Builder().build()) | ||
| controllerApis.handleIncrementalAlterConfigs(request) | ||
| val capturedResponse: ArgumentCaptor[AbstractResponse] = | ||
| ArgumentCaptor.forClass(classOf[AbstractResponse]) | ||
| verify(requestChannel).sendResponse( | ||
| ArgumentMatchers.eq(request), | ||
| capturedResponse.capture(), | ||
| ArgumentMatchers.eq(None)) | ||
| assertNotNull(capturedResponse.getValue) | ||
| val response = capturedResponse.getValue.asInstanceOf[IncrementalAlterConfigsResponse] | ||
| assertEquals(Set( | ||
| new AlterConfigsResourceResponse(). | ||
| setErrorCode(INVALID_REQUEST.code()). | ||
| setErrorMessage("Null value not supported for : my.custom.config"). | ||
| setResourceName("3"). | ||
| setResourceType(ConfigResource.Type.BROKER.id())), | ||
| response.data().responses().asScala.toSet) | ||
| } | ||
|
|
||
| @Test | ||
| def testUnauthorizedHandleAlterPartitionReassignments(): Unit = { | ||
| assertThrows(classOf[ClusterAuthorizationException], () => { | ||
|
|
||
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.
Why not throwing the exception directly? The
onErrormethod should handle it, right?