Line data Source code
1 : // Copyright (C) 2018 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.server.schema;
16 :
17 : import static com.google.common.collect.ImmutableList.toImmutableList;
18 :
19 : import com.google.common.annotations.VisibleForTesting;
20 : import com.google.common.collect.ImmutableList;
21 : import com.google.common.collect.ImmutableSortedMap;
22 : import com.google.common.collect.ImmutableSortedSet;
23 : import com.google.gerrit.entities.RefNames;
24 : import com.google.gerrit.exceptions.StorageException;
25 : import com.google.gerrit.server.config.AllUsersName;
26 : import com.google.gerrit.server.config.GerritServerConfig;
27 : import com.google.gerrit.server.git.GitRepositoryManager;
28 : import com.google.gerrit.server.notedb.Sequences;
29 : import com.google.inject.Inject;
30 : import java.io.IOException;
31 : import java.util.stream.IntStream;
32 : import org.eclipse.jgit.errors.ConfigInvalidException;
33 : import org.eclipse.jgit.lib.Config;
34 : import org.eclipse.jgit.lib.Repository;
35 :
36 : public class NoteDbSchemaUpdater {
37 : private final Config cfg;
38 : private final AllUsersName allUsersName;
39 : private final GitRepositoryManager repoManager;
40 : private final SchemaCreator schemaCreator;
41 : private final NoteDbSchemaVersionManager versionManager;
42 : private final NoteDbSchemaVersion.Arguments args;
43 : private final ImmutableSortedMap<Integer, Class<? extends NoteDbSchemaVersion>> schemaVersions;
44 :
45 : @Inject
46 : NoteDbSchemaUpdater(
47 : @GerritServerConfig Config cfg,
48 : AllUsersName allUsersName,
49 : GitRepositoryManager repoManager,
50 : SchemaCreator schemaCreator,
51 : NoteDbSchemaVersionManager versionManager,
52 : NoteDbSchemaVersion.Arguments args) {
53 15 : this(
54 : cfg,
55 : allUsersName,
56 : repoManager,
57 : schemaCreator,
58 : versionManager,
59 : args,
60 : NoteDbSchemaVersions.ALL);
61 15 : }
62 :
63 : NoteDbSchemaUpdater(
64 : Config cfg,
65 : AllUsersName allUsersName,
66 : GitRepositoryManager repoManager,
67 : SchemaCreator schemaCreator,
68 : NoteDbSchemaVersionManager versionManager,
69 : NoteDbSchemaVersion.Arguments args,
70 16 : ImmutableSortedMap<Integer, Class<? extends NoteDbSchemaVersion>> schemaVersions) {
71 16 : this.cfg = cfg;
72 16 : this.allUsersName = allUsersName;
73 16 : this.repoManager = repoManager;
74 16 : this.schemaCreator = schemaCreator;
75 16 : this.versionManager = versionManager;
76 16 : this.args = args;
77 16 : this.schemaVersions = schemaVersions;
78 16 : }
79 :
80 : public void update(UpdateUI ui) {
81 16 : ensureSchemaCreated();
82 :
83 16 : int currentVersion = versionManager.read();
84 16 : if (currentVersion == 0) {
85 : // The only valid case where there is no refs/meta/version is when running 3.x init for the
86 : // first time on a site that previously ran init on 2.16. A freshly created 3.x site will have
87 : // seeded refs/meta/version during AllProjectsCreator, so it won't hit this block.
88 1 : checkNoteDbConfigFor216();
89 : }
90 :
91 16 : for (int nextVersion : requiredUpgrades(currentVersion, schemaVersions.keySet())) {
92 : try {
93 1 : ui.message(String.format("Migrating data to schema %d ...", nextVersion));
94 1 : NoteDbSchemaVersions.get(schemaVersions, nextVersion).upgrade(args, ui);
95 1 : versionManager.increment(nextVersion - 1);
96 0 : } catch (Exception e) {
97 0 : throw new StorageException(
98 0 : String.format("Failed to upgrade to schema version %d", nextVersion), e);
99 1 : }
100 1 : }
101 16 : }
102 :
103 : private void ensureSchemaCreated() {
104 : try {
105 16 : schemaCreator.ensureCreated();
106 0 : } catch (IOException | ConfigInvalidException e) {
107 0 : throw new StorageException("Cannot initialize Gerrit site", e);
108 16 : }
109 16 : }
110 :
111 : // Config#getEnum requires this to be public, so give it an off-putting name.
112 1 : public enum PrimaryStorageFor216Compatibility {
113 1 : REVIEW_DB,
114 1 : NOTE_DB
115 : }
116 :
117 : private void checkNoteDbConfigFor216() {
118 : // Check that the NoteDb migration config matches what we expect from a site that both:
119 : // * Completed the change migration to NoteDB.
120 : // * Ran schema upgrades from a 2.16 final release.
121 :
122 1 : if (!cfg.getBoolean("noteDb", "changes", "write", false)
123 1 : || !cfg.getBoolean("noteDb", "changes", "read", false)
124 1 : || cfg.getEnum(
125 : "noteDb", "changes", "primaryStorage", PrimaryStorageFor216Compatibility.REVIEW_DB)
126 : != PrimaryStorageFor216Compatibility.NOTE_DB
127 1 : || !cfg.getBoolean("noteDb", "changes", "disableReviewDb", false)) {
128 1 : throw new StorageException(
129 : "You appear to be upgrading from a 2.x site, but the NoteDb change migration was"
130 : + " not completed. See documentation:\n"
131 : + "https://gerrit-review.googlesource.com/Documentation/note-db.html#migration");
132 : }
133 :
134 : // We don't have a direct way to check that 2.16 init was run; the most obvious side effect
135 : // would be upgrading the *ReviewDb* schema to the latest 2.16 schema version. But in 3.x we can
136 : // no longer access ReviewDb, so we can't check that directly.
137 : //
138 : // Instead, check for a NoteDb-specific side effect of the migration process: the presence of
139 : // the NoteDb group sequence ref. This is created by the schema 163 migration, which was part of
140 : // 2.16 and not 2.15.
141 : //
142 : // There are a few corner cases where we will proceed even if the schema is not fully up to
143 : // date:
144 : // * If a user happened to run init from master after schema 163 was added but before 2.16
145 : // final. We assume that someone savvy enough to do that has followed the documented
146 : // requirement of upgrading to 2.16 final before 3.0.
147 : // * If a user ran init in 2.16.x and the upgrade to 163 succeeded but a later update failed.
148 : // In this case the server literally will not start under 2.16. We assume the user will fix
149 : // this and get 2.16 running rather than abandoning 2.16 and jumping to 3.0 at this point.
150 1 : try (Repository allUsers = repoManager.openRepository(allUsersName)) {
151 1 : if (allUsers.exactRef(RefNames.REFS_SEQUENCES + Sequences.NAME_GROUPS) == null) {
152 1 : throw new StorageException(
153 : "You appear to be upgrading to 3.x from a version prior to 2.16; you must upgrade to"
154 : + " 2.16.x first");
155 : }
156 0 : } catch (IOException e) {
157 0 : throw new StorageException("Failed to check NoteDb migration state", e);
158 1 : }
159 1 : }
160 :
161 : @VisibleForTesting
162 : static ImmutableList<Integer> requiredUpgrades(
163 : int currentVersion, ImmutableSortedSet<Integer> allVersions) {
164 16 : int firstVersion = allVersions.first();
165 16 : int latestVersion = allVersions.last();
166 16 : if (currentVersion == latestVersion) {
167 16 : return ImmutableList.of();
168 1 : } else if (currentVersion > latestVersion) {
169 1 : throw new StorageException(
170 1 : String.format(
171 : "Cannot downgrade NoteDb schema from version %d to %d",
172 1 : currentVersion, latestVersion));
173 : }
174 :
175 : int firstUpgradeVersion;
176 1 : if (currentVersion == 0) {
177 : // Bootstrap NoteDb version to minimum supported schema number.
178 1 : firstUpgradeVersion = firstVersion;
179 : } else {
180 1 : if (currentVersion < firstVersion - 1) {
181 1 : throw new StorageException(
182 1 : String.format(
183 1 : "Cannot skip NoteDb schema from version %d to %d", currentVersion, firstVersion));
184 : }
185 1 : firstUpgradeVersion = currentVersion + 1;
186 : }
187 1 : return IntStream.rangeClosed(firstUpgradeVersion, latestVersion)
188 1 : .boxed()
189 1 : .collect(toImmutableList());
190 : }
191 : }
|