Skip to content
This repository has been archived by the owner on Sep 26, 2019. It is now read-only.

Plugin Framework #1435

Merged
merged 22 commits into from
May 17, 2019
Merged

Plugin Framework #1435

merged 22 commits into from
May 17, 2019

Conversation

shemnon
Copy link
Contributor

@shemnon shemnon commented May 10, 2019

PR description

Provide a mechanism for Pantheon to integrate plugin code at runtime.

Fixed Issue(s)

@shemnon shemnon marked this pull request as ready for review May 16, 2019 14:28
homeDirectory.resolve("plugins").toFile().mkdirs();
copyResource(
pluginName + ".jar", homeDirectory.resolve("plugins/" + pluginName + ".jar"));
PantheonNode.this.plugins.add(pluginName);
Copy link
Contributor

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 😄

Copy link
Contributor Author

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.

@@ -390,7 +403,7 @@ public Address getAddress() {
return Util.publicKeyToAddress(keyPair.getPublicKey());
}

Path homeDirectory() {
public Path homeDirectory() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nit) getHomeDirectory ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for consistency of getters.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed at the interface, along with other inconsistent names.

}

@Override
public void setPlugins(final List<String> plugins) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like dead code

Copy link
Contributor Author

@shemnon shemnon May 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we are acceptance testing specific plugins it will be needed, so I'll bring it back then.


for (final PantheonPlugin plugin : serviceLoader) {
try {
plugin.register(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a little dangerous to give plugins access to the object that can start / stop / register plugins? Why not just have a separate object that holds your services?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is written with Java modules in mind. The class they would need to cast it to (PantheonPluginContextImpl) will not be visible in the modules and the JVM will enforce that barrier at compile time and runtime. The interface only exposes a PantheonContext to them.

If we used a proxy class they will still be able to (pre Java modules) use reflection to navigate to the containing context and get access.

Plugins right now are not meant to run byzantine plugins.

If I am missing something ping me on slack.


public interface PantheonContext {

<T> Optional<T> getService(Class<T> serviceType);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious - why go with this generic pattern rather than just explicitly exposing some services like registerCLIOptions, getPantheonEvents, etc? Is the idea that this makes it easier for us to remove services in the future?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also wondering if we shouldn't just return the service class directly rather than wrapping in it in an Optional? And in the case where a plugin asks for a service that isn't registered, we could just throw an exception and disable that plugin. Seems safer to disable plugins that might be missing required services.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so we can expose more services later and not have to mutate the API. In a future revision the plugins themselves could expose services.

The Optional is there in case plugins want to do graceful degraded service. A plugin may want a service but not require it. For example a plugin that interacts with consensus engines would only expect one to be operating but would go down the list of possible engines that expose services to the plugin architecture.

public void register(final PantheonContext context) {
this.context = context;

if (System.getProperty("testPlugin.failAtRegister") != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any plans to add tests that hit these failure cases or check that the registered CLI flags are used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, with this commit. Validating the flag is a bit tricky and needs to be done as part of the acceptance tests.

package tech.pegasys.pantheon.plugins.services;

/** This service will be available during the registration callbacks. */
public interface PicoCLIOptions {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking my understanding here. Seems like the idea in tying this class to PicoCLI rather than making it something more generic like CLIOptions is that in the future we might use a different CLI system, that will be supported by a completely different service / api?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, strongly coupled to Pico cli library.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, graceful migration.

Copy link
Contributor

@AbdelStark AbdelStark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few comments left.
General mechanism looks good to me.

@@ -390,7 +403,7 @@ public Address getAddress() {
return Util.publicKeyToAddress(keyPair.getPublicKey());
}

Path homeDirectory() {
public Path homeDirectory() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 for consistency of getters.

@@ -184,6 +184,13 @@ public void startNode(final PantheonNode node) {
.directory(new File(System.getProperty("user.dir")).getParentFile())
.redirectErrorStream(true)
.redirectInput(Redirect.INHERIT);
if (!node.getPlugins().isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm quite sceptical to put environment variables directly in the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.

@@ -43,6 +43,7 @@
long DEFAULT_MIN_REFRESH_DELAY = 1;
String DOCKER_GENESIS_LOCATION = "/etc/pantheon/genesis.json";
String DOCKER_DATADIR_LOCATION = "/var/lib/pantheon";
String DOCKER_PLUGINSDIR_LOCATION = "/etc/pantheon/plugins";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will disappear with Kubernetes work as all others docker paths.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Until k8s lands we need it.

@@ -1205,6 +1217,21 @@ private Path dataDir() {
}
}

private Path pluginsDir() {
if (isFullInstantiation()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as previous comment. App should not behave differently when run inside docker.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a concern outside the scope of this PR. I am being consistent with the current architecture.

state = Lifecycle.STARTING;
final Iterator<PantheonPlugin> pluginsIterator = plugins.iterator();

while (pluginsIterator.hasNext()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to start plugins sequentially ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other option being parallel? I don't see parallel startup ending well.

package tech.pegasys.pantheon.plugins.services;

/** This service will be available during the registration callbacks. */
public interface PicoCLIOptions {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, strongly coupled to Pico cli library.

shemnon and others added 2 commits May 16, 2019 11:08
…antheonPluginContextImpl.java

Co-Authored-By: mbaxter <mbaxter@users.noreply.github.com>
unit tests
style fixes
* @param optionObject The instance of the object to be inspected. PicoCLI will reflect the fields
* of this object to extract the CLI options.
*/
void addPicoCLIOptions(String namespace, Object optionObject);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
void addPicoCLIOptions(String namespace, Object optionObject);
void addCLIOptions(String namespace, Object optionObject);

(optional) We could still probably make the method name more generic

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it is very tied to PicoCLI's style I feel we should communicate that wherever possible.

public class PluginsAcceptanceTest extends AcceptanceTestBase {
private PantheonNode node;

// context: https://en.wikipedia.org/wiki/The_Magic_Words_are_Squeamish_Ossifrage
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice 😄

@shemnon shemnon merged commit 1037eb5 into PegaSysEng:master May 17, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants