Yair Zaslavsky has uploaded a new change for review. Change subject: aaa: WIP - New Extensions Manager based on the extensions API ......................................................................
aaa: WIP - New Extensions Manager based on the extensions API This is just the beginning of my work on the extensions manager Change-Id: I7d170d5dda990fd85e9843ecbb4909749a88df75 Topic: AAA Signed-off-by: Yair Zaslavsky <yzasl...@redhat.com> --- M backend/manager/modules/extensions-api-root/pom.xml A backend/manager/modules/extensions-manager/src/main/java/org/ovirt/engine/core/extensions/mgr/ExtensionsManagerNew.java 2 files changed, 332 insertions(+), 0 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/27/26427/1 diff --git a/backend/manager/modules/extensions-api-root/pom.xml b/backend/manager/modules/extensions-api-root/pom.xml index 179c78e..6d32d77 100644 --- a/backend/manager/modules/extensions-api-root/pom.xml +++ b/backend/manager/modules/extensions-api-root/pom.xml @@ -13,6 +13,8 @@ <properties> <animal.sniffer.signature>java17</animal.sniffer.signature> + <maven.compiler.source>1.7</maven.compiler.source> + <maven.compiler.target>1.7</maven.compiler.target> </properties> <modules> diff --git a/backend/manager/modules/extensions-manager/src/main/java/org/ovirt/engine/core/extensions/mgr/ExtensionsManagerNew.java b/backend/manager/modules/extensions-manager/src/main/java/org/ovirt/engine/core/extensions/mgr/ExtensionsManagerNew.java new file mode 100644 index 0000000..6a5d220 --- /dev/null +++ b/backend/manager/modules/extensions-manager/src/main/java/org/ovirt/engine/core/extensions/mgr/ExtensionsManagerNew.java @@ -0,0 +1,330 @@ +package org.ovirt.engine.core.extensions.mgr; + +import static java.util.Arrays.sort; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.ServiceLoader; + +import org.apache.commons.lang.math.RandomUtils; +import org.jboss.modules.Module; +import org.jboss.modules.ModuleIdentifier; +import org.jboss.modules.ModuleLoadException; +import org.jboss.modules.ModuleLoader; +import org.ovirt.engine.api.extensions.Extension.ExtensionProperties; +import org.ovirt.engine.core.utils.EngineLocalConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import extensions.Base; +import extensions.Base.ConfigKeys; +import extensions.Base.ContextKeys; +import extensions.ExtMap; +import extensions.Extension; +/** + * This class is responsible for loading the required {@code Configuration} in order to create an extension. It holds + * the logic of ordering and solving conflicts during loading the configuration + */ +public class ExtensionsManagerNew { + private static final String ENGINE_EXTENSION_ENABLED = "ENGINE_EXTENSION_ENABLED_"; + + public class ExtensionEntry { + private File file; + private boolean enabled; + private boolean activated; + private Extension extension; + private ExtMap context; + private Logger logger = log; + + public ExtensionEntry(Properties props, File file) { + this.file = file; + load(props); + } + + public String getName() { + return (String) context.get(ExtensionProperties.NAME); + } + + public File getFile() { + return file; + } + + public boolean isEnabled() { + return enabled; + } + + public boolean isActivated() { + return activated; + } + + public List<String> getProvides() { + List<String> providers = new ArrayList<>(); + for (String provider : ((String) context.get(Base.ContextKeys.PROVIDES)).split(",")) { + providers.add(provider.trim()); + } + return providers; + } + + public ExtMap getContext() { + return context; + } + + public Extension getExtension() { + return extension; + } + + public Properties getConfig() { + return (Properties) context.get(ExtensionProperties.CONFIGURATION); + } + + private void load(Properties props) { + enabled = props.get(ConfigKeys.ENABLED) != null ? Boolean.parseBoolean(props.getProperty(ConfigKeys.ENABLED)) : true; + } + + } + + private static final Logger log = LoggerFactory.getLogger(ExtensionsManager.class); + private static volatile ExtensionsManagerNew instance = null; + private Map<String, ExtensionEntry> loadedEntries = new HashMap<>(); + private Map<String, Module> loadedModules = new HashMap<>(); + + public static ExtensionsManagerNew getInstance() { + if (instance == null) { + synchronized (ExtensionsManager.class) { + if (instance == null) { + instance = new ExtensionsManagerNew(); + } + } + } + return instance; + } + + public List<ExtensionEntry> getProvidedExtensions(String provides) { + List<ExtensionEntry> results = new ArrayList<>(); + for (ExtensionEntry entry : loadedEntries.values()) { + if (entry.activated && entry.getProvides().contains(provides)) { + results.add(entry); + } + } + return results; + } + + public ExtensionEntry getExtensionByName(String pluginName) throws ConfigurationException { + ExtensionEntry result = loadedEntries.get(pluginName); + if (result == null) { + throw new ConfigurationException(String.format( + "No configuration was found for extension named '%1$s'", + pluginName) + ); + + } + if (!result.activated) { + throw new ConfigurationException(String.format( + "The configuration '%1$s' is not active", + pluginName) + ); + } + return result; + } + + private ExtensionsManagerNew() { + for (File directory : EngineLocalConfig.getInstance().getExtensionsDirectories()) { + if (!directory.exists()) { + log.warn(String.format("The directory '%1$s' cotaning configuration files does not exist.", + directory.getAbsolutePath())); + } else { + + // The order of the files inside the directory is relevant, as the objects are created in the same order + // that + // the files are processed, so it is better to sort them so that objects will always be created in the + // same + // order regardless of how the filesystem decides to store the entries of the directory: + File[] files = directory.listFiles(); + if (files != null) { + sort(files); + for (File file : files) { + if (file.getName().endsWith(".properties")) { + load(file); + } + } + } + } + } + } + + public void load(Properties configuration) { + loadImpl(configuration, null); + } + + public void load(File file) { + try (FileInputStream inputStream = new FileInputStream(file)) { + Properties props = new Properties(); + props.load(inputStream); + loadImpl(props, file); + } catch (IOException exception) { + throw new ConfigurationException(String.format("Can't load object configuration file '%1$s'", + file.getAbsolutePath())); + } + } + + private synchronized void loadImpl(Properties props, File confFile) { + ExtensionEntry entry = new ExtensionEntry(props, confFile); + ExtensionEntry alreadyLoadedEntry = loadedEntries.get(entry.getName()); + if (alreadyLoadedEntry != null) { + throw new ConfigurationException(String.format( + "Could not load the configuration '%1$s' from file %2$s. A configuration with the same name was already loaded from file %3$s", + entry.getName(), + getFileName(entry.file), + getFileName(alreadyLoadedEntry.file)) + ); + } + loadedEntries.put(entry.getName(), entry); + EngineLocalConfig.getInstance().getBoolean(ENGINE_EXTENSION_ENABLED + entry.getName(), entry.enabled); + //Activate the extension + if (entry.enabled && entry.extension == null) { + try { + entry.context = new ExtMap(); + entry.context.put(ContextKeys.CONFIGURATION, props); + entry.context.put(ContextKeys.INSTANCE_NAME, props.getProperty(ConfigKeys.NAME) != null ? props.getProperty(ConfigKeys.NAME) : String.format("INSTANCE_NAME_%1$s", RandomUtils.nextInt())); + entry.context.put(ContextKeys.PROVIDES, props.getProperty(ConfigKeys.PROVIDES) != null ? props.getProperty(ConfigKeys.PROVIDES) : ""); + + entry.extension = (Extension) lookupService( + Extension.class, + entry.getConfig().getProperty(ConfigKeys.CLASS), + entry.getConfig().getProperty(ConfigKeys.MODULE) + ).newInstance(); + + ExtMap input = new ExtMap().mput( + Base.InvokeKeys.COMMAND, + Base.InvokeCommands.INITIALIZE + ).mput( + Base.InvokeKeys.CONTEXT, + entry.context + ); + ExtMap output = new ExtMap(); + dumpMap(entry, input); + try { + entry.extension.invoke(input, output); + } catch (Exception ex) { + output.mput( + Base.InvokeKeys.RESULT, + Base.InvokeResult.FAILED + ).mput( + Base.InvokeKeys.MESSAGE, + ex.getMessage() + ); + } + entry.logger = + LoggerFactory.getLogger(String.format( + "%1$s.dump.%2$s.%3%s", + ExtensionsManagerNew.class.getName(), + entry.context.get(Base.ContextKeys.EXTENSION_NAME), + entry.context.get(Base.ContextKeys.INSTANCE_NAME) + ) + ); + dumpMap(entry, output); + int result = output.<Integer> get(Base.InvokeKeys.RESULT); + switch (result) { + case Base.InvokeResult.SUCCESS: + break; + case Base.InvokeResult.FAILED: + throw new RuntimeException(output.<String> get(Base.InvokeKeys.MESSAGE)); + } + + entry.activated = true; + + } catch (Exception ex) { + log.error( + String.format( + "Error in activating extension %1$s. Exception message is %2$s", + entry.getName(), + ex.getMessage() + ) + ); + if (log.isDebugEnabled()) { + log.error("", ex); + } + } + } + } + + private String getFileName(File file) { + return file != null ? file.getAbsolutePath() : "N/A"; + } + + private Module loadModule(String moduleSpec) { + // If the module was not already loaded, load it + try { + Module module = loadedModules.get(moduleSpec); + if (module == null) { + ModuleLoader loader = ModuleLoader.forClass(this.getClass()); + if (loader == null) { + throw new ConfigurationException(String.format("The module '%1$s' cannot be loaded as the module system isn't enabled.", + moduleSpec)); + } + module = loader.loadModule(ModuleIdentifier.fromString(moduleSpec)); + loadedModules.put(moduleSpec, module); + } + return module; + } catch (ModuleLoadException exception) { + throw new ConfigurationException(String.format("The module '%1$s' cannot be loaded.", moduleSpec), + exception); + } + } + + private Class<?> lookupService(Class<?> serviceInterface, String serviceClassName, String moduleName) { + // Iterate over the service classes, and find the one that should + // be instantiated and initialized. + Module module = loadModule(moduleName); + Class<?> serviceClass = null; + for (Object service : ServiceLoader.load(serviceInterface, module.getClassLoader())) { + if (service.getClass().getName().equals(serviceClassName)) { + serviceClass = service.getClass(); + break; + } + } + if (serviceClass == null) { + throw new ConfigurationException(String.format("The module '%1$s' does not contain the service '%2$s'.", + module.getIdentifier().getName(), + serviceClassName)); + } + return serviceClass; + } + + private void dumpMap(ExtensionEntry entry, ExtMap map) { + if (entry.logger.isDebugEnabled()) { + entry.logger.debug(map.toString()); + } + } + + public void dump() { + log.info("Start of enabled extensions list"); + for (ExtensionEntry entry : loadedEntries.values()) { + if (entry.extension != null) { + log.info(String.format( + "Instance name: '%1$s', Extension name: '%2$s', Version: '%3$s', License: '%4$s', Home: '%5$s', Author '%6$s', File: '%7$s', Activated: '%8$s", + emptyIfNull(entry.context.get(ContextKeys.INSTANCE_NAME)), + emptyIfNull(entry.context.get(ContextKeys.EXTENSION_NAME)), + emptyIfNull(entry.context.get(ContextKeys.VERSION)), + emptyIfNull(entry.context.get(ContextKeys.LICENSE)), + emptyIfNull(entry.context.get(ContextKeys.HOME_URL)), + emptyIfNull(entry.context.get(ContextKeys.AUTHOR)), + emptyIfNull(getFileName(entry.file)), + entry.activated + ) + ); + } + } + log.info("End of enabled extensions list"); + } + + private Object emptyIfNull(Object value) { + return value == null ? "" : value; + } +} -- To view, visit http://gerrit.ovirt.org/26427 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: I7d170d5dda990fd85e9843ecbb4909749a88df75 Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Yair Zaslavsky <yzasl...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches