Merge changes from topic "notedb-schema-upgrade"
* changes:
Remove underlying ReviewDb from NoteDb wrappers
Sequences: Remove ReviewDb support
Ensure 2.16 upgrade is run before 3.x upgrade
NoteDb schema upgrades
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index 8a3f618..b73d57e 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -90,8 +90,8 @@
import com.google.gerrit.server.schema.DataSourceType;
import com.google.gerrit.server.schema.DatabaseModule;
import com.google.gerrit.server.schema.JdbcAccountPatchReviewStore;
+import com.google.gerrit.server.schema.NoteDbSchemaVersionCheck;
import com.google.gerrit.server.schema.ReviewDbSchemaModule;
-import com.google.gerrit.server.schema.ReviewDbSchemaVersionCheck;
import com.google.gerrit.server.securestore.SecureStoreClassName;
import com.google.gerrit.server.ssh.NoSshModule;
import com.google.gerrit.server.ssh.SshAddressesModule;
@@ -310,7 +310,7 @@
private Injector createCfgInjector() {
final List<Module> modules = new ArrayList<>();
modules.add(new ReviewDbSchemaModule());
- modules.add(ReviewDbSchemaVersionCheck.module());
+ modules.add(NoteDbSchemaVersionCheck.module());
modules.add(new AuthConfigModule());
return dbInjector.createChildInjector(modules);
}
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 33b3268..e480f77 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -97,7 +97,7 @@
import com.google.gerrit.server.schema.DataSourceProvider;
import com.google.gerrit.server.schema.InMemoryAccountPatchReviewStore;
import com.google.gerrit.server.schema.JdbcAccountPatchReviewStore;
-import com.google.gerrit.server.schema.ReviewDbSchemaVersionCheck;
+import com.google.gerrit.server.schema.NoteDbSchemaVersionCheck;
import com.google.gerrit.server.securestore.DefaultSecureStore;
import com.google.gerrit.server.securestore.SecureStore;
import com.google.gerrit.server.securestore.SecureStoreClassName;
@@ -290,7 +290,6 @@
JythonShell shell = new JythonShell();
shell.set("m", manager);
shell.set("ds", dbInjector.getInstance(DataSourceProvider.class));
- shell.set("schk", dbInjector.getInstance(ReviewDbSchemaVersionCheck.class));
shell.set("d", this);
shell.run();
} else {
@@ -397,7 +396,7 @@
private Injector createSysInjector() {
final List<Module> modules = new ArrayList<>();
- modules.add(ReviewDbSchemaVersionCheck.module());
+ modules.add(NoteDbSchemaVersionCheck.module());
modules.add(new DropWizardMetricMaker.RestModule());
modules.add(new LogFileCompressor.Module());
diff --git a/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java b/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
index 6ebd6a3..feeaa27 100644
--- a/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
+++ b/java/com/google/gerrit/pgm/LocalUsernamesToLowerCase.java
@@ -29,7 +29,7 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
-import com.google.gerrit.server.schema.ReviewDbSchemaVersionCheck;
+import com.google.gerrit.server.schema.NoteDbSchemaVersionCheck;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.inject.Inject;
import com.google.inject.Injector;
@@ -55,7 +55,7 @@
@Override
public int run() throws Exception {
Injector dbInjector = createDbInjector(MULTI_USER);
- manager.add(dbInjector, dbInjector.createChildInjector(ReviewDbSchemaVersionCheck.module()));
+ manager.add(dbInjector, dbInjector.createChildInjector(NoteDbSchemaVersionCheck.module()));
manager.start();
dbInjector
.createChildInjector(
diff --git a/java/com/google/gerrit/pgm/init/BaseInit.java b/java/com/google/gerrit/pgm/init/BaseInit.java
index 71957e1..9a92408 100644
--- a/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -42,6 +42,7 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.IndexModule;
import com.google.gerrit.server.plugins.JarScanner;
+import com.google.gerrit.server.schema.NoteDbSchemaUpdater;
import com.google.gerrit.server.schema.ReviewDbFactory;
import com.google.gerrit.server.schema.ReviewDbSchemaUpdater;
import com.google.gerrit.server.schema.UpdateUI;
@@ -358,7 +359,8 @@
public final ConsoleUI ui;
public final SitePaths site;
public final InitFlags flags;
- final ReviewDbSchemaUpdater schemaUpdater;
+ final ReviewDbSchemaUpdater reviewDbSchemaUpdater;
+ final NoteDbSchemaUpdater noteDbSchemaUpdater;
final SchemaFactory<ReviewDb> schema;
final GitRepositoryManager repositoryManager;
@@ -367,57 +369,23 @@
ConsoleUI ui,
SitePaths site,
InitFlags flags,
- ReviewDbSchemaUpdater schemaUpdater,
+ ReviewDbSchemaUpdater reviewDbSchemaUpdater,
+ NoteDbSchemaUpdater noteDbSchemaUpdater,
@ReviewDbFactory SchemaFactory<ReviewDb> schema,
GitRepositoryManager repositoryManager) {
this.ui = ui;
this.site = site;
this.flags = flags;
- this.schemaUpdater = schemaUpdater;
+ this.reviewDbSchemaUpdater = reviewDbSchemaUpdater;
+ this.noteDbSchemaUpdater = noteDbSchemaUpdater;
this.schema = schema;
this.repositoryManager = repositoryManager;
}
void upgradeSchema() throws OrmException {
final List<String> pruneList = new ArrayList<>();
- schemaUpdater.update(
- new UpdateUI() {
- @Override
- public void message(String message) {
- System.err.println(message);
- System.err.flush();
- }
-
- @Override
- public boolean yesno(boolean defaultValue, String message) {
- return ui.yesno(defaultValue, message);
- }
-
- @Override
- public void waitForUser() {
- ui.waitForUser();
- }
-
- @Override
- public String readString(
- String defaultValue, Set<String> allowedValues, String message) {
- return ui.readString(defaultValue, allowedValues, message);
- }
-
- @Override
- public boolean isBatch() {
- return ui.isBatch();
- }
-
- @Override
- public void pruneSchema(StatementExecutor e, List<String> prune) {
- for (String p : prune) {
- if (!pruneList.contains(p)) {
- pruneList.add(p);
- }
- }
- }
- });
+ UpdateUI uiImpl = new UpdateUIImpl(ui, pruneList);
+ reviewDbSchemaUpdater.update(uiImpl);
if (!pruneList.isEmpty()) {
StringBuilder msg = new StringBuilder();
@@ -442,6 +410,53 @@
}
}
}
+
+ noteDbSchemaUpdater.update(uiImpl);
+ }
+
+ private static class UpdateUIImpl implements UpdateUI {
+ private final ConsoleUI consoleUi;
+ private final List<String> pruneList;
+
+ UpdateUIImpl(ConsoleUI consoleUi, List<String> pruneList) {
+ this.consoleUi = consoleUi;
+ this.pruneList = pruneList;
+ }
+
+ @Override
+ public void message(String message) {
+ System.err.println(message);
+ System.err.flush();
+ }
+
+ @Override
+ public boolean yesno(boolean defaultValue, String message) {
+ return consoleUi.yesno(defaultValue, message);
+ }
+
+ @Override
+ public void waitForUser() {
+ consoleUi.waitForUser();
+ }
+
+ @Override
+ public String readString(String defaultValue, Set<String> allowedValues, String message) {
+ return consoleUi.readString(defaultValue, allowedValues, message);
+ }
+
+ @Override
+ public boolean isBatch() {
+ return consoleUi.isBatch();
+ }
+
+ @Override
+ public void pruneSchema(StatementExecutor e, List<String> prune) {
+ for (String p : prune) {
+ if (!pruneList.contains(p)) {
+ pruneList.add(p);
+ }
+ }
+ }
}
}
diff --git a/java/com/google/gerrit/reviewdb/client/RefNames.java b/java/com/google/gerrit/reviewdb/client/RefNames.java
index fd2fb56..5e45088 100644
--- a/java/com/google/gerrit/reviewdb/client/RefNames.java
+++ b/java/com/google/gerrit/reviewdb/client/RefNames.java
@@ -49,6 +49,9 @@
/** Sequence counters in NoteDb. */
public static final String REFS_SEQUENCES = "refs/sequences/";
+ /** NoteDb schema version number. */
+ public static final String REFS_VERSION = "refs/meta/version";
+
/**
* Prefix applied to merge commit base nodes.
*
diff --git a/java/com/google/gerrit/reviewdb/server/DisallowReadFromChangesReviewDbWrapper.java b/java/com/google/gerrit/reviewdb/server/DisallowedReviewDb.java
similarity index 97%
rename from java/com/google/gerrit/reviewdb/server/DisallowReadFromChangesReviewDbWrapper.java
rename to java/com/google/gerrit/reviewdb/server/DisallowedReviewDb.java
index fdf3d6c..60c3c95 100644
--- a/java/com/google/gerrit/reviewdb/server/DisallowReadFromChangesReviewDbWrapper.java
+++ b/java/com/google/gerrit/reviewdb/server/DisallowedReviewDb.java
@@ -23,7 +23,7 @@
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
-public class DisallowReadFromChangesReviewDbWrapper extends ReviewDbWrapper {
+public class DisallowedReviewDb extends ReviewDbWrapper {
private static final String MSG = "This table has been migrated to NoteDb";
private final Changes changes;
@@ -32,7 +32,7 @@
private final PatchSets patchSets;
private final PatchLineComments patchComments;
- public DisallowReadFromChangesReviewDbWrapper(ReviewDb db) {
+ public DisallowedReviewDb(ReviewDb db) {
super(db);
changes = new Changes(delegate.changes());
patchSetApprovals = new PatchSetApprovals(delegate.patchSetApprovals());
diff --git a/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java b/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java
index aed9778..1f0f12f 100644
--- a/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java
+++ b/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java
@@ -50,8 +50,8 @@
}
public static ReviewDb unwrapDb(ReviewDb db) {
- if (db instanceof DisallowReadFromChangesReviewDbWrapper) {
- return unwrapDb(((DisallowReadFromChangesReviewDbWrapper) db).unsafeGetDelegate());
+ if (db instanceof DisallowedReviewDb) {
+ return unwrapDb(((DisallowedReviewDb) db).unsafeGetDelegate());
}
return db;
}
diff --git a/java/com/google/gerrit/server/Sequences.java b/java/com/google/gerrit/server/Sequences.java
index 70a02a8..8381b5c 100644
--- a/java/com/google/gerrit/server/Sequences.java
+++ b/java/com/google/gerrit/server/Sequences.java
@@ -14,8 +14,6 @@
package com.google.gerrit.server;
-import static com.google.common.base.Preconditions.checkArgument;
-
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.metrics.Description;
@@ -29,14 +27,10 @@
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.notedb.RepoSequence;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
-import java.util.ArrayList;
-import java.util.List;
import org.eclipse.jgit.lib.Config;
@Singleton
@@ -55,8 +49,6 @@
GROUPS;
}
- private final Provider<ReviewDb> db;
- private final NotesMigration migration;
private final RepoSequence accountSeq;
private final RepoSequence changeSeq;
private final RepoSequence groupSeq;
@@ -65,15 +57,11 @@
@Inject
public Sequences(
@GerritServerConfig Config cfg,
- Provider<ReviewDb> db,
- NotesMigration migration,
GitRepositoryManager repoManager,
GitReferenceUpdated gitRefUpdated,
AllProjectsName allProjects,
AllUsersName allUsers,
MetricMaker metrics) {
- this.db = db;
- this.migration = migration;
int accountBatchSize = cfg.getInt("noteDb", "accounts", "sequenceBatchSize", 1);
accountSeq =
@@ -85,13 +73,15 @@
() -> ReviewDb.FIRST_ACCOUNT_ID,
accountBatchSize);
- int gap = getChangeSequenceGap(cfg);
- @SuppressWarnings("deprecation")
- RepoSequence.Seed changeSeed = () -> db.get().nextChangeId() + gap;
int changeBatchSize = cfg.getInt("noteDb", "changes", "sequenceBatchSize", 20);
changeSeq =
new RepoSequence(
- repoManager, gitRefUpdated, allProjects, NAME_CHANGES, changeSeed, changeBatchSize);
+ repoManager,
+ gitRefUpdated,
+ allProjects,
+ NAME_CHANGES,
+ () -> ReviewDb.FIRST_CHANGE_ID,
+ changeBatchSize);
int groupBatchSize = 1;
groupSeq =
@@ -120,31 +110,15 @@
}
public int nextChangeId() throws OrmException {
- if (!migration.readChangeSequence()) {
- return nextChangeId(db.get());
- }
try (Timer2.Context timer = nextIdLatency.start(SequenceType.CHANGES, false)) {
return changeSeq.next();
}
}
public ImmutableList<Integer> nextChangeIds(int count) throws OrmException {
- if (migration.readChangeSequence()) {
- try (Timer2.Context timer = nextIdLatency.start(SequenceType.CHANGES, count > 1)) {
- return changeSeq.next(count);
- }
+ try (Timer2.Context timer = nextIdLatency.start(SequenceType.CHANGES, count > 1)) {
+ return changeSeq.next(count);
}
-
- if (count == 0) {
- return ImmutableList.of();
- }
- checkArgument(count > 0, "count is negative: %s", count);
- List<Integer> ids = new ArrayList<>(count);
- ReviewDb db = this.db.get();
- for (int i = 0; i < count; i++) {
- ids.add(nextChangeId(db));
- }
- return ImmutableList.copyOf(ids);
}
public int nextGroupId() throws OrmException {
@@ -157,9 +131,4 @@
public RepoSequence getChangeIdRepoSequence() {
return changeSeq;
}
-
- @SuppressWarnings("deprecation")
- private static int nextChangeId(ReviewDb db) throws OrmException {
- return db.nextChangeId();
- }
}
diff --git a/java/com/google/gerrit/server/notedb/NoteDbSchemaVersionManager.java b/java/com/google/gerrit/server/notedb/NoteDbSchemaVersionManager.java
new file mode 100644
index 0000000..a8355c3
--- /dev/null
+++ b/java/com/google/gerrit/server/notedb/NoteDbSchemaVersionManager.java
@@ -0,0 +1,101 @@
+// 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.server.notedb;
+
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_VERSION;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gwtorm.server.OrmException;
+import java.io.IOException;
+import java.util.Optional;
+import javax.inject.Inject;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+public class NoteDbSchemaVersionManager {
+ private final AllProjectsName allProjectsName;
+ private final GitRepositoryManager repoManager;
+
+ @Inject
+ @VisibleForTesting
+ public NoteDbSchemaVersionManager(
+ AllProjectsName allProjectsName, GitRepositoryManager repoManager) {
+ // Can't inject GitReferenceUpdated here because it has dependencies that are not always
+ // available in this injector (e.g. during init). This is ok for now since no other ref updates
+ // during init are available to plugins, and there are not any other use cases for listening for
+ // updates to the version ref.
+ this.allProjectsName = allProjectsName;
+ this.repoManager = repoManager;
+ }
+
+ public int read() throws OrmException {
+ try (Repository repo = repoManager.openRepository(allProjectsName)) {
+ return IntBlob.parse(repo, REFS_VERSION).map(IntBlob::value).orElse(0);
+ } catch (IOException e) {
+ throw new OrmException("Failed to read " + REFS_VERSION, e);
+ }
+ }
+
+ public void init() throws IOException, OrmException {
+ try (Repository repo = repoManager.openRepository(allProjectsName);
+ RevWalk rw = new RevWalk(repo)) {
+ Optional<IntBlob> old = IntBlob.parse(repo, REFS_VERSION, rw);
+ if (old.isPresent()) {
+ throw new OrmException(
+ String.format(
+ "Expected no old version for %s, found %s", REFS_VERSION, old.get().value()));
+ }
+ IntBlob.store(
+ repo,
+ rw,
+ allProjectsName,
+ REFS_VERSION,
+ old.map(IntBlob::id).orElse(ObjectId.zeroId()),
+ // TODO(dborowitz): Find some way to not hard-code this constant here. We can't depend on
+ // NoteDbSchemaVersions from this package, because the schema java_library depends on the
+ // server java_library, so that would add a circular dependency. But *this* class must
+ // live in the server library, because it's used by things like NoteDbMigrator. One
+ // option: once NoteDbMigrator goes away, this class could move back to the schema
+ // subpackage.
+ 180,
+ GitReferenceUpdated.DISABLED);
+ }
+ }
+
+ public void increment(int expectedOldVersion) throws IOException, OrmException {
+ try (Repository repo = repoManager.openRepository(allProjectsName);
+ RevWalk rw = new RevWalk(repo)) {
+ Optional<IntBlob> old = IntBlob.parse(repo, REFS_VERSION, rw);
+ if (old.isPresent() && old.get().value() != expectedOldVersion) {
+ throw new OrmException(
+ String.format(
+ "Expected old version %d for %s, found %d",
+ expectedOldVersion, REFS_VERSION, old.get().value()));
+ }
+ IntBlob.store(
+ repo,
+ rw,
+ allProjectsName,
+ REFS_VERSION,
+ old.map(IntBlob::id).orElse(ObjectId.zeroId()),
+ expectedOldVersion + 1,
+ GitReferenceUpdated.DISABLED);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/notedb/NotesMigration.java b/java/com/google/gerrit/server/notedb/NotesMigration.java
index 9cee2cd..c7acaf1 100644
--- a/java/com/google/gerrit/server/notedb/NotesMigration.java
+++ b/java/com/google/gerrit/server/notedb/NotesMigration.java
@@ -57,9 +57,8 @@
public static final String READ = "read";
public static final String WRITE = "write";
public static final String DISABLE_REVIEW_DB = "disableReviewDb";
-
- private static final String PRIMARY_STORAGE = "primaryStorage";
- private static final String SEQUENCE = "sequence";
+ public static final String PRIMARY_STORAGE = "primaryStorage";
+ public static final String SEQUENCE = "sequence";
public static class Module extends AbstractModule {
@Override
diff --git a/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java b/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
index ff935d8..f60744f 100644
--- a/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
+++ b/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
@@ -64,6 +64,7 @@
import com.google.gerrit.server.notedb.ChangeBundleReader;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.MutableNotesMigration;
+import com.google.gerrit.server.notedb.NoteDbSchemaVersionManager;
import com.google.gerrit.server.notedb.NoteDbTable;
import com.google.gerrit.server.notedb.NoteDbUpdateManager;
import com.google.gerrit.server.notedb.NotesMigrationState;
@@ -151,6 +152,7 @@
private final MutableNotesMigration globalNotesMigration;
private final PrimaryStorageMigrator primaryStorageMigrator;
private final PluginSetContext<NotesMigrationStateListener> listeners;
+ private final NoteDbSchemaVersionManager versionManager;
private int threads;
private ImmutableList<Project.NameKey> projects = ImmutableList.of();
@@ -179,7 +181,8 @@
WorkQueue workQueue,
MutableNotesMigration globalNotesMigration,
PrimaryStorageMigrator primaryStorageMigrator,
- PluginSetContext<NotesMigrationStateListener> listeners) {
+ PluginSetContext<NotesMigrationStateListener> listeners,
+ NoteDbSchemaVersionManager versionManager) {
// Reload gerrit.config/notedb.config on each migrator invocation, in case a previous
// migration in the same process modified the on-disk contents. This ensures the defaults for
// trial/autoMigrate get set correctly below.
@@ -199,6 +202,7 @@
this.globalNotesMigration = globalNotesMigration;
this.primaryStorageMigrator = primaryStorageMigrator;
this.listeners = listeners;
+ this.versionManager = versionManager;
this.trial = getTrialMode(cfg);
this.autoMigrate = getAutoMigrate(cfg);
}
@@ -361,6 +365,7 @@
globalNotesMigration,
primaryStorageMigrator,
listeners,
+ versionManager,
threads > 1
? MoreExecutors.listeningDecorator(
workQueue.createQueue(threads, "RebuildChange", true))
@@ -391,6 +396,7 @@
private final MutableNotesMigration globalNotesMigration;
private final PrimaryStorageMigrator primaryStorageMigrator;
private final PluginSetContext<NotesMigrationStateListener> listeners;
+ private final NoteDbSchemaVersionManager versionManager;
private final ListeningExecutorService executor;
private final ImmutableList<Project.NameKey> projects;
@@ -417,6 +423,7 @@
MutableNotesMigration globalNotesMigration,
PrimaryStorageMigrator primaryStorageMigrator,
PluginSetContext<NotesMigrationStateListener> listeners,
+ NoteDbSchemaVersionManager versionManager,
ListeningExecutorService executor,
ImmutableList<Project.NameKey> projects,
ImmutableList<Change.Id> changes,
@@ -447,6 +454,7 @@
this.globalNotesMigration = globalNotesMigration;
this.primaryStorageMigrator = primaryStorageMigrator;
this.listeners = listeners;
+ this.versionManager = versionManager;
this.executor = executor;
this.projects = projects;
this.changes = changes;
@@ -546,7 +554,9 @@
}
}
- private NotesMigrationState turnOnWrites(NotesMigrationState prev) throws IOException {
+ private NotesMigrationState turnOnWrites(NotesMigrationState prev)
+ throws OrmException, IOException {
+ versionManager.init();
return saveState(prev, WRITE);
}
diff --git a/java/com/google/gerrit/server/schema/AbstractDisabledAccess.java b/java/com/google/gerrit/server/schema/AbstractDisabledAccess.java
index 17eb56e..7ebc741 100644
--- a/java/com/google/gerrit/server/schema/AbstractDisabledAccess.java
+++ b/java/com/google/gerrit/server/schema/AbstractDisabledAccess.java
@@ -14,11 +14,8 @@
package com.google.gerrit.server.schema;
-import static com.google.common.base.Preconditions.checkState;
-
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.Futures;
-import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
import com.google.gwtorm.client.Key;
import com.google.gwtorm.server.Access;
import com.google.gwtorm.server.AtomicUpdate;
@@ -26,9 +23,10 @@
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import java.util.Map;
-import java.util.function.Function;
abstract class AbstractDisabledAccess<T, K extends Key<?>> implements Access<T, K> {
+ private static final String GONE = "ReviewDb is gone";
+
private static <T> ResultSet<T> empty() {
return new ListResultSet<>(ImmutableList.of());
}
@@ -39,40 +37,30 @@
return Futures.immediateCheckedFuture(null);
}
- // Don't even hold a reference to delegate, so it's not possible to use it
- // accidentally.
- private final ReviewDbWrapper wrapper;
- private final String relationName;
- private final int relationId;
- private final Function<T, K> primaryKey;
- private final Function<Iterable<T>, Map<K, T>> toMap;
+ private final NoChangesReviewDb wrapper;
- AbstractDisabledAccess(ReviewDbWrapper wrapper, Access<T, K> delegate) {
+ AbstractDisabledAccess(NoChangesReviewDb wrapper) {
this.wrapper = wrapper;
- this.relationName = delegate.getRelationName();
- this.relationId = delegate.getRelationID();
- this.primaryKey = delegate::primaryKey;
- this.toMap = delegate::toMap;
}
@Override
public final int getRelationID() {
- return relationId;
+ throw new UnsupportedOperationException(GONE);
}
@Override
public final String getRelationName() {
- return relationName;
+ throw new UnsupportedOperationException(GONE);
}
@Override
public final K primaryKey(T entity) {
- return primaryKey.apply(entity);
+ throw new UnsupportedOperationException(GONE);
}
@Override
public final Map<K, T> toMap(Iterable<T> iterable) {
- return toMap.apply(iterable);
+ throw new UnsupportedOperationException(GONE);
}
@Override
@@ -118,15 +106,7 @@
@Override
public final void beginTransaction(K key) {
- // Keep track of when we've started a transaction so that we can avoid calling commit/rollback
- // on the underlying ReviewDb. This is just a simple arm's-length approach, and may produce
- // slightly different results from a native ReviewDb in corner cases like:
- // * beginning transactions on different tables simultaneously
- // * doing work between commit and rollback
- // These kinds of things are already misuses of ReviewDb, and shouldn't be happening in current
- // code anyway.
- checkState(!wrapper.inTransaction(), "already in transaction");
- wrapper.beginTransaction();
+ // Do nothing.
}
@Override
diff --git a/java/com/google/gerrit/server/schema/AllProjectsCreator.java b/java/com/google/gerrit/server/schema/AllProjectsCreator.java
index 85965ef..14da9eb 100644
--- a/java/com/google/gerrit/server/schema/AllProjectsCreator.java
+++ b/java/com/google/gerrit/server/schema/AllProjectsCreator.java
@@ -48,9 +48,11 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.meta.MetaDataUpdate;
import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.notedb.NoteDbSchemaVersionManager;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.notedb.RepoSequence;
import com.google.gerrit.server.project.ProjectConfig;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
import java.util.ArrayList;
@@ -73,6 +75,7 @@
private final AllProjectsName allProjectsName;
private final PersonIdent serverUser;
private final NotesMigration notesMigration;
+ private final NoteDbSchemaVersionManager versionManager;
private final ProjectConfig.Factory projectConfigFactory;
private final GroupReference anonymous;
private final GroupReference registered;
@@ -91,12 +94,14 @@
AllProjectsName allProjectsName,
@GerritPersonIdent PersonIdent serverUser,
NotesMigration notesMigration,
+ NoteDbSchemaVersionManager versionManager,
SystemGroupBackend systemGroupBackend,
ProjectConfig.Factory projectConfigFactory) {
this.repositoryManager = repositoryManager;
this.allProjectsName = allProjectsName;
this.serverUser = serverUser;
this.notesMigration = notesMigration;
+ this.versionManager = versionManager;
this.projectConfigFactory = projectConfigFactory;
this.anonymous = systemGroupBackend.getGroup(ANONYMOUS_USERS);
@@ -145,7 +150,7 @@
return this;
}
- public void create() throws IOException, ConfigInvalidException {
+ public void create() throws IOException, ConfigInvalidException, OrmException {
try (Repository git = repositoryManager.openRepository(allProjectsName)) {
initAllProjects(git);
} catch (RepositoryNotFoundException notFound) {
@@ -162,7 +167,8 @@
}
}
- private void initAllProjects(Repository git) throws IOException, ConfigInvalidException {
+ private void initAllProjects(Repository git)
+ throws IOException, ConfigInvalidException, OrmException {
BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate();
try (MetaDataUpdate md =
new MetaDataUpdate(GitReferenceUpdated.DISABLED, allProjectsName, git, bru)) {
@@ -231,6 +237,7 @@
config.commitToNewRef(md, RefNames.REFS_CONFIG);
initSequences(git, bru);
+ initSchemaVersion();
execute(git, bru);
}
}
@@ -268,6 +275,12 @@
}
}
+ private void initSchemaVersion() throws IOException, OrmException {
+ if (notesMigration.commitChangeWrites()) {
+ versionManager.init();
+ }
+ }
+
private void execute(Repository git, BatchRefUpdate bru) throws IOException {
try (RevWalk rw = new RevWalk(git)) {
bru.execute(rw, NullProgressMonitor.INSTANCE);
diff --git a/java/com/google/gerrit/server/schema/NoChangesReviewDbWrapper.java b/java/com/google/gerrit/server/schema/NoChangesReviewDb.java
similarity index 76%
rename from java/com/google/gerrit/server/schema/NoChangesReviewDbWrapper.java
rename to java/com/google/gerrit/server/schema/NoChangesReviewDb.java
index 7247490..083ecd1 100644
--- a/java/com/google/gerrit/server/schema/NoChangesReviewDbWrapper.java
+++ b/java/com/google/gerrit/server/schema/NoChangesReviewDb.java
@@ -27,17 +27,21 @@
import com.google.gerrit.reviewdb.server.PatchSetAccess;
import com.google.gerrit.reviewdb.server.PatchSetApprovalAccess;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
+import com.google.gerrit.reviewdb.server.SchemaVersionAccess;
+import com.google.gwtorm.server.Access;
import com.google.gwtorm.server.ListResultSet;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
+import com.google.gwtorm.server.StatementExecutor;
/**
* Wrapper for ReviewDb that never calls the underlying change tables.
*
* <p>See {@link NotesMigrationSchemaFactory} for discussion.
*/
-class NoChangesReviewDbWrapper extends ReviewDbWrapper {
+class NoChangesReviewDb implements ReviewDb {
+ private static final String GONE = "ReviewDb is gone";
+
private static <T> ResultSet<T> empty() {
return new ListResultSet<>(ImmutableList.of());
}
@@ -48,13 +52,17 @@
private final PatchSetAccess patchSets;
private final PatchLineCommentAccess patchComments;
- NoChangesReviewDbWrapper(ReviewDb db) {
- super(db);
- changes = new Changes(this, delegate);
- patchSetApprovals = new PatchSetApprovals(this, delegate);
- changeMessages = new ChangeMessages(this, delegate);
- patchSets = new PatchSets(this, delegate);
- patchComments = new PatchLineComments(this, delegate);
+ NoChangesReviewDb() {
+ changes = new Changes(this);
+ patchSetApprovals = new PatchSetApprovals(this);
+ changeMessages = new ChangeMessages(this);
+ patchSets = new PatchSets(this);
+ patchComments = new PatchLineComments(this);
+ }
+
+ @Override
+ public SchemaVersionAccess schemaVersion() {
+ throw new UnsupportedOperationException(GONE);
}
@Override
@@ -82,10 +90,39 @@
return patchComments;
}
+ @Override
+ public int nextChangeId() {
+ throw new UnsupportedOperationException(GONE);
+ }
+
+ @Override
+ public void commit() {}
+
+ @Override
+ public void rollback() {}
+
+ @Override
+ public void updateSchema(StatementExecutor e) {
+ throw new UnsupportedOperationException(GONE);
+ }
+
+ @Override
+ public void pruneSchema(StatementExecutor e) {
+ throw new UnsupportedOperationException(GONE);
+ }
+
+ @Override
+ public Access<?, ?>[] allRelations() {
+ throw new UnsupportedOperationException(GONE);
+ }
+
+ @Override
+ public void close() {}
+
private static class Changes extends AbstractDisabledAccess<Change, Change.Id>
implements ChangeAccess {
- private Changes(NoChangesReviewDbWrapper wrapper, ReviewDb db) {
- super(wrapper, db.changes());
+ private Changes(NoChangesReviewDb wrapper) {
+ super(wrapper);
}
@Override
@@ -97,8 +134,8 @@
private static class ChangeMessages
extends AbstractDisabledAccess<ChangeMessage, ChangeMessage.Key>
implements ChangeMessageAccess {
- private ChangeMessages(NoChangesReviewDbWrapper wrapper, ReviewDb db) {
- super(wrapper, db.changeMessages());
+ private ChangeMessages(NoChangesReviewDb wrapper) {
+ super(wrapper);
}
@Override
@@ -119,8 +156,8 @@
private static class PatchSets extends AbstractDisabledAccess<PatchSet, PatchSet.Id>
implements PatchSetAccess {
- private PatchSets(NoChangesReviewDbWrapper wrapper, ReviewDb db) {
- super(wrapper, db.patchSets());
+ private PatchSets(NoChangesReviewDb wrapper) {
+ super(wrapper);
}
@Override
@@ -137,8 +174,8 @@
private static class PatchSetApprovals
extends AbstractDisabledAccess<PatchSetApproval, PatchSetApproval.Key>
implements PatchSetApprovalAccess {
- private PatchSetApprovals(NoChangesReviewDbWrapper wrapper, ReviewDb db) {
- super(wrapper, db.patchSetApprovals());
+ private PatchSetApprovals(NoChangesReviewDb wrapper) {
+ super(wrapper);
}
@Override
@@ -165,8 +202,8 @@
private static class PatchLineComments
extends AbstractDisabledAccess<PatchLineComment, PatchLineComment.Key>
implements PatchLineCommentAccess {
- private PatchLineComments(NoChangesReviewDbWrapper wrapper, ReviewDb db) {
- super(wrapper, db.patchComments());
+ private PatchLineComments(NoChangesReviewDb wrapper) {
+ super(wrapper);
}
@Override
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java b/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java
new file mode 100644
index 0000000..436b67f
--- /dev/null
+++ b/java/com/google/gerrit/server/schema/NoteDbSchemaUpdater.java
@@ -0,0 +1,187 @@
+// 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.server.schema;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
+import static com.google.gerrit.server.notedb.NotesMigration.DISABLE_REVIEW_DB;
+import static com.google.gerrit.server.notedb.NotesMigration.PRIMARY_STORAGE;
+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.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.Sequences;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
+import com.google.gerrit.server.notedb.NoteDbSchemaVersionManager;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import java.io.IOException;
+import java.util.stream.IntStream;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+
+public class NoteDbSchemaUpdater {
+ private final Config cfg;
+ private final AllUsersName allUsersName;
+ private final GitRepositoryManager repoManager;
+ private final NotesMigration notesMigration;
+ private final NoteDbSchemaVersionManager versionManager;
+ private final NoteDbSchemaVersion.Arguments args;
+ private final ImmutableSortedMap<Integer, Class<? extends NoteDbSchemaVersion>> schemaVersions;
+
+ @Inject
+ NoteDbSchemaUpdater(
+ @GerritServerConfig Config cfg,
+ NotesMigration notesMigration,
+ NoteDbSchemaVersionManager versionManager,
+ NoteDbSchemaVersion.Arguments args,
+ GitRepositoryManager repoManager,
+ AllUsersName allUsersName) {
+ this(
+ cfg,
+ allUsersName,
+ repoManager,
+ notesMigration,
+ versionManager,
+ args,
+ NoteDbSchemaVersions.ALL);
+ }
+
+ NoteDbSchemaUpdater(
+ Config cfg,
+ AllUsersName allUsersName,
+ GitRepositoryManager repoManager,
+ NotesMigration notesMigration,
+ NoteDbSchemaVersionManager versionManager,
+ NoteDbSchemaVersion.Arguments args,
+ ImmutableSortedMap<Integer, Class<? extends NoteDbSchemaVersion>> schemaVersions) {
+ this.cfg = cfg;
+ this.allUsersName = allUsersName;
+ this.repoManager = repoManager;
+ this.notesMigration = notesMigration;
+ this.versionManager = versionManager;
+ this.args = args;
+ this.schemaVersions = schemaVersions;
+ }
+
+ public void update(UpdateUI ui) throws OrmException {
+ if (!notesMigration.commitChangeWrites()) {
+ // TODO(dborowitz): Only necessary to make migration tests pass; remove when NoteDb is the
+ // only option.
+ return;
+ }
+ int currentVersion = versionManager.read();
+ if (currentVersion == 0) {
+ // The only valid case where there is no refs/meta/version is when running 3.x init for the
+ // first time on a site that previously ran init on 2.16. A freshly created 3.x site will have
+ // seeded refs/meta/version during AllProjectsCreator, so it won't hit this block.
+ checkNoteDbConfigFor216();
+ }
+
+ for (int nextVersion : requiredUpgrades(currentVersion, schemaVersions.keySet())) {
+ try {
+ ui.message(String.format("Migrating data to schema %d ...", nextVersion));
+ NoteDbSchemaVersions.get(schemaVersions, nextVersion, args).upgrade(ui);
+ versionManager.increment(nextVersion - 1);
+ } catch (Exception e) {
+ throw new OrmException(
+ String.format("Failed to upgrade to schema version %d", nextVersion), e);
+ }
+ }
+ }
+
+ private void checkNoteDbConfigFor216() throws OrmException {
+ // Check that the NoteDb migration config matches what we expect from a site that both:
+ // * Completed the change migration to NoteDB.
+ // * Ran schema upgrades from a 2.16 final release.
+
+ if (!cfg.getBoolean(SECTION_NOTE_DB, CHANGES.key(), WRITE, false)
+ || !cfg.getBoolean(SECTION_NOTE_DB, CHANGES.key(), READ, false)
+ || cfg.getEnum(SECTION_NOTE_DB, CHANGES.key(), PRIMARY_STORAGE, PrimaryStorage.REVIEW_DB)
+ != PrimaryStorage.NOTE_DB
+ || !cfg.getBoolean(SECTION_NOTE_DB, CHANGES.key(), DISABLE_REVIEW_DB, false)) {
+ throw new OrmException(
+ "You appear to be upgrading from a 2.x site, but the NoteDb change migration was"
+ + " not completed. See documentation:\n"
+ + "https://gerrit-review.googlesource.com/Documentation/note-db.html#migration");
+ }
+
+ // We don't have a direct way to check that 2.16 init was run; the most obvious side effect
+ // would be upgrading the *ReviewDb* schema to the latest 2.16 schema version. But in 3.x we can
+ // no longer access ReviewDb, so we can't check that directly.
+ //
+ // Instead, check for a NoteDb-specific side effect of the migration process: the presence of
+ // the NoteDb group sequence ref. This is created by the schema 163 migration, which was part of
+ // 2.16 and not 2.15.
+ //
+ // There are a few corner cases where we will proceed even if the schema is not fully up to
+ // date:
+ // * If a user happened to run init from master after schema 163 was added but before 2.16
+ // final. We assume that someone savvy enough to do that has followed the documented
+ // requirement of upgrading to 2.16 final before 3.0.
+ // * If a user ran init in 2.16.x and the upgrade to 163 succeeded but a later update failed.
+ // In this case the server literally will not start under 2.16. We assume the user will fix
+ // this and get 2.16 running rather than abandoning 2.16 and jumping to 3.0 at this point.
+ try (Repository allUsers = repoManager.openRepository(allUsersName)) {
+ if (allUsers.exactRef(RefNames.REFS_SEQUENCES + Sequences.NAME_GROUPS) == null) {
+ throw new OrmException(
+ "You appear to be upgrading to 3.x from a version prior to 2.16; you must upgrade to"
+ + " 2.16.x first");
+ }
+ } catch (IOException e) {
+ throw new OrmException("Failed to check NoteDb migration state", e);
+ }
+ }
+
+ @VisibleForTesting
+ static ImmutableList<Integer> requiredUpgrades(
+ int currentVersion, ImmutableSortedSet<Integer> allVersions) throws OrmException {
+ int firstVersion = allVersions.first();
+ int latestVersion = allVersions.last();
+ if (currentVersion == latestVersion) {
+ return ImmutableList.of();
+ } else if (currentVersion > latestVersion) {
+ throw new OrmException(
+ String.format(
+ "Cannot downgrade NoteDb schema from version %d to %d",
+ currentVersion, latestVersion));
+ }
+
+ int firstUpgradeVersion;
+ if (currentVersion == 0) {
+ // Bootstrap NoteDb version to minimum supported schema number.
+ firstUpgradeVersion = firstVersion;
+ } else {
+ if (currentVersion < firstVersion - 1) {
+ throw new OrmException(
+ String.format(
+ "Cannot skip NoteDb schema from version %d to %d", currentVersion, firstVersion));
+ }
+ firstUpgradeVersion = currentVersion + 1;
+ }
+ return IntStream.rangeClosed(firstUpgradeVersion, latestVersion)
+ .boxed()
+ .collect(toImmutableList());
+ }
+}
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaVersion.java b/java/com/google/gerrit/server/schema/NoteDbSchemaVersion.java
new file mode 100644
index 0000000..e90b2b8
--- /dev/null
+++ b/java/com/google/gerrit/server/schema/NoteDbSchemaVersion.java
@@ -0,0 +1,43 @@
+// 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.server.schema;
+
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * Schema upgrade implementation.
+ *
+ * <p>Implementations must define a single public constructor that takes an {@link Arguments}. The
+ * recommended idiom is to pull out whichever individual fields from the {@code Arguments} are
+ * required by this implementation.
+ */
+interface NoteDbSchemaVersion {
+ @Singleton
+ class Arguments {
+ final GitRepositoryManager repoManager;
+ final AllProjectsName allProjects;
+
+ @Inject
+ Arguments(GitRepositoryManager repoManager, AllProjectsName allProjects) {
+ this.repoManager = repoManager;
+ this.allProjects = allProjects;
+ }
+ }
+
+ void upgrade(UpdateUI ui) throws Exception;
+}
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaVersionCheck.java b/java/com/google/gerrit/server/schema/NoteDbSchemaVersionCheck.java
new file mode 100644
index 0000000..9e26ee9
--- /dev/null
+++ b/java/com/google/gerrit/server/schema/NoteDbSchemaVersionCheck.java
@@ -0,0 +1,89 @@
+// 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.server.schema;
+
+import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.notedb.NoteDbSchemaVersionManager;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.ProvisionException;
+
+public class NoteDbSchemaVersionCheck implements LifecycleListener {
+ public static Module module() {
+ return new LifecycleModule() {
+ @Override
+ protected void configure() {
+ listener().to(NoteDbSchemaVersionCheck.class);
+ }
+ };
+ }
+
+ private final NotesMigration notesMigration;
+ private final NoteDbSchemaVersionManager versionManager;
+ private final SitePaths sitePaths;
+
+ @Inject
+ NoteDbSchemaVersionCheck(
+ NotesMigration notesMigration,
+ NoteDbSchemaVersionManager versionManager,
+ SitePaths sitePaths) {
+ this.notesMigration = notesMigration;
+ this.versionManager = versionManager;
+ this.sitePaths = sitePaths;
+ }
+
+ @Override
+ public void start() {
+ if (!notesMigration.commitChangeWrites()) {
+ // TODO(dborowitz): Only necessary to make migration tests pass; remove when NoteDb is the
+ // only option.
+ return;
+ }
+ try {
+ int current = versionManager.read();
+ if (current == 0) {
+ throw new ProvisionException(
+ String.format(
+ "Schema not yet initialized. Run init to initialize the schema:\n"
+ + "$ java -jar gerrit.war init -d %s",
+ sitePaths.site_path.toAbsolutePath()));
+ }
+ int expected = NoteDbSchemaVersions.LATEST;
+ if (current != expected) {
+ String advice =
+ current > expected
+ ? "Downgrade is not supported"
+ : String.format(
+ "Run init to upgrade:\n$ java -jar %s init -d %s",
+ sitePaths.gerrit_war.toAbsolutePath(), sitePaths.site_path.toAbsolutePath());
+ throw new ProvisionException(
+ String.format(
+ "Unsupported schema version %d; expected schema version %d. %s",
+ current, expected, advice));
+ }
+ } catch (OrmException e) {
+ throw new ProvisionException("Failed to read NoteDb schema version", e);
+ }
+ }
+
+ @Override
+ public void stop() {
+ // Do nothing.
+ }
+}
diff --git a/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java b/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java
new file mode 100644
index 0000000..ffd4760
--- /dev/null
+++ b/java/com/google/gerrit/server/schema/NoteDbSchemaVersions.java
@@ -0,0 +1,62 @@
+// 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.server.schema;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap;
+import static java.util.Comparator.naturalOrder;
+
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.primitives.Ints;
+import com.google.gerrit.server.UsedAt;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public class NoteDbSchemaVersions {
+ static final ImmutableSortedMap<Integer, Class<? extends NoteDbSchemaVersion>> ALL =
+ // List all supported NoteDb schema versions here.
+ Stream.of(Schema_180.class)
+ .collect(toImmutableSortedMap(naturalOrder(), v -> guessVersion(v).get(), v -> v));
+
+ public static final int FIRST = ALL.firstKey();
+ public static final int LATEST = ALL.lastKey();
+
+ // TODO(dborowitz): Migrate delete-project plugin to use this implementation.
+ @UsedAt(UsedAt.Project.PLUGIN_DELETE_PROJECT)
+ public static Optional<Integer> guessVersion(Class<?> c) {
+ String prefix = "Schema_";
+ if (!c.getSimpleName().startsWith(prefix)) {
+ return Optional.empty();
+ }
+ return Optional.ofNullable(Ints.tryParse(c.getSimpleName().substring(prefix.length())));
+ }
+
+ public static NoteDbSchemaVersion get(
+ ImmutableSortedMap<Integer, Class<? extends NoteDbSchemaVersion>> schemaVersions,
+ int i,
+ NoteDbSchemaVersion.Arguments args) {
+ Class<? extends NoteDbSchemaVersion> clazz = schemaVersions.get(i);
+ checkArgument(clazz != null, "Schema version not found: %s", i);
+ try {
+ return clazz.getDeclaredConstructor(NoteDbSchemaVersion.Arguments.class).newInstance(args);
+ } catch (InstantiationException
+ | IllegalAccessException
+ | NoSuchMethodException
+ | InvocationTargetException e) {
+ throw new IllegalStateException("failed to invoke constructor on " + clazz.getName(), e);
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/schema/NotesMigrationSchemaFactory.java b/java/com/google/gerrit/server/schema/NotesMigrationSchemaFactory.java
index 0d95610..c533619 100644
--- a/java/com/google/gerrit/server/schema/NotesMigrationSchemaFactory.java
+++ b/java/com/google/gerrit/server/schema/NotesMigrationSchemaFactory.java
@@ -14,7 +14,9 @@
package com.google.gerrit.server.schema;
-import com.google.gerrit.reviewdb.server.DisallowReadFromChangesReviewDbWrapper;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.gerrit.reviewdb.server.DisallowedReviewDb;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gwtorm.server.OrmException;
@@ -24,13 +26,10 @@
@Singleton
public class NotesMigrationSchemaFactory implements SchemaFactory<ReviewDb> {
- private final SchemaFactory<ReviewDb> delegate;
private final NotesMigration migration;
@Inject
- NotesMigrationSchemaFactory(
- @ReviewDbFactory SchemaFactory<ReviewDb> delegate, NotesMigration migration) {
- this.delegate = delegate;
+ NotesMigrationSchemaFactory(NotesMigration migration) {
this.migration = migration;
}
@@ -43,7 +42,7 @@
// in ReviewDb, it is generally programmer error to read changes from ReviewDb. However,
// since ReviewDb is still the primary storage for most or all changes, we still need to
// support writing to ReviewDb. This behavior is accomplished by wrapping in a
- // DisallowReadFromChangesReviewDbWrapper.
+ // DisallowedReviewDb.
//
// Some codepaths might need to be able to read from ReviewDb if they really need to,
// because they need to operate on the underlying source of truth, for example when reading
@@ -56,22 +55,22 @@
// continue to function.
//
// This is accomplished by setting the delegate ReviewDb *underneath*
- // DisallowReadFromChanges to be a complete no-op, with NoChangesReviewDbWrapper. With this
- // wrapper, all read operations return no results, and write operations silently do nothing.
- // This wrapper is not a public class and nobody should ever attempt to unwrap it.
+ // DisallowReadFromChanges to be a complete no-op, with NoChangesReviewDb. With this
+ // stub implementation, all read operations return no results, and write operations silently
+ // do nothing. This implementation is not a public class and callers couldn't do anything
+ // useful with it even if it were.
- // First create the wrappers which can not be removed by ReviewDbUtil#unwrapDb(ReviewDb).
- ReviewDb db = delegate.open();
- if (migration.readChanges() && migration.disableChangeReviewDb()) {
- // Disable writes to change tables in ReviewDb (ReviewDb access for changes are No-Ops).
- db = new NoChangesReviewDbWrapper(db);
- }
+ // First create the underlying stub.
+ checkState(migration.readChanges() && migration.disableChangeReviewDb());
+ // Disable writes to change tables in ReviewDb (ReviewDb access for changes are No-Ops); all
+ // other table accesses throw runtime exceptions.
+ ReviewDb db = new NoChangesReviewDb();
// Second create the wrappers which can be removed by ReviewDbUtil#unwrapDb(ReviewDb).
if (migration.readChanges()) {
// If reading changes from NoteDb is configured, changes should not be read from ReviewDb.
// Make sure that any attempt to read a change from ReviewDb anyway fails with an exception.
- db = new DisallowReadFromChangesReviewDbWrapper(db);
+ db = new DisallowedReviewDb(db);
}
return db;
}
diff --git a/java/com/google/gerrit/server/schema/ReviewDbSchemaCreator.java b/java/com/google/gerrit/server/schema/ReviewDbSchemaCreator.java
index 3a42f07..2113a36 100644
--- a/java/com/google/gerrit/server/schema/ReviewDbSchemaCreator.java
+++ b/java/com/google/gerrit/server/schema/ReviewDbSchemaCreator.java
@@ -42,7 +42,6 @@
import com.google.gerrit.server.group.db.InternalGroupUpdate;
import com.google.gerrit.server.index.group.GroupIndex;
import com.google.gerrit.server.index.group.GroupIndexCollection;
-import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gwtorm.jdbc.JdbcExecutor;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmDuplicateKeyException;
@@ -72,7 +71,6 @@
private final Config config;
private final MetricMaker metricMaker;
- private final NotesMigration migration;
private final AllProjectsName allProjectsName;
@Inject
@@ -88,7 +86,6 @@
@GerritServerId String serverId,
@GerritServerConfig Config config,
MetricMaker metricMaker,
- NotesMigration migration,
AllProjectsName apName) {
this(
site.site_path,
@@ -102,7 +99,6 @@
serverId,
config,
metricMaker,
- migration,
apName);
}
@@ -118,7 +114,6 @@
String serverId,
Config config,
MetricMaker metricMaker,
- NotesMigration migration,
AllProjectsName apName) {
site_path = site;
this.repoManager = repoManager;
@@ -132,7 +127,6 @@
this.config = config;
this.allProjectsName = apName;
- this.migration = migration;
this.metricMaker = metricMaker;
}
@@ -157,8 +151,6 @@
Sequences seqs =
new Sequences(
config,
- () -> db,
- migration,
repoManager,
GitReferenceUpdated.DISABLED,
allProjectsName,
diff --git a/java/com/google/gerrit/server/schema/ReviewDbSchemaVersion.java b/java/com/google/gerrit/server/schema/ReviewDbSchemaVersion.java
index 10a2d39..f911a8f 100644
--- a/java/com/google/gerrit/server/schema/ReviewDbSchemaVersion.java
+++ b/java/com/google/gerrit/server/schema/ReviewDbSchemaVersion.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.schema;
+import static com.google.common.base.Preconditions.checkState;
+
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
@@ -36,8 +38,17 @@
/** A version of the database schema. */
public abstract class ReviewDbSchemaVersion {
/** The current schema version. */
+ // DO NOT upgrade this version in the master branch. Future versions must all be implemented as
+ // NoteDbSchemaVersions. It may be upgraded on the stable-2.16 branch, in which case this will
+ // need to be updated upon merging. In any case, this number must not exceed the first NoteDb
+ // schema version (180).
public static final Class<Schema_170> C = Schema_170.class;
+ static {
+ checkState(C.equals(Schema_170.class));
+ checkState(guessVersion(C) < 180);
+ }
+
public static int getBinaryVersion() {
return guessVersion(C);
}
diff --git a/java/com/google/gerrit/server/schema/ReviewDbSchemaVersionCheck.java b/java/com/google/gerrit/server/schema/ReviewDbSchemaVersionCheck.java
deleted file mode 100644
index 12abc7e..0000000
--- a/java/com/google/gerrit/server/schema/ReviewDbSchemaVersionCheck.java
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (C) 2009 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.schema;
-
-import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.lifecycle.LifecycleModule;
-import com.google.gerrit.reviewdb.client.CurrentSchemaVersion;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.SchemaFactory;
-import com.google.inject.Inject;
-import com.google.inject.Module;
-import com.google.inject.ProvisionException;
-
-/** Validates the current schema version. */
-public class ReviewDbSchemaVersionCheck implements LifecycleListener {
- public static Module module() {
- return new LifecycleModule() {
- @Override
- protected void configure() {
- listener().to(ReviewDbSchemaVersionCheck.class);
- }
- };
- }
-
- private final SchemaFactory<ReviewDb> schema;
- private final SitePaths site;
-
- @Inject
- public ReviewDbSchemaVersionCheck(SchemaFactory<ReviewDb> schemaFactory, SitePaths site) {
- this.schema = schemaFactory;
- this.site = site;
- }
-
- @Override
- public void start() {
- try (ReviewDb db = schema.open()) {
- final CurrentSchemaVersion currentVer = getSchemaVersion(db);
- final int expectedVer = ReviewDbSchemaVersion.getBinaryVersion();
-
- if (currentVer == null) {
- throw new ProvisionException(
- "Schema not yet initialized."
- + " Run init to initialize the schema:\n"
- + "$ java -jar gerrit.war init -d "
- + site.site_path.toAbsolutePath());
- }
- if (currentVer.versionNbr < expectedVer) {
- throw new ProvisionException(
- "Unsupported schema version "
- + currentVer.versionNbr
- + "; expected schema version "
- + expectedVer
- + ". Run init to upgrade:\n"
- + "$ java -jar "
- + site.gerrit_war.toAbsolutePath()
- + " init -d "
- + site.site_path.toAbsolutePath());
- } else if (currentVer.versionNbr > expectedVer) {
- throw new ProvisionException(
- "Unsupported schema version "
- + currentVer.versionNbr
- + "; expected schema version "
- + expectedVer
- + ". Downgrade is not supported.");
- }
- } catch (OrmException e) {
- throw new ProvisionException("Cannot read schema_version", e);
- }
- }
-
- @Override
- public void stop() {}
-
- private CurrentSchemaVersion getSchemaVersion(ReviewDb db) {
- try {
- return db.schemaVersion().get(new CurrentSchemaVersion.Key());
- } catch (OrmException e) {
- return null;
- }
- }
-}
diff --git a/java/com/google/gerrit/server/schema/Schema_180.java b/java/com/google/gerrit/server/schema/Schema_180.java
new file mode 100644
index 0000000..4d16022
--- /dev/null
+++ b/java/com/google/gerrit/server/schema/Schema_180.java
@@ -0,0 +1,27 @@
+// 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.server.schema;
+
+public class Schema_180 implements NoteDbSchemaVersion {
+ @SuppressWarnings("unused")
+ Schema_180(Arguments args) {
+ // Do nothing.
+ }
+
+ @Override
+ public void upgrade(UpdateUI ui) {
+ // Do nothing; only used to populate the version ref, which is done by the caller.
+ }
+}
diff --git a/javatests/com/google/gerrit/server/notedb/NoteDbSchemaVersionManagerTest.java b/javatests/com/google/gerrit/server/notedb/NoteDbSchemaVersionManagerTest.java
new file mode 100644
index 0000000..c8900309
--- /dev/null
+++ b/javatests/com/google/gerrit/server/notedb/NoteDbSchemaVersionManagerTest.java
@@ -0,0 +1,79 @@
+package com.google.gerrit.server.notedb;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_VERSION;
+
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
+import com.google.gwtorm.server.OrmException;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Before;
+import org.junit.Test;
+
+public class NoteDbSchemaVersionManagerTest extends GerritBaseTests {
+ private NoteDbSchemaVersionManager manager;
+ private TestRepository<?> tr;
+
+ @Before
+ public void setUp() throws Exception {
+ AllProjectsName allProjectsName = new AllProjectsName("The-Projects");
+ GitRepositoryManager repoManager = new InMemoryRepositoryManager();
+ tr = new TestRepository<>(repoManager.createRepository(allProjectsName));
+ manager = new NoteDbSchemaVersionManager(allProjectsName, repoManager);
+ }
+
+ @Test
+ public void readMissing() throws Exception {
+ assertThat(manager.read()).isEqualTo(0);
+ }
+
+ @Test
+ public void read() throws Exception {
+ tr.update(REFS_VERSION, tr.blob("123"));
+ assertThat(manager.read()).isEqualTo(123);
+ }
+
+ @Test
+ public void readInvalid() throws Exception {
+ ObjectId blobId = tr.blob(" 1 2 3 ");
+ tr.update(REFS_VERSION, blobId);
+ try {
+ manager.read();
+ assert_().fail("expected OrmException");
+ } catch (OrmException e) {
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo("invalid value in refs/meta/version blob at " + blobId.name());
+ }
+ }
+
+ @Test
+ public void incrementFromMissing() throws Exception {
+ manager.increment(123);
+ assertThat(manager.read()).isEqualTo(124);
+ }
+
+ @Test
+ public void increment() throws Exception {
+ tr.update(REFS_VERSION, tr.blob("123"));
+ manager.increment(123);
+ assertThat(manager.read()).isEqualTo(124);
+ }
+
+ @Test
+ public void incrementWrongOldVersion() throws Exception {
+ tr.update(REFS_VERSION, tr.blob("123"));
+ try {
+ manager.increment(456);
+ assert_().fail("expected OrmException");
+ } catch (OrmException e) {
+ assertThat(e)
+ .hasMessageThat()
+ .isEqualTo("Expected old version 456 for refs/meta/version, found 123");
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/server/schema/NoteDbSchemaUpdaterTest.java b/javatests/com/google/gerrit/server/schema/NoteDbSchemaUpdaterTest.java
new file mode 100644
index 0000000..9af7b1b
--- /dev/null
+++ b/javatests/com/google/gerrit/server/schema/NoteDbSchemaUpdaterTest.java
@@ -0,0 +1,287 @@
+// 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.server.schema;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.server.schema.NoteDbSchemaUpdater.requiredUpgrades;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.Sequences;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.IntBlob;
+import com.google.gerrit.server.notedb.MutableNotesMigration;
+import com.google.gerrit.server.notedb.NoteDbSchemaVersionManager;
+import com.google.gerrit.server.notedb.NotesMigrationState;
+import com.google.gerrit.server.notedb.RepoSequence;
+import com.google.gerrit.testing.GerritBaseTests;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
+import com.google.gerrit.testing.TestUpdateUI;
+import com.google.gwtorm.server.OrmException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.Test;
+
+public class NoteDbSchemaUpdaterTest extends GerritBaseTests {
+ @Test
+ public void requiredUpgradesFromNoVersion() throws Exception {
+ assertThat(requiredUpgrades(0, versions(10))).containsExactly(10).inOrder();
+ assertThat(requiredUpgrades(0, versions(10, 11, 12))).containsExactly(10, 11, 12).inOrder();
+ }
+
+ @Test
+ public void requiredUpgradesFromExistingVersion() throws Exception {
+ ImmutableSortedSet<Integer> versions = versions(10, 11, 12, 13);
+ assertThat(requiredUpgrades(10, versions)).containsExactly(11, 12, 13).inOrder();
+ assertThat(requiredUpgrades(11, versions)).containsExactly(12, 13).inOrder();
+ assertThat(requiredUpgrades(12, versions)).containsExactly(13).inOrder();
+ assertThat(requiredUpgrades(13, versions)).isEmpty();
+ }
+
+ @Test
+ public void downgradeNotSupported() throws Exception {
+ try {
+ requiredUpgrades(14, versions(10, 11, 12, 13));
+ assert_().fail("expected OrmException");
+ } catch (OrmException e) {
+ assertThat(e)
+ .hasMessageThat()
+ .contains("Cannot downgrade NoteDb schema from version 14 to 13");
+ }
+ }
+
+ @Test
+ public void skipToFirstVersionNotSupported() throws Exception {
+ ImmutableSortedSet<Integer> versions = versions(10, 11, 12);
+ assertThat(requiredUpgrades(9, versions)).containsExactly(10, 11, 12).inOrder();
+ try {
+ requiredUpgrades(8, versions);
+ assert_().fail("expected OrmException");
+ } catch (OrmException e) {
+ assertThat(e).hasMessageThat().contains("Cannot skip NoteDb schema from version 8 to 10");
+ }
+ }
+
+ private static class TestUpdate {
+ protected final Config cfg;
+ protected final AllProjectsName allProjectsName;
+ protected final AllUsersName allUsersName;
+ protected final NoteDbSchemaUpdater updater;
+ protected final GitRepositoryManager repoManager;
+ protected final NoteDbSchemaVersion.Arguments args;
+ private final List<String> messages;
+
+ TestUpdate(Optional<Integer> initialVersion) throws Exception {
+ cfg = new Config();
+ allProjectsName = new AllProjectsName("The-Projects");
+ allUsersName = new AllUsersName("The-Users");
+ repoManager = new InMemoryRepositoryManager();
+ try (Repository repo = repoManager.createRepository(allProjectsName)) {
+ if (initialVersion.isPresent()) {
+ TestRepository<?> tr = new TestRepository<>(repo);
+ tr.update(RefNames.REFS_VERSION, tr.blob(initialVersion.get().toString()));
+ }
+ }
+ repoManager.createRepository(allUsersName).close();
+
+ setUp();
+
+ args = new NoteDbSchemaVersion.Arguments(repoManager, allProjectsName);
+ NoteDbSchemaVersionManager versionManager =
+ new NoteDbSchemaVersionManager(allProjectsName, repoManager);
+ MutableNotesMigration notesMigration = MutableNotesMigration.newDisabled();
+ notesMigration.setFrom(NotesMigrationState.NOTE_DB);
+ updater =
+ new NoteDbSchemaUpdater(
+ cfg,
+ allUsersName,
+ repoManager,
+ notesMigration,
+ versionManager,
+ args,
+ ImmutableSortedMap.of(10, TestSchema_10.class, 11, TestSchema_11.class));
+ messages = new ArrayList<>();
+ }
+
+ protected void setNotesMigrationConfig() {
+ cfg.setString("noteDb", "changes", "write", "true");
+ cfg.setString("noteDb", "changes", "read", "true");
+ cfg.setString("noteDb", "changes", "primaryStorage", "NOTE_DB");
+ cfg.setString("noteDb", "changes", "disableReviewDb", "true");
+ }
+
+ protected void seedGroupSequenceRef() throws Exception {
+ new RepoSequence(
+ repoManager,
+ GitReferenceUpdated.DISABLED,
+ allUsersName,
+ Sequences.NAME_GROUPS,
+ () -> 1,
+ 1)
+ .next();
+ }
+
+ protected void setUp() throws Exception {}
+
+ ImmutableList<String> update() throws Exception {
+ updater.update(
+ new TestUpdateUI() {
+ @Override
+ public void message(String m) {
+ messages.add(m);
+ }
+ });
+ return getMessages();
+ }
+
+ ImmutableList<String> getMessages() {
+ return ImmutableList.copyOf(messages);
+ }
+
+ Optional<Integer> readVersion() throws Exception {
+ try (Repository repo = repoManager.openRepository(allProjectsName)) {
+ return IntBlob.parse(repo, RefNames.REFS_VERSION).map(IntBlob::value);
+ }
+ }
+
+ private static class TestSchema_10 implements NoteDbSchemaVersion {
+ @SuppressWarnings("unused")
+ TestSchema_10(Arguments args) {
+ // Do nothing.
+ }
+
+ @Override
+ public void upgrade(UpdateUI ui) {
+ ui.message("body of 10");
+ }
+ }
+
+ private static class TestSchema_11 implements NoteDbSchemaVersion {
+ @SuppressWarnings("unused")
+ TestSchema_11(Arguments args) {
+ // Do nothing.
+ }
+
+ @Override
+ public void upgrade(UpdateUI ui) {
+ ui.message("BODY OF 11");
+ }
+ }
+ }
+
+ @Test
+ public void bootstrapUpdateWith216Prerequisites() throws Exception {
+ TestUpdate u =
+ new TestUpdate(Optional.empty()) {
+ @Override
+ public void setUp() throws Exception {
+ setNotesMigrationConfig();
+ seedGroupSequenceRef();
+ }
+ };
+ assertThat(u.update())
+ .containsExactly(
+ "Migrating data to schema 10 ...",
+ "body of 10",
+ "Migrating data to schema 11 ...",
+ "BODY OF 11")
+ .inOrder();
+ assertThat(u.readVersion()).hasValue(11);
+ }
+
+ @Test
+ public void bootstrapUpdateFailsWithoutNotesMigrationConfig() throws Exception {
+ TestUpdate u =
+ new TestUpdate(Optional.empty()) {
+ @Override
+ public void setUp() throws Exception {
+ seedGroupSequenceRef();
+ }
+ };
+ try {
+ u.update();
+ assert_().fail("expected OrmException");
+ } catch (OrmException e) {
+ assertThat(e).hasMessageThat().contains("NoteDb change migration was not completed");
+ }
+ assertThat(u.getMessages()).isEmpty();
+ assertThat(u.readVersion()).isEmpty();
+ }
+
+ @Test
+ public void bootstrapUpdateFailsWithoutGroupSequenceRef() throws Exception {
+ TestUpdate u =
+ new TestUpdate(Optional.empty()) {
+ @Override
+ public void setUp() throws Exception {
+ setNotesMigrationConfig();
+ }
+ };
+ try {
+ u.update();
+ assert_().fail("expected OrmException");
+ } catch (OrmException e) {
+ assertThat(e).hasMessageThat().contains("upgrade to 2.16.x first");
+ }
+ assertThat(u.getMessages()).isEmpty();
+ assertThat(u.readVersion()).isEmpty();
+ }
+
+ @Test
+ public void updateTwoVersions() throws Exception {
+ TestUpdate u = new TestUpdate(Optional.of(9));
+ assertThat(u.update())
+ .containsExactly(
+ "Migrating data to schema 10 ...",
+ "body of 10",
+ "Migrating data to schema 11 ...",
+ "BODY OF 11")
+ .inOrder();
+ assertThat(u.readVersion()).hasValue(11);
+ }
+
+ @Test
+ public void updateOneVersion() throws Exception {
+ TestUpdate u = new TestUpdate(Optional.of(10));
+ assertThat(u.update())
+ .containsExactly("Migrating data to schema 11 ...", "BODY OF 11")
+ .inOrder();
+ assertThat(u.readVersion()).hasValue(11);
+ }
+
+ @Test
+ public void updateNoOp() throws Exception {
+ // This test covers the state when running the updater after initializing a new 3.x site, which
+ // seeds the schema version ref with the latest version.
+ TestUpdate u = new TestUpdate(Optional.of(11));
+ assertThat(u.update()).isEmpty();
+ assertThat(u.readVersion()).hasValue(11);
+ }
+
+ private static ImmutableSortedSet<Integer> versions(Integer... versions) {
+ return ImmutableSortedSet.copyOf(versions);
+ }
+}
diff --git a/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionsTest.java b/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionsTest.java
new file mode 100644
index 0000000..02388ba
--- /dev/null
+++ b/javatests/com/google/gerrit/server/schema/NoteDbSchemaVersionsTest.java
@@ -0,0 +1,78 @@
+// 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.server.schema;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+import static com.google.gerrit.server.schema.NoteDbSchemaVersions.guessVersion;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Streams;
+import com.google.common.reflect.ClassPath;
+import com.google.common.reflect.ClassPath.ClassInfo;
+import com.google.gerrit.testing.GerritBaseTests;
+import java.util.stream.IntStream;
+import org.junit.Test;
+
+public class NoteDbSchemaVersionsTest extends GerritBaseTests {
+ @Test
+ public void testGuessVersion() {
+ assertThat(guessVersion(getClass())).isEmpty();
+ assertThat(guessVersion(Schema_180.class)).hasValue(180);
+ }
+
+ @Test
+ public void contiguousVersions() {
+ ImmutableSortedSet<Integer> keys = NoteDbSchemaVersions.ALL.keySet();
+ ImmutableList<Integer> expected =
+ IntStream.rangeClosed(keys.first(), keys.last()).boxed().collect(toImmutableList());
+ assertThat(keys).containsExactlyElementsIn(expected).inOrder();
+ }
+
+ @Test
+ public void exceedsReviewDbVersion() {
+ assertThat(NoteDbSchemaVersions.ALL.firstKey())
+ // TODO(dborowitz): Replace with hard-coded max number once ReviewDb code is deleted.
+ .isGreaterThan(ReviewDbSchemaVersion.guessVersion(ReviewDbSchemaVersion.C));
+ }
+
+ @Test
+ public void containsAllNoteDbSchemas() throws Exception {
+ int minNoteDbVersion = 180;
+ ImmutableList<Integer> allSchemaVersions =
+ ClassPath.from(getClass().getClassLoader())
+ .getTopLevelClasses(getClass().getPackage().getName())
+ .stream()
+ .map(ClassInfo::load)
+ .map(NoteDbSchemaVersions::guessVersion)
+ .flatMap(Streams::stream)
+ .filter(v -> v >= minNoteDbVersion)
+ .sorted()
+ .collect(toImmutableList());
+ assertThat(NoteDbSchemaVersions.ALL.keySet())
+ .containsExactlyElementsIn(allSchemaVersions)
+ .inOrder();
+ }
+
+ @Test
+ public void schemaConstructors() throws Exception {
+ NoteDbSchemaVersion.Arguments args = new NoteDbSchemaVersion.Arguments(null, null);
+ for (int version : NoteDbSchemaVersions.ALL.keySet()) {
+ NoteDbSchemaVersions.get(NoteDbSchemaVersions.ALL, version, args);
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInNoteDbTest.java b/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInNoteDbTest.java
deleted file mode 100644
index f17550e..0000000
--- a/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInNoteDbTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-// 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.server.schema;
-
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.truth.Truth.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.SECTION_NOTE_DB;
-
-import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
-import com.google.gerrit.server.Sequences;
-import com.google.gerrit.server.ServerInitiated;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.GerritServerIdProvider;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.group.InternalGroup;
-import com.google.gerrit.server.group.db.GroupNameNotes;
-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.gerrit.testing.TestUpdateUI;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.inject.Inject;
-import java.io.IOException;
-import java.sql.PreparedStatement;
-import java.sql.Statement;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-public class Schema_166_to_167_WithGroupsInNoteDbTest {
- private static Config createConfig() {
- Config config = new Config();
- config.setString(GerritServerIdProvider.SECTION, null, GerritServerIdProvider.KEY, "1234567");
-
- // Disable groups in ReviewDb. This means the primary storage for groups is NoteDb.
- config.setBoolean(SECTION_NOTE_DB, GROUPS.key(), DISABLE_REVIEW_DB, true);
-
- return config;
- }
-
- @Rule
- public InMemoryTestEnvironment testEnv =
- new InMemoryTestEnvironment(Schema_166_to_167_WithGroupsInNoteDbTest::createConfig);
-
- @Inject private Schema_167 schema167;
- @Inject private ReviewDb db;
- @Inject private GitRepositoryManager gitRepoManager;
- @Inject private AllUsersName allUsersName;
- @Inject private @ServerInitiated GroupsUpdate groupsUpdate;
- @Inject private Sequences seq;
-
- private JdbcSchema jdbcSchema;
-
- @Before
- public void initDb() throws Exception {
- jdbcSchema = ReviewDbWrapper.unwrapJbdcSchema(db);
-
- try (Statement stmt = jdbcSchema.getConnection().createStatement()) {
- stmt.execute(
- "CREATE TABLE account_groups ("
- + " group_uuid varchar(255) DEFAULT '' NOT NULL,"
- + " group_id INTEGER DEFAULT 0 NOT NULL,"
- + " name varchar(255) DEFAULT '' NOT NULL,"
- + " created_on TIMESTAMP,"
- + " description CLOB,"
- + " owner_group_uuid varchar(255) DEFAULT '' NOT NULL,"
- + " visible_to_all CHAR(1) DEFAULT 'N' NOT NULL"
- + ")");
- }
- }
-
- @Test
- public void migrationIsSkipped() throws Exception {
- // Create a group in NoteDb (doesn't create the group in ReviewDb since
- // disableReviewDb == true)
- InternalGroup internalGroup =
- groupsUpdate.createGroup(
- InternalGroupCreation.builder()
- .setNameKey(new AccountGroup.NameKey("users"))
- .setGroupUUID(new AccountGroup.UUID("users"))
- .setId(new AccountGroup.Id(seq.nextGroupId()))
- .build(),
- InternalGroupUpdate.builder().setDescription("description").build());
-
- // Insert the group into ReviewDb
- AccountGroup group1 =
- newGroup()
- .setName(internalGroup.getName())
- .setGroupUuid(internalGroup.getGroupUUID())
- .setId(internalGroup.getId())
- .setCreatedOn(internalGroup.getCreatedOn())
- .setDescription(internalGroup.getDescription())
- .setGroupUuid(internalGroup.getGroupUUID())
- .setVisibleToAll(internalGroup.isVisibleToAll())
- .build();
- storeInReviewDb(group1);
-
- // Update the group description in ReviewDb so that the group state differs between ReviewDb and
- // NoteDb
- group1.setDescription("outdated");
- updateInReviewDb(group1);
-
- // Create a group that only exists in ReviewDb
- AccountGroup group2 = newGroup().setName("reviewDbOnlyGroup").build();
- storeInReviewDb(group2);
-
- // Remember the SHA1 of the group ref in NoteDb
- ObjectId groupSha1 = getGroupSha1(group1.getGroupUUID());
-
- executeSchemaMigration(schema167);
-
- // Verify the groups in NoteDb: "users" should still exist, "reviewDbOnlyGroup" should not have
- // been created
- ImmutableList<GroupReference> groupReferences = getAllGroupsFromNoteDb();
- ImmutableList<String> groupNames =
- groupReferences.stream().map(GroupReference::getName).collect(toImmutableList());
- assertThat(groupNames).contains("users");
- assertThat(groupNames).doesNotContain("reviewDbOnlyGroup");
-
- // Verify that the group refs in NoteDb were not touched.
- assertThat(getGroupSha1(group1.getGroupUUID())).isEqualTo(groupSha1);
- assertThat(getGroupSha1(group2.getGroupUUID())).isNull();
- }
-
- private static TestGroup.Builder newGroup() {
- return TestGroup.builder();
- }
-
- private void storeInReviewDb(AccountGroup... groups) throws Exception {
- try (PreparedStatement stmt =
- jdbcSchema
- .getConnection()
- .prepareStatement(
- "INSERT INTO account_groups"
- + " (group_uuid,"
- + " group_id,"
- + " name,"
- + " description,"
- + " created_on,"
- + " owner_group_uuid,"
- + " visible_to_all) VALUES (?, ?, ?, ?, ?, ?, ?)")) {
- for (AccountGroup group : groups) {
- stmt.setString(1, group.getGroupUUID().get());
- stmt.setInt(2, group.getId().get());
- stmt.setString(3, group.getName());
- stmt.setString(4, group.getDescription());
- stmt.setTimestamp(5, group.getCreatedOn());
- stmt.setString(6, group.getOwnerGroupUUID().get());
- stmt.setString(7, group.isVisibleToAll() ? "Y" : "N");
- stmt.addBatch();
- }
- stmt.executeBatch();
- }
- }
-
- private void updateInReviewDb(AccountGroup... groups) throws Exception {
- try (PreparedStatement stmt =
- jdbcSchema
- .getConnection()
- .prepareStatement(
- "UPDATE account_groups SET"
- + " group_uuid = ?,"
- + " name = ?,"
- + " description = ?,"
- + " created_on = ?,"
- + " owner_group_uuid = ?,"
- + " visible_to_all = ?"
- + " WHERE group_id = ?")) {
- for (AccountGroup group : groups) {
- stmt.setString(1, group.getGroupUUID().get());
- stmt.setString(2, group.getName());
- stmt.setString(3, group.getDescription());
- stmt.setTimestamp(4, group.getCreatedOn());
- stmt.setString(5, group.getOwnerGroupUUID().get());
- stmt.setString(6, group.isVisibleToAll() ? "Y" : "N");
- stmt.setInt(7, group.getId().get());
- stmt.addBatch();
- }
- stmt.executeBatch();
- }
- }
-
- private void executeSchemaMigration(ReviewDbSchemaVersion schema) throws Exception {
- schema.migrateData(db, new TestUpdateUI());
- }
-
- private ImmutableList<GroupReference> getAllGroupsFromNoteDb()
- throws IOException, ConfigInvalidException {
- try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName)) {
- return GroupNameNotes.loadAllGroups(allUsersRepo);
- }
- }
-
- @Nullable
- private ObjectId getGroupSha1(AccountGroup.UUID groupUuid) throws IOException {
- try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName)) {
- Ref ref = allUsersRepo.exactRef(RefNames.refsGroups(groupUuid));
- return ref != null ? ref.getObjectId() : null;
- }
- }
-}
diff --git a/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInReviewDbTest.java b/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInReviewDbTest.java
deleted file mode 100644
index 890ae32..0000000
--- a/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInReviewDbTest.java
+++ /dev/null
@@ -1,1125 +0,0 @@
-// 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.server.schema;
-
-import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.extensions.common.testing.CommitInfoSubject.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.SECTION_NOTE_DB;
-import static com.google.gerrit.truth.OptionalSubject.assertThat;
-
-import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.extensions.api.GerritApi;
-import com.google.gerrit.extensions.api.accounts.AccountInput;
-import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo;
-import com.google.gerrit.extensions.api.groups.GroupInput;
-import com.google.gerrit.extensions.common.AccountInfo;
-import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.extensions.common.GroupAuditEventInfo;
-import com.google.gerrit.extensions.common.GroupAuditEventInfo.GroupMemberAuditEventInfo;
-import com.google.gerrit.extensions.common.GroupAuditEventInfo.Type;
-import com.google.gerrit.extensions.common.GroupAuditEventInfo.UserMemberAuditEventInfo;
-import com.google.gerrit.extensions.common.GroupInfo;
-import com.google.gerrit.extensions.common.GroupOptionsInfo;
-import com.google.gerrit.extensions.registration.DynamicSet;
-import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbWrapper;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.Sequences;
-import com.google.gerrit.server.account.GroupBackend;
-import com.google.gerrit.server.account.GroupUUID;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.config.GerritServerId;
-import com.google.gerrit.server.config.GerritServerIdProvider;
-import com.google.gerrit.server.git.CommitUtil;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.group.InternalGroup;
-import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.group.db.GroupConfig;
-import com.google.gerrit.server.group.db.GroupNameNotes;
-import com.google.gerrit.server.group.db.GroupsConsistencyChecker;
-import com.google.gerrit.server.group.testing.InternalGroupSubject;
-import com.google.gerrit.server.group.testing.TestGroupBackend;
-import com.google.gerrit.server.util.time.TimeUtil;
-import com.google.gerrit.testing.InMemoryTestEnvironment;
-import com.google.gerrit.testing.TestTimeUtil;
-import com.google.gerrit.testing.TestTimeUtil.TempClockStep;
-import com.google.gerrit.testing.TestUpdateUI;
-import com.google.gerrit.truth.OptionalSubject;
-import com.google.gwtorm.jdbc.JdbcSchema;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import java.io.IOException;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.Statement;
-import java.sql.Timestamp;
-import java.time.LocalDate;
-import java.time.Month;
-import java.time.ZoneOffset;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.TimeUnit;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevSort;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-public class Schema_166_to_167_WithGroupsInReviewDbTest {
- private static Config createConfig() {
- Config config = new Config();
- config.setString(GerritServerIdProvider.SECTION, null, GerritServerIdProvider.KEY, "1234567");
-
- // Enable groups in ReviewDb. This means the primary storage for groups is ReviewDb.
- config.setBoolean(SECTION_NOTE_DB, GROUPS.key(), DISABLE_REVIEW_DB, false);
-
- return config;
- }
-
- @Rule
- public InMemoryTestEnvironment testEnv =
- new InMemoryTestEnvironment(Schema_166_to_167_WithGroupsInReviewDbTest::createConfig);
-
- @Inject private GerritApi gApi;
- @Inject private Schema_167 schema167;
- @Inject private ReviewDb db;
- @Inject private GitRepositoryManager gitRepoManager;
- @Inject private AllUsersName allUsersName;
- @Inject private GroupsConsistencyChecker consistencyChecker;
- @Inject private IdentifiedUser currentUser;
- @Inject private @GerritServerId String serverId;
- @Inject private @GerritPersonIdent PersonIdent serverIdent;
- @Inject private GroupBundle.Factory groupBundleFactory;
- @Inject private GroupBackend groupBackend;
- @Inject private DynamicSet<GroupBackend> backends;
- @Inject private Sequences seq;
-
- private JdbcSchema jdbcSchema;
-
- @Before
- public void initDb() throws Exception {
- jdbcSchema = ReviewDbWrapper.unwrapJbdcSchema(db);
-
- try (Statement stmt = jdbcSchema.getConnection().createStatement()) {
- stmt.execute(
- "CREATE TABLE account_groups ("
- + " group_uuid varchar(255) DEFAULT '' NOT NULL,"
- + " group_id INTEGER DEFAULT 0 NOT NULL,"
- + " name varchar(255) DEFAULT '' NOT NULL,"
- + " created_on TIMESTAMP,"
- + " description CLOB,"
- + " owner_group_uuid varchar(255) DEFAULT '' NOT NULL,"
- + " visible_to_all CHAR(1) DEFAULT 'N' NOT NULL"
- + ")");
-
- stmt.execute(
- "CREATE TABLE account_group_members ("
- + " group_id INTEGER DEFAULT 0 NOT NULL,"
- + " account_id INTEGER DEFAULT 0 NOT NULL"
- + ")");
-
- stmt.execute(
- "CREATE TABLE account_group_members_audit ("
- + " group_id INTEGER DEFAULT 0 NOT NULL,"
- + " account_id INTEGER DEFAULT 0 NOT NULL,"
- + " added_by INTEGER DEFAULT 0 NOT NULL,"
- + " added_on TIMESTAMP,"
- + " removed_by INTEGER,"
- + " removed_on TIMESTAMP"
- + ")");
-
- stmt.execute(
- "CREATE TABLE account_group_by_id ("
- + " group_id INTEGER DEFAULT 0 NOT NULL,"
- + " include_uuid VARCHAR(255) DEFAULT '' NOT NULL"
- + ")");
-
- stmt.execute(
- "CREATE TABLE account_group_by_id_aud ("
- + " group_id INTEGER DEFAULT 0 NOT NULL,"
- + " include_uuid VARCHAR(255) DEFAULT '' NOT NULL,"
- + " added_by INTEGER DEFAULT 0 NOT NULL,"
- + " added_on TIMESTAMP,"
- + " removed_by INTEGER,"
- + " removed_on TIMESTAMP"
- + ")");
- }
- }
-
- @Before
- public void setTimeForTesting() {
- TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
- }
-
- @After
- public void resetTime() {
- TestTimeUtil.useSystemTime();
- }
-
- @Test
- public void reviewDbOnlyGroupsAreMigratedToNoteDb() throws Exception {
- // Create groups only in ReviewDb
- AccountGroup group1 = newGroup().setName("verifiers").build();
- AccountGroup group2 = newGroup().setName("contributors").build();
- storeInReviewDb(group1, group2);
-
- executeSchemaMigration(schema167, group1, group2);
-
- ImmutableList<GroupReference> groups = getAllGroupsFromNoteDb();
- ImmutableList<String> groupNames =
- groups.stream().map(GroupReference::getName).collect(toImmutableList());
- assertThat(groupNames).containsAllOf("verifiers", "contributors");
- }
-
- @Test
- public void alreadyExistingGroupsAreMigratedToNoteDb() throws Exception {
- // Create group in NoteDb and ReviewDb
- GroupInput groupInput = new GroupInput();
- groupInput.name = "verifiers";
- groupInput.description = "old";
- GroupInfo group1 = gApi.groups().create(groupInput).get();
- storeInReviewDb(group1);
-
- // Update group only in ReviewDb
- AccountGroup group1InReviewDb = getFromReviewDb(new AccountGroup.Id(group1.groupId));
- group1InReviewDb.setDescription("new");
- updateInReviewDb(group1InReviewDb);
-
- // Create a second group in NoteDb and ReviewDb
- GroupInfo group2 = gApi.groups().create("contributors").get();
- storeInReviewDb(group2);
-
- executeSchemaMigration(schema167, group1, group2);
-
- // Verify that both groups are present in NoteDb
- ImmutableList<GroupReference> groups = getAllGroupsFromNoteDb();
- ImmutableList<String> groupNames =
- groups.stream().map(GroupReference::getName).collect(toImmutableList());
- assertThat(groupNames).containsAllOf("verifiers", "contributors");
-
- // Verify that group1 has the description from ReviewDb
- Optional<InternalGroup> group1InNoteDb = getGroupFromNoteDb(new AccountGroup.UUID(group1.id));
- assertThatGroup(group1InNoteDb).value().description().isEqualTo("new");
- }
-
- @Test
- public void adminGroupIsMigratedToNoteDb() throws Exception {
- // Administrators group is automatically created for all Gerrit servers (NoteDb only).
- GroupInfo adminGroup = gApi.groups().id("Administrators").get();
- storeInReviewDb(adminGroup);
-
- executeSchemaMigration(schema167, adminGroup);
-
- ImmutableList<GroupReference> groups = getAllGroupsFromNoteDb();
- ImmutableList<String> groupNames =
- groups.stream().map(GroupReference::getName).collect(toImmutableList());
- assertThat(groupNames).contains("Administrators");
- }
-
- @Test
- public void nonInteractiveUsersGroupIsMigratedToNoteDb() throws Exception {
- // 'Non-Interactive Users' group is automatically created for all Gerrit servers (NoteDb only).
- GroupInfo nonInteractiveUsersGroup = gApi.groups().id("Non-Interactive Users").get();
- storeInReviewDb(nonInteractiveUsersGroup);
-
- executeSchemaMigration(schema167, nonInteractiveUsersGroup);
-
- ImmutableList<GroupReference> groups = getAllGroupsFromNoteDb();
- ImmutableList<String> groupNames =
- groups.stream().map(GroupReference::getName).collect(toImmutableList());
- assertThat(groupNames).contains("Non-Interactive Users");
- }
-
- @Test
- public void groupsAreConsistentAfterMigrationToNoteDb() throws Exception {
- // Administrators group are automatically created for all Gerrit servers (NoteDb only).
- GroupInfo adminGroup = gApi.groups().id("Administrators").get();
- GroupInfo nonInteractiveUsersGroup = gApi.groups().id("Non-Interactive Users").get();
- storeInReviewDb(adminGroup, nonInteractiveUsersGroup);
-
- AccountGroup group1 = newGroup().setName("verifiers").build();
- AccountGroup group2 = newGroup().setName("contributors").build();
- storeInReviewDb(group1, group2);
-
- executeSchemaMigration(schema167, group1, group2);
-
- List<ConsistencyCheckInfo.ConsistencyProblemInfo> consistencyProblems =
- consistencyChecker.check();
- assertThat(consistencyProblems).isEmpty();
- }
-
- @Test
- public void nameIsKeptDuringMigrationToNoteDb() throws Exception {
- AccountGroup group = newGroup().setName("verifiers").build();
- storeInReviewDb(group);
-
- executeSchemaMigration(schema167, group);
-
- Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
- assertThatGroup(groupInNoteDb).value().name().isEqualTo("verifiers");
- }
-
- @Test
- public void emptyNameIsKeptDuringMigrationToNoteDb() throws Exception {
- AccountGroup group = newGroup().setName("").build();
- storeInReviewDb(group);
-
- executeSchemaMigration(schema167, group);
-
- Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
- assertThatGroup(groupInNoteDb).value().name().isEqualTo("");
- }
-
- @Test
- public void uuidIsKeptDuringMigrationToNoteDb() throws Exception {
- AccountGroup.UUID groupUuid = new AccountGroup.UUID("ABCDEF");
- AccountGroup group = newGroup().setGroupUuid(groupUuid).build();
- storeInReviewDb(group);
-
- executeSchemaMigration(schema167, group);
-
- Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(groupUuid);
- assertThatGroup(groupInNoteDb).value().groupUuid().isEqualTo(groupUuid);
- }
-
- @Test
- public void idIsKeptDuringMigrationToNoteDb() throws Exception {
- AccountGroup.Id id = new AccountGroup.Id(12345);
- AccountGroup group = newGroup().setId(id).build();
- storeInReviewDb(group);
-
- executeSchemaMigration(schema167, group);
-
- Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
- assertThatGroup(groupInNoteDb).value().id().isEqualTo(id);
- }
-
- @Test
- public void createdOnIsKeptDuringMigrationToNoteDb() throws Exception {
- Timestamp createdOn =
- Timestamp.from(
- LocalDate.of(2018, Month.FEBRUARY, 20)
- .atTime(18, 2, 56)
- .atZone(ZoneOffset.UTC)
- .toInstant());
- AccountGroup group = newGroup().setCreatedOn(createdOn).build();
- storeInReviewDb(group);
-
- executeSchemaMigration(schema167, group);
-
- Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
- assertThatGroup(groupInNoteDb).value().createdOn().isEqualTo(createdOn);
- }
-
- @Test
- public void ownerUuidIsKeptDuringMigrationToNoteDb() throws Exception {
- AccountGroup.UUID ownerGroupUuid = new AccountGroup.UUID("UVWXYZ");
- AccountGroup group = newGroup().setOwnerGroupUuid(ownerGroupUuid).build();
- storeInReviewDb(group);
-
- executeSchemaMigration(schema167, group);
-
- Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
- assertThatGroup(groupInNoteDb).value().ownerGroupUuid().isEqualTo(ownerGroupUuid);
- }
-
- @Test
- public void descriptionIsKeptDuringMigrationToNoteDb() throws Exception {
- AccountGroup group = newGroup().setDescription("A test group").build();
- storeInReviewDb(group);
-
- executeSchemaMigration(schema167, group);
-
- Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
- assertThatGroup(groupInNoteDb).value().description().isEqualTo("A test group");
- }
-
- @Test
- public void absentDescriptionIsKeptDuringMigrationToNoteDb() throws Exception {
- AccountGroup group = newGroup().build();
- storeInReviewDb(group);
-
- executeSchemaMigration(schema167, group);
-
- Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
- assertThatGroup(groupInNoteDb).value().description().isNull();
- }
-
- @Test
- public void visibleToAllIsKeptDuringMigrationToNoteDb() throws Exception {
- AccountGroup group = newGroup().setVisibleToAll(true).build();
- storeInReviewDb(group);
-
- executeSchemaMigration(schema167, group);
-
- Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
- assertThatGroup(groupInNoteDb).value().visibleToAll().isTrue();
- }
-
- @Test
- public void membersAreKeptDuringMigrationToNoteDb() throws Exception {
- AccountGroup group = newGroup().build();
- storeInReviewDb(group);
- Account.Id member1 = new Account.Id(23456);
- Account.Id member2 = new Account.Id(93483);
- addMembersInReviewDb(group.getId(), member1, member2);
-
- executeSchemaMigration(schema167, group);
-
- Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
- assertThatGroup(groupInNoteDb).value().members().containsExactly(member1, member2);
- }
-
- @Test
- public void subgroupsAreKeptDuringMigrationToNoteDb() throws Exception {
- AccountGroup group = newGroup().build();
- storeInReviewDb(group);
- AccountGroup.UUID subgroup1 = new AccountGroup.UUID("FGHIKL");
- AccountGroup.UUID subgroup2 = new AccountGroup.UUID("MNOPQR");
- addSubgroupsInReviewDb(group.getId(), subgroup1, subgroup2);
-
- executeSchemaMigration(schema167, group);
-
- Optional<InternalGroup> groupInNoteDb = getGroupFromNoteDb(group.getGroupUUID());
- assertThatGroup(groupInNoteDb).value().subgroups().containsExactly(subgroup1, subgroup2);
- }
-
- @Test
- public void logFormatWithAccountsAndGerritGroups() throws Exception {
- AccountInfo user1 = createAccount("user1");
- AccountInfo user2 = createAccount("user2");
-
- AccountGroup group1 = createInReviewDb("group1");
- AccountGroup group2 = createInReviewDb("group2");
- AccountGroup group3 = createInReviewDb("group3");
-
- // Add some accounts
- try (TempClockStep step = TestTimeUtil.freezeClock()) {
- addMembersInReviewDb(
- group1.getId(), new Account.Id(user1._accountId), new Account.Id(user2._accountId));
- }
- TimeUtil.nowTs();
-
- // Add some Gerrit groups
- try (TempClockStep step = TestTimeUtil.freezeClock()) {
- addSubgroupsInReviewDb(group1.getId(), group2.getGroupUUID(), group3.getGroupUUID());
- }
-
- executeSchemaMigration(schema167, group1, group2, group3);
-
- GroupBundle noteDbBundle = readGroupBundleFromNoteDb(group1.getGroupUUID());
-
- ImmutableList<CommitInfo> log = log(group1);
- assertThat(log).hasSize(4);
-
- // Verify commit that created the group
- assertThat(log.get(0)).message().isEqualTo("Create group");
- assertThat(log.get(0)).author().name().isEqualTo(serverIdent.getName());
- assertThat(log.get(0)).author().email().isEqualTo(serverIdent.getEmailAddress());
- assertThat(log.get(0)).author().date().isEqualTo(noteDbBundle.group().getCreatedOn());
- assertThat(log.get(0)).author().tz().isEqualTo(serverIdent.getTimeZoneOffset());
- assertThat(log.get(0)).committer().isEqualTo(log.get(0).author);
-
- // Verify commit that the group creator as member
- assertThat(log.get(1))
- .message()
- .isEqualTo(
- "Update group\n\nAdd: "
- + currentUser.getName()
- + " <"
- + currentUser.getAccountId()
- + "@"
- + serverId
- + ">");
- assertThat(log.get(1)).author().name().isEqualTo(currentUser.getName());
- assertThat(log.get(1)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
- assertThat(log.get(1)).committer().hasSameDateAs(log.get(1).author);
-
- // Verify commit that added members
- assertThat(log.get(2))
- .message()
- .isEqualTo(
- "Update group\n"
- + "\n"
- + ("Add: user1 <" + user1._accountId + "@" + serverId + ">\n")
- + ("Add: user2 <" + user2._accountId + "@" + serverId + ">"));
- assertThat(log.get(2)).author().name().isEqualTo(currentUser.getName());
- assertThat(log.get(2)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
- assertThat(log.get(2)).committer().hasSameDateAs(log.get(2).author);
-
- // Verify commit that added Gerrit groups
- assertThat(log.get(3))
- .message()
- .isEqualTo(
- "Update group\n"
- + "\n"
- + ("Add-group: " + group2.getName() + " <" + group2.getGroupUUID().get() + ">\n")
- + ("Add-group: " + group3.getName() + " <" + group3.getGroupUUID().get() + ">"));
- assertThat(log.get(3)).author().name().isEqualTo(currentUser.getName());
- assertThat(log.get(3)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
- assertThat(log.get(3)).committer().hasSameDateAs(log.get(3).author);
-
- // Verify that audit log is correctly read by Gerrit
- List<? extends GroupAuditEventInfo> auditEvents =
- gApi.groups().id(group1.getGroupUUID().get()).auditLog();
- assertThat(auditEvents).hasSize(5);
- AccountInfo currentUserInfo = gApi.accounts().id(currentUser.getAccountId().get()).get();
- assertMemberAuditEvent(
- auditEvents.get(4), Type.ADD_USER, currentUser.getAccountId(), currentUserInfo);
- assertMemberAuditEvents(
- auditEvents.get(3),
- auditEvents.get(2),
- Type.ADD_USER,
- currentUser.getAccountId(),
- user1,
- user2);
- assertSubgroupAuditEvents(
- auditEvents.get(1),
- auditEvents.get(0),
- Type.ADD_GROUP,
- currentUser.getAccountId(),
- toGroupInfo(group2),
- toGroupInfo(group3));
- }
-
- @Test
- public void logFormatWithSystemGroups() throws Exception {
- AccountGroup group = createInReviewDb("group");
-
- try (TempClockStep step = TestTimeUtil.freezeClock()) {
- addSubgroupsInReviewDb(
- group.getId(), SystemGroupBackend.ANONYMOUS_USERS, SystemGroupBackend.REGISTERED_USERS);
- }
-
- executeSchemaMigration(schema167, group);
-
- GroupBundle noteDbBundle = readGroupBundleFromNoteDb(group.getGroupUUID());
-
- ImmutableList<CommitInfo> log = log(group);
- assertThat(log).hasSize(3);
-
- // Verify commit that created the group
- assertThat(log.get(0)).message().isEqualTo("Create group");
- assertThat(log.get(0)).author().name().isEqualTo(serverIdent.getName());
- assertThat(log.get(0)).author().email().isEqualTo(serverIdent.getEmailAddress());
- assertThat(log.get(0)).author().date().isEqualTo(noteDbBundle.group().getCreatedOn());
- assertThat(log.get(0)).author().tz().isEqualTo(serverIdent.getTimeZoneOffset());
- assertThat(log.get(0)).committer().isEqualTo(log.get(0).author);
-
- // Verify commit that the group creator as member
- assertThat(log.get(1))
- .message()
- .isEqualTo(
- "Update group\n\nAdd: "
- + currentUser.getName()
- + " <"
- + currentUser.getAccountId()
- + "@"
- + serverId
- + ">");
- assertThat(log.get(1)).author().name().isEqualTo(currentUser.getName());
- assertThat(log.get(1)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
- assertThat(log.get(1)).committer().hasSameDateAs(log.get(1).author);
-
- // Verify commit that added system groups
- assertThat(log.get(2))
- .message()
- .isEqualTo(
- "Update group\n"
- + "\n"
- + "Add-group: Anonymous Users <global:Anonymous-Users>\n"
- + "Add-group: Registered Users <global:Registered-Users>");
- assertThat(log.get(2)).author().name().isEqualTo(currentUser.getName());
- assertThat(log.get(2)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
- assertThat(log.get(2)).committer().hasSameDateAs(log.get(2).author);
-
- // Verify that audit log is correctly read by Gerrit
- List<? extends GroupAuditEventInfo> auditEvents =
- gApi.groups().id(group.getGroupUUID().get()).auditLog();
- assertThat(auditEvents).hasSize(3);
- AccountInfo currentUserInfo = gApi.accounts().id(currentUser.getAccountId().get()).get();
- assertMemberAuditEvent(
- auditEvents.get(2), Type.ADD_USER, currentUser.getAccountId(), currentUserInfo);
- assertSubgroupAuditEvents(
- auditEvents.get(1),
- auditEvents.get(0),
- Type.ADD_GROUP,
- currentUser.getAccountId(),
- groupInfoForExternalGroup(SystemGroupBackend.ANONYMOUS_USERS),
- groupInfoForExternalGroup(SystemGroupBackend.REGISTERED_USERS));
- }
-
- @Test
- public void logFormatWithExternalGroup() throws Exception {
- AccountGroup group = createInReviewDb("group");
-
- TestGroupBackend testGroupBackend = new TestGroupBackend();
- backends.add("gerrit", testGroupBackend);
- AccountGroup.UUID subgroupUuid = testGroupBackend.create("test").getGroupUUID();
- assertThat(groupBackend.handles(subgroupUuid)).isTrue();
- addSubgroupsInReviewDb(group.getId(), subgroupUuid);
-
- executeSchemaMigration(schema167, group);
-
- GroupBundle noteDbBundle = readGroupBundleFromNoteDb(group.getGroupUUID());
-
- ImmutableList<CommitInfo> log = log(group);
- assertThat(log).hasSize(3);
-
- // Verify commit that created the group
- assertThat(log.get(0)).message().isEqualTo("Create group");
- assertThat(log.get(0)).author().name().isEqualTo(serverIdent.getName());
- assertThat(log.get(0)).author().email().isEqualTo(serverIdent.getEmailAddress());
- assertThat(log.get(0)).author().date().isEqualTo(noteDbBundle.group().getCreatedOn());
- assertThat(log.get(0)).author().tz().isEqualTo(serverIdent.getTimeZoneOffset());
- assertThat(log.get(0)).committer().isEqualTo(log.get(0).author);
-
- // Verify commit that the group creator as member
- assertThat(log.get(1))
- .message()
- .isEqualTo(
- "Update group\n\nAdd: "
- + currentUser.getName()
- + " <"
- + currentUser.getAccountId()
- + "@"
- + serverId
- + ">");
- assertThat(log.get(1)).author().name().isEqualTo(currentUser.getName());
- assertThat(log.get(1)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
- assertThat(log.get(1)).committer().hasSameDateAs(log.get(1).author);
-
- // Verify commit that added system groups
- // Note: The schema migration can only resolve names of Gerrit groups, not of external groups
- // and system groups, hence the UUID shows up in commit messages where we would otherwise
- // expect the group name.
- assertThat(log.get(2))
- .message()
- .isEqualTo(
- "Update group\n"
- + "\n"
- + "Add-group: "
- + subgroupUuid.get()
- + " <"
- + subgroupUuid.get()
- + ">");
- assertThat(log.get(2)).author().name().isEqualTo(currentUser.getName());
- assertThat(log.get(2)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
- assertThat(log.get(2)).committer().hasSameDateAs(log.get(2).author);
-
- // Verify that audit log is correctly read by Gerrit
- List<? extends GroupAuditEventInfo> auditEvents =
- gApi.groups().id(group.getGroupUUID().get()).auditLog();
- assertThat(auditEvents).hasSize(2);
- AccountInfo currentUserInfo = gApi.accounts().id(currentUser.getAccountId().get()).get();
- assertMemberAuditEvent(
- auditEvents.get(1), Type.ADD_USER, currentUser.getAccountId(), currentUserInfo);
- assertSubgroupAuditEvent(
- auditEvents.get(0),
- Type.ADD_GROUP,
- currentUser.getAccountId(),
- groupInfoForExternalGroup(subgroupUuid));
- }
-
- @Test
- public void logFormatWithNonExistingExternalGroup() throws Exception {
- AccountGroup group = createInReviewDb("group");
-
- AccountGroup.UUID subgroupUuid = new AccountGroup.UUID("notExisting:foo");
-
- assertThat(groupBackend.handles(subgroupUuid)).isFalse();
- addSubgroupsInReviewDb(group.getId(), subgroupUuid);
-
- executeSchemaMigration(schema167, group);
-
- GroupBundle noteDbBundle = readGroupBundleFromNoteDb(group.getGroupUUID());
-
- ImmutableList<CommitInfo> log = log(group);
- assertThat(log).hasSize(3);
-
- // Verify commit that created the group
- assertThat(log.get(0)).message().isEqualTo("Create group");
- assertThat(log.get(0)).author().name().isEqualTo(serverIdent.getName());
- assertThat(log.get(0)).author().email().isEqualTo(serverIdent.getEmailAddress());
- assertThat(log.get(0)).author().date().isEqualTo(noteDbBundle.group().getCreatedOn());
- assertThat(log.get(0)).author().tz().isEqualTo(serverIdent.getTimeZoneOffset());
- assertThat(log.get(0)).committer().isEqualTo(log.get(0).author);
-
- // Verify commit that the group creator as member
- assertThat(log.get(1))
- .message()
- .isEqualTo(
- "Update group\n\nAdd: "
- + currentUser.getName()
- + " <"
- + currentUser.getAccountId()
- + "@"
- + serverId
- + ">");
- assertThat(log.get(1)).author().name().isEqualTo(currentUser.getName());
- assertThat(log.get(1)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
- assertThat(log.get(1)).committer().hasSameDateAs(log.get(1).author);
-
- // Verify commit that added system groups
- // Note: The schema migration can only resolve names of Gerrit groups, not of external groups
- // and system groups, hence the UUID shows up in commit messages where we would otherwise
- // expect the group name.
- assertThat(log.get(2))
- .message()
- .isEqualTo("Update group\n" + "\n" + "Add-group: notExisting:foo <notExisting:foo>");
- assertThat(log.get(2)).author().name().isEqualTo(currentUser.getName());
- assertThat(log.get(2)).author().email().isEqualTo(currentUser.getAccountId() + "@" + serverId);
- assertThat(log.get(2)).committer().hasSameDateAs(log.get(2).author);
-
- // Verify that audit log is correctly read by Gerrit
- List<? extends GroupAuditEventInfo> auditEvents =
- gApi.groups().id(group.getGroupUUID().get()).auditLog();
- assertThat(auditEvents).hasSize(2);
- AccountInfo currentUserInfo = gApi.accounts().id(currentUser.getAccountId().get()).get();
- assertMemberAuditEvent(
- auditEvents.get(1), Type.ADD_USER, currentUser.getAccountId(), currentUserInfo);
- assertSubgroupAuditEvent(
- auditEvents.get(0),
- Type.ADD_GROUP,
- currentUser.getAccountId(),
- groupInfoForExternalGroup(subgroupUuid));
- }
-
- private static TestGroup.Builder newGroup() {
- return TestGroup.builder();
- }
-
- private AccountGroup createInReviewDb(String groupName) throws Exception {
- AccountGroup group =
- new AccountGroup(
- new AccountGroup.NameKey(groupName),
- new AccountGroup.Id(seq.nextGroupId()),
- GroupUUID.make(groupName, serverIdent),
- TimeUtil.nowTs());
- storeInReviewDb(group);
- addMembersInReviewDb(group.getId(), currentUser.getAccountId());
- return group;
- }
-
- private void storeInReviewDb(GroupInfo... groups) throws Exception {
- storeInReviewDb(
- Arrays.stream(groups)
- .map(Schema_166_to_167_WithGroupsInReviewDbTest::toAccountGroup)
- .toArray(AccountGroup[]::new));
- }
-
- private void storeInReviewDb(AccountGroup... groups) throws Exception {
- try (PreparedStatement stmt =
- jdbcSchema
- .getConnection()
- .prepareStatement(
- "INSERT INTO account_groups"
- + " (group_uuid,"
- + " group_id,"
- + " name,"
- + " description,"
- + " created_on,"
- + " owner_group_uuid,"
- + " visible_to_all) VALUES (?, ?, ?, ?, ?, ?, ?)")) {
- for (AccountGroup group : groups) {
- stmt.setString(1, group.getGroupUUID().get());
- stmt.setInt(2, group.getId().get());
- stmt.setString(3, group.getName());
- stmt.setString(4, group.getDescription());
- stmt.setTimestamp(5, group.getCreatedOn());
- stmt.setString(6, group.getOwnerGroupUUID().get());
- stmt.setString(7, group.isVisibleToAll() ? "Y" : "N");
- stmt.addBatch();
- }
- stmt.executeBatch();
- }
- }
-
- private void updateInReviewDb(AccountGroup... groups) throws Exception {
- try (PreparedStatement stmt =
- jdbcSchema
- .getConnection()
- .prepareStatement(
- "UPDATE account_groups SET"
- + " group_uuid = ?,"
- + " name = ?,"
- + " description = ?,"
- + " created_on = ?,"
- + " owner_group_uuid = ?,"
- + " visible_to_all = ?"
- + " WHERE group_id = ?")) {
- for (AccountGroup group : groups) {
- stmt.setString(1, group.getGroupUUID().get());
- stmt.setString(2, group.getName());
- stmt.setString(3, group.getDescription());
- stmt.setTimestamp(4, group.getCreatedOn());
- stmt.setString(5, group.getOwnerGroupUUID().get());
- stmt.setString(6, group.isVisibleToAll() ? "Y" : "N");
- stmt.setInt(7, group.getId().get());
- stmt.addBatch();
- }
- stmt.executeBatch();
- }
- }
-
- private AccountGroup getFromReviewDb(AccountGroup.Id groupId) throws Exception {
- try (Statement stmt = jdbcSchema.getConnection().createStatement();
- ResultSet rs =
- stmt.executeQuery(
- "SELECT group_uuid,"
- + " name,"
- + " description,"
- + " created_on,"
- + " owner_group_uuid,"
- + " visible_to_all"
- + " FROM account_groups"
- + " WHERE group_id = "
- + groupId.get())) {
- if (!rs.next()) {
- throw new OrmException(String.format("Group %s not found", groupId.get()));
- }
-
- AccountGroup.UUID groupUuid = new AccountGroup.UUID(rs.getString(1));
- AccountGroup.NameKey groupName = new AccountGroup.NameKey(rs.getString(2));
- String description = rs.getString(3);
- Timestamp createdOn = rs.getTimestamp(4);
- AccountGroup.UUID ownerGroupUuid = new AccountGroup.UUID(rs.getString(5));
- boolean visibleToAll = "Y".equals(rs.getString(6));
-
- AccountGroup group = new AccountGroup(groupName, groupId, groupUuid, createdOn);
- group.setDescription(description);
- group.setOwnerGroupUUID(ownerGroupUuid);
- group.setVisibleToAll(visibleToAll);
-
- if (rs.next()) {
- throw new OrmException(String.format("Group ID %s is ambiguous", groupId.get()));
- }
-
- return group;
- }
- }
-
- private void addMembersInReviewDb(AccountGroup.Id groupId, Account.Id... memberIds)
- throws Exception {
- try (PreparedStatement addMemberStmt =
- jdbcSchema
- .getConnection()
- .prepareStatement(
- "INSERT INTO account_group_members"
- + " (group_id,"
- + " account_id) VALUES ("
- + groupId.get()
- + ", ?)");
- PreparedStatement addMemberAuditStmt =
- jdbcSchema
- .getConnection()
- .prepareStatement(
- "INSERT INTO account_group_members_audit"
- + " (group_id,"
- + " account_id,"
- + " added_by,"
- + " added_on) VALUES ("
- + groupId.get()
- + ", ?, "
- + currentUser.getAccountId().get()
- + ", ?)")) {
- Timestamp addedOn = TimeUtil.nowTs();
- for (Account.Id memberId : memberIds) {
- addMemberStmt.setInt(1, memberId.get());
- addMemberStmt.addBatch();
-
- addMemberAuditStmt.setInt(1, memberId.get());
- addMemberAuditStmt.setTimestamp(2, addedOn);
- addMemberAuditStmt.addBatch();
- }
- addMemberStmt.executeBatch();
- addMemberAuditStmt.executeBatch();
- }
- }
-
- private void addSubgroupsInReviewDb(AccountGroup.Id groupId, AccountGroup.UUID... subgroupUuids)
- throws Exception {
- try (PreparedStatement addSubGroupStmt =
- jdbcSchema
- .getConnection()
- .prepareStatement(
- "INSERT INTO account_group_by_id"
- + " (group_id,"
- + " include_uuid) VALUES ("
- + groupId.get()
- + ", ?)");
- PreparedStatement addSubGroupAuditStmt =
- jdbcSchema
- .getConnection()
- .prepareStatement(
- "INSERT INTO account_group_by_id_aud"
- + " (group_id,"
- + " include_uuid,"
- + " added_by,"
- + " added_on) VALUES ("
- + groupId.get()
- + ", ?, "
- + currentUser.getAccountId().get()
- + ", ?)")) {
- Timestamp addedOn = TimeUtil.nowTs();
- for (AccountGroup.UUID subgroupUuid : subgroupUuids) {
- addSubGroupStmt.setString(1, subgroupUuid.get());
- addSubGroupStmt.addBatch();
-
- addSubGroupAuditStmt.setString(1, subgroupUuid.get());
- addSubGroupAuditStmt.setTimestamp(2, addedOn);
- addSubGroupAuditStmt.addBatch();
- }
- addSubGroupStmt.executeBatch();
- addSubGroupAuditStmt.executeBatch();
- }
- }
-
- private AccountInfo createAccount(String name) throws RestApiException {
- AccountInput accountInput = new AccountInput();
- accountInput.username = name;
- accountInput.name = name;
- return gApi.accounts().create(accountInput).get();
- }
-
- private GroupBundle readGroupBundleFromNoteDb(AccountGroup.UUID groupUuid) throws Exception {
- try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName)) {
- return groupBundleFactory.fromNoteDb(allUsersName, allUsersRepo, groupUuid);
- }
- }
-
- private void executeSchemaMigration(ReviewDbSchemaVersion schema, AccountGroup... groupsToVerify)
- throws Exception {
- executeSchemaMigration(
- schema,
- Arrays.stream(groupsToVerify)
- .map(AccountGroup::getGroupUUID)
- .toArray(AccountGroup.UUID[]::new));
- }
-
- private void executeSchemaMigration(ReviewDbSchemaVersion schema, GroupInfo... groupsToVerify)
- throws Exception {
- executeSchemaMigration(
- schema,
- Arrays.stream(groupsToVerify)
- .map(i -> new AccountGroup.UUID(i.id))
- .toArray(AccountGroup.UUID[]::new));
- }
-
- private void executeSchemaMigration(
- ReviewDbSchemaVersion schema, AccountGroup.UUID... groupsToVerify) throws Exception {
- List<GroupBundle> reviewDbBundles = new ArrayList<>();
- for (AccountGroup.UUID groupUuid : groupsToVerify) {
- reviewDbBundles.add(GroupBundle.Factory.fromReviewDb(db, groupUuid));
- }
-
- schema.migrateData(db, new TestUpdateUI());
-
- for (GroupBundle reviewDbBundle : reviewDbBundles) {
- assertMigratedCleanly(readGroupBundleFromNoteDb(reviewDbBundle.uuid()), reviewDbBundle);
- }
- }
-
- private void assertMigratedCleanly(GroupBundle noteDbBundle, GroupBundle expectedReviewDbBundle) {
- assertThat(GroupBundle.compareWithAudits(expectedReviewDbBundle, noteDbBundle)).isEmpty();
- }
-
- private ImmutableList<CommitInfo> log(AccountGroup group) throws Exception {
- ImmutableList.Builder<CommitInfo> result = ImmutableList.builder();
- List<Date> commitDates = new ArrayList<>();
- try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName);
- RevWalk rw = new RevWalk(allUsersRepo)) {
- Ref ref = allUsersRepo.exactRef(RefNames.refsGroups(group.getGroupUUID()));
- if (ref != null) {
- rw.sort(RevSort.REVERSE);
- rw.setRetainBody(true);
- rw.markStart(rw.parseCommit(ref.getObjectId()));
- for (RevCommit c : rw) {
- result.add(CommitUtil.toCommitInfo(c));
- commitDates.add(c.getCommitterIdent().getWhen());
- }
- }
- }
- assertThat(commitDates).named("commit timestamps for %s", result).isOrdered();
- return result.build();
- }
-
- private ImmutableList<GroupReference> getAllGroupsFromNoteDb()
- throws IOException, ConfigInvalidException {
- try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName)) {
- return GroupNameNotes.loadAllGroups(allUsersRepo);
- }
- }
-
- private Optional<InternalGroup> getGroupFromNoteDb(AccountGroup.UUID groupUuid) throws Exception {
- try (Repository allUsersRepo = gitRepoManager.openRepository(allUsersName)) {
- return GroupConfig.loadForGroup(allUsersName, allUsersRepo, groupUuid).getLoadedGroup();
- }
- }
-
- private static OptionalSubject<InternalGroupSubject, InternalGroup> assertThatGroup(
- Optional<InternalGroup> group) {
- return assertThat(group, InternalGroupSubject::assertThat).named("group");
- }
-
- private void assertMemberAuditEvent(
- GroupAuditEventInfo info,
- Type expectedType,
- Account.Id expectedUser,
- AccountInfo expectedMember) {
- assertThat(info.user._accountId).isEqualTo(expectedUser.get());
- assertThat(info.type).isEqualTo(expectedType);
- assertThat(info).isInstanceOf(UserMemberAuditEventInfo.class);
- assertAccount(((UserMemberAuditEventInfo) info).member, expectedMember);
- }
-
- private void assertMemberAuditEvents(
- GroupAuditEventInfo info1,
- GroupAuditEventInfo info2,
- Type expectedType,
- Account.Id expectedUser,
- AccountInfo expectedMember1,
- AccountInfo expectedMember2) {
- assertThat(info1).isInstanceOf(UserMemberAuditEventInfo.class);
- assertThat(info2).isInstanceOf(UserMemberAuditEventInfo.class);
-
- UserMemberAuditEventInfo event1 = (UserMemberAuditEventInfo) info1;
- UserMemberAuditEventInfo event2 = (UserMemberAuditEventInfo) info2;
-
- assertThat(event1.member._accountId)
- .isAnyOf(expectedMember1._accountId, expectedMember2._accountId);
- assertThat(event2.member._accountId)
- .isAnyOf(expectedMember1._accountId, expectedMember2._accountId);
- assertThat(event1.member._accountId).isNotEqualTo(event2.member._accountId);
-
- if (event1.member._accountId == expectedMember1._accountId) {
- assertMemberAuditEvent(info1, expectedType, expectedUser, expectedMember1);
- assertMemberAuditEvent(info2, expectedType, expectedUser, expectedMember2);
- } else {
- assertMemberAuditEvent(info1, expectedType, expectedUser, expectedMember2);
- assertMemberAuditEvent(info2, expectedType, expectedUser, expectedMember1);
- }
- }
-
- private void assertSubgroupAuditEvent(
- GroupAuditEventInfo info,
- Type expectedType,
- Account.Id expectedUser,
- GroupInfo expectedSubGroup) {
- assertThat(info.user._accountId).isEqualTo(expectedUser.get());
- assertThat(info.type).isEqualTo(expectedType);
- assertThat(info).isInstanceOf(GroupMemberAuditEventInfo.class);
- assertGroup(((GroupMemberAuditEventInfo) info).member, expectedSubGroup);
- }
-
- private void assertSubgroupAuditEvents(
- GroupAuditEventInfo info1,
- GroupAuditEventInfo info2,
- Type expectedType,
- Account.Id expectedUser,
- GroupInfo expectedSubGroup1,
- GroupInfo expectedSubGroup2) {
- assertThat(info1).isInstanceOf(GroupMemberAuditEventInfo.class);
- assertThat(info2).isInstanceOf(GroupMemberAuditEventInfo.class);
-
- GroupMemberAuditEventInfo event1 = (GroupMemberAuditEventInfo) info1;
- GroupMemberAuditEventInfo event2 = (GroupMemberAuditEventInfo) info2;
-
- assertThat(event1.member.id).isAnyOf(expectedSubGroup1.id, expectedSubGroup2.id);
- assertThat(event2.member.id).isAnyOf(expectedSubGroup1.id, expectedSubGroup2.id);
- assertThat(event1.member.id).isNotEqualTo(event2.member.id);
-
- if (event1.member.id.equals(expectedSubGroup1.id)) {
- assertSubgroupAuditEvent(info1, expectedType, expectedUser, expectedSubGroup1);
- assertSubgroupAuditEvent(info2, expectedType, expectedUser, expectedSubGroup2);
- } else {
- assertSubgroupAuditEvent(info1, expectedType, expectedUser, expectedSubGroup2);
- assertSubgroupAuditEvent(info2, expectedType, expectedUser, expectedSubGroup1);
- }
- }
-
- private void assertAccount(AccountInfo actual, AccountInfo expected) {
- assertThat(actual._accountId).isEqualTo(expected._accountId);
- assertThat(actual.name).isEqualTo(expected.name);
- assertThat(actual.email).isEqualTo(expected.email);
- assertThat(actual.username).isEqualTo(expected.username);
- }
-
- private void assertGroup(GroupInfo actual, GroupInfo expected) {
- assertThat(actual.id).isEqualTo(expected.id);
- assertThat(actual.name).isEqualTo(expected.name);
- assertThat(actual.groupId).isEqualTo(expected.groupId);
- }
-
- private GroupInfo groupInfoForExternalGroup(AccountGroup.UUID groupUuid) {
- GroupInfo groupInfo = new GroupInfo();
- groupInfo.id = IdString.fromDecoded(groupUuid.get()).encoded();
-
- if (groupBackend.handles(groupUuid)) {
- groupInfo.name = groupBackend.get(groupUuid).getName();
- }
-
- return groupInfo;
- }
-
- private static AccountGroup toAccountGroup(GroupInfo info) {
- AccountGroup group =
- new AccountGroup(
- new AccountGroup.NameKey(info.name),
- new AccountGroup.Id(info.groupId),
- new AccountGroup.UUID(info.id),
- info.createdOn);
- group.setDescription(info.description);
- if (info.ownerId != null) {
- group.setOwnerGroupUUID(new AccountGroup.UUID(info.ownerId));
- }
- group.setVisibleToAll(
- info.options != null && info.options.visibleToAll != null && info.options.visibleToAll);
- return group;
- }
-
- private static GroupInfo toGroupInfo(AccountGroup group) {
- GroupInfo groupInfo = new GroupInfo();
- groupInfo.id = group.getGroupUUID().get();
- groupInfo.groupId = group.getId().get();
- groupInfo.name = group.getName();
- groupInfo.createdOn = group.getCreatedOn();
- groupInfo.description = group.getDescription();
- groupInfo.owner = group.getOwnerGroupUUID().get();
- groupInfo.options = new GroupOptionsInfo();
- groupInfo.options.visibleToAll = group.isVisibleToAll() ? true : null;
- return groupInfo;
- }
-}