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

Fix src and dest namespace handling in clone table operation #5334

Open
wants to merge 5 commits into
base: 2.1
Choose a base branch
from

Conversation

dlmarion
Copy link
Contributor

Closes #5324

@dlmarion dlmarion added this to the 2.1.4 milestone Feb 13, 2025
@dlmarion dlmarion self-assigned this Feb 13, 2025
@dlmarion dlmarion requested a review from ctubbsii February 13, 2025 22:26
Copy link
Member

@ctubbsii ctubbsii left a comment

Choose a reason for hiding this comment

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

Aside from the comment I made about the exception handling and related comments, this change looks good... but may not go far enough.

A good test of one of the problems with this code that this PR should fix, is to try to clone a table from a namespace, n1, where the user has no permissions, into a table in namespace, n2, where they have create table permission and table read permission. Such a test should fail prior to these fixes, and should pass after these fixes.

In addition to these changes (after my comments are addressed), I think more needs to be done in this PR to fully address the underlying issues. Specifically, the fate operation CloneTable currently reserves the srcNamespaceId, but I think it needs to reserve the destination namespaceId instead. I'm not sure it needs to reserve the source namespace at all. In one of the subsequent fate steps, it also recomputes the destination namespaceId, and then clobbers it, and then tries to reserve the destination namespace if it's different than the source namespace. It doesn't need to recompute the destination namespace if that was already done, and it may not need to make a second reservation, either.

Comment on lines 299 to 302
} catch (TableNotFoundException e) {
// shouldn't happen, but possible once cloning between namespaces is supported
throw new ThriftTableOperationException(srcTableId.canonical(), null, tableOp,
TableOperationExceptionType.NOTFOUND, "");
Copy link
Member

Choose a reason for hiding this comment

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

This shouldn't be added to the existing try block, because it's specific to the new line to look up the source namespaceId. It's not applicable to the retrieval of the namespaceId for the destination table name.

Also, the comment in here isn't applicable to this exception. The reason this shouldn't happen is because the source tableId should exist, and it did at some point prior to this call, when we looked up the tableId from the name that was passed in to the client request to clone. So, the proper comment should be something like // could happen if the table was deleted while processing this request

(Also, not related to this PR, but the comment about the NamespaceNotFoundException saying "shouldn't happen" doesn't make sense because cloning between namespaces is already supported.)

It also occurs to me that we need to make sure that you can't clone into the built-in accumulo namespace. There might be a check for that elsewhere... I'm not sure. But this should be tested.

@dlmarion
Copy link
Contributor Author

dlmarion commented Feb 14, 2025

A good test of one of the problems with this code that this PR should fix, is to try to clone a table from a namespace, n1, where the user has no permissions, into a table in namespace, n2, where they have create table permission and table read permission. Such a test should fail prior to these fixes, and should pass after these fixes.

This test fails because the code calls flush on the src table in namespace n1 where the user has no permissions.

EDIT: I have the test working now. I misunderstood one of your statements.

@dlmarion
Copy link
Contributor Author

A good test of one of the problems with this code that this PR should fix, is to try to clone a table from a namespace, n1, where the user has no permissions, into a table in namespace, n2, where they have create table permission and table read permission. Such a test should fail prior to these fixes, and should pass after these fixes.

The testCloneNamespacePermissions included in this PR, which gives the necessary permissions to a user and clones a table from one namespace to another, succeeds in the 2.1 branch.

@dlmarion dlmarion requested a review from ctubbsii February 14, 2025 16:10
Copy link
Member

@ctubbsii ctubbsii left a comment

Choose a reason for hiding this comment

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

Looks good overall. I'm not 100% certain about whether the source namespace must also be reserved (I think no, but not sure), and I made some suggestions to add to the test coverage. The fact that there's a flush involved, and that some permissions required on the source table for that, mitigate the security risks of this a bit, but it's still a bit concerning that one might be able to clone with only alter-table or write permission on the source and without read permission on the source.

Comment on lines +46 to +48
long val = Utils.reserveNamespace(environment, cloneInfo.getNamespaceId(), tid, false, true,
TableOperation.CLONE);
val += Utils.reserveTable(environment, cloneInfo.srcTableId, tid, false, true,
val += Utils.reserveTable(environment, cloneInfo.getSrcTableId(), tid, false, true,
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 this is right, but I don't know fate well enough. I'd like to hear from @keith-turner about whether this is sufficient for the clone operation:

  1. reserve destination namespace (READ lock)
  2. reserve source table (READ lock)

I don't think we need to reserve the source namespace as well (I think reserving the source table is sufficient), but I'm not 100% certain about that.

Copy link
Contributor

Choose a reason for hiding this comment

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

Its good to reserve the source namespace because it prevents it from being deleted or renamed during this operation.

Copy link
Member

Choose a reason for hiding this comment

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

The source namespace can't be deleted because the source table can't be deleted. I don't think we care about the source namespace being renamed, so long as we only look up the ID once. If a rename happens, we shouldn't care.

Copy link
Contributor

Choose a reason for hiding this comment

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

Looked into this a bit more and opened #5389. If #5389 were fixed then it would be good if this code got a read lock on the source namespace. Most other code will get the read lock on a namespace and then lock the table, it would be good to follow that pattern here w/ the soruce namespace planning that #5389 will be fixed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would also be good to eventually get the lock on the dest namespace like the code used to do.

Copy link
Contributor

@keith-turner keith-turner Mar 7, 2025

Choose a reason for hiding this comment

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

The source namespace can't be deleted because the source table can't be deleted. I don't think we care about the source namespace being renamed, so long as we only look up the ID once. If a rename happens, we shouldn't care.

Just saw this comment, did not see when I posted the comment about #5389. The model I assumed all fate table operations were following was :

  1. All table operations get a read lock on the namespace they are operating on
  2. All destructive namespace operations get a write lock on the namespace.

Destructive namespace ops do not lock each table in the namespace, but the assumption that every table op gets a namespace read lock makes this unnecessary. If per table ops do not lock the namespace and namepsace ops do not lock the tables then nothing prevents them from running concurrently.

Copy link
Contributor

Choose a reason for hiding this comment

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

We could try to optimize the locking via transitive assumptions I suppose. Like clone table can assume delete table will get a read lock on the namesapce and a write lock on the table and therefore it does not need to get the namespace lock only the table read lock. None of the fate operations work like this currently. They just get the read lock on the namespace and then the table lock. To me its much easier to reason about if all table ops follow the same protocol.

Copy link
Contributor

Choose a reason for hiding this comment

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

If we do want to look into optimizing namespace locking that seems fine. I would advise doing that in this PR though. Making clone table, that is not called frequently, do namespace locking differently than all other fate operations seems confusing. If there is a performance benefit in optimizing, it would be good to do all table operations in the same PR and modify frequently called operations like bulk import and compact to get this benefit.

@@ -37,20 +35,12 @@ class CloneZookeeper extends ManagerRepo {
public CloneZookeeper(CloneInfo cloneInfo, ClientContext context)
throws NamespaceNotFoundException {
this.cloneInfo = cloneInfo;
this.cloneInfo.namespaceId = Namespaces.getNamespaceId(context,
Copy link
Member

Choose a reason for hiding this comment

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

This is going to change the serialization, and could cause problems with a 2.1.4 upgrade if there are any ongoing clone operations. That should be noted in the release notes for 2.1.4.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How so? namespaceId is resolved in FateServiceHandler, passed to CloneTable and set in CloneInfo there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, are you saying that this may cause pre-2.1.4 Clone fate operations to fail, and that I should still check and set this if it's null in CloneInfo?

Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure exactly what to expect when the serialization of the fate operation's classes are changed, but if a clonetable operation was serialized with 2.1.3, and then interrupted, and then resumed after upgrading to 2.1.4, the deserialization in order to resume the operation will probably fail in some way. I'm not sure if you need to do anything to check it in the code... maybe just warn users that they should make sure there's no outstanding clone operations before doing an upgrade.

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 made a change in 3cfb06c to handle the case of pre-2.1.4 fate transactions with a note to remove that handling in the 3.1 merge.

Copy link
Member

Choose a reason for hiding this comment

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

Seems like that will handle the case I was concerned about, but I was also just generally concerned about any slight change to CloneInfo that might break deserialization of the older version.

Comment on lines +404 to +405
client.securityOperations().grantTablePermission(newUserName, srcTableName,
TablePermission.READ);
Copy link
Member

Choose a reason for hiding this comment

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

This is the expected success case. However, prior to this fix, I believe if this was set on the destination namespace, but NOT on the source table, then the operation would still succeed. The test should check that case, to make sure that it succeeds before this fix (to verify the bug), and fails after this fix.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added suggested test in 3cfb06c. I will see if it succeeds in the 2.1 branch.

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 copied this test to the 2.1 branch and is succeeds as is with no modifications. Based on what you are saying above, this test should have failed because the clone operation should have succeeded instead of throwing an exception.

Copy link
Member

@ctubbsii ctubbsii Mar 7, 2025

Choose a reason for hiding this comment

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

I copied the test to 2.1 myself and it failed on the assertThrows... (the clone succeeded with the wrong permissions). I'm not sure why it would succeed for you, but I've tried several times, and am certain that I'm on the 2.1 branch without your fixes, and the test fails.

Copy link
Member

Choose a reason for hiding this comment

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

I also confirmed it passes in your branch after the fixes.

@dlmarion dlmarion requested a review from keith-turner March 5, 2025 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

CloneTable does not properly determine and validate the source and destination namespaceIds
3 participants