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:

committed by
Marco Miller

parent
b4b7c82382
commit
350fc68114
@@ -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
|
||||
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::
|
||||
+
|
||||
Set the timeout value for loading JavaScript plugins in Gerrit UI.
|
||||
|
@@ -215,7 +215,8 @@ public abstract class AbstractDaemonTest {
|
||||
beforeTest(description);
|
||||
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;
|
||||
base.evaluate();
|
||||
} finally {
|
||||
@@ -298,12 +299,16 @@ public abstract class AbstractDaemonTest {
|
||||
|
||||
@Before
|
||||
public void clearSender() {
|
||||
sender.clear();
|
||||
if (sender != null) {
|
||||
sender.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void startEventRecorder() {
|
||||
eventRecorder = eventRecorderFactory.create(admin);
|
||||
if (eventRecorderFactory != null) {
|
||||
eventRecorder = eventRecorderFactory.create(admin);
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
@@ -318,7 +323,9 @@ public abstract class AbstractDaemonTest {
|
||||
|
||||
@After
|
||||
public void closeEventRecorder() {
|
||||
eventRecorder.close();
|
||||
if (eventRecorder != null) {
|
||||
eventRecorder.close();
|
||||
}
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -20,6 +20,7 @@ import com.google.common.base.MoreObjects;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.LinkedHashMultimap;
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -50,8 +51,10 @@ import java.nio.file.Paths;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -87,6 +90,7 @@ public class PluginLoader implements LifecycleListener {
|
||||
private final Provider<String> urlProvider;
|
||||
private final PersistentCacheFactory persistentCacheFactory;
|
||||
private final boolean remoteAdmin;
|
||||
private final Set<String> mandatoryPlugins;
|
||||
private final UniversalServerPluginProvider serverPluginFactory;
|
||||
private final GerritRuntime gerritRuntime;
|
||||
|
||||
@@ -114,6 +118,8 @@ public class PluginLoader implements LifecycleListener {
|
||||
serverPluginFactory = pluginFactory;
|
||||
|
||||
remoteAdmin = cfg.getBoolean("plugins", null, "allowRemoteAdmin", false);
|
||||
mandatoryPlugins =
|
||||
ImmutableSet.copyOf(Arrays.asList(cfg.getStringList("plugins", null, "mandatory")));
|
||||
this.gerritRuntime = gerritRuntime;
|
||||
|
||||
long checkFrequency =
|
||||
@@ -381,50 +387,57 @@ public class PluginLoader implements LifecycleListener {
|
||||
}
|
||||
|
||||
public synchronized void rescan() {
|
||||
Set<String> loadedPlugins = new HashSet<>();
|
||||
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);
|
||||
|
||||
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)) {
|
||||
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);
|
||||
}
|
||||
Set<String> missingMandatory = Sets.difference(mandatoryPlugins, loadedPlugins);
|
||||
if (!missingMandatory.isEmpty()) {
|
||||
throw new MissingMandatoryPluginsException(missingMandatory);
|
||||
}
|
||||
|
||||
cleanInBackground();
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user