Skip to content
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

KAFKA-13889: Fix AclsDelta to handle ACCESS_CONTROL_ENTRY_RECORD quickly followed by REMOVE_ACCESS_CONTROL_ENTRY_RECORD for same ACL #12160

Merged
merged 3 commits into from May 19, 2022

Conversation

andymg3
Copy link
Contributor

@andymg3 andymg3 commented May 13, 2022

JIRA

https://issues.apache.org/jira/browse/KAFKA-13889

Description

Testing

  • Added unit tests for new behavior

Committer Checklist (excluded from commit message)

  • Verify design and implementation
  • Verify test coverage and CI build status
  • Verify documentation (including upgrade notes)

@andymg3 andymg3 force-pushed the KAFKA-13889 branch 3 times, most recently from 57a6b08 to 3ef6626 Compare May 18, 2022 13:59
@jsancio jsancio self-requested a review May 18, 2022 16:51
Copy link
Member

@jsancio jsancio left a comment

Choose a reason for hiding this comment

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

Thanks for the improvements @andymg3

Comment on lines +64 to +86
if (image.acls().containsKey(record.id())) {
changes.put(record.id(), Optional.empty());
} else if (changes.containsKey(record.id())) {
changes.remove(record.id());
Copy link
Member

Choose a reason for hiding this comment

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

Btw, the Jira mentions that we don't have "remove acl" to be idempotent. "Remove acl" is idempotent if they are process in the same batch/delta.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mind clarifying? With the exception thrown below I think ACL removal is still not idempotent. If an ACL is added, then removed, then removed again, then the exception below will be thrown as it would have been removed from the Map.

Copy link
Member

Choose a reason for hiding this comment

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

You are correct. I missed the else if.

} else if (changes.containsKey(record.id())) {
changes.remove(record.id());
} else {
throw new RuntimeException("Failed to find existing ACL with ID " + record.id() + " in either image or changes");
Copy link
Member

Choose a reason for hiding this comment

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

Can we throw an IllegalStateException?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree that exception is more appropriate. I went with RuntimeException because thats what we throw in AclControlManager (https://github.com/apache/kafka/blob/trunk/metadata/src/main/java/org/apache/kafka/controller/AclControlManager.java#L202) when we come across a removal unexpectedly. My thought process was to be consistent. Having said that, they are two different components so I'm fine updating it here to IllegalStateException. Do you think we should throw the same exception in AclControlManager?

@@ -61,7 +61,13 @@ public void replay(AccessControlEntryRecord record) {
}

public void replay(RemoveAccessControlEntryRecord record) {
Copy link
Member

Choose a reason for hiding this comment

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

Can we document this method? Seems subtle enough to warrant documentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will add a java doc to this method.

changes.remove(record.id());
} else {
throw new RuntimeException("Failed to find existing ACL with ID " + record.id() + " in either image or changes");
}
Copy link
Member

Choose a reason for hiding this comment

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

This comment is for:

public Map<Uuid, Optional<StandardAcl>> changes() {
    return changes;
}

Can we document this method? I am particularly interested on the return type. I get the impression that a value of Optional.empty should interpreted as a delete.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's correct and that's the existing behavior. I'll add a comment clarifying that.

import java.util.Optional;

@Timeout(40)
public class AclsDeltaTest {
Copy link
Member

Choose a reason for hiding this comment

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

I am okay moving these test to AclImageTest. The two types are closely related. We already have "delta" tests in the "image" suite.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm yeah that's true. I guess I don't see a big downside of having a separate test class as obviously it is different to some degree. So will keep the separate test class unless you have concerns.


delta.replay(inputAclRecord);
assertTrue(delta.changes().containsKey(aclId));
assertTrue(delta.changes().get(aclId).isPresent());
Copy link
Member

Choose a reason for hiding this comment

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

I think you can remove this since you are implicitly testing this in the line below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True but delta.changes().get(aclId) could return null in which case we'd throw a NPE which would be a bit more challenging to debug from the test logs in my view so will leave as is unless you have concerns.

Copy link
Member

Choose a reason for hiding this comment

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

Agree but now that you have assertEquals(Optional.of(testStandardAcl()), delta.changes().get(aclId)); there shouldn't be a NPE, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. I think I can get rid of those two above assertions now. I just tested this with an invalid input to the Map and saw:

AclsDeltaTest > testRemovesDeleteIfNotInImage() FAILED
    org.opentest4j.AssertionFailedError: expected: <Optional[StandardAcl(resourceType=ANY, resourceName=foo, patternType=ANY, principal=User:user, host=host, operation=ANY, permissionType=ANY)]> but was: <null>

So we should be safe from seeing a NPE. Will update.

delta.replay(inputAclRecord);
assertTrue(delta.changes().containsKey(aclId));
assertTrue(delta.changes().get(aclId).isPresent());
assertEquals(testStandardAcl(), delta.changes().get(aclId).get());
Copy link
Member

Choose a reason for hiding this comment

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

You can remove the get() and compare it against Optional.of(testStandardAcl()).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree that's cleaner and it prevents NPEs if delta.changes().get(aclId) returns null

}

@Test
public void testThrowsExceptionOnInvalidState() {
Copy link
Member

Choose a reason for hiding this comment

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

Can we also test with an AclsImage that has ACLs but for the one being removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will add.

Copy link
Member

@jsancio jsancio left a comment

Choose a reason for hiding this comment

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

Thanks for the update. Just one minor comment.

Comment on lines +64 to +86
if (image.acls().containsKey(record.id())) {
changes.put(record.id(), Optional.empty());
} else if (changes.containsKey(record.id())) {
changes.remove(record.id());
Copy link
Member

Choose a reason for hiding this comment

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

You are correct. I missed the else if.


delta.replay(inputAclRecord);
assertTrue(delta.changes().containsKey(aclId));
assertTrue(delta.changes().get(aclId).isPresent());
Copy link
Member

Choose a reason for hiding this comment

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

Agree but now that you have assertEquals(Optional.of(testStandardAcl()), delta.changes().get(aclId)); there shouldn't be a NPE, right?

Copy link
Member

@jsancio jsancio left a comment

Choose a reason for hiding this comment

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

LGTM. The test failures seem unrelated:

Build / JDK 8 and Scala 2.12 / testSeekAndCommitWithBrokerFailures() – kafka.api.ConsumerBounceTest3s
Build / ARM / testListenerConnectionRateLimitWhenActualRateAboveLimit() – kafka.network.ConnectionQuotasTest

@jsancio jsancio merged commit a8e3a25 into apache:trunk May 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants