changeset 13:11f027c26096

Clearly separate configuration used to launch from ongoing configuration changes via storage schema. - Also included here is a fix that ensures log level is respected globally.
author Jon VanAlten <jon.vanalten@redhat.com>
date Tue, 06 Dec 2011 14:28:07 -0500
parents fdec0f43fe00
children e9dbb0bee8d0
files src/com/redhat/thermostat/agent/Agent.java src/com/redhat/thermostat/agent/Main.java src/com/redhat/thermostat/agent/MongoStorage.java src/com/redhat/thermostat/agent/Storage.java src/com/redhat/thermostat/agent/config/Configuration.java src/com/redhat/thermostat/agent/config/ConfigurationWatcher.java src/com/redhat/thermostat/agent/config/StartupConfiguration.java src/com/redhat/thermostat/backend/Backend.java src/com/redhat/thermostat/backend/BackendRegistry.java src/com/redhat/thermostat/backend/sample/SampleBackend.java src/com/redhat/thermostat/backend/system/SystemBackend.java src/com/redhat/thermostat/common/Constants.java src/com/redhat/thermostat/common/utils/LoggingUtils.java
diffstat 13 files changed, 390 insertions(+), 303 deletions(-) [+]
line wrap: on
line diff
--- a/src/com/redhat/thermostat/agent/Agent.java	Tue Dec 06 13:39:50 2011 -0500
+++ b/src/com/redhat/thermostat/agent/Agent.java	Tue Dec 06 14:28:07 2011 -0500
@@ -1,10 +1,10 @@
 package com.redhat.thermostat.agent;
 
 import java.util.UUID;
-import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import com.redhat.thermostat.agent.config.Configuration;
+import com.redhat.thermostat.agent.config.ConfigurationWatcher;
+import com.redhat.thermostat.agent.config.StartupConfiguration;
 import com.redhat.thermostat.backend.Backend;
 import com.redhat.thermostat.backend.BackendRegistry;
 import com.redhat.thermostat.common.LaunchException;
@@ -19,15 +19,16 @@
 
     private final UUID id;
     private final BackendRegistry backendRegistry;
-    private final Configuration config;
+    private final StartupConfiguration config;
 
     private Storage storage;
+    private Thread configWatcherThread = null;
 
-    public Agent(BackendRegistry backendRegistry, Configuration config, Storage storage) {
+    public Agent(BackendRegistry backendRegistry, StartupConfiguration config, Storage storage) {
         this(backendRegistry, UUID.randomUUID(), config, storage);
     }
 
-    public Agent(BackendRegistry registry, UUID agentId, Configuration config, Storage storage) {
+    public Agent(BackendRegistry registry, UUID agentId, StartupConfiguration config, Storage storage) {
         this.id = agentId;
         this.backendRegistry = registry;
         this.config = config;
@@ -36,7 +37,9 @@
 
     private void startBackends() throws LaunchException {
         for (Backend be : backendRegistry.getAll()) {
+            logger.fine("Attempting to start backend: " + be.getName());
             if (!be.activate()) {
+                logger.warning("Issue while starting backend: " + be.getName());
                 // When encountering issues during startup, we should not attempt to continue activating.
                 stopBackends();
                 throw new LaunchException("Could not activate backend: " + be.getName());
@@ -46,21 +49,41 @@
 
     private void stopBackends() {
         for (Backend be : backendRegistry.getAll()) {
+            logger.fine("Attempting to stop backend: " +be.getName());
             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());
+                logger.warning("Issue while deactivating backend: " + be.getName());
             }
         }
     }
 
-    public void start() throws LaunchException {
-        startBackends();
-        config.publish();
+    public synchronized void start() throws LaunchException {
+        if (configWatcherThread == null) {
+            startBackends();
+            storage.addAgentInformation(config);
+            configWatcherThread = new Thread(new ConfigurationWatcher(storage), "Configuration Watcher");
+            configWatcherThread.start();
+        } else {
+            logger.warning("Attempt to start agent when already started.");
+        }
     }
 
-    public void stop() {
-        config.unpublish();
-        stopBackends();
+    public synchronized void stop() {
+        if (configWatcherThread != null) {
+            configWatcherThread.interrupt(); // This thread checks for its own interrupted state and ends if interrupted.
+            while (configWatcherThread.isAlive()) {
+                try {
+                    configWatcherThread.join();
+                } catch (InterruptedException e) {
+                    logger.fine("Interrupted while waiting for ConfigurationWatcher to die.");
+                }
+            }
+            configWatcherThread = null;
+            storage.removeAgentInformation();
+            stopBackends();
+        } else {
+            logger.warning("Attempt to stop agent which is not active");
+        }
     }
 
     public UUID getId() {
--- a/src/com/redhat/thermostat/agent/Main.java	Tue Dec 06 13:39:50 2011 -0500
+++ b/src/com/redhat/thermostat/agent/Main.java	Tue Dec 06 14:28:07 2011 -0500
@@ -5,16 +5,14 @@
 import java.net.UnknownHostException;
 import java.util.Properties;
 import java.util.logging.Level;
-import java.util.logging.LogManager;
 import java.util.logging.Logger;
 
-import com.redhat.thermostat.agent.config.Configuration;
+import com.redhat.thermostat.agent.config.StartupConfiguration;
 import com.redhat.thermostat.backend.BackendLoadException;
 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;
-import com.redhat.thermostat.common.utils.StringUtils;
 
 public final class Main {
 
@@ -25,13 +23,7 @@
     public static void main(String[] args) {
         long startTimestamp = System.currentTimeMillis();
 
-        try {
-            LogManager.getLogManager().readConfiguration(
-                    StringUtils.toInputStream(Constants.LOGGING_CONFIG));
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-
+        LoggingUtils.setGlobalLogLevel(Level.ALL);
         LoggingUtils.resetAndGetRootLogger();
         Logger logger = LoggingUtils.getLogger(Main.class);
 
@@ -46,9 +38,9 @@
             System.exit(Constants.EXIT_UNABLE_TO_READ_PROPERTIES);
         }
 
-        Configuration config = null;
+        StartupConfiguration config = null;
         try {
-            config = new Configuration(startTimestamp, args, props);
+            config = new StartupConfiguration(startTimestamp, args, props);
         } catch (LaunchException le) {
             logger.log(Level.SEVERE,
                     "Unable to instantiate startup configuration.",
@@ -57,16 +49,16 @@
         }
 
         logger.setLevel(config.getLogLevel());
+        LoggingUtils.setGlobalLogLevel(config.getLogLevel());
 
         Storage storage = new MongoStorage();
         try {
             storage.connect(config.getDatabaseURIAsString());
-            logger.fine("connected");
+            logger.fine("Connected to database server.");
         } catch (UnknownHostException uhe) {
             logger.log(Level.SEVERE, "Could not initialize storage layer.", uhe);
             System.exit(Constants.EXIT_UNABLE_TO_CONNECT_TO_DATABASE);
         }
-        config.setStorage(storage);
 
         BackendRegistry backendRegistry = null;
         try {
@@ -80,6 +72,7 @@
         config.setAgent(agent);
         storage.setAgentId(agent.getId());
         try {
+            logger.fine("Starting agent.");
             agent.start();
         } catch (LaunchException le) {
             logger.log(Level.SEVERE,
--- a/src/com/redhat/thermostat/agent/MongoStorage.java	Tue Dec 06 13:39:50 2011 -0500
+++ b/src/com/redhat/thermostat/agent/MongoStorage.java	Tue Dec 06 14:28:07 2011 -0500
@@ -14,7 +14,7 @@
 import com.mongodb.Mongo;
 import com.mongodb.MongoURI;
 import com.mongodb.WriteConcern;
-import com.redhat.thermostat.agent.config.Configuration;
+import com.redhat.thermostat.agent.config.StartupConfiguration;
 import com.redhat.thermostat.common.Constants;
 import com.redhat.thermostat.common.CpuStat;
 import com.redhat.thermostat.common.HostInfo;
@@ -43,7 +43,7 @@
     }
 
     @Override
-    public void addAgentInformation(Configuration config) {
+    public void addAgentInformation(StartupConfiguration config) {
         DBCollection configCollection = db.getCollection(StorageConstants.COLLECTION_AGENT_CONFIG);
         DBObject toInsert = config.toDBObject();
         /* cast required to disambiguate between putAll(BSONObject) and putAll(Map) */
@@ -144,5 +144,4 @@
         toInsert.put(StorageConstants.KEY_MEMORY_STATS_COMMIT_LIMIT, stat.getCommitLimit());
         memoryStatsCollection.insert(toInsert, WriteConcern.NORMAL);
     }
-
 }
--- a/src/com/redhat/thermostat/agent/Storage.java	Tue Dec 06 13:39:50 2011 -0500
+++ b/src/com/redhat/thermostat/agent/Storage.java	Tue Dec 06 14:28:07 2011 -0500
@@ -3,7 +3,7 @@
 import java.net.UnknownHostException;
 import java.util.UUID;
 
-import com.redhat.thermostat.agent.config.Configuration;
+import com.redhat.thermostat.agent.config.StartupConfiguration;
 import com.redhat.thermostat.common.CpuStat;
 import com.redhat.thermostat.common.HostInfo;
 import com.redhat.thermostat.common.MemoryStat;
@@ -13,7 +13,7 @@
 
     public void setAgentId(UUID id);
 
-    public void addAgentInformation(Configuration config);
+    public void addAgentInformation(StartupConfiguration config);
 
     public void removeAgentInformation();
 
--- a/src/com/redhat/thermostat/agent/config/Configuration.java	Tue Dec 06 13:39:50 2011 -0500
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,242 +0,0 @@
-package com.redhat.thermostat.agent.config;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import com.mongodb.BasicDBObject;
-import com.mongodb.DBObject;
-import com.redhat.thermostat.agent.Agent;
-import com.redhat.thermostat.agent.Defaults;
-import com.redhat.thermostat.agent.Storage;
-import com.redhat.thermostat.agent.StorageConstants;
-import com.redhat.thermostat.common.Constants;
-import com.redhat.thermostat.common.LaunchException;
-import com.redhat.thermostat.common.utils.LoggingUtils;
-
-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 static Logger logger = LoggingUtils.getLogger(Configuration.class);
-
-    private final long startTimestamp;
-
-    private Properties props;
-
-    private Level logLevel;
-    private boolean localMode;
-    private int mongodPort;
-    private int mongosPort;
-    private String databaseURI;
-    private String completeDatabaseURI;
-
-    private String hostname;
-
-    private Agent agent;
-    private Storage storage = null;
-
-    public Configuration(long startTime, String[] args, Properties props) throws LaunchException {
-        this.props = props;
-
-        initFromDefaults();
-        initFromProperties();
-        initFromArguments(args);
-
-        if (localMode) {
-            completeDatabaseURI = databaseURI + ":" + mongodPort;
-            hostname = Constants.AGENT_LOCAL_HOSTNAME;
-        } else {
-            completeDatabaseURI = databaseURI + ":" + mongosPort;
-            try {
-                InetAddress addr = InetAddress.getLocalHost();
-                hostname = addr.getCanonicalHostName();
-            } catch (UnknownHostException e) {
-                logger.log(Level.FINE, "Error determining local hostname.");
-            }
-        }
-        startTimestamp = startTime;
-    }
-
-    private void initFromDefaults() {
-        logLevel = Defaults.LOGGING_LEVEL;
-        localMode = Defaults.LOCAL_MODE;
-        mongodPort = Defaults.MONGOD_PORT;
-        mongosPort = Defaults.MONGOS_PORT;
-        databaseURI = Defaults.DATABASE_URI;
-    }
-
-    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));
-        }
-    }
-
-    private void initFromArguments(String[] args) throws LaunchException {
-        Arguments arguments;
-
-        arguments = new Arguments(args);
-        if (arguments.isModeSpecified()) {
-            localMode = arguments.getLocalMode();
-        }
-        if (arguments.isLogLevelSpecified()) {
-            logLevel = arguments.getLogLevel();
-        }
-    }
-
-    public void setStorage(Storage storage) {
-        this.storage = storage;
-    }
-
-    public Level getLogLevel() {
-        return logLevel;
-    }
-
-    public String getDatabaseURIAsString() {
-        return completeDatabaseURI;
-    }
-
-    public String getHostname() {
-        return hostname;
-    }
-
-    // TODO move this into Storage as well
-    public DBObject toDBObject() {
-        BasicDBObject result = new BasicDBObject();
-        // TODO explicit exception if agent not yet set.
-        result.put(StorageConstants.KEY_AGENT_ID, agent.getId().toString());
-        result.put(StorageConstants.KEY_AGENT_CONFIG_AGENT_START_TIME, startTimestamp);
-        // TODO create nested backend config parts
-        return result;
-    }
-
-    public void setAgent(Agent agent) {
-        this.agent = agent;
-    }
-
-    public void publish() {
-        storage.addAgentInformation(this);
-        // TODO Start configuration-change-detection thread.
-    }
-
-    public void unpublish() {
-        // TODO Stop configuration-change-detection thread.
-        storage.removeAgentInformation();
-    }
-
-    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 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 configMap;
-    }
-
-    /**
-     *
-     * @param backendName
-     * @param configurationKey
-     * @return
-     */
-    public String getBackendConfigValue(String backendName, String configurationKey) {
-        return storage.getBackendConfig(backendName, configurationKey);
-    }
-
-    /**
-     * Exposes the command line arguments in a more object-oriented style.
-     * <p>
-     * Please check that an option was specified (using the various is*Specified() methods) before using its value.
-     */
-    private static class Arguments {
-        private final boolean localMode;
-        private final boolean modeSpecified;
-        private final Level logLevel;
-        private final boolean logLevelSpecified;
-
-        public Arguments(String[] args) throws LaunchException {
-            boolean local = false;
-            boolean explicitMode = false;
-            Level level = null;
-            boolean explicitLogLevel = false;
-            for (int index = 0; index < args.length; index++) {
-                if (args[index].equals(Constants.AGENT_ARGUMENT_LOGLEVEL)) {
-                    index++;
-                    if (index < args.length) {
-                        try {
-                            level = Level.parse(args[index].toUpperCase());
-                            explicitLogLevel = true;
-                        } catch (IllegalArgumentException iae) {
-                            throw new LaunchException("Invalid argument for " + Constants.AGENT_ARGUMENT_LOGLEVEL, iae);
-                        }
-                    } else {
-                        throw new LaunchException("Missing argument for " + Constants.AGENT_ARGUMENT_LOGLEVEL);
-                    }
-                } else if (args[index].equals(Constants.AGENT_ARGUMENT_LOCAL)) {
-                    explicitMode = true;
-                    local = true;
-                }
-            }
-            logLevel = level;
-            logLevelSpecified = explicitLogLevel;
-            localMode = local;
-            modeSpecified = explicitMode;
-        }
-
-        public boolean isModeSpecified() {
-            return modeSpecified;
-        }
-
-        public boolean getLocalMode() {
-            if (!isModeSpecified()) {
-                throw new IllegalStateException("local mode is not valid");
-            }
-            return localMode;
-        }
-
-        public boolean isLogLevelSpecified() {
-            return logLevelSpecified;
-        }
-
-        public Level getLogLevel() {
-            if (!isLogLevelSpecified()) {
-                throw new IllegalStateException("log level not explicitly specified");
-            }
-            return logLevel;
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/agent/config/ConfigurationWatcher.java	Tue Dec 06 14:28:07 2011 -0500
@@ -0,0 +1,34 @@
+package com.redhat.thermostat.agent.config;
+
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.agent.Storage;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+
+public class ConfigurationWatcher implements Runnable {
+
+    private static final Logger logger = LoggingUtils.getLogger(ConfigurationWatcher.class);
+
+    private Storage storage;
+    
+    public ConfigurationWatcher(Storage storage) {
+        this.storage = storage;
+    }
+
+    @Override
+    public void run() {
+        logger.fine("Watching for configuration changes.");
+        while (!Thread.interrupted()) {
+            checkConfigUpdates();
+        }
+        logger.fine("No longer watching for configuration changes.");
+    }
+
+    // TODO It would be best to develop this algorithm when we have a client that can initiate changes, so that it can be tested.
+    private void checkConfigUpdates() {
+        try { // THIS IS ONLY TEMPORARY.  Until we do implement this algorithm, we don't want this thread busy hogging CPU.
+            Thread.sleep(2000);
+        } catch (InterruptedException ignore) { }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/com/redhat/thermostat/agent/config/StartupConfiguration.java	Tue Dec 06 14:28:07 2011 -0500
@@ -0,0 +1,216 @@
+package com.redhat.thermostat.agent.config;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.mongodb.BasicDBObject;
+import com.mongodb.DBObject;
+import com.redhat.thermostat.agent.Agent;
+import com.redhat.thermostat.agent.Defaults;
+import com.redhat.thermostat.agent.StorageConstants;
+import com.redhat.thermostat.common.Constants;
+import com.redhat.thermostat.common.LaunchException;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+
+public final class StartupConfiguration {
+
+    /* 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 static Logger logger = LoggingUtils.getLogger(StartupConfiguration.class);
+
+    private final long startTimestamp;
+
+    private Properties props;
+
+    private Level logLevel;
+    private boolean localMode;
+    private int mongodPort;
+    private int mongosPort;
+    private String databaseURI;
+    private String completeDatabaseURI;
+
+    private String hostname;
+
+    private Agent agent;
+
+    public StartupConfiguration(long startTime, String[] args, Properties props) throws LaunchException {
+        this.props = props;
+
+        initFromDefaults();
+        initFromProperties();
+        initFromArguments(args);
+
+        if (localMode) {
+            completeDatabaseURI = databaseURI + ":" + mongodPort;
+            hostname = Constants.AGENT_LOCAL_HOSTNAME;
+        } else {
+            completeDatabaseURI = databaseURI + ":" + mongosPort;
+            try {
+                InetAddress addr = InetAddress.getLocalHost();
+                hostname = addr.getCanonicalHostName();
+            } catch (UnknownHostException e) {
+                logger.log(Level.FINE, "Error determining local hostname.");
+            }
+        }
+        startTimestamp = startTime;
+    }
+
+    private void initFromDefaults() {
+        logLevel = Defaults.LOGGING_LEVEL;
+        localMode = Defaults.LOCAL_MODE;
+        mongodPort = Defaults.MONGOD_PORT;
+        mongosPort = Defaults.MONGOS_PORT;
+        databaseURI = Defaults.DATABASE_URI;
+    }
+
+    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));
+        }
+    }
+
+    private void initFromArguments(String[] args) throws LaunchException {
+        Arguments arguments;
+
+        arguments = new Arguments(args);
+        if (arguments.isModeSpecified()) {
+            localMode = arguments.getLocalMode();
+        }
+        if (arguments.isLogLevelSpecified()) {
+            logLevel = arguments.getLogLevel();
+        }
+    }
+
+    public Level getLogLevel() {
+        return logLevel;
+    }
+
+    public String getDatabaseURIAsString() {
+        return completeDatabaseURI;
+    }
+
+    public String getHostname() {
+        return hostname;
+    }
+
+    // TODO move this into Storage as well
+    public DBObject toDBObject() {
+        BasicDBObject result = new BasicDBObject();
+        // TODO explicit exception if agent not yet set.
+        result.put(StorageConstants.KEY_AGENT_ID, agent.getId().toString());
+        result.put(StorageConstants.KEY_AGENT_CONFIG_AGENT_START_TIME, startTimestamp);
+        // TODO create nested backend config parts
+        return result;
+    }
+
+    public void setAgent(Agent agent) {
+        this.agent = agent;
+    }
+
+    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 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 configMap;
+    }
+
+    /**
+     * Exposes the command line arguments in a more object-oriented style.
+     * <p>
+     * Please check that an option was specified (using the various is*Specified() methods) before using its value.
+     */
+    private static class Arguments {
+        private final boolean localMode;
+        private final boolean modeSpecified;
+        private final Level logLevel;
+        private final boolean logLevelSpecified;
+
+        public Arguments(String[] args) throws LaunchException {
+            boolean local = false;
+            boolean explicitMode = false;
+            Level level = null;
+            boolean explicitLogLevel = false;
+            for (int index = 0; index < args.length; index++) {
+                if (args[index].equals(Constants.AGENT_ARGUMENT_LOGLEVEL)) {
+                    index++;
+                    if (index < args.length) {
+                        try {
+                            level = Level.parse(args[index].toUpperCase());
+                            explicitLogLevel = true;
+                        } catch (IllegalArgumentException iae) {
+                            throw new LaunchException("Invalid argument for " + Constants.AGENT_ARGUMENT_LOGLEVEL, iae);
+                        }
+                    } else {
+                        throw new LaunchException("Missing argument for " + Constants.AGENT_ARGUMENT_LOGLEVEL);
+                    }
+                } else if (args[index].equals(Constants.AGENT_ARGUMENT_LOCAL)) {
+                    explicitMode = true;
+                    local = true;
+                }
+            }
+            logLevel = level;
+            logLevelSpecified = explicitLogLevel;
+            localMode = local;
+            modeSpecified = explicitMode;
+        }
+
+        public boolean isModeSpecified() {
+            return modeSpecified;
+        }
+
+        public boolean getLocalMode() {
+            if (!isModeSpecified()) {
+                throw new IllegalStateException("local mode is not valid");
+            }
+            return localMode;
+        }
+
+        public boolean isLogLevelSpecified() {
+            return logLevelSpecified;
+        }
+
+        public Level getLogLevel() {
+            if (!isLogLevelSpecified()) {
+                throw new IllegalStateException("log level not explicitly specified");
+            }
+            return logLevel;
+        }
+    }
+}
--- a/src/com/redhat/thermostat/backend/Backend.java	Tue Dec 06 13:39:50 2011 -0500
+++ b/src/com/redhat/thermostat/backend/Backend.java	Tue Dec 06 14:28:07 2011 -0500
@@ -11,42 +11,81 @@
  */
 public abstract class Backend {
 
+    private boolean initialConfigurationComplete = false;
     protected Storage storage;
 
-    public final void setInitialConfiguration(Map<String, String> configMap) {
+    /**
+     * 
+     * @param configMap a map containing the settings that this backend has been configured with.
+     * @throws LaunchException if map contains values that this backend does not accept.
+     */
+    public final void setInitialConfiguration(Map<String, String> configMap) throws BackendLoadException {
+        if (initialConfigurationComplete) {
+            throw new BackendLoadException("A backend may only receive intitial configuration once.");
+        }
         for (Entry<String, String> e : configMap.entrySet()) {
-            setConfigurationValue(e.getKey(), e.getValue());
+            String key = e.getKey();
+            String value = e.getValue();
+            try {
+                setConfigurationValue(key, value);
+            } catch (IllegalArgumentException iae) {
+                throw new BackendLoadException("Attempt to set invalid backend configuration for " + getName()
+                        + " backend.  Key: " + key + "   Value: " + value, iae);
+            }
         }
+        initialConfigurationComplete = true;
     }
 
-    protected abstract void setConfigurationValue(String name, String value);
-
-
-
-    /** Returns the name of the {@link Backend} */
-    public abstract String getName();
-
-    /** Returns the description of the {@link Backend} */
-    public abstract String getDescription();
-
-    /** Returns the vendor of the {@link Backend} */
-    public abstract String getVendor();
-
-    /** Returns the version of the {@link Backend} */
-    public abstract String getVersion();
-
-    /**
-     * Returns a map containing the settings of this backend
-     */
-    public abstract Map<String, String> getConfigurationMap();
-
     protected void setStorage(Storage storage) {
         this.storage = storage;
     }
 
     /**
+     * Set the named configuration to the given value.
+     * @param name
+     * @param value
+     * @throws IllegalArgumentException if either the key does not refer to a valid configuration option
+     *                                  for this backend or the value is not valid for the key
+     */
+    protected abstract void setConfigurationValue(String name, String value);
+
+    /**
+     * @return the name of the {@link Backend}
+     */
+    public abstract String getName();
+
+    /**
+     * @returns the description of the {@link Backend}
+     */
+    public abstract String getDescription();
+
+    /**
+     * @return the vendor of the {@link Backend}
+     */
+    public abstract String getVendor();
+
+    /** 
+     * @return the version of the {@link Backend}
+     */
+    public abstract String getVersion();
+
+    /**
+     * @return a map containing the settings of this backend
+     */
+    public abstract Map<String, String> getConfigurationMap();
+
+    /**
+     * 
+     * @param key The constant key that corresponds to the desired configuration value
+     * @return The current value of the configuration value corresponding to the key given.
+     * @throws IllegalArgumentException if the key does not refer to a valid configuration option for
+     *                                  this backend
+     */
+    public abstract String getConfigurationValue(String key);
+
+    /**
      * Activate the {@link Backend}.  Based on the current configuration,
-     * begin pushing data to the Storage layer.  If the {@link Backend} is
+     * begin pushing data to the {@link 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
@@ -64,8 +103,7 @@
     public abstract boolean deactivate();
 
     /**
-     * Returns a boolean indicating if the backend is currently active on this
-     * host
+     * @return a boolean indicating whether the backend is currently active on this host
      */
     public abstract boolean isActive();
 
--- a/src/com/redhat/thermostat/backend/BackendRegistry.java	Tue Dec 06 13:39:50 2011 -0500
+++ b/src/com/redhat/thermostat/backend/BackendRegistry.java	Tue Dec 06 14:28:07 2011 -0500
@@ -8,7 +8,7 @@
 import java.util.logging.Logger;
 
 import com.redhat.thermostat.agent.Storage;
-import com.redhat.thermostat.agent.config.Configuration;
+import com.redhat.thermostat.agent.config.StartupConfiguration;
 import com.redhat.thermostat.backend.system.SystemBackend;
 import com.redhat.thermostat.common.utils.LoggingUtils;
 
@@ -22,7 +22,7 @@
 
     private final Map<String, Backend> registeredBackends;
 
-    public BackendRegistry(Configuration config, Storage storage) throws BackendLoadException {
+    public BackendRegistry(StartupConfiguration config, Storage storage) throws BackendLoadException {
         registeredBackends = new HashMap<String, Backend>();
 
         /*
--- a/src/com/redhat/thermostat/backend/sample/SampleBackend.java	Tue Dec 06 13:39:50 2011 -0500
+++ b/src/com/redhat/thermostat/backend/sample/SampleBackend.java	Tue Dec 06 14:28:07 2011 -0500
@@ -56,6 +56,11 @@
     }
 
     @Override
+    public String getConfigurationValue(String key) {
+        throw new IllegalArgumentException("SampleBackend does not actually accept any configuration.");
+    }
+
+    @Override
     public boolean activate() {
         currentlyActive = true;
         return true;
--- a/src/com/redhat/thermostat/backend/system/SystemBackend.java	Tue Dec 06 13:39:50 2011 -0500
+++ b/src/com/redhat/thermostat/backend/system/SystemBackend.java	Tue Dec 06 14:28:07 2011 -0500
@@ -97,4 +97,10 @@
         return (timer != null);
     }
 
+    @Override
+    public String getConfigurationValue(String key) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
 }
--- a/src/com/redhat/thermostat/common/Constants.java	Tue Dec 06 13:39:50 2011 -0500
+++ b/src/com/redhat/thermostat/common/Constants.java	Tue Dec 06 14:28:07 2011 -0500
@@ -6,7 +6,7 @@
  */
 public class Constants {
 
-    public static final String LOGGING_CONFIG = "com.redhat.thermostat.level=ALL";
+    public static final String LOG_LEVEL_CONFIG = "com.redhat.thermostat.level=";
 
     public static final String AGENT_PROPERTIES_FILE = "config/agent.properties";
 
--- a/src/com/redhat/thermostat/common/utils/LoggingUtils.java	Tue Dec 06 13:39:50 2011 -0500
+++ b/src/com/redhat/thermostat/common/utils/LoggingUtils.java	Tue Dec 06 14:28:07 2011 -0500
@@ -6,6 +6,7 @@
 import java.util.logging.LogManager;
 import java.util.logging.Logger;
 
+import com.redhat.thermostat.common.Constants;
 import com.redhat.thermostat.common.LogFormatter;
 
 /**
@@ -16,6 +17,8 @@
  */
 public final class LoggingUtils {
 
+    private static Logger root;
+
     private LoggingUtils() {
         /* should not be instantiated */
     }
@@ -25,7 +28,7 @@
      * works around http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4462908
      */
     public static Logger resetAndGetRootLogger() {
-        Logger root = Logger.getLogger("");
+        root = Logger.getLogger("");
         Handler[] handlers = root.getHandlers();
         for (Handler handler : handlers) {
             root.removeHandler(handler);
@@ -39,6 +42,18 @@
         return root;
     }
 
+    public static void setGlobalLogLevel(Level level) {
+        try {
+            LogManager.getLogManager().readConfiguration(StringUtils.toInputStream(Constants.LOG_LEVEL_CONFIG + level.toString()));
+        } catch (Exception e) {
+            if (root != null) {
+                root.log(Level.WARNING, "Error setting new log level.", e);
+            } else {
+                e.printStackTrace();
+            }
+        }
+    }
+
     /**
      * Returns an appropriate logger to be used by class klass.
      */