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

[#3348] feat(core,server): Add the list operation of the user #4055

Merged
merged 31 commits into from
Sep 24, 2024

Conversation

jerqi
Copy link
Contributor

@jerqi jerqi commented Jul 3, 2024

What changes were proposed in this pull request?

Add the list operation of the user

Why are the changes needed?

Fix: #3348

Does this PR introduce any user-facing change?

I will add the document later.

How was this patch tested?

Add the new ut.

Comment on lines 104 to 110
public String[] listUsers(String metalake) throws NoSuchMetalakeException {
return doWithNonAdminLock(() -> userGroupManager.listUsers(metalake));
}

public User[] listUsersInfo(String metalake) throws NoSuchMetalakeException {
return doWithNonAdminLock(() -> userGroupManager.listUsersInfo(metalake));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think listUsers -> listUserNames, listUsersInfo -> listUsers would be more appropriate

Copy link
Contributor

Choose a reason for hiding this comment

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

Done.

@jerqi jerqi force-pushed the ISSUE-3348 branch 4 times, most recently from a124b10 to 2f1476c Compare July 3, 2024 11:45
@jerqi jerqi closed this Jul 4, 2024
@jerqi jerqi reopened this Jul 4, 2024
@jerqi
Copy link
Contributor Author

jerqi commented Jul 4, 2024

@lw-yang Could you take an another look?

Comment on lines 253 to 256
for (UserPO userPO : userPOs) {
List<RolePO> rolePOs = RoleMetaService.getInstance().listRolesByUserId(userPO.getUserId());
userEntities.add(POConverters.fromUserPO(userPO, rolePOs, namespace));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

the num of users > 1w is common situation, this may execute for a long time, maybe we should consider using cache in listUsers

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cc @yuqi1129 Maybe we can have a common cache layer for this.

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 it is better to use a paging query.

@jerryshao
Copy link
Contributor

@jerqi can you please update the PR.

@jerqi jerqi force-pushed the ISSUE-3348 branch 2 times, most recently from 9d07ea0 to a57e4e3 Compare July 16, 2024 06:56
@jerqi
Copy link
Contributor Author

jerqi commented Jul 16, 2024

@lw-yang @jerryshao Do you have any other suggestion?

LOG.error("Listing user under metalake {} failed due to storage issues", metalake, ioe);
throw new RuntimeException(ioe);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

  1. I think you need to use tree lock here.
  2. listUserNames can be implemented based on listUsers, don't need to duplicate the codes.

Copy link
Contributor Author

@jerqi jerqi Jul 17, 2024

Choose a reason for hiding this comment

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

  1. I think you need to use tree lock here.

I have used tree lock in the UserOperations.

  1. listUserNames can be implemented based on listUsers, don't need to duplicate the codes.

OK, I have refactored the code.

userPOs.addAll(
SessionUtils.doWithoutCommitAndFetchResult(
UserMetaMapper.class,
mapper -> mapper.listUserPOsByMetalakeId(metalakeId.get()))));
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we don't have to use transaction here, simply because we don't use select for update, so the transaction is not useful and will lead to dead lock.

Copy link
Contributor

Choose a reason for hiding this comment

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

I modified. But it shouldn't bring dead lock using the transaction.

@Getter
@ToString
@EqualsAndHashCode(callSuper = true)
public class NameListResponse extends BaseResponse {
Copy link
Contributor

Choose a reason for hiding this comment

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

You should implement validate() method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

CatalogListResponse doesn't implement validate, too. I created an issue #4197 to track it.

super(0);
this.users = null;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Also here.

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.

for (UserPO userPO : userPOs) {
List<RolePO> rolePOs = RoleMetaService.getInstance().listRolesByUserId(userPO.getUserId());
userEntities.add(POConverters.fromUserPO(userPO, rolePOs, namespace));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you really want to return all the roles for all the users? This will be extremely cost, is that really the frontend needs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now, we adopts the lazily loading information about users/roles.

} catch (Throwable t) {
throw t;
} finally {
SqlSessions.closeSqlSession();
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do you need to add this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed.

@jerqi jerqi force-pushed the ISSUE-3348 branch 3 times, most recently from 62c5484 to 8118c63 Compare September 9, 2024 03:05
@jerqi
Copy link
Contributor Author

jerqi commented Sep 9, 2024

After discussing with @shaofengshi offline, we need the ability to list all the role information of the user. It will be convenient for users. I have supported the user lazily loading. So if we only want to load the user names, we don't need to load unnecessary information.
In the future, we have many users, we can support to page. It's an improvement. We can support it in an another pull request.

@jerqi jerqi force-pushed the ISSUE-3348 branch 3 times, most recently from 4b728a5 to b2d206d Compare September 9, 2024 08:25
@jerqi jerqi self-assigned this Sep 9, 2024
@xunliu
Copy link
Member

xunliu commented Sep 13, 2024

Query all data at once or cache buffering in the service also has the problem of dirty data. I think paged queries are better.
@jerryshao @jerqi What do you think?

try {
Namespace namespace = AuthorizationUtils.ofUserNamespace(metalake);
return store.list(namespace, UserEntity.class, Entity.EntityType.USER).stream()
.map(entity -> (User) entity)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need this line?

Copy link
Contributor

Choose a reason for hiding this comment

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

So you only get the user list here, right? Will you also fetch the roles for each user, I saw that User and UserDTO have roles fields, will this be empty?

Copy link
Contributor Author

@jerqi jerqi Sep 14, 2024

Choose a reason for hiding this comment

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

Yes, I only get the user list here. I won't fetch roles unless they are needed. Now the roles is empty for every user.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You can see the pull request. #4690
UserEntity roles field don't be fetched directly. We will put a supplier method field instead. So we don't fetch the values directly if we only need the names.
If we need all the information of entities, we can also have the ability to put them. You can see the list details. Because the front side need the all the users information, so I provide the interface.

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean here why do we need to do a type conversion entity -> (User) entity?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, it's unnecessary. I ever designed other interface listEntitiesByRelation. It need me to use type cast. For this place, we don't need. I have removed it.

+ " mt ON ut.metalake_id = mt.metalake_id"
+ " WHERE mt.metalake_name = #{metalakeName}"
+ " AND ut.deleted_at = 0 AND mt.deleted_at = 0";
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This SQL will not throw an exception when metalake is not existed, it will only return an empty list. In your definition, you have NoSuchMetalakeException defined, to throw an such exception, you have to add one more check to see if metalake is existed or not.

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 the checkMetalakeExists.

ErrorResponse errorResponse2 = resp3.readEntity(ErrorResponse.class);
Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, errorResponse2.getCode());
Assertions.assertEquals(RuntimeException.class.getSimpleName(), errorResponse2.getType());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you also need to add integration tests for this.

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 have added IT in the AccessControlId.java.

@jerqi jerqi requested a review from jerryshao September 14, 2024 02:08
@jerryshao
Copy link
Contributor

Please fix the IT issue here.

@yuqi1129
Copy link
Contributor

@xunliu , @yuqi1129 you wanner another look?

Sure, I will review it today.

@jerqi
Copy link
Contributor Author

jerqi commented Sep 23, 2024

Please fix the IT issue here.

I have fixed the issue. Waiting for CI.

* data from multiple joined tables. The PO won't be written to database. So we don't need the inner
* class Builder.
*/
public class CombinedUserPO extends UserPO {
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 CombinedUserPO may not be a good name, But I haven't a good suggestion.
CombinedUserPO is more like a combined multiple-user.

Copy link
Contributor Author

@jerqi jerqi Sep 23, 2024

Choose a reason for hiding this comment

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

How about ExtendUserPO?

Comment on lines 42 to 46
for (SupportsSkippingFields<T> method : methods) {
if (skippingFields.containsAll(method.supportsSkippingFields())) {
return method.execute();
}
}
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 a double negative logic, let me confused.
Why select SQL based on skip fields instead of required fields?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because the number of required fields may be very large, and it's hard to maintain when we add a new field.

Copy link
Member

Choose a reason for hiding this comment

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

Wouldn't it be clearer to use an enumeration?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From my opinion, it can't improve the code readability to use enumeration. It seems better to use fields instead of enumeration. Because it's more flexible and robust than enumeration.

Comment on lines 294 to 312
public List<Field> supportsSkippingFields() {
return Lists.newArrayList(UserEntity.ROLE_IDS, UserEntity.ROLE_NAMES);
}

@Override
public List<UserEntity> execute() {
List<UserPO> userPOs =
SessionUtils.getWithoutCommit(
UserMetaMapper.class, mapper -> mapper.listUserPOsByMetalake(metalakeName));
return userPOs.stream()
.map(
po ->
POConverters.fromUserPO(
po,
Collections.emptyList(),
AuthorizationUtils.ofUserNamespace(metalakeName)))
.collect(Collectors.toList());
}
}
Copy link
Member

Choose a reason for hiding this comment

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

SkippingFields and execution are just a mapping and are not directly related, e.g. execution needs to use the values in SkippingFields.

Copy link
Contributor Author

@jerqi jerqi Sep 23, 2024

Choose a reason for hiding this comment

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

It's hard to use skippingFields to generate the SQL. So this just uses mapping.

* @param type the detailed type of the entity
* @param entityType the general type of the entity
* @param skippingFields Some fields may have a relatively high acquisition cost, EntityStore
* provide an optional setting to avoid fetching these high-cost fields to improve the
Copy link
Contributor

Choose a reason for hiding this comment

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

provides

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK.

import org.apache.gravitino.Field;

/**
* This class is the collection wrapper of SupportsSkippingFields handler. The class will contains
Copy link
Contributor

Choose a reason for hiding this comment

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

contains-> contain

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

*
* @return The fields which could be skipped.
*/
List<Field> supportsSkippingFields();
Copy link
Contributor

Choose a reason for hiding this comment

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

supportsSkippingFields -> allSkppingFields or totalSkipFields.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK.

Copy link
Member

@xunliu xunliu left a comment

Choose a reason for hiding this comment

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

LGTM
@jerryshao you wanner another look?

@jerryshao
Copy link
Contributor

Just make the CI pass, I'm fine with the current PR.

@jerqi jerqi closed this Sep 24, 2024
@jerqi jerqi reopened this Sep 24, 2024
@yuqi1129 yuqi1129 merged commit 92a0ec8 into apache:main Sep 24, 2024
54 of 55 checks passed
@yuqi1129 yuqi1129 deleted the ISSUE-3348 branch September 24, 2024 02:46
jerqi added a commit to qqqttt123/gravitino that referenced this pull request Sep 24, 2024
…pache#4055)

Add the list operation of the user

Fix: apache#3348

I will add the document later.

Add the new ut.
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.

[FEATURE] Need REST API to list all users' info in a metalake
6 participants