-
Notifications
You must be signed in to change notification settings - Fork 130
Plugin Framework #1435
Plugin Framework #1435
Changes from all commits
49ebfc3
0dead54
fb486a2
4f0b4ef
6dfb20a
613705b
ce7928b
042150d
3ae2fce
b9b5e46
3afabf6
efeabf7
4640c8c
94f0ba8
92b383b
581c842
e5a4583
ebd4246
a79fb04
9c96867
2c81b2b
b0d0774
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -96,6 +96,8 @@ public class PantheonNode implements NodeConfiguration, RunnableNode, AutoClosea | |
private HttpRequestFactory httpRequestFactory; | ||
private boolean useWsForJsonRpc = false; | ||
private String token = null; | ||
private final List<String> plugins = new ArrayList<>(); | ||
private final List<String> extraCLIOptions; | ||
|
||
public PantheonNode( | ||
final String name, | ||
|
@@ -110,15 +112,17 @@ public PantheonNode( | |
final GenesisConfigProvider genesisConfigProvider, | ||
final boolean p2pEnabled, | ||
final boolean discoveryEnabled, | ||
final boolean bootnodeEligible) | ||
final boolean bootnodeEligible, | ||
final List<String> plugins, | ||
final List<String> extraCLIOptions) | ||
throws IOException { | ||
this.bootnodeEligible = bootnodeEligible; | ||
this.homeDirectory = Files.createTempDirectory("acctest"); | ||
keyfilePath.ifPresent( | ||
path -> { | ||
try { | ||
copyResource(path, homeDirectory.resolve("key")); | ||
} catch (IOException e) { | ||
} catch (final IOException e) { | ||
LOG.error("Could not find key file \"{}\" in resources", path); | ||
} | ||
}); | ||
|
@@ -135,6 +139,18 @@ public PantheonNode( | |
this.devMode = devMode; | ||
this.p2pEnabled = p2pEnabled; | ||
this.discoveryEnabled = discoveryEnabled; | ||
plugins.forEach( | ||
pluginName -> { | ||
try { | ||
homeDirectory.resolve("plugins").toFile().mkdirs(); | ||
copyResource( | ||
pluginName + ".jar", homeDirectory.resolve("plugins/" + pluginName + ".jar")); | ||
PantheonNode.this.plugins.add(pluginName); | ||
} catch (final IOException e) { | ||
LOG.error("Could not find plugin \"{}\" in resources", pluginName); | ||
} | ||
}); | ||
this.extraCLIOptions = extraCLIOptions; | ||
LOG.info("Created PantheonNode {}", this.toString()); | ||
} | ||
|
||
|
@@ -391,7 +407,7 @@ public Address getAddress() { | |
return Util.publicKeyToAddress(keyPair.getPublicKey()); | ||
} | ||
|
||
Path homeDirectory() { | ||
public Path homeDirectory() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (nit) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +1 for consistency of getters. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed at the interface, along with other inconsistent names. |
||
return homeDirectory; | ||
} | ||
|
||
|
@@ -481,6 +497,15 @@ Optional<PermissioningConfiguration> getPermissioningConfiguration() { | |
return permissioningConfiguration; | ||
} | ||
|
||
public List<String> getPlugins() { | ||
return plugins; | ||
} | ||
|
||
@Override | ||
public List<String> getExtraCLIOptions() { | ||
return extraCLIOptions; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return MoreObjects.toStringHelper(this) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -177,13 +177,21 @@ public void startNode(final PantheonNode node) { | |
params.add(permissioningConfiguration.getNodeSmartContractAddress().toString()); | ||
} | ||
}); | ||
params.addAll(node.getExtraCLIOptions()); | ||
|
||
LOG.info("Creating pantheon process with params {}", params); | ||
final ProcessBuilder processBuilder = | ||
new ProcessBuilder(params) | ||
.directory(new File(System.getProperty("user.dir")).getParentFile()) | ||
.redirectErrorStream(true) | ||
.redirectInput(Redirect.INHERIT); | ||
if (!node.getPlugins().isEmpty()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm quite sceptical to put environment variables directly in the code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a test harness and environmental variables is the documented way we add Java options (such as memory) to the invocation. This reflects real world usage. |
||
processBuilder | ||
.environment() | ||
.put( | ||
"PANTHEON_OPTS", | ||
"-Dpantheon.plugins.dir=" + dataDir.resolve("plugins").toAbsolutePath().toString()); | ||
} | ||
|
||
try { | ||
final Process process = processBuilder.start(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
/* | ||
* Copyright 2019 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. | ||
*/ | ||
package tech.pegasys.pantheon.tests.acceptance.plugins; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
import tech.pegasys.pantheon.tests.acceptance.dsl.AcceptanceTestBase; | ||
import tech.pegasys.pantheon.tests.acceptance.dsl.node.PantheonNode; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.Collections; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.stream.Stream; | ||
|
||
import org.awaitility.Awaitility; | ||
import org.junit.Before; | ||
import org.junit.Ignore; | ||
import org.junit.Test; | ||
|
||
public class PluginsAcceptanceTest extends AcceptanceTestBase { | ||
private PantheonNode node; | ||
|
||
// context: https://en.wikipedia.org/wiki/The_Magic_Words_are_Squeamish_Ossifrage | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice 😄 |
||
private static final String MAGIC_WORDS = "Squemish Ossifrage"; | ||
|
||
@Before | ||
public void setUp() throws Exception { | ||
node = | ||
pantheon.createPluginsNode( | ||
"node1", | ||
Collections.singletonList("testPlugin"), | ||
Collections.singletonList("--Xtest-option=" + MAGIC_WORDS)); | ||
cluster.start(node); | ||
} | ||
|
||
@Test | ||
public void shouldRegister() throws IOException { | ||
final Path registrationFile = node.homeDirectory().resolve("plugins/testPlugin.registered"); | ||
waitForFile(registrationFile); | ||
|
||
// this assert is false as CLI will not be parsed at this point | ||
assertThat(Files.readAllLines(registrationFile).stream().anyMatch(s -> s.contains(MAGIC_WORDS))) | ||
.isFalse(); | ||
} | ||
|
||
@Test | ||
public void shouldStart() throws IOException { | ||
final Path registrationFile = node.homeDirectory().resolve("plugins/testPlugin.started"); | ||
waitForFile(registrationFile); | ||
|
||
// this assert is true as CLI will be parsed at this point | ||
assertThat(Files.readAllLines(registrationFile).stream().anyMatch(s -> s.contains(MAGIC_WORDS))) | ||
.isTrue(); | ||
} | ||
|
||
@Test | ||
@Ignore("No way to do a graceful shutdown of Pantheon at the moment.") | ||
public void shouldStop() { | ||
cluster.stopNode(node); | ||
waitForFile(node.homeDirectory().resolve("plugins/testPlugin.stopped")); | ||
} | ||
|
||
private void waitForFile(final Path path) { | ||
final File file = path.toFile(); | ||
Awaitility.waitAtMost(30, TimeUnit.SECONDS) | ||
.until( | ||
() -> { | ||
if (file.exists()) { | ||
try (final Stream<String> s = Files.lines(path)) { | ||
return s.count() > 0; | ||
} | ||
} else { | ||
return false; | ||
} | ||
}); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice trick - haven't seen this syntax before 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's essential for inner classes where names are shadowed.