-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-7007: All ACL changes should use single /kafka-acl-changes path #5161
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
16f5597
259df49
6013925
b29bd45
237db81
9185513
5769d56
7046b0c
b919bd1
3280303
ae50f4e
23bb972
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 |
|---|---|---|
|
|
@@ -944,9 +944,10 @@ class KafkaZkClient private (zooKeeperClient: ZooKeeperClient, isSecure: Boolean | |
| * Creates the required zk nodes for Acl storage | ||
| */ | ||
| def createAclPaths(): Unit = { | ||
| createRecursive(AclChangeNotificationZNode.path, throwIfPathExists = false) | ||
|
|
||
| ZkAclStore.stores.foreach(store => { | ||
| createRecursive(store.aclPath, throwIfPathExists = false) | ||
| createRecursive(store.aclChangePath, throwIfPathExists = false) | ||
| ResourceType.values.foreach(resourceType => createRecursive(store.path(resourceType), throwIfPathExists = false)) | ||
| }) | ||
| } | ||
|
|
@@ -1005,12 +1006,27 @@ class KafkaZkClient private (zooKeeperClient: ZooKeeperClient, isSecure: Boolean | |
| } | ||
|
|
||
| /** | ||
| * Creates Acl change notification message | ||
| * @param resource resource name | ||
| * Creates colon separated Acl change notification message. | ||
| * | ||
| * Kafka 2.0 saw the format of the ACL change event change from a 'colon separated' string, to a JSON message. | ||
|
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. The comment needs to be adjusted since we are no longer using JSON.
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. Good catch - done. |
||
| * This method will create a message in the old format, which is still needed while a cluster has | ||
| * 'inter.broker.protocol.version' < 2.0. | ||
| * | ||
| * @param resource resource pattern that has changed | ||
| */ | ||
| def createLegacyAclChangeNotification(resource: Resource): Unit = { | ||
|
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. Would it be better to have a single createAclChangeNotification() method and pass in a isLegacy flag?
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.
I like it being more explicit in the function name than some boolean flag. If you feel strongly I can change. |
||
| val path = AclChangeNotificationSequenceZNode.createPath | ||
| val createRequest = CreateRequest(path, AclChangeNotificationSequenceZNode.encodeLegacy(resource), acls(path), CreateMode.PERSISTENT_SEQUENTIAL) | ||
| val createResponse = retryRequestUntilConnected(createRequest) | ||
| createResponse.maybeThrow | ||
| } | ||
|
|
||
| /** | ||
| * Creates JSON Acl change notification message. | ||
| * @param resource resource pattern that has changed | ||
| */ | ||
| def createAclChangeNotification(resource: Resource): Unit = { | ||
| val store = ZkAclStore(resource.nameType) | ||
| val path = store.changeSequenceZNode.createPath | ||
| val path = AclChangeNotificationSequenceZNode.createPath | ||
| val createRequest = CreateRequest(path, AclChangeNotificationSequenceZNode.encode(resource), acls(path), CreateMode.PERSISTENT_SEQUENTIAL) | ||
| val createResponse = retryRequestUntilConnected(createRequest) | ||
| createResponse.maybeThrow | ||
|
|
@@ -1034,24 +1050,21 @@ class KafkaZkClient private (zooKeeperClient: ZooKeeperClient, isSecure: Boolean | |
| * @throws KeeperException if there is an error while deleting Acl change notifications | ||
| */ | ||
| def deleteAclChangeNotifications(): Unit = { | ||
| ZkAclStore.stores.foreach(store => { | ||
| val getChildrenResponse = retryRequestUntilConnected(GetChildrenRequest(store.aclChangePath)) | ||
| if (getChildrenResponse.resultCode == Code.OK) { | ||
| deleteAclChangeNotifications(store, getChildrenResponse.children) | ||
| } else if (getChildrenResponse.resultCode != Code.NONODE) { | ||
| getChildrenResponse.maybeThrow | ||
| } | ||
| }) | ||
| val getChildrenResponse = retryRequestUntilConnected(GetChildrenRequest(AclChangeNotificationZNode.path)) | ||
| if (getChildrenResponse.resultCode == Code.OK) { | ||
| deleteAclChangeNotifications(getChildrenResponse.children) | ||
| } else if (getChildrenResponse.resultCode != Code.NONODE) { | ||
| getChildrenResponse.maybeThrow | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Deletes the Acl change notifications associated with the given sequence nodes | ||
| * @param sequenceNodes | ||
| */ | ||
| private def deleteAclChangeNotifications(store: ZkAclStore, sequenceNodes: Seq[String]): Unit = { | ||
| val aclChangeNotificationSequenceZNode = store.changeSequenceZNode | ||
| private def deleteAclChangeNotifications(sequenceNodes: Seq[String]): Unit = { | ||
| val deleteRequests = sequenceNodes.map { sequenceNode => | ||
| DeleteRequest(aclChangeNotificationSequenceZNode.deletePath(sequenceNode), ZkVersion.NoVersion) | ||
| DeleteRequest(AclChangeNotificationSequenceZNode.deletePath(sequenceNode), ZkVersion.NoVersion) | ||
| } | ||
|
|
||
| val deleteResponses = retryRequestsUntilConnected(deleteRequests) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,7 @@ import kafka.security.auth.{Acl, Resource, ResourceType} | |
| import kafka.server.{ConfigType, DelegationTokenManager} | ||
| import kafka.utils.Json | ||
| import org.apache.kafka.common.TopicPartition | ||
| import org.apache.kafka.common.errors.UnsupportedVersionException | ||
| import org.apache.kafka.common.network.ListenerName | ||
| import org.apache.kafka.common.resource.ResourceNameType | ||
| import org.apache.kafka.common.security.auth.SecurityProtocol | ||
|
|
@@ -42,6 +43,7 @@ import scala.beans.BeanProperty | |
| import scala.collection.JavaConverters._ | ||
| import scala.collection.mutable.ArrayBuffer | ||
| import scala.collection.{Seq, breakOut} | ||
| import scala.util.{Failure, Success, Try} | ||
|
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. unused imports Failure, Success
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. |
||
|
|
||
| // This file contains objects for encoding/decoding data stored in ZooKeeper nodes (znodes). | ||
|
|
||
|
|
@@ -461,22 +463,12 @@ case class ZkAclStore(nameType: ResourceNameType) { | |
| val aclPath: String = nameType match { | ||
| case ResourceNameType.LITERAL => "/kafka-acl" | ||
| case ResourceNameType.PREFIXED => "/kafka-prefixed-acl" | ||
| case _ => throw new IllegalArgumentException("Unknown name type:" + nameType) | ||
| } | ||
|
|
||
| val aclChangePath: String = nameType match { | ||
| case ResourceNameType.LITERAL => "/kafka-acl-changes" | ||
| case ResourceNameType.PREFIXED => "/kafka-prefixed-acl-changes" | ||
| case _ => throw new IllegalArgumentException("Unknown name type:" + nameType) | ||
| case _ => throw new IllegalArgumentException("Invalid name type:" + nameType) | ||
| } | ||
|
|
||
| def path(resourceType: ResourceType) = s"$aclPath/$resourceType" | ||
|
|
||
| def path(resourceType: ResourceType, resourceName: String): String = s"$aclPath/$resourceType/$resourceName" | ||
|
|
||
| def changeSequenceZNode: AclChangeNotificationSequenceZNode = AclChangeNotificationSequenceZNode(this) | ||
|
|
||
| def decode(notificationMessage: Array[Byte]): Resource = AclChangeNotificationSequenceZNode.decode(nameType, notificationMessage) | ||
| } | ||
|
|
||
| object ZkAclStore { | ||
|
|
@@ -485,7 +477,7 @@ object ZkAclStore { | |
| .map(nameType => ZkAclStore(nameType)) | ||
|
|
||
| val securePaths: Seq[String] = stores | ||
| .flatMap(store => List(store.aclPath, store.aclChangePath)) | ||
| .flatMap(store => List(store.aclPath)) ++ Seq(AclChangeNotificationZNode.path) | ||
| } | ||
|
|
||
| object ResourceZNode { | ||
|
|
@@ -495,26 +487,49 @@ object ResourceZNode { | |
| def decode(bytes: Array[Byte], stat: Stat): VersionedAcls = VersionedAcls(Acl.fromBytes(bytes), stat.getVersion) | ||
| } | ||
|
|
||
| object AclChangeNotificationZNode { | ||
| def path = "/kafka-acl-changes" | ||
| } | ||
|
|
||
| case class AclChangeEvent(@BeanProperty @JsonProperty("version") version: Int, | ||
|
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 class is no longer used.
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. |
||
| @BeanProperty @JsonProperty("resourceType") resourceType: String, | ||
| @BeanProperty @JsonProperty("name") name: String, | ||
| @BeanProperty @JsonProperty("resourceNameType") resourceNameType: String) { | ||
| if (version > AclChangeEvent.currentVersion) | ||
| throw new UnsupportedVersionException(s"Acl change event received for unsupported version: $version") | ||
|
|
||
| def toResource : Try[Resource] = { | ||
| for { | ||
| resType <- Try(ResourceType.fromString(resourceType)) | ||
| nameType <- Try(ResourceNameType.valueOf(resourceNameType)) | ||
| resource = Resource(resType, name, nameType) | ||
| } yield resource | ||
| } | ||
| } | ||
|
|
||
| object AclChangeEvent { | ||
| val currentVersion: Int = 1 | ||
|
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 is no longer needed?
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. No, and done. |
||
| } | ||
|
|
||
| object AclChangeNotificationSequenceZNode { | ||
| val Separator = ":" | ||
| def SequenceNumberPrefix = "acl_changes_" | ||
|
|
||
| def encode(resource: Resource): Array[Byte] = { | ||
| (resource.resourceType.name + Separator + resource.name).getBytes(UTF_8) | ||
| } | ||
| def createPath = s"${AclChangeNotificationZNode.path}/$SequenceNumberPrefix" | ||
| def deletePath(sequenceNode: String) = s"${AclChangeNotificationZNode.path}/$sequenceNode" | ||
|
|
||
| def decode(nameType: ResourceNameType, bytes: Array[Byte]): Resource = { | ||
| val str = new String(bytes, UTF_8) | ||
| str.split(Separator, 2) match { | ||
| case Array(resourceType, name, _*) => Resource(ResourceType.fromString(resourceType), name, nameType) | ||
| case _ => throw new IllegalArgumentException("expected a string in format ResourceType:ResourceName but got " + str) | ||
| } | ||
| def encodeLegacy(resource: Resource): Array[Byte] = { | ||
| if (resource.nameType != ResourceNameType.LITERAL) | ||
| throw new IllegalArgumentException("Only literal resource patterns can be encoded") | ||
|
|
||
| val legacyName = resource.resourceType + Resource.Separator + resource.name | ||
| legacyName.getBytes(UTF_8) | ||
| } | ||
| } | ||
|
|
||
| case class AclChangeNotificationSequenceZNode(store: ZkAclStore) { | ||
| def createPath = s"${store.aclChangePath}/${AclChangeNotificationSequenceZNode.SequenceNumberPrefix}" | ||
| def deletePath(sequenceNode: String) = s"${store.aclChangePath}/$sequenceNode" | ||
| def encode(resource: Resource): Array[Byte] = | ||
| resource.toString.getBytes(UTF_8) | ||
|
|
||
| def decode(bytes: Array[Byte]): Resource = | ||
| Resource.fromString(new String(bytes, UTF_8)) | ||
| } | ||
|
|
||
| object ClusterZNode { | ||
|
|
||
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.
Hmm, this is called by LiteralAclChangeStore.decode(), which should only decode a 2-part name.
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.
Good spot. I've changed LiteralAclChangeStore.decode() to have its own impl. (Basically the old contents of Resource.fromString), to maintain v1.1 behaviour