This is an automated email from the ASF dual-hosted git repository.
roryqi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 584890d261 [#10411] feat(core): Extend metadata object owner to
support group (#10848)
584890d261 is described below
commit 584890d261ed46ec31c843182d0dd38c1f766f59
Author: Bharath Krishna <[email protected]>
AuthorDate: Thu Apr 23 23:26:58 2026 -0700
[#10411] feat(core): Extend metadata object owner to support group (#10848)
### What changes were proposed in this pull request?
Remove the USER-only restriction in `OwnerManager.setOwner()` and add
GROUP support to `notifyOwnerChange()`, enabling groups to be set as
metadata object owners.
Changes:
- Remove `Preconditions.checkArgument(ownerType == Owner.Type.USER)`
guard in `OwnerManager.setOwner()`
- Extend `notifyOwnerChange()` to resolve group entity IDs
- Fix `GroupEntity` namespace in test (was using `ofUserNamespace`,
corrected to `ofGroupNamespace`)
- Replace `testGroupTypeOwnerNotSupported` with tests that verify group
ownership works
### Why are the changes needed?
The owner field was implicitly assumed to point to a User. In practice,
the logical owner of a resource (Schema, Table, etc.) is often a team
(Group) rather than an individual. The downstream layers (REST API,
DTOs, relational store) already support GROUP — only `OwnerManager` had
a guard blocking it.
Fix: #10411
### Does this PR introduce _any_ user-facing change?
Yes. The `setOwner` API now accepts `Owner.Type.GROUP` in addition to
`Owner.Type.USER`.
### How was this patch tested?
Unit tests in `TestOwnerManager`:
- `testGroupTypeOwner`: Sets group as owner, verifies, then replaces
with user owner (exercises `notifyOwnerChange` GROUP path)
- `testSetNonExistentGroupAsOwner`: Verifies `NotFoundException` for
non-existent group
---
.../integration/test/authorization/OwnerIT.java | 10 +++--
.../gravitino/authorization/OwnerManager.java | 41 ++++++++++++--------
.../gravitino/authorization/TestOwnerManager.java | 44 +++++++++++++++++-----
3 files changed, 66 insertions(+), 29 deletions(-)
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/OwnerIT.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/OwnerIT.java
index e76356f1e6..4427dcaf53 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/OwnerIT.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/OwnerIT.java
@@ -447,12 +447,16 @@ public class OwnerIT extends BaseIT {
metalake.addGroup(groupName);
// Now set the owner of catalog to the group
- Assertions.assertThrows(
- IllegalArgumentException.class,
- () -> metalake.setOwner(catalogObject, groupName, Owner.Type.GROUP));
+ metalake.setOwner(catalogObject, groupName, Owner.Type.GROUP);
+ Owner groupOwner = metalake.getOwner(catalogObject).get();
+ Assertions.assertEquals(groupName, groupOwner.name());
+ Assertions.assertEquals(Owner.Type.GROUP, groupOwner.type());
// Drop the group
ordinaryClient.removeGroup(groupName);
+ // The owner should be removed as well
+ Owner ownerAfterGroupDrop =
metalake.getOwner(catalogObject).orElse(null);
+ Assertions.assertNull(ownerAfterGroupDrop);
}
// Cleanup
client.disableMetalake(metalakeNameE);
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/OwnerManager.java
b/core/src/main/java/org/apache/gravitino/authorization/OwnerManager.java
index 5b7ce28c98..70e10f1488 100644
--- a/core/src/main/java/org/apache/gravitino/authorization/OwnerManager.java
+++ b/core/src/main/java/org/apache/gravitino/authorization/OwnerManager.java
@@ -18,7 +18,6 @@
*/
package org.apache.gravitino.authorization;
-import com.google.common.base.Preconditions;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
@@ -65,9 +64,6 @@ public class OwnerManager implements OwnerDispatcher {
@Override
public void setOwner(
String metalake, MetadataObject metadataObject, String ownerName,
Owner.Type ownerType) {
- // TODO(ISSUE-9375): Support GROUP type owner in the future.
- Preconditions.checkArgument(
- ownerType == Owner.Type.USER, "Only USER type is supported as owner
currently.");
NameIdentifier objectIdent = MetadataObjectUtil.toEntityIdent(metalake,
metadataObject);
try {
Optional<Owner> originOwner = getOwner(metalake, metadataObject);
@@ -113,6 +109,8 @@ public class OwnerManager implements OwnerDispatcher {
newOwner.name = ownerName;
newOwner.type = Owner.Type.GROUP;
+ } else {
+ throw new IllegalArgumentException("Unsupported owner type: " +
ownerType);
}
AuthorizationUtils.callAuthorizationPluginForMetadataObject(
@@ -139,8 +137,9 @@ public class OwnerManager implements OwnerDispatcher {
private void notifyOwnerChange(Owner oldOwner, String metalake,
MetadataObject metadataObject) {
GravitinoAuthorizer gravitinoAuthorizer =
GravitinoEnv.getInstance().gravitinoAuthorizer();
if (gravitinoAuthorizer != null) {
- if (oldOwner.type() == Owner.Type.USER) {
- try {
+ try {
+ Long oldOwnerId;
+ if (oldOwner.type() == Owner.Type.USER) {
UserEntity userEntity =
GravitinoEnv.getInstance()
.entityStore()
@@ -148,17 +147,27 @@ public class OwnerManager implements OwnerDispatcher {
NameIdentifierUtil.ofUser(metalake, oldOwner.name()),
Entity.EntityType.USER,
UserEntity.class);
- gravitinoAuthorizer.handleMetadataOwnerChange(
- metalake,
- userEntity.id(),
- MetadataObjectUtil.toEntityIdent(metalake, metadataObject),
- Entity.EntityType.valueOf(metadataObject.type().name()));
- } catch (IOException e) {
- LOG.warn(e.getMessage(), e);
+ oldOwnerId = userEntity.id();
+ } else if (oldOwner.type() == Owner.Type.GROUP) {
+ GroupEntity groupEntity =
+ GravitinoEnv.getInstance()
+ .entityStore()
+ .get(
+ NameIdentifierUtil.ofGroup(metalake, oldOwner.name()),
+ Entity.EntityType.GROUP,
+ GroupEntity.class);
+ oldOwnerId = groupEntity.id();
+ } else {
+ LOG.warn("Unsupported owner type: {}", oldOwner.type());
+ return;
}
- } else {
- throw new UnsupportedOperationException(
- "Notification for Group Owner is not supported yet.");
+ gravitinoAuthorizer.handleMetadataOwnerChange(
+ metalake,
+ oldOwnerId,
+ MetadataObjectUtil.toEntityIdent(metalake, metadataObject),
+ Entity.EntityType.valueOf(metadataObject.type().name()));
+ } catch (IOException e) {
+ LOG.warn(e.getMessage(), e);
}
}
}
diff --git
a/core/src/test/java/org/apache/gravitino/authorization/TestOwnerManager.java
b/core/src/test/java/org/apache/gravitino/authorization/TestOwnerManager.java
index 1840a44251..56dd78f797 100644
---
a/core/src/test/java/org/apache/gravitino/authorization/TestOwnerManager.java
+++
b/core/src/test/java/org/apache/gravitino/authorization/TestOwnerManager.java
@@ -67,9 +67,13 @@ import org.apache.gravitino.storage.RandomIdGenerator;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
import org.mockito.Mockito;
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class TestOwnerManager {
private static final String JDBC_STORE_PATH =
"/tmp/gravitino_jdbc_entityStore_" +
UUID.randomUUID().toString().replace("-", "");
@@ -152,7 +156,7 @@ public class TestOwnerManager {
.withName(GROUP)
.withRoleNames(Collections.emptyList())
.withRoleIds(Collections.emptyList())
- .withNamespace(AuthorizationUtils.ofUserNamespace(METALAKE))
+ .withNamespace(AuthorizationUtils.ofGroupNamespace(METALAKE))
.withAuditInfo(audit)
.build();
entityStore.put(groupEntity, false /* overwritten*/);
@@ -176,6 +180,7 @@ public class TestOwnerManager {
}
@Test
+ @Order(1)
public void testOwner() {
// Test no owner
MetadataObject metalakeObject =
@@ -205,18 +210,37 @@ public class TestOwnerManager {
}
@Test
- public void testGroupTypeOwnerNotSupported() {
- // Test that GROUP type owner is not supported and throws
IllegalArgumentException
+ @Order(2)
+ public void testGroupTypeOwner() {
MetadataObject metalakeObject =
MetadataObjects.of(Lists.newArrayList(METALAKE),
MetadataObject.Type.METALAKE);
- // Verify that setting GROUP type owner throws IllegalArgumentException
- IllegalArgumentException exception =
- Assertions.assertThrows(
- IllegalArgumentException.class,
- () -> ownerManager.setOwner(METALAKE, metalakeObject, GROUP,
Owner.Type.GROUP));
+ // Test to set the group as the owner
+ ownerManager.setOwner(METALAKE, metalakeObject, GROUP, Owner.Type.GROUP);
+ Mockito.verify(authorizationPlugin, Mockito.atLeastOnce())
+ .onOwnerSet(Mockito.any(), Mockito.any(), Mockito.any());
+
+ Owner owner = ownerManager.getOwner(METALAKE, metalakeObject).get();
+ Assertions.assertEquals(GROUP, owner.name());
+ Assertions.assertEquals(Owner.Type.GROUP, owner.type());
+
+ // Test replacing group owner with user owner
+ ownerManager.setOwner(METALAKE, metalakeObject, USER, Owner.Type.USER);
+ Owner replacedOwner = ownerManager.getOwner(METALAKE,
metalakeObject).get();
+ Assertions.assertEquals(USER, replacedOwner.name());
+ Assertions.assertEquals(Owner.Type.USER, replacedOwner.type());
+ }
- Assertions.assertEquals(
- "Only USER type is supported as owner currently.",
exception.getMessage());
+ @Test
+ @Order(3)
+ public void testSetNonExistentGroupAsOwner() {
+ MetadataObject metalakeObject =
+ MetadataObjects.of(Lists.newArrayList(METALAKE),
MetadataObject.Type.METALAKE);
+
+ Assertions.assertThrows(
+ NotFoundException.class,
+ () ->
+ ownerManager.setOwner(
+ METALAKE, metalakeObject, "non-existent-group",
Owner.Type.GROUP));
}
}