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