package org.rhq.embeddedagent.extension;
import java.io.CharArrayWriter;
import java.io.File;
import java.net.InetAddress;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import org.jboss.as.network.SocketBinding;
import org.jboss.as.server.ServerEnvironment;
import org.jboss.logging.Logger;
import org.jboss.modules.Module;
import org.jboss.modules.Resource;
import org.jboss.msc.service.Service;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StartException;
import org.jboss.msc.service.StopContext;
import org.jboss.msc.value.InjectedValue;
import org.rhq.enterprise.agent.AgentConfigurationConstants;
import org.rhq.enterprise.agent.AgentMain;
import org.rhq.enterprise.agent.AgentPrintWriter;
import org.rhq.enterprise.communications.ServiceContainerConfigurationConstants;
public class AgentService implements Service<AgentService> {
public static final ServiceName SERVICE_NAME = ServiceName.of("org.rhq").append(
AgentSubsystemExtension.SUBSYSTEM_NAME);
private final Logger log = Logger.getLogger(AgentService.class);
/**
* Our subsystem add-step handler will inject this as a dependency for us.
* This service gives us information about the server, like the install directory, data directory, etc.
* Package-scoped so the add-step handler can access this.
*/
final InjectedValue<ServerEnvironment> envServiceValue = new InjectedValue<ServerEnvironment>();
/**
* Our subsystem add-step handler will inject this as a dependency for us.
* This object will provide the binding address and port for the agent listener.
*/
final InjectedValue<SocketBinding> agentListenerBinding = new InjectedValue<SocketBinding>();
/**
* This service can be configured to be told explicitly about certain plugins to be
* enabled or disabled. This map holds that configuration. These aren't necessarily
* all the plugins that will be loaded, but they are those plugins this service was
* explicitly told about and indicates if they should be enabled or disabled.
* TODO: For any plugin not specified will, by default, be WHAT? Disabled???
*/
private Map<String, Boolean> plugins = Collections.synchronizedMap(new HashMap<String, Boolean>());
/**
* Configuration settings that override the out-of-box configuration file. These are settings
* that the user set in the subsystem (e.g. standalone.xml or via AS CLI).
*/
private Map<String, String> configOverrides = Collections.synchronizedMap(new HashMap<String, String>());
/**
* This is the actual embedded agent. This is what handles the plugin container lifecycle
* and communication to/from the server.
*/
private AtomicReference<AgentMain> theAgent = new AtomicReference<AgentMain>();
/**
* This is the daemon thread running the agent.
*/
private Thread agentThread;
public AgentService() {
}
@Override
public AgentService getValue() throws IllegalStateException, IllegalArgumentException {
return this;
}
@Override
public void start(StartContext context) throws StartException {
log.info("Embedded agent service starting");
startAgent();
}
@Override
public void stop(StopContext context) {
log.info("Embedded agent service stopping");
stopAgent();
}
/**
* Returns the set of plugins the service knows about and whether
* or not those plugins are to be enabled or disabled.
* You get back a copy, not the actual map.
*
* @return plugins and their enable-flag
*/
protected Map<String, Boolean> getPlugins() {
synchronized (plugins) {
return new HashMap<String, Boolean>(plugins);
}
}
/**
* Sets the enable flags for plugins.
*
* @return plugins and their enable-flag (if <code>null</code>, assumes an empty map)
*/
protected void setPlugins(Map<String, Boolean> pluginsWithEnableFlag) {
synchronized (plugins) {
plugins.clear();
if (pluginsWithEnableFlag != null) {
plugins.putAll(pluginsWithEnableFlag);
}
}
log.info("New plugin definitions: " + pluginsWithEnableFlag);
}
protected void setConfigurationOverrides(Map<String, String> overrides) {
synchronized (configOverrides) {
configOverrides.clear();
if (overrides != null) {
configOverrides.putAll(overrides);
}
}
}
protected boolean isAgentStarted() {
AgentMain agent = theAgent.get();
return (agent != null && agent.isStarted());
}
protected void startAgent() throws StartException {
if (isAgentStarted()) {
log.info("Embedded agent is already started.");
return;
}
log.info("Starting the embedded agent now");
try {
// make sure we pre-configure the agent with some settings taken from our runtime environment
SocketBinding agentListenerBindingValue = agentListenerBinding.getValue();
String agentBindAddress = agentListenerBindingValue.getAddress().getHostAddress();
String agentBindPort = String.valueOf(agentListenerBindingValue.getAbsolutePort());
// just pick one if we weren't given one - we can't bind "to all"
if (agentBindAddress.equals("0.0.0.0") || agentBindAddress.equals("::/128")) {
agentBindAddress = InetAddress.getLocalHost().getCanonicalHostName();
}
configOverrides.put(ServiceContainerConfigurationConstants.CONNECTOR_BIND_ADDRESS, agentBindAddress);
configOverrides.put(ServiceContainerConfigurationConstants.CONNECTOR_BIND_PORT, agentBindPort);
// if the agent was told to explicitly enable some plugins, add them to the "enabledPlugins" preference.
// if the agent was told to explicitly disnable some plugins, add them to the "disabledPlugins" preference.
StringBuilder enabledPlugins = new StringBuilder();
StringBuilder disabledPlugins = new StringBuilder();
for (Map.Entry<String, Boolean> entry : plugins.entrySet()) {
String pluginName = entry.getKey();
Boolean enabled = entry.getValue();
if (enabled) {
enabledPlugins.append((enabledPlugins.length() > 0) ? "," : "").append(pluginName);
} else {
disabledPlugins.append((disabledPlugins.length() > 0) ? "," : "").append(pluginName);
}
}
if (enabledPlugins.length() > 0) {
configOverrides.put(AgentConfigurationConstants.PLUGINS_ENABLED, enabledPlugins.toString());
}
if (disabledPlugins.length() > 0) {
configOverrides.put(AgentConfigurationConstants.PLUGINS_DISABLED, disabledPlugins.toString());
}
ServerEnvironment env = envServiceValue.getValue();
boolean resetConfigurationAtStartup = true;
AgentConfigurationSetup configSetup = new AgentConfigurationSetup(
getExportedResource("conf/agent-configuration.xml"), resetConfigurationAtStartup, configOverrides, env);
// prepare the agent logging first thing so the agent logs messages using this config
configSetup.prepareLogConfigFile(getExportedResource("conf/log4j.xml"));
configSetup.preConfigureAgent();
// build the startup command line arguments to pass to the agent
String[] args = new String[3];
args[0] = "--daemon";
args[1] = "--pref=" + configSetup.getPreferencesNodeName();
args[2] = "--output=" + new File(env.getServerLogDir(), "embedded-agent.out").getAbsolutePath();
theAgent.set(new AgentMain(args));
agentThread = new Thread("Embedded Agent Start Thread") {
public void run() {
try {
theAgent.get().start();
} catch (InterruptedException e) {
// agent just exited due to being shutdown, die quietly
log.debug("Embedded agent has exited.");
} catch (Throwable t) {
log.error("Embedded agent aborted with exception.", t);
}
};
};
agentThread.setDaemon(true);
agentThread.start();
} catch (Exception e) {
throw new StartException(e);
}
}
protected void stopAgent() {
try {
if (!isAgentStarted()) {
log.info("Embedded agent is already stopped.");
} else {
log.info("Stopping the embedded agent now");
theAgent.get().shutdown();
}
} finally {
if (agentThread != null) {
agentThread.interrupt();
}
}
theAgent.set(null);
}
protected String executePromptCommand(String command) throws Exception {
AgentMain agent = theAgent.get();
if (agent == null) {
throw new IllegalStateException("Embedded agent is not available");
}
CharArrayWriter listener = new CharArrayWriter();
AgentPrintWriter apw = agent.getOut();
try {
apw.addListener(listener);
agent.executePromptCommand(command);
} 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;
}
/**
* Gets information about a file that is inside our module. Use this to
* obtain files locationed in the embedded agent, for example, pass in
* "conf/agent-configuration.xml" to get the config file.
*
* @param name name of the agent file
* @return object referencing the file from our module
*/
private Resource getExportedResource(String name) {
Module module = Module.forClass(getClass());
Resource r = module.getExportedResource("rhq-agent", name);
return r;
}
}