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.acceptance;
16 :
17 : import static com.google.common.base.Preconditions.checkArgument;
18 : import static com.google.common.base.Preconditions.checkState;
19 : import static java.lang.annotation.RetentionPolicy.RUNTIME;
20 : import static java.util.Objects.requireNonNull;
21 :
22 : import com.google.auto.value.AutoValue;
23 : import com.google.common.base.MoreObjects;
24 : import com.google.common.base.Strings;
25 : import com.google.common.base.Ticker;
26 : import com.google.common.collect.ImmutableList;
27 : import com.google.gerrit.acceptance.AbstractDaemonTest.TestTicker;
28 : import com.google.gerrit.acceptance.FakeGroupAuditService.FakeGroupAuditServiceModule;
29 : import com.google.gerrit.acceptance.ReindexGroupsAtStartup.ReindexGroupsAtStartupModule;
30 : import com.google.gerrit.acceptance.ReindexProjectsAtStartup.ReindexProjectsAtStartupModule;
31 : import com.google.gerrit.acceptance.config.ConfigAnnotationParser;
32 : import com.google.gerrit.acceptance.config.GerritConfig;
33 : import com.google.gerrit.acceptance.config.GerritConfigs;
34 : import com.google.gerrit.acceptance.config.GlobalPluginConfig;
35 : import com.google.gerrit.acceptance.config.GlobalPluginConfigs;
36 : import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
37 : import com.google.gerrit.acceptance.testsuite.account.AccountOperationsImpl;
38 : import com.google.gerrit.acceptance.testsuite.change.ChangeOperations;
39 : import com.google.gerrit.acceptance.testsuite.change.ChangeOperationsImpl;
40 : import com.google.gerrit.acceptance.testsuite.change.PerCommentOperationsImpl;
41 : import com.google.gerrit.acceptance.testsuite.change.PerDraftCommentOperationsImpl;
42 : import com.google.gerrit.acceptance.testsuite.change.PerPatchsetOperationsImpl;
43 : import com.google.gerrit.acceptance.testsuite.change.PerRobotCommentOperationsImpl;
44 : import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
45 : import com.google.gerrit.acceptance.testsuite.group.GroupOperationsImpl;
46 : import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
47 : import com.google.gerrit.acceptance.testsuite.project.ProjectOperationsImpl;
48 : import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
49 : import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperationsImpl;
50 : import com.google.gerrit.common.Nullable;
51 : import com.google.gerrit.extensions.annotations.Exports;
52 : import com.google.gerrit.extensions.config.FactoryModule;
53 : import com.google.gerrit.index.IndexType;
54 : import com.google.gerrit.index.testing.FakeIndexModule;
55 : import com.google.gerrit.lucene.LuceneIndexModule;
56 : import com.google.gerrit.pgm.Daemon;
57 : import com.google.gerrit.pgm.Init;
58 : import com.google.gerrit.server.config.GerritRuntime;
59 : import com.google.gerrit.server.config.GerritServerConfig;
60 : import com.google.gerrit.server.config.SitePath;
61 : import com.google.gerrit.server.experiments.ConfigExperimentFeatures.ConfigExperimentFeaturesModule;
62 : import com.google.gerrit.server.git.receive.AsyncReceiveCommits.AsyncReceiveCommitsModule;
63 : import com.google.gerrit.server.git.validators.CommitValidationListener;
64 : import com.google.gerrit.server.index.options.AutoFlush;
65 : import com.google.gerrit.server.schema.JdbcAccountPatchReviewStore;
66 : import com.google.gerrit.server.ssh.NoSshModule;
67 : import com.google.gerrit.server.util.ReplicaUtil;
68 : import com.google.gerrit.server.util.SocketUtil;
69 : import com.google.gerrit.server.util.SystemLog;
70 : import com.google.gerrit.testing.FakeEmailSender.FakeEmailSenderModule;
71 : import com.google.gerrit.testing.InMemoryRepositoryManager;
72 : import com.google.gerrit.testing.SshMode;
73 : import com.google.gerrit.testing.TestLoggingActivator;
74 : import com.google.inject.AbstractModule;
75 : import com.google.inject.BindingAnnotation;
76 : import com.google.inject.Injector;
77 : import com.google.inject.Key;
78 : import com.google.inject.Module;
79 : import com.google.inject.Provides;
80 : import com.google.inject.Singleton;
81 : import com.google.inject.multibindings.OptionalBinder;
82 : import java.lang.annotation.Annotation;
83 : import java.lang.annotation.Retention;
84 : import java.lang.reflect.Field;
85 : import java.net.InetAddress;
86 : import java.net.InetSocketAddress;
87 : import java.net.URI;
88 : import java.nio.file.Path;
89 : import java.util.Arrays;
90 : import java.util.HashMap;
91 : import java.util.Locale;
92 : import java.util.Map;
93 : import java.util.concurrent.BrokenBarrierException;
94 : import java.util.concurrent.CyclicBarrier;
95 : import java.util.concurrent.ExecutorService;
96 : import java.util.concurrent.Executors;
97 : import java.util.concurrent.Future;
98 : import java.util.concurrent.TimeUnit;
99 : import java.util.stream.Stream;
100 : import org.eclipse.jgit.lib.Config;
101 : import org.eclipse.jgit.lib.RepositoryCache;
102 : import org.eclipse.jgit.util.FS;
103 : import org.junit.rules.TemporaryFolder;
104 :
105 : public class GerritServer implements AutoCloseable {
106 : public static class StartupException extends Exception {
107 : private static final long serialVersionUID = 1L;
108 :
109 : StartupException(String msg, Throwable cause) {
110 1 : super(msg, cause);
111 1 : }
112 : }
113 :
114 : /** Marker on {@link InetSocketAddress} for test SSH server. */
115 : @Retention(RUNTIME)
116 : @BindingAnnotation
117 : public @interface TestSshServerAddress {}
118 :
119 : @AutoValue
120 138 : public abstract static class Description {
121 : public static Description forTestClass(
122 : org.junit.runner.Description testDesc, String configName) {
123 132 : VerifyNoPiiInChangeNotes verifyNoPiiInChangeNotes =
124 132 : get(VerifyNoPiiInChangeNotes.class, testDesc.getTestClass());
125 132 : return new AutoValue_GerritServer_Description(
126 : testDesc,
127 : configName,
128 132 : !has(UseLocalDisk.class, testDesc.getTestClass()) && !forceLocalDisk(),
129 132 : !has(NoHttpd.class, testDesc.getTestClass()),
130 132 : has(Sandboxed.class, testDesc.getTestClass()),
131 132 : has(SkipProjectClone.class, testDesc.getTestClass()),
132 132 : has(UseSsh.class, testDesc.getTestClass()),
133 132 : verifyNoPiiInChangeNotes != null && verifyNoPiiInChangeNotes.value(),
134 : false, // @UseSystemTime is only valid on methods.
135 132 : get(UseClockStep.class, testDesc.getTestClass()),
136 132 : get(UseTimezone.class, testDesc.getTestClass()),
137 : null, // @GerritConfig is only valid on methods.
138 : null, // @GerritConfigs is only valid on methods.
139 : null, // @GlobalPluginConfig is only valid on methods.
140 : null); // @GlobalPluginConfigs is only valid on methods.
141 : }
142 :
143 : public static Description forTestMethod(
144 : org.junit.runner.Description testDesc, String configName) {
145 138 : UseClockStep useClockStep = testDesc.getAnnotation(UseClockStep.class);
146 138 : if (testDesc.getAnnotation(UseSystemTime.class) == null && useClockStep == null) {
147 : // Only read the UseClockStep from the class if on method level neither @UseSystemTime nor
148 : // @UseClockStep have been used.
149 : // If the method defines @UseSystemTime or @UseClockStep it should overwrite @UseClockStep
150 : // on class level.
151 138 : useClockStep = get(UseClockStep.class, testDesc.getTestClass());
152 : }
153 138 : VerifyNoPiiInChangeNotes verifyNoPiiInChangeNotes =
154 138 : testDesc.getAnnotation(VerifyNoPiiInChangeNotes.class);
155 138 : if (verifyNoPiiInChangeNotes == null) {
156 138 : verifyNoPiiInChangeNotes = get(VerifyNoPiiInChangeNotes.class, testDesc.getTestClass());
157 : }
158 :
159 138 : return new AutoValue_GerritServer_Description(
160 : testDesc,
161 : configName,
162 138 : (testDesc.getAnnotation(UseLocalDisk.class) == null
163 138 : && !has(UseLocalDisk.class, testDesc.getTestClass()))
164 138 : && !forceLocalDisk(),
165 138 : testDesc.getAnnotation(NoHttpd.class) == null
166 138 : && !has(NoHttpd.class, testDesc.getTestClass()),
167 138 : testDesc.getAnnotation(Sandboxed.class) != null
168 138 : || has(Sandboxed.class, testDesc.getTestClass()),
169 138 : testDesc.getAnnotation(SkipProjectClone.class) != null
170 138 : || has(SkipProjectClone.class, testDesc.getTestClass()),
171 138 : testDesc.getAnnotation(UseSsh.class) != null
172 138 : || has(UseSsh.class, testDesc.getTestClass()),
173 138 : verifyNoPiiInChangeNotes != null && verifyNoPiiInChangeNotes.value(),
174 138 : testDesc.getAnnotation(UseSystemTime.class) != null,
175 : useClockStep,
176 138 : testDesc.getAnnotation(UseTimezone.class) != null
177 1 : ? testDesc.getAnnotation(UseTimezone.class)
178 138 : : get(UseTimezone.class, testDesc.getTestClass()),
179 138 : testDesc.getAnnotation(GerritConfig.class),
180 138 : testDesc.getAnnotation(GerritConfigs.class),
181 138 : testDesc.getAnnotation(GlobalPluginConfig.class),
182 138 : testDesc.getAnnotation(GlobalPluginConfigs.class));
183 : }
184 :
185 : private static boolean has(Class<? extends Annotation> annotation, Class<?> clazz) {
186 138 : for (; clazz != null; clazz = clazz.getSuperclass()) {
187 138 : if (clazz.getAnnotation(annotation) != null) {
188 64 : return true;
189 : }
190 : }
191 138 : return false;
192 : }
193 :
194 : @Nullable
195 : private static <T extends Annotation> T get(Class<T> annotation, Class<?> clazz) {
196 138 : for (; clazz != null; clazz = clazz.getSuperclass()) {
197 138 : if (clazz.getAnnotation(annotation) != null) {
198 19 : return clazz.getAnnotation(annotation);
199 : }
200 : }
201 138 : return null;
202 : }
203 :
204 : abstract org.junit.runner.Description testDescription();
205 :
206 : @Nullable
207 : abstract String configName();
208 :
209 : abstract boolean memory();
210 :
211 : abstract boolean httpd();
212 :
213 : abstract boolean sandboxed();
214 :
215 : abstract boolean skipProjectClone();
216 :
217 : abstract boolean useSshAnnotation();
218 :
219 : abstract boolean verifyNoPiiInChangeNotes();
220 :
221 : boolean useSsh() {
222 138 : return useSshAnnotation() && SshMode.useSsh();
223 : }
224 :
225 : abstract boolean useSystemTime();
226 :
227 : @Nullable
228 : abstract UseClockStep useClockStep();
229 :
230 : @Nullable
231 : abstract UseTimezone useTimezone();
232 :
233 : @Nullable
234 : abstract GerritConfig config();
235 :
236 : @Nullable
237 : abstract GerritConfigs configs();
238 :
239 : @Nullable
240 : abstract GlobalPluginConfig pluginConfig();
241 :
242 : @Nullable
243 : abstract GlobalPluginConfigs pluginConfigs();
244 :
245 : private void checkValidAnnotations() {
246 138 : if (useClockStep() != null && useSystemTime()) {
247 0 : throw new IllegalStateException("Use either @UseClockStep or @UseSystemTime, not both");
248 : }
249 138 : if (configs() != null && config() != null) {
250 0 : throw new IllegalStateException("Use either @GerritConfigs or @GerritConfig, not both");
251 : }
252 138 : if (pluginConfigs() != null && pluginConfig() != null) {
253 0 : throw new IllegalStateException(
254 : "Use either @GlobalPluginConfig or @GlobalPluginConfigs, not both");
255 : }
256 138 : if ((pluginConfigs() != null || pluginConfig() != null) && memory()) {
257 0 : throw new IllegalStateException("Must use @UseLocalDisk with @GlobalPluginConfig(s)");
258 : }
259 138 : }
260 :
261 : private Config buildConfig(Config baseConfig) {
262 138 : if (configs() != null) {
263 15 : return ConfigAnnotationParser.parse(baseConfig, configs());
264 137 : } else if (config() != null) {
265 45 : return ConfigAnnotationParser.parse(baseConfig, config());
266 : } else {
267 136 : return baseConfig;
268 : }
269 : }
270 :
271 : private Map<String, Config> buildPluginConfigs() {
272 15 : if (pluginConfigs() != null) {
273 1 : return ConfigAnnotationParser.parse(pluginConfigs());
274 15 : } else if (pluginConfig() != null) {
275 1 : return ConfigAnnotationParser.parse(pluginConfig());
276 : }
277 14 : return new HashMap<>();
278 : }
279 : }
280 :
281 : private static boolean forceLocalDisk() {
282 132 : String value = Strings.nullToEmpty(System.getenv("GERRIT_FORCE_LOCAL_DISK"));
283 132 : if (value.isEmpty()) {
284 132 : value = Strings.nullToEmpty(System.getProperty("gerrit.forceLocalDisk"));
285 : }
286 132 : switch (value.trim().toLowerCase(Locale.US)) {
287 : case "1":
288 : case "yes":
289 : case "true":
290 0 : return true;
291 : default:
292 132 : return false;
293 : }
294 : }
295 :
296 : /**
297 : * Initializes on-disk site but does not start server.
298 : *
299 : * @param desc server description
300 : * @param baseConfig default config values; merged with config from {@code desc} and then written
301 : * into {@code site/etc/gerrit.config}.
302 : * @param site temp directory where site will live.
303 : */
304 : public static void init(Description desc, Config baseConfig, Path site) throws Exception {
305 15 : checkArgument(!desc.memory(), "can't initialize site path for in-memory test: %s", desc);
306 15 : Config cfg = desc.buildConfig(baseConfig);
307 15 : Map<String, Config> pluginConfigs = desc.buildPluginConfigs();
308 :
309 15 : MergeableFileBasedConfig gerritConfig =
310 : new MergeableFileBasedConfig(
311 15 : site.resolve("etc").resolve("gerrit.config").toFile(), FS.DETECTED);
312 15 : gerritConfig.load();
313 15 : gerritConfig.merge(cfg);
314 15 : mergeTestConfig(gerritConfig);
315 15 : String configuredIndexBackend = cfg.getString("index", null, "type");
316 15 : if (configuredIndexBackend == null) {
317 : // Propagate index type to pgms that run off of the gerrit.config file on local disk.
318 15 : IndexType indexType = IndexType.fromEnvironment().orElse(new IndexType("fake"));
319 15 : gerritConfig.setString("index", null, "type", indexType.isLucene() ? "lucene" : "fake");
320 : }
321 15 : gerritConfig.save();
322 :
323 15 : Init init = new Init();
324 15 : int rc =
325 15 : init.main(
326 : new String[] {
327 15 : "-d", site.toString(), "--batch", "--no-auto-start", "--skip-plugins",
328 : });
329 15 : if (rc != 0) {
330 0 : throw new RuntimeException("Couldn't initialize site");
331 : }
332 :
333 15 : for (String pluginName : pluginConfigs.keySet()) {
334 1 : MergeableFileBasedConfig pluginCfg =
335 : new MergeableFileBasedConfig(
336 1 : site.resolve("etc").resolve(pluginName + ".config").toFile(), FS.DETECTED);
337 1 : pluginCfg.load();
338 1 : pluginCfg.merge(pluginConfigs.get(pluginName));
339 1 : pluginCfg.save();
340 1 : }
341 15 : }
342 :
343 : /**
344 : * Initializes new Gerrit site and returns started server.
345 : *
346 : * <p>A new temporary directory for the site will be created with {@code temporaryFolder}, even in
347 : * the server is otherwise configured in-memory. Closing the server stops the daemon but does not
348 : * delete the temporary directory..
349 : *
350 : * @param temporaryFolder helper rule for creating site directories.
351 : * @param desc server description.
352 : * @param baseConfig default config values; merged with config from {@code desc}.
353 : * @param testSysModule additional Guice module to use.
354 : * @param testSshModule additional Guice module to use.
355 : * @return started server.
356 : */
357 : public static GerritServer initAndStart(
358 : TemporaryFolder temporaryFolder,
359 : Description desc,
360 : Config baseConfig,
361 : @Nullable Module testSysModule,
362 : @Nullable Module testAuditModule,
363 : @Nullable Module testSshModule)
364 : throws Exception {
365 132 : Path site = temporaryFolder.newFolder().toPath();
366 : try {
367 132 : if (!desc.memory()) {
368 8 : init(desc, baseConfig, site);
369 : }
370 132 : return start(desc, baseConfig, site, testSysModule, testAuditModule, testSshModule, null);
371 1 : } catch (Exception e) {
372 1 : throw e;
373 : }
374 : }
375 :
376 : /**
377 : * Starts Gerrit server from existing on-disk site.
378 : *
379 : * @param desc server description.
380 : * @param baseConfig default config values; merged with config from {@code desc}.
381 : * @param site existing temporary directory for site. Required, but may be empty, for in-memory
382 : * servers. For on-disk servers, assumes that {@link #init} was previously called to
383 : * initialize this directory. Can be retrieved from the returned instance via {@link
384 : * #getSitePath()}.
385 : * @param testSysModule optional additional module to add to the system injector.
386 : * @param testSshModule optional additional module to add to the ssh injector.
387 : * @param inMemoryRepoManager {@link InMemoryRepositoryManager} that should be used if the site is
388 : * started in memory
389 : * @param additionalArgs additional command-line arguments for the daemon program; only allowed if
390 : * the test is not in-memory.
391 : * @return started server.
392 : */
393 : public static GerritServer start(
394 : Description desc,
395 : Config baseConfig,
396 : Path site,
397 : @Nullable Module testSysModule,
398 : @Nullable Module testAuditModule,
399 : @Nullable Module testSshModule,
400 : @Nullable InMemoryRepositoryManager inMemoryRepoManager,
401 : String... additionalArgs)
402 : throws Exception {
403 138 : checkArgument(site != null, "site is required (even for in-memory server");
404 138 : desc.checkValidAnnotations();
405 138 : TestLoggingActivator.configureLogging();
406 138 : CyclicBarrier serverStarted = new CyclicBarrier(2);
407 138 : Daemon daemon =
408 : new Daemon(
409 : () -> {
410 : try {
411 15 : serverStarted.await();
412 0 : } catch (InterruptedException | BrokenBarrierException e) {
413 0 : throw new RuntimeException(e);
414 15 : }
415 15 : },
416 : site);
417 138 : daemon.setEmailModuleForTesting(new FakeEmailSenderModule());
418 138 : daemon.setAuditEventModuleForTesting(
419 138 : MoreObjects.firstNonNull(testAuditModule, new FakeGroupAuditServiceModule()));
420 138 : if (testSysModule != null) {
421 17 : daemon.addAdditionalSysModuleForTesting(testSysModule);
422 : }
423 138 : if (testSshModule != null) {
424 1 : daemon.addAdditionalSshModuleForTesting(testSshModule);
425 : }
426 138 : daemon.setEnableSshd(desc.useSsh());
427 138 : daemon.addAdditionalSysModuleForTesting(
428 138 : new AbstractModule() {
429 : @Override
430 : protected void configure() {
431 138 : bind(CommitValidationListener.class)
432 138 : .annotatedWith(Exports.named("object-visibility-listener"))
433 138 : .to(GitObjectVisibilityChecker.class);
434 138 : }
435 : });
436 138 : daemon.addAdditionalSysModuleForTesting(
437 138 : new AbstractModule() {
438 : @Override
439 : protected void configure() {
440 138 : super.configure();
441 : // GerritServer isn't restarted between tests. TestTicker allows to replace actual
442 : // Ticker in tests without restarting server and transparently for other code.
443 : // Alternative option with Provider<Ticker> is less convinient, because it affects how
444 : // gerrit code should be written - i.e. Ticker must not be stored in fields and must
445 : // always be obtained from the provider.
446 138 : TestTicker testTicker = new TestTicker();
447 138 : OptionalBinder.newOptionalBinder(binder(), Ticker.class)
448 138 : .setBinding()
449 138 : .toInstance(testTicker);
450 138 : bind(TestTicker.class).toInstance(testTicker);
451 138 : }
452 : });
453 :
454 138 : if (desc.memory()) {
455 132 : checkArgument(additionalArgs.length == 0, "cannot pass args to in-memory server");
456 132 : return startInMemory(desc, site, baseConfig, daemon, inMemoryRepoManager);
457 : }
458 15 : return startOnDisk(desc, site, daemon, serverStarted, additionalArgs);
459 : }
460 :
461 : private static GerritServer startInMemory(
462 : Description desc,
463 : Path site,
464 : Config baseConfig,
465 : Daemon daemon,
466 : @Nullable InMemoryRepositoryManager inMemoryRepoManager)
467 : throws Exception {
468 132 : Config cfg = desc.buildConfig(baseConfig);
469 132 : daemon.setReplica(ReplicaUtil.isReplica(baseConfig) || ReplicaUtil.isReplica(cfg));
470 132 : mergeTestConfig(cfg);
471 : // Set the log4j configuration to an invalid one to prevent system logs
472 : // from getting configured and creating log files.
473 132 : System.setProperty(SystemLog.LOG4J_CONFIGURATION, "invalidConfiguration");
474 132 : cfg.setBoolean("httpd", null, "requestLog", false);
475 132 : cfg.setBoolean("sshd", null, "requestLog", false);
476 132 : cfg.setBoolean("index", "lucene", "testInmemory", true);
477 132 : cfg.setBoolean("index", null, "onlineUpgrade", false);
478 132 : cfg.setString("gitweb", null, "cgi", "");
479 132 : cfg.setString(
480 : "accountPatchReviewDb", null, "url", JdbcAccountPatchReviewStore.TEST_IN_MEMORY_URL);
481 :
482 132 : String configuredIndexBackend = cfg.getString("index", null, "type");
483 : IndexType indexType;
484 132 : if (configuredIndexBackend != null) {
485 : // Explicitly configured index backend from gerrit.config trumps any other ways to configure
486 : // index backends so that Reindex tests can be explicit about the backend they want to test
487 : // against.
488 1 : indexType = new IndexType(configuredIndexBackend);
489 : } else {
490 : // Allow configuring the index backend based on sys/env variables so that integration tests
491 : // can be run against different index backends.
492 132 : indexType = IndexType.fromEnvironment().orElse(new IndexType("fake"));
493 : }
494 132 : if (indexType.isLucene()) {
495 1 : daemon.setIndexModule(
496 1 : LuceneIndexModule.singleVersionAllLatest(
497 1 : 0, ReplicaUtil.isReplica(baseConfig), AutoFlush.ENABLED));
498 : } else {
499 132 : daemon.setIndexModule(FakeIndexModule.latestVersion(false));
500 : }
501 :
502 132 : daemon.setEnableHttpd(desc.httpd());
503 132 : daemon.setInMemory(true);
504 132 : daemon.setDatabaseForTesting(
505 132 : ImmutableList.of(
506 : new InMemoryTestingDatabaseModule(cfg, site, inMemoryRepoManager),
507 132 : new AbstractModule() {
508 : @Override
509 : protected void configure() {
510 132 : bind(GerritRuntime.class).toInstance(GerritRuntime.DAEMON);
511 132 : }
512 : },
513 : new ConfigExperimentFeaturesModule()));
514 132 : daemon.addAdditionalSysModuleForTesting(
515 : new ReindexProjectsAtStartupModule(), new ReindexGroupsAtStartupModule());
516 132 : daemon.start();
517 132 : return new GerritServer(desc, null, createTestInjector(daemon), daemon, null);
518 : }
519 :
520 : private static GerritServer startOnDisk(
521 : Description desc,
522 : Path site,
523 : Daemon daemon,
524 : CyclicBarrier serverStarted,
525 : String[] additionalArgs)
526 : throws Exception {
527 15 : requireNonNull(site);
528 15 : daemon.addAdditionalSysModuleForTesting(
529 : new ReindexProjectsAtStartupModule(), new ReindexGroupsAtStartupModule());
530 15 : ExecutorService daemonService = Executors.newSingleThreadExecutor();
531 15 : String[] args =
532 15 : Stream.concat(
533 15 : Stream.of(
534 15 : "-d", site.toString(), "--headless", "--console-log", "--show-stack-trace"),
535 15 : Arrays.stream(additionalArgs))
536 15 : .toArray(String[]::new);
537 : @SuppressWarnings("unused")
538 15 : Future<?> possiblyIgnoredError =
539 15 : daemonService.submit(
540 : () -> {
541 15 : int rc = daemon.main(args);
542 15 : if (rc != 0) {
543 1 : System.err.println("Failed to start Gerrit daemon");
544 1 : serverStarted.reset();
545 : }
546 15 : return null;
547 : });
548 : try {
549 15 : serverStarted.await();
550 1 : } catch (BrokenBarrierException e) {
551 1 : daemon.stop();
552 1 : throw new StartupException("Failed to start Gerrit daemon; see log", e);
553 15 : }
554 15 : System.out.println("Gerrit Server Started");
555 :
556 15 : return new GerritServer(desc, site, createTestInjector(daemon), daemon, daemonService);
557 : }
558 :
559 : private static void mergeTestConfig(Config cfg) {
560 138 : String forceEphemeralPort = String.format("%s:0", getLocalHost().getHostName());
561 138 : String url = "http://" + forceEphemeralPort + "/";
562 :
563 138 : if (cfg.getString("gerrit", null, "canonicalWebUrl") == null) {
564 138 : cfg.setString("gerrit", null, "canonicalWebUrl", url);
565 : }
566 138 : if (cfg.getString("httpd", null, "listenUrl") == null) {
567 138 : cfg.setString("httpd", null, "listenUrl", url);
568 : }
569 138 : if (cfg.getString("sshd", null, "listenAddress") == null) {
570 20 : cfg.setString("sshd", null, "listenAddress", forceEphemeralPort);
571 : }
572 138 : cfg.setBoolean("sshd", null, "testUseInsecureRandom", true);
573 138 : cfg.unset("cache", null, "directory");
574 138 : cfg.setString("gerrit", null, "basePath", "git");
575 138 : cfg.setBoolean("sendemail", null, "enable", true);
576 138 : cfg.setInt("sendemail", null, "threadPoolSize", 0);
577 138 : cfg.setInt("plugins", null, "checkFrequency", 0);
578 :
579 138 : cfg.setInt("sshd", null, "threads", 1);
580 138 : cfg.setInt("sshd", null, "commandStartThreads", 1);
581 138 : cfg.setInt("receive", null, "threadPoolSize", 1);
582 138 : cfg.setInt("index", null, "threads", 1);
583 138 : if (cfg.getString("index", null, "mergeabilityComputationBehavior") == null) {
584 138 : cfg.setString("index", null, "mergeabilityComputationBehavior", "NEVER");
585 : }
586 138 : }
587 :
588 : private static Injector createTestInjector(Daemon daemon) throws Exception {
589 138 : Injector sysInjector = getInjector(daemon, "sysInjector");
590 138 : Module module =
591 138 : new FactoryModule() {
592 : @Override
593 : protected void configure() {
594 138 : bindConstant().annotatedWith(SshEnabled.class).to(daemon.getEnableSshd());
595 138 : bind(AccountCreator.class);
596 138 : bind(AccountOperations.class).to(AccountOperationsImpl.class);
597 138 : bind(GroupOperations.class).to(GroupOperationsImpl.class);
598 138 : bind(ProjectOperations.class).to(ProjectOperationsImpl.class);
599 138 : bind(RequestScopeOperations.class).to(RequestScopeOperationsImpl.class);
600 138 : bind(ChangeOperations.class).to(ChangeOperationsImpl.class);
601 138 : factory(PerPatchsetOperationsImpl.Factory.class);
602 138 : factory(PerCommentOperationsImpl.Factory.class);
603 138 : factory(PerDraftCommentOperationsImpl.Factory.class);
604 138 : factory(PerRobotCommentOperationsImpl.Factory.class);
605 138 : factory(PushOneCommit.Factory.class);
606 138 : install(InProcessProtocol.module());
607 138 : install(new NoSshModule());
608 138 : install(new AsyncReceiveCommitsModule());
609 138 : factory(ProjectResetter.Builder.Factory.class);
610 138 : }
611 :
612 : @Provides
613 : @Singleton
614 : @Nullable
615 : @TestSshServerAddress
616 : InetSocketAddress getSshAddress(@GerritServerConfig Config cfg) {
617 138 : String addr = cfg.getString("sshd", null, "listenAddress");
618 : // We do not use InitSshd.isOff to avoid coupling GerritServer to the SSH code.
619 138 : return !"off".equalsIgnoreCase(addr)
620 20 : ? SocketUtil.resolve(cfg.getString("sshd", null, "listenAddress"), 0)
621 125 : : null;
622 : }
623 : };
624 138 : return sysInjector.createChildInjector(module);
625 : }
626 :
627 : private static Injector getInjector(Object obj, String field)
628 : throws SecurityException, NoSuchFieldException, IllegalArgumentException,
629 : IllegalAccessException {
630 138 : Field f = obj.getClass().getDeclaredField(field);
631 138 : f.setAccessible(true);
632 138 : Object v = f.get(obj);
633 138 : checkArgument(v instanceof Injector, "not an Injector: %s", v);
634 138 : return (Injector) f.get(obj);
635 : }
636 :
637 : private static InetAddress getLocalHost() {
638 138 : return InetAddress.getLoopbackAddress();
639 : }
640 :
641 : private final Description desc;
642 : private final Path sitePath;
643 :
644 : private Daemon daemon;
645 : private ExecutorService daemonService;
646 : private Injector testInjector;
647 : private String url;
648 : private InetSocketAddress httpAddress;
649 :
650 : private GerritServer(
651 : Description desc,
652 : @Nullable Path sitePath,
653 : Injector testInjector,
654 : Daemon daemon,
655 138 : @Nullable ExecutorService daemonService) {
656 138 : this.desc = requireNonNull(desc);
657 138 : this.sitePath = sitePath;
658 138 : this.testInjector = requireNonNull(testInjector);
659 138 : this.daemon = requireNonNull(daemon);
660 138 : this.daemonService = daemonService;
661 :
662 138 : Config cfg = testInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
663 138 : url = cfg.getString("gerrit", null, "canonicalWebUrl");
664 138 : URI uri = URI.create(url);
665 138 : httpAddress = new InetSocketAddress(uri.getHost(), uri.getPort());
666 138 : }
667 :
668 : public String getUrl() {
669 132 : return url;
670 : }
671 :
672 : InetSocketAddress getHttpAddress() {
673 6 : return httpAddress;
674 : }
675 :
676 : public Injector getTestInjector() {
677 138 : return testInjector;
678 : }
679 :
680 : public Injector getHttpdInjector() {
681 2 : return daemon.getHttpdInjector();
682 : }
683 :
684 : Description getDescription() {
685 0 : return desc;
686 : }
687 :
688 : public static GerritServer restartAsSlave(GerritServer server) throws Exception {
689 3 : checkState(server.desc.sandboxed(), "restarting as slave requires @Sandboxed");
690 :
691 3 : Path site = server.testInjector.getInstance(Key.get(Path.class, SitePath.class));
692 :
693 3 : Config cfg = server.testInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
694 3 : cfg.setBoolean("container", null, "replica", true);
695 :
696 3 : InMemoryRepositoryManager inMemoryRepoManager = null;
697 3 : if (hasBinding(server.testInjector, InMemoryRepositoryManager.class)) {
698 3 : inMemoryRepoManager = server.testInjector.getInstance(InMemoryRepositoryManager.class);
699 : }
700 :
701 3 : server.close();
702 3 : server.daemon.stop();
703 3 : return start(server.desc, cfg, site, null, null, null, inMemoryRepoManager);
704 : }
705 :
706 : public static GerritServer restart(
707 : GerritServer server, @Nullable Module testSysModule, @Nullable Module testSshModule)
708 : throws Exception {
709 1 : checkState(server.desc.sandboxed(), "restarting as slave requires @Sandboxed");
710 1 : Config cfg = server.testInjector.getInstance(Key.get(Config.class, GerritServerConfig.class));
711 1 : Path site = server.testInjector.getInstance(Key.get(Path.class, SitePath.class));
712 :
713 1 : InMemoryRepositoryManager inMemoryRepoManager = null;
714 1 : if (hasBinding(server.testInjector, InMemoryRepositoryManager.class)) {
715 1 : inMemoryRepoManager = server.testInjector.getInstance(InMemoryRepositoryManager.class);
716 : }
717 :
718 1 : server.close();
719 1 : server.daemon.stop();
720 1 : return start(server.desc, cfg, site, testSysModule, null, testSshModule, inMemoryRepoManager);
721 : }
722 :
723 : private static boolean hasBinding(Injector injector, Class<?> clazz) {
724 3 : return injector.getExistingBinding(Key.get(clazz)) != null;
725 : }
726 :
727 : @Override
728 : public void close() throws Exception {
729 138 : daemon.getLifecycleManager().stop();
730 138 : if (daemonService != null) {
731 15 : System.out.println("Gerrit Server Shutdown");
732 15 : daemonService.shutdownNow();
733 15 : daemonService.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
734 : }
735 138 : RepositoryCache.clear();
736 138 : }
737 :
738 : public Path getSitePath() {
739 0 : return sitePath;
740 : }
741 :
742 : @Override
743 : public String toString() {
744 0 : return MoreObjects.toStringHelper(this).addValue(desc).toString();
745 : }
746 : }
|