org.rhq.enterprise.agent.AgentManagement Maven / Gradle / Ivy
The newest version!
/*
* RHQ Management Platform
* Copyright (C) 2005-2008 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.agent;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.ExecutionException;
import java.util.prefs.Preferences;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.MBeanServerInvocationHandler;
import javax.management.ObjectName;
import org.rhq.core.clientapi.descriptor.AgentPluginDescriptorUtil;
import org.rhq.core.clientapi.descriptor.plugin.PluginDescriptor;
import org.rhq.core.clientapi.server.core.CoreServerService;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.discovery.AvailabilityReport;
import org.rhq.core.domain.measurement.AvailabilityType;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.pc.PluginContainer;
import org.rhq.core.pc.PluginContainerConfiguration;
import org.rhq.core.pc.inventory.InventoryManager;
import org.rhq.core.pc.inventory.ResourceContainer;
import org.rhq.core.pluginapi.operation.OperationResult;
import org.rhq.core.util.MessageDigestGenerator;
import org.rhq.enterprise.agent.AgentRestartCounter.AgentRestartReason;
import org.rhq.enterprise.communications.ServiceContainerMetricsMBean;
import org.rhq.enterprise.communications.command.client.ClientCommandSender;
import org.rhq.enterprise.communications.command.client.ClientCommandSenderMetrics;
import org.rhq.enterprise.communications.command.client.ClientRemotePojoFactory;
/**
* This is the management layer for the agent. This is the MBean that is used to manage the agent itself. It emits the
* agent's metric data.
*
* @author John Mazzitelli
*/
public class AgentManagement implements AgentManagementMBean, MBeanRegistration {
/**
* The agent being monitored.
*/
private AgentMain m_agent;
/**
* Where this MBean is registered.
*/
private MBeanServer m_mbs;
/**
* The name this MBean instance is registered under.
*/
private ObjectName m_objectName;
/**
* Constructor for {@link AgentManagement}.
*
* @param agent the agent to be monitored
*/
public AgentManagement(AgentMain agent) {
m_agent = agent;
}
public void updateAgent() {
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Void run() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.sleep(5000L); // give our updateAgent() caller a chance to return and finish
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
AgentUpdateThread.updateAgentNow(m_agent, false);
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else {
throw new RuntimeException(e);
}
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
return null;
}
});
}
public void switchToServer(String server) {
m_agent.switchToServer(server);
}
public void restart() {
// restarting the agent is a suicidal act - this MBean instance will
// be unregistered after we shutdown. Therefore, we must do this in a
// separate thread so as to allow this method to return successfully
// first. Therefore, this method must inherently do its thing asynchronously.
// Another important fact is that if this method is called from the rhq-agent plugin
// which is co-located in the same JVM, restarting the plugin container actually creates
// a whole bunch of new classloaders that ALL inherit the access control context of the
// agent plugin executing thread and classloader. The access control context contains
// a reference to the agent plugin's classloader through its protection domain and thus,
// by using the rhq-agent's restart, shutdown or restartPluginContainer operations we
// create a classloader leak.
//
// The old rhq-agent's plugin classloader will never be released and will carry along with
// it all the classes, slowly contributing to the eventual permgen depletion and OOMEs.
//
// To solve this problem, we run the restart, shutdown and restartPluginContainer methods with
// the access control context of this class. This class is defined in the agent itself and thus
// its access control context doesn't contain the "baggage" from the plugin classloaders.
new Thread(new Runnable() {
public void run() {
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Void run() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.sleep(5000L); // give our restart() caller a chance to return and finish
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
m_agent.shutdown();
m_agent.start();
m_agent.getAgentRestartCounter().restartedAgent(AgentRestartReason.OPERATION);
} catch (Exception e) {
e.printStackTrace(); // TODO what do to here?
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
return null;
}
});
}
}, "RHQ Agent Restart Thread").start();
}
public void shutdown() {
// shutting down the agent is a suicidal act - this MBean instance will
// be unregistered after we shutdown. Therefore, we must do this in a
// separate thread so as to allow this method to return successfully
// first. Therefore, this method must inherently do its thing asynchronously.
// see the explanation in the restart() method for why we're running this as
// a privileged action
new Thread(new Runnable() {
public void run() {
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Void run() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.sleep(5000L); // give our shutdown() caller a chance to return and finish
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
m_agent.shutdown();
} catch (InterruptedException e) {
// exit the thread
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
return null;
}
});
}
}, "RHQ Agent Shutdown Thread").start();
}
public void downloadLatestFailoverList() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
m_agent.performPrimaryServerSwitchoverCheck();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public void updatePlugins() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
m_agent.updatePlugins();
restartPluginContainer();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public OperationResult retrieveAllPluginInfo() {
List plugins;
OperationResult info = new OperationResult();
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
PluginUpdate updater = getPluginUpdateObject();
plugins = updater.getCurrentPluginFiles();
PluginContainerConfiguration pcConfig = m_agent.getConfiguration().getPluginContainerConfiguration();
List enabledPlugins = pcConfig.getEnabledPlugins();
List disabledPlugins = pcConfig.getDisabledPlugins();
PropertyList list = new PropertyList("plugins".intern());
info.getComplexResults().put(list);
if (plugins.size() > 0) {
for (File plugin : plugins) {
String pluginName;
String pluginDisplayName;
try {
URL url = plugin.toURI().toURL();
PluginDescriptor descriptor = AgentPluginDescriptorUtil.loadPluginDescriptorFromUrl(url);
pluginName = descriptor.getName();
pluginDisplayName = descriptor.getDisplayName();
} catch (Exception t) {
pluginName = "?cannot-parse-descriptor?".intern();
pluginDisplayName = "?cannot-parse-descriptor?".intern();
}
PropertyMap map = new PropertyMap("plugin".intern());
map.put(new PropertySimple(PLUGIN_INFO_NAME, pluginName));
map.put(new PropertySimple(PLUGIN_INFO_DISPLAY_NAME, pluginDisplayName));
map.put(new PropertySimple(PLUGIN_INFO_PATH, plugin.getAbsoluteFile()));
map.put(new PropertySimple(PLUGIN_INFO_TIMESTAMP, new Date(plugin.lastModified())));
map.put(new PropertySimple(PLUGIN_INFO_SIZE, plugin.length()));
// plugin is either whitelisted or the white list is empty
boolean isEnabled = enabledPlugins.isEmpty() || enabledPlugins.contains(pluginName);
// ..and is not on the black list
isEnabled &= !disabledPlugins.contains(pluginName);
map.put(new PropertySimple(PLUGIN_INFO_ENABLED, isEnabled));
try {
map.put(new PropertySimple(PLUGIN_INFO_MD5, MessageDigestGenerator.getDigestString(plugin)));
} catch (IOException e) {
map.put(new PropertySimple(PLUGIN_INFO_MD5, e.toString()));
}
list.add(map);
}
}
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
return info;
}
public OperationResult retrievePluginInfo(String pluginName) {
List plugins;
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
PluginUpdate updater = getPluginUpdateObject();
plugins = updater.getCurrentPluginFiles();
if (plugins.size() > 0) {
for (File plugin : plugins) {
String pluginDisplayName;
String pluginNameToReturn;
try {
URL url = plugin.toURI().toURL();
PluginDescriptor pluginDescriptor = AgentPluginDescriptorUtil.loadPluginDescriptorFromUrl(url);
pluginDisplayName = pluginDescriptor.getDisplayName();
pluginNameToReturn = pluginDescriptor.getName();
} catch (Exception t) {
continue;
}
if (pluginNameToReturn.toLowerCase().equals(pluginName.toLowerCase())) {
OperationResult opResults = new OperationResult();
Configuration info = opResults.getComplexResults();
info.put(new PropertySimple(PLUGIN_INFO_NAME, pluginNameToReturn));
info.put(new PropertySimple(PLUGIN_INFO_DISPLAY_NAME, pluginDisplayName));
info.put(new PropertySimple(PLUGIN_INFO_PATH, plugin.getAbsoluteFile()));
info.put(new PropertySimple(PLUGIN_INFO_TIMESTAMP, new Date(plugin.lastModified())));
info.put(new PropertySimple(PLUGIN_INFO_SIZE, plugin.length()));
try {
info.put(new PropertySimple(PLUGIN_INFO_MD5, MessageDigestGenerator.getDigestString(plugin)));
} catch (IOException e) {
info.put(new PropertySimple(PLUGIN_INFO_MD5, e.toString()));
}
return opResults;
}
}
}
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
throw new IllegalArgumentException("There is no plugin named [" + pluginName + "]");
}
public void restartPluginContainer() {
// see the explanation in the restart() method for why we're running this as
// a privileged action
new Thread(new Runnable() {
public void run() {
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Void run() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.sleep(5000L); // give our caller a chance to return and finish
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
m_agent.shutdownPluginContainer();
m_agent.startPluginContainer(500L);
} catch (InterruptedException e) {
// exit the thread
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
return null;
}
});
}
}, "RHQ Agent Plugin Container Restart Thread").start();
}
public OperationResult executeAvailabilityScan(Boolean changesOnly) {
boolean changes = (changesOnly != null) ? changesOnly.booleanValue() : false;
AvailabilityReport report;
InventoryManager inventoryManager = PluginContainer.getInstance().getInventoryManager();
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
report = inventoryManager.executeAvailabilityScanImmediately(changes);
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
OperationResult opResult = new OperationResult();
Configuration complexResults = opResult.getComplexResults();
PropertyList list = new PropertyList("resourceAvailabilities");
complexResults.put(list);
String agentName;
Boolean changesOnlyFromReport;
if (report != null) {
agentName = report.getAgentName();
changesOnlyFromReport = Boolean.valueOf(report.isChangesOnlyReport());
List avails = report.getResourceAvailability();
if (avails.size() > 0) {
for (AvailabilityReport.Datum avail : avails) {
boolean isUp = avail.getAvailabilityType() == AvailabilityType.UP;
// lookup the heavy-weight resource object
int resourceId = avail.getResourceId();
ResourceContainer resourceContainer = inventoryManager.getResourceContainer(resourceId);
Resource resource = resourceContainer.getResource();
PropertyMap map = new PropertyMap("resourceAvailability");
map.put(new PropertySimple("resourceId", Integer.valueOf(resource.getId())));
map.put(new PropertySimple("resourceName", resource.getName()));
map.put(new PropertySimple("isAvailable", Boolean.valueOf(isUp)));
list.add(map);
}
}
} else {
// report was null - this means there are no committed resources in inventory
agentName = m_agent.getConfiguration().getAgentName();
changesOnlyFromReport = changesOnly;
}
complexResults.put(new PropertySimple("agentName", agentName));
complexResults.put(new PropertySimple("isChangesOnly", changesOnlyFromReport));
return opResult;
}
public String getVersion() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
return Version.getProductVersion();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public long getCurrentTime() {
return System.currentTimeMillis();
}
public String retrieveCurrentDateTime(String timeZone) {
DateFormat df = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.FULL);
if (timeZone == null || timeZone.length() == 0) {
df.setTimeZone(TimeZone.getDefault());
} else {
df.setTimeZone(TimeZone.getTimeZone(timeZone));
}
return df.format(new Date());
}
public void setDebugMode(Boolean enabled, Boolean traceMessaging) throws ExecutionException {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
if (enabled != null && enabled.booleanValue()) {
executePromptCommand("debug -f log4j-debug.xml");
if (traceMessaging != null && traceMessaging.booleanValue()) {
executePromptCommand("debug -c true");
} else {
executePromptCommand("debug -c false");
}
} else {
executePromptCommand("debug -f log4j.xml");
executePromptCommand("debug -c false");
}
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
return;
}
public String executePromptCommand(final String command) throws ExecutionException {
// we don't know what command will get executed, so let's proactively run it in the privileged action
// for why it is a good idea, see the comments in the restart() method
if (command.startsWith("update ")) {
throw new ExecutionException(new IllegalArgumentException(
"Cannot invoke 'update' prompt command - use 'Update Agent' operation instead"));
}
try {
return AccessController.doPrivileged(new PrivilegedExceptionAction() {
@Override
public String run() throws Exception {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
CharArrayWriter listener = new CharArrayWriter();
AgentPrintWriter apw = m_agent.getOut();
try {
apw.addListener(listener);
m_agent.executePromptCommand(
command); // TODO should we do something if false is returned? (i.e. kill agent?)
} catch (Exception e) {
throw new ExecutionException(listener.toString(),
e); // the message is the output, cause is the thrown exception
} finally {
apw.removeListener(listener);
}
String output = listener.toString();
return output;
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
});
} catch (PrivilegedActionException e) {
Throwable cause = e.getCause();
if (cause instanceof ExecutionException) {
throw (ExecutionException) cause;
} else {
throw new ExecutionException(e);
}
}
}
public String getAgentHomeDirectory() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
return m_agent.getAgentHomeDirectory();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public int getNumberAgentRestarts() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
return m_agent.getAgentRestartCounter().getNumberOfRestarts();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public String getReasonForLastRestart() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
return m_agent.getAgentRestartCounter().getLastAgentRestartReason().toString();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public long getAgentServerClockDifference() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
return m_agent.getAgentServerClockDifference();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public long getUptime() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
long start_time = m_agent.getStartTime();
if (start_time > 0) {
return (System.currentTimeMillis() - start_time) / 1000L; // we want units in seconds
}
return 0L;
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public long getNumberSuccessfulCommandsReceived() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
return getServerSideMetrics().getNumberSuccessfulCommandsReceived();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public long getNumberFailedCommandsReceived() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
return getServerSideMetrics().getNumberFailedCommandsReceived();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public long getNumberTotalCommandsReceived() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
return getServerSideMetrics().getNumberTotalCommandsReceived();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public long getAverageExecutionTimeReceived() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
return getServerSideMetrics().getAverageExecutionTimeReceived();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public long getAverageExecutionTimeSent() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
return getClientSideMetrics().getAverageExecutionTimeSent();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public long getNumberSuccessfulCommandsSent() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
return getClientSideMetrics().getNumberSuccessfulCommandsSent();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public long getNumberFailedCommandsSent() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
return getClientSideMetrics().getNumberFailedCommandsSent();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public long getNumberTotalCommandsSent() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
ClientCommandSenderMetrics metrics = getClientSideMetrics();
return metrics.getNumberSuccessfulCommandsSent() + metrics.getNumberFailedCommandsSent();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public long getNumberCommandsActiveSent() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
return getClientSideMetrics().getNumberCommandsActive();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public long getNumberCommandsInQueue() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
return getClientSideMetrics().getNumberCommandsInQueue();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public long getNumberCommandsSpooled() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
return getClientSideMetrics().getNumberCommandsSpooled();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public boolean isSending() {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
ClientCommandSenderMetrics metrics = getClientSideMetrics();
return metrics.isSending();
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
}
public long getJVMFreeMemory() {
return Runtime.getRuntime().freeMemory();
}
public long getJVMTotalMemory() {
return Runtime.getRuntime().totalMemory();
}
public int getJVMActiveThreads() {
return ManagementFactory.getThreadMXBean().getThreadCount();
}
public Properties getAgentConfiguration() {
Properties properties = new Properties();
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
try {
Preferences prefs = m_agent.getConfiguration().getPreferences();
String[] keys = prefs.keys();
for (String key : keys) {
properties.setProperty(key, prefs.get(key, ""));
}
} catch (Exception e) {
properties.setProperty("ERROR", e.getMessage()); // this should really never happen
}
} finally {
Thread.currentThread().setContextClassLoader(originalCL);
}
return properties;
}
public void mergeIntoAgentConfiguration(Properties config) {
ClassLoader originalCL = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
if ((config != null) && (config.size() > 0)) {
Preferences prefs = m_agent.getConfiguration().getPreferences();
Set