Mercurial > hg > release > thermostat-0.7
changeset 7:93c6c1c31eb8
Load configured backends by classname.
author | Jon VanAlten <jon.vanalten@redhat.com> |
---|---|
date | Mon, 28 Nov 2011 17:57:18 -0500 |
parents | 45e55e0d1e65 |
children | 3db016a4e023 |
files | config/agent.properties src/com/redhat/thermostat/agent/Agent.java src/com/redhat/thermostat/agent/Main.java src/com/redhat/thermostat/agent/config/Configuration.java src/com/redhat/thermostat/backend/Backend.java src/com/redhat/thermostat/backend/BackendLoadException.java src/com/redhat/thermostat/backend/BackendRegistry.java src/com/redhat/thermostat/backend/sample/SampleBackend.java src/com/redhat/thermostat/common/Constants.java src/com/redhat/thermostat/common/LaunchException.java |
diffstat | 10 files changed, 256 insertions(+), 139 deletions(-) [+] |
line wrap: on
line diff
--- a/config/agent.properties Wed Nov 23 17:18:53 2011 -0500 +++ b/config/agent.properties Mon Nov 28 17:57:18 2011 -0500 @@ -8,11 +8,18 @@ config_url=mongodb://127.0.0.1 # ## Backend Configuration -# This must be a comma separated list naming each backend that should run +# This must be a comma separated list naming the fully qualified class name for +# each backend that should run backends= -# For each backend specified above, there must be a .active property specified. +# Backends may also use their name as prefix for backend-specific configuration. +# For example, if backend foo requires a property called bar, then a line +# containing 'foo.bar=baz' should be included. 'foo' in this case should be +# the human-friendly alias for the backend, ie the string returned by the +# getName() method of the class listed in the backends string. +# +# For each backend, there may be a .active property specified. # ie: for backend 'foo' there should be a 'foo.active = bar' line. 'bar' -# should be a comma separated list including one or more of: +# must be a comma separated list including one or more of: # 'new' - The backend should attempt to attach to any new java process that # starts. Existing processes should not be instrumented. # 'all' - The backend should attempt to attach to all existing java @@ -21,7 +28,11 @@ # are equivalent to the Linux process id. This allows for the case # of specific existing java processes to be instrumented. # -# Backends may also use their name as prefix for backend-specific configuration. -# For example, if backend foo requires a property called bar, then a line -# containing 'foo.bar=baz' should be included. - +# Alternatively, you may specify 'none' and the backend will not begin collecting +# from any VM, but will be available to clients who wish to activate it. +# +# If there is no .active property specified, then 'none' is implied. +# +## Sample backend configuration +#sample-backend.active=none +#sample-backend.myconfiguration=property \ No newline at end of file
--- a/src/com/redhat/thermostat/agent/Agent.java Wed Nov 23 17:18:53 2011 -0500 +++ b/src/com/redhat/thermostat/agent/Agent.java Mon Nov 28 17:57:18 2011 -0500 @@ -1,11 +1,16 @@ package com.redhat.thermostat.agent; import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; import com.mongodb.DB; import com.redhat.thermostat.agent.config.Configuration; +import com.redhat.thermostat.backend.Backend; import com.redhat.thermostat.backend.BackendRegistry; import com.redhat.thermostat.common.Constants; +import com.redhat.thermostat.common.LaunchException; +import com.redhat.thermostat.common.utils.LoggingUtils; /** * Represents the Agent running on a host. @@ -16,6 +21,8 @@ private final BackendRegistry backendRegistry; private final Configuration config; + private static final Logger LOGGER = LoggingUtils.getLogger(Agent.class); + private DB database; public Agent(BackendRegistry backendRegistry, Configuration config, DB db) { @@ -30,23 +37,33 @@ config.setCollection(database.getCollection(Constants.AGENT_CONFIG_COLLECTION_NAME)); } - private void loadConfiguredBackends() { - // TODO Once Configuration has relevant methods for getting list of backend names and backend-specific parameters, iterate over that list, - // activating as per configuration parameters and adding each to the registry + private void startBackends() throws LaunchException { + for (Backend be : backendRegistry.getAll()) { + if (!be.activate()) { + // When encountering issues during startup, we should not attempt to continue activating. + stopBackends(); + throw new LaunchException("Could not activate backend: " + be.getName()); + } + } } - private void stopAllBackends() { - // TODO Inverse of the above. Stop each backend, remove from registry. + private void stopBackends() { + for (Backend be : backendRegistry.getAll()) { + if (!be.deactivate()) { + // When encountering issues during shutdown, we should attempt to shut down remaining backends. + LOGGER.log(Level.WARNING, "Issue while deactivating backend: " + be.getName()); + } + } } - public void start() { - loadConfiguredBackends(); + public void start() throws LaunchException { + startBackends(); config.publish(); } public void stop() { config.unpublish(); - stopAllBackends(); + stopBackends(); } public UUID getId() {
--- a/src/com/redhat/thermostat/agent/Main.java Wed Nov 23 17:18:53 2011 -0500 +++ b/src/com/redhat/thermostat/agent/Main.java Mon Nov 28 17:57:18 2011 -0500 @@ -11,6 +11,7 @@ import com.mongodb.Mongo; import com.mongodb.MongoURI; import com.redhat.thermostat.agent.config.Configuration; +import com.redhat.thermostat.backend.BackendLoadException; import com.redhat.thermostat.backend.BackendRegistry; import com.redhat.thermostat.common.Constants; import com.redhat.thermostat.common.LaunchException; @@ -52,7 +53,14 @@ logger.setLevel(config.getLogLevel()); - BackendRegistry backendRegistry = BackendRegistry.getInstance(); + BackendRegistry backendRegistry = null; + try { + backendRegistry = new BackendRegistry(config); + } catch (BackendLoadException ble) { + System.err.println("Could not get BackendRegistry instance."); + ble.printStackTrace(); + System.exit(Constants.EXIT_BACKEND_LOAD_ERROR); + } Mongo mongo = null; DB db = null; @@ -69,7 +77,13 @@ Agent agent = new Agent(backendRegistry, config, db); config.setAgent(agent); - agent.start(); + try { + agent.start(); + } catch (LaunchException le) { + System.err.println("Agent could not start, probably because a configured backend could not be activated."); + le.printStackTrace(); + System.exit(Constants.EXIT_BACKEND_START_ERROR); + } logger.fine("agent published"); try {
--- a/src/com/redhat/thermostat/agent/config/Configuration.java Wed Nov 23 17:18:53 2011 -0500 +++ b/src/com/redhat/thermostat/agent/config/Configuration.java Mon Nov 28 17:57:18 2011 -0500 @@ -2,14 +2,13 @@ import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; -import java.util.Set; import java.util.logging.Level; import com.mongodb.BasicDBObject; @@ -23,23 +22,32 @@ public final class Configuration { + /* FIXME + * + * This class needs some love. It mixes up startup configuration with runtime configuration, + * while each is handled in very different ways. It probably should be split into separate + * classes, but it makes very little sense to do that before we have a Storage abstraction + * hiding implementation details (ie Mongo API stuff). + */ + private Properties props; + private Level logLevel; private boolean localMode; private int mongodPort; private int mongosPort; private String databaseURI; private String completeDatabaseURI; - private Backends backends; private String hostname; private Agent agent; - private boolean published = false; private DBCollection dbCollection = null; public Configuration(String[] args, Properties props) throws LaunchException { + this.props = props; + initFromDefaults(); - initFromProperties(props); + initFromProperties(); initFromArguments(args); if (localMode) { @@ -64,14 +72,13 @@ databaseURI = Defaults.DATABASE_URI; } - private void initFromProperties(Properties props) { + private void initFromProperties() { if (props.getProperty(Constants.AGENT_PROPERTY_MONGOD_PORT) != null) { mongodPort = Integer.valueOf(props.getProperty(Constants.AGENT_PROPERTY_MONGOD_PORT)); } if (props.getProperty(Constants.AGENT_PROPERTY_MONGOS_PORT) != null) { mongosPort = Integer.valueOf(props.getProperty(Constants.AGENT_PROPERTY_MONGOS_PORT)); } - backends = new Backends(props); } private void initFromArguments(String[] args) throws LaunchException { @@ -86,6 +93,7 @@ } } + // TODO hide Mongo stuff behind Storage facade public void setCollection(DBCollection collection) { dbCollection = collection; } @@ -102,12 +110,13 @@ return hostname; } + // TODO all of this should be assembled somewhere behind the Storage facade, once it exists. public DBObject toDBObject() { BasicDBObject result = new BasicDBObject(); // TODO explicit exception if agent not yet set. result.put(Constants.AGENT_ID, agent.getId().toString()); result.put(Constants.AGENT_CONFIG_KEY_HOST, hostname); - result.put(Constants.AGENT_CONFIG_KEY_BACKENDS, backends.toDBObject()); + // TODO create nested backend config parts return result; } @@ -116,29 +125,52 @@ } public void publish() { - // TODO explicit exception if dbCollection has not yet been set. + // TODO Hide Mongo stuff behind Storage facade. dbCollection.insert(toDBObject(), WriteConcern.SAFE); // TODO Start configuration-change-detection thread. - published = true; } public void unpublish() { // TODO Stop configuration-change-detection thread. + // TODO hide Mongo stuff behind storage facade. dbCollection.remove(new BasicDBObject(Constants.AGENT_ID, agent.getId().toString()), WriteConcern.NORMAL); - published = false; + } + + public List<String> getStartupBackendClassNames() { + String fullPropertyString = props.getProperty(Constants.AGENT_PROPERTY_BACKENDS); + if ((fullPropertyString == null) // Avoid NPE + || (fullPropertyString.length() == 0)) { /* If we do the split() on this empty string, + * it will produce an array of size 1 containing + * the empty string, which we do not want. + */ + return new ArrayList<String>(); + } else { + return Arrays.asList(fullPropertyString.trim().split(",")); + } } - public String getBackendConfigValue(String backendName, String configurationKey) { - String value = null; - if (published) { - value = getBackendConfigFromDatabase(backendName, configurationKey); - } else { - value = backends.getConfigValue(backendName, configurationKey); + public Map<String, String> getStartupBackendConfigMap(String backendName) { + String prefix = backendName + "."; + Map<String, String> configMap = new HashMap<String, String>(); + for (Entry<Object, Object> e : props.entrySet()) { + String key = (String) e.getKey(); + if (key.startsWith(prefix)) { + String value = (String) e.getValue(); + String mapKey = key.substring(prefix.length()); + configMap.put(mapKey, value); + } } - return value; + return configMap; } - private String getBackendConfigFromDatabase(String backendName, String configurationKey) { + /** + * + * @param backendName + * @param configurationKey + * @return + */ + public String getBackendConfigValue(String backendName, String configurationKey) { + // TODO hide Mongo stuff behind Storage facade. DBObject config = dbCollection.findOne(new BasicDBObject(Constants.AGENT_ID, agent.getId().toString())); // TODO get the appropriate value from this agent's configuration. return null; @@ -210,74 +242,4 @@ return logLevel; } } - - /** - * A wrapper around the backend-specific information. Used mainly for convenience during startup; once running all config should happen via database. - */ - private static class Backends { - /* TODO Do we really need to do all this mapping? These values should only be used at startup, maybe best to just have convenience wrappers - * around the properties file... - */ - /** {backend-name: { opt1: va1, opt2:val2, } } */ - private Map<String, Map<String, String>> info; - - public Backends(Properties props) { - info = new HashMap<String, Map<String, String>>(); - initializeFromProperties(props); - } - - private void initializeFromProperties(Properties props) { - List<String> backendNames = Arrays.asList(props.getProperty(Constants.AGENT_PROPERTY_BACKENDS).trim().split(",")); - for (String backendName : backendNames) { - // TODO Initialize Map<String, String> of properties for each. - } - } - - public String getConfigValue(String backendName, String configurationKey) { - // TODO make this more robust, appropriate exceptions for invalid input values. - Map<String, String> backendValues = info.get(backendName); - return backendValues.get(configurationKey); - } - - private void addBackend(String backendName) { - if (!info.containsKey(backendName)) { - info.put(backendName, new HashMap<String, String>()); - } - } - - public Object toDBObject() { - BasicDBObject result = new BasicDBObject(); - for (Entry<String, Map<String, String>> e : info.entrySet()) { - BasicDBObject config = new BasicDBObject(); - config.putAll(e.getValue()); - result.put(e.getKey(), config); - } - return result; - } - - public Set<String> getBackends() { - return info.keySet(); - } - - public Set<String> getMatchingBackends(String key, String value) { - // TODO perhaps extend this to regex? - Set<String> matched = new HashSet<String>(); - for (Entry<String, Map<String, String>> e : info.entrySet()) { - if (e.getValue().get(key) != null && e.getValue().get(key).equals(value)) { - matched.add(e.getKey()); - } - } - return matched; - } - - public void addConfig(String backend, String key, String value) { - addBackend(backend); - info.get(backend).put(key, value); - } - - public Map<String, String> getConfig(String backend) { - return info.get(backend); - } - - } }
--- a/src/com/redhat/thermostat/backend/Backend.java Wed Nov 23 17:18:53 2011 -0500 +++ b/src/com/redhat/thermostat/backend/Backend.java Mon Nov 28 17:57:18 2011 -0500 @@ -8,21 +8,20 @@ * registered with the {@link BackendRegistry}. */ public abstract class Backend { - private String name; - public Backend(String name, Map<String, String> properties) { - this.name = name; - for (Entry<String, String> e : properties.entrySet()) { + public Backend() { + } + + public final void setInitialConfiguration(Map<String, String> configMap) { + for (Entry<String, String> e : configMap.entrySet()) { setConfigurationValue(e.getKey(), e.getValue()); } } - public abstract void setConfigurationValue(String name, String value); + protected abstract void setConfigurationValue(String name, String value); /** Returns the name of the {@link Backend} */ - public String getName() { - return name; - } + public abstract String getName(); /** Returns the description of the {@link Backend} */ public abstract String getDescription(); @@ -39,16 +38,19 @@ public abstract Map<String, String> getConfigurationMap(); /** - * Activate the {@link Backend}. The backend should start gathering general - * data (not specific to any vm) and start sending it + * Activate the {@link Backend}. Based on the current configuration, + * begin pushing data to the Storage layer. If the {@link Backend} is + * already active, this method should have no effect * * @return true on success, false if there was an error */ public abstract boolean activate(); /** - * Deactivate the {@link Backend}. The backend should release resources at - * this point. + * Deactivate the {@link Backend}. The backend should release any + * resources that were obtained as a direct result of a call to + * {@link #activate()}. If the {@link Backend} is not active, this + * method should have no effect * * @return true on success */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/backend/BackendLoadException.java Mon Nov 28 17:57:18 2011 -0500 @@ -0,0 +1,14 @@ +package com.redhat.thermostat.backend; + +public class BackendLoadException extends Exception { + + private static final long serialVersionUID = 4057881401012295723L; + + public BackendLoadException(String message) { + super(message); + } + + public BackendLoadException(String message, Throwable cause) { + super(message, cause); + } +}
--- a/src/com/redhat/thermostat/backend/BackendRegistry.java Wed Nov 23 17:18:53 2011 -0500 +++ b/src/com/redhat/thermostat/backend/BackendRegistry.java Mon Nov 28 17:57:18 2011 -0500 @@ -1,7 +1,14 @@ package com.redhat.thermostat.backend; -import java.util.ArrayList; -import java.util.List; +import java.lang.reflect.Constructor; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.redhat.thermostat.agent.config.Configuration; +import com.redhat.thermostat.common.utils.LoggingUtils; /** * A registry for {@link Backend}s. Each {@link Backend} should call @@ -9,35 +16,46 @@ */ public final class BackendRegistry { - private static BackendRegistry INSTANCE = null; + private static final Logger LOGGER = LoggingUtils.getLogger(BackendRegistry.class); + + private final Map<String, Backend> registeredBackends; - private final List<Backend> registeredBackends; - - private BackendRegistry() { - registeredBackends = new ArrayList<Backend>(); + public BackendRegistry(Configuration config) throws BackendLoadException { + registeredBackends = new HashMap<String, Backend>(); + + for (String backendClassName : config.getStartupBackendClassNames()) { + LOGGER.log(Level.FINE, "Initializing backend: \"" + backendClassName + "\""); + Backend backend = null; + try { + Class<? > c = Class.forName(backendClassName); + Class<? extends Backend> narrowed = c.asSubclass(Backend.class); + Constructor<? extends Backend> backendConstructor = narrowed.getConstructor(); + backend = backendConstructor.newInstance(); + backend.setInitialConfiguration(config.getStartupBackendConfigMap(backend.getName())); + } catch (Exception e) { + throw new BackendLoadException("Could not instantiate configured backend class: " + backendClassName, e); + } + register(backend); + } } - public static synchronized BackendRegistry getInstance() { - if (INSTANCE == null) { - INSTANCE = new BackendRegistry(); + private synchronized void register(Backend backend) throws BackendLoadException { + if (registeredBackends.containsKey(backend.getName())) { + throw new BackendLoadException("Attempt to register two backends with the same name: " + backend.getName()); } - return INSTANCE; + registeredBackends.put(backend.getName(), backend); } - public synchronized void register(Backend backend) { - registeredBackends.add(backend); + private synchronized void unregister(Backend backend) { + registeredBackends.remove(backend.getName()); } - public synchronized void unregister(Backend backend) { - registeredBackends.remove(backend); - } - - public synchronized Backend[] getAll() { - return registeredBackends.toArray(new Backend[0]); + public synchronized Collection<Backend> getAll() { + return registeredBackends.values(); } public synchronized Backend getByName(String name) { - for (Backend backend : registeredBackends) { + for (Backend backend : registeredBackends.values()) { if (backend.getName().equals((name))) { return backend; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/com/redhat/thermostat/backend/sample/SampleBackend.java Mon Nov 28 17:57:18 2011 -0500 @@ -0,0 +1,75 @@ +package com.redhat.thermostat.backend.sample; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.redhat.thermostat.backend.Backend; +import com.redhat.thermostat.common.utils.LoggingUtils; + +/** Just an example backend implementation. This is really just to test the loading and configuration mechanisms + * + */ +public class SampleBackend extends Backend { + private final String NAME = "sample-backend"; + private final String DESCRIPTION = "A backend which does nothing at all."; + private final String VENDOR = "Nobody"; + private final String VERSION = "0.1"; + private boolean currentlyActive = false; + + private Logger LOGGER = LoggingUtils.getLogger(SampleBackend.class); + + public SampleBackend() { + super(); + } + + @Override + protected void setConfigurationValue(String name, String value) { + LOGGER.log(Level.FINE, "Setting configuration value for backend: " + this.NAME); + LOGGER.log(Level.FINE, "key: " + name + " value: " + value); + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getDescription() { + return DESCRIPTION; + } + + @Override + public String getVendor() { + return VENDOR; + } + + @Override + public String getVersion() { + return VERSION; + } + + @Override + public Map<String, String> getConfigurationMap() { + return new HashMap<String, String>(); + } + + @Override + public boolean activate() { + currentlyActive = true; + return true; + } + + @Override + public boolean deactivate() { + currentlyActive = false; + return true; + } + + @Override + public boolean isActive() { + return currentlyActive; + } + +}
--- a/src/com/redhat/thermostat/common/Constants.java Wed Nov 23 17:18:53 2011 -0500 +++ b/src/com/redhat/thermostat/common/Constants.java Mon Nov 28 17:57:18 2011 -0500 @@ -14,6 +14,8 @@ public static final int EXIT_UNABLE_TO_CONNECT_TO_DATABASE = 2; public static final int EXIT_UNABLE_TO_READ_PROPERTIES = 3; public static final int EXIT_CONFIGURATION_ERROR = 4; + public static final int EXIT_BACKEND_LOAD_ERROR = 5; + public static final int EXIT_BACKEND_START_ERROR = 6; public static final String THERMOSTAT_DB = "thermostat"; @@ -34,4 +36,5 @@ public static final String AGENT_PROPERTY_BACKENDS = "backends"; public static final String AGENT_LOCAL_HOSTNAME = "localhost"; + }
--- a/src/com/redhat/thermostat/common/LaunchException.java Wed Nov 23 17:18:53 2011 -0500 +++ b/src/com/redhat/thermostat/common/LaunchException.java Mon Nov 28 17:57:18 2011 -0500 @@ -4,9 +4,10 @@ * of program. * */ -@SuppressWarnings("serial") public class LaunchException extends Exception { + private static final long serialVersionUID = -6757521147558143649L; + public LaunchException(String message) { super(message); }