We split the groups by uuid cache into two caches: 1. "groups_byuuid" stays the same. Caches by uuid without reading the SHA-1 of the ref from NoteDb. 2. "groups_byuuid_persisted" is the persistent variant of the above cache. This is used in the loader of "groups_byuuid" and it has the SHA-1 of the ref in the key. The new setup eases cold start times. The split is useful to allow accessing groups without loading the SHA-1 for each lookup. As a follow-up, we can also implement another method that gets multiple groups at the same time. This will be more efficient given that we will only need to access NoteDb once instead of once per group. This will be needed when getting the subgroups. While at it, we needed to add a new method that loads a group (to load it for a specific revision) so we also add a few @Nullable for other methods (since the sha-1 can be null). Change-Id: Ia52b3a3edcae0589c785e9d093776ec26dc324ca
		
			
				
	
	
		
			207 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			207 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
// Copyright (C) 2017 The Android Open Source Project
 | 
						|
//
 | 
						|
// Licensed 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 com.google.gerrit.server.group.db;
 | 
						|
 | 
						|
import com.google.common.collect.ImmutableList;
 | 
						|
import com.google.common.collect.ImmutableSet;
 | 
						|
import com.google.common.flogger.FluentLogger;
 | 
						|
import com.google.gerrit.common.Nullable;
 | 
						|
import com.google.gerrit.entities.AccountGroup;
 | 
						|
import com.google.gerrit.entities.AccountGroupByIdAudit;
 | 
						|
import com.google.gerrit.entities.AccountGroupMemberAudit;
 | 
						|
import com.google.gerrit.entities.GroupReference;
 | 
						|
import com.google.gerrit.entities.InternalGroup;
 | 
						|
import com.google.gerrit.server.config.AllUsersName;
 | 
						|
import com.google.gerrit.server.git.GitRepositoryManager;
 | 
						|
import com.google.inject.Inject;
 | 
						|
import com.google.inject.Singleton;
 | 
						|
import java.io.IOException;
 | 
						|
import java.util.List;
 | 
						|
import java.util.Optional;
 | 
						|
import java.util.stream.Stream;
 | 
						|
import org.eclipse.jgit.errors.ConfigInvalidException;
 | 
						|
import org.eclipse.jgit.lib.ObjectId;
 | 
						|
import org.eclipse.jgit.lib.Ref;
 | 
						|
import org.eclipse.jgit.lib.Repository;
 | 
						|
 | 
						|
/**
 | 
						|
 * A database accessor for read calls related to groups.
 | 
						|
 *
 | 
						|
 * <p>All calls which read group related details from the database are gathered here. Other classes
 | 
						|
 * should always use this class instead of accessing the database directly. There are a few
 | 
						|
 * exceptions though: schema classes, wrapper classes, and classes executed during init. The latter
 | 
						|
 * ones should use {@code GroupsOnInit} instead.
 | 
						|
 *
 | 
						|
 * <p>Most callers should not need to read groups directly from the database; they should use the
 | 
						|
 * {@link com.google.gerrit.server.account.GroupCache GroupCache} instead.
 | 
						|
 *
 | 
						|
 * <p>If not explicitly stated, all methods of this class refer to <em>internal</em> groups.
 | 
						|
 */
 | 
						|
@Singleton
 | 
						|
public class Groups {
 | 
						|
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 | 
						|
 | 
						|
  private final GitRepositoryManager repoManager;
 | 
						|
  private final AllUsersName allUsersName;
 | 
						|
  private final AuditLogReader auditLogReader;
 | 
						|
 | 
						|
  @Inject
 | 
						|
  public Groups(
 | 
						|
      GitRepositoryManager repoManager, AllUsersName allUsersName, AuditLogReader auditLogReader) {
 | 
						|
    this.repoManager = repoManager;
 | 
						|
    this.allUsersName = allUsersName;
 | 
						|
    this.auditLogReader = auditLogReader;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the {@code InternalGroup} for the specified UUID if it exists.
 | 
						|
   *
 | 
						|
   * @param groupUuid the UUID of the group
 | 
						|
   * @return the found {@code InternalGroup} if it exists, or else an empty {@code Optional}
 | 
						|
   * @throws IOException if the group couldn't be retrieved from NoteDb
 | 
						|
   * @throws ConfigInvalidException if the group couldn't be retrieved from NoteDb
 | 
						|
   */
 | 
						|
  public Optional<InternalGroup> getGroup(AccountGroup.UUID groupUuid)
 | 
						|
      throws IOException, ConfigInvalidException {
 | 
						|
    try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
 | 
						|
      return getGroupFromNoteDb(allUsersName, allUsersRepo, groupUuid);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the {@code InternalGroup} for the specified UUID and groupRefObjectId
 | 
						|
   *
 | 
						|
   * @param groupUuid the UUID of the group
 | 
						|
   * @param groupRefObjectId the ref revision of this group
 | 
						|
   * @return the found {@code InternalGroup} if it exists, or else an empty {@code Optional}
 | 
						|
   * @throws IOException if the group couldn't be retrieved from NoteDb
 | 
						|
   * @throws ConfigInvalidException if the group couldn't be retrieved from NoteDb
 | 
						|
   */
 | 
						|
  public Optional<InternalGroup> getGroup(
 | 
						|
      AccountGroup.UUID groupUuid, @Nullable ObjectId groupRefObjectId)
 | 
						|
      throws IOException, ConfigInvalidException {
 | 
						|
    try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
 | 
						|
      return getGroupFromNoteDb(allUsersName, allUsersRepo, groupUuid, groupRefObjectId);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Loads an internal group from NoteDb using the group UUID. This method returns the latest state
 | 
						|
   * of the internal group.
 | 
						|
   */
 | 
						|
  private static Optional<InternalGroup> getGroupFromNoteDb(
 | 
						|
      AllUsersName allUsersName, Repository allUsersRepository, AccountGroup.UUID groupUuid)
 | 
						|
      throws IOException, ConfigInvalidException {
 | 
						|
    return getGroupFromNoteDb(allUsersName, allUsersRepository, groupUuid, null);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Loads an internal group from NoteDb at the revision provided as {@link ObjectId}. This method
 | 
						|
   * is used to get a specific state of this group.
 | 
						|
   */
 | 
						|
  private static Optional<InternalGroup> getGroupFromNoteDb(
 | 
						|
      AllUsersName allUsersName,
 | 
						|
      Repository allUsersRepository,
 | 
						|
      AccountGroup.UUID uuid,
 | 
						|
      @Nullable ObjectId groupRefObjectId)
 | 
						|
      throws IOException, ConfigInvalidException {
 | 
						|
    GroupConfig groupConfig =
 | 
						|
        GroupConfig.loadForGroup(allUsersName, allUsersRepository, uuid, groupRefObjectId);
 | 
						|
    Optional<InternalGroup> loadedGroup = groupConfig.getLoadedGroup();
 | 
						|
    if (loadedGroup.isPresent()) {
 | 
						|
      // Check consistency with group name notes.
 | 
						|
      GroupsNoteDbConsistencyChecker.ensureConsistentWithGroupNameNotes(
 | 
						|
          allUsersRepository, loadedGroup.get());
 | 
						|
    }
 | 
						|
    return loadedGroup;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns {@code GroupReference}s for all internal groups.
 | 
						|
   *
 | 
						|
   * @return a stream of the {@code GroupReference}s of all internal groups
 | 
						|
   * @throws IOException if an error occurs while reading from NoteDb
 | 
						|
   * @throws ConfigInvalidException if the data in NoteDb is in an incorrect format
 | 
						|
   */
 | 
						|
  public Stream<GroupReference> getAllGroupReferences() throws IOException, ConfigInvalidException {
 | 
						|
    try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
 | 
						|
      return GroupNameNotes.loadAllGroups(allUsersRepo).stream();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns all known external groups. External groups are 'known' when they are specified as a
 | 
						|
   * subgroup of an internal group.
 | 
						|
   *
 | 
						|
   * @param internalGroupsRefs contains a list of all groups refs that we should inspect
 | 
						|
   * @return a stream of the UUIDs of the known external groups
 | 
						|
   * @throws IOException if an error occurs while reading from NoteDb
 | 
						|
   * @throws ConfigInvalidException if the data in NoteDb is in an incorrect format
 | 
						|
   */
 | 
						|
  public Stream<AccountGroup.UUID> getExternalGroups(ImmutableList<Ref> internalGroupsRefs)
 | 
						|
      throws IOException, ConfigInvalidException {
 | 
						|
    try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
 | 
						|
      return getExternalGroupsFromNoteDb(allUsersName, allUsersRepo, internalGroupsRefs);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  private static Stream<AccountGroup.UUID> getExternalGroupsFromNoteDb(
 | 
						|
      AllUsersName allUsersName, Repository allUsersRepo, ImmutableList<Ref> internalGroupsRefs)
 | 
						|
      throws IOException, ConfigInvalidException {
 | 
						|
    ImmutableSet.Builder<AccountGroup.UUID> allSubgroups = ImmutableSet.builder();
 | 
						|
    for (Ref internalGroupRef : internalGroupsRefs) {
 | 
						|
      AccountGroup.UUID uuid = AccountGroup.UUID.fromRef(internalGroupRef.getName());
 | 
						|
      if (uuid == null) {
 | 
						|
        logger.atWarning().log(
 | 
						|
            "Failed to get the group UUID from ref: %s", internalGroupRef.getName());
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      Optional<InternalGroup> group =
 | 
						|
          getGroupFromNoteDb(allUsersName, allUsersRepo, uuid, internalGroupRef.getObjectId());
 | 
						|
      group.map(InternalGroup::getSubgroups).ifPresent(allSubgroups::addAll);
 | 
						|
    }
 | 
						|
    return allSubgroups.build().stream().filter(groupUuid -> !groupUuid.isInternalGroup());
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the membership audit records for a given group.
 | 
						|
   *
 | 
						|
   * @param allUsersRepo All-Users repository.
 | 
						|
   * @param groupUuid the UUID of the group
 | 
						|
   * @return the audit records, in arbitrary order; empty if the group does not exist
 | 
						|
   * @throws IOException if an error occurs while reading from NoteDb
 | 
						|
   * @throws ConfigInvalidException if the group couldn't be retrieved from NoteDb
 | 
						|
   */
 | 
						|
  public List<AccountGroupMemberAudit> getMembersAudit(
 | 
						|
      Repository allUsersRepo, AccountGroup.UUID groupUuid)
 | 
						|
      throws IOException, ConfigInvalidException {
 | 
						|
    return auditLogReader.getMembersAudit(allUsersRepo, groupUuid);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns the subgroup audit records for a given group.
 | 
						|
   *
 | 
						|
   * @param repo All-Users repository.
 | 
						|
   * @param groupUuid the UUID of the group
 | 
						|
   * @return the audit records, in arbitrary order; empty if the group does not exist
 | 
						|
   * @throws IOException if an error occurs while reading from NoteDb
 | 
						|
   * @throws ConfigInvalidException if the group couldn't be retrieved from NoteDb
 | 
						|
   */
 | 
						|
  public List<AccountGroupByIdAudit> getSubgroupsAudit(Repository repo, AccountGroup.UUID groupUuid)
 | 
						|
      throws IOException, ConfigInvalidException {
 | 
						|
    return auditLogReader.getSubgroupsAudit(repo, groupUuid);
 | 
						|
  }
 | 
						|
}
 |