diff --git a/Documentation/config-auto-site-initialization.txt b/Documentation/config-auto-site-initialization.txt new file mode 100644 index 0000000000..4c204fd178 --- /dev/null +++ b/Documentation/config-auto-site-initialization.txt @@ -0,0 +1,82 @@ +Gerrit Code Review - Automatic Site Initialization on Startup +============================================================= + +Description +----------- + +Gerrit supports automatic site initialization on server startup +when Gerrit runs in a servlet container. Both creation of a new site +and upgrade of an existing site are supported. Installation of +plugins during the site creation/initialization is not yet supported. + +This feature may be useful for such setups where Gerrit administrators +don't have direct access to the database and the file system of the +server where Gerrit should be deployed and, therefore, cannot perform +the init from their local machine prior to deploying Gerrit on such a +server. It may also make deployment and testing in a local servlet +container faster to setup as the init step could be skipped. + +Gerrit Configuration +-------------------- + +The site initialization will be performed only if the `gerrit.init` +system property exists (the value of the property is not used, only the +existence of the property matters). + +If the `gerrit.site_path` system property is defined then the init is +run for that site. The database connectivity, in that case, is defined +in the `etc/gerrit.config`. + +If `gerrit.site_path` is not defined then Gerrit will try to find an +existing site by looking into the `system_config` table in the database +defined via the `jdbc/ReviewDb` JNDI property. If the `system_config` +table exists then the `site_path` from that table is used for the +initialization. The database connectivity is defined by the +`jdbc/ReviewDb` JNDI property. + +Finally, if neither the `gerrit.site_path` property nor the +`system_config` table exists, the `gerrit.init_path` system property, +if defined, will be used to determine the site path. The database +connectivity, also for this case, is defined by the `jdbc/ReviewDb` +JNDI property. + +Example 1 +~~~~~~~~~ + +Prepare Tomcat so that a site is initialized at a given path using +the H2 database (if the site doesn't exist yet) or using whatever +database is defined in `etc/gerrit.config` of that site: + +---- + $ export CATALINA_OPTS='-Dgerrit.init -Dgerrit.site_path=/path/to/site' + $ catalina.sh start +---- + +Example 2 +~~~~~~~~~ + +Prepare Tomcat so that an existing site with the path defined in the +`system_config` table is initialized (upgraded) on Gerrit startup. The +assumption is that the `jdbc/ReviewDb` JNDI property is defined in +Tomcat: + +---- + $ export CATALINA_OPTS='-Dgerrit.init' + $ catalina.sh start +---- + +Example 3 +~~~~~~~~~ + +Assuming the database schema doesn't exist in the database defined +via the `jdbc/ReviewDb` JNDI property, initialize a new site using that +database and a given path: + +---- + $ export CATALINA_OPTS='-Dgerrit.init -Dgerrit.init_path=/path/to/site' + $ catalina.sh start +---- + +GERRIT +------ +Part of link:index.html[Gerrit Code Review] diff --git a/Documentation/index.txt b/Documentation/index.txt index 28e0184b58..ee768555ec 100644 --- a/Documentation/index.txt +++ b/Documentation/index.txt @@ -62,6 +62,7 @@ Index .. link:dev-design.html[System Design] .. link:config-contact.html[User Contact Information] .. link:config-reverseproxy.html[Reverse Proxy] +.. link:config-auto-site-initialization.html[Automatic Site Initialization on Startup] .. link:pgm-index.html[Server Side Administrative Tools] . Developer .. link:dev-readme.html[Developer Setup] diff --git a/Documentation/install-j2ee.txt b/Documentation/install-j2ee.txt index f6853c81b9..5ba8cb147f 100644 --- a/Documentation/install-j2ee.txt +++ b/Documentation/install-j2ee.txt @@ -46,6 +46,9 @@ If you enabled Bouncy Castle Crypto during 'init', copy the JAR from `'$site_path'/lib` into your servlet container's extensions directory so it's available to Gerrit Code Review. +* ('Optional') link:config-auto-site-initialization.html[ +Configure Automatic Site Initialization on Startup] + Jetty 7.x --------- diff --git a/Documentation/install.txt b/Documentation/install.txt index 8137f53773..d80b197add 100644 --- a/Documentation/install.txt +++ b/Documentation/install.txt @@ -146,6 +146,7 @@ For more information, see the related topics in this manual: * link:config-themes.html[Themes] * link:config-gitweb.html[Gitweb Integration] * link:config-gerrit.html[Other System Settings] +* link:config-auto-site-initialization.html[Automatic Site Initialization on Startup] [[anonymous_access]] diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK index 2a88694f55..38480bc276 100644 --- a/gerrit-pgm/BUCK +++ b/gerrit-pgm/BUCK @@ -1,4 +1,6 @@ -INIT_SRCS = ['src/main/java/com/google/gerrit/pgm/' + n for n in [ +SRCS = 'src/main/java/com/google/gerrit/pgm/' + +INIT_API_SRCS = [SRCS + n for n in [ 'init/InitFlags.java', 'init/InitStep.java', 'init/InitStep.java', @@ -10,7 +12,7 @@ INIT_SRCS = ['src/main/java/com/google/gerrit/pgm/' + n for n in [ java_library( name = 'init-api', - srcs = INIT_SRCS, + srcs = INIT_API_SRCS, deps = [ '//gerrit-server:server', '//lib:jsr305', @@ -23,16 +25,65 @@ java_library( java_sources( name = 'init-api-src', - srcs = INIT_SRCS, + srcs = INIT_API_SRCS, visibility = ['PUBLIC'], ) +INIT_BASE_SRCS = [SRCS + 'BaseInit.java'] + glob( + [SRCS + n for n in [ + 'init/**/*.java', + 'util/**/*.java', + ]], + excludes = INIT_API_SRCS + + [SRCS + n for n in [ + 'init/Browser.java', + 'util/ErrorLogFile.java', + 'util/GarbageCollectionLogFile.java', + 'util/LogFileCompressor.java', + 'util/RuntimeShutdown.java', + ]] + ) + +INIT_BASE_RSRCS = ['src/main/resources/com/google/gerrit/pgm/libraries.config'] + +java_library2( + name = 'init-base', + srcs = INIT_BASE_SRCS, + resources = INIT_BASE_RSRCS, + deps = [ + ':init-api', + '//gerrit-common:server', + '//gerrit-extension-api:api', + '//gerrit-reviewdb:server', + '//gerrit-server:server', + '//gerrit-util-cli:cli', + '//lib/commons:dbcp', + '//lib/guice:guice', + '//lib/guice:guice-assistedinject', + '//lib/jgit:jgit', + '//lib/mina:sshd', + '//lib:args4j', + '//lib:guava', + '//lib:gwtjsonrpc', + '//lib:gwtorm', + '//lib:jsr305', + ], + compile_deps = ['//gerrit-launcher:launcher'], + visibility = ['//gerrit-war:'], +) + java_library2( name = 'pgm', - srcs = glob(['src/main/java/**/*.java'], excludes = INIT_SRCS), - resources = glob(['src/main/resources/**/*']), + srcs = glob( + ['src/main/java/**/*.java'], + excludes = INIT_API_SRCS + INIT_BASE_SRCS + ), + resources = glob( + ['src/main/resources/**/*'], + excludes = INIT_BASE_RSRCS), deps = [ ':init-api', + ':init-base', '//gerrit-cache-h2:cache-h2', '//gerrit-common:server', '//gerrit-extension-api:api', @@ -49,13 +100,9 @@ java_library2( '//lib:args4j', '//lib:guava', '//lib:gwtorm', - '//lib:gwtjsonrpc', '//lib:h2', - '//lib:jsr305', '//lib:servlet-api-3_0', - '//lib/commons:dbcp', '//lib/guice:guice', - '//lib/guice:guice-assistedinject', '//lib/guice:guice-servlet', '//lib/jetty:server', '//lib/jetty:servlet', @@ -63,7 +110,6 @@ java_library2( '//lib/log:api', '//lib/log:log4j', '//lib/lucene:core', - '//lib/mina:sshd', '//lib/prolog:prolog-cafe', ], compile_deps = ['//gerrit-launcher:launcher'], @@ -80,6 +126,7 @@ java_test( srcs = glob(['src/test/java/**/*.java']), deps = [ ':init-api', + ':init-base', ':pgm', '//gerrit-server:server', '//lib:junit', diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java new file mode 100644 index 0000000000..b830959154 --- /dev/null +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/BaseInit.java @@ -0,0 +1,291 @@ +// Copyright (C) 2013 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.pgm; + +import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER; +import static com.google.inject.Stage.PRODUCTION; + +import com.google.common.base.Objects; +import com.google.common.collect.Lists; +import com.google.gerrit.pgm.init.InitFlags; +import com.google.gerrit.pgm.init.InitModule; +import com.google.gerrit.pgm.init.InstallPlugins; +import com.google.gerrit.pgm.init.SitePathInitializer; +import com.google.gerrit.pgm.util.ConsoleUI; +import com.google.gerrit.pgm.util.Die; +import com.google.gerrit.pgm.util.SiteProgram; +import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.config.SitePath; +import com.google.gerrit.server.config.SitePaths; +import com.google.gerrit.server.git.GitRepositoryManager; +import com.google.gerrit.server.schema.SchemaUpdater; +import com.google.gerrit.server.schema.UpdateUI; +import com.google.gwtorm.jdbc.JdbcExecutor; +import com.google.gwtorm.jdbc.JdbcSchema; +import com.google.gwtorm.server.OrmException; +import com.google.gwtorm.server.SchemaFactory; +import com.google.gwtorm.server.StatementExecutor; +import com.google.inject.AbstractModule; +import com.google.inject.CreationException; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; +import com.google.inject.spi.Message; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import javax.sql.DataSource; + +/** Initialize a new Gerrit installation. */ +public class BaseInit extends SiteProgram { + + private final boolean standalone; + + public BaseInit() { + this.standalone = true; + } + + public BaseInit(File sitePath, boolean standalone) { + this(sitePath, null, standalone); + } + + public BaseInit(File sitePath, final Provider dsProvider, + boolean standalone) { + super(sitePath, dsProvider); + this.standalone = standalone; + } + + @Override + public int run() throws Exception { + final SiteInit init = createSiteInit(); + beforeInit(init); + + init.flags.autoStart = getAutoStart() && init.site.isNew;; + + final SiteRun run; + try { + init.initializer.run(); + init.flags.deleteOnFailure = false; + + run = createSiteRun(init); + run.upgradeSchema(); + } catch (Exception failure) { + if (init.flags.deleteOnFailure) { + recursiveDelete(getSitePath()); + } + throw failure; + } catch (Error failure) { + if (init.flags.deleteOnFailure) { + recursiveDelete(getSitePath()); + } + throw failure; + } + + System.err.println("Initialized " + getSitePath().getCanonicalPath()); + afterInit(run); + return 0; + } + + protected void beforeInit(SiteInit init) throws Exception { + } + + protected void afterInit(SiteRun run) throws Exception { + } + + protected List getInstallPlugins() { + return null; + } + + protected boolean getAutoStart() { + return false; + } + + static class SiteInit { + final SitePaths site; + final InitFlags flags; + final ConsoleUI ui; + final SitePathInitializer initializer; + + @Inject + SiteInit(final SitePaths site, final InitFlags flags, final ConsoleUI ui, + final SitePathInitializer initializer) { + this.site = site; + this.flags = flags; + this.ui = ui; + this.initializer = initializer; + } + } + + private SiteInit createSiteInit() { + final ConsoleUI ui = getConsoleUI(); + final File sitePath = getSitePath(); + final List m = new ArrayList(); + + m.add(new InitModule(standalone)); + m.add(new AbstractModule() { + @Override + protected void configure() { + bind(ConsoleUI.class).toInstance(ui); + bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath); + List plugins = + Objects.firstNonNull(getInstallPlugins(), Lists. newArrayList()); + bind(new TypeLiteral>() {}).annotatedWith( + InstallPlugins.class).toInstance(plugins); + } + }); + + try { + return Guice.createInjector(PRODUCTION, m).getInstance(SiteInit.class); + } catch (CreationException ce) { + final Message first = ce.getErrorMessages().iterator().next(); + Throwable why = first.getCause(); + + if (why instanceof Die) { + throw (Die) why; + } + + final StringBuilder buf = new StringBuilder(ce.getMessage()); + while (why != null) { + buf.append("\n"); + buf.append(why.getMessage()); + why = why.getCause(); + if (why != null) { + buf.append("\n caused by "); + } + } + throw die(buf.toString(), new RuntimeException("InitInjector failed", ce)); + } + } + + protected ConsoleUI getConsoleUI() { + return ConsoleUI.getInstance(false); + } + + static class SiteRun { + final ConsoleUI ui; + final SitePaths site; + final InitFlags flags; + final SchemaUpdater schemaUpdater; + final SchemaFactory schema; + final GitRepositoryManager repositoryManager; + + @Inject + SiteRun(final ConsoleUI ui, final SitePaths site, final InitFlags flags, + final SchemaUpdater schemaUpdater, + final SchemaFactory schema, + final GitRepositoryManager repositoryManager) { + this.ui = ui; + this.site = site; + this.flags = flags; + this.schemaUpdater = schemaUpdater; + this.schema = schema; + this.repositoryManager = repositoryManager; + } + + void upgradeSchema() throws OrmException { + final List pruneList = new ArrayList(); + schemaUpdater.update(new UpdateUI() { + @Override + public void message(String msg) { + System.err.println(msg); + System.err.flush(); + } + + @Override + public boolean yesno(boolean def, String msg) { + return ui.yesno(def, msg); + } + + @Override + public boolean isBatch() { + return ui.isBatch(); + } + + @Override + public void pruneSchema(StatementExecutor e, List prune) { + for (String p : prune) { + if (!pruneList.contains(p)) { + pruneList.add(p); + } + } + } + }); + + if (!pruneList.isEmpty()) { + StringBuilder msg = new StringBuilder(); + msg.append("Execute the following SQL to drop unused objects:\n"); + msg.append("\n"); + for (String sql : pruneList) { + msg.append(" "); + msg.append(sql); + msg.append(";\n"); + } + + if (ui.isBatch()) { + System.err.print(msg); + System.err.flush(); + + } else if (ui.yesno(true, "%s\nExecute now", msg)) { + final JdbcSchema db = (JdbcSchema) schema.open(); + try { + final JdbcExecutor e = new JdbcExecutor(db); + try { + for (String sql : pruneList) { + e.execute(sql); + } + } finally { + e.close(); + } + } finally { + db.close(); + } + } + } + } + } + + private SiteRun createSiteRun(final SiteInit init) { + return createSysInjector(init).getInstance(SiteRun.class); + } + + private Injector createSysInjector(final SiteInit init) { + final List modules = new ArrayList(); + modules.add(new AbstractModule() { + @Override + protected void configure() { + bind(ConsoleUI.class).toInstance(init.ui); + bind(InitFlags.class).toInstance(init.flags); + } + }); + return createDbInjector(SINGLE_USER).createChildInjector(modules); + } + + private static void recursiveDelete(File path) { + File[] entries = path.listFiles(); + if (entries != null) { + for (File e : entries) { + recursiveDelete(e); + } + } + if (!path.delete() && path.exists()) { + System.err.println("warn: Cannot remove " + path); + } + } +} diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java index ae1ab71b40..1cab5152d2 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java @@ -14,56 +14,36 @@ package com.google.gerrit.pgm; -import static com.google.gerrit.server.schema.DataSourceProvider.Context.SINGLE_USER; -import static com.google.inject.Stage.PRODUCTION; - import com.google.common.base.Function; import com.google.common.base.Joiner; -import com.google.common.base.Objects; import com.google.common.collect.Lists; import com.google.gerrit.common.PageLinks; import com.google.gerrit.pgm.init.Browser; -import com.google.gerrit.pgm.init.InitFlags; -import com.google.gerrit.pgm.init.InitModule; import com.google.gerrit.pgm.init.InitPlugins; import com.google.gerrit.pgm.init.InitPlugins.PluginData; -import com.google.gerrit.pgm.init.InstallPlugins; -import com.google.gerrit.pgm.init.SitePathInitializer; import com.google.gerrit.pgm.util.ConsoleUI; -import com.google.gerrit.pgm.util.Die; import com.google.gerrit.pgm.util.ErrorLogFile; import com.google.gerrit.pgm.util.IoUtil; -import com.google.gerrit.pgm.util.SiteProgram; -import com.google.gerrit.reviewdb.server.ReviewDb; +import com.google.gerrit.server.config.GerritServerConfigModule; import com.google.gerrit.server.config.SitePath; -import com.google.gerrit.server.config.SitePaths; -import com.google.gerrit.server.git.GitRepositoryManager; -import com.google.gerrit.server.schema.SchemaUpdater; -import com.google.gerrit.server.schema.UpdateUI; import com.google.gerrit.server.util.HostPlatform; -import com.google.gwtorm.jdbc.JdbcExecutor; -import com.google.gwtorm.jdbc.JdbcSchema; -import com.google.gwtorm.server.OrmException; -import com.google.gwtorm.server.SchemaFactory; -import com.google.gwtorm.server.StatementExecutor; import com.google.inject.AbstractModule; -import com.google.inject.CreationException; import com.google.inject.Guice; import com.google.inject.Inject; -import com.google.inject.Injector; import com.google.inject.Module; -import com.google.inject.TypeLiteral; -import com.google.inject.spi.Message; +import com.google.inject.Provider; import org.kohsuke.args4j.Option; import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.List; +import java.util.ArrayList; + +import javax.sql.DataSource; /** Initialize a new Gerrit installation. */ -public class Init extends SiteProgram { +public class Init extends BaseInit { @Option(name = "--batch", usage = "Batch mode; skip interactive prompting") private boolean batchMode; @@ -79,20 +59,26 @@ public class Init extends SiteProgram { @Option(name = "--install-plugin", usage = "Install given plugin without asking", multiValued = true) private List installPlugins; + @Inject + Browser browser; + public Init() { } public Init(File sitePath) { - super(sitePath); + this(sitePath, null); + } + + public Init(File sitePath, final Provider dsProvider) { + super(sitePath, dsProvider, true); batchMode = true; noAutoStart = true; } @Override - public int run() throws Exception { + protected void beforeInit(SiteInit init) throws Exception { ErrorLogFile.errorOnlyConsole(); - final SiteInit init = createSiteInit(); if (!skipPlugins) { final List plugins = InitPlugins.listPlugins(init.site); ConsoleUI ui = ConsoleUI.getInstance(false); @@ -106,252 +92,83 @@ public class Init extends SiteProgram { } else { ui.message("No plugins found.\n"); } - return 0; } } - - init.flags.autoStart = !noAutoStart && init.site.isNew; - init.flags.skipPlugins = skipPlugins; - - final SiteRun run; - try { - init.initializer.run(); - init.flags.deleteOnFailure = false; - - run = createSiteRun(init); - run.upgradeSchema(); - } catch (Exception failure) { - if (init.flags.deleteOnFailure) { - recursiveDelete(getSitePath()); - } - throw failure; - } catch (Error failure) { - if (init.flags.deleteOnFailure) { - recursiveDelete(getSitePath()); - } - throw failure; - } - - System.err.println("Initialized " + getSitePath().getCanonicalPath()); - run.start(); - return 0; - } - - static class SiteInit { - final SitePaths site; - final InitFlags flags; - final ConsoleUI ui; - final SitePathInitializer initializer; - - @Inject - SiteInit(final SitePaths site, final InitFlags flags, final ConsoleUI ui, - final SitePathInitializer initializer) { - this.site = site; - this.flags = flags; - this.ui = ui; - this.initializer = initializer; - } } - private SiteInit createSiteInit() { - final ConsoleUI ui = ConsoleUI.getInstance(batchMode); - final File sitePath = getSitePath(); - final List m = new ArrayList(); - - m.add(new InitModule()); - m.add(new AbstractModule() { - @Override - protected void configure() { - bind(ConsoleUI.class).toInstance(ui); - bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath); - List plugins = - Objects.firstNonNull(installPlugins, Lists. newArrayList()); - bind(new TypeLiteral>() {}).annotatedWith( - InstallPlugins.class).toInstance(plugins); - } - }); - - try { - return Guice.createInjector(PRODUCTION, m).getInstance(SiteInit.class); - } catch (CreationException ce) { - final Message first = ce.getErrorMessages().iterator().next(); - Throwable why = first.getCause(); - - if (why instanceof Die) { - throw (Die) why; - } - - final StringBuilder buf = new StringBuilder(ce.getMessage()); - while (why != null) { - buf.append("\n"); - buf.append(why.getMessage()); - why = why.getCause(); - if (why != null) { - buf.append("\n caused by "); - } - } - throw die(buf.toString(), new RuntimeException("InitInjector failed", ce)); - } - } - - static class SiteRun { - final ConsoleUI ui; - final SitePaths site; - final InitFlags flags; - final SchemaUpdater schemaUpdater; - final SchemaFactory schema; - final GitRepositoryManager repositoryManager; - final Browser browser; - - @Inject - SiteRun(final ConsoleUI ui, final SitePaths site, final InitFlags flags, - final SchemaUpdater schemaUpdater, - final SchemaFactory schema, - final GitRepositoryManager repositoryManager, - final Browser browser) { - this.ui = ui; - this.site = site; - this.flags = flags; - this.schemaUpdater = schemaUpdater; - this.schema = schema; - this.repositoryManager = repositoryManager; - this.browser = browser; - } - - void upgradeSchema() throws OrmException { - final List pruneList = new ArrayList(); - schemaUpdater.update(new UpdateUI() { - @Override - public void message(String msg) { - System.err.println(msg); - System.err.flush(); - } - - @Override - public boolean yesno(boolean def, String msg) { - return ui.yesno(def, msg); - } - - @Override - public boolean isBatch() { - return ui.isBatch(); - } - - @Override - public void pruneSchema(StatementExecutor e, List prune) { - for (String p : prune) { - if (!pruneList.contains(p)) { - pruneList.add(p); - } - } - } - }); - - if (!pruneList.isEmpty()) { - StringBuilder msg = new StringBuilder(); - msg.append("Execute the following SQL to drop unused objects:\n"); - msg.append("\n"); - for (String sql : pruneList) { - msg.append(" "); - msg.append(sql); - msg.append(";\n"); - } - - if (ui.isBatch()) { - System.err.print(msg); - System.err.flush(); - - } else if (ui.yesno(true, "%s\nExecute now", msg)) { - final JdbcSchema db = (JdbcSchema) schema.open(); - try { - final JdbcExecutor e = new JdbcExecutor(db); - try { - for (String sql : pruneList) { - e.execute(sql); - } - } finally { - e.close(); - } - } finally { - db.close(); - } - } - } - } - - void start() throws Exception { - if (flags.autoStart) { - if (HostPlatform.isWin32()) { - System.err.println("Automatic startup not supported on Win32."); - - } else { - startDaemon(); - if (!ui.isBatch()) { - browser.open(PageLinks.ADMIN_PROJECTS); - } - } - } - } - - void startDaemon() { - final String[] argv = {site.gerrit_sh.getAbsolutePath(), "start"}; - final Process proc; - try { - System.err.println("Executing " + argv[0] + " " + argv[1]); - proc = Runtime.getRuntime().exec(argv); - } catch (IOException e) { - System.err.println("error: cannot start Gerrit: " + e.getMessage()); - return; - } - - try { - proc.getOutputStream().close(); - } catch (IOException e) { - } - - IoUtil.copyWithThread(proc.getInputStream(), System.err); - IoUtil.copyWithThread(proc.getErrorStream(), System.err); - - for (;;) { - try { - final int rc = proc.waitFor(); - if (rc != 0) { - System.err.println("error: cannot start Gerrit: exit status " + rc); - } - break; - } catch (InterruptedException e) { - // retry - } - } - } - - } - - private SiteRun createSiteRun(final SiteInit init) { - return createSysInjector(init).getInstance(SiteRun.class); - } - - private Injector createSysInjector(final SiteInit init) { - final List modules = new ArrayList(); + @Override + protected void afterInit(SiteRun run) throws Exception { + List modules = Lists.newArrayList(); modules.add(new AbstractModule() { @Override protected void configure() { - bind(ConsoleUI.class).toInstance(init.ui); - bind(InitFlags.class).toInstance(init.flags); + bind(File.class).annotatedWith(SitePath.class).toInstance(getSitePath()); + bind(Browser.class); } }); - return createDbInjector(SINGLE_USER).createChildInjector(modules); + modules.add(new GerritServerConfigModule()); + Guice.createInjector(modules).injectMembers(this); + start(run); } - private static void recursiveDelete(File path) { - File[] entries = path.listFiles(); - if (entries != null) { - for (File e : entries) { - recursiveDelete(e); + @Override + protected List getInstallPlugins() { + return installPlugins; + } + + @Override + protected ConsoleUI getConsoleUI() { + return ConsoleUI.getInstance(batchMode); + } + + @Override + protected boolean getAutoStart() { + return !noAutoStart; + } + + void start(SiteRun run) throws Exception { + if (run.flags.autoStart) { + if (HostPlatform.isWin32()) { + System.err.println("Automatic startup not supported on Win32."); + + } else { + startDaemon(run); + if (!run.ui.isBatch()) { + browser.open(PageLinks.ADMIN_PROJECTS); + } } } - if (!path.delete() && path.exists()) { - System.err.println("warn: Cannot remove " + path); + } + + void startDaemon(SiteRun run) { + final String[] argv = {run.site.gerrit_sh.getAbsolutePath(), "start"}; + final Process proc; + try { + System.err.println("Executing " + argv[0] + " " + argv[1]); + proc = Runtime.getRuntime().exec(argv); + } catch (IOException e) { + System.err.println("error: cannot start Gerrit: " + e.getMessage()); + return; + } + + try { + proc.getOutputStream().close(); + } catch (IOException e) { + } + + IoUtil.copyWithThread(proc.getInputStream(), System.err); + IoUtil.copyWithThread(proc.getErrorStream(), System.err); + + for (;;) { + try { + final int rc = proc.waitFor(); + if (rc != 0) { + System.err.println("error: cannot start Gerrit: exit status " + rc); + } + break; + } catch (InterruptedException e) { + // retry + } } } diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java index 8b3d87ea8c..94185eb98a 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitModule.java @@ -23,6 +23,13 @@ import java.lang.annotation.Annotation; /** Injection configuration for the site initialization process. */ public class InitModule extends FactoryModule { + + private final boolean standalone; + + public InitModule(boolean standalone) { + this.standalone = standalone; + } + @Override protected void configure() { bind(SitePaths.class); @@ -36,14 +43,20 @@ public class InitModule extends FactoryModule { step().to(UpgradeFrom2_0_x.class); step().to(InitGitManager.class); - step().to(InitDatabase.class); + if (standalone) { + step().to(InitDatabase.class); + } step().to(InitAuth.class); step().to(InitSendEmail.class); - step().to(InitContainer.class); + if (standalone) { + step().to(InitContainer.class); + } step().to(InitSshd.class); step().to(InitHttpd.class); step().to(InitCache.class); - step().to(InitPlugins.class); + if (standalone) { + step().to(InitPlugins.class); + } } protected LinkedBindingBuilder step() { diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java index 1c2d024558..71a4d86ce6 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java @@ -22,7 +22,7 @@ import static com.google.gerrit.pgm.init.InitUtil.savePublic; import static com.google.gerrit.pgm.init.InitUtil.saveSecure; import static com.google.gerrit.pgm.init.InitUtil.version; -import com.google.gerrit.pgm.Init; +import com.google.gerrit.pgm.BaseInit; import com.google.gerrit.pgm.util.ConsoleUI; import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.mail.OutgoingEmail; @@ -85,7 +85,7 @@ public class SitePathInitializer { savePublic(flags.cfg); saveSecure(flags.sec); - extract(site.gerrit_sh, Init.class, "gerrit.sh"); + extract(site.gerrit_sh, BaseInit.class, "gerrit.sh"); chmod(0755, site.gerrit_sh); chmod(0700, site.tmp_dir); diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java index f4f0bd276c..11968db405 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java @@ -17,6 +17,8 @@ package com.google.gerrit.pgm.util; import static com.google.inject.Scopes.SINGLETON; import static com.google.inject.Stage.PRODUCTION; +import com.google.common.collect.Lists; +import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.lifecycle.LifecycleModule; import com.google.gerrit.server.config.GerritServerConfig; import com.google.gerrit.server.config.GerritServerConfigModule; @@ -29,11 +31,15 @@ import com.google.gerrit.server.schema.DatabaseModule; import com.google.gerrit.server.schema.SchemaModule; import com.google.gwtorm.server.OrmException; import com.google.inject.AbstractModule; +import com.google.inject.Binding; import com.google.inject.CreationException; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; +import com.google.inject.Provider; +import com.google.inject.TypeLiteral; +import com.google.inject.name.Named; import com.google.inject.name.Names; import com.google.inject.spi.Message; @@ -41,6 +47,8 @@ import org.eclipse.jgit.lib.Config; import org.kohsuke.args4j.Option; import java.io.File; +import java.lang.annotation.Annotation; +import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -51,11 +59,14 @@ public abstract class SiteProgram extends AbstractProgram { @Option(name = "--site-path", aliases = {"-d"}, usage = "Local directory containing site data") private File sitePath = new File("."); + protected Provider dsProvider; + protected SiteProgram() { } - protected SiteProgram(File sitePath) { + protected SiteProgram(File sitePath, final Provider dsProvider) { this.sitePath = sitePath; + this.dsProvider = dsProvider; } /** @return the site path specified on the command line. */ @@ -92,17 +103,31 @@ public abstract class SiteProgram extends AbstractProgram { @Override protected void configure() { bind(DataSourceProvider.Context.class).toInstance(context); - bind(Key.get(DataSource.class, Names.named("ReviewDb"))) - .toProvider(SiteLibraryBasedDataSourceProvider.class) - .in(SINGLETON); - listener().to(SiteLibraryBasedDataSourceProvider.class); + if (dsProvider != null) { + bind(Key.get(DataSource.class, Names.named("ReviewDb"))) + .toProvider(dsProvider) + .in(SINGLETON); + if (LifecycleListener.class.isAssignableFrom(dsProvider.getClass())) { + listener().toInstance((LifecycleListener) dsProvider); + } + } else { + bind(Key.get(DataSource.class, Names.named("ReviewDb"))) + .toProvider(SiteLibraryBasedDataSourceProvider.class) + .in(SINGLETON); + listener().to(SiteLibraryBasedDataSourceProvider.class); + } } }); Module configModule = new GerritServerConfigModule(); modules.add(configModule); Injector cfgInjector = Guice.createInjector(sitePathModule, configModule); Config cfg = cfgInjector.getInstance(Key.get(Config.class, GerritServerConfig.class)); - String dbType = cfg.getString("database", null, "type"); + String dbType; + if (dsProvider != null) { + dbType = getDbType(dsProvider); + } else { + dbType = cfg.getString("database", null, "type"); + } final DataSourceType dst = Guice.createInjector(new DataSourceModule(), configModule, sitePathModule).getInstance( @@ -151,6 +176,44 @@ public abstract class SiteProgram extends AbstractProgram { } } + private String getDbType(Provider dsProvider) { + String dbProductName; + try { + Connection conn = dsProvider.get().getConnection(); + try { + dbProductName = conn.getMetaData().getDatabaseProductName().toLowerCase(); + } finally { + conn.close(); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + + List modules = Lists.newArrayList(); + modules.add(new AbstractModule() { + @Override + protected void configure() { + bind(File.class).annotatedWith(SitePath.class).toInstance(sitePath); + } + }); + modules.add(new GerritServerConfigModule()); + modules.add(new DataSourceModule()); + Injector i = Guice.createInjector(modules); + List> dsTypeBindings = + i.findBindingsByType(new TypeLiteral() {}); + for (Binding binding : dsTypeBindings) { + Annotation annotation = binding.getKey().getAnnotation(); + if (annotation instanceof Named) { + if (((Named) annotation).value().toLowerCase().contains(dbProductName)) { + return ((Named) annotation).value(); + } + } + } + throw new IllegalStateException(String.format( + "Cannot guess database type from the database product name '%s'", + dbProductName)); + } + @SuppressWarnings("deprecation") private static boolean isCannotCreatePoolException(Throwable why) { return why instanceof org.apache.commons.dbcp.SQLNestedException diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK index 03461b8a27..fa9a88f7e5 100644 --- a/gerrit-war/BUCK +++ b/gerrit-war/BUCK @@ -9,11 +9,13 @@ java_library2( '//gerrit-httpd:httpd', '//gerrit-lucene:lucene', '//gerrit-openid:openid', + '//gerrit-pgm:init-base', '//gerrit-reviewdb:server', '//gerrit-server:server', '//gerrit-server/src/main/prolog:common', '//gerrit-solr:solr', '//gerrit-sshd:sshd', + '//lib:guava', '//lib:gwtorm', '//lib/guice:guice', '//lib/guice:guice-servlet', diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java new file mode 100644 index 0000000000..0948c46a08 --- /dev/null +++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/SiteInitializer.java @@ -0,0 +1,87 @@ +// Copyright (C) 2013 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.httpd; + +import com.google.gerrit.pgm.BaseInit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; + +public final class SiteInitializer { + private static final Logger log = LoggerFactory + .getLogger(SiteInitializer.class); + + final String sitePath; + final String initPath; + + SiteInitializer(String sitePath, String initPath) { + this.sitePath = sitePath; + this.initPath = initPath; + } + + public void init() { + try { + + if (sitePath != null) { + File site = new File(sitePath); + log.info(String.format("Initializing site at %s", + site.getAbsolutePath())); + new BaseInit(site, false).run(); + return; + } + + Connection conn = connectToDb(); + try { + File site = getSiteFromReviewDb(conn); + if (site == null && initPath != null) { + site = new File(initPath); + } + + if (site != null) { + log.info(String.format("Initializing site at %s", + site.getAbsolutePath())); + new BaseInit(site, new ReviewDbDataSourceProvider(), false).run(); + } + } finally { + conn.close(); + } + } catch (Exception e) { + log.error("Site init failed", e); + throw new RuntimeException(e); + } + } + + private Connection connectToDb() throws SQLException { + return new ReviewDbDataSourceProvider().get().getConnection(); + } + + private File getSiteFromReviewDb(Connection conn) { + try { + ResultSet rs = conn.createStatement().executeQuery( + "select site_path from system_config"); + if (rs.next()) { + return new File(rs.getString(1)); + } + return null; + } catch (SQLException e) { + return null; + } + } +} diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java index 3c70cf61e3..bccd0decb5 100644 --- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java +++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java @@ -115,6 +115,10 @@ public class WebAppInitializer extends GuiceServletContextListener sitePath = new File(path); } + if (System.getProperty("gerrit.init") != null) { + new SiteInitializer(path, System.getProperty("gerrit.init_path")).init(); + } + try { dbInjector = createDbInjector(); } catch (CreationException ce) {