| // 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.update.context.RefUpdateContext.RefUpdateType.OFFLINE_OPERATION; |
| |
| 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.entities.RefNames; |
| import com.google.gerrit.exceptions.StorageException; |
| import com.google.gerrit.server.Sequence; |
| 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.update.context.RefUpdateContext; |
| import com.google.inject.Inject; |
| import java.io.IOException; |
| import java.util.stream.IntStream; |
| import org.eclipse.jgit.errors.ConfigInvalidException; |
| 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 SchemaCreator schemaCreator; |
| private final NoteDbSchemaVersionManager versionManager; |
| private final NoteDbSchemaVersion.Arguments args; |
| private final ImmutableSortedMap<Integer, Class<? extends NoteDbSchemaVersion>> schemaVersions; |
| |
| @Inject |
| NoteDbSchemaUpdater( |
| @GerritServerConfig Config cfg, |
| AllUsersName allUsersName, |
| GitRepositoryManager repoManager, |
| SchemaCreator schemaCreator, |
| NoteDbSchemaVersionManager versionManager, |
| NoteDbSchemaVersion.Arguments args) { |
| this( |
| cfg, |
| allUsersName, |
| repoManager, |
| schemaCreator, |
| versionManager, |
| args, |
| NoteDbSchemaVersions.ALL); |
| } |
| |
| NoteDbSchemaUpdater( |
| Config cfg, |
| AllUsersName allUsersName, |
| GitRepositoryManager repoManager, |
| SchemaCreator schemaCreator, |
| NoteDbSchemaVersionManager versionManager, |
| NoteDbSchemaVersion.Arguments args, |
| ImmutableSortedMap<Integer, Class<? extends NoteDbSchemaVersion>> schemaVersions) { |
| this.cfg = cfg; |
| this.allUsersName = allUsersName; |
| this.repoManager = repoManager; |
| this.schemaCreator = schemaCreator; |
| this.versionManager = versionManager; |
| this.args = args; |
| this.schemaVersions = schemaVersions; |
| } |
| |
| public void update(UpdateUI ui) { |
| ensureSchemaCreated(); |
| |
| 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(); |
| } |
| try (RefUpdateContext ctx = RefUpdateContext.open(OFFLINE_OPERATION)) { |
| for (int nextVersion : requiredUpgrades(currentVersion, schemaVersions.keySet())) { |
| try { |
| ui.message(String.format("Migrating data to schema %d ...", nextVersion)); |
| NoteDbSchemaVersions.get(schemaVersions, nextVersion).upgrade(args, ui); |
| versionManager.increment(nextVersion - 1); |
| } catch (Exception e) { |
| throw new StorageException( |
| String.format("Failed to upgrade to schema version %d", nextVersion), e); |
| } |
| } |
| } |
| } |
| |
| private void ensureSchemaCreated() { |
| try { |
| schemaCreator.ensureCreated(); |
| } catch (IOException | ConfigInvalidException e) { |
| throw new StorageException("Cannot initialize Gerrit site", e); |
| } |
| } |
| |
| // Config#getEnum requires this to be public, so give it an off-putting name. |
| public enum PrimaryStorageFor216Compatibility { |
| REVIEW_DB, |
| NOTE_DB |
| } |
| |
| private void checkNoteDbConfigFor216() { |
| // 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("noteDb", "changes", "write", false) |
| || !cfg.getBoolean("noteDb", "changes", "read", false) |
| || cfg.getEnum( |
| "noteDb", "changes", "primaryStorage", PrimaryStorageFor216Compatibility.REVIEW_DB) |
| != PrimaryStorageFor216Compatibility.NOTE_DB |
| || !cfg.getBoolean("noteDb", "changes", "disableReviewDb", false)) { |
| throw new StorageException( |
| "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 + Sequence.NAME_GROUPS) == null) { |
| throw new StorageException( |
| "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 StorageException("Failed to check NoteDb migration state", e); |
| } |
| } |
| |
| @VisibleForTesting |
| static ImmutableList<Integer> requiredUpgrades( |
| int currentVersion, ImmutableSortedSet<Integer> allVersions) { |
| int firstVersion = allVersions.first(); |
| int latestVersion = allVersions.last(); |
| if (currentVersion == latestVersion) { |
| return ImmutableList.of(); |
| } else if (currentVersion > latestVersion) { |
| throw new StorageException( |
| 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 StorageException( |
| String.format( |
| "Cannot skip NoteDb schema from version %d to %d", currentVersion, firstVersion)); |
| } |
| firstUpgradeVersion = currentVersion + 1; |
| } |
| return IntStream.rangeClosed(firstUpgradeVersion, latestVersion) |
| .boxed() |
| .collect(toImmutableList()); |
| } |
| } |