This is an automated email from the ASF dual-hosted git repository.
jshao pushed a commit to branch branch-0.6
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/branch-0.6 by this push:
new 1d43f49d6 [#4873] feat(core,server): Supports to list group (#4879)
(#5036)
1d43f49d6 is described below
commit 1d43f49d64ca75fb7728407a687a04ec4a5fe9ca
Author: roryqi <[email protected]>
AuthorDate: Fri Sep 27 20:10:50 2024 +0800
[#4873] feat(core,server): Supports to list group (#4879) (#5036)
### What changes were proposed in this pull request?
Supports to list groups.
### Why are the changes needed?
Fix: #4873
### Does this PR introduce _any_ user-facing change?
I will add the document later.
### How was this patch tested?
UT.
Co-authored-by: Qiang-Liu <[email protected]>
---
.../apache/gravitino/client/GravitinoClient.java | 20 +++
.../apache/gravitino/client/GravitinoMetalake.java | 39 ++++++
.../org/apache/gravitino/client/TestUserGroup.java | 52 ++++++++
.../gravitino/dto/responses/GroupListResponse.java | 74 ++++++++++++
.../apache/gravitino/dto/util/DTOConverters.java | 13 ++
.../authorization/AccessControlDispatcher.java | 18 +++
.../authorization/AccessControlManager.java | 10 ++
.../gravitino/authorization/UserGroupManager.java | 61 +++++++---
.../hook/AccessControlHookDispatcher.java | 10 ++
.../gravitino/storage/relational/JDBCBackend.java | 2 +
.../storage/relational/mapper/GroupMetaMapper.java | 9 ++
.../mapper/GroupMetaSQLProviderFactory.java | 11 +-
.../provider/base/GroupMetaBaseSQLProvider.java | 36 ++++++
.../mapper/provider/h2/GroupMetaH2Provider.java | 52 ++++++++
.../storage/relational/po/ExtendedGroupPO.java | 59 +++++++++
.../relational/service/GroupMetaService.java | 32 +++++
.../storage/relational/utils/POConverters.java | 54 ++++++++-
.../authorization/TestAccessControlManager.java | 22 ++++
.../relational/service/TestGroupMetaService.java | 84 ++++++++++++-
.../test/authorization/AccessControlIT.java | 30 +++++
.../gravitino/server/web/rest/GroupOperations.java | 32 +++++
.../server/web/rest/TestGroupOperations.java | 134 ++++++++++++++++++---
22 files changed, 811 insertions(+), 43 deletions(-)
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
index a7656fd02..a8a46ff8f 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java
@@ -227,6 +227,26 @@ public class GravitinoClient extends GravitinoClientBase
return getMetalake().getGroup(group);
}
+ /**
+ * List the groups.
+ *
+ * @return The Group list
+ * @throws NoSuchMetalakeException If the Metalake with the given name does
not exist.
+ */
+ public Group[] listGroups() throws NoSuchMetalakeException {
+ return getMetalake().listGroups();
+ }
+
+ /**
+ * List the group names.
+ *
+ * @return The group names list.
+ * @throws NoSuchMetalakeException If the Metalake with the given name does
not exist.
+ */
+ public String[] listGroupNames() throws NoSuchMetalakeException {
+ return getMetalake().listGroupNames();
+ }
+
/**
* Gets a Role.
*
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
index 8f98b6fd3..4905681b7 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java
@@ -62,6 +62,7 @@ import org.apache.gravitino.dto.responses.DeleteResponse;
import org.apache.gravitino.dto.responses.DropResponse;
import org.apache.gravitino.dto.responses.EntityListResponse;
import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.dto.responses.GroupListResponse;
import org.apache.gravitino.dto.responses.GroupResponse;
import org.apache.gravitino.dto.responses.NameListResponse;
import org.apache.gravitino.dto.responses.OwnerResponse;
@@ -635,6 +636,44 @@ public class GravitinoMetalake extends MetalakeDTO
return resp.getGroup();
}
+ /**
+ * Lists the groups
+ *
+ * @return The Group list
+ * @throws NoSuchMetalakeException If the Metalake with the given name does
not exist.
+ */
+ public Group[] listGroups() throws NoSuchMetalakeException {
+ Map<String, String> params = new HashMap<>();
+ params.put("details", "true");
+
+ GroupListResponse resp =
+ restClient.get(
+ String.format(API_METALAKES_GROUPS_PATH, name(),
BLANK_PLACEHOLDER),
+ params,
+ GroupListResponse.class,
+ Collections.emptyMap(),
+ ErrorHandlers.groupErrorHandler());
+ resp.validate();
+ return resp.getGroups();
+ }
+
+ /**
+ * Lists the group names
+ *
+ * @return The Group Name List
+ * @throws NoSuchMetalakeException If the Metalake with the given name does
not exist.
+ */
+ public String[] listGroupNames() throws NoSuchMetalakeException {
+ NameListResponse resp =
+ restClient.get(
+ String.format(API_METALAKES_GROUPS_PATH, name(),
BLANK_PLACEHOLDER),
+ NameListResponse.class,
+ Collections.emptyMap(),
+ ErrorHandlers.groupErrorHandler());
+ resp.validate();
+ return resp.getNames();
+ }
+
/**
* Gets a Role.
*
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestUserGroup.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestUserGroup.java
index 67a3035ed..ff98b2ca6 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestUserGroup.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestUserGroup.java
@@ -23,8 +23,10 @@ import static
javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import static javax.servlet.http.HttpServletResponse.SC_OK;
import static org.apache.hc.core5.http.HttpStatus.SC_SERVER_ERROR;
+import com.fasterxml.jackson.core.JsonProcessingException;
import java.time.Instant;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import org.apache.gravitino.authorization.Group;
import org.apache.gravitino.authorization.User;
@@ -35,6 +37,7 @@ import org.apache.gravitino.dto.authorization.UserDTO;
import org.apache.gravitino.dto.requests.GroupAddRequest;
import org.apache.gravitino.dto.requests.UserAddRequest;
import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.dto.responses.GroupListResponse;
import org.apache.gravitino.dto.responses.GroupResponse;
import org.apache.gravitino.dto.responses.MetalakeResponse;
import org.apache.gravitino.dto.responses.NameListResponse;
@@ -327,6 +330,55 @@ public class TestUserGroup extends TestBase {
Assertions.assertThrows(RuntimeException.class, () ->
gravitinoClient.removeGroup(groupName));
}
+ @Test
+ public void testListGroupNames() throws JsonProcessingException {
+ String groupPath = withSlash(String.format(API_METALAKES_GROUPS_PATH,
metalakeName, ""));
+ NameListResponse listResponse = new NameListResponse(new String[]
{"group1", "group2"});
+ buildMockResource(Method.GET, groupPath, null, listResponse, SC_OK);
+ Assertions.assertArrayEquals(
+ new String[] {"group1", "group2"}, gravitinoClient.listGroupNames());
+ ErrorResponse errRespNoMetaLake =
+ ErrorResponse.notFound(NoSuchMetalakeException.class.getSimpleName(),
"metalake not found");
+ buildMockResource(Method.GET, groupPath, null, errRespNoMetaLake,
SC_NOT_FOUND);
+ Exception ex =
+ Assertions.assertThrows(
+ NoSuchMetalakeException.class, () ->
gravitinoClient.listGroupNames());
+ Assertions.assertEquals("metalake not found", ex.getMessage());
+
+ // Test RuntimeException
+ ErrorResponse errResp = ErrorResponse.internalError("internal error");
+ buildMockResource(Method.GET, groupPath, null, errResp, SC_SERVER_ERROR);
+
+ Assertions.assertThrows(RuntimeException.class, () ->
gravitinoClient.listGroupNames());
+ }
+
+ @Test
+ public void testListGroups() throws JsonProcessingException {
+ String groupPath = withSlash(String.format(API_METALAKES_GROUPS_PATH,
metalakeName, ""));
+ GroupDTO group1 = mockGroupDTO("group1");
+ GroupDTO group2 = mockGroupDTO("group2");
+ GroupDTO group3 = mockGroupDTO("group3");
+ Map<String, String> params = new HashMap<>();
+ GroupListResponse listResponse = new GroupListResponse(new GroupDTO[]
{group1, group2, group3});
+ buildMockResource(Method.GET, groupPath, params, null, listResponse,
SC_OK);
+
+ Group[] groups = gravitinoClient.listGroups();
+ Assertions.assertEquals(3, groups.length);
+ assertGroup(group1, groups[0]);
+ assertGroup(group2, groups[1]);
+ assertGroup(group3, groups[2]);
+ ErrorResponse errResNoMetaLake =
+ ErrorResponse.notFound(NoSuchMetalakeException.class.getSimpleName(),
"metalake not found");
+ buildMockResource(Method.GET, groupPath, params, null, errResNoMetaLake,
SC_NOT_FOUND);
+ Exception ex =
+ Assertions.assertThrows(NoSuchMetalakeException.class, () ->
gravitinoClient.listGroups());
+ Assertions.assertEquals("metalake not found", ex.getMessage());
+ // Test RuntimeException
+ ErrorResponse errResp = ErrorResponse.internalError("internal error");
+ buildMockResource(Method.GET, groupPath, params, null, errResp,
SC_SERVER_ERROR);
+ Assertions.assertThrows(RuntimeException.class, () ->
gravitinoClient.listGroups());
+ }
+
private UserDTO mockUserDTO(String name) {
return UserDTO.builder()
.withName(name)
diff --git
a/common/src/main/java/org/apache/gravitino/dto/responses/GroupListResponse.java
b/common/src/main/java/org/apache/gravitino/dto/responses/GroupListResponse.java
new file mode 100644
index 000000000..271fb9a92
--- /dev/null
+++
b/common/src/main/java/org/apache/gravitino/dto/responses/GroupListResponse.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.dto.responses;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import java.util.Arrays;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.dto.authorization.GroupDTO;
+
+/** Represents a response for a list of groups. */
+@Getter
+@ToString
+@EqualsAndHashCode(callSuper = true)
+public class GroupListResponse extends BaseResponse {
+
+ @JsonProperty("groups")
+ private final GroupDTO[] groups;
+
+ /**
+ * Constructor for GroupListResponse.
+ *
+ * @param groups The array of group DTOs.
+ */
+ public GroupListResponse(GroupDTO[] groups) {
+ super(0);
+ this.groups = groups;
+ }
+
+ /** Default constructor for GroupListResponse. (Used for Jackson
deserialization.) */
+ public GroupListResponse() {
+ super();
+ this.groups = null;
+ }
+
+ /**
+ * Validates the response data.
+ *
+ * @throws IllegalArgumentException if the name or audit is not set.
+ */
+ @Override
+ public void validate() throws IllegalArgumentException {
+ super.validate();
+
+ Preconditions.checkArgument(groups != null, "groups must not be null");
+ Arrays.stream(groups)
+ .forEach(
+ group -> {
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(group.name()), "group 'name' must not
be blank");
+ Preconditions.checkArgument(
+ group.auditInfo() != null, "group 'auditInfo' must not be
null");
+ });
+ }
+}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
index 8e706c139..38224493b 100644
--- a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
+++ b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
@@ -691,6 +691,19 @@ public class DTOConverters {
return
Arrays.stream(users).map(DTOConverters::toDTO).toArray(UserDTO[]::new);
}
+ /**
+ * Converts an array of Groups to an array of GroupDTOs.
+ *
+ * @param groups The groups to be converted.
+ * @return The array of GroupDTOs.
+ */
+ public static GroupDTO[] toDTOs(Group[] groups) {
+ if (ArrayUtils.isEmpty(groups)) {
+ return new GroupDTO[0];
+ }
+ return
Arrays.stream(groups).map(DTOConverters::toDTO).toArray(GroupDTO[]::new);
+ }
+
/**
* Converts a DistributionDTO to a Distribution.
*
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
index 3214c187f..a3919512b 100644
---
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
+++
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
@@ -129,6 +129,24 @@ public interface AccessControlDispatcher {
Group getGroup(String metalake, String group)
throws NoSuchGroupException, NoSuchMetalakeException;
+ /**
+ * List groups
+ *
+ * @param metalake The Metalake of the Group.
+ * @return The list of groups
+ * @throws NoSuchMetalakeException If the Metalake with the given name does
not exist.
+ */
+ Group[] listGroups(String metalake);
+
+ /**
+ * List group names
+ *
+ * @param metalake The Metalake of the Group.
+ * @return The list of group names
+ * @throws NoSuchMetalakeException If the Metalake with the given name does
not exist.
+ */
+ String[] listGroupNames(String metalake);
+
/**
* Grant roles to a user.
*
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
index c2f2976aa..75b0f9f1e 100644
---
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
+++
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
@@ -95,6 +95,16 @@ public class AccessControlManager implements
AccessControlDispatcher {
return userGroupManager.getGroup(metalake, group);
}
+ @Override
+ public Group[] listGroups(String metalake) throws NoSuchMetalakeException {
+ return userGroupManager.listGroups(metalake);
+ }
+
+ @Override
+ public String[] listGroupNames(String metalake) throws
NoSuchMetalakeException {
+ return userGroupManager.listGroupNames(metalake);
+ }
+
@Override
public User grantRolesToUser(String metalake, List<String> roles, String
user)
throws NoSuchUserException, NoSuchRoleException, NoSuchMetalakeException
{
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/UserGroupManager.java
b/core/src/main/java/org/apache/gravitino/authorization/UserGroupManager.java
index be1b687f3..cd852ab66 100644
---
a/core/src/main/java/org/apache/gravitino/authorization/UserGroupManager.java
+++
b/core/src/main/java/org/apache/gravitino/authorization/UserGroupManager.java
@@ -24,6 +24,7 @@ import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import org.apache.gravitino.Entity;
+import org.apache.gravitino.Entity.EntityType;
import org.apache.gravitino.EntityAlreadyExistsException;
import org.apache.gravitino.EntityStore;
import org.apache.gravitino.Namespace;
@@ -124,23 +125,6 @@ class UserGroupManager {
return listUsersInternal(metalake, true /* allFields */);
}
- private User[] listUsersInternal(String metalake, boolean allFields) {
- try {
- AuthorizationUtils.checkMetalakeExists(metalake);
-
- Namespace namespace = AuthorizationUtils.ofUserNamespace(metalake);
- return store
- .list(namespace, UserEntity.class, Entity.EntityType.USER, allFields)
- .toArray(new User[0]);
- } catch (NoSuchEntityException e) {
- LOG.error("Metalake {} does not exist", metalake, e);
- throw new NoSuchMetalakeException(METALAKE_DOES_NOT_EXIST_MSG, metalake);
- } catch (IOException ioe) {
- LOG.error("Listing user under metalake {} failed due to storage issues",
metalake, ioe);
- throw new RuntimeException(ioe);
- }
- }
-
Group addGroup(String metalake, String group) throws
GroupAlreadyExistsException {
try {
AuthorizationUtils.checkMetalakeExists(metalake);
@@ -197,4 +181,47 @@ class UserGroupManager {
throw new RuntimeException(ioe);
}
}
+
+ Group[] listGroups(String metalake) {
+ return listGroupInternal(metalake, true);
+ }
+
+ String[] listGroupNames(String metalake) {
+ return Arrays.stream(listGroupInternal(metalake, false))
+ .map(Group::name)
+ .toArray(String[]::new);
+ }
+
+ private User[] listUsersInternal(String metalake, boolean allFields) {
+ try {
+ AuthorizationUtils.checkMetalakeExists(metalake);
+
+ Namespace namespace = AuthorizationUtils.ofUserNamespace(metalake);
+ return store
+ .list(namespace, UserEntity.class, Entity.EntityType.USER, allFields)
+ .toArray(new User[0]);
+ } catch (NoSuchEntityException e) {
+ LOG.error("Metalake {} does not exist", metalake, e);
+ throw new NoSuchMetalakeException(METALAKE_DOES_NOT_EXIST_MSG, metalake);
+ } catch (IOException ioe) {
+ LOG.error("Listing user under metalake {} failed due to storage issues",
metalake, ioe);
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ private Group[] listGroupInternal(String metalake, boolean allFields) {
+ try {
+ AuthorizationUtils.checkMetalakeExists(metalake);
+ Namespace namespace = AuthorizationUtils.ofGroupNamespace(metalake);
+ return store
+ .list(namespace, GroupEntity.class, EntityType.GROUP, allFields)
+ .toArray(new Group[0]);
+ } catch (NoSuchEntityException e) {
+ LOG.error("Metalake {} does not exist", metalake, e);
+ throw new NoSuchMetalakeException(METALAKE_DOES_NOT_EXIST_MSG, metalake);
+ } catch (IOException ioe) {
+ LOG.error("Listing group under metalake {} failed due to storage
issues", metalake, ioe);
+ throw new RuntimeException(ioe);
+ }
+ }
}
diff --git
a/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java
b/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java
index 65ed2c9da..e16974764 100644
---
a/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java
+++
b/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java
@@ -98,6 +98,16 @@ public class AccessControlHookDispatcher implements
AccessControlDispatcher {
return dispatcher.getGroup(metalake, group);
}
+ @Override
+ public Group[] listGroups(String metalake) throws NoSuchMetalakeException {
+ return dispatcher.listGroups(metalake);
+ }
+
+ @Override
+ public String[] listGroupNames(String metalake) throws
NoSuchMetalakeException {
+ return dispatcher.listGroupNames(metalake);
+ }
+
@Override
public User grantRolesToUser(String metalake, List<String> roles, String
user)
throws NoSuchUserException, NoSuchRoleException, NoSuchMetalakeException
{
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
index 42b079234..c1f72c360 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
@@ -108,6 +108,8 @@ public class JDBCBackend implements RelationalBackend {
return (List<E>)
UserMetaService.getInstance().listUsersByNamespace(namespace, allFields);
case ROLE:
return (List<E>)
RoleMetaService.getInstance().listRolesByNamespace(namespace);
+ case GROUP:
+ return (List<E>)
GroupMetaService.getInstance().listGroupsByNamespace(namespace, allFields);
default:
throw new UnsupportedEntityTypeException(
"Unsupported entity type: %s for list operation", entityType);
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaMapper.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaMapper.java
index 5743095dd..ae554a2a4 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaMapper.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaMapper.java
@@ -20,6 +20,7 @@
package org.apache.gravitino.storage.relational.mapper;
import java.util.List;
+import org.apache.gravitino.storage.relational.po.ExtendedGroupPO;
import org.apache.gravitino.storage.relational.po.GroupPO;
import org.apache.ibatis.annotations.DeleteProvider;
import org.apache.ibatis.annotations.InsertProvider;
@@ -51,6 +52,14 @@ public interface GroupMetaMapper {
GroupPO selectGroupMetaByMetalakeIdAndName(
@Param("metalakeId") Long metalakeId, @Param("groupName") String name);
+ @SelectProvider(type = GroupMetaSQLProviderFactory.class, method =
"listGroupPOsByMetalake")
+ List<GroupPO> listGroupPOsByMetalake(@Param("metalakeName") String
metalakeName);
+
+ @SelectProvider(
+ type = GroupMetaSQLProviderFactory.class,
+ method = "listExtendedGroupPOsByMetalakeId")
+ List<ExtendedGroupPO> listExtendedGroupPOsByMetalakeId(Long metalakeId);
+
@InsertProvider(type = GroupMetaSQLProviderFactory.class, method =
"insertGroupMeta")
void insertGroupMeta(@Param("groupMeta") GroupPO groupPO);
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaSQLProviderFactory.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaSQLProviderFactory.java
index bb96ce5a0..eb8dcc614 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaSQLProviderFactory.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/GroupMetaSQLProviderFactory.java
@@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableMap;
import java.util.Map;
import org.apache.gravitino.storage.relational.JDBCBackend.JDBCBackendType;
import
org.apache.gravitino.storage.relational.mapper.provider.base.GroupMetaBaseSQLProvider;
+import
org.apache.gravitino.storage.relational.mapper.provider.h2.GroupMetaH2Provider;
import org.apache.gravitino.storage.relational.po.GroupPO;
import org.apache.gravitino.storage.relational.session.SqlSessionFactoryHelper;
import org.apache.ibatis.annotations.Param;
@@ -46,8 +47,6 @@ public class GroupMetaSQLProviderFactory {
static class GroupMetaMySQLProvider extends GroupMetaBaseSQLProvider {}
- static class GroupMetaH2Provider extends GroupMetaBaseSQLProvider {}
-
public static String selectGroupIdBySchemaIdAndName(
@Param("metalakeId") Long metalakeId, @Param("groupName") String name) {
return getProvider().selectGroupIdBySchemaIdAndName(metalakeId, name);
@@ -83,6 +82,14 @@ public class GroupMetaSQLProviderFactory {
return getProvider().listGroupsByRoleId(roleId);
}
+ public static String listGroupPOsByMetalake(@Param("metalakeName") String
metalakeName) {
+ return getProvider().listGroupPOsByMetalake(metalakeName);
+ }
+
+ public static String listExtendedGroupPOsByMetalakeId(@Param("metalakeId")
Long metalakeId) {
+ return getProvider().listExtendedGroupPOsByMetalakeId(metalakeId);
+ }
+
public static String deleteGroupMetasByLegacyTimeline(
@Param("legacyTimeline") Long legacyTimeline, @Param("limit") int limit)
{
return getProvider().deleteGroupMetasByLegacyTimeline(legacyTimeline,
limit);
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/GroupMetaBaseSQLProvider.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/GroupMetaBaseSQLProvider.java
index 0c26d7488..a52e1b861 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/GroupMetaBaseSQLProvider.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/GroupMetaBaseSQLProvider.java
@@ -20,7 +20,9 @@ package
org.apache.gravitino.storage.relational.mapper.provider.base;
import static
org.apache.gravitino.storage.relational.mapper.GroupMetaMapper.GROUP_TABLE_NAME;
import static
org.apache.gravitino.storage.relational.mapper.RoleMetaMapper.GROUP_ROLE_RELATION_TABLE_NAME;
+import static
org.apache.gravitino.storage.relational.mapper.RoleMetaMapper.ROLE_TABLE_NAME;
+import org.apache.gravitino.storage.relational.mapper.MetalakeMetaMapper;
import org.apache.gravitino.storage.relational.po.GroupPO;
import org.apache.ibatis.annotations.Param;
@@ -34,6 +36,40 @@ public class GroupMetaBaseSQLProvider {
+ " AND deleted_at = 0";
}
+ public String listGroupPOsByMetalake(@Param("metalakeName") String
metalakeName) {
+ return "SELECT gt.group_id as groupId, gt.group_name as groupName,
gt.metalake_id as metalakeId,"
+ + " gt.audit_info as auditInfo, gt.current_version as currentVersion,
gt.last_version as lastVersion,"
+ + " gt.deleted_at as deletedAt FROM "
+ + GROUP_TABLE_NAME
+ + " gt JOIN "
+ + MetalakeMetaMapper.TABLE_NAME
+ + " mt ON gt.metalake_id = mt.metalake_id WHERE mt.metalake_name =
#{metalakeName}"
+ + " AND gt.deleted_at = 0 AND mt.deleted_at = 0";
+ }
+
+ public String listExtendedGroupPOsByMetalakeId(Long metalakeId) {
+ return "SELECT gt.group_id as groupId, gt.group_name as groupName,"
+ + " gt.metalake_id as metalakeId,"
+ + " gt.audit_info as auditInfo,"
+ + " gt.current_version as currentVersion, gt.last_version as
lastVersion,"
+ + " gt.deleted_at as deletedAt,"
+ + " JSON_ARRAYAGG(rot.role_name) as roleNames,"
+ + " JSON_ARRAYAGG(rot.role_id) as roleIds"
+ + " FROM "
+ + GROUP_TABLE_NAME
+ + " gt LEFT OUTER JOIN "
+ + GROUP_ROLE_RELATION_TABLE_NAME
+ + " rt ON rt.group_id = gt.group_id"
+ + " LEFT OUTER JOIN "
+ + ROLE_TABLE_NAME
+ + " rot ON rot.role_id = rt.role_id"
+ + " WHERE "
+ + " gt.deleted_at = 0 AND"
+ + " (rot.deleted_at = 0 OR rot.deleted_at is NULL) AND"
+ + " (rt.deleted_at = 0 OR rt.deleted_at is NULL) AND gt.metalake_id =
#{metalakeId}"
+ + " GROUP BY gt.group_id";
+ }
+
public String selectGroupMetaByMetalakeIdAndName(
@Param("metalakeId") Long metalakeId, @Param("groupName") String name) {
return "SELECT group_id as groupId, group_name as groupName,"
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/h2/GroupMetaH2Provider.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/h2/GroupMetaH2Provider.java
new file mode 100644
index 000000000..175d9d8ae
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/h2/GroupMetaH2Provider.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.storage.relational.mapper.provider.h2;
+
+import static
org.apache.gravitino.storage.relational.mapper.GroupMetaMapper.GROUP_TABLE_NAME;
+import static
org.apache.gravitino.storage.relational.mapper.RoleMetaMapper.GROUP_ROLE_RELATION_TABLE_NAME;
+import static
org.apache.gravitino.storage.relational.mapper.RoleMetaMapper.ROLE_TABLE_NAME;
+
+import
org.apache.gravitino.storage.relational.mapper.provider.base.GroupMetaBaseSQLProvider;
+import org.apache.ibatis.annotations.Param;
+
+public class GroupMetaH2Provider extends GroupMetaBaseSQLProvider {
+ @Override
+ public String listExtendedGroupPOsByMetalakeId(@Param("metalakeId") Long
metalakeId) {
+ return "SELECT gt.group_id as groupId, gt.group_name as groupName,"
+ + " gt.metalake_id as metalakeId,"
+ + " gt.audit_info as auditInfo,"
+ + " gt.current_version as currentVersion, gt.last_version as
lastVersion,"
+ + " gt.deleted_at as deletedAt,"
+ + " '[' || GROUP_CONCAT('\"' || rot.role_name || '\"') || ']' as
roleNames,"
+ + " '[' || GROUP_CONCAT('\"' || rot.role_id || '\"') || ']' as roleIds"
+ + " FROM "
+ + GROUP_TABLE_NAME
+ + " gt LEFT OUTER JOIN "
+ + GROUP_ROLE_RELATION_TABLE_NAME
+ + " rt ON rt.group_id = gt.group_id"
+ + " LEFT OUTER JOIN "
+ + ROLE_TABLE_NAME
+ + " rot ON rot.role_id = rt.role_id"
+ + " WHERE "
+ + " gt.deleted_at = 0 AND"
+ + " (rot.deleted_at = 0 OR rot.deleted_at is NULL) AND"
+ + " (rt.deleted_at = 0 OR rt.deleted_at is NULL) AND gt.metalake_id =
#{metalakeId}"
+ + " GROUP BY gt.group_id";
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/po/ExtendedGroupPO.java
b/core/src/main/java/org/apache/gravitino/storage/relational/po/ExtendedGroupPO.java
new file mode 100644
index 000000000..390a00398
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/po/ExtendedGroupPO.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.storage.relational.po;
+
+import java.util.Objects;
+
+/**
+ * ExtendedGroupPO add extra roleNames and roleIds for GroupPO. This PO is
only used for reading the
+ * data from multiple joined tables. The PO won't be written to database. So
we don't need the inner
+ * class Builder.
+ */
+public class ExtendedGroupPO extends GroupPO {
+
+ private String roleNames;
+ private String roleIds;
+
+ public String getRoleNames() {
+ return roleNames;
+ }
+
+ public String getRoleIds() {
+ return roleIds;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (!(o instanceof ExtendedGroupPO)) {
+ return false;
+ }
+ ExtendedGroupPO that = (ExtendedGroupPO) o;
+ return Objects.equals(getRoleIds(), that.getRoleIds())
+ && Objects.equals(getRoleNames(), that.getRoleNames());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), getRoleIds(), getRoleNames());
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/service/GroupMetaService.java
b/core/src/main/java/org/apache/gravitino/storage/relational/service/GroupMetaService.java
index 2ffc10dac..4329b3a0a 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/service/GroupMetaService.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/service/GroupMetaService.java
@@ -32,6 +32,7 @@ import java.util.stream.Collectors;
import org.apache.gravitino.Entity;
import org.apache.gravitino.HasIdentifier;
import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
import org.apache.gravitino.authorization.AuthorizationUtils;
import org.apache.gravitino.exceptions.NoSuchEntityException;
import org.apache.gravitino.meta.GroupEntity;
@@ -39,6 +40,7 @@ import org.apache.gravitino.meta.RoleEntity;
import org.apache.gravitino.storage.relational.mapper.GroupMetaMapper;
import org.apache.gravitino.storage.relational.mapper.GroupRoleRelMapper;
import org.apache.gravitino.storage.relational.mapper.OwnerMetaMapper;
+import org.apache.gravitino.storage.relational.po.ExtendedGroupPO;
import org.apache.gravitino.storage.relational.po.GroupPO;
import org.apache.gravitino.storage.relational.po.GroupRoleRelPO;
import org.apache.gravitino.storage.relational.po.RolePO;
@@ -249,6 +251,36 @@ public class GroupMetaService {
return newEntity;
}
+ public List<GroupEntity> listGroupsByNamespace(Namespace namespace, boolean
allFields) {
+ AuthorizationUtils.checkGroupNamespace(namespace);
+ String metalakeName = namespace.level(0);
+
+ if (allFields) {
+ Long metalakeId =
MetalakeMetaService.getInstance().getMetalakeIdByName(metalakeName);
+ List<ExtendedGroupPO> groupPOs =
+ SessionUtils.getWithoutCommit(
+ GroupMetaMapper.class, mapper ->
mapper.listExtendedGroupPOsByMetalakeId(metalakeId));
+ return groupPOs.stream()
+ .map(
+ po ->
+ POConverters.fromExtendedGroupPO(
+ po, AuthorizationUtils.ofGroupNamespace(metalakeName)))
+ .collect(Collectors.toList());
+ } else {
+ List<GroupPO> groupPOs =
+ SessionUtils.getWithoutCommit(
+ GroupMetaMapper.class, mapper ->
mapper.listGroupPOsByMetalake(metalakeName));
+ return groupPOs.stream()
+ .map(
+ po ->
+ POConverters.fromGroupPO(
+ po,
+ Collections.emptyList(),
+ AuthorizationUtils.ofGroupNamespace(metalakeName)))
+ .collect(Collectors.toList());
+ }
+ }
+
public int deleteGroupMetasByLegacyTimeline(long legacyTimeline, int limit) {
int[] groupDeletedCount = new int[] {0};
int[] groupRoleRelDeletedCount = new int[] {0};
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
index da1f3d06a..f6392127b 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
@@ -49,6 +49,7 @@ import org.apache.gravitino.meta.TagEntity;
import org.apache.gravitino.meta.TopicEntity;
import org.apache.gravitino.meta.UserEntity;
import org.apache.gravitino.storage.relational.po.CatalogPO;
+import org.apache.gravitino.storage.relational.po.ExtendedGroupPO;
import org.apache.gravitino.storage.relational.po.ExtendedUserPO;
import org.apache.gravitino.storage.relational.po.FilesetPO;
import org.apache.gravitino.storage.relational.po.FilesetVersionPO;
@@ -733,7 +734,7 @@ public class POConverters {
/**
* Convert {@link ExtendedUserPO} to {@link UserEntity}
*
- * @param userPO CombinedUserPo object to be converted
+ * @param userPO ExtendedUserPO object to be converted
* @param namespace Namespace object to be associated with the user
* @return UserEntity object from ExtendedUserPO object
*/
@@ -814,6 +815,57 @@ public class POConverters {
}
}
+ /**
+ * Convert {@link ExtendedGroupPO} to {@link GroupEntity}
+ *
+ * @param groupPO ExtendedGroupPO object to be converted
+ * @param namespace Namespace object to be associated with the user
+ * @return GroupEntity object from ExtendedGroupPO object
+ */
+ public static GroupEntity fromExtendedGroupPO(ExtendedGroupPO groupPO,
Namespace namespace) {
+ try {
+ GroupEntity.Builder builder =
+ GroupEntity.builder()
+ .withId(groupPO.getGroupId())
+ .withName(groupPO.getGroupName())
+ .withNamespace(namespace)
+ .withAuditInfo(
+ JsonUtils.anyFieldMapper().readValue(groupPO.getAuditInfo(),
AuditInfo.class));
+
+ if (StringUtils.isNotBlank(groupPO.getRoleNames())) {
+ List<String> roleNamesFromJson =
+ JsonUtils.anyFieldMapper().readValue(groupPO.getRoleNames(),
List.class);
+ List<String> roleNames =
+
roleNamesFromJson.stream().filter(StringUtils::isNotBlank).collect(Collectors.toList());
+ if (!roleNames.isEmpty()) {
+ builder.withRoleNames(roleNames);
+ }
+ }
+
+ if (StringUtils.isNotBlank(groupPO.getRoleIds())) {
+ // Different JSON AGG from backends will produce different types data,
we
+ // can only use Object. PostSQL produces the data with type Long. H2
produces
+ // the data with type String.
+ List<Object> roleIdsFromJson =
+ JsonUtils.anyFieldMapper().readValue(groupPO.getRoleIds(),
List.class);
+ List<Long> roleIds =
+ roleIdsFromJson.stream()
+ .filter(Objects::nonNull)
+ .map(String::valueOf)
+ .filter(StringUtils::isNotBlank)
+ .map(Long::valueOf)
+ .collect(Collectors.toList());
+
+ if (!roleIds.isEmpty()) {
+ builder.withRoleIds(roleIds);
+ }
+ }
+ return builder.build();
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException("Failed to deserialize json object:", e);
+ }
+ }
+
/**
* Initialize UserRoleRelPO
*
diff --git
a/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
b/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
index b299c15ef..a2b6dace1 100644
---
a/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
+++
b/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
@@ -298,6 +298,28 @@ public class TestAccessControlManager {
Assertions.assertTrue(exception.getMessage().contains("Group not-exist
does not exist"));
}
+ @Test
+ public void testListGroupss() {
+ accessControlManager.addGroup("metalake_list", "testList1");
+ accessControlManager.addGroup("metalake_list", "testList2");
+
+ // Test to list groups
+ String[] expectGroupNames = new String[] {"testList1", "testList2"};
+ String[] actualGroupNames =
accessControlManager.listGroupNames("metalake_list");
+ Arrays.sort(actualGroupNames);
+ Assertions.assertArrayEquals(expectGroupNames, actualGroupNames);
+ Group[] groups = accessControlManager.listGroups("metalake_list");
+ Arrays.sort(groups, Comparator.comparing(Group::name));
+ Assertions.assertArrayEquals(
+ expectGroupNames,
Arrays.stream(groups).map(Group::name).toArray(String[]::new));
+
+ // Test with NoSuchMetalakeException
+ Assertions.assertThrows(
+ NoSuchMetalakeException.class, () ->
accessControlManager.listGroupNames("no-exist"));
+ Assertions.assertThrows(
+ NoSuchMetalakeException.class, () ->
accessControlManager.listGroups("no-exist"));
+ }
+
@Test
public void testRemoveGroup() {
accessControlManager.addGroup(METALAKE, "testRemove");
diff --git
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestGroupMetaService.java
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestGroupMetaService.java
index 22246ba0c..77cd9d110 100644
---
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestGroupMetaService.java
+++
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestGroupMetaService.java
@@ -27,6 +27,7 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Instant;
+import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
@@ -119,6 +120,77 @@ class TestGroupMetaService extends TestJDBCBackend {
Sets.newHashSet(group2.roleNames()),
Sets.newHashSet(actualGroup.roleNames()));
}
+ @Test
+ void testListGroups() throws IOException {
+ AuditInfo auditInfo =
+
AuditInfo.builder().withCreator("creator").withCreateTime(Instant.now()).build();
+ BaseMetalake metalake =
+ createBaseMakeLake(RandomIdGenerator.INSTANCE.nextId(), metalakeName,
auditInfo);
+ backend.insert(metalake, false);
+
+ CatalogEntity catalog =
+ createCatalog(
+ RandomIdGenerator.INSTANCE.nextId(), Namespace.of(metalakeName),
"catalog", auditInfo);
+ backend.insert(catalog, false);
+
+ GroupEntity group1 =
+ createGroupEntity(
+ RandomIdGenerator.INSTANCE.nextId(),
+ AuthorizationUtils.ofGroupNamespace(metalakeName),
+ "group1",
+ auditInfo);
+
+ RoleEntity role1 =
+ createRoleEntity(
+ RandomIdGenerator.INSTANCE.nextId(),
+ AuthorizationUtils.ofRoleNamespace("metalake"),
+ "role1",
+ auditInfo,
+ "catalog");
+ backend.insert(role1, false);
+
+ RoleEntity role2 =
+ createRoleEntity(
+ RandomIdGenerator.INSTANCE.nextId(),
+ AuthorizationUtils.ofRoleNamespace("metalake"),
+ "role2",
+ auditInfo,
+ "catalog");
+ backend.insert(role2, false);
+
+ GroupEntity group2 =
+ createGroupEntity(
+ RandomIdGenerator.INSTANCE.nextId(),
+ AuthorizationUtils.ofGroupNamespace("metalake"),
+ "group2",
+ auditInfo,
+ Lists.newArrayList(role1.name(), role2.name()),
+ Lists.newArrayList(role1.id(), role2.id()));
+
+ backend.insert(group1, false);
+ backend.insert(group2, false);
+
+ GroupMetaService groupMetaService = GroupMetaService.getInstance();
+ List<GroupEntity> actualGroups =
+ groupMetaService.listGroupsByNamespace(
+ AuthorizationUtils.ofGroupNamespace(metalakeName), true);
+ actualGroups.sort(Comparator.comparing(GroupEntity::name));
+ List<GroupEntity> expectGroups = Lists.newArrayList(group1, group2);
+ Assertions.assertEquals(expectGroups.size(), actualGroups.size());
+ for (int index = 0; index < expectGroups.size(); index++) {
+ Assertions.assertEquals(expectGroups.get(index).name(),
actualGroups.get(index).name());
+ if (expectGroups.get(index).roleNames() == null) {
+ Assertions.assertNull(actualGroups.get(index).roleNames());
+ } else {
+ Assertions.assertEquals(
+ expectGroups.get(index).roleNames().size(),
actualGroups.get(index).roleNames().size());
+ for (String roleName : expectGroups.get(index).roleNames()) {
+
Assertions.assertTrue(actualGroups.get(index).roleNames().contains(roleName));
+ }
+ }
+ }
+ }
+
@Test
void insertGroup() throws IOException {
AuditInfo auditInfo =
@@ -243,7 +315,7 @@ class TestGroupMetaService extends TestJDBCBackend {
GroupEntity group3Overwrite =
createGroupEntity(
group1.id(),
- AuthorizationUtils.ofUserNamespace(metalakeName),
+ AuthorizationUtils.ofGroupNamespace(metalakeName),
"group3Overwrite",
auditInfo,
Lists.newArrayList(role3.name()),
@@ -260,7 +332,7 @@ class TestGroupMetaService extends TestJDBCBackend {
GroupEntity group4Overwrite =
createGroupEntity(
group1.id(),
- AuthorizationUtils.ofUserNamespace(metalakeName),
+ AuthorizationUtils.ofGroupNamespace(metalakeName),
"group4Overwrite",
auditInfo);
Assertions.assertDoesNotThrow(() ->
groupMetaService.insertGroup(group4Overwrite, true));
@@ -779,7 +851,7 @@ class TestGroupMetaService extends TestJDBCBackend {
GroupEntity group1 =
createGroupEntity(
RandomIdGenerator.INSTANCE.nextId(),
- AuthorizationUtils.ofUserNamespace(metalakeName),
+ AuthorizationUtils.ofGroupNamespace(metalakeName),
"group1",
auditInfo,
Lists.newArrayList(role1.name(), role2.name()),
@@ -787,7 +859,7 @@ class TestGroupMetaService extends TestJDBCBackend {
GroupEntity group2 =
createGroupEntity(
RandomIdGenerator.INSTANCE.nextId(),
- AuthorizationUtils.ofUserNamespace(metalakeName),
+ AuthorizationUtils.ofGroupNamespace(metalakeName),
"group2",
auditInfo,
Lists.newArrayList(role1.name(), role2.name()),
@@ -795,7 +867,7 @@ class TestGroupMetaService extends TestJDBCBackend {
GroupEntity group3 =
createGroupEntity(
RandomIdGenerator.INSTANCE.nextId(),
- AuthorizationUtils.ofUserNamespace(metalakeName),
+ AuthorizationUtils.ofGroupNamespace(metalakeName),
"group3",
auditInfo,
Lists.newArrayList(role1.name(), role2.name()),
@@ -803,7 +875,7 @@ class TestGroupMetaService extends TestJDBCBackend {
GroupEntity group4 =
createGroupEntity(
RandomIdGenerator.INSTANCE.nextId(),
- AuthorizationUtils.ofUserNamespace(metalakeName),
+ AuthorizationUtils.ofGroupNamespace(metalakeName),
"group4",
auditInfo,
Lists.newArrayList(role1.name(), role2.name()),
diff --git
a/integration-test/src/test/java/org/apache/gravitino/integration/test/authorization/AccessControlIT.java
b/integration-test/src/test/java/org/apache/gravitino/integration/test/authorization/AccessControlIT.java
index 0501ffb46..6bd8cd0b1 100644
---
a/integration-test/src/test/java/org/apache/gravitino/integration/test/authorization/AccessControlIT.java
+++
b/integration-test/src/test/java/org/apache/gravitino/integration/test/authorization/AccessControlIT.java
@@ -146,8 +146,38 @@ public class AccessControlIT extends AbstractIT {
// Get a not-existed group
Assertions.assertThrows(NoSuchGroupException.class, () ->
metalake.getGroup("not-existed"));
+ Map<String, String> properties = Maps.newHashMap();
+ properties.put("k1", "v1");
+ SecurableObject metalakeObject =
+ SecurableObjects.ofMetalake(
+ metalakeName,
Lists.newArrayList(Privileges.CreateCatalog.allow()));
+
+ // Test the group with the role
+ metalake.createRole("role2", properties,
Lists.newArrayList(metalakeObject));
+ metalake.grantRolesToGroup(Lists.newArrayList("role2"), groupName);
+
+ // List groups
+ String anotherGroup = "group2#456";
+ metalake.addGroup(anotherGroup);
+ String[] groupNames = metalake.listGroupNames();
+ Arrays.sort(groupNames);
+ Assertions.assertEquals(Lists.newArrayList(groupName, anotherGroup),
Arrays.asList(groupNames));
+
+ List<Group> groups =
+ Arrays.stream(metalake.listGroups())
+ .sorted(Comparator.comparing(Group::name))
+ .collect(Collectors.toList());
+ Assertions.assertEquals(
+ Lists.newArrayList(groupName, anotherGroup),
+ groups.stream().map(Group::name).collect(Collectors.toList()));
+ Assertions.assertEquals(Lists.newArrayList("role2"),
groups.get(0).roles());
+
Assertions.assertTrue(metalake.removeGroup(groupName));
Assertions.assertFalse(metalake.removeGroup(groupName));
+
+ // clean up
+ metalake.removeGroup(anotherGroup);
+ metalake.deleteRole("role2");
}
@Test
diff --git
a/server/src/main/java/org/apache/gravitino/server/web/rest/GroupOperations.java
b/server/src/main/java/org/apache/gravitino/server/web/rest/GroupOperations.java
index 537bafb9e..12cf76993 100644
---
a/server/src/main/java/org/apache/gravitino/server/web/rest/GroupOperations.java
+++
b/server/src/main/java/org/apache/gravitino/server/web/rest/GroupOperations.java
@@ -22,19 +22,24 @@ import com.codahale.metrics.annotation.ResponseMetered;
import com.codahale.metrics.annotation.Timed;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import org.apache.gravitino.GravitinoEnv;
import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
import org.apache.gravitino.authorization.AccessControlDispatcher;
import org.apache.gravitino.authorization.AuthorizationUtils;
import org.apache.gravitino.dto.requests.GroupAddRequest;
+import org.apache.gravitino.dto.responses.GroupListResponse;
import org.apache.gravitino.dto.responses.GroupResponse;
+import org.apache.gravitino.dto.responses.NameListResponse;
import org.apache.gravitino.dto.responses.RemoveResponse;
import org.apache.gravitino.dto.util.DTOConverters;
import org.apache.gravitino.lock.LockType;
@@ -134,4 +139,31 @@ public class GroupOperations {
return ExceptionHandlers.handleGroupException(OperationType.REMOVE,
group, metalake, e);
}
}
+
+ @GET
+ @Produces("application/vnd.gravitino.v1+json")
+ @Timed(name = "list-group." + MetricNames.HTTP_PROCESS_DURATION, absolute =
true)
+ @ResponseMetered(name = "list-group", absolute = true)
+ public Response listGroups(
+ @PathParam("metalake") String metalake,
+ @QueryParam("details") @DefaultValue("false") boolean verbose) {
+ LOG.info("Received list groups request.");
+ try {
+ return Utils.doAs(
+ httpRequest,
+ () -> {
+ if (verbose) {
+ return Utils.ok(
+ new GroupListResponse(
+
DTOConverters.toDTOs(accessControlManager.listGroups(metalake))));
+ } else {
+ return Utils.ok(new
NameListResponse(accessControlManager.listGroupNames(metalake)));
+ }
+ });
+
+ } catch (Exception e) {
+ return ExceptionHandlers.handleGroupException(
+ OperationType.LIST, Namespace.empty().toString(), metalake, e);
+ }
+ }
}
diff --git
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestGroupOperations.java
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestGroupOperations.java
index c3b34bc6b..77f0cf979 100644
---
a/server/src/test/java/org/apache/gravitino/server/web/rest/TestGroupOperations.java
+++
b/server/src/test/java/org/apache/gravitino/server/web/rest/TestGroupOperations.java
@@ -34,6 +34,7 @@ import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.gravitino.Config;
import org.apache.gravitino.GravitinoEnv;
@@ -43,7 +44,9 @@ import org.apache.gravitino.dto.authorization.GroupDTO;
import org.apache.gravitino.dto.requests.GroupAddRequest;
import org.apache.gravitino.dto.responses.ErrorConstants;
import org.apache.gravitino.dto.responses.ErrorResponse;
+import org.apache.gravitino.dto.responses.GroupListResponse;
import org.apache.gravitino.dto.responses.GroupResponse;
+import org.apache.gravitino.dto.responses.NameListResponse;
import org.apache.gravitino.dto.responses.RemoveResponse;
import org.apache.gravitino.exceptions.GroupAlreadyExistsException;
import org.apache.gravitino.exceptions.NoSuchGroupException;
@@ -119,7 +122,7 @@ public class TestGroupOperations extends JerseyTest {
.accept("application/vnd.gravitino.v1+json")
.post(Entity.entity(req, MediaType.APPLICATION_JSON_TYPE));
- Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(Status.OK.getStatusCode(), resp.getStatus());
Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp.getMediaType());
GroupResponse groupResponse = resp.readEntity(GroupResponse.class);
@@ -138,7 +141,7 @@ public class TestGroupOperations extends JerseyTest {
.accept("application/vnd.gravitino.v1+json")
.post(Entity.entity(req, MediaType.APPLICATION_JSON_TYPE));
- Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
resp1.getStatus());
+ Assertions.assertEquals(Status.NOT_FOUND.getStatusCode(),
resp1.getStatus());
Assertions.assertEquals(MediaType.APPLICATION_JSON_TYPE,
resp1.getMediaType());
ErrorResponse errorResponse = resp1.readEntity(ErrorResponse.class);
@@ -153,7 +156,7 @@ public class TestGroupOperations extends JerseyTest {
.accept("application/vnd.gravitino.v1+json")
.post(Entity.entity(req, MediaType.APPLICATION_JSON_TYPE));
- Assertions.assertEquals(Response.Status.CONFLICT.getStatusCode(),
resp2.getStatus());
+ Assertions.assertEquals(Status.CONFLICT.getStatusCode(),
resp2.getStatus());
ErrorResponse errorResponse1 = resp2.readEntity(ErrorResponse.class);
Assertions.assertEquals(ErrorConstants.ALREADY_EXISTS_CODE,
errorResponse1.getCode());
@@ -168,8 +171,7 @@ public class TestGroupOperations extends JerseyTest {
.accept("application/vnd.gravitino.v1+json")
.post(Entity.entity(req, MediaType.APPLICATION_JSON_TYPE));
- Assertions.assertEquals(
- Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp3.getStatus());
+ Assertions.assertEquals(Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp3.getStatus());
ErrorResponse errorResponse2 = resp3.readEntity(ErrorResponse.class);
Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE,
errorResponse2.getCode());
@@ -241,14 +243,103 @@ public class TestGroupOperations extends JerseyTest {
Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResponse2.getType());
}
- private Group buildGroup(String group) {
- return GroupEntity.builder()
- .withId(1L)
- .withName(group)
- .withRoleNames(Collections.emptyList())
- .withAuditInfo(
-
AuditInfo.builder().withCreator("creator").withCreateTime(Instant.now()).build())
- .build();
+ @Test
+ public void testListGroupNames() {
+ when(manager.listGroupNames(any())).thenReturn(new String[] {"group"});
+
+ Response resp =
+ target("/metalakes/metalake1/groups/")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+
+ NameListResponse listResponse = resp.readEntity(NameListResponse.class);
+ Assertions.assertEquals(0, listResponse.getCode());
+
+ Assertions.assertEquals(1, listResponse.getNames().length);
+ Assertions.assertEquals("group", listResponse.getNames()[0]);
+
+ // Test to throw NoSuchMetalakeException
+ doThrow(new NoSuchMetalakeException("mock
error")).when(manager).listGroupNames(any());
+ Response resp1 =
+ target("/metalakes/metalake1/groups/")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
resp1.getStatus());
+
+ ErrorResponse errorResponse = resp1.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE,
errorResponse.getCode());
+ Assertions.assertEquals(NoSuchMetalakeException.class.getSimpleName(),
errorResponse.getType());
+
+ // Test to throw internal RuntimeException
+ doThrow(new RuntimeException("mock
error")).when(manager).listGroupNames(any());
+ Response resp3 =
+ target("/metalakes/metalake1/groups")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(
+ Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp3.getStatus());
+
+ ErrorResponse errorResponse2 = resp3.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE,
errorResponse2.getCode());
+ Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResponse2.getType());
+ }
+
+ @Test
+ public void testListGroups() {
+ Group group = buildGroup("group");
+ when(manager.listGroups(any())).thenReturn(new Group[] {group});
+
+ Response resp =
+ target("/metalakes/metalake1/groups/")
+ .queryParam("details", "true")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+ Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+
+ GroupListResponse listResponse = resp.readEntity(GroupListResponse.class);
+ Assertions.assertEquals(0, listResponse.getCode());
+
+ Assertions.assertEquals(1, listResponse.getGroups().length);
+ Assertions.assertEquals(group.name(), listResponse.getGroups()[0].name());
+ Assertions.assertEquals(group.roles(),
listResponse.getGroups()[0].roles());
+
+ // Test to throw NoSuchMetalakeException
+ doThrow(new NoSuchMetalakeException("mock
error")).when(manager).listGroups(any());
+ Response resp1 =
+ target("/metalakes/metalake1/groups/")
+ .queryParam("details", "true")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(),
resp1.getStatus());
+
+ ErrorResponse errorResponse = resp1.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE,
errorResponse.getCode());
+ Assertions.assertEquals(NoSuchMetalakeException.class.getSimpleName(),
errorResponse.getType());
+
+ // Test to throw internal RuntimeException
+ doThrow(new RuntimeException("mock
error")).when(manager).listGroups(any());
+ Response resp3 =
+ target("/metalakes/metalake1/groups")
+ .queryParam("details", "true")
+ .request(MediaType.APPLICATION_JSON_TYPE)
+ .accept("application/vnd.gravitino.v1+json")
+ .get();
+
+ Assertions.assertEquals(
+ Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp3.getStatus());
+
+ ErrorResponse errorResponse2 = resp3.readEntity(ErrorResponse.class);
+ Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE,
errorResponse2.getCode());
+ Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResponse2.getType());
}
@Test
@@ -261,7 +352,7 @@ public class TestGroupOperations extends JerseyTest {
.accept("application/vnd.gravitino.v1+json")
.delete();
- Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp.getStatus());
+ Assertions.assertEquals(Status.OK.getStatusCode(), resp.getStatus());
RemoveResponse removeResponse = resp.readEntity(RemoveResponse.class);
Assertions.assertEquals(0, removeResponse.getCode());
Assertions.assertTrue(removeResponse.removed());
@@ -274,7 +365,7 @@ public class TestGroupOperations extends JerseyTest {
.accept("application/vnd.gravitino.v1+json")
.delete();
- Assertions.assertEquals(Response.Status.OK.getStatusCode(),
resp2.getStatus());
+ Assertions.assertEquals(Status.OK.getStatusCode(), resp2.getStatus());
RemoveResponse removeResponse2 = resp2.readEntity(RemoveResponse.class);
Assertions.assertEquals(0, removeResponse2.getCode());
Assertions.assertFalse(removeResponse2.removed());
@@ -286,11 +377,20 @@ public class TestGroupOperations extends JerseyTest {
.accept("application/vnd.gravitino.v1+json")
.delete();
- Assertions.assertEquals(
- Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp3.getStatus());
+ Assertions.assertEquals(Status.INTERNAL_SERVER_ERROR.getStatusCode(),
resp3.getStatus());
ErrorResponse errorResponse = resp3.readEntity(ErrorResponse.class);
Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE,
errorResponse.getCode());
Assertions.assertEquals(RuntimeException.class.getSimpleName(),
errorResponse.getType());
}
+
+ private Group buildGroup(String group) {
+ return GroupEntity.builder()
+ .withId(1L)
+ .withName(group)
+ .withRoleNames(Collections.emptyList())
+ .withAuditInfo(
+
AuditInfo.builder().withCreator("creator").withCreateTime(Instant.now()).build())
+ .build();
+ }
}