package org.rhq.embeddedagent.extension; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.util.Map; import java.util.Properties; import java.util.prefs.Preferences; import org.apache.log4j.LogManager; import org.apache.log4j.xml.DOMConfigurator; import org.jboss.as.server.ServerEnvironment; import org.jboss.logging.Logger; import org.jboss.modules.Resource; import org.jboss.util.StringPropertyReplacer; import org.rhq.core.util.stream.StreamUtil; import org.rhq.enterprise.agent.AgentConfiguration; import org.rhq.enterprise.agent.AgentConfigurationConstants; import org.rhq.enterprise.agent.AgentConfigurationUpgrade; import org.rhq.enterprise.communications.ServiceContainerConfigurationConstants; public class AgentConfigurationSetup { private final Logger log = Logger.getLogger(AgentConfigurationSetup.class); private static final String DATA_DIRECTORY_NAME = "embeddedagent"; /** * The location of the configuration file - can be a file path or path within classloader. */ private final Resource configFile; /** * Properties that will be used to override preferences found in the preferences node and the configuration * preferences file. */ private final Map<String, String> configurationOverrides; /** * If <code>true</code>, will revert the agent's configuration back to the original configuration file. * Otherwise, the configuration will be that which is currently persisted in the preferences store. */ private final boolean resetConfigurationAtStartup; /** * The preferences node name that identifies the configuration set used to configure the services. */ private final String preferencesNodeName; /** * Provides environment information about the server in which we are embedded. */ private final ServerEnvironment serverEnvironment; public AgentConfigurationSetup(Resource configFile, boolean resetConfigurationAtStartup, Map<String, String> overrides, ServerEnvironment serverEnv) { this.configFile = configFile; this.resetConfigurationAtStartup = resetConfigurationAtStartup; this.serverEnvironment = serverEnv; this.configurationOverrides = prepareConfigurationOverrides(overrides); String agentName = this.configurationOverrides.get(AgentConfigurationConstants.NAME); preferencesNodeName = agentName; System.setProperty("rhq.agent.preferences-node", preferencesNodeName); } public String getPreferencesNodeName() { return this.preferencesNodeName; } private Map<String, String> prepareConfigurationOverrides(Map<String, String> overrides) { // perform some checking to setup defaults if need be String agentName = overrides.get(AgentConfigurationConstants.NAME); if (agentName == null || agentName.trim().length() == 0 || "-".equals(agentName)) { agentName = "embeddedagent-" + serverEnvironment.getNodeName(); } agentName = StringPropertyReplacer.replaceProperties(agentName); overrides.put(AgentConfigurationConstants.NAME, agentName); File dataDir = getAgentDataDirectory(); File pluginsDir = new File(serverEnvironment.getServerDataDir(), "embeddedagent-plugins"); overrides.put(AgentConfigurationConstants.DATA_DIRECTORY, dataDir.getAbsolutePath()); overrides.put(AgentConfigurationConstants.PLUGINS_DIRECTORY, pluginsDir.getAbsolutePath()); overrides.put(ServiceContainerConfigurationConstants.DATA_DIRECTORY, dataDir.getAbsolutePath()); // Some defaults are fine for standalone agents, but not for embedded agents. // Don't take sysprops that might be set in the VM - must configure the overrides via this subsystem's config. overrides.put(AgentConfigurationConstants.DO_NOT_OVERRIDE_PREFS_WITH_SYSPROPS, "true"); overrides.put(ServiceContainerConfigurationConstants.MBEANSERVER_NAME, "rhqembeddedagent"); overrides.put(AgentConfigurationConstants.DO_NOT_INSTALL_SHUTDOWN_HOOK, "true"); return overrides; } private File getAgentDataDirectory() { File dir = new File(serverEnvironment.getServerDataDir(), DATA_DIRECTORY_NAME); dir.mkdirs(); return dir; } public void preConfigureAgent() throws Exception { // we need to store the preferences prior to starting the agent if (resetConfigurationAtStartup) { log.debug("Resetting the embedded agent's configuration back to its original settings"); reloadAgentConfiguration(); cleanDataDirectory(); } else { log.debug("Loading the embedded agent's pre-existing configuration from preferences"); prepareConfigurationPreferences(); } return; } /** * Prepares the log config file so it writes the logs to the server's log directory. * This is needed if we call or use any agent class because it wants to use log4j. * This MUST be called prior to using any class that logs via log4j. * * @param logConfigFile the agent's out-of-box log config file * @return the new log config file that the agent should use * @throws Exception */ public void prepareLogConfigFile(Resource logConfigFile) throws Exception { try { File logDir = this.serverEnvironment.getServerLogDir(); String agentLogFile = new File(logDir, "embedded-agent.log").getAbsolutePath(); String cmdTraceLogFile = new File(logDir, "embedded-agent-command-trace.log").getAbsolutePath(); String logConfig = new String(StreamUtil.slurp(logConfigFile.openStream())); logConfig = logConfig.replace("\"logs/agent.log\"", "\"" + agentLogFile + "\""); logConfig = logConfig.replace("\"logs/command-trace.log\"", "\"" + cmdTraceLogFile + "\""); for (String app : new String[] { "ref=\"FILE\"", "ref=\"COMMANDTRACE\"" }) { logConfig = logConfig.replace("<!-- <appender-ref " + app + "/> -->", "<appender-ref " + app + "/>"); } File runtimeLogConfigFile = new File(getAgentDataDirectory(), "/log4j.xml"); ByteArrayInputStream in = new ByteArrayInputStream(logConfig.getBytes()); StreamUtil.copy(in, new FileOutputStream(runtimeLogConfigFile)); // this hot deploys the log4j.xml into log4j which is what the agent wants to use LogManager.resetConfiguration(); DOMConfigurator.configure(runtimeLogConfigFile.toURI().toURL()); } catch (Exception e) { log.error("Cannot tell the agent to put its logs in the logs directory - look elsewhere for the log files"); } } private Properties getAgentConfigurationProperties() { try { Properties properties = new Properties(); Preferences prefs = getPreferencesNode(); for (String key : prefs.keys()) { properties.setProperty(key, prefs.get(key, "?")); } return properties; } catch (Exception e) { throw new RuntimeException(e); } } private void reloadAgentConfiguration() throws Exception { // resetting the schema number to 0 forces prepareConfigurationPreferences // to clear prefs, reload cofig file, but it keeps the security token if we have one. Preferences prefNode = getPreferencesNode(); prefNode.putInt(AgentConfigurationConstants.CONFIG_SCHEMA_VERSION, 0); prefNode.flush(); // because of the above, this loads in the config file, wiping any existing prefs away (except token) prepareConfigurationPreferences(); } private void cleanDataDirectory() { AgentConfiguration config = new AgentConfiguration(getPreferencesNode()); File dataDir = config.getDataDirectory(); cleanDataFile(dataDir); // it is conceivable the comm services data directory was configured in a different // place than where the agent's data directory is - make sure we clean out that other data dir File commDataDir = config.getServiceContainerPreferences().getDataDirectory(); if (!commDataDir.getAbsolutePath().equals(dataDir.getAbsolutePath())) { cleanDataFile(commDataDir); } return; } /** * This will ensure the agent's configuration preferences are populated. If need be, the configuration file is * loaded and all overrides are overlaid on top of the preferences. The preferences are also upgraded to ensure they * conform to the latest configuration schema version. * * @return the agent configuration * * @throws Exception */ private AgentConfiguration prepareConfigurationPreferences() throws Exception { Preferences prefNode = getPreferencesNode(); AgentConfiguration config = new AgentConfiguration(prefNode); if (config.getAgentConfigurationVersion() == 0) { config = loadConfigurationFile(); } // now that the configuration preferences are loaded, we need to override them with any bootstrap override properties Map<String, String> overrides = configurationOverrides; if (overrides != null) { for (Map.Entry<String, String> entry : overrides.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); // allow ${var} notation in the values so we can provide variable replacements in the values value = StringPropertyReplacer.replaceProperties(value); prefNode.put(key, value); } } // let's make sure our configuration is upgraded to the latest schema AgentConfigurationUpgrade.upgradeToLatest(config.getPreferences()); return config; } /** * Loads the configuration file. * * @return the configuration that was loaded * * @throws Exception on failure */ private AgentConfiguration loadConfigurationFile() throws Exception { // We need to clear out any previous configuration in case the current config file doesn't specify a preference // that already exists in the preferences node. In this case, the configuration file wants to fall back on the // default value and if we don't clear the preferences, we aren't guaranteed the value stored in the backing // store is the default value. // But first we need to backup these original preferences in case the config file fails to load - // we'll restore the original values in that case. // Note that we squirrel away any security token we already have - we need to preserve this when we can // because otherwise the agent will not be able to re-register with any previous name is was registered with. Preferences prefNode = getPreferencesNode(); String securityToken = prefNode.get(AgentConfigurationConstants.AGENT_SECURITY_TOKEN, null); ByteArrayOutputStream backup = new ByteArrayOutputStream(); prefNode.exportSubtree(backup); prefNode.clear(); // now load in the preferences try { ByteArrayOutputStream rawConfigFile = new ByteArrayOutputStream(); InputStream rawConfigInputStream = configFile.openStream(); StreamUtil.copy(rawConfigInputStream, rawConfigFile, true); String newConfig = StringPropertyReplacer.replaceProperties(rawConfigFile.toString()); ByteArrayInputStream newConfigInputStream = new ByteArrayInputStream(newConfig.getBytes()); Preferences.importPreferences(newConfigInputStream); AgentConfiguration newAgentConfig = new AgentConfiguration(prefNode); if (newAgentConfig.getAgentConfigurationVersion() == 0) { throw new IllegalArgumentException("Bad preferences node"); } // If we had a security token, restore it so we can maintain our known registration with the server. // Note that if the configuration file already had a security token defined, it will be used and the old // token we had will be thrown away. if (securityToken != null) { if (newAgentConfig.getAgentSecurityToken() == null) { log.debug("Restoring embedded agent security token"); newAgentConfig.setAgentSecurityToken(securityToken); } else { log.debug("Not restoring embedded agent security token, the config file was preconfigured with one"); } } prefNode.flush(); } catch (Exception e) { // a problem occurred importing the config file; let's restore our original values try { Preferences.importPreferences(new ByteArrayInputStream(backup.toByteArray())); } catch (Exception e1) { // its conceivable the same problem occurred here as with the original exception (backing store problem?) // let's throw the original exception, not this one } throw e; } AgentConfiguration agentConfig = new AgentConfiguration(prefNode); return agentConfig; } /** * Returns the preferences for this agent. The node returned is where all preferences are to be stored. * * @return the agent preferences */ private Preferences getPreferencesNode() { Preferences topNode = Preferences.userRoot().node(AgentConfigurationConstants.PREFERENCE_NODE_PARENT); Preferences prefNode = topNode.node(preferencesNodeName); return prefNode; } /** * This will delete the given file and if its a directory, will recursively delete its contents and its * subdirectories. * * @param file the file/directory to delete */ private void cleanDataFile(File file) { boolean deleted; File[] doomedFiles = file.listFiles(); if (doomedFiles != null) { for (File doomedFile : doomedFiles) { cleanDataFile(doomedFile); // call this method recursively } } deleted = file.delete(); if (!deleted) { log.warn("Cannot clean data file [" + file + "]"); } return; } }