Merge changes I552aa95c,Ie65aeb40,I7cf2b92f
* changes: Add Javadoc and use a more general name for SchemaUpgradeTestEnvironment Use retry mechanism for group creations and name updates RetryHelper: Let callers care about exception handling
This commit is contained in:
		@@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkState;
 | 
			
		||||
 | 
			
		||||
import com.google.common.annotations.VisibleForTesting;
 | 
			
		||||
import com.google.common.base.Strings;
 | 
			
		||||
import com.google.common.base.Throwables;
 | 
			
		||||
import com.google.common.collect.Iterables;
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
import com.google.common.util.concurrent.Runnables;
 | 
			
		||||
@@ -37,6 +38,7 @@ import com.google.gerrit.server.git.MetaDataUpdate;
 | 
			
		||||
import com.google.gerrit.server.index.change.ReindexAfterRefUpdate;
 | 
			
		||||
import com.google.gerrit.server.update.RefUpdateUtil;
 | 
			
		||||
import com.google.gerrit.server.update.RetryHelper;
 | 
			
		||||
import com.google.gerrit.server.update.RetryHelper.Action;
 | 
			
		||||
import com.google.gerrit.server.update.RetryHelper.ActionType;
 | 
			
		||||
import com.google.gwtorm.server.OrmDuplicateKeyException;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
@@ -498,8 +500,7 @@ public class AccountsUpdate {
 | 
			
		||||
 | 
			
		||||
  private Optional<AccountState> updateAccount(AccountUpdate accountUpdate)
 | 
			
		||||
      throws IOException, ConfigInvalidException, OrmException {
 | 
			
		||||
    return retryHelper.execute(
 | 
			
		||||
        ActionType.ACCOUNT_UPDATE,
 | 
			
		||||
    return executeAccountUpdate(
 | 
			
		||||
        () -> {
 | 
			
		||||
          try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
 | 
			
		||||
            UpdatedAccount updatedAccount = accountUpdate.update(allUsersRepo);
 | 
			
		||||
@@ -513,6 +514,20 @@ public class AccountsUpdate {
 | 
			
		||||
        });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Optional<AccountState> executeAccountUpdate(Action<Optional<AccountState>> action)
 | 
			
		||||
      throws IOException, ConfigInvalidException, OrmException {
 | 
			
		||||
    try {
 | 
			
		||||
      return retryHelper.execute(
 | 
			
		||||
          ActionType.ACCOUNT_UPDATE, action, LockFailureException.class::isInstance);
 | 
			
		||||
    } catch (Exception e) {
 | 
			
		||||
      Throwables.throwIfUnchecked(e);
 | 
			
		||||
      Throwables.throwIfInstanceOf(e, IOException.class);
 | 
			
		||||
      Throwables.throwIfInstanceOf(e, ConfigInvalidException.class);
 | 
			
		||||
      Throwables.throwIfInstanceOf(e, OrmException.class);
 | 
			
		||||
      throw new OrmException(e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private ExternalIdNotes createExternalIdNotes(
 | 
			
		||||
      Repository allUsersRepo,
 | 
			
		||||
      Optional<ObjectId> rev,
 | 
			
		||||
 
 | 
			
		||||
@@ -41,6 +41,7 @@ import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_RE
 | 
			
		||||
import com.google.common.base.Function;
 | 
			
		||||
import com.google.common.base.Splitter;
 | 
			
		||||
import com.google.common.base.Strings;
 | 
			
		||||
import com.google.common.base.Throwables;
 | 
			
		||||
import com.google.common.collect.BiMap;
 | 
			
		||||
import com.google.common.collect.HashBiMap;
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
@@ -149,6 +150,7 @@ import com.google.gerrit.server.update.Context;
 | 
			
		||||
import com.google.gerrit.server.update.RepoContext;
 | 
			
		||||
import com.google.gerrit.server.update.RepoOnlyOp;
 | 
			
		||||
import com.google.gerrit.server.update.RetryHelper;
 | 
			
		||||
import com.google.gerrit.server.update.RetryHelper.Action;
 | 
			
		||||
import com.google.gerrit.server.update.RetryHelper.ActionType;
 | 
			
		||||
import com.google.gerrit.server.update.UpdateException;
 | 
			
		||||
import com.google.gerrit.server.util.LabelVote;
 | 
			
		||||
@@ -2891,10 +2893,7 @@ class ReceiveCommits {
 | 
			
		||||
                for (Ref ref : byCommit.get(c.copy())) {
 | 
			
		||||
                  PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
 | 
			
		||||
                  Optional<ChangeData> cd =
 | 
			
		||||
                      retryHelper.execute(
 | 
			
		||||
                          ActionType.CHANGE_QUERY,
 | 
			
		||||
                          () -> byLegacyId(psId.getParentKey()),
 | 
			
		||||
                          t -> t instanceof OrmException);
 | 
			
		||||
                      executeIndexQuery(() -> byLegacyId(psId.getParentKey()));
 | 
			
		||||
                  if (cd.isPresent() && cd.get().change().getDest().equals(branch)) {
 | 
			
		||||
                    existingPatchSets++;
 | 
			
		||||
                    bu.addOp(
 | 
			
		||||
@@ -2906,11 +2905,7 @@ class ReceiveCommits {
 | 
			
		||||
 | 
			
		||||
                for (String changeId : c.getFooterLines(CHANGE_ID)) {
 | 
			
		||||
                  if (byKey == null) {
 | 
			
		||||
                    byKey =
 | 
			
		||||
                        retryHelper.execute(
 | 
			
		||||
                            ActionType.CHANGE_QUERY,
 | 
			
		||||
                            () -> openChangesByKeyByBranch(branch),
 | 
			
		||||
                            t -> t instanceof OrmException);
 | 
			
		||||
                    byKey = executeIndexQuery(() -> openChangesByKeyByBranch(branch));
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                  ChangeNotes onto = byKey.get(new Change.Key(changeId.trim()));
 | 
			
		||||
@@ -2967,6 +2962,16 @@ class ReceiveCommits {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private <T> T executeIndexQuery(Action<T> action) throws OrmException {
 | 
			
		||||
    try {
 | 
			
		||||
      return retryHelper.execute(ActionType.INDEX_QUERY, action, OrmException.class::isInstance);
 | 
			
		||||
    } catch (Exception e) {
 | 
			
		||||
      Throwables.throwIfUnchecked(e);
 | 
			
		||||
      Throwables.throwIfInstanceOf(e, OrmException.class);
 | 
			
		||||
      throw new OrmException(e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void updateAccountInfo() {
 | 
			
		||||
    if (setFullNameTo == null) {
 | 
			
		||||
      return;
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ import static com.google.gerrit.server.group.db.Groups.getExistingGroupFromRevie
 | 
			
		||||
import com.google.auto.value.AutoValue;
 | 
			
		||||
import com.google.common.annotations.VisibleForTesting;
 | 
			
		||||
import com.google.common.base.Strings;
 | 
			
		||||
import com.google.common.base.Throwables;
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import com.google.common.collect.ImmutableSet;
 | 
			
		||||
import com.google.common.collect.Sets;
 | 
			
		||||
@@ -48,11 +49,13 @@ import com.google.gerrit.server.config.GerritServerConfig;
 | 
			
		||||
import com.google.gerrit.server.config.GerritServerId;
 | 
			
		||||
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 | 
			
		||||
import com.google.gerrit.server.git.GitRepositoryManager;
 | 
			
		||||
import com.google.gerrit.server.git.LockFailureException;
 | 
			
		||||
import com.google.gerrit.server.git.MetaDataUpdate;
 | 
			
		||||
import com.google.gerrit.server.git.RenameGroupOp;
 | 
			
		||||
import com.google.gerrit.server.group.InternalGroup;
 | 
			
		||||
import com.google.gerrit.server.notedb.GroupsMigration;
 | 
			
		||||
import com.google.gerrit.server.update.RefUpdateUtil;
 | 
			
		||||
import com.google.gerrit.server.update.RetryHelper;
 | 
			
		||||
import com.google.gwtorm.server.OrmDuplicateKeyException;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
@@ -111,6 +114,7 @@ public class GroupsUpdate {
 | 
			
		||||
  private final MetaDataUpdateFactory metaDataUpdateFactory;
 | 
			
		||||
  private final GroupsMigration groupsMigration;
 | 
			
		||||
  private final GitReferenceUpdated gitRefUpdated;
 | 
			
		||||
  private final RetryHelper retryHelper;
 | 
			
		||||
  private final boolean reviewDbUpdatesAreBlocked;
 | 
			
		||||
 | 
			
		||||
  @Inject
 | 
			
		||||
@@ -129,6 +133,7 @@ public class GroupsUpdate {
 | 
			
		||||
      GroupsMigration groupsMigration,
 | 
			
		||||
      @GerritServerConfig Config config,
 | 
			
		||||
      GitReferenceUpdated gitRefUpdated,
 | 
			
		||||
      RetryHelper retryHelper,
 | 
			
		||||
      @Assisted @Nullable IdentifiedUser currentUser) {
 | 
			
		||||
    this.repoManager = repoManager;
 | 
			
		||||
    this.allUsersName = allUsersName;
 | 
			
		||||
@@ -141,6 +146,7 @@ public class GroupsUpdate {
 | 
			
		||||
    this.serverId = serverId;
 | 
			
		||||
    this.groupsMigration = groupsMigration;
 | 
			
		||||
    this.gitRefUpdated = gitRefUpdated;
 | 
			
		||||
    this.retryHelper = retryHelper;
 | 
			
		||||
    this.currentUser = currentUser;
 | 
			
		||||
    metaDataUpdateFactory =
 | 
			
		||||
        getMetaDataUpdateFactory(metaDataUpdateInternalFactory, currentUser, serverIdent, serverId);
 | 
			
		||||
@@ -220,8 +226,7 @@ public class GroupsUpdate {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO(aliceks): Add retry mechanism.
 | 
			
		||||
    InternalGroup createdGroup = createGroupInNoteDb(groupCreation, groupUpdate);
 | 
			
		||||
    InternalGroup createdGroup = createGroupInNoteDbWithRetry(groupCreation, groupUpdate);
 | 
			
		||||
    updateCachesOnGroupCreation(createdGroup);
 | 
			
		||||
    return createdGroup;
 | 
			
		||||
  }
 | 
			
		||||
@@ -265,8 +270,8 @@ public class GroupsUpdate {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO(aliceks): Add retry mechanism.
 | 
			
		||||
    Optional<UpdateResult> noteDbUpdateResult = updateGroupInNoteDb(groupUuid, groupUpdate);
 | 
			
		||||
    Optional<UpdateResult> noteDbUpdateResult =
 | 
			
		||||
        updateGroupInNoteDbWithRetry(groupUuid, groupUpdate);
 | 
			
		||||
    return noteDbUpdateResult.orElse(reviewDbUpdateResult);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -476,9 +481,26 @@ public class GroupsUpdate {
 | 
			
		||||
    db.accountGroupById().delete(subgroupsToRemove);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private InternalGroup createGroupInNoteDb(
 | 
			
		||||
  private InternalGroup createGroupInNoteDbWithRetry(
 | 
			
		||||
      InternalGroupCreation groupCreation, InternalGroupUpdate groupUpdate)
 | 
			
		||||
      throws IOException, ConfigInvalidException, OrmException {
 | 
			
		||||
    try {
 | 
			
		||||
      return retryHelper.execute(
 | 
			
		||||
          RetryHelper.ActionType.GROUP_UPDATE,
 | 
			
		||||
          () -> createGroupInNoteDb(groupCreation, groupUpdate),
 | 
			
		||||
          LockFailureException.class::isInstance);
 | 
			
		||||
    } catch (Exception e) {
 | 
			
		||||
      Throwables.throwIfUnchecked(e);
 | 
			
		||||
      Throwables.throwIfInstanceOf(e, IOException.class);
 | 
			
		||||
      Throwables.throwIfInstanceOf(e, ConfigInvalidException.class);
 | 
			
		||||
      Throwables.throwIfInstanceOf(e, OrmDuplicateKeyException.class);
 | 
			
		||||
      throw new IOException(e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private InternalGroup createGroupInNoteDb(
 | 
			
		||||
      InternalGroupCreation groupCreation, InternalGroupUpdate groupUpdate)
 | 
			
		||||
      throws IOException, ConfigInvalidException, OrmDuplicateKeyException {
 | 
			
		||||
    try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
 | 
			
		||||
      AccountGroup.NameKey groupName = groupUpdate.getName().orElseGet(groupCreation::getNameKey);
 | 
			
		||||
      GroupNameNotes groupNameNotes =
 | 
			
		||||
@@ -496,6 +518,24 @@ public class GroupsUpdate {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Optional<UpdateResult> updateGroupInNoteDbWithRetry(
 | 
			
		||||
      AccountGroup.UUID groupUuid, InternalGroupUpdate groupUpdate)
 | 
			
		||||
      throws IOException, ConfigInvalidException, OrmDuplicateKeyException, NoSuchGroupException {
 | 
			
		||||
    try {
 | 
			
		||||
      return retryHelper.execute(
 | 
			
		||||
          RetryHelper.ActionType.GROUP_UPDATE,
 | 
			
		||||
          () -> updateGroupInNoteDb(groupUuid, groupUpdate),
 | 
			
		||||
          LockFailureException.class::isInstance);
 | 
			
		||||
    } catch (Exception e) {
 | 
			
		||||
      Throwables.throwIfUnchecked(e);
 | 
			
		||||
      Throwables.throwIfInstanceOf(e, IOException.class);
 | 
			
		||||
      Throwables.throwIfInstanceOf(e, ConfigInvalidException.class);
 | 
			
		||||
      Throwables.throwIfInstanceOf(e, OrmDuplicateKeyException.class);
 | 
			
		||||
      Throwables.throwIfInstanceOf(e, NoSuchGroupException.class);
 | 
			
		||||
      throw new IOException(e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Optional<UpdateResult> updateGroupInNoteDb(
 | 
			
		||||
      AccountGroup.UUID groupUuid, InternalGroupUpdate groupUpdate)
 | 
			
		||||
      throws IOException, ConfigInvalidException, OrmDuplicateKeyException, NoSuchGroupException {
 | 
			
		||||
 
 | 
			
		||||
@@ -40,14 +40,11 @@ import com.google.gerrit.metrics.MetricMaker;
 | 
			
		||||
import com.google.gerrit.server.config.GerritServerConfig;
 | 
			
		||||
import com.google.gerrit.server.git.LockFailureException;
 | 
			
		||||
import com.google.gerrit.server.notedb.NotesMigration;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Singleton;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.time.Duration;
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
import org.eclipse.jgit.errors.ConfigInvalidException;
 | 
			
		||||
import org.eclipse.jgit.lib.Config;
 | 
			
		||||
 | 
			
		||||
@Singleton
 | 
			
		||||
@@ -64,8 +61,9 @@ public class RetryHelper {
 | 
			
		||||
 | 
			
		||||
  public enum ActionType {
 | 
			
		||||
    ACCOUNT_UPDATE,
 | 
			
		||||
    CHANGE_QUERY,
 | 
			
		||||
    CHANGE_UPDATE
 | 
			
		||||
    CHANGE_UPDATE,
 | 
			
		||||
    GROUP_UPDATE,
 | 
			
		||||
    INDEX_QUERY
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@@ -182,22 +180,24 @@ public class RetryHelper {
 | 
			
		||||
    return defaultTimeout;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public <T> T execute(ActionType actionType, Action<T> action)
 | 
			
		||||
      throws IOException, ConfigInvalidException, OrmException {
 | 
			
		||||
    return execute(actionType, action, t -> t instanceof LockFailureException);
 | 
			
		||||
  public <T> T execute(
 | 
			
		||||
      ActionType actionType, Action<T> action, Predicate<Throwable> exceptionPredicate)
 | 
			
		||||
      throws Exception {
 | 
			
		||||
    return execute(actionType, action, defaults(), exceptionPredicate);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public <T> T execute(
 | 
			
		||||
      ActionType actionType, Action<T> action, Predicate<Throwable> exceptionPredicate)
 | 
			
		||||
      throws IOException, ConfigInvalidException, OrmException {
 | 
			
		||||
      ActionType actionType,
 | 
			
		||||
      Action<T> action,
 | 
			
		||||
      Options opts,
 | 
			
		||||
      Predicate<Throwable> exceptionPredicate)
 | 
			
		||||
      throws Exception {
 | 
			
		||||
    try {
 | 
			
		||||
      return execute(actionType, action, defaults(), exceptionPredicate);
 | 
			
		||||
      return executeWithAttemptAndTimeoutCount(actionType, action, opts, exceptionPredicate);
 | 
			
		||||
    } catch (Throwable t) {
 | 
			
		||||
      Throwables.throwIfUnchecked(t);
 | 
			
		||||
      Throwables.throwIfInstanceOf(t, IOException.class);
 | 
			
		||||
      Throwables.throwIfInstanceOf(t, ConfigInvalidException.class);
 | 
			
		||||
      Throwables.throwIfInstanceOf(t, OrmException.class);
 | 
			
		||||
      throw new OrmException(t);
 | 
			
		||||
      Throwables.throwIfInstanceOf(t, Exception.class);
 | 
			
		||||
      throw new IllegalStateException(t);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -212,7 +212,7 @@ public class RetryHelper {
 | 
			
		||||
        // Either we aren't full-NoteDb, or the underlying ref storage doesn't support atomic
 | 
			
		||||
        // transactions. Either way, retrying a partially-failed operation is not idempotent, so
 | 
			
		||||
        // don't do it automatically. Let the end user decide whether they want to retry.
 | 
			
		||||
        return execute(
 | 
			
		||||
        return executeWithTimeoutCount(
 | 
			
		||||
            ActionType.CHANGE_UPDATE,
 | 
			
		||||
            () -> changeAction.call(updateFactory),
 | 
			
		||||
            RetryerBuilder.<T>newBuilder().build());
 | 
			
		||||
@@ -237,7 +237,7 @@ public class RetryHelper {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Executes an action with a given retryer.
 | 
			
		||||
   * Executes an action and records the number of attempts and the timeout as metrics.
 | 
			
		||||
   *
 | 
			
		||||
   * @param actionType the type of the action
 | 
			
		||||
   * @param action the action which should be executed and retried on failure
 | 
			
		||||
@@ -247,7 +247,7 @@ public class RetryHelper {
 | 
			
		||||
   * @throws Throwable any error or exception that made the action fail, callers are expected to
 | 
			
		||||
   *     catch and inspect this Throwable to decide carefully whether it should be re-thrown
 | 
			
		||||
   */
 | 
			
		||||
  private <T> T execute(
 | 
			
		||||
  private <T> T executeWithAttemptAndTimeoutCount(
 | 
			
		||||
      ActionType actionType,
 | 
			
		||||
      Action<T> action,
 | 
			
		||||
      Options opts,
 | 
			
		||||
@@ -257,14 +257,14 @@ public class RetryHelper {
 | 
			
		||||
    try {
 | 
			
		||||
      RetryerBuilder<T> retryerBuilder = createRetryerBuilder(opts, exceptionPredicate);
 | 
			
		||||
      retryerBuilder.withRetryListener(listener);
 | 
			
		||||
      return execute(actionType, action, retryerBuilder.build());
 | 
			
		||||
      return executeWithTimeoutCount(actionType, action, retryerBuilder.build());
 | 
			
		||||
    } finally {
 | 
			
		||||
      metrics.attemptCounts.record(actionType, listener.getAttemptCount());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Executes an action with a given retryer.
 | 
			
		||||
   * Executes an action and records the timeout as metric.
 | 
			
		||||
   *
 | 
			
		||||
   * @param actionType the type of the action
 | 
			
		||||
   * @param action the action which should be executed and retried on failure
 | 
			
		||||
@@ -273,7 +273,7 @@ public class RetryHelper {
 | 
			
		||||
   * @throws Throwable any error or exception that made the action fail, callers are expected to
 | 
			
		||||
   *     catch and inspect this Throwable to decide carefully whether it should be re-thrown
 | 
			
		||||
   */
 | 
			
		||||
  private <T> T execute(ActionType actionType, Action<T> action, Retryer<T> retryer)
 | 
			
		||||
  private <T> T executeWithTimeoutCount(ActionType actionType, Action<T> action, Retryer<T> retryer)
 | 
			
		||||
      throws Throwable {
 | 
			
		||||
    try {
 | 
			
		||||
      return retryer.call(() -> action.call());
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,16 @@ import org.junit.rules.MethodRule;
 | 
			
		||||
import org.junit.runners.model.FrameworkMethod;
 | 
			
		||||
import org.junit.runners.model.Statement;
 | 
			
		||||
 | 
			
		||||
public final class SchemaUpgradeTestEnvironment implements MethodRule {
 | 
			
		||||
/**
 | 
			
		||||
 * An in-memory test environment for integration tests.
 | 
			
		||||
 *
 | 
			
		||||
 * <p>This test environment emulates the internals of a Gerrit server without starting a Gerrit
 | 
			
		||||
 * site. ReviewDb as well as NoteDb are represented by in-memory representations.
 | 
			
		||||
 *
 | 
			
		||||
 * <p>Each test is executed with a fresh and clean test environment. Hence, modifications applied in
 | 
			
		||||
 * one test don't carry over to subsequent ones.
 | 
			
		||||
 */
 | 
			
		||||
public final class InMemoryTestEnvironment implements MethodRule {
 | 
			
		||||
  private final Provider<Config> configProvider;
 | 
			
		||||
 | 
			
		||||
  @Inject private AccountManager accountManager;
 | 
			
		||||
@@ -50,7 +59,7 @@ public final class SchemaUpgradeTestEnvironment implements MethodRule {
 | 
			
		||||
  private LifecycleManager lifecycle;
 | 
			
		||||
 | 
			
		||||
  /** Create a test environment using an empty base config. */
 | 
			
		||||
  public SchemaUpgradeTestEnvironment() {
 | 
			
		||||
  public InMemoryTestEnvironment() {
 | 
			
		||||
    this(Config::new);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -62,7 +71,7 @@ public final class SchemaUpgradeTestEnvironment implements MethodRule {
 | 
			
		||||
   *
 | 
			
		||||
   * @param configProvider possibly-lazy provider for the base config.
 | 
			
		||||
   */
 | 
			
		||||
  public SchemaUpgradeTestEnvironment(Provider<Config> configProvider) {
 | 
			
		||||
  public InMemoryTestEnvironment(Provider<Config> configProvider) {
 | 
			
		||||
    this.configProvider = configProvider;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -0,0 +1,154 @@
 | 
			
		||||
// Copyright (C) 2018 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.acceptance.api.group;
 | 
			
		||||
 | 
			
		||||
import static com.google.common.truth.Truth8.assertThat;
 | 
			
		||||
import static com.google.gerrit.server.notedb.NoteDbTable.GROUPS;
 | 
			
		||||
import static com.google.gerrit.server.notedb.NotesMigration.DISABLE_REVIEW_DB;
 | 
			
		||||
import static com.google.gerrit.server.notedb.NotesMigration.READ;
 | 
			
		||||
import static com.google.gerrit.server.notedb.NotesMigration.SECTION_NOTE_DB;
 | 
			
		||||
import static com.google.gerrit.server.notedb.NotesMigration.WRITE;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableSet;
 | 
			
		||||
import com.google.gerrit.common.data.GroupReference;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Account;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.AccountGroup;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.group.ServerInitiated;
 | 
			
		||||
import com.google.gerrit.server.group.db.Groups;
 | 
			
		||||
import com.google.gerrit.server.group.db.GroupsUpdate;
 | 
			
		||||
import com.google.gerrit.server.group.db.InternalGroupCreation;
 | 
			
		||||
import com.google.gerrit.server.group.db.InternalGroupUpdate;
 | 
			
		||||
import com.google.gerrit.testing.InMemoryTestEnvironment;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
import org.eclipse.jgit.errors.ConfigInvalidException;
 | 
			
		||||
import org.eclipse.jgit.lib.Config;
 | 
			
		||||
import org.junit.Rule;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
 | 
			
		||||
public class GroupsUpdateIT {
 | 
			
		||||
 | 
			
		||||
  private static Config createPureNoteDbConfig() {
 | 
			
		||||
    Config config = new Config();
 | 
			
		||||
    config.setBoolean(SECTION_NOTE_DB, GROUPS.key(), WRITE, true);
 | 
			
		||||
    config.setBoolean(SECTION_NOTE_DB, GROUPS.key(), READ, true);
 | 
			
		||||
    config.setBoolean(SECTION_NOTE_DB, GROUPS.key(), DISABLE_REVIEW_DB, true);
 | 
			
		||||
    return config;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Rule
 | 
			
		||||
  public InMemoryTestEnvironment testEnvironment =
 | 
			
		||||
      new InMemoryTestEnvironment(GroupsUpdateIT::createPureNoteDbConfig);
 | 
			
		||||
 | 
			
		||||
  @Inject @ServerInitiated private Provider<GroupsUpdate> groupsUpdateProvider;
 | 
			
		||||
  @Inject private Groups groups;
 | 
			
		||||
  @Inject private ReviewDb reviewDb;
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void groupCreationIsRetriedWhenFailedDueToConcurrentNameModification() throws Exception {
 | 
			
		||||
    InternalGroupCreation groupCreation = getGroupCreation("users", "users-UUID");
 | 
			
		||||
    InternalGroupUpdate groupUpdate =
 | 
			
		||||
        InternalGroupUpdate.builder()
 | 
			
		||||
            .setMemberModification(
 | 
			
		||||
                new CreateAnotherGroupOnceAsSideEffectOfMemberModification("verifiers"))
 | 
			
		||||
            .build();
 | 
			
		||||
    createGroup(groupCreation, groupUpdate);
 | 
			
		||||
 | 
			
		||||
    Stream<String> allGroupNames = getAllGroupNames();
 | 
			
		||||
    assertThat(allGroupNames).containsAllOf("users", "verifiers");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  public void groupRenameIsRetriedWhenFailedDueToConcurrentNameModification() throws Exception {
 | 
			
		||||
    createGroup("users", "users-UUID");
 | 
			
		||||
 | 
			
		||||
    InternalGroupUpdate groupUpdate =
 | 
			
		||||
        InternalGroupUpdate.builder()
 | 
			
		||||
            .setName(new AccountGroup.NameKey("contributors"))
 | 
			
		||||
            .setMemberModification(
 | 
			
		||||
                new CreateAnotherGroupOnceAsSideEffectOfMemberModification("verifiers"))
 | 
			
		||||
            .build();
 | 
			
		||||
    updateGroup(new AccountGroup.UUID("users-UUID"), groupUpdate);
 | 
			
		||||
 | 
			
		||||
    Stream<String> allGroupNames = getAllGroupNames();
 | 
			
		||||
    assertThat(allGroupNames).containsAllOf("contributors", "verifiers");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void createGroup(String groupName, String groupUuid) throws Exception {
 | 
			
		||||
    InternalGroupCreation groupCreation = getGroupCreation(groupName, groupUuid);
 | 
			
		||||
    InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().build();
 | 
			
		||||
 | 
			
		||||
    createGroup(groupCreation, groupUpdate);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void createGroup(InternalGroupCreation groupCreation, InternalGroupUpdate groupUpdate)
 | 
			
		||||
      throws OrmException, IOException, ConfigInvalidException {
 | 
			
		||||
    groupsUpdateProvider.get().createGroup(reviewDb, groupCreation, groupUpdate);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void updateGroup(AccountGroup.UUID groupUuid, InternalGroupUpdate groupUpdate)
 | 
			
		||||
      throws Exception {
 | 
			
		||||
    groupsUpdateProvider.get().updateGroup(reviewDb, groupUuid, groupUpdate);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Stream<String> getAllGroupNames()
 | 
			
		||||
      throws OrmException, IOException, ConfigInvalidException {
 | 
			
		||||
    return groups.getAllGroupReferences(reviewDb).map(GroupReference::getName);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static InternalGroupCreation getGroupCreation(String groupName, String groupUuid) {
 | 
			
		||||
    return InternalGroupCreation.builder()
 | 
			
		||||
        .setGroupUUID(new AccountGroup.UUID(groupUuid))
 | 
			
		||||
        .setNameKey(new AccountGroup.NameKey(groupName))
 | 
			
		||||
        .setId(new AccountGroup.Id(Math.abs(groupName.hashCode())))
 | 
			
		||||
        .build();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class CreateAnotherGroupOnceAsSideEffectOfMemberModification
 | 
			
		||||
      implements InternalGroupUpdate.MemberModification {
 | 
			
		||||
 | 
			
		||||
    private boolean groupCreated = false;
 | 
			
		||||
    private String groupName;
 | 
			
		||||
 | 
			
		||||
    public CreateAnotherGroupOnceAsSideEffectOfMemberModification(String groupName) {
 | 
			
		||||
      this.groupName = groupName;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Set<Account.Id> apply(ImmutableSet<Account.Id> members) {
 | 
			
		||||
      if (!groupCreated) {
 | 
			
		||||
        createGroup();
 | 
			
		||||
        groupCreated = true;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return members;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void createGroup() {
 | 
			
		||||
      InternalGroupCreation groupCreation = getGroupCreation(groupName, groupName + "-UUID");
 | 
			
		||||
      InternalGroupUpdate groupUpdate = InternalGroupUpdate.builder().build();
 | 
			
		||||
      try {
 | 
			
		||||
        groupsUpdateProvider.get().createGroup(reviewDb, groupCreation, groupUpdate);
 | 
			
		||||
      } catch (OrmException | IOException | ConfigInvalidException e) {
 | 
			
		||||
        throw new IllegalStateException(e);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -25,7 +25,7 @@ import com.google.gerrit.reviewdb.client.AccountGroup;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.AccountGroup.Id;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.restapi.group.CreateGroup;
 | 
			
		||||
import com.google.gerrit.testing.SchemaUpgradeTestEnvironment;
 | 
			
		||||
import com.google.gerrit.testing.InMemoryTestEnvironment;
 | 
			
		||||
import com.google.gerrit.testing.TestUpdateUI;
 | 
			
		||||
import com.google.gwtorm.jdbc.JdbcSchema;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
@@ -44,7 +44,7 @@ import org.junit.Test;
 | 
			
		||||
 | 
			
		||||
public class Schema_150_to_151_Test {
 | 
			
		||||
 | 
			
		||||
  @Rule public SchemaUpgradeTestEnvironment testEnv = new SchemaUpgradeTestEnvironment();
 | 
			
		||||
  @Rule public InMemoryTestEnvironment testEnv = new InMemoryTestEnvironment();
 | 
			
		||||
 | 
			
		||||
  @Inject private CreateGroup.Factory createGroupFactory;
 | 
			
		||||
  @Inject private Schema_151 schema151;
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ import com.google.gerrit.server.account.AccountCache;
 | 
			
		||||
import com.google.gerrit.server.account.VersionedAccountPreferences;
 | 
			
		||||
import com.google.gerrit.server.config.AllUsersName;
 | 
			
		||||
import com.google.gerrit.server.git.GitRepositoryManager;
 | 
			
		||||
import com.google.gerrit.testing.SchemaUpgradeTestEnvironment;
 | 
			
		||||
import com.google.gerrit.testing.InMemoryTestEnvironment;
 | 
			
		||||
import com.google.gerrit.testing.TestUpdateUI;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
@@ -46,7 +46,7 @@ import org.junit.Rule;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
 | 
			
		||||
public class Schema_159_to_160_Test {
 | 
			
		||||
  @Rule public SchemaUpgradeTestEnvironment testEnv = new SchemaUpgradeTestEnvironment();
 | 
			
		||||
  @Rule public InMemoryTestEnvironment testEnv = new InMemoryTestEnvironment();
 | 
			
		||||
 | 
			
		||||
  @Inject private AccountCache accountCache;
 | 
			
		||||
  @Inject private AllUsersName allUsersName;
 | 
			
		||||
 
 | 
			
		||||
@@ -26,7 +26,7 @@ import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 | 
			
		||||
import com.google.gerrit.server.git.GitRepositoryManager;
 | 
			
		||||
import com.google.gerrit.server.git.MetaDataUpdate;
 | 
			
		||||
import com.google.gerrit.server.git.ProjectConfig;
 | 
			
		||||
import com.google.gerrit.testing.SchemaUpgradeTestEnvironment;
 | 
			
		||||
import com.google.gerrit.testing.InMemoryTestEnvironment;
 | 
			
		||||
import com.google.gerrit.testing.TestUpdateUI;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
@@ -39,7 +39,7 @@ import org.junit.Rule;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
 | 
			
		||||
public class Schema_161_to_162_Test {
 | 
			
		||||
  @Rule public SchemaUpgradeTestEnvironment testEnv = new SchemaUpgradeTestEnvironment();
 | 
			
		||||
  @Rule public InMemoryTestEnvironment testEnv = new InMemoryTestEnvironment();
 | 
			
		||||
 | 
			
		||||
  @Inject private AllProjectsName allProjectsName;
 | 
			
		||||
  @Inject private AllUsersName allUsersName;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user