Merge branch 'stable-2.13' into stable-2.14
* stable-2.13:
Setup: Fix typo in method name
Option to setup a copy of the master site during init
Change-Id: I9bd02845d4ff7bb5d5170f423fe817faf1cb8700
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
index bae32a2..5bff79f 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -43,6 +43,7 @@
// main section
static final String MAIN_SECTION = "main";
static final String SHARED_DIRECTORY_KEY = "sharedDirectory";
+ static final String DEFAULT_SHARED_DIRECTORY = "shared";
// peerInfo section
static final String PEER_INFO_SECTION = "peerInfo";
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java
index 44461ef..5534927 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Setup.java
@@ -14,17 +14,46 @@
package com.ericsson.gerrit.plugins.highavailability;
-import static com.ericsson.gerrit.plugins.highavailability.Configuration.*;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.CACHE_SECTION;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.CLEANUP_INTERVAL_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.CLUSTER_NAME_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.CONNECTION_TIMEOUT_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_CLEANUP_INTERVAL;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_CLUSTER_NAME;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_MAX_TRIES;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_RETRY_INTERVAL;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_SHARED_DIRECTORY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_THREAD_POOL_SIZE;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_TIMEOUT_MS;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.HTTP_SECTION;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.INDEX_SECTION;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.JGROUPS_SUBSECTION;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.MAIN_SECTION;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.MAX_TRIES_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.PASSWORD_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.PEER_INFO_SECTION;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.RETRY_INTERVAL_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.SHARED_DIRECTORY_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.SOCKET_TIMEOUT_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.STATIC_SUBSECTION;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.STRATEGY_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.THREAD_POOL_SIZE_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.URL_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.USER_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.WEBSESSION_SECTION;
import com.ericsson.gerrit.plugins.highavailability.Configuration.PeerInfoStrategy;
import com.google.common.base.Strings;
import com.google.gerrit.common.FileUtil;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitFlags;
import com.google.gerrit.pgm.init.api.InitStep;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
+import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.Paths;
import java.util.EnumSet;
import java.util.Objects;
import org.eclipse.jgit.storage.file.FileBasedConfig;
@@ -34,14 +63,24 @@
private final ConsoleUI ui;
private final String pluginName;
+ private final InitFlags flags;
private final SitePaths site;
+ private final SetupLocalHAReplica setupLocalHAReplica;
+
private FileBasedConfig config;
@Inject
- public Setup(ConsoleUI ui, @PluginName String pluginName, SitePaths site) {
+ public Setup(
+ ConsoleUI ui,
+ @PluginName String pluginName,
+ InitFlags flags,
+ SitePaths site,
+ SetupLocalHAReplica setupLocalHAReplica) {
this.ui = ui;
this.pluginName = pluginName;
+ this.flags = flags;
this.site = site;
+ this.setupLocalHAReplica = setupLocalHAReplica;
}
@Override
@@ -54,23 +93,28 @@
Path pluginConfigFile = site.etc_dir.resolve(pluginName + ".config");
config = new FileBasedConfig(pluginConfigFile.toFile(), FS.DETECTED);
config.load();
- configureMainSection();
- configurePeerInfoSection();
configureHttp();
configureCacheSection();
configureIndexSection();
- configureWebsessiosSection();
- config.save();
+ configureWebsessionsSection();
+ if (!createHAReplicaSite(config)) {
+ configureMainSection();
+ configurePeerInfoSection();
+ config.save();
+ }
+ flags.cfg.setBoolean("database", "h2", "autoServer", true);
}
}
private void configureMainSection() {
ui.header("Main section");
- String sharedDir =
- promptAndSetString("Shared directory", MAIN_SECTION, SHARED_DIRECTORY_KEY, null);
- if (!Strings.isNullOrEmpty(sharedDir)) {
- Path shared = site.site_path.resolve(sharedDir);
- FileUtil.mkdirsOrDie(shared, "cannot create " + shared);
+ String sharedDirDefault = ui.isBatch() ? DEFAULT_SHARED_DIRECTORY : null;
+ String shared =
+ promptAndSetString(
+ "Shared directory", MAIN_SECTION, SHARED_DIRECTORY_KEY, sharedDirDefault);
+ if (!Strings.isNullOrEmpty(shared)) {
+ Path resolved = site.site_path.resolve(Paths.get(shared));
+ FileUtil.mkdirsOrDie(resolved, "cannot create " + resolved);
}
}
@@ -127,7 +171,7 @@
str(DEFAULT_THREAD_POOL_SIZE));
}
- private void configureWebsessiosSection() {
+ private void configureWebsessionsSection() {
ui.header("Websession section");
promptAndSetString(
"Cleanup interval", WEBSESSION_SECTION, CLEANUP_INTERVAL_KEY, DEFAULT_CLEANUP_INTERVAL);
@@ -156,6 +200,30 @@
return Integer.toString(n);
}
+ private boolean createHAReplicaSite(FileBasedConfig pluginConfig) throws Exception {
+ ui.header("HA replica site setup");
+ ui.message(
+ "It is possible to create a copy of the master site and configure both sites to run\n"
+ + "in HA mode as peers. This is possible when the directory where the copy will be\n"
+ + "created is accessible from this machine\n"
+ + "\n"
+ + "NOTE: This step is optional. If you want to create the other site manually, or\n"
+ + "if the other site needs to be created in a directory not accessible from this\n"
+ + "machine then please skip this step.\n");
+ if (ui.yesno(true, "Create a HA replica site")) {
+ String replicaPath = ui.readString("ha/1", "Location of the HA replica");
+ Path replica = site.site_path.resolve(Paths.get(replicaPath));
+ if (Files.exists(replica)) {
+ ui.message("%s already exists, exiting", replica);
+ return true;
+ }
+ config.save();
+ setupLocalHAReplica.run(new SitePaths(replica), pluginConfig);
+ return true;
+ }
+ return false;
+ }
+
@Override
public void postRun() throws Exception {}
}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/SetupLocalHAReplica.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/SetupLocalHAReplica.java
new file mode 100644
index 0000000..53d33ed
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/SetupLocalHAReplica.java
@@ -0,0 +1,189 @@
+// Copyright (C) 2017 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.ericsson.gerrit.plugins.highavailability;
+
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.CLUSTER_NAME_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_CLUSTER_NAME;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.DEFAULT_SHARED_DIRECTORY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.JGROUPS_SUBSECTION;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.MAIN_SECTION;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.PEER_INFO_SECTION;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.SHARED_DIRECTORY_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.STRATEGY_KEY;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.FileUtil;
+import com.google.gerrit.pgm.init.api.InitFlags;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.inject.Inject;
+import java.io.IOException;
+import java.nio.file.FileVisitOption;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.List;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+
+class SetupLocalHAReplica {
+ private final SitePaths master;
+ private final FileBasedConfig masterConfig;
+
+ private Path sharedDir;
+ private SitePaths replica;
+
+ @Inject
+ SetupLocalHAReplica(SitePaths master, InitFlags flags) {
+ this.master = master;
+ this.masterConfig = flags.cfg;
+ this.sharedDir = master.site_path.resolve(DEFAULT_SHARED_DIRECTORY);
+ }
+
+ void run(SitePaths replica, FileBasedConfig pluginConfig) throws Exception {
+ this.replica = replica;
+
+ FileUtil.mkdirsOrDie(replica.site_path, "cannot create " + replica.site_path);
+
+ configureMainSection(pluginConfig);
+ configurePeerInfo(pluginConfig);
+
+ for (Path dir : listDirsForCopy()) {
+ copyFiles(dir);
+ }
+
+ mkdir(replica.logs_dir);
+ mkdir(replica.tmp_dir);
+ symlink(Paths.get(masterConfig.getString("gerrit", null, "basePath")));
+ symlink(sharedDir);
+
+ FileBasedConfig replicaConfig =
+ new FileBasedConfig(replica.gerrit_config.toFile(), FS.DETECTED);
+ replicaConfig.load();
+
+ if ("h2".equals(masterConfig.getString("database", null, "type"))) {
+ masterConfig.setBoolean("database", "h2", "autoServer", true);
+ replicaConfig.setBoolean("database", "h2", "autoServer", true);
+ symlinkH2ReviewDbDir();
+ }
+ }
+
+ private List<Path> listDirsForCopy() throws IOException {
+ ImmutableList.Builder<Path> toSkipBuilder = ImmutableList.builder();
+ toSkipBuilder.add(
+ master.resolve(masterConfig.getString("gerrit", null, "basePath")),
+ master.db_dir,
+ master.logs_dir,
+ replica.site_path,
+ master.site_path.resolve(sharedDir),
+ master.tmp_dir);
+ if ("h2".equals(masterConfig.getString("database", null, "type"))) {
+ toSkipBuilder.add(
+ master.resolve(masterConfig.getString("database", null, "database")).getParent());
+ }
+ final ImmutableList<Path> toSkip = toSkipBuilder.build();
+
+ final ArrayList<Path> dirsForCopy = new ArrayList<>();
+ Files.walkFileTree(
+ master.site_path,
+ EnumSet.of(FileVisitOption.FOLLOW_LINKS),
+ Integer.MAX_VALUE,
+ new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
+ throws IOException {
+ if (Files.isSameFile(dir, master.site_path)) {
+ return FileVisitResult.CONTINUE;
+ }
+
+ Path p = master.site_path.relativize(dir);
+ if (shouldSkip(p)) {
+ return FileVisitResult.SKIP_SUBTREE;
+ }
+ dirsForCopy.add(p);
+ return FileVisitResult.CONTINUE;
+ }
+
+ private boolean shouldSkip(Path p) throws IOException {
+ Path resolved = master.site_path.resolve(p);
+ for (Path skip : toSkip) {
+ if (Files.exists(skip) && Files.isSameFile(resolved, skip)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+
+ return dirsForCopy;
+ }
+
+ private void copyFiles(Path dir) throws IOException {
+ final Path source = master.site_path.resolve(dir);
+ final Path target = replica.site_path.resolve(dir);
+ Files.createDirectories(target);
+ Files.walkFileTree(
+ source,
+ EnumSet.noneOf(FileVisitOption.class),
+ 1,
+ new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+ throws IOException {
+ Path f = source.relativize(file);
+ if (Files.isRegularFile(file)) {
+ Files.copy(file, target.resolve(f));
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+
+ private static void mkdir(Path dir) throws IOException {
+ Files.createDirectories(dir);
+ }
+
+ private void symlink(Path path) throws IOException {
+ if (!path.isAbsolute()) {
+ Files.createSymbolicLink(
+ replica.site_path.resolve(path),
+ master.site_path.resolve(path).toAbsolutePath().normalize());
+ }
+ }
+
+ private void symlinkH2ReviewDbDir() throws IOException {
+ symlink(Paths.get(masterConfig.getString("database", null, "database")).getParent());
+ }
+
+ private void configureMainSection(FileBasedConfig pluginConfig) throws IOException {
+ pluginConfig.setString(
+ MAIN_SECTION,
+ null,
+ SHARED_DIRECTORY_KEY,
+ master.site_path.relativize(sharedDir).toString());
+ pluginConfig.save();
+ }
+
+ private void configurePeerInfo(FileBasedConfig pluginConfig) throws IOException {
+ pluginConfig.setString(PEER_INFO_SECTION, null, STRATEGY_KEY, "jgroups");
+ pluginConfig.setString(
+ PEER_INFO_SECTION, JGROUPS_SUBSECTION, CLUSTER_NAME_KEY, DEFAULT_CLUSTER_NAME);
+ pluginConfig.save();
+ }
+}
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 80998d5..e537545 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -34,7 +34,8 @@
When given as a relative path, then it is resolved against the $SITE_PATH
or Gerrit server. For example, if $SITE_PATH is "/gerrit/root" and
sharedDirectory is given as "shared/dir" then the real path of the shared
- directory is "/gerrit/root/shared/dir".
+ directory is "/gerrit/root/shared/dir". When not specified, the default
+ is "shared".
peerInfo.strategy
: Strategy to find other peers. Supported strategies are `static` or `jgroups`.