Skip to content

Commit

Permalink
Implement a dynamic reload mechanism for Besu plugins. (#261)
Browse files Browse the repository at this point in the history
* Implement a dynamic reload mechanism for Besu plugins.

- Added `reloadConfiguration` method in `plugin-api`.
- Added `admin_reloadPlugin` RPC endpoint.
    - if the first parameter is specified the API will attempt to reload the individual plugin if found in the map.
    - if no parameter is specified the API will attempt to reload all plugins.
- Added method in `BesuPluginContextImpl` to retrieve a map of named plugins.


Signed-off-by: Abdelhamid Bakhta <[email protected]>
  • Loading branch information
AbdelStark authored Jan 7, 2020
1 parent 6e67988 commit 94b26dd
Show file tree
Hide file tree
Showing 20 changed files with 211 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ public void startNode(final BesuNode node) {
node.getStaticNodes().stream()
.map(EnodeURL::fromString)
.collect(Collectors.toList()))
.besuPluginContext(new BesuPluginContextImpl())
.build();

runner.start();
Expand Down
20 changes: 16 additions & 4 deletions besu/src/main/java/org/hyperledger/besu/RunnerBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@
import org.hyperledger.besu.metrics.prometheus.MetricsService;
import org.hyperledger.besu.nat.NatMethod;
import org.hyperledger.besu.nat.upnp.UpnpNatManager;
import org.hyperledger.besu.plugin.BesuPlugin;
import org.hyperledger.besu.services.BesuPluginContextImpl;
import org.hyperledger.besu.util.NetworkUtility;

import java.io.IOException;
Expand Down Expand Up @@ -134,6 +136,7 @@ public class RunnerBuilder {
private Optional<PermissioningConfiguration> permissioningConfiguration = Optional.empty();
private Collection<EnodeURL> staticNodes = Collections.emptyList();
private Optional<String> identityString = Optional.empty();
private BesuPluginContextImpl besuPluginContext;

public RunnerBuilder vertx(final Vertx vertx) {
this.vertx = vertx;
Expand Down Expand Up @@ -255,6 +258,11 @@ public RunnerBuilder identityString(final Optional<String> identityString) {
return this;
}

public RunnerBuilder besuPluginContext(final BesuPluginContextImpl besuPluginContext) {
this.besuPluginContext = besuPluginContext;
return this;
}

public Runner build() {

Preconditions.checkNotNull(besuController);
Expand Down Expand Up @@ -419,7 +427,8 @@ public Runner build() {
privacyParameters,
jsonRpcConfiguration,
webSocketConfiguration,
metricsConfiguration);
metricsConfiguration,
besuPluginContext.getNamedPlugins());
jsonRpcHttpService =
Optional.of(
new JsonRpcHttpService(
Expand Down Expand Up @@ -476,7 +485,8 @@ public Runner build() {
privacyParameters,
jsonRpcConfiguration,
webSocketConfiguration,
metricsConfiguration);
metricsConfiguration,
besuPluginContext.getNamedPlugins());

final SubscriptionManager subscriptionManager =
createSubscriptionManager(vertx, transactionPool);
Expand Down Expand Up @@ -605,7 +615,8 @@ private Map<String, JsonRpcMethod> jsonRpcMethods(
final PrivacyParameters privacyParameters,
final JsonRpcConfiguration jsonRpcConfiguration,
final WebSocketConfiguration webSocketConfiguration,
final MetricsConfiguration metricsConfiguration) {
final MetricsConfiguration metricsConfiguration,
final Map<String, BesuPlugin> namedPlugins) {
final Map<String, JsonRpcMethod> methods =
new JsonRpcMethodsFactory()
.methods(
Expand All @@ -627,7 +638,8 @@ private Map<String, JsonRpcMethod> jsonRpcMethods(
privacyParameters,
jsonRpcConfiguration,
webSocketConfiguration,
metricsConfiguration);
metricsConfiguration,
namedPlugins);
methods.putAll(besuController.getAdditionalJsonRpcMethods(jsonRpcApis));
return methods;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1522,6 +1522,7 @@ private void synchronize(
.metricsConfiguration(metricsConfiguration)
.staticNodes(staticNodes)
.identityString(identityString)
.besuPluginContext(besuPluginContext)
.build();

addShutdownHook(runner);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.Optional;
import java.util.ServiceLoader;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.google.common.annotations.VisibleForTesting;
Expand Down Expand Up @@ -211,4 +212,10 @@ private Optional<ClassLoader> pluginDirectoryLoader(final Path pluginsDir) {

return Optional.empty();
}

public Map<String, BesuPlugin> getNamedPlugins() {
return plugins.stream()
.filter(plugin -> plugin.getName().isPresent())
.collect(Collectors.toMap(plugin -> plugin.getName().get(), plugin -> plugin, (a, b) -> b));
}
}
2 changes: 2 additions & 0 deletions besu/src/test/java/org/hyperledger/besu/RunnerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import org.hyperledger.besu.plugin.services.storage.rocksdb.RocksDBMetricsFactory;
import org.hyperledger.besu.plugin.services.storage.rocksdb.configuration.RocksDBFactoryConfiguration;
import org.hyperledger.besu.services.BesuConfigurationImpl;
import org.hyperledger.besu.services.BesuPluginContextImpl;
import org.hyperledger.besu.testutil.TestClock;

import java.math.BigInteger;
Expand Down Expand Up @@ -211,6 +212,7 @@ private void syncFromGenesis(final SyncMode mode, final GenesisConfigFile genesi
.webSocketConfiguration(aheadWebSocketConfiguration)
.metricsConfiguration(aheadMetricsConfiguration)
.dataDir(dbAhead)
.besuPluginContext(new BesuPluginContextImpl())
.build();
try {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ public void initMocks() throws Exception {
when(mockRunnerBuilder.metricsConfiguration(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.staticNodes(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.identityString(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.besuPluginContext(any())).thenReturn(mockRunnerBuilder);
when(mockRunnerBuilder.build()).thenReturn(mockRunner);

when(storageService.getByName("rocksdb")).thenReturn(Optional.of(rocksDBStorageFactory));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -125,6 +126,7 @@ public Map<String, JsonRpcMethod> methods() {
privacyParameters,
jsonRpcConfiguration,
webSocketConfiguration,
metricsConfiguration);
metricsConfiguration,
new HashMap<>());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class RpcApis {
public static final RpcApi TX_POOL = new RpcApi("TXPOOL");
// Disable TRACE functionality while under development
// public static final RpcApi TRACE = new RpcApi("TRACE");
public static final RpcApi PLUGINS = new RpcApi("PLUGINS");

public static final List<RpcApi> DEFAULT_JSON_RPC_APIS = Arrays.asList(ETH, NET, WEB3);

Expand Down Expand Up @@ -59,6 +60,8 @@ public static Optional<RpcApi> valueOf(final String name) {
// Disable TRACE functionality while under development
// } else if (name.equals(TRACE.getCliValue())) {
// return Optional.of(TRACE);
} else if (name.equals(PLUGINS.getCliValue())) {
return Optional.of(PLUGINS);
} else {
return Optional.empty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ public enum RpcMethod {
TX_POOL_BESU_STATISTICS("txpool_besuStatistics"),
TX_POOL_BESU_TRANSACTIONS("txpool_besuTransactions"),
WEB3_CLIENT_VERSION("web3_clientVersion"),
WEB3_SHA3("web3_sha3");
WEB3_SHA3("web3_sha3"),
PLUGINS_RELOAD_CONFIG("plugins_reloadPluginConfig");

private final String methodName;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods;

import org.hyperledger.besu.ethereum.api.jsonrpc.RpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.JsonRpcRequestContext;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.exception.InvalidJsonRpcParameters;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcError;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcErrorResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcResponse;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.response.JsonRpcSuccessResponse;
import org.hyperledger.besu.plugin.BesuPlugin;

import java.util.Map;
import java.util.concurrent.CompletableFuture;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class PluginsReloadConfiguration implements JsonRpcMethod {

private static final Logger LOG = LogManager.getLogger();
private final Map<String, BesuPlugin> namedPlugins;

public PluginsReloadConfiguration(final Map<String, BesuPlugin> namedPlugins) {
this.namedPlugins = namedPlugins;
}

@Override
public String getName() {
return RpcMethod.PLUGINS_RELOAD_CONFIG.getMethodName();
}

@Override
public JsonRpcResponse response(final JsonRpcRequestContext requestContext) {
try {
final String pluginName = requestContext.getRequiredParameter(0, String.class);
if (!namedPlugins.containsKey(pluginName)) {
LOG.error(
"Plugin cannot be reloaded because no plugin has been registered with specified name: {}.",
pluginName);
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.INTERNAL_ERROR);
}
reloadPluginConfig(namedPlugins.get(pluginName));
return new JsonRpcSuccessResponse(requestContext.getRequest().getId());
} catch (InvalidJsonRpcParameters invalidJsonRpcParameters) {
return new JsonRpcErrorResponse(
requestContext.getRequest().getId(), JsonRpcError.INVALID_PARAMS);
}
}

private void reloadPluginConfig(final BesuPlugin plugin) {
final String name = plugin.getName().orElseThrow();
LOG.info("Reloading plugin configuration: {}.", name);
final CompletableFuture<Void> future = plugin.reloadConfiguration();
future.thenAcceptAsync(aVoid -> LOG.info("Plugin {} has been reloaded.", name));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.AdminPeers;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.AdminRemovePeer;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.PluginsReloadConfiguration;
import org.hyperledger.besu.ethereum.api.query.BlockchainQueries;
import org.hyperledger.besu.ethereum.p2p.network.P2PNetwork;
import org.hyperledger.besu.plugin.BesuPlugin;

import java.math.BigInteger;
import java.util.Map;
Expand All @@ -37,18 +39,21 @@ public class AdminJsonRpcMethods extends ApiGroupJsonRpcMethods {
private final GenesisConfigOptions genesisConfigOptions;
private final P2PNetwork p2pNetwork;
private final BlockchainQueries blockchainQueries;
private final Map<String, BesuPlugin> namedPlugins;

public AdminJsonRpcMethods(
final String clientVersion,
final BigInteger networkId,
final GenesisConfigOptions genesisConfigOptions,
final P2PNetwork p2pNetwork,
final BlockchainQueries blockchainQueries) {
final BlockchainQueries blockchainQueries,
final Map<String, BesuPlugin> namedPlugins) {
this.clientVersion = clientVersion;
this.networkId = networkId;
this.genesisConfigOptions = genesisConfigOptions;
this.p2pNetwork = p2pNetwork;
this.blockchainQueries = blockchainQueries;
this.namedPlugins = namedPlugins;
}

@Override
Expand All @@ -65,6 +70,7 @@ protected Map<String, JsonRpcMethod> create() {
clientVersion, networkId, genesisConfigOptions, p2pNetwork, blockchainQueries),
new AdminPeers(p2pNetwork),
new AdminChangeLogLevel(),
new AdminGenerateLogBloomCache(blockchainQueries));
new AdminGenerateLogBloomCache(blockchainQueries),
new PluginsReloadConfiguration(namedPlugins));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.hyperledger.besu.ethereum.permissioning.NodeLocalConfigPermissioningController;
import org.hyperledger.besu.metrics.ObservableMetricsSystem;
import org.hyperledger.besu.metrics.prometheus.MetricsConfiguration;
import org.hyperledger.besu.plugin.BesuPlugin;

import java.math.BigInteger;
import java.util.Collection;
Expand Down Expand Up @@ -63,7 +64,8 @@ public Map<String, JsonRpcMethod> methods(
final PrivacyParameters privacyParameters,
final JsonRpcConfiguration jsonRpcConfiguration,
final WebSocketConfiguration webSocketConfiguration,
final MetricsConfiguration metricsConfiguration) {
final MetricsConfiguration metricsConfiguration,
final Map<String, BesuPlugin> namedPlugins) {
final Map<String, JsonRpcMethod> enabled = new HashMap<>();

if (!rpcApis.isEmpty()) {
Expand All @@ -73,7 +75,12 @@ public Map<String, JsonRpcMethod> methods(
final List<JsonRpcMethods> availableApiGroups =
List.of(
new AdminJsonRpcMethods(
clientVersion, networkId, genesisConfigOptions, p2pNetwork, blockchainQueries),
clientVersion,
networkId,
genesisConfigOptions,
p2pNetwork,
blockchainQueries,
namedPlugins),
new DebugJsonRpcMethods(blockchainQueries, protocolSchedule, metricsSystem),
new EeaJsonRpcMethods(
blockchainQueries, protocolSchedule, transactionPool, privacyParameters),
Expand All @@ -98,7 +105,8 @@ public Map<String, JsonRpcMethod> methods(
new Web3JsonRpcMethods(clientVersion),
// TRACE Methods (Disabled while under development)
// new TraceJsonRpcMethods(blockchainQueries,protocolSchedule)
new TxPoolJsonRpcMethods(transactionPool));
new TxPoolJsonRpcMethods(transactionPool),
new PluginsJsonRpcMethods(namedPlugins));

for (final JsonRpcMethods apiGroup : availableApiGroups) {
enabled.putAll(apiGroup.create(rpcApis));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.methods;

import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApi;
import org.hyperledger.besu.ethereum.api.jsonrpc.RpcApis;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.JsonRpcMethod;
import org.hyperledger.besu.ethereum.api.jsonrpc.internal.methods.PluginsReloadConfiguration;
import org.hyperledger.besu.plugin.BesuPlugin;

import java.util.Map;

public class PluginsJsonRpcMethods extends ApiGroupJsonRpcMethods {

private final Map<String, BesuPlugin> namedPlugins;

public PluginsJsonRpcMethods(final Map<String, BesuPlugin> namedPlugins) {
this.namedPlugins = namedPlugins;
}

@Override
protected RpcApi getApiGroup() {
return RpcApis.PLUGINS;
}

@Override
protected Map<String, JsonRpcMethod> create() {
return mapOf(new PluginsReloadConfiguration(namedPlugins));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -158,7 +159,8 @@ protected Map<String, JsonRpcMethod> getRpcMethods(
privacyParameters,
config,
mock(WebSocketConfiguration.class),
mock(MetricsConfiguration.class));
mock(MetricsConfiguration.class),
new HashMap<>());
}

protected void startService() throws Exception {
Expand Down
Loading

0 comments on commit 94b26dd

Please sign in to comment.