/java/com/google/gerrit/pgm/init/BaseInit.java
Java | 525 lines | 442 code | 55 blank | 28 comment | 39 complexity | d0250417a8dc3ca9cd93e032cb3837e4 MD5 | raw file
1// Copyright (C) 2013 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
15package com.google.gerrit.pgm.init;
16
17import static com.google.gerrit.reviewdb.server.ReviewDbUtil.unwrapDb;
18import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER;
19import static com.google.inject.Scopes.SINGLETON;
20import static com.google.inject.Stage.PRODUCTION;
21
22import com.google.common.base.MoreObjects;
23import com.google.common.base.Strings;
24import com.google.gerrit.common.Die;
25import com.google.gerrit.common.IoUtil;
26import com.google.gerrit.metrics.DisabledMetricMaker;
27import com.google.gerrit.metrics.MetricMaker;
28import com.google.gerrit.pgm.init.api.ConsoleUI;
29import com.google.gerrit.pgm.init.api.InitFlags;
30import com.google.gerrit.pgm.init.api.InstallAllPlugins;
31import com.google.gerrit.pgm.init.api.InstallPlugins;
32import com.google.gerrit.pgm.init.api.LibraryDownload;
33import com.google.gerrit.pgm.init.index.IndexManagerOnInit;
34import com.google.gerrit.pgm.init.index.elasticsearch.ElasticIndexModuleOnInit;
35import com.google.gerrit.pgm.init.index.lucene.LuceneIndexModuleOnInit;
36import com.google.gerrit.pgm.util.SiteProgram;
37import com.google.gerrit.reviewdb.server.ReviewDb;
38import com.google.gerrit.server.config.GerritServerConfigModule;
39import com.google.gerrit.server.config.SitePath;
40import com.google.gerrit.server.config.SitePaths;
41import com.google.gerrit.server.git.GitRepositoryManager;
42import com.google.gerrit.server.index.IndexModule;
43import com.google.gerrit.server.plugins.JarScanner;
44import com.google.gerrit.server.schema.ReviewDbFactory;
45import com.google.gerrit.server.schema.SchemaUpdater;
46import com.google.gerrit.server.schema.UpdateUI;
47import com.google.gerrit.server.securestore.SecureStore;
48import com.google.gerrit.server.securestore.SecureStoreClassName;
49import com.google.gerrit.server.securestore.SecureStoreProvider;
50import com.google.gwtorm.jdbc.JdbcExecutor;
51import com.google.gwtorm.jdbc.JdbcSchema;
52import com.google.gwtorm.server.OrmException;
53import com.google.gwtorm.server.SchemaFactory;
54import com.google.gwtorm.server.StatementExecutor;
55import com.google.inject.AbstractModule;
56import com.google.inject.CreationException;
57import com.google.inject.Guice;
58import com.google.inject.Inject;
59import com.google.inject.Injector;
60import com.google.inject.Module;
61import com.google.inject.Provider;
62import com.google.inject.TypeLiteral;
63import com.google.inject.spi.Message;
64import com.google.inject.util.Providers;
65import java.io.FileNotFoundException;
66import java.io.IOException;
67import java.nio.file.FileVisitResult;
68import java.nio.file.Files;
69import java.nio.file.Path;
70import java.nio.file.Paths;
71import java.nio.file.SimpleFileVisitor;
72import java.nio.file.attribute.BasicFileAttributes;
73import java.util.ArrayList;
74import java.util.Collections;
75import java.util.List;
76import java.util.Set;
77import javax.sql.DataSource;
78import org.slf4j.Logger;
79import org.slf4j.LoggerFactory;
80
81/** Initialize a new Gerrit installation. */
82public class BaseInit extends SiteProgram {
83 private static final Logger log = LoggerFactory.getLogger(BaseInit.class);
84
85 private final boolean standalone;
86 private final boolean initDb;
87 protected final PluginsDistribution pluginsDistribution;
88 private final List<String> pluginsToInstall;
89
90 private Injector sysInjector;
91
92 protected BaseInit(PluginsDistribution pluginsDistribution, List<String> pluginsToInstall) {
93 this.standalone = true;
94 this.initDb = true;
95 this.pluginsDistribution = pluginsDistribution;
96 this.pluginsToInstall = pluginsToInstall;
97 }
98
99 public BaseInit(
100 Path sitePath,
101 boolean standalone,
102 boolean initDb,
103 PluginsDistribution pluginsDistribution,
104 List<String> pluginsToInstall) {
105 this(sitePath, null, standalone, initDb, pluginsDistribution, pluginsToInstall);
106 }
107
108 public BaseInit(
109 Path sitePath,
110 final Provider<DataSource> dsProvider,
111 boolean standalone,
112 boolean initDb,
113 PluginsDistribution pluginsDistribution,
114 List<String> pluginsToInstall) {
115 super(sitePath, dsProvider);
116 this.standalone = standalone;
117 this.initDb = initDb;
118 this.pluginsDistribution = pluginsDistribution;
119 this.pluginsToInstall = pluginsToInstall;
120 }
121
122 @Override
123 public int run() throws Exception {
124 final SiteInit init = createSiteInit();
125 if (beforeInit(init)) {
126 return 0;
127 }
128
129 init.flags.autoStart = getAutoStart() && init.site.isNew;
130 init.flags.dev = isDev() && init.site.isNew;
131 init.flags.skipPlugins = skipPlugins();
132 init.flags.deleteCaches = getDeleteCaches();
133 init.flags.isNew = init.site.isNew;
134
135 final SiteRun run;
136 try {
137 init.initializer.run();
138 init.flags.deleteOnFailure = false;
139
140 Injector sysInjector = createSysInjector(init);
141 IndexManagerOnInit indexManager = sysInjector.getInstance(IndexManagerOnInit.class);
142 try {
143 indexManager.start();
144 run = createSiteRun(init);
145 run.upgradeSchema();
146
147 init.initializer.postRun(sysInjector);
148 } finally {
149 indexManager.stop();
150 }
151 } catch (Exception | Error failure) {
152 if (init.flags.deleteOnFailure) {
153 recursiveDelete(getSitePath());
154 }
155 throw failure;
156 }
157
158 System.err.println("Initialized " + getSitePath().toRealPath().normalize());
159 afterInit(run);
160 return 0;
161 }
162
163 protected boolean skipPlugins() {
164 return false;
165 }
166
167 protected String getSecureStoreLib() {
168 return null;
169 }
170
171 protected boolean skipAllDownloads() {
172 return false;
173 }
174
175 protected List<String> getSkippedDownloads() {
176 return Collections.emptyList();
177 }
178
179 /**
180 * Invoked before site init is called.
181 *
182 * @param init initializer instance.
183 * @throws Exception
184 */
185 protected boolean beforeInit(SiteInit init) throws Exception {
186 return false;
187 }
188
189 /**
190 * Invoked after site init is called.
191 *
192 * @param run completed run instance.
193 * @throws Exception
194 */
195 protected void afterInit(SiteRun run) throws Exception {}
196
197 protected List<String> getInstallPlugins() {
198 try {
199 if (pluginsToInstall != null && pluginsToInstall.isEmpty()) {
200 return Collections.emptyList();
201 }
202 List<String> names = pluginsDistribution.listPluginNames();
203 if (pluginsToInstall != null) {
204 names.removeIf(n -> !pluginsToInstall.contains(n));
205 }
206 return names;
207 } catch (FileNotFoundException e) {
208 log.warn("Couldn't find distribution archive location. No plugin will be installed");
209 return null;
210 }
211 }
212
213 protected boolean installAllPlugins() {
214 return false;
215 }
216
217 protected boolean getAutoStart() {
218 return false;
219 }
220
221 public static class SiteInit {
222 public final SitePaths site;
223 final InitFlags flags;
224 final ConsoleUI ui;
225 final SitePathInitializer initializer;
226
227 @Inject
228 SiteInit(
229 final SitePaths site,
230 final InitFlags flags,
231 final ConsoleUI ui,
232 final SitePathInitializer initializer) {
233 this.site = site;
234 this.flags = flags;
235 this.ui = ui;
236 this.initializer = initializer;
237 }
238 }
239
240 private SiteInit createSiteInit() {
241 final ConsoleUI ui = getConsoleUI();
242 final Path sitePath = getSitePath();
243 final List<Module> m = new ArrayList<>();
244 final SecureStoreInitData secureStoreInitData = discoverSecureStoreClass();
245 final String currentSecureStoreClassName = getConfiguredSecureStoreClass();
246
247 if (secureStoreInitData != null
248 && currentSecureStoreClassName != null
249 && !currentSecureStoreClassName.equals(secureStoreInitData.className)) {
250 String err =
251 String.format(
252 "Different secure store was previously configured: %s. "
253 + "Use SwitchSecureStore program to switch between implementations.",
254 currentSecureStoreClassName);
255 throw die(err);
256 }
257
258 m.add(new GerritServerConfigModule());
259 m.add(new InitModule(standalone, initDb));
260 m.add(
261 new AbstractModule() {
262 @Override
263 protected void configure() {
264 bind(ConsoleUI.class).toInstance(ui);
265 bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath);
266 List<String> plugins =
267 MoreObjects.firstNonNull(getInstallPlugins(), new ArrayList<String>());
268 bind(new TypeLiteral<List<String>>() {})
269 .annotatedWith(InstallPlugins.class)
270 .toInstance(plugins);
271 bind(new TypeLiteral<Boolean>() {})
272 .annotatedWith(InstallAllPlugins.class)
273 .toInstance(installAllPlugins());
274 bind(PluginsDistribution.class).toInstance(pluginsDistribution);
275
276 String secureStoreClassName;
277 if (secureStoreInitData != null) {
278 secureStoreClassName = secureStoreInitData.className;
279 } else {
280 secureStoreClassName = currentSecureStoreClassName;
281 }
282 if (secureStoreClassName != null) {
283 ui.message("Using secure store: %s\n", secureStoreClassName);
284 }
285 bind(SecureStoreInitData.class).toProvider(Providers.of(secureStoreInitData));
286 bind(String.class)
287 .annotatedWith(SecureStoreClassName.class)
288 .toProvider(Providers.of(secureStoreClassName));
289 bind(SecureStore.class).toProvider(SecureStoreProvider.class).in(SINGLETON);
290 bind(new TypeLiteral<List<String>>() {})
291 .annotatedWith(LibraryDownload.class)
292 .toInstance(getSkippedDownloads());
293 bind(Boolean.class).annotatedWith(LibraryDownload.class).toInstance(skipAllDownloads());
294
295 bind(MetricMaker.class).to(DisabledMetricMaker.class);
296 }
297 });
298
299 try {
300 return Guice.createInjector(PRODUCTION, m).getInstance(SiteInit.class);
301 } catch (CreationException ce) {
302 final Message first = ce.getErrorMessages().iterator().next();
303 Throwable why = first.getCause();
304
305 if (why instanceof Die) {
306 throw (Die) why;
307 }
308
309 final StringBuilder buf = new StringBuilder(ce.getMessage());
310 while (why != null) {
311 buf.append("\n");
312 buf.append(why.getMessage());
313 why = why.getCause();
314 if (why != null) {
315 buf.append("\n caused by ");
316 }
317 }
318 throw die(buf.toString(), new RuntimeException("InitInjector failed", ce));
319 }
320 }
321
322 protected ConsoleUI getConsoleUI() {
323 return ConsoleUI.getInstance(false);
324 }
325
326 private SecureStoreInitData discoverSecureStoreClass() {
327 String secureStore = getSecureStoreLib();
328 if (Strings.isNullOrEmpty(secureStore)) {
329 return null;
330 }
331
332 Path secureStoreLib = Paths.get(secureStore);
333 if (!Files.exists(secureStoreLib)) {
334 throw new InvalidSecureStoreException(String.format("File %s doesn't exist", secureStore));
335 }
336 try (JarScanner scanner = new JarScanner(secureStoreLib)) {
337 List<String> secureStores = scanner.findSubClassesOf(SecureStore.class);
338 if (secureStores.isEmpty()) {
339 throw new InvalidSecureStoreException(
340 String.format(
341 "Cannot find class implementing %s interface in %s",
342 SecureStore.class.getName(), secureStore));
343 }
344 if (secureStores.size() > 1) {
345 throw new InvalidSecureStoreException(
346 String.format(
347 "%s has more that one implementation of %s interface",
348 secureStore, SecureStore.class.getName()));
349 }
350 IoUtil.loadJARs(secureStoreLib);
351 return new SecureStoreInitData(secureStoreLib, secureStores.get(0));
352 } catch (IOException e) {
353 throw new InvalidSecureStoreException(String.format("%s is not a valid jar", secureStore));
354 }
355 }
356
357 public static class SiteRun {
358 public final ConsoleUI ui;
359 public final SitePaths site;
360 public final InitFlags flags;
361 final SchemaUpdater schemaUpdater;
362 final SchemaFactory<ReviewDb> schema;
363 final GitRepositoryManager repositoryManager;
364
365 @Inject
366 SiteRun(
367 ConsoleUI ui,
368 SitePaths site,
369 InitFlags flags,
370 SchemaUpdater schemaUpdater,
371 @ReviewDbFactory SchemaFactory<ReviewDb> schema,
372 GitRepositoryManager repositoryManager) {
373 this.ui = ui;
374 this.site = site;
375 this.flags = flags;
376 this.schemaUpdater = schemaUpdater;
377 this.schema = schema;
378 this.repositoryManager = repositoryManager;
379 }
380
381 void upgradeSchema() throws OrmException {
382 final List<String> pruneList = new ArrayList<>();
383 schemaUpdater.update(
384 new UpdateUI() {
385 @Override
386 public void message(String message) {
387 System.err.println(message);
388 System.err.flush();
389 }
390
391 @Override
392 public boolean yesno(boolean defaultValue, String message) {
393 return ui.yesno(defaultValue, message);
394 }
395
396 @Override
397 public void waitForUser() {
398 ui.waitForUser();
399 }
400
401 @Override
402 public String readString(
403 String defaultValue, Set<String> allowedValues, String message) {
404 return ui.readString(defaultValue, allowedValues, message);
405 }
406
407 @Override
408 public boolean isBatch() {
409 return ui.isBatch();
410 }
411
412 @Override
413 public void pruneSchema(StatementExecutor e, List<String> prune) {
414 for (String p : prune) {
415 if (!pruneList.contains(p)) {
416 pruneList.add(p);
417 }
418 }
419 }
420 });
421
422 if (!pruneList.isEmpty()) {
423 StringBuilder msg = new StringBuilder();
424 msg.append("Execute the following SQL to drop unused objects:\n");
425 msg.append("\n");
426 for (String sql : pruneList) {
427 msg.append(" ");
428 msg.append(sql);
429 msg.append(";\n");
430 }
431
432 if (ui.isBatch()) {
433 System.err.print(msg);
434 System.err.flush();
435
436 } else if (ui.yesno(true, "%s\nExecute now", msg)) {
437 try (JdbcSchema db = (JdbcSchema) unwrapDb(schema.open());
438 JdbcExecutor e = new JdbcExecutor(db)) {
439 for (String sql : pruneList) {
440 e.execute(sql);
441 }
442 }
443 }
444 }
445 }
446 }
447
448 private SiteRun createSiteRun(SiteInit init) {
449 return createSysInjector(init).getInstance(SiteRun.class);
450 }
451
452 private Injector createSysInjector(SiteInit init) {
453 if (sysInjector == null) {
454 final List<Module> modules = new ArrayList<>();
455 modules.add(
456 new AbstractModule() {
457 @Override
458 protected void configure() {
459 bind(ConsoleUI.class).toInstance(init.ui);
460 bind(InitFlags.class).toInstance(init.flags);
461 }
462 });
463 Injector dbInjector = createDbInjector(SINGLE_USER);
464 switch (IndexModule.getIndexType(dbInjector)) {
465 case LUCENE:
466 modules.add(new LuceneIndexModuleOnInit());
467 break;
468 case ELASTICSEARCH:
469 modules.add(new ElasticIndexModuleOnInit());
470 break;
471 default:
472 throw new IllegalStateException("unsupported index.type");
473 }
474 sysInjector = dbInjector.createChildInjector(modules);
475 }
476 return sysInjector;
477 }
478
479 private static void recursiveDelete(Path path) {
480 final String msg = "warn: Cannot remove ";
481 try {
482 Files.walkFileTree(
483 path,
484 new SimpleFileVisitor<Path>() {
485 @Override
486 public FileVisitResult visitFile(Path f, BasicFileAttributes attrs) throws IOException {
487 try {
488 Files.delete(f);
489 } catch (IOException e) {
490 System.err.println(msg + f);
491 }
492 return FileVisitResult.CONTINUE;
493 }
494
495 @Override
496 public FileVisitResult postVisitDirectory(Path dir, IOException err) {
497 try {
498 // Previously warned if err was not null; if dir is not empty as a
499 // result, will cause an error that will be logged below.
500 Files.delete(dir);
501 } catch (IOException e) {
502 System.err.println(msg + dir);
503 }
504 return FileVisitResult.CONTINUE;
505 }
506
507 @Override
508 public FileVisitResult visitFileFailed(Path f, IOException e) {
509 System.err.println(msg + f);
510 return FileVisitResult.CONTINUE;
511 }
512 });
513 } catch (IOException e) {
514 System.err.println(msg + path);
515 }
516 }
517
518 protected boolean isDev() {
519 return false;
520 }
521
522 protected boolean getDeleteCaches() {
523 return false;
524 }
525}