Plugable system in practice 00

Introduction

This blog used to introduce a practice of plugin system implementation of zstack.

I will finish base of plugin load, metadate definition, capability negotiation and usage part of Java. And following section describe abstractions of this implementation.

Abstractions

I make some abstractions to satisfy our aim.

  • Unique identifer to find plugin and execute it
  • Capability negotiation and version information
  • Observer pattern to keep all modules use same way access plugin

PluginInterface

Plugin capability currently defines SUPPORTED and UNSUPPORTED for plugin definition

1
2
3
4
public enum PluginCapabilityState {
SUPPORTED,
UNSUPPORTED
}

Define a plugin interface including three methods:

1
2
3
4
5
6
7
public interface PluginInterface {
String pluginUniqueName();

String version();

Map<String, PluginCapabilityState> capabilities();
}

note: capabilities offer a map about custom plugin and expected use enum value

use reflection to collect interfaces extend this interface as the metadata of all kinds of plugins:

1
2
3
public interface PluginEndpointSender extends PluginInterface {
boolean send(PluginEndpointData message);
}

so we need a manage class as the factory of plugin

PluginManager

1
2
3
4
5
public interface PluginManager {
boolean isCapabilitySupported(String pluginName, String capability);

<T extends PluginInterface> T getPlugin(Class<? extends PluginInterface> pluginClass);
}

this class defines two methods, first one used to report plugin capability and another one return singleton plugin.

And in order to reduce complexity, only scan interfaces under abstraction module as meta interfaces

1
2
3
4
5
6
7
8
9
10
11
12
13
Platform.getReflections().getSubTypesOf(PluginInterface.class).forEach(clz -> {
if (!clz.getCanonicalName().contains("org.zstack.abstraction")
|| !clz.isInterface()) {
return;
}

if (interfaceMetadata.contains(clz)) {
throw new CloudRuntimeException(
String.format("duplicate PluginProtocol[name: %s]", clz));
}

interfaceMetadata.add(clz);
});

then load plugin instances from meta class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interfaceMetadata.forEach(clz -> Platform.getReflections().getSubTypesOf(clz)
.forEach(pluginInstanceClz -> {
try {
PluginInterface pluginInterface = pluginInstanceClz.getConstructor().newInstance();
if (pluginInstances.containsKey(pluginInterface.pluginUniqueName())) {
throw new CloudRuntimeException(String.format("duplicate plugin[class: %s]",
pluginInstanceClz));
}

pluginInstances.put(pluginInstanceClz, pluginInterface);
logger.debug(String.format("load plugin: %s, name: %s, capabilities: \n %s",
pluginInterface.version(),
pluginInterface.pluginUniqueName(),
JSONObjectUtil.toJsonString(pluginInterface.capabilities())));
} catch (Exception e) {
throw new CloudRuntimeException(e);
}
}));

class will be used as the key and instance singleton will be stored and ready to use.

Use getPlugin could get the singleton. But currently version and uniqueName do not have specific usage but capabilties could be used to check if the plugin support the feature.

Version is used to check pluginInterface’s compatibility if pluginInterface has any uncompatible change, old version of plugin can run under compatible mode or just rejects load the plugin.

And now all plugins implemented pluginInterface will be loaded and not security check which should be done in pluginInterface and I will design it in next blogs.

Conclusion

Now I implemented part of loading the plugin, for usage is quite easy because developer only need to store the plugin class name as a variable to access the plugin but current safety issue is still should cared by all modules use plugin manager. So in next blog, I will do more works to resolve security requirements.