/**
* Copyright (C) 2008 - Abiquo Holdings S.L. All rights reserved.
*
* Please see /opt/abiquo/tomcat/webapps/legal/ on Abiquo server
* or contact contact@abiquo.com for licensing information.
*/
package com.abiquo.hypervisor.plugin;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.ImmutableMap.copyOf;
import static com.google.common.collect.ImmutableSet.copyOf;
import static com.google.common.collect.Iterables.all;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Sets.newHashSet;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.ServiceLoader;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.abiquo.hypervisor.model.HypervisorConnection;
import com.abiquo.hypervisor.model.enumerator.ConstraintKey;
import com.abiquo.hypervisor.model.enumerator.DiskFormatType;
import com.abiquo.hypervisor.plugin.IHypervisor.OPERATION;
import com.abiquo.hypervisor.plugin.annotation.AgentConnectionMetadata;
import com.abiquo.hypervisor.plugin.annotation.HostConnectionMetadata;
import com.abiquo.hypervisor.plugin.annotation.HypervisorMetadata;
import com.abiquo.hypervisor.plugin.annotation.ManagerConnectionMetadata;
import com.abiquo.hypervisor.plugin.enumerator.FieldConstraint;
import com.abiquo.hypervisor.plugin.exception.HypervisorPluginError;
import com.abiquo.hypervisor.plugin.exception.HypervisorPluginException;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* Hold {@link IHypervisor} instances loaded using a {@link ServiceLoader}
*/
public class PluginManager
{
private final static Logger LOG = LoggerFactory.getLogger(PluginManager.class);
private final static TypeResolver typeResolver = new TypeResolver();
private static PluginManager instance;
private final Map<String, LoadedPlugin> instances;
private class LoadedPlugin
{
public Class<IHypervisorConnection> connectionClass;
public IHypervisor<IHypervisorConnection> hypervisorObject;
public HypervisorMetadata metadata;
public Set<OPERATION> unsupportedOperations;
public Map<ConstraintKey, String> constraints;
public LoadedPlugin(final Class<IHypervisorConnection> connectionClass,
final IHypervisor<IHypervisorConnection> hypervisorObject)
{
this.connectionClass = connectionClass;
this.hypervisorObject = hypervisorObject;
this.metadata = hypervisorObject.getClass().getAnnotation(HypervisorMetadata.class);
this.unsupportedOperations = newHashSet();
this.constraints = newHashMap();
}
}
private PluginManager()
{
Map<String, LoadedPlugin> plugins = Maps.newHashMap();
for (IHypervisor<IHypervisorConnection> hypervisor : ServiceLoader.load(IHypervisor.class))
{
Optional<LoadedPlugin> plugin = loadPlugin(hypervisor);
if (plugin.isPresent())
{
checkDuplicatedPlugins(plugins, plugin);
plugins.put(plugin.get().metadata.type(), plugin.get());
}
}
instances = Collections.unmodifiableMap(plugins);
}
/** Only one instance per hypervisor plugin. */
private void checkDuplicatedPlugins(final Map<String, LoadedPlugin> plugins,
final Optional<LoadedPlugin> plugin)
{
if (plugins.containsKey(plugin.get().metadata.type()))
{
String message =
String.format("A plugin for hypervisor %s has been already loaded",
plugin.get().metadata.type());
LOG.error(message);
throw new IllegalStateException(message);
}
}
/**
* Returns the {@link PluginManager} singleton instance
*
* @return the singleton instance
*/
public static PluginManager getInstance()
{
if (instance == null)
{
throw new IllegalStateException("PluginManager not initialized. "
+ "This indicate some problem during the PluginLoaderContextListener startup");
}
return instance;
}
/**
* Returns the loaded plugins names.
*
* @return the loaded plugins names.
*/
public Set<String> getSupportedHypervisorTypes()
{
return instances.keySet();
}
/**
* Returns the loaded plugins.
*
* @return the loaded plugins.
*/
public Collection<LoadedPlugin> getSupportedHypervisors()
{
return instances.values();
}
/**
* Check if there is a plugin loaded for the given hypervisor type.
*
* @param hypervisorType the hypervisor type to check
* @return True if there is a plugin loaded for the given hypervisor type and false otherwise
*/
public boolean isLoaded(final String hypervisorType)
{
return instances.keySet().contains(hypervisorType);
}
/**
* Returns the single {@link IHypervisor} instance based on the given hypervisor type
*
* @param type the hypervisor type
* @return The {@link IHypervisor} instance
*/
public Optional<IHypervisor<IHypervisorConnection>> getHypervisorPlugin(final String type)
{
if (!instances.containsKey(type))
{
LOG.error("Trying to get the plugin instance for the non supported hypervisor type {}",
type);
return Optional.absent();
}
return Optional.of(instances.get(type).hypervisorObject);
}
/**
* Returns the single {@link IManagerHypervisor} instance based on the given hypervisor type
*
* @param type the hypervisor type
* @return The {@link IHypervisor} instance
*/
public Optional<IManagerHypervisor<IHypervisorConnection>> getManagerHypervisorPlugin(
final String type)
{
if (!instances.containsKey(type))
{
LOG.error(
"Trying to get the plugin instance for the non supported manager hypervisor type {}",
type);
return Optional.absent();
}
if (!isManagerHypervisor(type))
{
LOG.error(
"Trying to get the manager plugin instance for the non manager hypervisor type {}",
type);
return Optional.absent();
}
return Optional
.of((IManagerHypervisor<IHypervisorConnection>) instances.get(type).hypervisorObject);
}
public boolean isManagerHypervisor(final String type)
{
return instances.containsKey(type) ? IManagerHypervisor.class.isAssignableFrom(instances
.get(type).hypervisorObject.getClass()) : false;
}
private boolean isManagerHypervisor(final IHypervisor<IHypervisorConnection> plugin)
{
return IManagerHypervisor.class.isAssignableFrom(plugin.getClass());
}
/**
* Returns the metadata of a plugin based on the given hypervisor type
*
* @param type the hypervisor type
* @return the metadata of a plugin based on the given hypervisor type
* @throws HypervisorPluginException if there is not a loaded plugin for the given hypervisor
* type
*/
public Optional<HypervisorMetadata> getHypervisorMetadata(final String type)
{
if (!instances.containsKey(type))
{
LOG.warn("Trying to get the plugin metadata for the non supported hypervisor type {}",
type);
return Optional.absent();
}
return Optional.fromNullable(instances.get(type).metadata);
}
/**
* Given a loaded plugin retrieve its metadata. If the plugin is not loaded or has no metadata
* an <code>Optional.absent()</code> is returned.
*
* @param plugin to load its metadata.
* @return metadata of the plugin, Optional.absent() otherwise.
*/
public Optional<HypervisorMetadata> getHypervisorMetadata(
final IHypervisor<IHypervisorConnection> plugin)
{
return extractHypervisorMetadata(plugin);
}
public boolean isOperationSupported(final String hypervisorType, final OPERATION operation)
{
LoadedPlugin plugin = instances.get(hypervisorType);
return plugin != null ? !plugin.unsupportedOperations.contains(operation) : false;
}
public String getConstraintValue(final String hypervisorType, final ConstraintKey constraintKey)
{
LoadedPlugin plugin = instances.get(hypervisorType);
return plugin != null ? plugin.constraints.get(constraintKey) : null;
}
public Map<ConstraintKey, String> getConstraints(final String hypervisorType)
{
LoadedPlugin plugin = instances.get(hypervisorType);
return plugin != null ? plugin.constraints : Collections.<ConstraintKey, String> emptyMap();
}
/**
* Creates a new instance of {@link IHypervisorConnection} based on the given HypervisorType
*
* @param type the hypervisor type
* @return The {@link IHypervisorConnection} instance
* @throws HypervisorPluginException if there is not a loaded plugin for the given
* HypervisorType
*/
public IHypervisorConnection newHypervisorConnection(final String type)
throws HypervisorPluginException
{
LoadedPlugin plugin = instances.get(type);
if (plugin == null)
{
LOG.error(
"Trying to get the connection instance for the non supported hypervisor type {}",
type);
throw new HypervisorPluginException(HypervisorPluginError.THURMAN0, "Hypervisor type "
+ type);
}
try
{
return plugin.connectionClass.newInstance();
}
catch (Exception e)
{
LOG.error("Unable to create new plugin instance hypervisor type {}", type);
throw new HypervisorPluginException(HypervisorPluginError.PLUGIN_NEW_INSTANCE_ERROR,
"Hypervisor type " + type);
}
}
protected Optional<LoadedPlugin> loadPlugin(final IHypervisor<IHypervisorConnection> plugin)
throws IllegalStateException
{
Optional<HypervisorMetadata> optionalMetadata = extractHypervisorMetadata(plugin);
if (!optionalMetadata.isPresent())
{
LOG.error(
"The plugin {} has invalid format. {} annotation is missing. Ignoring this plugin",
new Object[] {plugin.getClass().getName(), HypervisorMetadata.class.getName()});
return Optional.absent();
}
HypervisorMetadata metadata = optionalMetadata.get();
final List<DiskFormatType> diskFormatTypes = Arrays.asList(DiskFormatType.values());
List<DiskFormatType> compatibleTypes = Arrays.asList(metadata.compatibleDiskFormatTypes());
// Hypervisor type must be informed
if (isNullOrEmpty(metadata.type()))
{
LOG.error("The type of the plugin {} is null or empty. Ignoring this plugin",
new Object[] {plugin.getClass().getName()});
return Optional.absent();
}
// Hypervisor friendly name must be informed
if (isNullOrEmpty(metadata.friendlyName()))
{
LOG.error("The friendly name of the plugin {} is null or empty. Ignoring this plugin",
new Object[] {metadata.type()});
return Optional.absent();
}
// All compatible types must be in DiskFormatType otherwise we don't load this plugin
boolean allContained = Iterables.any(compatibleTypes, new Predicate<DiskFormatType>()
{
@Override
public boolean apply(final DiskFormatType input)
{
return diskFormatTypes.contains(input);
}
});
if (!allContained)
{
LOG.error(
"The plugin {} has invalid configuration compatibles disk format type is not a known disk format types (is not in DiskFormatTypes). Ignoring this plugin",
metadata.type());
return Optional.absent();
}
// Base format must be in compatible formats otherwise we don't load this plugin
if (!compatibleTypes.contains(metadata.baseDiskFormatType()))
{
LOG.error(
"The plugin {} has invalid configuration base disk format type is not in compatible disk format types. {} is not in compatibleDiskFormatTypes. Ignoring this plugin",
new Object[] {metadata.type(), metadata.baseDiskFormatType()});
return Optional.absent();
}
// Validate specific plugin configuration
try
{
plugin.validateConfiguration();
}
catch (IllegalStateException e)
{
LOG.error("The plugin {} has invalid configuration: {}", new Object[] {metadata.type(),
e.getMessage()});
return Optional.absent();
}
try
{
Class<IHypervisorConnection> connectionClass =
resolveIHypervisorConnection(plugin.getClass());
LoadedPlugin loadedPlugin = new LoadedPlugin(connectionClass, plugin);
loadedPlugin.unsupportedOperations = copyOf(getUnsupportedOperations(plugin));
loadedPlugin.constraints = copyOf(loadConstraints(plugin));
LOG.info("Loaded plugin for hypervisor type {}", metadata.type());
return Optional.of(loadedPlugin);
}
catch (Exception e)
{
LOG.error("Unable to load plugin for hypervisor type {}", metadata.type(), e);
}
return Optional.absent();
}
private Optional<HypervisorMetadata> extractHypervisorMetadata(
final IHypervisor<IHypervisorConnection> plugin)
{
Class< ? extends IHypervisor> hypClass = plugin.getClass();
return Optional.fromNullable(hypClass.getAnnotation(HypervisorMetadata.class));
}
private Set<OPERATION> getUnsupportedOperations(final IHypervisor<IHypervisorConnection> plugin)
{
Set<OPERATION> unsupportedOperations = Sets.newHashSet();
for (Method method : plugin.getClass().getMethods())
{
if (method.getAnnotation(UnsupportedOperation.class) != null)
{
OPERATION op = OPERATION.fromName(method.getName());
if (op != null)
{
unsupportedOperations.add(op);
}
}
}
return unsupportedOperations;
}
private Map<ConstraintKey, String> loadConstraints(
final IHypervisor<IHypervisorConnection> plugin)
{
Map<ConstraintKey, String> constraints = Maps.newHashMap();
for (ConstraintKey key : ConstraintKey.values())
{
String value = plugin.getConstraint(key.name());
if (value != null)
{
constraints.put(key, value);
}
}
constraints.putAll(loadConnectionConstraints(plugin.getClass().getAnnotation(
HostConnectionMetadata.class)));
constraints.putAll(loadConnectionConstraints(plugin.getClass().getAnnotation(
AgentConnectionMetadata.class)));
constraints.putAll(loadConnectionConstraints(plugin.getClass().getAnnotation(
ManagerConnectionMetadata.class)));
if (isManagerHypervisor(plugin)
&& !constraints.containsKey(ConstraintKey.DISCOVER_THROUGH_MANAGER))
{
constraints.put(ConstraintKey.DISCOVER_THROUGH_MANAGER, Boolean.TRUE.toString());
}
return constraints;
}
private Map<ConstraintKey, String> loadConnectionConstraints(
final HostConnectionMetadata metadata)
{
FieldConstraint ip = FieldConstraint.NO_APPLICABLE;
FieldConstraint port = FieldConstraint.NO_APPLICABLE;
FieldConstraint credentials = FieldConstraint.NO_APPLICABLE;
if (metadata != null)
{
ip = metadata.ip();
port = metadata.port();
credentials = metadata.credentials();
}
return ImmutableMap.<ConstraintKey, String> builder().put(ConstraintKey.HOST_IP, ip.name())
.put(ConstraintKey.HOST_PORT, port.name())
.put(ConstraintKey.HOST_CREDENTIALS, credentials.name()).build();
}
private Map<ConstraintKey, String> loadConnectionConstraints(
final ManagerConnectionMetadata metadata)
{
FieldConstraint ip = FieldConstraint.NO_APPLICABLE;
FieldConstraint port = FieldConstraint.NO_APPLICABLE;
FieldConstraint credentials = FieldConstraint.NO_APPLICABLE;
if (metadata != null)
{
ip = metadata.ip();
port = metadata.port();
credentials = metadata.credentials();
}
return ImmutableMap.<ConstraintKey, String> builder()
.put(ConstraintKey.MANAGER_IP, ip.name()).put(ConstraintKey.MANAGER_PORT, port.name())
.put(ConstraintKey.MANAGER_CREDENTIALS, credentials.name()).build();
}
private Map<ConstraintKey, String> loadConnectionConstraints(
final AgentConnectionMetadata metadata)
{
FieldConstraint ip = FieldConstraint.NO_APPLICABLE;
FieldConstraint port = FieldConstraint.NO_APPLICABLE;
FieldConstraint credentials = FieldConstraint.NO_APPLICABLE;
if (metadata != null)
{
ip = metadata.ip();
port = metadata.port();
credentials = metadata.credentials();
}
return ImmutableMap.<ConstraintKey, String> builder()
.put(ConstraintKey.AGENT_IP, ip.name()).put(ConstraintKey.AGENT_PORT, port.name())
.put(ConstraintKey.AGENT_CREDENTIALS, credentials.name()).build();
}
/**
* Introspect an {@link IHypervisor} object to know the class of its
* {@link IHypervisorConnection}
*
* @param hclass the class of an {@link IHypervisor} implementation
* @return the class of the associated generic type for {@link IHypervisorConnection}
*/
@SuppressWarnings("unchecked")
// verified by ''typeParametersFor(IHypervisor.class)''
private static Class<IHypervisorConnection> resolveIHypervisorConnection(final Type hclass)
throws HypervisorPluginException
{
List<ResolvedType> rt = typeResolver.resolve(hclass).typeParametersFor(IHypervisor.class);
if (rt == null || 1 != rt.size())
{
throw new HypervisorPluginException(HypervisorPluginError.THURMAN0, hclass.toString());
}
return (Class<IHypervisorConnection>) rt.get(0).getErasedType();
}
/**
* This method is meant to be executed <b>only</b> at start up time! To retrieve the instance
* use the {@link #getInstance()} instead.
*
* @see PluginManager#getInstance()
* @throws IllegalStateException if the instance is already created.
*/
public static synchronized void createInstance()
{
if (instance != null)
{
throw new IllegalStateException("An instance of the PluginManager already exists! Use getInstance() method instead");
}
instance = new PluginManager();
}
/**
* Validates the given {@link HypervisorConnection} for the {@link IHypervisor} implementation
* of the indicated hypervisor plugin type.
*
* @param connection the connection to validate
* @param type the hypervisor type
* @return True if is a valid connection and false otherwise.
*/
public boolean validateHostConnection(final HypervisorConnection connection, final String type)
{
if (!isLoaded(type))
{
return false;
}
return all(filter(instances.get(type).constraints.entrySet(), mandatoryFields),
new Predicate<Entry<ConstraintKey, String>>()
{
@Override
public boolean apply(final Entry<ConstraintKey, String> entry)
{
switch (entry.getKey())
{
case HOST_IP:
return isNotNull(connection.getIp());
case HOST_PORT:
return isNotNull(connection.getPort());
case HOST_CREDENTIALS:
return isNotNull(connection.getUser())
&& isNotNull(connection.getPassword());
case MANAGER_IP:
return isNotNull(connection.getManagerIp());
case MANAGER_PORT:
return isNotNull(connection.getManagerPort());
case MANAGER_CREDENTIALS:
return isNotNull(connection.getManagerUser())
&& isNotNull(connection.getManagerPassword());
case AGENT_IP:
return isNotNull(connection.getAgentIp());
case AGENT_PORT:
return isNotNull(connection.getAgentPort());
case AGENT_CREDENTIALS:
return isNotNull(connection.getAgentUser())
&& isNotNull(connection.getAgentPassword());
default:
return true;
}
}
});
}
/**
* Validates the given {@link HypervisorConnection} for the {@link IManagerHypervisor}
* implementation of the indicated hypervisor plugin type.
*
* @param connection the connection to validate
* @param type the hypervisor type
* @return True if is a valid connection and false otherwise.
*/
public boolean validateManagerConnection(final HypervisorConnection connection,
final String type)
{
if (!isLoaded(type) || !isManagerHypervisor(type))
{
return false;
}
return all(filter(instances.get(type).constraints.entrySet(), mandatoryFields),
new Predicate<Entry<ConstraintKey, String>>()
{
@Override
public boolean apply(final Entry<ConstraintKey, String> entry)
{
switch (entry.getKey())
{
case MANAGER_IP:
return isNotNull(connection.getManagerIp());
case MANAGER_PORT:
return isNotNull(connection.getManagerPort());
case MANAGER_CREDENTIALS:
return isNotNull(connection.getManagerUser())
&& isNotNull(connection.getManagerPassword());
default:
return true;
}
}
});
}
/**
* Evaluates if the {@link ConstraintKey} has assigned the value
* {@link FieldConstraint#MANDATORY}
*/
private static final Predicate<Entry<ConstraintKey, String>> mandatoryFields =
new Predicate<Entry<ConstraintKey, String>>()
{
@Override
public boolean apply(final Entry<ConstraintKey, String> entry)
{
return FieldConstraint.MANDATORY.name().equalsIgnoreCase(entry.getValue());
}
};
private boolean isNotNull(final Object value)
{
return value != null;
}
}