/*
* RHQ Management Platform
* Copyright (C) 2005-2013 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.core.pluginapi.inventory;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.resource.ProcessScan;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.domain.resource.ResourceType;
import org.rhq.core.pluginapi.availability.AvailabilityCollectorRunnable;
import org.rhq.core.pluginapi.availability.AvailabilityContext;
import org.rhq.core.pluginapi.availability.AvailabilityFacet;
import org.rhq.core.pluginapi.component.ComponentInvocationContext;
import org.rhq.core.pluginapi.content.ContentContext;
import org.rhq.core.pluginapi.event.EventContext;
import org.rhq.core.pluginapi.operation.OperationContext;
import org.rhq.core.pluginapi.upgrade.ResourceUpgradeContext;
import org.rhq.core.system.ProcessInfo;
import org.rhq.core.system.SystemInfo;
import org.rhq.core.system.SystemInfoFactory;
import org.rhq.core.system.pquery.ProcessInfoQuery;
import org.rhq.core.util.MessageDigestGenerator;
/**
* The context object that {@link ResourceComponent} objects will have access - it will have all the information that
* the resource components needs during their lifetime.
*
* <p>This context class is currently designed to be an immutable object. Instances of this context object are to be
* created by the plugin container only.</p>
*
* @param <T> the parent resource component type for this component. This means you can nest a hierarchy of resource
* components that mimic the resource type hierarchy as defined in a plugin deployment descriptor.
*
* @author John Mazzitelli
*/
@SuppressWarnings("unchecked")
public class ResourceContext<T extends ResourceComponent<?>> {
private static final Log LOG = LogFactory.getLog(ResourceContext.class);
private final T parentResourceComponent;
private final ResourceContext<?> parentResourceContext;
private final Configuration pluginConfiguration;
private final SystemInfo systemInformation;
private final ResourceDiscoveryComponent<T> resourceDiscoveryComponent;
private final Resource resource;
private File temporaryDirectory; // Lazily evaluated
private final File baseDataDirectory; // base data directory from system
private final String pluginContainerName;
private final EventContext eventContext;
private final OperationContext operationContext;
private final ContentContext contentContext;
private final AvailabilityContext availabilityContext;
private final InventoryContext inventoryContext;
private final PluginContainerDeployment pluginContainerDeployment;
private final ResourceTypeProcesses trackedProcesses;
private final ComponentInvocationContext componentInvocationContext;
private static class Children {
public final ResourceType resourceType;
public final String parentResourceUuid;
public Children(String parentResourceUuid, ResourceType resourceType) {
this.parentResourceUuid = parentResourceUuid;
this.resourceType = resourceType;
}
@Override
public int hashCode() {
int uuidHashCode = parentResourceUuid == null ? 1 : parentResourceUuid.hashCode();
return 31 * uuidHashCode * resourceType.getId();
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (!(other instanceof Children)) {
return false;
}
Children o = (Children) other;
return (parentResourceUuid == null ? o.parentResourceUuid == null : parentResourceUuid
.equals(o.parentResourceUuid)) && resourceType.equals(o.resourceType);
}
}
private static Map<Children, ResourceTypeProcesses> PROCESSES_PER_PARENT_PER_RESOURCE_TYPE = new HashMap<Children, ResourceTypeProcesses>();
/**
* Creates a new {@link ResourceContext} object with a default {@link ComponentInvocationContext}.
*
* @deprecated as of RHQ 4.9.
*/
@Deprecated
public ResourceContext(Resource resource, T parentResourceComponent, ResourceContext<?> parentResourceContext,
ResourceDiscoveryComponent<T> resourceDiscoveryComponent, SystemInfo systemInfo, File temporaryDirectory,
File baseDataDirectory, String pluginContainerName, EventContext eventContext, OperationContext operationContext,
ContentContext contentContext, AvailabilityContext availabilityContext, InventoryContext inventoryContext,
PluginContainerDeployment pluginContainerDeployment) {
this(resource, parentResourceComponent, parentResourceContext, resourceDiscoveryComponent, systemInfo,
temporaryDirectory, baseDataDirectory, pluginContainerName, eventContext, operationContext, contentContext,
availabilityContext, inventoryContext, pluginContainerDeployment, new ComponentInvocationContext() {
@Override
public boolean isInterrupted() {
throw new UnsupportedOperationException();
}
@Override
public void markInterrupted() {
throw new UnsupportedOperationException();
}
});
}
/**
* Creates a new {@link ResourceContext} object.
*
* <b>NOTE:</b> The plugin container is responsible for instantiating these objects; plugin writers should never
* have to actually create context objects.
*
* @param resource the resource whose {@link org.rhq.core.pluginapi.inventory.ResourceComponent}
* will be given this context object of the plugin
* @param parentResourceComponent the parent component of the context's associated resource component (or null if parent resource is null)
* @param parentResourceContext the resource context of the parent resource (or null if parent resource is null)
* @param resourceDiscoveryComponent the discovery component that can be used to detect other resources of the same
* type as this resource (may be <code>null</code>)
* @param systemInfo information about the system on which the plugin and its plugin container are
* running
* @param temporaryDirectory a temporary directory for plugin use that is destroyed at plugin container shutdown
* @param baseDataDirectory a directory where plugins can store persisted data that survives plugin container restarts
* @param pluginContainerName the name of the plugin container in which the discovery component is running.
* Components can be assured this name is unique across <b>all</b> plugin
* containers/agents running in the RHQ environment.
* @param eventContext an {@link EventContext}, if the resource supports one or more types of
* {@link org.rhq.core.domain.event.Event}s, or <code>null</code> otherwise
* @param operationContext an {@link OperationContext} the plugin can use to interoperate with the
* operation manager
* @param contentContext a {@link ContentContext} the plugin can use to interoperate with the content
* manager
* @param availabilityContext a {@link AvailabilityContext} the plugin can use to interoperate with the
* plugin container inventory manager
* @param pluginContainerDeployment indicates where the plugin container is running
* @param componentInvocationContext a {@link ComponentInvocationContext} the plugin can use to determine if the
* current component invocation has been canceled or timed out.
*/
public ResourceContext(Resource resource, T parentResourceComponent, ResourceContext<?> parentResourceContext,
ResourceDiscoveryComponent<T> resourceDiscoveryComponent, SystemInfo systemInfo, File temporaryDirectory,
File baseDataDirectory, String pluginContainerName, EventContext eventContext, OperationContext operationContext,
ContentContext contentContext, AvailabilityContext availabilityContext, InventoryContext inventoryContext,
PluginContainerDeployment pluginContainerDeployment, ComponentInvocationContext componentInvocationContext) {
this.resource = resource;
this.parentResourceComponent = parentResourceComponent;
this.parentResourceContext = parentResourceContext;
this.resourceDiscoveryComponent = resourceDiscoveryComponent;
this.systemInformation = systemInfo;
this.pluginConfiguration = resource.getPluginConfiguration();
this.baseDataDirectory = baseDataDirectory;
if (pluginContainerName!=null) {
this.pluginContainerName = pluginContainerName.intern();
} else {
this.pluginContainerName = null;
}
this.pluginContainerDeployment = pluginContainerDeployment;
this.temporaryDirectory = temporaryDirectory;
this.eventContext = eventContext;
this.operationContext = operationContext;
this.contentContext = contentContext;
this.availabilityContext = availabilityContext;
this.inventoryContext = inventoryContext;
String parentResourceUuid = "";
if (resource.getParentResource() != null) {
parentResourceUuid = resource.getParentResource().getUuid();
}
this.trackedProcesses = getTrackedProcesses(parentResourceUuid, resource.getResourceType());
this.componentInvocationContext = componentInvocationContext;
}
/**
* The {@link Resource#getResourceKey() resource key} of the resource this context is associated with. This resource
* key is unique across all of the resource's siblings. That is to say, this resource key is unique among all
* children of the {@link #getParentResourceComponent() parent}.
*
* @return resource key of the associated resource
*/
public String getResourceKey() {
return this.resource.getResourceKey();
}
/**
* The {@link Resource#getResourceType() resource type} of the resource this context is associated with.
*
* @return type of the associated resource
*/
public ResourceType getResourceType() {
return this.resource.getResourceType();
}
/**
* The {@link Resource#getVersion() version} of the resource this context is associated with.
*
* @return the resource's version string
*
* @since 1.2
*/
public String getVersion() {
return this.resource.getVersion();
}
/**
* The data directory of the resource this context is associated with.
*
* @return resource data directory
*/
public File getResourceDataDirectory() {
File resourceDataDirectory = new File(baseDataDirectory, this.getAncestryBasedResourceKey());
try {
File oldResourceDataDirectory = new File(baseDataDirectory, this.resource.getUuid());
if (oldResourceDataDirectory.exists()) {
oldResourceDataDirectory.renameTo(resourceDataDirectory);
}
} catch (Exception e) {
//Just prevent an exception related to renaming of the old
//data resource directory from causing this method fail.
//This method should continue and create the new folder
//as if the old folder never existed.
}
if (!resourceDataDirectory.exists()) {
resourceDataDirectory.mkdirs();
}
return resourceDataDirectory;
}
/**
* The data directory of a child to be created for the resource this context is associated with.
*
* @return child resource data directory
*/
public File getFutureChildResourceDataDirectory(String childResourceKey) {
File childResourceDataDirectory = new File(baseDataDirectory, this.getAncestryBasedResourceKey(childResourceKey));
if (!childResourceDataDirectory.exists()) {
childResourceDataDirectory.mkdirs();
}
return childResourceDataDirectory;
}
/**
* The parent of the resource component that is associated with this context.
*
* @return parent component of the associated resource component
*/
public T getParentResourceComponent() {
return this.parentResourceComponent;
}
/**
* Returns the resource context of the parent resource or null if there is no parent resource.
* <p>
* (This method is protected to be able to share that information with the {@link ResourceUpgradeContext}
* but at the same time to not pollute the ResourceContext public API with data that doesn't belong
* to it).
*
* @return
*/
protected ResourceContext<?> getParentResourceContext() {
return this.parentResourceContext;
}
/**
* Returns a {@link SystemInfo} object that contains information about the platform/operating system that the
* resource is running on. With this object, you can natively obtain things such as the operating system name, its
* hostname,and other things. Please refer to the javadoc on {@link SystemInfo} for more details on the types of
* information you can access.
*
* @return system information object
*/
public SystemInfo getSystemInformation() {
return this.systemInformation;
}
/**
* Returns the resource's plugin configuration. This is used to configure the subsystem that is used to actually
* talk to the managed resource. Do not confuse this with the <i>resource configuration</i>, which is the actual
* configuration settings for the managed resource itself.
*
* @return plugin configuration
*/
public Configuration getPluginConfiguration() {
return this.pluginConfiguration.deepCopy();
}
/**
* Returns the information on the native operating system process in which the managed resource is running. If
* native support is not available or the process for some reason can no longer be found, this may return <code>
* null</code>.
*
* The returned {@link ProcessInfo} always has a fresh snapshot of non static data: it's whether newly created
* or got refreshed in order to determine if the process was still running.
*
* @return information on the resource's process, or null if process was not found
*/
public ProcessInfo getNativeProcess() {
ProcessInfo processInfo = null;
synchronized (trackedProcesses) {
//right, we've entered the critical section...
//we might have waited for another thread to actually fill in the tracked processes
//so let's check again if we really need to run the discovery
processInfo = trackedProcesses.getProcessInfo(resource.getResourceKey());
if (isRediscoveryRequired(processInfo)) {
if (LOG.isTraceEnabled()) {
LOG.trace("getNativeProcess(): recheck for rediscovery confirmed the need for it");
}
try {
Set<DiscoveredResourceDetails> details = Collections.emptySet();
List<ProcessScanResult> processes = getNativeProcessesForType();
if (!processes.isEmpty()) {
ResourceDiscoveryContext<T> context;
context = new ResourceDiscoveryContext<T>(this.resource.getResourceType(), this.parentResourceComponent,
this.parentResourceContext, this.systemInformation, processes, Collections.EMPTY_LIST,
getPluginContainerName(), getPluginContainerDeployment());
details = this.resourceDiscoveryComponent.discoverResources(context);
}
trackedProcesses.update(details);
processInfo = trackedProcesses.getProcessInfo(resource.getResourceKey());
} catch (Exception e) {
LOG.warn("Cannot get native process for resource [" + this.resource.getResourceKey() + "] - discovery failed", e);
}
}
}
if (LOG.isTraceEnabled()) {
LOG.trace("getNativeProcess(): rediscovery done");
}
return processInfo;
}
private boolean isRediscoveryRequired(ProcessInfo processInfo) {
return processInfo == null || !processInfo.freshSnapshot().isRunning();
}
/**
* Scans the current list of running processes and returns information on all processes that may contain resources
* of the {@link #getResourceType() same type as this resource}. More specifically, this method will scan all the
* processes and try to match them up with the {@link ResourceType#getProcessScans() PIQL queries} associated with
* this resource's type.
*
* @return information on the processes that may be running this resource or other resources of the same type
*
* @see ResourceType#getProcessScans()
*/
public List<ProcessScanResult> getNativeProcessesForType() {
// perform auto-discovery PIQL queries now to see if we can auto-detect resources that are running now of this type
List<ProcessScanResult> scanResults = new ArrayList<ProcessScanResult>();
SystemInfo systemInfo = SystemInfoFactory.createSystemInfo();
try {
Set<ProcessScan> processScans = this.resource.getResourceType().getProcessScans();
if (processScans != null && !processScans.isEmpty()) {
ProcessInfoQuery piq = new ProcessInfoQuery(systemInfo.getAllProcesses());
for (ProcessScan processScan : processScans) {
List<ProcessInfo> queryResults = piq.query(processScan.getQuery());
if ((queryResults != null) && (queryResults.size() > 0)) {
for (ProcessInfo autoDiscoveredProcess : queryResults) {
scanResults.add(new ProcessScanResult(processScan, autoDiscoveredProcess));
}
}
}
}
} catch (UnsupportedOperationException uoe) {
}
return scanResults;
}
/**
* A temporary directory for plugin use that is destroyed at plugin container shutdown. Plugins should use this if they need to
* write temporary files that they do not expect to remain after the plugin container is restarted. This directory is shared
* among all plugins - plugins must ensure they write unique files here, as other plugins may be using this same
* directory. Typically, plugins will use the {@link File#createTempFile(String, String, File)} API when writing to
* this directory.
*
* @return location for plugin temporary files
*/
public File getTemporaryDirectory() {
if (this.temporaryDirectory==null) {
this.temporaryDirectory = new File(System.getProperty("java.io.tmpdir"), "AGENT_TMP");
this.temporaryDirectory.mkdirs();
}
return temporaryDirectory;
}
/**
* Directory where plugins can store persisted data that survives plugin container restarts. Each plugin will have their own
* data directory. The returned directory may not yet exist - it is up to each individual plugin to manage this
* directory as they see fit (this includes performing the initial creation when the directory is first needed).
*
* @return location for plugins to store persisted data
*/
public File getDataDirectory() {
return new File(baseDataDirectory, resource.getResourceType().getPlugin());
}
/**
* The name of the plugin container in which the resource component is running. Components
* can be assured this name is unique across <b>all</b> plugin containers/agents running
* in the RHQ environment.
*
* @return the name of the plugin container
*/
public String getPluginContainerName() {
return pluginContainerName;
}
/**
* Indicates where the plugin container (and therefore where the plugins) are deployed and running.
* See {@link PluginContainerDeployment} for more information on what the return value means.
*
* @return indicator of where the plugin container is deployed and running
*
* @since 1.3
*/
public PluginContainerDeployment getPluginContainerDeployment() {
return pluginContainerDeployment;
}
/**
* Returns an {@link EventContext}, if the resource supports one or more types of
* {@link org.rhq.core.domain.event.Event}s, or <code>null</code> otherwise.
*
* @return an <code>EventContext</code>, if the resource supports one or more types of
* {@link org.rhq.core.domain.event.Event}s, or <code>null</code> otherwise
*/
public EventContext getEventContext() {
return eventContext;
}
/**
* Returns an {@link OperationContext} that allows the plugin to access the operation functionality provided by the
* plugin container.
*
* @return operation context object
*/
public OperationContext getOperationContext() {
return operationContext;
}
/**
* Returns a {@link ContentContext} that allows the plugin to access the content functionality provided by the
* plugin container.
*
* @return content context object
*/
public ContentContext getContentContext() {
return contentContext;
}
/**
* Returns an {@link AvailabilityContext} that allows the plugin to access the availability functionality provided by the
* plugin container.
*
* @return availability context object
*/
public AvailabilityContext getAvailabilityContext() {
return availabilityContext;
}
/**
* Returns an {@link InventoryContext} that allows the plugin to access inventory related functionality provided by the
* plugin container.
*
* @return the inventory context
*/
public InventoryContext getInventoryContext() {
return inventoryContext;
}
/**
* @deprecated Use {@link AvailabilityContext#createAvailabilityCollectorRunnable(AvailabilityFacet, long)}
*/
@Deprecated
public AvailabilityCollectorRunnable createAvailabilityCollectorRunnable(AvailabilityFacet availChecker,
long interval) {
return getAvailabilityContext().createAvailabilityCollectorRunnable(availChecker, interval);
}
/**
* Returns a shared object representing the processes detected for given resource type under given parent.
* Note that this comes from a static field so it is shared by any resource contexts representing a
* resource of the same type under a single parent. This is to reduce the number of needed discoveries
* to a minimum.
*
* @param parentResourceUuid
* @param resourceType
* @return
*/
private static ResourceTypeProcesses getTrackedProcesses(String parentResourceUuid, ResourceType resourceType) {
synchronized (PROCESSES_PER_PARENT_PER_RESOURCE_TYPE) {
Children key = new Children(parentResourceUuid, resourceType);
ResourceTypeProcesses ret = PROCESSES_PER_PARENT_PER_RESOURCE_TYPE.get(key);
if (ret == null) {
ret = new ResourceTypeProcesses();
PROCESSES_PER_PARENT_PER_RESOURCE_TYPE.put(key, ret);
}
return ret;
}
}
/**
* Calculates a unique key based on parents' resource keys. The final key is the SHA256
* all the ancestry resource keys.
*
* @return key
*/
private String getAncestryBasedResourceKey() {
return this.getAncestryBasedResourceKey(null);
}
/**
* Calculates a unique key based on parents' resource keys.
*
* @param prefixKey extra key to be appended at the beginning of the digest process
* @return key
*/
private String getAncestryBasedResourceKey(String prefixKey) {
MessageDigestGenerator messageDigest = new MessageDigestGenerator(MessageDigestGenerator.SHA_256);
if (prefixKey != null) {
messageDigest.add(prefixKey.getBytes());
}
messageDigest.add(this.resource.getResourceKey().getBytes());
ResourceContext<?> ancestor = this.parentResourceContext;
while (ancestor != null) {
messageDigest.add(ancestor.getResourceKey().getBytes());
ancestor = ancestor.getParentResourceContext();
}
return messageDigest.getDigestString();
}
/**
* Return the {@link ComponentInvocationContext} object which plugins can use to determine if the component
* invocation has been interrupted.
*
* @return a {@link ComponentInvocationContext} object
*/
public ComponentInvocationContext getComponentInvocationContext() {
return componentInvocationContext;
}
/**
* Returns the {@link String} representation of the underlying resource.
* @return a {@link String} representation of the underlying resource
*/
public String getResourceDetails() {
return resource.toString();
}
}