Merge "Merge branch 'stable-2.14'"
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java
index 52dc4f2..2b13df0 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GerritServer.java
@@ -61,6 +61,14 @@
import org.eclipse.jgit.util.FS;
public class GerritServer implements AutoCloseable {
+ public static class StartupException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ StartupException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+ }
+
@AutoValue
public abstract static class Description {
public static Description forTestClass(
@@ -301,7 +309,12 @@
}
return null;
});
- serverStarted.await();
+ try {
+ serverStarted.await();
+ } catch (BrokenBarrierException e) {
+ daemon.stop();
+ throw new StartupException("Failed to start Gerrit daemon; see log", e);
+ }
System.out.println("Gerrit Server Started");
return new GerritServer(desc, createTestInjector(daemon), daemon, daemonService);
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/StandaloneSiteTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
new file mode 100644
index 0000000..be34ba6
--- /dev/null
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/StandaloneSiteTest.java
@@ -0,0 +1,142 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.stream.Collectors.joining;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.Streams;
+import com.google.gerrit.launcher.GerritLauncher;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.util.ManualRequestContext;
+import com.google.gerrit.server.util.OneOffRequestContext;
+import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.testutil.ConfigSuite;
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import java.util.Arrays;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+
+@RunWith(ConfigSuite.class)
+@UseLocalDisk
+public abstract class StandaloneSiteTest {
+ protected class ServerContext implements RequestContext, AutoCloseable {
+ private final GerritServer server;
+ private final ManualRequestContext ctx;
+
+ private ServerContext(GerritServer server) throws Exception {
+ this.server = server;
+ Injector i = server.getTestInjector();
+ if (adminId == null) {
+ adminId = i.getInstance(AccountCreator.class).admin().getId();
+ }
+ ctx = i.getInstance(OneOffRequestContext.class).openAs(adminId);
+ }
+
+ @Override
+ public CurrentUser getUser() {
+ return ctx.getUser();
+ }
+
+ @Override
+ public Provider<ReviewDb> getReviewDbProvider() {
+ return ctx.getReviewDbProvider();
+ }
+
+ public Injector getInjector() {
+ return server.getTestInjector();
+ }
+
+ @Override
+ public void close() throws Exception {
+ try {
+ ctx.close();
+ } finally {
+ server.close();
+ }
+ }
+ }
+
+ @ConfigSuite.Parameter public Config baseConfig;
+ @ConfigSuite.Name private String configName;
+
+ private final TemporaryFolder tempSiteDir = new TemporaryFolder();
+
+ private final TestRule testRunner =
+ new TestRule() {
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ beforeTest(description);
+ base.evaluate();
+ }
+ };
+ }
+ };
+
+ @Rule public RuleChain ruleChain = RuleChain.outerRule(tempSiteDir).around(testRunner);
+
+ protected SitePaths sitePaths;
+
+ private GerritServer.Description serverDesc;
+ private Account.Id adminId;
+
+ private void beforeTest(Description description) throws Exception {
+ serverDesc = GerritServer.Description.forTestMethod(description, configName);
+ sitePaths = new SitePaths(tempSiteDir.getRoot().toPath());
+ GerritServer.init(serverDesc, baseConfig, sitePaths.site_path);
+ }
+
+ protected ServerContext startServer() throws Exception {
+ return new ServerContext(startImpl());
+ }
+
+ protected void assertServerStartupFails() throws Exception {
+ try (GerritServer server = startImpl()) {
+ fail("expected server startup to fail");
+ } catch (GerritServer.StartupException e) {
+ // Expected.
+ }
+ }
+
+ private GerritServer startImpl() throws Exception {
+ return GerritServer.start(serverDesc, baseConfig, sitePaths.site_path);
+ }
+
+ protected static void runGerrit(String... args) throws Exception {
+ assertThat(GerritLauncher.mainImpl(args))
+ .named("gerrit.war " + Arrays.stream(args).collect(joining(" ")))
+ .isEqualTo(0);
+ }
+
+ @SafeVarargs
+ protected static void runGerrit(Iterable<String>... multiArgs) throws Exception {
+ runGerrit(
+ Arrays.stream(multiArgs).flatMap(args -> Streams.stream(args)).toArray(String[]::new));
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/OfflineNoteDbMigrationIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/OfflineNoteDbMigrationIT.java
index 1676f3e..9b36b03 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/OfflineNoteDbMigrationIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/OfflineNoteDbMigrationIT.java
@@ -17,48 +17,36 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.acceptance.AccountCreator;
-import com.google.gerrit.acceptance.GerritServer;
import com.google.gerrit.acceptance.NoHttpd;
-import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.acceptance.UseLocalDisk;
+import com.google.gerrit.acceptance.StandaloneSiteTest;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.common.ChangeInput;
-import com.google.gerrit.launcher.GerritLauncher;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
-import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.index.GerritIndexStatus;
+import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.notedb.ConfigNotesMigration;
import com.google.gerrit.server.notedb.NoteDbChangeState;
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
import com.google.gerrit.server.notedb.NoteDbChangeState.RefState;
import com.google.gerrit.server.notedb.NotesMigrationState;
-import com.google.gerrit.server.util.ManualRequestContext;
-import com.google.gerrit.server.util.OneOffRequestContext;
-import com.google.gerrit.testutil.ConfigSuite;
-import com.google.gerrit.testutil.TempFileUtil;
-import com.google.inject.Injector;
-import java.nio.file.Path;
-import java.util.stream.Stream;
-import org.eclipse.jgit.lib.Config;
+import com.google.gerrit.server.schema.ReviewDbFactory;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
-import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
/**
* Tests for offline {@code migrate-to-note-db} program.
@@ -67,119 +55,115 @@
* adding tests to {@link com.google.gerrit.acceptance.server.notedb.OnlineNoteDbMigrationIT} if
* possible.
*/
-@UseLocalDisk
@NoHttpd
-public class OfflineNoteDbMigrationIT {
- @Rule
- public TestWatcher testWatcher =
- new TestWatcher() {
- @Override
- protected void starting(Description description) {
- serverDesc = GerritServer.Description.forTestMethod(description, ConfigSuite.DEFAULT);
- }
- };
-
- private GerritServer.Description serverDesc;
-
- private Path site;
+public class OfflineNoteDbMigrationIT extends StandaloneSiteTest {
private StoredConfig gerritConfig;
+ private Project.NameKey project;
+ private Change.Id changeId;
+
@Before
public void setUp() throws Exception {
- site = TempFileUtil.createTempDirectory().toPath();
- GerritServer.init(serverDesc, new Config(), site);
- gerritConfig = new FileBasedConfig(new SitePaths(site).gerrit_config.toFile(), FS.detect());
- }
-
- @After
- public void tearDown() throws Exception {
- TempFileUtil.cleanup();
- }
-
- @Test
- public void rebuildEmptySiteStartingWithNoteDbDisabed() throws Exception {
- assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
- migrate();
- assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
- }
-
- @Test
- public void rebuildEmptySiteStartingWithNoteDbEnabled() throws Exception {
- setNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
- migrate();
- assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
+ gerritConfig = new FileBasedConfig(sitePaths.gerrit_config.toFile(), FS.detect());
}
@Test
public void rebuildOneChangeTrialMode() throws Exception {
assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
- Project.NameKey project = new Project.NameKey("project");
+ setUpOneChange();
- Account.Id accountId;
- Change.Id id;
- try (GerritServer server = startServer();
- ManualRequestContext ctx = openContext(server)) {
- accountId = ctx.getUser().getAccountId();
- GerritApi gApi = server.getTestInjector().getInstance(GerritApi.class);
- gApi.projects().create("project");
-
- ChangeInput in = new ChangeInput(project.get(), "master", "Test change");
- in.newBranch = true;
- id = new Change.Id(gApi.changes().create(in).info()._number);
- }
-
- migrate("--trial");
+ migrate();
assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
- try (GerritServer server = startServer();
- ManualRequestContext ctx = openContext(server, accountId)) {
- GitRepositoryManager repoManager =
- server.getTestInjector().getInstance(GitRepositoryManager.class);
+ try (ServerContext ctx = startServer()) {
+ GitRepositoryManager repoManager = ctx.getInjector().getInstance(GitRepositoryManager.class);
ObjectId metaId;
try (Repository repo = repoManager.openRepository(project)) {
- Ref ref = repo.exactRef(RefNames.changeMetaRef(id));
+ Ref ref = repo.exactRef(RefNames.changeMetaRef(changeId));
assertThat(ref).isNotNull();
metaId = ref.getObjectId();
}
- ReviewDb db = ReviewDbUtil.unwrapDb(ctx.getReviewDbProvider().get());
- Change c = db.changes().get(id);
- assertThat(c).isNotNull();
- NoteDbChangeState state = NoteDbChangeState.parse(c);
- assertThat(state).isNotNull();
- assertThat(state.getPrimaryStorage()).isEqualTo(PrimaryStorage.REVIEW_DB);
- assertThat(state.getRefState()).hasValue(RefState.create(metaId, ImmutableMap.of()));
+ try (ReviewDb db = openUnderlyingReviewDb(ctx)) {
+ Change c = db.changes().get(changeId);
+ assertThat(c).isNotNull();
+ NoteDbChangeState state = NoteDbChangeState.parse(c);
+ assertThat(state).isNotNull();
+ assertThat(state.getPrimaryStorage()).isEqualTo(PrimaryStorage.REVIEW_DB);
+ assertThat(state.getRefState()).hasValue(RefState.create(metaId, ImmutableMap.of()));
+ }
}
}
- private GerritServer startServer() throws Exception {
- return GerritServer.start(serverDesc, new Config(), site);
+ @Test
+ public void migrateOneChange() throws Exception {
+ assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
+ setUpOneChange();
+
+ migrate("--trial", "false");
+ assertNotesMigrationState(NotesMigrationState.NOTE_DB_UNFUSED);
+
+ try (ServerContext ctx = startServer()) {
+ GitRepositoryManager repoManager = ctx.getInjector().getInstance(GitRepositoryManager.class);
+ try (Repository repo = repoManager.openRepository(project)) {
+ assertThat(repo.exactRef(RefNames.changeMetaRef(changeId))).isNotNull();
+ }
+
+ try (ReviewDb db = openUnderlyingReviewDb(ctx)) {
+ Change c = db.changes().get(changeId);
+ assertThat(c).isNotNull();
+ NoteDbChangeState state = NoteDbChangeState.parse(c);
+ assertThat(state).isNotNull();
+ assertThat(state.getPrimaryStorage()).isEqualTo(PrimaryStorage.NOTE_DB);
+ assertThat(state.getRefState()).isEmpty();
+
+ ChangeInput in = new ChangeInput(project.get(), "master", "NoteDb-only change");
+ in.newBranch = true;
+ GerritApi gApi = ctx.getInjector().getInstance(GerritApi.class);
+ Change.Id id2 = new Change.Id(gApi.changes().create(in).info()._number);
+ assertThat(db.changes().get(id2)).isNull();
+ }
+ }
}
- private ManualRequestContext openContext(GerritServer server) throws Exception {
- Injector i = server.getTestInjector();
- TestAccount a = i.getInstance(AccountCreator.class).admin();
- return openContext(server, a.getId());
+ @Test
+ public void migrationDoesNotRequireIndex() throws Exception {
+ assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
+ setUpOneChange();
+
+ int version = ChangeSchemaDefinitions.INSTANCE.getLatest().getVersion();
+ GerritIndexStatus status = new GerritIndexStatus(sitePaths);
+ assertThat(status.getReady(ChangeSchemaDefinitions.NAME, version)).isTrue();
+ status.setReady(ChangeSchemaDefinitions.NAME, version, false);
+ status.save();
+
+ migrate("--trial", "false");
+ assertNotesMigrationState(NotesMigrationState.NOTE_DB_UNFUSED);
+
+ status = new GerritIndexStatus(sitePaths);
+ assertThat(status.getReady(ChangeSchemaDefinitions.NAME, version)).isFalse();
+
+ // TODO(dborowitz): Remove when offline migration includes reindex.
+ assertServerStartupFails();
}
- private ManualRequestContext openContext(GerritServer server, Account.Id accountId)
- throws Exception {
- return server.getTestInjector().getInstance(OneOffRequestContext.class).openAs(accountId);
+ private void setUpOneChange() throws Exception {
+ project = new Project.NameKey("project");
+ try (ServerContext ctx = startServer()) {
+ GerritApi gApi = ctx.getInjector().getInstance(GerritApi.class);
+ gApi.projects().create("project");
+
+ ChangeInput in = new ChangeInput(project.get(), "master", "Test change");
+ in.newBranch = true;
+ changeId = new Change.Id(gApi.changes().create(in).info()._number);
+ }
}
private void migrate(String... additionalArgs) throws Exception {
- String[] args =
- Stream.concat(
- Stream.of("migrate-to-note-db", "-d", site.toString(), "--show-stack-trace"),
- Stream.of(additionalArgs))
- .toArray(String[]::new);
- assertThat(GerritLauncher.mainImpl(args)).isEqualTo(0);
- }
-
- private void setNotesMigrationState(NotesMigrationState state) throws Exception {
- gerritConfig.load();
- ConfigNotesMigration.setConfigValues(gerritConfig, state.migration());
- gerritConfig.save();
+ runGerrit(
+ ImmutableList.of(
+ "migrate-to-note-db", "-d", sitePaths.site_path.toString(), "--show-stack-trace"),
+ ImmutableList.copyOf(additionalArgs));
}
private void assertNotesMigrationState(NotesMigrationState expected) throws Exception {
@@ -187,4 +171,10 @@
assertThat(NotesMigrationState.forNotesMigration(new ConfigNotesMigration(gerritConfig)))
.hasValue(expected);
}
+
+ private ReviewDb openUnderlyingReviewDb(ServerContext ctx) throws Exception {
+ return ctx.getInjector()
+ .getInstance(Key.get(new TypeLiteral<SchemaFactory<ReviewDb>>() {}, ReviewDbFactory.class))
+ .open();
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ReindexIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ReindexIT.java
index 79bbba2..10c51df 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ReindexIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ReindexIT.java
@@ -14,48 +14,14 @@
package com.google.gerrit.acceptance.pgm;
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.gerrit.launcher.GerritLauncher;
-import com.google.gerrit.testutil.TempFileUtil;
-import java.io.File;
-import org.junit.After;
-import org.junit.Before;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.StandaloneSiteTest;
import org.junit.Test;
-public class ReindexIT {
- private File sitePath;
-
- @Before
- public void createTempDirectory() throws Exception {
- sitePath = TempFileUtil.createTempDirectory();
- }
-
- @After
- public void destroySite() throws Exception {
- if (sitePath != null) {
- TempFileUtil.cleanup();
- }
- }
-
+@NoHttpd
+public class ReindexIT extends StandaloneSiteTest {
@Test
public void reindexEmptySite() throws Exception {
- initSite();
- runGerrit("reindex", "-d", sitePath.toString(), "--show-stack-trace");
- }
-
- private void initSite() throws Exception {
- runGerrit(
- "init",
- "-d",
- sitePath.getPath(),
- "--batch",
- "--no-auto-start",
- "--skip-plugins",
- "--show-stack-trace");
- }
-
- private static void runGerrit(String... args) throws Exception {
- assertThat(GerritLauncher.mainImpl(args)).isEqualTo(0);
+ runGerrit("reindex", "-d", sitePaths.site_path.toString(), "--show-stack-trace");
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
index 8902c4e..ae875f4 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
@@ -122,6 +122,10 @@
// want precise control over when auto-rebuilding happens.
cfg.setBoolean("index", null, "autoReindexIfStale", false);
+ // setNotesMigration tries to keep IDs in sync between ReviewDb and NoteDb, which is behavior
+ // unique to this test. This gets prohibitively slow if we use the default sequence gap.
+ cfg.setInt("noteDb", "changes", "initialSequenceGap", 0);
+
return cfg;
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
index 4008634..bb72a58 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
@@ -17,10 +17,18 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.server.notedb.NotesMigrationState.NOTE_DB_UNFUSED;
+import static com.google.gerrit.server.notedb.NotesMigrationState.READ_WRITE_NO_SEQUENCE;
+import static com.google.gerrit.server.notedb.NotesMigrationState.READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY;
+import static com.google.gerrit.server.notedb.NotesMigrationState.READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY;
+import static com.google.gerrit.server.notedb.NotesMigrationState.REVIEW_DB;
+import static com.google.gerrit.server.notedb.NotesMigrationState.WRITE;
+import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.Sandboxed;
@@ -28,6 +36,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.notedb.ConfigNotesMigration;
import com.google.gerrit.server.notedb.NoteDbChangeState;
@@ -37,13 +46,18 @@
import com.google.gerrit.server.notedb.rebuild.MigrationException;
import com.google.gerrit.server.notedb.rebuild.NoteDbMigrator;
import com.google.gerrit.server.schema.ReviewDbFactory;
+import com.google.gerrit.testutil.ConfigSuite;
import com.google.gerrit.testutil.NoteDbMode;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.List;
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.storage.file.FileBasedConfig;
@@ -56,12 +70,21 @@
public class OnlineNoteDbMigrationIT extends AbstractDaemonTest {
private static final String INVALID_STATE = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
+ @ConfigSuite.Default
+ public static Config defaultConfig() {
+ Config cfg = new Config();
+ cfg.setInt("noteDb", "changes", "sequenceBatchSize", 10);
+ cfg.setInt("noteDb", "changes", "initialSequenceGap", 500);
+ return cfg;
+ }
+
// Tests in this class are generally interested in the actual ReviewDb contents, but the shifting
// migration state may result in various kinds of wrappers showing up unexpectedly.
@Inject @ReviewDbFactory private SchemaFactory<ReviewDb> schemaFactory;
@Inject private SitePaths sitePaths;
@Inject private Provider<NoteDbMigrator.Builder> migratorBuilderProvider;
+ @Inject private Sequences sequences;
private FileBasedConfig gerritConfig;
@@ -69,7 +92,7 @@
public void setUp() throws Exception {
assume().that(NoteDbMode.get()).isEqualTo(NoteDbMode.OFF);
gerritConfig = new FileBasedConfig(sitePaths.gerrit_config.toFile(), FS.detect());
- assertNotesMigrationState(NotesMigrationState.REVIEW_DB);
+ assertNotesMigrationState(REVIEW_DB);
}
@Test
@@ -89,13 +112,13 @@
b -> b.setProjects(ps),
NoteDbMigrator::migrate);
- setNotesMigrationState(NotesMigrationState.READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY);
+ setNotesMigrationState(READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY);
assertMigrationException(
"Migration has already progressed past the endpoint of the \"trial mode\" state",
b -> b.setTrialMode(true),
NoteDbMigrator::migrate);
- setNotesMigrationState(NotesMigrationState.READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY);
+ setNotesMigrationState(READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY);
assertMigrationException(
"Cannot force rebuild changes; NoteDb is already the primary storage for some changes",
b -> b.setForceRebuild(true),
@@ -103,6 +126,13 @@
}
@Test
+ @GerritConfig(name = "noteDb.changes.initialSequenceGap", value = "-7")
+ public void initialSequenceGapMustBeNonNegative() throws Exception {
+ setNotesMigrationState(READ_WRITE_NO_SEQUENCE);
+ assertMigrationException("Sequence gap must be non-negative: -7", b -> b, m -> {});
+ }
+
+ @Test
public void rebuildOneChangeTrialModeAndForceRebuild() throws Exception {
PushOneCommit.Result r = createChange();
Change.Id id = r.getChange().getId();
@@ -110,7 +140,7 @@
try (NoteDbMigrator migrator = migratorBuilderProvider.get().setTrialMode(true).build()) {
migrator.migrate();
}
- assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
+ assertNotesMigrationState(READ_WRITE_NO_SEQUENCE);
ObjectId oldMetaId;
try (Repository repo = repoManager.openRepository(project);
@@ -134,7 +164,7 @@
}
migrate(b -> b.setTrialMode(true));
- assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
+ assertNotesMigrationState(READ_WRITE_NO_SEQUENCE);
try (Repository repo = repoManager.openRepository(project);
ReviewDb db = schemaFactory.open()) {
@@ -145,7 +175,7 @@
}
migrate(b -> b.setTrialMode(true).setForceRebuild(true));
- assertNotesMigrationState(NotesMigrationState.READ_WRITE_NO_SEQUENCE);
+ assertNotesMigrationState(READ_WRITE_NO_SEQUENCE);
try (Repository repo = repoManager.openRepository(project);
ReviewDb db = schemaFactory.open()) {
@@ -163,7 +193,7 @@
@Test
public void rebuildSubsetOfChanges() throws Exception {
- setNotesMigrationState(NotesMigrationState.WRITE);
+ setNotesMigrationState(WRITE);
PushOneCommit.Result r1 = createChange();
PushOneCommit.Result r2 = createChange();
@@ -191,7 +221,7 @@
@Test
public void rebuildSubsetOfProjects() throws Exception {
- setNotesMigrationState(NotesMigrationState.WRITE);
+ setNotesMigrationState(WRITE);
Project.NameKey p2 = createProject("project2");
TestRepository<?> tr2 = cloneProject(p2, admin);
@@ -221,6 +251,92 @@
}
}
+ @Test
+ public void enableSequencesNoGap() throws Exception {
+ testEnableSequences(0, 2, "12");
+ }
+
+ @Test
+ public void enableSequencesWithGap() throws Exception {
+ testEnableSequences(-1, 502, "512");
+ }
+
+ private void testEnableSequences(int builderOption, int expectedFirstId, String expectedRefValue)
+ throws Exception {
+ PushOneCommit.Result r = createChange();
+ Change.Id id = r.getChange().getId();
+ assertThat(id.get()).isEqualTo(1);
+
+ migrate(
+ b ->
+ b.setSequenceGap(builderOption)
+ .setStopAtStateForTesting(READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY));
+
+ assertThat(sequences.nextChangeId()).isEqualTo(expectedFirstId);
+ assertThat(sequences.nextChangeId()).isEqualTo(expectedFirstId + 1);
+
+ try (Repository repo = repoManager.openRepository(allProjects);
+ ObjectReader reader = repo.newObjectReader()) {
+ Ref ref = repo.exactRef("refs/sequences/changes");
+ assertThat(ref).isNotNull();
+ ObjectLoader loader = reader.open(ref.getObjectId());
+ assertThat(loader.getType()).isEqualTo(Constants.OBJ_BLOB);
+ // Acquired a block of 10 to serve the first nextChangeId call after migration.
+ assertThat(new String(loader.getCachedBytes(), UTF_8)).isEqualTo(expectedRefValue);
+ }
+
+ try (ReviewDb db = schemaFactory.open()) {
+ // Underlying, unused ReviewDb is still on its own sequence.
+ @SuppressWarnings("deprecation")
+ int nextFromReviewDb = db.nextChangeId();
+ assertThat(nextFromReviewDb).isEqualTo(3);
+ }
+ }
+
+ @Test
+ public void fullMigration() throws Exception {
+ PushOneCommit.Result r = createChange();
+ Change.Id id = r.getChange().getId();
+
+ migrate(b -> b);
+ assertNotesMigrationState(NOTE_DB_UNFUSED);
+
+ assertThat(sequences.nextChangeId()).isEqualTo(502);
+
+ ObjectId oldMetaId;
+ int rowVersion;
+ try (ReviewDb db = schemaFactory.open();
+ Repository repo = repoManager.openRepository(project)) {
+ Ref ref = repo.exactRef(RefNames.changeMetaRef(id));
+ assertThat(ref).isNotNull();
+ oldMetaId = ref.getObjectId();
+
+ Change c = db.changes().get(id);
+ assertThat(c.getTopic()).isNull();
+ rowVersion = c.getRowVersion();
+ NoteDbChangeState s = NoteDbChangeState.parse(c);
+ assertThat(s.getPrimaryStorage()).isEqualTo(PrimaryStorage.NOTE_DB);
+ assertThat(s.getRefState()).isEmpty();
+ }
+
+ // Do not open a new context, to simulate races with other threads that opened a context earlier
+ // in the migration process; this needs to work.
+ gApi.changes().id(id.get()).topic(name("a-topic"));
+
+ // Of course, it should also work with a new context.
+ resetCurrentApiUser();
+ gApi.changes().id(id.get()).topic(name("another-topic"));
+
+ try (ReviewDb db = schemaFactory.open();
+ Repository repo = repoManager.openRepository(project)) {
+ assertThat(repo.exactRef(RefNames.changeMetaRef(id)).getObjectId()).isNotEqualTo(oldMetaId);
+
+ Change c = db.changes().get(id);
+ assertThat(c.getTopic()).isNull();
+ assertThat(c.getRowVersion()).isEqualTo(rowVersion);
+ }
+ }
+
private void assertNotesMigrationState(NotesMigrationState expected) throws Exception {
assertThat(NotesMigrationState.forNotesMigration(notesMigration)).hasValue(expected);
gerritConfig.load();
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java
index 9a1cc49..3cbf71a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java
@@ -37,6 +37,7 @@
import java.util.ArrayList;
import java.util.List;
import org.kohsuke.args4j.Option;
+import org.kohsuke.args4j.spi.ExplicitBooleanOptionHandler;
public class MigrateToNoteDb extends SiteProgram {
@Option(name = "--threads", usage = "Number of threads to use for rebuilding NoteDb")
@@ -70,10 +71,20 @@
name = "--trial",
usage =
"trial mode: migrate changes and turn on reading from NoteDb, but leave ReviewDb as"
- + " the source of truth"
+ + " the source of truth",
+ handler = ExplicitBooleanOptionHandler.class
)
private boolean trial = true; // TODO(dborowitz): Default to false in 3.0.
+ @Option(
+ name = "--sequence-gap",
+ usage =
+ "gap in change sequence numbers between last ReviewDb number and first NoteDb number;"
+ + " negative indicates using the value of noteDb.changes.initialSequenceGap (default"
+ + " 1000)"
+ )
+ private int sequenceGap;
+
private Injector dbInjector;
private Injector sysInjector;
private LifecycleManager dbManager;
@@ -108,6 +119,7 @@
.setChanges(changes.stream().map(Change.Id::new).collect(toList()))
.setTrialMode(trial)
.setForceRebuild(force)
+ .setSequenceGap(sequenceGap)
.build()) {
if (!projects.isEmpty() || !changes.isEmpty()) {
migrator.rebuild();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/Sequences.java b/gerrit-server/src/main/java/com/google/gerrit/server/Sequences.java
index e6d56ee..f1f0e6f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/Sequences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/Sequences.java
@@ -37,6 +37,10 @@
public class Sequences {
public static final String CHANGES = "changes";
+ public static int getChangeSequenceGap(Config cfg) {
+ return cfg.getInt("noteDb", "changes", "initialSequenceGap", 1000);
+ }
+
private final Provider<ReviewDb> db;
private final NotesMigration migration;
private final RepoSequence changeSeq;
@@ -51,7 +55,7 @@
this.db = db;
this.migration = migration;
- int gap = cfg.getInt("noteDb", "changes", "initialSequenceGap", 0);
+ int gap = getChangeSequenceGap(cfg);
changeSeq =
new RepoSequence(
repoManager,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
index a40f9cf..a866314 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
@@ -16,19 +16,21 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.reviewdb.server.ReviewDbUtil.unwrapDb;
import static com.google.gerrit.server.notedb.NotesMigrationState.NOTE_DB_UNFUSED;
import static com.google.gerrit.server.notedb.NotesMigrationState.READ_WRITE_NO_SEQUENCE;
+import static com.google.gerrit.server.notedb.NotesMigrationState.READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY;
import static com.google.gerrit.server.notedb.NotesMigrationState.READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY;
import static com.google.gerrit.server.notedb.NotesMigrationState.WRITE;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
-import com.google.common.base.Predicates;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Stopwatch;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.Iterables;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Ordering;
import com.google.common.collect.SetMultimap;
@@ -42,12 +44,18 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.Sequences;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.LockFailureException;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.notedb.ConfigNotesMigration;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.notedb.NotesMigrationState;
+import com.google.gerrit.server.notedb.PrimaryStorageMigrator;
+import com.google.gerrit.server.notedb.RepoSequence;
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder.NoPatchSetsException;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
@@ -65,6 +73,7 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.storage.file.FileBasedConfig;
@@ -78,31 +87,45 @@
private static final Logger log = LoggerFactory.getLogger(NoteDbMigrator.class);
public static class Builder {
+ private final Config cfg;
private final SitePaths sitePaths;
private final SchemaFactory<ReviewDb> schemaFactory;
+ private final GitRepositoryManager repoManager;
+ private final AllProjectsName allProjects;
private final ChangeRebuilder rebuilder;
private final WorkQueue workQueue;
private final NotesMigration globalNotesMigration;
+ private final PrimaryStorageMigrator primaryStorageMigrator;
private int threads;
private ImmutableList<Project.NameKey> projects = ImmutableList.of();
private ImmutableList<Change.Id> changes = ImmutableList.of();
private OutputStream progressOut = NullOutputStream.INSTANCE;
+ private NotesMigrationState stopAtState;
private boolean trial;
private boolean forceRebuild;
+ private int sequenceGap = -1;
@Inject
Builder(
+ @GerritServerConfig Config cfg,
SitePaths sitePaths,
SchemaFactory<ReviewDb> schemaFactory,
+ GitRepositoryManager repoManager,
+ AllProjectsName allProjects,
ChangeRebuilder rebuilder,
WorkQueue workQueue,
- NotesMigration globalNotesMigration) {
+ NotesMigration globalNotesMigration,
+ PrimaryStorageMigrator primaryStorageMigrator) {
+ this.cfg = cfg;
this.sitePaths = sitePaths;
this.schemaFactory = schemaFactory;
+ this.repoManager = repoManager;
+ this.allProjects = allProjects;
this.rebuilder = rebuilder;
this.workQueue = workQueue;
this.globalNotesMigration = globalNotesMigration;
+ this.primaryStorageMigrator = primaryStorageMigrator;
}
/**
@@ -166,6 +189,18 @@
}
/**
+ * Stop at a specific migration state, for testing only.
+ *
+ * @param stopAtState state to stop at.
+ * @return this.
+ */
+ @VisibleForTesting
+ public Builder setStopAtStateForTesting(NotesMigrationState stopAtState) {
+ this.stopAtState = stopAtState;
+ return this;
+ }
+
+ /**
* Rebuild in "trial mode": configure Gerrit to write to and read from NoteDb, but leave
* ReviewDb as the source of truth for all changes.
*
@@ -196,61 +231,108 @@
return this;
}
+ /**
+ * Gap between ReviewDb change sequence numbers and NoteDb.
+ *
+ * <p>If NoteDb sequences are enabled in a running server, there is a race between the migration
+ * step that calls {@code nextChangeId()} to seed the ref, and other threads that call {@code
+ * nextChangeId()} to create new changes. In order to prevent these operations stepping on one
+ * another, we use this value to skip some predefined sequence numbers. This is strongly
+ * recommended in a running server.
+ *
+ * <p>If the migration takes place offline, there is no race with other threads, and this option
+ * may be set to 0. However, admins may still choose to use a gap, for example to make it easier
+ * to distinguish changes that were created before and after the NoteDb migration.
+ *
+ * <p>By default, uses the value from {@code noteDb.changes.initialSequenceGap} in {@code
+ * gerrit.config}, which defaults to 1000.
+ *
+ * @param sequenceGap sequence gap size; if negative, use the default.
+ * @return this.
+ */
+ public Builder setSequenceGap(int sequenceGap) {
+ this.sequenceGap = sequenceGap;
+ return this;
+ }
+
public NoteDbMigrator build() throws MigrationException {
return new NoteDbMigrator(
sitePaths,
schemaFactory,
+ repoManager,
+ allProjects,
rebuilder,
globalNotesMigration,
+ primaryStorageMigrator,
threads > 1
? MoreExecutors.listeningDecorator(workQueue.createQueue(threads, "RebuildChange"))
: MoreExecutors.newDirectExecutorService(),
projects,
changes,
progressOut,
+ stopAtState,
trial,
- forceRebuild);
+ forceRebuild,
+ sequenceGap >= 0 ? sequenceGap : Sequences.getChangeSequenceGap(cfg));
}
}
private final FileBasedConfig gerritConfig;
private final SchemaFactory<ReviewDb> schemaFactory;
+ private final GitRepositoryManager repoManager;
+ private final AllProjectsName allProjects;
private final ChangeRebuilder rebuilder;
private final NotesMigration globalNotesMigration;
+ private final PrimaryStorageMigrator primaryStorageMigrator;
private final ListeningExecutorService executor;
private final ImmutableList<Project.NameKey> projects;
private final ImmutableList<Change.Id> changes;
private final OutputStream progressOut;
+ private final NotesMigrationState stopAtState;
private final boolean trial;
private final boolean forceRebuild;
+ private final int sequenceGap;
private NoteDbMigrator(
SitePaths sitePaths,
SchemaFactory<ReviewDb> schemaFactory,
+ GitRepositoryManager repoManager,
+ AllProjectsName allProjects,
ChangeRebuilder rebuilder,
NotesMigration globalNotesMigration,
+ PrimaryStorageMigrator primaryStorageMigrator,
ListeningExecutorService executor,
ImmutableList<Project.NameKey> projects,
ImmutableList<Change.Id> changes,
OutputStream progressOut,
+ NotesMigrationState stopAtState,
boolean trial,
- boolean forceRebuild)
+ boolean forceRebuild,
+ int sequenceGap)
throws MigrationException {
if (!changes.isEmpty() && !projects.isEmpty()) {
throw new MigrationException("Cannot set both changes and projects");
}
+ if (sequenceGap < 0) {
+ throw new MigrationException("Sequence gap must be non-negative: " + sequenceGap);
+ }
this.schemaFactory = schemaFactory;
this.rebuilder = rebuilder;
+ this.repoManager = repoManager;
+ this.allProjects = allProjects;
this.globalNotesMigration = globalNotesMigration;
+ this.primaryStorageMigrator = primaryStorageMigrator;
this.gerritConfig = new FileBasedConfig(sitePaths.gerrit_config.toFile(), FS.detect());
this.executor = executor;
this.projects = projects;
this.changes = changes;
this.progressOut = progressOut;
+ this.stopAtState = stopAtState;
this.trial = trial;
this.forceRebuild = forceRebuild;
+ this.sequenceGap = sequenceGap;
}
@Override
@@ -281,6 +363,9 @@
boolean rebuilt = false;
while (state.compareTo(NOTE_DB_UNFUSED) < 0) {
+ if (state.equals(stopAtState)) {
+ return;
+ }
boolean stillNeedsRebuild = forceRebuild && !rebuilt;
if (trial && state.compareTo(READ_WRITE_NO_SEQUENCE) >= 0) {
if (stillNeedsRebuild && state == READ_WRITE_NO_SEQUENCE) {
@@ -303,7 +388,7 @@
state = rebuildAndEnableReads(state);
rebuilt = true;
} else {
- state = enableSequences();
+ state = enableSequences(state);
}
break;
case READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY:
@@ -311,11 +396,16 @@
state = rebuildAndEnableReads(state);
rebuilt = true;
} else {
- state = setNoteDbPrimary();
+ state = setNoteDbPrimary(state);
}
break;
case READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY:
- state = disableReviewDb();
+ // The only way we can get here is if there was a failure on a previous run of
+ // setNoteDbPrimary, since that method moves to NOTE_DB_UNFUSED if it completes
+ // successfully. Assume that not all changes were converted and re-run the step.
+ // migrateToNoteDbPrimary is a relatively fast no-op for already-migrated changes, so this
+ // isn't actually repeating work.
+ state = setNoteDbPrimary(state);
break;
case NOTE_DB_UNFUSED:
// Done!
@@ -340,16 +430,80 @@
return saveState(prev, READ_WRITE_NO_SEQUENCE);
}
- private NotesMigrationState enableSequences() {
- throw new UnsupportedOperationException("not yet implemented");
+ private NotesMigrationState enableSequences(NotesMigrationState prev)
+ throws OrmException, IOException {
+ try (ReviewDb db = schemaFactory.open()) {
+ @SuppressWarnings("deprecation")
+ RepoSequence seq =
+ new RepoSequence(
+ repoManager,
+ allProjects,
+ Sequences.CHANGES,
+ // If sequenceGap is 0, this writes into the sequence ref the same ID that is returned
+ // by the call to seq.next() below. If we actually used this as a change ID, that
+ // would be a problem, but we just discard it, so this is safe.
+ () -> db.nextChangeId() + sequenceGap - 1,
+ 1);
+ seq.next();
+ }
+ return saveState(prev, READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY);
}
- private NotesMigrationState setNoteDbPrimary() {
- throw new UnsupportedOperationException("not yet implemented");
+ private NotesMigrationState setNoteDbPrimary(NotesMigrationState prev)
+ throws MigrationException, OrmException, IOException {
+ checkState(
+ projects.isEmpty() && changes.isEmpty(),
+ "Should not have attempted setNoteDbPrimary with a subset of changes");
+ checkState(
+ prev == READ_WRITE_WITH_SEQUENCE_REVIEW_DB_PRIMARY
+ || prev == READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY,
+ "Unexpected start state for setNoteDbPrimary: %s",
+ prev);
+
+ // Before changing the primary storage of old changes, ensure new changes are created with
+ // NoteDb primary.
+ prev = saveState(prev, READ_WRITE_WITH_SEQUENCE_NOTE_DB_PRIMARY);
+
+ Stopwatch sw = Stopwatch.createStarted();
+ log.info("Setting primary storage to NoteDb");
+ List<Change.Id> allChanges;
+ try (ReviewDb db = unwrapDb(schemaFactory.open())) {
+ allChanges = Streams.stream(db.changes().all()).map(Change::getId).collect(toList());
+ }
+
+ List<ListenableFuture<Boolean>> futures =
+ allChanges
+ .stream()
+ .map(
+ id ->
+ executor.submit(
+ () -> {
+ // TODO(dborowitz): Avoid reopening db if using a single thread.
+ try (ReviewDb db = unwrapDb(schemaFactory.open())) {
+ primaryStorageMigrator.migrateToNoteDbPrimary(id);
+ return true;
+ } catch (Exception e) {
+ log.error("Error migrating primary storage for " + id, e);
+ return false;
+ }
+ }))
+ .collect(toList());
+
+ boolean ok = futuresToBoolean(futures, "Error migrating primary storage");
+ double t = sw.elapsed(TimeUnit.MILLISECONDS) / 1000d;
+ log.info(
+ String.format(
+ "Migrated primary storage of %d changes in %.01fs (%.01f/s)\n",
+ allChanges.size(), t, allChanges.size() / t));
+ if (!ok) {
+ throw new MigrationException("Migrating primary storage for some changes failed, see log");
+ }
+
+ return disableReviewDb(prev);
}
- private NotesMigrationState disableReviewDb() {
- throw new UnsupportedOperationException("not yet implemented");
+ private NotesMigrationState disableReviewDb(NotesMigrationState prev) throws IOException {
+ return saveState(prev, NOTE_DB_UNFUSED);
}
private Optional<NotesMigrationState> loadState() throws IOException {
@@ -394,7 +548,6 @@
if (!globalNotesMigration.commitChangeWrites()) {
throw new MigrationException("Cannot rebuild without noteDb.changes.write=true");
}
- boolean ok;
Stopwatch sw = Stopwatch.createStarted();
log.info("Rebuilding changes in NoteDb");
@@ -416,13 +569,7 @@
futures.add(future);
}
- try {
- ok = Iterables.all(Futures.allAsList(futures).get(), Predicates.equalTo(true));
- } catch (InterruptedException | ExecutionException e) {
- log.error("Error rebuilding projects", e);
- ok = false;
- }
-
+ boolean ok = futuresToBoolean(futures, "Error rebuilding projects");
double t = sw.elapsed(TimeUnit.MILLISECONDS) / 1000d;
log.info(
String.format(
@@ -502,4 +649,13 @@
}
return ok;
}
+
+ private static boolean futuresToBoolean(List<ListenableFuture<Boolean>> futures, String errMsg) {
+ try {
+ return Futures.allAsList(futures).get().stream().allMatch(b -> b);
+ } catch (InterruptedException | ExecutionException e) {
+ log.error(errMsg, e);
+ return false;
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/update/FusedNoteDbBatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/update/FusedNoteDbBatchUpdate.java
index b979636..7db5e43 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/update/FusedNoteDbBatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/update/FusedNoteDbBatchUpdate.java
@@ -286,7 +286,6 @@
@Assisted CurrentUser user,
@Assisted Timestamp when) {
super(repoManager, serverIdent, project, user, when);
- checkArgument(!db.changesTablesEnabled(), "expected Change tables to be disabled on %s", db);
this.changeNotesFactory = changeNotesFactory;
this.changeControlFactory = changeControlFactory;
this.changeUpdateFactory = changeUpdateFactory;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/update/UnfusedNoteDbBatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/update/UnfusedNoteDbBatchUpdate.java
index b5c7256..ce96c0e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/update/UnfusedNoteDbBatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/update/UnfusedNoteDbBatchUpdate.java
@@ -14,7 +14,6 @@
package com.google.gerrit.server.update;
-import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
@@ -267,7 +266,6 @@
@Assisted CurrentUser user,
@Assisted Timestamp when) {
super(repoManager, serverIdent, project, user, when);
- checkArgument(!db.changesTablesEnabled(), "expected Change tables to be disabled on %s", db);
this.changeNotesFactory = changeNotesFactory;
this.changeControlFactory = changeControlFactory;
this.changeUpdateFactory = changeUpdateFactory;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/RepoSequenceTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/RepoSequenceTest.java
index 4b302b7..66ccdad 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/RepoSequenceTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/RepoSequenceTest.java
@@ -264,13 +264,7 @@
Runnable afterReadRef,
Retryer<RefUpdate.Result> retryer) {
return new RepoSequence(
- repoManager,
- project,
- name,
- () -> start,
- batchSize,
- afterReadRef,
- retryer);
+ repoManager, project, name, () -> start, batchSize, afterReadRef, retryer);
}
private ObjectId writeBlob(String sequenceName, String value) {