Allow to configure mandatory plugins

In gerrit.config we can now define a list of mandatory plugins:

  [plugins]
    mandatory = replication
    mandatory = hooks

If a mandatory plugin does not load, either because it is not present in
the plugins directory or because it fails to load, Gerrit start will
fail.

Change-Id: I26c578051b0f91844bab0a9b46e0fac34d88a15e
This commit is contained in:
Saša Živkov
2019-05-13 14:13:51 +02:00
committed by Marco Miller
parent b4b7c82382
commit 350fc68114
5 changed files with 142 additions and 45 deletions

View File

@@ -3481,6 +3481,11 @@ Enable remote installation, enable and disable of plugins over HTTP
and SSH. If set to true Administrators can install new plugins and SSH. If set to true Administrators can install new plugins
remotely, or disable existing plugins. Defaults to false. remotely, or disable existing plugins. Defaults to false.
[[plugins.mandatory]]plugins.mandatory::
+
List of mandatory plugins. If a plugin from this list does not load,
Gerrit start will fail.
[[plugins.jsLoadTimeout]]plugins.jsLoadTimeout:: [[plugins.jsLoadTimeout]]plugins.jsLoadTimeout::
+ +
Set the timeout value for loading JavaScript plugins in Gerrit UI. Set the timeout value for loading JavaScript plugins in Gerrit UI.

View File

@@ -215,7 +215,8 @@ public abstract class AbstractDaemonTest {
beforeTest(description); beforeTest(description);
ProjectResetter.Config input = requireNonNull(resetProjects()); ProjectResetter.Config input = requireNonNull(resetProjects());
try (ProjectResetter resetter = projectResetter.builder().build(input)) { try (ProjectResetter resetter =
projectResetter != null ? projectResetter.builder().build(input) : null) {
AbstractDaemonTest.this.resetter = resetter; AbstractDaemonTest.this.resetter = resetter;
base.evaluate(); base.evaluate();
} finally { } finally {
@@ -298,12 +299,16 @@ public abstract class AbstractDaemonTest {
@Before @Before
public void clearSender() { public void clearSender() {
sender.clear(); if (sender != null) {
sender.clear();
}
} }
@Before @Before
public void startEventRecorder() { public void startEventRecorder() {
eventRecorder = eventRecorderFactory.create(admin); if (eventRecorderFactory != null) {
eventRecorder = eventRecorderFactory.create(admin);
}
} }
@Before @Before
@@ -318,7 +323,9 @@ public abstract class AbstractDaemonTest {
@After @After
public void closeEventRecorder() { public void closeEventRecorder() {
eventRecorder.close(); if (eventRecorder != null) {
eventRecorder.close();
}
} }
@AfterClass @AfterClass

View File

@@ -0,0 +1,30 @@
// Copyright (C) 2019 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.server.plugins;
import java.util.Collection;
/** Raised when one or more mandatory plugins are missing. */
public class MissingMandatoryPluginsException extends RuntimeException {
private static final long serialVersionUID = 1L;
public MissingMandatoryPluginsException(Collection<String> pluginNames) {
super(getMessage(pluginNames));
}
private static String getMessage(Collection<String> pluginNames) {
return String.format("Cannot find or load the following mandatory plugins: %s", pluginNames);
}
}

View File

@@ -20,6 +20,7 @@ import com.google.common.base.MoreObjects;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ComparisonChain; import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@@ -50,8 +51,10 @@ import java.nio.file.Paths;
import java.util.AbstractMap; import java.util.AbstractMap;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -87,6 +90,7 @@ public class PluginLoader implements LifecycleListener {
private final Provider<String> urlProvider; private final Provider<String> urlProvider;
private final PersistentCacheFactory persistentCacheFactory; private final PersistentCacheFactory persistentCacheFactory;
private final boolean remoteAdmin; private final boolean remoteAdmin;
private final Set<String> mandatoryPlugins;
private final UniversalServerPluginProvider serverPluginFactory; private final UniversalServerPluginProvider serverPluginFactory;
private final GerritRuntime gerritRuntime; private final GerritRuntime gerritRuntime;
@@ -114,6 +118,8 @@ public class PluginLoader implements LifecycleListener {
serverPluginFactory = pluginFactory; serverPluginFactory = pluginFactory;
remoteAdmin = cfg.getBoolean("plugins", null, "allowRemoteAdmin", false); remoteAdmin = cfg.getBoolean("plugins", null, "allowRemoteAdmin", false);
mandatoryPlugins =
ImmutableSet.copyOf(Arrays.asList(cfg.getStringList("plugins", null, "mandatory")));
this.gerritRuntime = gerritRuntime; this.gerritRuntime = gerritRuntime;
long checkFrequency = long checkFrequency =
@@ -381,50 +387,57 @@ public class PluginLoader implements LifecycleListener {
} }
public synchronized void rescan() { public synchronized void rescan() {
Set<String> loadedPlugins = new HashSet<>();
SetMultimap<String, Path> pluginsFiles = prunePlugins(pluginsDir); SetMultimap<String, Path> pluginsFiles = prunePlugins(pluginsDir);
if (pluginsFiles.isEmpty()) {
return; if (!pluginsFiles.isEmpty()) {
syncDisabledPlugins(pluginsFiles);
Map<String, Path> activePlugins = filterDisabled(pluginsFiles);
for (Map.Entry<String, Path> entry : jarsFirstSortedPluginsSet(activePlugins)) {
String name = entry.getKey();
Path path = entry.getValue();
String fileName = path.getFileName().toString();
if (!isUiPlugin(fileName) && !serverPluginFactory.handles(path)) {
logger.atWarning().log(
"No Plugin provider was found that handles this file format: %s", fileName);
continue;
}
FileSnapshot brokenTime = broken.get(name);
if (brokenTime != null && !brokenTime.isModified(path.toFile())) {
continue;
}
Plugin active = running.get(name);
if (active != null && !active.isModified(path)) {
loadedPlugins.add(name);
continue;
}
if (active != null) {
logger.atInfo().log("Reloading plugin %s", active.getName());
}
try {
Plugin loadedPlugin = runPlugin(name, path, active);
if (!loadedPlugin.isDisabled()) {
loadedPlugins.add(name);
logger.atInfo().log(
"%s plugin %s, version %s",
active == null ? "Loaded" : "Reloaded",
loadedPlugin.getName(),
loadedPlugin.getVersion());
}
} catch (PluginInstallException e) {
logger.atWarning().withCause(e.getCause()).log("Cannot load plugin %s", name);
}
}
} }
syncDisabledPlugins(pluginsFiles); Set<String> missingMandatory = Sets.difference(mandatoryPlugins, loadedPlugins);
if (!missingMandatory.isEmpty()) {
Map<String, Path> activePlugins = filterDisabled(pluginsFiles); throw new MissingMandatoryPluginsException(missingMandatory);
for (Map.Entry<String, Path> entry : jarsFirstSortedPluginsSet(activePlugins)) {
String name = entry.getKey();
Path path = entry.getValue();
String fileName = path.getFileName().toString();
if (!isUiPlugin(fileName) && !serverPluginFactory.handles(path)) {
logger.atWarning().log(
"No Plugin provider was found that handles this file format: %s", fileName);
continue;
}
FileSnapshot brokenTime = broken.get(name);
if (brokenTime != null && !brokenTime.isModified(path.toFile())) {
continue;
}
Plugin active = running.get(name);
if (active != null && !active.isModified(path)) {
continue;
}
if (active != null) {
logger.atInfo().log("Reloading plugin %s", active.getName());
}
try {
Plugin loadedPlugin = runPlugin(name, path, active);
if (!loadedPlugin.isDisabled()) {
logger.atInfo().log(
"%s plugin %s, version %s",
active == null ? "Loaded" : "Reloaded",
loadedPlugin.getName(),
loadedPlugin.getVersion());
}
} catch (PluginInstallException e) {
logger.atWarning().withCause(e.getCause()).log("Cannot load plugin %s", name);
}
} }
cleanInBackground(); cleanInBackground();

View File

@@ -0,0 +1,42 @@
// Copyright (C) 2019 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.api.plugin;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.server.plugins.MissingMandatoryPluginsException;
import org.junit.Test;
import org.junit.runner.Description;
@NoHttpd
public class PluginLoaderIT extends AbstractDaemonTest {
Description testDescription;
@Override
protected void beforeTest(Description description) throws Exception {
this.testDescription = description;
}
@Override
protected void afterTest() throws Exception {}
@Test(expected = MissingMandatoryPluginsException.class)
@GerritConfig(name = "plugins.mandatory", value = "my-mandatory-plugin")
public void shouldFailToStartGerritWhenMandatoryPluginsAreMissing() throws Exception {
super.beforeTest(testDescription);
}
}