blob: 9d8674fa41e6c1cf7426684788dc77d2ee93c049 [file] [log] [blame]
// 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.acceptance;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import static java.util.Objects.requireNonNull;
import com.google.auto.value.AutoValue;
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.base.Ticker;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest.TestTicker;
import com.google.gerrit.acceptance.FakeGroupAuditService.FakeGroupAuditServiceModule;
import com.google.gerrit.acceptance.GrantDirectPushPermissionsOnStartup.GrantDirectPushPermissionsOnStartupModule;
import com.google.gerrit.acceptance.ReindexGroupsAtStartup.ReindexGroupsAtStartupModule;
import com.google.gerrit.acceptance.ReindexProjectsAtStartup.ReindexProjectsAtStartupModule;
import com.google.gerrit.acceptance.config.ConfigAnnotationParser;
import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.config.GerritConfigs;
import com.google.gerrit.acceptance.config.GerritSystemProperties;
import com.google.gerrit.acceptance.config.GerritSystemProperty;
import com.google.gerrit.acceptance.config.GlobalPluginConfig;
import com.google.gerrit.acceptance.config.GlobalPluginConfigs;
import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
import com.google.gerrit.acceptance.testsuite.account.AccountOperationsImpl;
import com.google.gerrit.acceptance.testsuite.change.ChangeOperations;
import com.google.gerrit.acceptance.testsuite.change.ChangeOperationsImpl;
import com.google.gerrit.acceptance.testsuite.change.PerCommentOperationsImpl;
import com.google.gerrit.acceptance.testsuite.change.PerDraftCommentOperationsImpl;
import com.google.gerrit.acceptance.testsuite.change.PerPatchsetOperationsImpl;
import com.google.gerrit.acceptance.testsuite.change.PerRobotCommentOperationsImpl;
import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
import com.google.gerrit.acceptance.testsuite.group.GroupOperationsImpl;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperationsImpl;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperationsImpl;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.config.FactoryModule;
import com.google.gerrit.index.IndexType;
import com.google.gerrit.index.testing.FakeIndexModule;
import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.pgm.Daemon;
import com.google.gerrit.pgm.Init;
import com.google.gerrit.server.config.GerritRuntime;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.experiments.ConfigExperimentFeatures.ConfigExperimentFeaturesModule;
import com.google.gerrit.server.git.receive.AsyncReceiveCommits.AsyncReceiveCommitsModule;
import com.google.gerrit.server.git.validators.CommitValidationListener;
import com.google.gerrit.server.index.AbstractIndexModule;
import com.google.gerrit.server.index.options.AutoFlush;
import com.google.gerrit.server.schema.JdbcAccountPatchReviewStore;
import com.google.gerrit.server.ssh.NoSshModule;
import com.google.gerrit.server.util.ReplicaUtil;
import com.google.gerrit.server.util.SocketUtil;
import com.google.gerrit.server.util.SystemLog;
import com.google.gerrit.testing.FakeAccountPatchReviewStore.FakeAccountPatchReviewStoreModule;
import com.google.gerrit.testing.FakeEmailSender.FakeEmailSenderModule;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import com.google.gerrit.testing.SshMode;
import com.google.gerrit.testing.TestLoggingActivator;
import com.google.inject.AbstractModule;
import com.google.inject.BindingAnnotation;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.google.inject.multibindings.OptionalBinder;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.RepositoryCache;
import org.eclipse.jgit.util.FS;
import org.junit.rules.TemporaryFolder;
public class GerritServer implements AutoCloseable {
public static class StartupException extends Exception {
private static final long serialVersionUID = 1L;
StartupException(String msg, Throwable cause) {
super(msg, cause);
}
}
/** Marker on {@link InetSocketAddress} for test SSH server. */
@Retention(RUNTIME)
@BindingAnnotation
public @interface TestSshServerAddress {}
@AutoValue
public abstract static class Description {
public static Description forTestClass(
org.junit.runner.Description testDesc, String configName) {
VerifyNoPiiInChangeNotes verifyNoPiiInChangeNotes =
get(VerifyNoPiiInChangeNotes.class, testDesc.getTestClass());
return new AutoValue_GerritServer_Description(
testDesc,
configName,
!has(UseLocalDisk.class, testDesc.getTestClass()) && !forceLocalDisk(),
!has(NoHttpd.class, testDesc.getTestClass()),
has(Sandboxed.class, testDesc.getTestClass()),
has(SkipProjectClone.class, testDesc.getTestClass()),
has(UseSsh.class, testDesc.getTestClass()),
verifyNoPiiInChangeNotes != null && verifyNoPiiInChangeNotes.value(),
false, // @UseSystemTime is only valid on methods.
get(UseClockStep.class, testDesc.getTestClass()),
get(UseTimezone.class, testDesc.getTestClass()),
null, // @GerritSystemProperty is only valid on methods.
null, // @GerritSystemProperties is only valid on methods.
null, // @GerritConfig is only valid on methods.
null, // @GerritConfigs is only valid on methods.
null, // @GlobalPluginConfig is only valid on methods.
null); // @GlobalPluginConfigs is only valid on methods.
}
public static Description forTestMethod(
org.junit.runner.Description testDesc, String configName) {
UseClockStep useClockStep = testDesc.getAnnotation(UseClockStep.class);
if (testDesc.getAnnotation(UseSystemTime.class) == null && useClockStep == null) {
// Only read the UseClockStep from the class if on method level neither @UseSystemTime nor
// @UseClockStep have been used.
// If the method defines @UseSystemTime or @UseClockStep it should overwrite @UseClockStep
// on class level.
useClockStep = get(UseClockStep.class, testDesc.getTestClass());
}
VerifyNoPiiInChangeNotes verifyNoPiiInChangeNotes =
testDesc.getAnnotation(VerifyNoPiiInChangeNotes.class);
if (verifyNoPiiInChangeNotes == null) {
verifyNoPiiInChangeNotes = get(VerifyNoPiiInChangeNotes.class, testDesc.getTestClass());
}
return new AutoValue_GerritServer_Description(
testDesc,
configName,
(testDesc.getAnnotation(UseLocalDisk.class) == null
&& !has(UseLocalDisk.class, testDesc.getTestClass()))
&& !forceLocalDisk(),
testDesc.getAnnotation(NoHttpd.class) == null
&& !has(NoHttpd.class, testDesc.getTestClass()),
testDesc.getAnnotation(Sandboxed.class) != null
|| has(Sandboxed.class, testDesc.getTestClass()),
testDesc.getAnnotation(SkipProjectClone.class) != null
|| has(SkipProjectClone.class, testDesc.getTestClass()),
testDesc.getAnnotation(UseSsh.class) != null
|| has(UseSsh.class, testDesc.getTestClass()),
verifyNoPiiInChangeNotes != null && verifyNoPiiInChangeNotes.value(),
testDesc.getAnnotation(UseSystemTime.class) != null,
useClockStep,
testDesc.getAnnotation(UseTimezone.class) != null
? testDesc.getAnnotation(UseTimezone.class)
: get(UseTimezone.class, testDesc.getTestClass()),
testDesc.getAnnotation(GerritSystemProperty.class),
testDesc.getAnnotation(GerritSystemProperties.class),
testDesc.getAnnotation(GerritConfig.class),
testDesc.getAnnotation(GerritConfigs.class),
testDesc.getAnnotation(GlobalPluginConfig.class),
testDesc.getAnnotation(GlobalPluginConfigs.class));
}
private static boolean has(Class<? extends Annotation> annotation, Class<?> clazz) {
for (; clazz != null; clazz = clazz.getSuperclass()) {
if (clazz.getAnnotation(annotation) != null) {
return true;
}
}
return false;
}
@Nullable
private static <T extends Annotation> T get(Class<T> annotation, Class<?> clazz) {
for (; clazz != null; clazz = clazz.getSuperclass()) {
if (clazz.getAnnotation(annotation) != null) {
return clazz.getAnnotation(annotation);
}
}
return null;
}
abstract org.junit.runner.Description testDescription();
@Nullable
abstract String configName();
abstract boolean memory();
abstract boolean httpd();
abstract boolean sandboxed();
public abstract boolean skipProjectClone();
abstract boolean useSshAnnotation();
abstract boolean verifyNoPiiInChangeNotes();
boolean useSsh() {
return useSshAnnotation() && SshMode.useSsh();
}
abstract boolean useSystemTime();
@Nullable
abstract UseClockStep useClockStep();
@Nullable
abstract UseTimezone useTimezone();
@Nullable
abstract GerritSystemProperty systemProperty();
@Nullable
abstract GerritSystemProperties systemProperties();
@Nullable
abstract GerritConfig config();
@Nullable
abstract GerritConfigs configs();
@Nullable
abstract GlobalPluginConfig pluginConfig();
@Nullable
abstract GlobalPluginConfigs pluginConfigs();
private void checkValidAnnotations() {
if (useClockStep() != null && useSystemTime()) {
throw new IllegalStateException("Use either @UseClockStep or @UseSystemTime, not both");
}
if (systemProperties() != null && systemProperty() != null) {
throw new IllegalStateException(
"Use either @GerritSystemProperties or @GerritSystemProperty, not both");
}
if (configs() != null && config() != null) {
throw new IllegalStateException("Use either @GerritConfigs or @GerritConfig, not both");
}
if (pluginConfigs() != null && pluginConfig() != null) {
throw new IllegalStateException(
"Use either @GlobalPluginConfig or @GlobalPluginConfigs, not both");
}
if ((pluginConfigs() != null || pluginConfig() != null) && memory()) {
throw new IllegalStateException("Must use @UseLocalDisk with @GlobalPluginConfig(s)");
}
}
private Config buildConfig(Config baseConfig) {
if (configs() != null) {
return ConfigAnnotationParser.parse(baseConfig, configs());
} else if (config() != null) {
return ConfigAnnotationParser.parse(baseConfig, config());
} else {
return baseConfig;
}
}
private Map<String, Config> buildPluginConfigs() {
if (pluginConfigs() != null) {
return ConfigAnnotationParser.parse(pluginConfigs());
} else if (pluginConfig() != null) {
return ConfigAnnotationParser.parse(pluginConfig());
}
return new HashMap<>();
}
}
private static boolean forceLocalDisk() {
String value = Strings.nullToEmpty(System.getenv("GERRIT_FORCE_LOCAL_DISK"));
if (value.isEmpty()) {
value = Strings.nullToEmpty(System.getProperty("gerrit.forceLocalDisk"));
}
switch (value.trim().toLowerCase(Locale.US)) {
case "1":
case "yes":
case "true":
return true;
default:
return false;
}
}
/**
* Initializes on-disk site but does not start server.
*
* @param desc server description
* @param baseConfig default config values; merged with config from {@code desc} and then written
* into {@code site/etc/gerrit.config}.
* @param site temp directory where site will live.
*/
public static void init(Description desc, Config baseConfig, Path site) throws Exception {
checkArgument(!desc.memory(), "can't initialize site path for in-memory test: %s", desc);
Config cfg = desc.buildConfig(baseConfig);
Map<String, Config> pluginConfigs = desc.buildPluginConfigs();
MergeableFileBasedConfig gerritConfig =
new MergeableFileBasedConfig(
site.resolve("etc").resolve("gerrit.config").toFile(), FS.DETECTED);
gerritConfig.load();
gerritConfig.merge(cfg);
mergeTestConfig(gerritConfig);
String configuredIndexBackend = cfg.getString("index", null, "type");
if (configuredIndexBackend == null) {
// Propagate index type to pgms that run off of the gerrit.config file on local disk.
IndexType indexType = IndexType.fromEnvironment().orElseGet(() -> new IndexType("fake"));
gerritConfig.setString("index", null, "type", indexType.isLucene() ? "lucene" : "fake");
}
gerritConfig.save();
Init init = new Init();
int rc =
init.main(
new String[] {
"-d", site.toString(), "--batch", "--no-auto-start", "--skip-plugins",
});
if (rc != 0) {
throw new RuntimeException("Couldn't initialize site");
}
for (String pluginName : pluginConfigs.keySet()) {
MergeableFileBasedConfig pluginCfg =
new MergeableFileBasedConfig(
site.resolve("etc").resolve(pluginName + ".config").toFile(), FS.DETECTED);
pluginCfg.load();
pluginCfg.merge(pluginConfigs.get(pluginName));
pluginCfg.save();
}
}
/**
* Initializes new Gerrit site and returns started server.
*
* <p>A new temporary directory for the site will be created with {@code temporaryFolder}, even in
* the server is otherwise configured in-memory. Closing the server stops the daemon but does not
* delete the temporary directory..
*
* @param temporaryFolder helper rule for creating site directories.
* @param desc server description.
* @param baseConfig default config values; merged with config from {@code desc}.
* @param testSysModule additional Guice module to use.
* @param testSshModule additional Guice module to use.
* @return started server.
*/
public static GerritServer initAndStart(
TemporaryFolder temporaryFolder,
Description desc,
Config baseConfig,
@Nullable Module testSysModule,
@Nullable Module testAuditModule,
@Nullable Module testSshModule)
throws Exception {
Path site = temporaryFolder.newFolder().toPath();
try {
if (!desc.memory()) {
init(desc, baseConfig, site);
}
return start(desc, baseConfig, site, testSysModule, testAuditModule, testSshModule, null);
} catch (Exception e) {
throw e;
}
}
/**
* Starts Gerrit server from existing on-disk site.
*
* @param desc server description.
* @param baseConfig default config values; merged with config from {@code desc}.
* @param site existing temporary directory for site. Required, but may be empty, for in-memory
* servers. For on-disk servers, assumes that {@link #init} was previously called to
* initialize this directory. Can be retrieved from the returned instance via {@link
* #getSitePath()}.
* @param testSysModule optional additional module to add to the system injector.
* @param testSshModule optional additional module to add to the ssh injector.
* @param inMemoryRepoManager {@link InMemoryRepositoryManager} that should be used if the site is
* started in memory
* @param additionalArgs additional command-line arguments for the daemon program; only allowed if
* the test is not in-memory.
* @return started server.
*/
public static GerritServer start(
Description desc,
Config baseConfig,
Path site,
@Nullable Module testSysModule,
@Nullable Module testAuditModule,
@Nullable Module testSshModule,
@Nullable InMemoryRepositoryManager inMemoryRepoManager,
String... additionalArgs)
throws Exception {
checkArgument(site != null, "site is required (even for in-memory server");
desc.checkValidAnnotations();
TestLoggingActivator.configureLogging();
CyclicBarrier serverStarted = new CyclicBarrier(2);
Daemon daemon =
new Daemon(
() -> {
try {
serverStarted.await();
} catch (InterruptedException | BrokenBarrierException e) {
throw new RuntimeException(e);
}
},
site);
daemon.setAccountPatchReviewStoreModuleForTesting(new FakeAccountPatchReviewStoreModule());
daemon.setEmailModuleForTesting(new FakeEmailSenderModule());
daemon.setAuditEventModuleForTesting(
MoreObjects.firstNonNull(testAuditModule, new FakeGroupAuditServiceModule()));
if (testSysModule != null) {
daemon.addAdditionalSysModuleForTesting(testSysModule);
}
if (testSshModule != null) {
daemon.addAdditionalSshModuleForTesting(testSshModule);
}
daemon.addAdditionalSysModuleForTesting(
new AbstractModule() {
@Override
protected void configure() {
bind(CommitValidationListener.class)
.annotatedWith(Exports.named("object-visibility-listener"))
.to(GitObjectVisibilityChecker.class);
}
});
daemon.addAdditionalSysModuleForTesting(
new AbstractModule() {
@Override
protected void configure() {
super.configure();
// GerritServer isn't restarted between tests. TestTicker allows to replace actual
// Ticker in tests without restarting server and transparently for other code.
// Alternative option with Provider<Ticker> is less convinient, because it affects how
// gerrit code should be written - i.e. Ticker must not be stored in fields and must
// always be obtained from the provider.
TestTicker testTicker = new TestTicker();
OptionalBinder.newOptionalBinder(binder(), Ticker.class)
.setBinding()
.toInstance(testTicker);
bind(TestTicker.class).toInstance(testTicker);
}
});
// Assure that HTTPD is enabled if SSHD is not required. If both are disabled the Gerrit server
// does not start. Alternatively we could assure that SSHD is enabled if HTTPD is not required,
// but this would break the tests at Google, because they don't have support for SSHD.
daemon.setEnableHttpd(desc.httpd() || !desc.useSsh());
daemon.setEnableSshd(desc.useSsh());
daemon.setReplica(
ReplicaUtil.isReplica(baseConfig) || ReplicaUtil.isReplica(desc.buildConfig(baseConfig)));
if (desc.memory()) {
checkArgument(additionalArgs.length == 0, "cannot pass args to in-memory server");
AbstractIndexModule testIndexModule =
(testSysModule instanceof AbstractIndexModule)
? (AbstractIndexModule) testSysModule
: null;
return startInMemory(desc, site, baseConfig, daemon, inMemoryRepoManager, testIndexModule);
}
return startOnDisk(desc, site, daemon, serverStarted, additionalArgs);
}
private static GerritServer startInMemory(
Description desc,
Path site,
Config baseConfig,
Daemon daemon,
@Nullable InMemoryRepositoryManager inMemoryRepoManager,
@Nullable AbstractIndexModule testIndexModule)
throws Exception {
Config cfg = desc.buildConfig(baseConfig);
mergeTestConfig(cfg);
// Set the log4j configuration to an invalid one to prevent system logs
// from getting configured and creating log files.
System.setProperty(SystemLog.LOG4J_CONFIGURATION, "invalidConfiguration");
cfg.setBoolean("httpd", null, "requestLog", false);
cfg.setBoolean("sshd", null, "requestLog", false);
cfg.setBoolean("index", "lucene", "testInmemory", true);
cfg.setBoolean("index", null, "onlineUpgrade", false);
cfg.setString("gitweb", null, "cgi", "");
cfg.setString(
"accountPatchReviewDb", null, "url", JdbcAccountPatchReviewStore.TEST_IN_MEMORY_URL);
String configuredIndexBackend = cfg.getString("index", null, "type");
IndexType indexType =
(configuredIndexBackend != null)
? new IndexType(configuredIndexBackend)
: IndexType.fromEnvironment().orElseGet(() -> new IndexType("fake"));
daemon.setIndexModule(createIndexModule(indexType, baseConfig, testIndexModule));
daemon.setEnableHttpd(desc.httpd());
daemon.setInMemory(true);
daemon.setDatabaseForTesting(
ImmutableList.of(
new InMemoryTestingDatabaseModule(cfg, site, inMemoryRepoManager),
new AbstractModule() {
@Override
protected void configure() {
bind(GerritRuntime.class).toInstance(GerritRuntime.DAEMON);
}
},
new ConfigExperimentFeaturesModule()));
daemon.addAdditionalSysModuleForTesting(
new GrantDirectPushPermissionsOnStartupModule(),
new ReindexProjectsAtStartupModule(),
new ReindexGroupsAtStartupModule());
daemon.start();
return new GerritServer(desc, null, createTestInjector(daemon), Optional.of(daemon), null);
}
private static AbstractIndexModule createIndexModule(
IndexType indexType, Config baseConfig, @Nullable AbstractIndexModule testIndexModule) {
if (testIndexModule != null) {
return testIndexModule;
}
return indexType.isLucene()
? LuceneIndexModule.singleVersionAllLatest(
0, ReplicaUtil.isReplica(baseConfig), AutoFlush.ENABLED)
: FakeIndexModule.latestVersion(false);
}
private static GerritServer startOnDisk(
Description desc,
Path site,
Daemon daemon,
CyclicBarrier serverStarted,
String[] additionalArgs)
throws Exception {
requireNonNull(site);
daemon.addAdditionalSysModuleForTesting(
new GrantDirectPushPermissionsOnStartupModule(),
new ReindexProjectsAtStartupModule(),
new ReindexGroupsAtStartupModule());
ExecutorService daemonService = Executors.newSingleThreadExecutor();
String[] args =
Stream.concat(
Stream.of(
"-d", site.toString(), "--headless", "--console-log", "--show-stack-trace"),
Arrays.stream(additionalArgs))
.toArray(String[]::new);
@SuppressWarnings("unused")
Future<?> possiblyIgnoredError =
daemonService.submit(
() -> {
int rc = daemon.main(args);
if (rc != 0) {
System.err.println("Failed to start Gerrit daemon");
serverStarted.reset();
}
return null;
});
try {
serverStarted.await();
} catch (BrokenBarrierException e) {
daemon.stop();
throw new StartupException("Failed to start Gerrit daemon; see log", e);
}
System.out.println("Gerrit Server Started");
return new GerritServer(
desc, site, createTestInjector(daemon), Optional.of(daemon), daemonService);
}
private static void mergeTestConfig(Config cfg) {
String forceEphemeralPort = String.format("%s:0", getLocalHost().getHostName());
String url = "http://" + forceEphemeralPort + "/";
if (cfg.getString("gerrit", null, "canonicalWebUrl") == null) {
cfg.setString("gerrit", null, "canonicalWebUrl", url);
}
if (cfg.getString("httpd", null, "listenUrl") == null) {
cfg.setString("httpd", null, "listenUrl", url);
}
if (cfg.getString("sshd", null, "listenAddress") == null) {
cfg.setString("sshd", null, "listenAddress", forceEphemeralPort);
}
cfg.setBoolean("sshd", null, "testUseInsecureRandom", true);
cfg.unset("cache", null, "directory");
cfg.setString("gerrit", null, "basePath", "git");
cfg.setBoolean("sendemail", null, "enable", true);
cfg.setInt("sendemail", null, "threadPoolSize", 0);
cfg.setInt("execution", null, "fanOutThreadPoolSize", 0);
cfg.setInt("plugins", null, "checkFrequency", 0);
cfg.setInt("sshd", null, "threads", 1);
cfg.setInt("sshd", null, "commandStartThreads", 1);
cfg.setInt("receive", null, "threadPoolSize", 1);
cfg.setInt("index", null, "threads", 1);
if (cfg.getString("index", null, "mergeabilityComputationBehavior") == null) {
cfg.setString("index", null, "mergeabilityComputationBehavior", "NEVER");
}
}
private static Injector createTestInjector(Daemon daemon) throws Exception {
Injector sysInjector = getInjector(daemon, "sysInjector");
Module module =
new FactoryModule() {
@Override
protected void configure() {
bindConstant().annotatedWith(SshEnabled.class).to(daemon.getEnableSshd());
bind(AccountCreator.class);
bind(AccountOperations.class).to(AccountOperationsImpl.class);
bind(GroupOperations.class).to(GroupOperationsImpl.class);
bind(ProjectOperations.class).to(ProjectOperationsImpl.class);
bind(RequestScopeOperations.class).to(RequestScopeOperationsImpl.class);
bind(ChangeOperations.class).to(ChangeOperationsImpl.class);
factory(PerPatchsetOperationsImpl.Factory.class);
factory(PerCommentOperationsImpl.Factory.class);
factory(PerDraftCommentOperationsImpl.Factory.class);
factory(PerRobotCommentOperationsImpl.Factory.class);
install(new PushOneCommit.Module());
install(InProcessProtocol.module());
install(new NoSshModule());
install(new AsyncReceiveCommitsModule());
factory(ProjectResetter.Builder.Factory.class);
}
@Provides
@Singleton
@Nullable
@TestSshServerAddress
InetSocketAddress getSshAddress(@GerritServerConfig Config cfg) {
String addr = cfg.getString("sshd", null, "listenAddress");
// We do not use InitSshd.isOff to avoid coupling GerritServer to the SSH code.
return !"off".equalsIgnoreCase(addr)
? SocketUtil.resolve(cfg.getString("sshd", null, "listenAddress"), 0)
: null;
}
};
return sysInjector.createChildInjector(module);
}
private static Injector getInjector(Object obj, String field)
throws SecurityException, NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
Object v = f.get(obj);
checkArgument(v instanceof Injector, "not an Injector: %s", v);
return (Injector) f.get(obj);
}
private static InetAddress getLocalHost() {
return InetAddress.getLoopbackAddress();
}
private final Description desc;
private final Path sitePath;
private final Optional<Daemon> daemon;
private final ExecutorService daemonService;
protected final Injector testInjector;
private final String url;
private final Optional<InetSocketAddress> httpAddress;
protected GerritServer(
Description desc,
@Nullable Path sitePath,
Injector testInjector,
Optional<Daemon> daemon,
@Nullable ExecutorService daemonService) {
this.desc = requireNonNull(desc);
this.sitePath = sitePath;
this.testInjector = requireNonNull(testInjector);
this.daemon = requireNonNull(daemon);
this.daemonService = daemonService;
Config cfg = testInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
url = cfg.getString("gerrit", null, "canonicalWebUrl");
if (daemon.isPresent()) {
URI uri = URI.create(url);
httpAddress = Optional.of(new InetSocketAddress(uri.getHost(), uri.getPort()));
} else {
httpAddress = Optional.empty();
}
}
public String getUrl() {
return url;
}
Optional<InetSocketAddress> getHttpAddress() {
return httpAddress;
}
public Injector getTestInjector() {
return testInjector;
}
public Optional<Injector> getHttpdInjector() {
return daemon.map(Daemon::getHttpdInjector);
}
Description getDescription() {
return desc;
}
public static GerritServer restartAsSlave(GerritServer server) throws Exception {
checkState(server.desc.sandboxed(), "restarting as slave requires @Sandboxed");
Path site = server.testInjector.getInstance(Key.get(Path.class, SitePath.class));
Config cfg = server.testInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
cfg.setBoolean("container", null, "replica", true);
InMemoryRepositoryManager inMemoryRepoManager = null;
if (hasBinding(server.testInjector, InMemoryRepositoryManager.class)) {
inMemoryRepoManager = server.testInjector.getInstance(InMemoryRepositoryManager.class);
}
server.close();
server.daemon.ifPresent(Daemon::stop);
return start(server.desc, cfg, site, null, null, null, inMemoryRepoManager);
}
public static GerritServer restart(
GerritServer server, @Nullable Module testSysModule, @Nullable Module testSshModule)
throws Exception {
checkState(server.desc.sandboxed(), "restarting as slave requires @Sandboxed");
Config cfg = server.testInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
Path site = server.testInjector.getInstance(Key.get(Path.class, SitePath.class));
InMemoryRepositoryManager inMemoryRepoManager = null;
if (hasBinding(server.testInjector, InMemoryRepositoryManager.class)) {
inMemoryRepoManager = server.testInjector.getInstance(InMemoryRepositoryManager.class);
}
server.close();
server.daemon.ifPresent(Daemon::stop);
return start(server.desc, cfg, site, testSysModule, null, testSshModule, inMemoryRepoManager);
}
private static boolean hasBinding(Injector injector, Class<?> clazz) {
return injector.getExistingBinding(Key.get(clazz)) != null;
}
@Override
public void close() throws Exception {
daemon.ifPresent(d -> d.getLifecycleManager().stop());
if (daemonService != null) {
System.out.println("Gerrit Server Shutdown");
daemonService.shutdownNow();
daemonService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
}
RepositoryCache.clear();
}
public Path getSitePath() {
return sitePath;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this).addValue(desc).toString();
}
public boolean isReplica() {
return daemon.map(Daemon::isReplica).orElse(false);
}
}