changeset 1382:7caa7f9afb14

Separate storage command from agent-cli Backport of PR1608. We get an exception that the RMI registry port is already in use for running "thermostat storage --status". This happens because agent-core's activator creates a thread pool for JMX connections, which in turn creates the RMI registry. My proposed solution is to separate the storage command from the agent bundles, currently it resides in agent-cli. This patch creates a storage-cli bundle that houses StorageCommand and its related classes. There are then some modifications to the ServiceCommand in order to invoke the storage command without having a bundle dependency on storage-cli, which would also entail having to export StorageCommand. Reviewed-by: vanaltj Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-December/009022.html PR1648
author Elliott Baron <ebaron@redhat.com>
date Mon, 10 Feb 2014 19:08:50 -0500
parents 869c5e163e7a
children 21466861b0ef
files agent/cli/pom.xml agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/Activator.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/ServiceCommand.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/DBConfig.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/DBOptionParser.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/DBStartupConfiguration.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/MongoProcessRunner.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StalePidFileException.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageAlreadyRunningException.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageCommand.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageNotRunningException.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageStartException.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageStopException.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/locale/LocaleResources.java agent/cli/src/main/resources/com/redhat/thermostat/agent/cli/impl/strings.properties agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ActivatorTest.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ServiceCommandTest.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/db/DBStartupConfigurationTest.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/db/MongoProcessRunnerTest.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/db/StorageCommandTest.java agent/cli/src/test/resources/brokenDbConfig.properties agent/cli/src/test/resources/brokenDbConfig2.properties agent/cli/src/test/resources/testDbConfig.properties agent/cli/src/test/resources/testDbConfig2.properties distribution/assembly/core-assembly.xml distribution/config/commands/service.properties distribution/config/commands/storage.properties distribution/pom.xml storage/cli/pom.xml storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/Activator.java storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/DBConfig.java storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/DBOptionParser.java storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/DBStartupConfiguration.java storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/MongoProcessRunner.java storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/StalePidFileException.java storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/StorageAlreadyRunningException.java storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/StorageCommand.java storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/StorageNotRunningException.java storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/StorageStartException.java storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/StorageStopException.java storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/locale/LocaleResources.java storage/cli/src/main/resources/com/redhat/thermostat/storage/cli/internal/strings.properties storage/cli/src/test/java/com/redhat/thermostat/storage/cli/internal/ActivatorTest.java storage/cli/src/test/java/com/redhat/thermostat/storage/cli/internal/DBStartupConfigurationTest.java storage/cli/src/test/java/com/redhat/thermostat/storage/cli/internal/MongoProcessRunnerTest.java storage/cli/src/test/java/com/redhat/thermostat/storage/cli/internal/StorageCommandTest.java storage/cli/src/test/resources/brokenDbConfig.properties storage/cli/src/test/resources/brokenDbConfig2.properties storage/cli/src/test/resources/testDbConfig.properties storage/cli/src/test/resources/testDbConfig2.properties storage/pom.xml
diffstat 51 files changed, 2271 insertions(+), 1926 deletions(-) [+]
line wrap: on
line diff
--- a/agent/cli/pom.xml	Fri Dec 20 11:42:37 2013 +0100
+++ b/agent/cli/pom.xml	Mon Feb 10 19:08:50 2014 -0500
@@ -127,7 +127,6 @@
             <Private-Package>
               com.redhat.thermostat.agent.cli.impl,
               com.redhat.thermostat.agent.cli.impl.locale,
-              com.redhat.thermostat.agent.cli.impl.db,
             </Private-Package>
             <!-- Do not autogenerate uses clauses in Manifests -->
             <_nouses>true</_nouses>
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/Activator.java	Fri Dec 20 11:42:37 2013 +0100
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/Activator.java	Mon Feb 10 19:08:50 2014 -0500
@@ -41,13 +41,11 @@
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 
-import com.redhat.thermostat.agent.cli.impl.db.StorageCommand;
 import com.redhat.thermostat.common.ExitStatus;
 import com.redhat.thermostat.common.MultipleServiceTracker;
 import com.redhat.thermostat.common.MultipleServiceTracker.Action;
 import com.redhat.thermostat.common.cli.CommandRegistry;
 import com.redhat.thermostat.common.cli.CommandRegistryImpl;
-import com.redhat.thermostat.shared.config.CommonPaths;
 import com.redhat.thermostat.storage.core.WriterID;
 
 public class Activator implements BundleActivator {
@@ -62,7 +60,6 @@
         
         Class<?>[] deps = new Class<?>[] {
                 ExitStatus.class,
-                CommonPaths.class,
                 WriterID.class // agent app uses it
         };
         tracker = new MultipleServiceTracker(context, deps, new Action() {
@@ -70,11 +67,9 @@
             @Override
             public void dependenciesAvailable(Map<String, Object> services) {
                 ExitStatus exitStatus = (ExitStatus) services.get(ExitStatus.class.getName());
-                CommonPaths paths = (CommonPaths) services.get(CommonPaths.class.getName());
                 WriterID writerID = (WriterID) services.get(WriterID.class.getName());
                 agentApplication = new AgentApplication(context, exitStatus, writerID);
                 reg.registerCommand("service", new ServiceCommand(context));
-                reg.registerCommand("storage", new StorageCommand(exitStatus, paths));
                 reg.registerCommand("agent", agentApplication);
             }
 
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/ServiceCommand.java	Fri Dec 20 11:42:37 2013 +0100
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/ServiceCommand.java	Mon Feb 10 19:08:50 2014 -0500
@@ -43,12 +43,11 @@
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;
 
-import com.redhat.thermostat.agent.cli.impl.db.StorageAlreadyRunningException;
-import com.redhat.thermostat.agent.cli.impl.db.StorageCommand;
 import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.cli.AbstractCommand;
+import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
 import com.redhat.thermostat.common.tools.ApplicationState;
@@ -68,6 +67,7 @@
     private BundleContext context;
     private Launcher launcher;
     private boolean storageFailed = false;
+    private CommandContext cmdCtx;
 
     public ServiceCommand(BundleContext context) {
         this.context = context;
@@ -77,6 +77,7 @@
 
     @Override
     public void run(CommandContext ctx) throws CommandException {
+        cmdCtx = ctx;
         ServiceReference launcherRef = context.getServiceReference(Launcher.class);
         if (launcherRef == null) {
             throw new CommandException(translator.localize(LocaleResources.LAUNCHER_UNAVAILABLE));
@@ -96,32 +97,47 @@
         launcher.run(storageStopArgs, false);
         
         context.ungetService(launcherRef);
+        cmdCtx = null;
     }
 
     @Override
     public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
-        if (actionEvent.getSource() instanceof StorageCommand) {
-            StorageCommand storage = (StorageCommand) actionEvent.getSource();
+        if (actionEvent.getSource() instanceof AbstractStateNotifyingCommand) {
+            AbstractStateNotifyingCommand storage = (AbstractStateNotifyingCommand) actionEvent.getSource();
             // Implementation detail: there is a single StorageCommand instance registered
             // as an OSGi service.  We remove ourselves as listener so that we don't get
             // notified in the case that the command is invoked by some other means later.
             storage.getNotifier().removeActionListener(this);
-            switch (actionEvent.getActionId()) {
-            case START:
-                String dbUrl = storage.getConfiguration().getDBConnectionString();
-                String[] agentArgs =  new String[] {"agent", "-d", dbUrl};
-                System.err.println(translator.localize(LocaleResources.STARTING_AGENT).getContents());
-                launcher.run(agentArgs, false);
-                break;
-            case FAIL:
-                storageFailed = true;
-                Object payload = actionEvent.getPayload();
-                if (payload instanceof StorageAlreadyRunningException) {
-                    System.err.println(translator.localize(LocaleResources.STORAGE_ALREADY_RUNNING).getContents());
+            
+            try {
+                switch (actionEvent.getActionId()) {
+                case START:
+                    // Payload is connection URL
+                    Object payload = actionEvent.getPayload();
+                    if (payload == null || !(payload instanceof String)) {
+                        throw new CommandException(translator.localize(LocaleResources.UNEXPECTED_RESULT_STORAGE));
+                    }
+                    String dbUrl = (String) payload;
+                    String[] agentArgs =  new String[] {"agent", "-d", dbUrl};
+                    cmdCtx.getConsole().getError().println(translator.localize(LocaleResources.STARTING_AGENT).getContents());
+                    launcher.run(agentArgs, false);
+                    break;
+                case FAIL:
+                    storageFailed = true;
+                    // Payload is exception
+                    payload = actionEvent.getPayload();
+                    if (payload == null || !(payload instanceof Exception)) {
+                        throw new CommandException(translator.localize(LocaleResources.UNEXPECTED_RESULT_STORAGE));
+                    }
+                    Exception ex = (Exception) payload;
+                    cmdCtx.getConsole().getError().println(ex.getMessage());
+                    break;
                 }
-                break;
+            } catch (CommandException e) {
+                cmdCtx.getConsole().getError().println(e.getMessage());
+            } finally {
+                agentBarrier.release();
             }
-            agentBarrier.release();
         }
     }
 
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/DBConfig.java	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.cli.impl.db;
-
-
-/**
- * Set of configuration options that the {@link StorageCommand} understands.
- * Keys map to properties in $THERMOSTAT_HOME/storage/db.properties.
- */
-public enum DBConfig {
-
-    /**
-     * The bind IP address.
-     */
-    BIND,
-    /**
-     * The port on which mongodb will be listening.
-     */
-    PORT,
-    /**
-     * Weather or not to start mongodb with SSL enabled.
-     */
-    SSL_ENABLE,
-    /**
-     * The PEM encoded SSL certificate + SSL key.
-     */
-    SSL_PEM_FILE,
-    /**
-     * The passphrase for the encrypted SSL key. Only used if the private key was encrypted.
-     */
-    SSL_KEY_PASSWORD,
-    
-}
-
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/DBOptionParser.java	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,123 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.cli.impl.db;
-
-import com.redhat.thermostat.common.cli.Arguments;
-import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
-import com.redhat.thermostat.common.config.ThermostatOptionParser;
-import com.redhat.thermostat.shared.config.InvalidConfigurationException;
-import com.redhat.thermostat.shared.locale.Translate;
-
-public class DBOptionParser implements ThermostatOptionParser {
-    
-    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-
-    private boolean quiet;
-    
-    private DBStartupConfiguration configuration;
-    
-    private Arguments args;
-
-    private DBArgs serviceAction;
-    
-    private boolean dryRun;
-    
-    public DBOptionParser(DBStartupConfiguration configuration, Arguments args) {
-        this.args = args;
-        this.configuration = configuration;
-    }
-    
-    @Override
-    public void parse() throws InvalidConfigurationException {
-
-        if (args.hasArgument(DBArgs.START.option)) {
-            serviceAction = DBArgs.START;
-        } else if (args.hasArgument(DBArgs.STOP.option)) {
-            serviceAction = DBArgs.STOP;
-        } else if (args.hasArgument(DBArgs.STATUS.option)) {
-            serviceAction = DBArgs.STATUS;
-        } else {
-            throw new InvalidConfigurationException(translator.localize(LocaleResources.COMMAND_STORAGE_ARGUMENT_REQUIRED));
-        }
-
-        if (args.hasArgument(DBArgs.DRY.option)) {
-            dryRun = true;
-        }
-        
-        if (args.hasArgument(DBArgs.QUIET.option)) {
-            quiet = true;
-        }
-        
-        // leave at the end, since it depends on the previous settings
-        String address = configuration.getBindIP();
-        long port = configuration.getPort();
-        configuration.setDBConnectionString("mongodb://" + address + ":" + port);
-    }
-
-    public boolean isDryRun() {
-        return dryRun;
-    }
-    
-    public DBArgs getAction() {
-        return serviceAction;
-    }
-
-    static enum DBArgs {
-                
-        DRY("dryRun"),
-        
-        HELP("help"),
-        
-        START("start"),
-        STOP("stop"),
-        
-        QUIET("quiet"),
-        
-        STATUS("status");
-        
-        private String option;
-        
-        DBArgs(String option) {
-            this.option = option;
-        }
-    }
-
-    public boolean isQuiet() {
-        return quiet;
-    }
-}
-
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/DBStartupConfiguration.java	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,194 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.cli.impl.db;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Properties;
-
-import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
-import com.redhat.thermostat.shared.config.InvalidConfigurationException;
-import com.redhat.thermostat.shared.locale.Translate;
-import com.redhat.thermostat.storage.config.StartupConfiguration;
-
-public class DBStartupConfiguration implements StartupConfiguration {
-
-    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
-
-    private File dbPath;
-    private File logFile;
-    private File pidFile;
-    private boolean sslEnabled = false;
-    private File sslPemFile;
-    private String sslKeyPassphrase;
-        
-    private long localPort;
-    
-    private String dbConnectionString;
-    
-    private String ip;
-        
-    public DBStartupConfiguration(File systemProperties, File userProperties,
-            File dbPath, File logFile, File pidFile) throws InvalidConfigurationException {
-        this.dbPath = dbPath;
-        this.logFile = logFile;
-        this.pidFile = pidFile;
-        readAndSetProperties(systemProperties, userProperties);
-    }
-    
-    public File getDBPath() {
-        return dbPath;
-    }
-    
-    public File getLogFile() {
-        return logFile;
-    }
-    
-    public File getPidFile() {
-        return pidFile;
-    }
-   
-    public void setPort(long localPort) {
-        this.localPort = localPort;
-    }
-    
-    public long getPort() {
-        return localPort;
-    }
-    
-    void setDBConnectionString(String dbConnectionString) {
-        this.dbConnectionString = dbConnectionString;
-    }
-    
-    @Override
-    public String getDBConnectionString() {
-        return dbConnectionString;
-    }
-
-    void setBindIP(String ip) {
-        this.ip = ip;
-    }
-    
-    public String getBindIP() {
-        return ip;
-    }
-
-    public boolean isSslEnabled() {
-        return sslEnabled;
-    }
-
-    void setSslEnabled(boolean sslEnabled) {
-        this.sslEnabled = sslEnabled;
-    }
-
-    /**
-     * 
-     * @return The file containing the server certificate and the private key in PEM format or null
-     *         if nothing was specified in $THERMOSTAT_HOME/storage/db.properties.
-     */
-    public File getSslPemFile() {
-        return sslPemFile;
-    }
-
-    void setSslPemFile(File sslPemFile) {
-        this.sslPemFile = sslPemFile;
-    }
-
-    /**
-     * 
-     * @return The passphrase for the encrypted server key or null if config was
-     *         not set.
-     */
-    public String getSslKeyPassphrase() {
-        return sslKeyPassphrase;
-    }
-
-    void setSslKeyPassphrase(String sslKeyPassphrase) {
-        this.sslKeyPassphrase = sslKeyPassphrase;
-    }
-    
-    private void readAndSetProperties(File systemPropertiesFile, File userPropertiesFile) throws InvalidConfigurationException {
-        
-        Properties systemProperties = new Properties();
-        try {
-            systemProperties.load(new FileInputStream(systemPropertiesFile));
-        } catch (IOException e) {
-            throw new InvalidConfigurationException(/* "Could not find system configuration", */e);
-        }
-        
-        Properties properties = new Properties(systemProperties);
-        try {
-            properties.load(new FileInputStream(userPropertiesFile));
-        } catch (IOException e) {
-            // that's fine. we will just rely on system properties
-        }
-
-        readAndSetProperties(properties);
-    }
-
-    private void readAndSetProperties(Properties properties) {
-        String port = properties.getProperty(DBConfig.PORT.name());
-        if (port != null) {
-            int localPort = Integer.parseInt(port);
-            setPort(localPort);
-        } else {
-            throw new InvalidConfigurationException(t.localize(LocaleResources.MISSING_PROPERTY, DBConfig.PORT.toString()));
-        }
-        
-        String ip = properties.getProperty(DBConfig.BIND.name());
-        if (ip != null) {
-            setBindIP(ip);
-        } else {
-            throw new InvalidConfigurationException(t.localize(LocaleResources.MISSING_PROPERTY, DBConfig.BIND.toString()));
-        }
-        
-        // optional config
-        String enableSSLConfig = properties.getProperty(DBConfig.SSL_ENABLE.name());
-        setSslEnabled(Boolean.parseBoolean(enableSSLConfig));
-        
-        String pemFile = properties.getProperty(DBConfig.SSL_PEM_FILE.name());
-        if (pemFile != null) {
-            setSslPemFile(new File(pemFile));
-        }
-        
-        String keyPassPhrase = properties.getProperty(DBConfig.SSL_KEY_PASSWORD.name());
-        setSslKeyPassphrase(keyPassPhrase);
-        
-    }
-}
-
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/MongoProcessRunner.java	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,312 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.cli.impl.db;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.Charset;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
-import com.redhat.thermostat.common.tools.ApplicationException;
-import com.redhat.thermostat.common.utils.LoggedExternalProcess;
-import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.service.process.UnixProcessUtilities;
-import com.redhat.thermostat.shared.config.InvalidConfigurationException;
-import com.redhat.thermostat.shared.locale.LocalizedString;
-import com.redhat.thermostat.shared.locale.Translate;
-
-public class MongoProcessRunner {
-    
-    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-    private static final Logger logger = LoggingUtils.getLogger(MongoProcessRunner.class);
-
-    private static final String MONGO_PROCESS = "mongod";
-
-    private static final String [] MONGO_BASIC_ARGS = {
-        "mongod", "--quiet", "--fork", "--auth", "--nohttpinterface", "--bind_ip"
-    };
-
-    private static final String [] MONGO_SHUTDOWN_ARGS = {
-        "kill", "-s", "TERM"
-    };
-
-    private static final String NO_JOURNAL_ARGUMENT = "--nojournal";
-    static final String NO_JOURNAL_FIRST_VERSION = "1.9.2";
-
-    private DBStartupConfiguration configuration;
-    private boolean isQuiet;
-    private Integer pid;
-    
-    public MongoProcessRunner(DBStartupConfiguration configuration, boolean quiet) {
-        this.configuration = configuration;
-        this.isQuiet = quiet;
-    }
-
-    private boolean checkPid() {
-        File pidfile = configuration.getPidFile();
-        Charset charset = Charset.defaultCharset();
-        if (pidfile.exists()) {
-            try (BufferedReader reader = Files.newBufferedReader(pidfile.toPath(), charset)) {
-                pid = doGetPid(reader);
-            } catch (IOException ex) {
-                logger.log(Level.WARNING, "Exception while reading pid file", ex);
-                pid = null;
-            } catch (NumberFormatException e) {
-                logger.log(Level.WARNING, "Mongo PID file does not contain a valid PID", e);
-                pid = null;
-            }
-        } else {
-            pid = null;
-        }
-        return (pid != null);
-    }
-    
-    // package private for testing
-    Integer doGetPid(BufferedReader reader) throws IOException {
-        String line = reader.readLine();
-        // readLine() returns null on EOF
-        if (line == null || line.isEmpty()) {
-            return null;
-        }
-        else {
-            return Integer.parseInt(line);
-        }
-    }
-
-    private void deleteStalePidFile() {
-        pid = null;
-        LocalizedString message = translator.localize(LocaleResources.STALE_PID_FILE_NO_MATCHING_PROCESS, configuration.getPidFile().toString(), MONGO_PROCESS);
-        // Mongo didn't remove its PID file? Work around the issue. Log
-        // the event, remove the stale pid file and continue.
-        logger.log(Level.WARNING, message.getContents());
-        try {
-            Files.delete(configuration.getPidFile().toPath());
-        } catch (IOException benign) {
-            // ignore this benign error
-        }
-    }
-    
-    public boolean isStorageRunning() {
-        if (!checkPid()) {
-            return false;
-        }
-        
-        String processName = UnixProcessUtilities.getInstance().getProcessName(pid);
-        // TODO: check if we want mongos or mongod from the configs
-        boolean processIsRunning = processName != null && processName.equalsIgnoreCase(MONGO_PROCESS);
-        if (!processIsRunning) {
-            deleteStalePidFile();
-        }
-        return processIsRunning;
-    }
-    
-    /**
-     * Start the mongod process.
-     *
-     * @throws ApplicationException to signal an error starting the process. Callers should catch this and handle appropriately.
-     */
-    public void startService() throws IOException, InterruptedException,
-            ApplicationException, InvalidConfigurationException {
-
-        if (isStorageRunning()) {
-            LocalizedString message = translator.localize(LocaleResources.STORAGE_ALREADY_RUNNING_WITH_PID, String.valueOf(pid));
-            throw new StorageAlreadyRunningException(pid, message.getContents());
-        }
-        
-        String dbVersion;
-        try {
-            dbVersion = getDBVersion();
-        } catch (IOException e) {
-            LocalizedString message = translator.localize(
-                    LocaleResources.CANNOT_EXECUTE_PROCESS, MONGO_PROCESS);
-            throw new ApplicationException(message.getContents(), e);
-
-        }
-        List<String> commands = null;
-        commands = getStartupCommand(dbVersion);
-        
-        display(translator.localize(LocaleResources.STARTING_STORAGE_SERVER));
-        
-        LoggedExternalProcess process = new LoggedExternalProcess(commands);
-        int status = -1;
-        try {
-            status = process.runAndReturnResult();
-        } catch (ApplicationException ae) {
-            LocalizedString message = translator.localize(LocaleResources.CANNOT_EXECUTE_PROCESS, MONGO_PROCESS);
-            throw ae;
-        }
-
-        Thread.sleep(500);
-
-        if (status == 0) {
-            if (!isStorageRunning()) {
-                status = -1;
-            }
-        }
-
-        if (status == 0) {
-            display(translator.localize(LocaleResources.SERVER_LISTENING_ON, configuration.getDBConnectionString()));
-            display(translator.localize(LocaleResources.LOG_FILE_AT, configuration.getLogFile().toString()));
-            display(translator.localize(LocaleResources.PID_IS, String.valueOf(pid)));
-            
-        } else {
-            // don't display anything when throwing an exception; whatever catches the exception will do so.
-            LocalizedString message = translator.localize(LocaleResources.CANNOT_START_SERVER,
-                             configuration.getDBPath().toString(),
-                             String.valueOf(status));
-            throw new StorageStartException(configuration.getDBPath(), status, message.getContents());
-        }
-    }
-    
-    /**
-     * Stop the mongod process.
-     *
-     * @throws ApplicationException to signal an error stopping the storage. Callers should catch this and handle appropriately.
-     */
-    public void stopService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
- 
-        if (!isStorageRunning()) {
-            LocalizedString message = translator.localize(LocaleResources.STORAGE_NOT_RUNNING);
-            throw new StorageNotRunningException(message.getContents());
-        }
-        List<String> commands = new ArrayList<>(Arrays.asList(MONGO_SHUTDOWN_ARGS));
-        commands.add(String.valueOf(pid));
-
-        LoggedExternalProcess process = new LoggedExternalProcess(commands);
-        int status = process.runAndReturnResult();
-        if (status == 0) {
-            display(translator.localize(LocaleResources.SERVER_SHUTDOWN_COMPLETE, configuration.getDBPath().toString()));
-            display(translator.localize(LocaleResources.LOG_FILE_AT, configuration.getLogFile().toString()));
-            // all went well, make sure to remove pid file.
-            try {
-                Files.delete(configuration.getPidFile().toPath());
-            } catch (IOException e) {
-                // ignore
-            }
-        } else {
-            // don't display anything when throwing an exception; whatever catches the exception will do so.
-            LocalizedString message = translator.localize(LocaleResources.CANNOT_SHUTDOWN_SERVER,
-                    configuration.getDBPath().toString(),
-                    String.valueOf(status));
-            throw new StorageStopException(configuration.getDBPath(), status, message.getContents());
-        }
-    }
-    
-    List<String> getStartupCommand(String dbVersion) throws IOException, InvalidConfigurationException {
-        List<String> commands = new ArrayList<>(Arrays.asList(MONGO_BASIC_ARGS));
-        
-        if (dbVersion.compareTo(NO_JOURNAL_FIRST_VERSION) >= 0) {
-            commands.add(1, NO_JOURNAL_ARGUMENT);
-        }
-        commands.add(configuration.getBindIP());
-
-        commands.add("--dbpath");
-        commands.add(configuration.getDBPath().getCanonicalPath());
-
-        commands.add("--logpath");
-        commands.add(configuration.getLogFile().getCanonicalPath());
-
-        commands.add("--pidfilepath");
-        commands.add(configuration.getPidFile().getCanonicalPath());
-
-        commands.add("--port");
-        commands.add(Long.toString(configuration.getPort()));
-        
-        if (configuration.isSslEnabled()) {
-            // check for configuration which has a chance of working :)
-            if (configuration.getSslPemFile() == null) {
-                throw new InvalidConfigurationException(translator.localize(LocaleResources.MISSING_PEM));
-            } else if (configuration.getSslKeyPassphrase() == null) {
-                throw new InvalidConfigurationException(translator.localize(LocaleResources.MISSING_PASSPHRASE));
-            }
-            commands.add("--sslOnNormalPorts");
-            commands.add("--sslPEMKeyFile");
-            commands.add(configuration.getSslPemFile().getCanonicalPath());
-            commands.add("--sslPEMKeyPassword");
-            commands.add(configuration.getSslKeyPassphrase());
-        }
-        
-        return commands;
-    }
- 
-    private String getDBVersion() throws IOException {
-        Process process = new ProcessBuilder(Arrays.asList("mongod", "--version")).start();
-        InputStream out = process.getInputStream();
-        return doGetDBVersion(out);
-    }
-    
-    // package private for testing
-    String doGetDBVersion(InputStream in) throws IOException {
-        // Default to no-journal first version if we can't parse the version
-        // output for some reason.
-        String versionString = NO_JOURNAL_FIRST_VERSION;
-        String firstLine = null;
-        try(InputStreamReader reader = new InputStreamReader(in)) {
-            BufferedReader bufReader = new BufferedReader(reader);
-            firstLine = bufReader.readLine();
-            int commaIdx = firstLine.indexOf(",", 12);
-            if (commaIdx != -1) {
-                versionString = firstLine.substring(12, commaIdx);
-            } else {
-                versionString = firstLine.substring(12);
-            }
-        } catch (Exception e) {
-            // catching Exception here in order to also catch potential NPEs or
-            // IndexOutOfBoundExceptions. If those conditions happen we fall
-            // back to the no journal first version.
-            logger.log(Level.WARNING, "Failed to parse mongodb version from: '" +
-                firstLine + "'. Assuming version " + NO_JOURNAL_FIRST_VERSION, e);
-        }
-        return versionString;
-    }
-
-    private void display(LocalizedString message) {
-        if (!isQuiet) {
-            System.out.println(message.getContents());
-        }
-    }
-}
-
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StalePidFileException.java	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-package com.redhat.thermostat.agent.cli.impl.db;
-
-import java.io.File;
-
-import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
-import com.redhat.thermostat.common.tools.ApplicationException;
-import com.redhat.thermostat.shared.locale.Translate;
-
-@SuppressWarnings("serial")
-public class StalePidFileException extends ApplicationException {
-
-    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-
-    private final File pidFile;
-
-    public StalePidFileException(File pidFile) {
-        super(translator.localize(LocaleResources.STALE_PID_FILE, pidFile.toString()).getContents());
-        this.pidFile = pidFile;
-    }
-
-    public File getPidFile() {
-        return pidFile;
-    }
-
-}
-
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageAlreadyRunningException.java	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-package com.redhat.thermostat.agent.cli.impl.db;
-
-import com.redhat.thermostat.common.tools.ApplicationException;
-
-@SuppressWarnings("serial")
-public class StorageAlreadyRunningException extends ApplicationException {
-
-    private final int storagePid;
-
-    public StorageAlreadyRunningException(int pid, String message) {
-        super(message);
-        storagePid = pid;
-    }
-
-    public int getStoragePid() {
-        return storagePid;
-    }
-
-}
-
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageCommand.java	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,211 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.cli.impl.db;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
-import com.redhat.thermostat.common.ExitStatus;
-import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand;
-import com.redhat.thermostat.common.cli.Arguments;
-import com.redhat.thermostat.common.cli.CommandContext;
-import com.redhat.thermostat.common.cli.CommandException;
-import com.redhat.thermostat.common.tools.ApplicationException;
-import com.redhat.thermostat.common.tools.ApplicationState;
-import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.shared.config.CommonPaths;
-import com.redhat.thermostat.shared.config.InvalidConfigurationException;
-import com.redhat.thermostat.shared.locale.Translate;
-
-public class StorageCommand extends AbstractStateNotifyingCommand {
-
-    private static final Logger logger = LoggingUtils.getLogger(StorageCommand.class);
-    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
-
-    private DBStartupConfiguration configuration;
-    private DBOptionParser parser;
-    private final ExitStatus exitStatus;
-    private final CommonPaths paths;
-    
-    private MongoProcessRunner runner;
-    
-    public StorageCommand(ExitStatus exitStatus, CommonPaths paths) {
-        this.exitStatus = exitStatus;
-        this.paths = paths;
-    }
-    
-    private void parseArguments(Arguments args) throws InvalidConfigurationException {
-        File dbPath = paths.getUserStorageDirectory();
-        File logFile = paths.getUserStorageLogFile();
-        File pidFile = paths.getUserStoragePidFile();
-        File systemPropertyFile = paths.getSystemStorageConfigurationFile();
-        if (!systemPropertyFile.exists()) {
-            throw new InvalidConfigurationException(t.localize(LocaleResources.MISSING_DB_CONFIG, systemPropertyFile.toString()));
-        }
-        File userPropertyFile = paths.getUserStorageConfigurationFile();
-        // read everything that is in the configs
-        this.configuration = new DBStartupConfiguration(systemPropertyFile, userPropertyFile, dbPath, logFile, pidFile);
-        parser = new DBOptionParser(configuration, args);
-        parser.parse();
-    }
-    
-    @Override
-    public void run(CommandContext ctx) throws CommandException {
-        parseArgsAndRun(ctx);
-    }
-
-    private void parseArgsAndRun(CommandContext ctx)
-            throws InvalidConfigurationException {
-        parseArguments(ctx.getArguments());
-
-        // dry run means we don't do anything at all
-        if (parser.isDryRun()) return;
-        
-        runner = createRunner();
-        try {
-            switch (parser.getAction()) {
-            case START:
-                startService();
-                break;
-            case STOP:
-                stopService();
-                break;
-            case STATUS:
-                printServiceStatus(ctx);
-                break;
-             default:
-                break;
-            }
-            getNotifier().fireAction(ApplicationState.SUCCESS);
-        } catch (InvalidConfigurationException e) {
-            // rethrow
-            throw e;
-        } catch (ApplicationException e) {
-            logger.log(Level.WARNING, e.getMessage());
-            getNotifier().fireAction(ApplicationState.FAIL, e);
-        } catch (Exception e) {
-            logger.log(Level.WARNING, e.getMessage(), e);
-            getNotifier().fireAction(ApplicationState.FAIL, e);
-        }
-    }
-    
-    private void startService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
-        try {
-            createNeededDirectories();
-            runner.startService();
-        } catch (ApplicationException | InvalidConfigurationException | IOException e) {
-            // something went wrong set status appropriately. This makes sure
-            // that the JVM exits with this status.
-            exitStatus.setExitStatus(ExitStatus.EXIT_ERROR);
-            // rethrow
-            throw e;
-        }
-        getNotifier().fireAction(ApplicationState.START);
-    }
-    
-    private void createNeededDirectories() throws InvalidConfigurationException {
-        File[] requiredDirectories = new File[] {
-                configuration.getDBPath(),
-        };
-
-        for (File directory : requiredDirectories) {
-            if (!directory.isDirectory() && !directory.mkdirs()) {
-                throw new InvalidConfigurationException(t.localize(LocaleResources.MISSING_DB_DIR));
-            }
-        }
-
-        File[] requiredFiles = new File[] {
-                configuration.getLogFile(),
-                configuration.getPidFile(),
-        };
-
-        for (File file : requiredFiles) {
-            File directory = file.getParentFile();
-            if (!directory.isDirectory() && !directory.mkdirs()) {
-                throw new InvalidConfigurationException(t.localize(LocaleResources.MISSING_DB_DIR));
-            }
-        }
-    }
-    
-    private void stopService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
-        try {
-            check();
-            runner.stopService();
-        } catch (ApplicationException | InvalidConfigurationException | InterruptedException | IOException e) {
-            // something went wrong set status appropriately. This makes sure
-            // that the JVM exits with this status.
-            exitStatus.setExitStatus(ExitStatus.EXIT_ERROR);
-            throw e;
-        }
-        getNotifier().fireAction(ApplicationState.STOP);
-    }
-    
-    private void check() throws InvalidConfigurationException {
-        if (!configuration.getDBPath().exists() ||
-            !configuration.getLogFile().getParentFile().exists() ||
-            !configuration.getPidFile().getParentFile().exists())
-        {
-            throw new InvalidConfigurationException(t.localize(LocaleResources.MISSING_DB_DIR));
-        }
-    }
-
-    private void printServiceStatus(CommandContext ctx) {
-        if (runner.isStorageRunning()) {
-            ctx.getConsole().getOutput().println(t.localize(LocaleResources.STORAGE_RUNNING).getContents());
-        } else {
-            ctx.getConsole().getOutput().println(t.localize(LocaleResources.STORAGE_NOT_RUNNING).getContents());
-        }
-    }
-    
-    MongoProcessRunner createRunner() {
-        return new MongoProcessRunner(configuration, parser.isQuiet());
-    }
-
-    public DBStartupConfiguration getConfiguration() {
-        return configuration;
-    }
-
-    @Override
-    public boolean isStorageRequired() {
-        return false;
-    }
-
-}
-
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageNotRunningException.java	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.cli.impl.db;
-
-import com.redhat.thermostat.common.tools.ApplicationException;
-
-@SuppressWarnings("serial")
-public class StorageNotRunningException extends ApplicationException {
-
-    public StorageNotRunningException(String message) {
-        super(message);
-    }
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageStartException.java	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-package com.redhat.thermostat.agent.cli.impl.db;
-
-import java.io.File;
-
-import com.redhat.thermostat.common.tools.ApplicationException;
-
-@SuppressWarnings("serial")
-public class StorageStartException extends ApplicationException {
-
-    private final File dbFile;
-    private final int status;
-
-    public StorageStartException(File dbPath, int status, String message) {
-        super(message);
-        this.dbFile = dbPath;
-        this.status = status;
-    }
-
-    public File getDbPath() {
-        return dbFile;
-    }
-
-    public int getStatus() {
-        return status;
-    }
-
-}
-
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageStopException.java	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-package com.redhat.thermostat.agent.cli.impl.db;
-
-import java.io.File;
-
-import com.redhat.thermostat.common.tools.ApplicationException;
-
-@SuppressWarnings("serial")
-public class StorageStopException extends ApplicationException {
-
-    private final File dbConfig;
-    private final int status;
-
-    public StorageStopException(File dbConfig, int status, String message) {
-        super(message);
-        this.dbConfig = dbConfig;
-        this.status = status;
-    }
-
-    public File getDbConfig() {
-        return dbConfig;
-    }
-
-    public int getStatus() {
-        return status;
-    }
-
-}
-
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/locale/LocaleResources.java	Fri Dec 20 11:42:37 2013 +0100
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/locale/LocaleResources.java	Mon Feb 10 19:08:50 2014 -0500
@@ -42,29 +42,9 @@
 
     STARTING_AGENT,
 
-    COMMAND_STORAGE_ARGUMENT_REQUIRED,
-
-    STORAGE_ALREADY_RUNNING,
-    STORAGE_ALREADY_RUNNING_WITH_PID,
     SERVICE_FAILED_TO_START_DB,
-    SERVER_SHUTDOWN_COMPLETE,
-    LOG_FILE_AT,
-    CANNOT_START_SERVER,
-    CANNOT_SHUTDOWN_SERVER,
-    STALE_PID_FILE,
-    STALE_PID_FILE_NO_MATCHING_PROCESS,
-    STARTING_STORAGE_SERVER,
-    CANNOT_EXECUTE_PROCESS,
-    SERVER_LISTENING_ON,
-    PID_IS,
     LAUNCHER_UNAVAILABLE,
-    MISSING_PROPERTY,
-    MISSING_PEM,
-    MISSING_PASSPHRASE,
-    MISSING_DB_CONFIG,
-    MISSING_DB_DIR,
-    STORAGE_RUNNING,
-    STORAGE_NOT_RUNNING
+    UNEXPECTED_RESULT_STORAGE,
     ;
 
     static final String RESOURCE_BUNDLE = "com.redhat.thermostat.agent.cli.impl.strings";
--- a/agent/cli/src/main/resources/com/redhat/thermostat/agent/cli/impl/strings.properties	Fri Dec 20 11:42:37 2013 +0100
+++ b/agent/cli/src/main/resources/com/redhat/thermostat/agent/cli/impl/strings.properties	Mon Feb 10 19:08:50 2014 -0500
@@ -1,25 +1,5 @@
 STARTING_AGENT = starting agent now...
 
-COMMAND_STORAGE_ARGUMENT_REQUIRED = either --start or --stop must be given
-
 SERVICE_FAILED_TO_START_DB = Service failed to start due to error starting storage.
-STORAGE_ALREADY_RUNNING = Storage is already running. Please use "agent --start" to start the agent
-STORAGE_ALREADY_RUNNING_WITH_PID = Storage is already running with pid {0}
-SERVER_SHUTDOWN_COMPLETE = server shutdown complete: {0}
-LOG_FILE_AT = log file is here: {0}
-CANNOT_START_SERVER = cannot start server {0}, exit status: {1}. Please check that your configuration is valid
-CANNOT_SHUTDOWN_SERVER = cannot shutdown server {0}, exit status: {1}. Please check that your configuration is valid
-STALE_PID_FILE = stale pid file: {0}
-STALE_PID_FILE_NO_MATCHING_PROCESS = A stale pid file ({0}) is present but there is no matching {1} process. Removing stale pid file.
-STARTING_STORAGE_SERVER = starting storage server...
-CANNOT_EXECUTE_PROCESS = can not execute {0} process. is it installed?
-SERVER_LISTENING_ON = server listening on ip: {0}
-PID_IS = pid: {0}
 LAUNCHER_UNAVAILABLE = Launcher is not available
-MISSING_PROPERTY = {0} property missing
-MISSING_PEM = No SSL PEM file specified!
-MISSING_PASSPHRASE = No SSL key passphrase set!
-MISSING_DB_CONFIG = can't access database configuration file {0}
-MISSING_DB_DIR = database directories do not exist...
-STORAGE_RUNNING = Storage is running
-STORAGE_NOT_RUNNING = Storage is not running
+UNEXPECTED_RESULT_STORAGE = Unexpected result from storage.
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ActivatorTest.java	Fri Dec 20 11:42:37 2013 +0100
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ActivatorTest.java	Mon Feb 10 19:08:50 2014 -0500
@@ -42,9 +42,7 @@
 
 import org.junit.Test;
 
-import com.redhat.thermostat.agent.cli.impl.db.StorageCommand;
 import com.redhat.thermostat.common.ExitStatus;
-import com.redhat.thermostat.shared.config.CommonPaths;
 import com.redhat.thermostat.storage.core.WriterID;
 import com.redhat.thermostat.testutils.StubBundleContext;
 
@@ -56,10 +54,8 @@
 
         ExitStatus exitStatus = mock(ExitStatus.class);
         WriterID writerID = mock(WriterID.class);
-        CommonPaths paths = mock(CommonPaths.class);
         bundleContext.registerService(WriterID.class, writerID, null);
         bundleContext.registerService(ExitStatus.class, exitStatus, null);
-        bundleContext.registerService(CommonPaths.class, paths, null);
         
         Activator activator = new Activator();
 
@@ -67,16 +63,15 @@
         
         activator.start(bundleContext);
         
-        assertEquals(3, bundleContext.getServiceListeners().size());
+        assertEquals(2, bundleContext.getServiceListeners().size());
         
         assertCommandIsRegistered(bundleContext, "agent", AgentApplication.class);
         assertCommandIsRegistered(bundleContext, "service", ServiceCommand.class);
-        assertCommandIsRegistered(bundleContext, "storage", StorageCommand.class);
 
         activator.stop(bundleContext);
 
         assertEquals(0, bundleContext.getServiceListeners().size());
-        assertEquals(3, bundleContext.getAllServices().size());
+        assertEquals(2, bundleContext.getAllServices().size());
     }
 }
 
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ServiceCommandTest.java	Fri Dec 20 11:42:37 2013 +0100
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ServiceCommandTest.java	Mon Feb 10 19:08:50 2014 -0500
@@ -36,16 +36,17 @@
 
 package com.redhat.thermostat.agent.cli.impl;
 
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.isA;
-import static org.mockito.Mockito.anyBoolean;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.never;
 
+import java.io.PrintStream;
 import java.util.Collection;
 
 import junit.framework.Assert;
@@ -55,15 +56,14 @@
 import org.junit.Test;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
-import org.osgi.framework.ServiceReference;
 
-import com.redhat.thermostat.agent.cli.impl.db.DBStartupConfiguration;
-import com.redhat.thermostat.agent.cli.impl.db.StorageCommand;
 import com.redhat.thermostat.common.ActionEvent;
 import com.redhat.thermostat.common.ActionListener;
 import com.redhat.thermostat.common.ActionNotifier;
+import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.Console;
 import com.redhat.thermostat.common.tools.ApplicationState;
 import com.redhat.thermostat.launcher.Launcher;
 import com.redhat.thermostat.testutils.StubBundleContext;
@@ -91,16 +91,18 @@
         bundleContext.registerService(Launcher.class, mockLauncher, null);
         serviceCommand = new ServiceCommand(bundleContext);
         
-        StorageCommand mockStorageCommand = mock(StorageCommand.class);
+        AbstractStateNotifyingCommand mockStorageCommand = mock(AbstractStateNotifyingCommand.class);
         mockActionEvent = mock(ActionEvent.class);
         when(mockActionEvent.getSource()).thenReturn(mockStorageCommand);
         mockCommandContext = mock(CommandContext.class);
+        Console console = mock(Console.class);
+        PrintStream err = mock(PrintStream.class);
+        when(console.getError()).thenReturn(err);
+        when(mockCommandContext.getConsole()).thenReturn(console);
         
         ActionNotifier<ApplicationState> mockNotifier = mock(ActionNotifier.class);
-        DBStartupConfiguration mockDbsConfiguration = mock(DBStartupConfiguration.class);
         when(mockStorageCommand.getNotifier()).thenReturn(mockNotifier);
-        when(mockStorageCommand.getConfiguration()).thenReturn(mockDbsConfiguration);
-        when(mockDbsConfiguration.getDBConnectionString()).thenReturn(new String("Test String"));
+        when(mockActionEvent.getPayload()).thenReturn(new String("Test String"));
     }
 
     @After
@@ -156,6 +158,7 @@
                     when(mockActionEvent.getActionId()).thenReturn(ApplicationState.START);
                 } else {
                     when(mockActionEvent.getActionId()).thenReturn(ApplicationState.FAIL);
+                    when(mockActionEvent.getPayload()).thenReturn(new Exception("Test Exception"));
                 }
                 ++count;
                 
@@ -197,6 +200,7 @@
                 listeners = (Collection<ActionListener<ApplicationState>>)args[1];
                 
                 when(mockActionEvent.getActionId()).thenReturn(ApplicationState.FAIL);
+                when(mockActionEvent.getPayload()).thenReturn(new Exception("Test Exception"));
                 
                 for(ActionListener<ApplicationState> listener : listeners) {
                     listener.actionPerformed(mockActionEvent);
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/db/DBStartupConfigurationTest.java	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,127 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.cli.impl.db;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-
-import java.io.File;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import com.redhat.thermostat.shared.config.InvalidConfigurationException;
-
-public class DBStartupConfigurationTest {
-    
-    private File dbLogFile;
-    private File dbPidFile;
-    private File dbPath;
-    
-    @Before
-    public void setUp() {
-        dbLogFile = new File("db.log");
-        dbPidFile = new File("db.pid");
-        dbPath = new File("somepath");
-    }
-    
-    @After
-    public void tearDown() {
-        dbLogFile = null;
-        dbPidFile = null;
-        dbPath = null;
-    }
-
-    @Test
-    public void canGetConfigFromPropertiesFile() throws Exception {
-        File dbProps = new File(this.getClass().getResource("/testDbConfig.properties").getFile());
-        File canNotBeFoundFile = new File("");
-        DBStartupConfiguration dbConfig = new DBStartupConfiguration(dbProps, canNotBeFoundFile, dbPath, dbLogFile, dbPidFile);
-        
-        assertEquals(dbLogFile.getAbsolutePath(), dbConfig.getLogFile().getAbsolutePath());
-        assertEquals(dbPidFile.getAbsolutePath(), dbConfig.getPidFile().getAbsolutePath());
-        assertEquals(dbPath.getAbsolutePath(), dbConfig.getDBPath().getAbsolutePath());
-        assertEquals("127.0.0.1", dbConfig.getBindIP());
-        assertEquals(27518, dbConfig.getPort());
-        assertEquals(true, dbConfig.isSslEnabled());
-        assertEquals("/path/to/some/pem/file.pem", dbConfig.getSslPemFile().getAbsolutePath());
-        assertEquals("somepassword", dbConfig.getSslKeyPassphrase());
-    }
-    
-    @Test
-    public void canGetConfigFromPropertiesFile2() throws Exception {
-        File dbProps = new File(this.getClass().getResource("/testDbConfig2.properties").getFile());
-        File canNotBeFoundFile = new File("");
-        DBStartupConfiguration dbConfig = new DBStartupConfiguration(dbProps, canNotBeFoundFile, dbPath, dbLogFile, dbPidFile);
-        
-        assertEquals(dbLogFile.getAbsolutePath(), dbConfig.getLogFile().getAbsolutePath());
-        assertEquals(dbPidFile.getAbsolutePath(), dbConfig.getPidFile().getAbsolutePath());
-        assertEquals(dbPath.getAbsolutePath(), dbConfig.getDBPath().getAbsolutePath());
-        assertEquals("127.0.0.1", dbConfig.getBindIP());
-        assertEquals(27518, dbConfig.getPort());
-        assertEquals(false, dbConfig.isSslEnabled());
-        assertNull(dbConfig.getSslPemFile());
-        assertNull(dbConfig.getSslKeyPassphrase());
-    }
-    
-    @Test
-    public void missingBindThrowsConfigException() throws Exception {
-        File dbProps = new File(this.getClass().getResource("/brokenDbConfig.properties").getFile());
-        File canNotBeFoundFile = new File("");
-        try {
-            new DBStartupConfiguration(dbProps, canNotBeFoundFile, dbPath, dbLogFile, dbPidFile);
-            fail("BIND was not specified in properties file");
-        } catch (InvalidConfigurationException e) {
-            assertEquals("BIND property missing", e.getMessage());
-        }
-    }
-    
-    @Test
-    public void missingPortThrowsConfigException() throws Exception {
-        File dbProps = new File(this.getClass().getResource("/brokenDbConfig2.properties").getFile());
-        File canNotBeFoundFile = new File("");
-        try {
-            new DBStartupConfiguration(dbProps, canNotBeFoundFile, dbPath, dbLogFile, dbPidFile);
-            fail("PORT was not specified in properties file");
-        } catch (InvalidConfigurationException e) {
-            assertEquals("PORT property missing", e.getMessage());
-        }
-    }
-}
-
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/db/MongoProcessRunnerTest.java	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,161 +0,0 @@
-package com.redhat.thermostat.agent.cli.impl.db;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.io.BufferedReader;
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import com.redhat.thermostat.agent.cli.impl.db.MongoProcessRunner;
-import com.redhat.thermostat.shared.config.InvalidConfigurationException;
-
-public class MongoProcessRunnerTest {
-
-    private MongoProcessRunner runner;
-    private DBStartupConfiguration config;
-    private static final String NO_JOURNAL_MONGODB_VERSION = "2.0.0";
-    private static final String JOURNAL_MONGODB_VERSION = "1.8.0";
-    private static final String BIND_IP = "127.0.0.1";
-    private static final long PORT = 12456;
-    
-    @Before
-    public void setUp() {
-        File dbPath = new File("/path/to/db");
-        File logPath = new File("/path/to/log");
-        File pidFile = new File("/path/to/pid");
-        config = mock(DBStartupConfiguration.class);
-        when(config.getBindIP()).thenReturn(BIND_IP);
-        when(config.getPort()).thenReturn(PORT);
-        when(config.getDBPath()).thenReturn(dbPath);
-        when(config.getLogFile()).thenReturn(logPath);
-        when(config.getPidFile()).thenReturn(pidFile);
-        runner = new MongoProcessRunner(config, false);
-    }
-    
-    @After
-    public void tearDown() {
-        runner = null;
-        config = null;
-    }
-    
-    @Test
-    public void testCommandArgumentsWithJournalVersion() throws Exception {
-        String[] expected = { "mongod", "--nojournal", "--quiet", "--fork",
-                "--auth", "--nohttpinterface", "--bind_ip", config.getBindIP(),
-                "--dbpath", config.getDBPath().getCanonicalPath(), "--logpath",
-                config.getLogFile().getCanonicalPath(), "--pidfilepath",
-                config.getPidFile().getCanonicalPath(), "--port",
-                Long.toString(config.getPort()) };
-        List<String> cmds = runner.getStartupCommand(NO_JOURNAL_MONGODB_VERSION);
-        String[] actual = cmds.toArray(new String[0]);
-        verifyEquals(expected, actual);
-    }
-    
-    @Test
-    public void testCommandArgumentsWithNoJournalVersion() throws Exception {
-        String[] expected = { "mongod", "--quiet", "--fork", "--auth",
-                "--nohttpinterface", "--bind_ip", config.getBindIP(),
-                "--dbpath", config.getDBPath().getCanonicalPath(), "--logpath",
-                config.getLogFile().getCanonicalPath(), "--pidfilepath",
-                config.getPidFile().getCanonicalPath(), "--port",
-                Long.toString(config.getPort()) };
-        List<String> cmds = runner.getStartupCommand(JOURNAL_MONGODB_VERSION);
-        String[] actual = cmds.toArray(new String[0]);
-        verifyEquals(expected, actual);
-    }
-    
-    @Test
-    public void testCommandArgumentsWithSSLEnabled() throws Exception {
-        when(config.isSslEnabled()).thenReturn(true);
-        File pemFile = new File("/path/to/cert_and_key.pem");
-        when(config.getSslPemFile()).thenReturn(pemFile);
-        when(config.getSslKeyPassphrase()).thenReturn("non-null");
-        String[] expected = { "mongod", "--quiet", "--fork", "--auth",
-                "--nohttpinterface", "--bind_ip", config.getBindIP(),
-                "--dbpath", config.getDBPath().getCanonicalPath(), "--logpath",
-                config.getLogFile().getCanonicalPath(), "--pidfilepath",
-                config.getPidFile().getCanonicalPath(), "--port",
-                Long.toString(config.getPort()), "--sslOnNormalPorts",
-                "--sslPEMKeyFile", config.getSslPemFile().getCanonicalPath(),
-                "--sslPEMKeyPassword", config.getSslKeyPassphrase()
-        };
-        List<String> cmds = runner.getStartupCommand(JOURNAL_MONGODB_VERSION);
-        String[] actual = cmds.toArray(new String[0]);
-        verifyEquals(expected, actual);
-    }
-    
-    @Test
-    public void testCommandArgumentsWithSSLEnabledThrowsInvalidConfigException() throws IOException {
-        when(config.isSslEnabled()).thenReturn(true);
-        // PEM file can't be null when SSL == true
-        when(config.getSslPemFile()).thenReturn(null);
-        try {
-            runner.getStartupCommand(JOURNAL_MONGODB_VERSION);
-            fail("Should have thrown exception!");
-        } catch (InvalidConfigurationException e) {
-            assertEquals("No SSL PEM file specified!", e.getMessage());
-        }
-        // Key password can't be null when SSL == true and keyfile present
-        File pemFile = new File("/path/to/ssl.pem");
-        when(config.getSslPemFile()).thenReturn(pemFile);
-        when(config.getSslKeyPassphrase()).thenReturn(null);
-        try {
-            runner.getStartupCommand(JOURNAL_MONGODB_VERSION);
-            fail("Should have thrown exception!");
-        } catch (InvalidConfigurationException e) {
-            assertEquals("No SSL key passphrase set!", e.getMessage());
-        }
-    }
-    
-    @Test
-    public void testCheckPidNull() throws IOException {
-        BufferedReader reader = mock(BufferedReader.class);
-        when(reader.readLine()).thenReturn(null);
-        Integer pid = runner.doGetPid(reader);
-        assertNull(pid);
-    }
-    
-    @Test
-    public void canGetVersionFromVersionCmdOutputV22() throws IOException {
-        String versionOutput = "db version v2.2.4, pdfile version 4.5\n" +
-                               "Mon Aug 26 17:13:45 git version: nogitversion";
-        ByteArrayInputStream in = new ByteArrayInputStream(versionOutput.getBytes());
-        String version = runner.doGetDBVersion(in);
-        assertEquals("2.2.4", version);
-    }
-    
-    @Test
-    public void canGetVersionFromVersionCmdOutputV24() throws IOException {
-        String versionOutput = "db version v2.4.5\n" +
-                               "Mon Aug 26 18:01:28.404 git version: nogitversion";
-        ByteArrayInputStream in = new ByteArrayInputStream(versionOutput.getBytes());
-        String version = runner.doGetDBVersion(in);
-        assertEquals("2.4.5", version);
-    }
-    
-    @Test
-    public void canProceedIfGetDbVersionThrowsException() throws IOException {
-        String versionOutput = "foo\n" +
-                               "Mon Aug 26 18:01:28.404 git version: nogitversion";
-        ByteArrayInputStream in = new ByteArrayInputStream(versionOutput.getBytes());
-        String version = runner.doGetDBVersion(in);
-        assertEquals(MongoProcessRunner.NO_JOURNAL_FIRST_VERSION, version);
-    }
-
-    private void verifyEquals(String[] expected, String[] actual) {
-        assertEquals(expected.length, actual.length);
-        for (int i=0; i < expected.length; i++) {
-            assertEquals(expected[i], actual[i]);
-        }
-    }
-}
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/db/StorageCommandTest.java	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,332 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat 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; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat 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 Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.cli.impl.db;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Properties;
-import java.util.concurrent.CountDownLatch;
-
-import junit.framework.Assert;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import com.redhat.thermostat.common.ActionEvent;
-import com.redhat.thermostat.common.ActionListener;
-import com.redhat.thermostat.common.ExitStatus;
-import com.redhat.thermostat.common.cli.CommandContext;
-import com.redhat.thermostat.common.cli.CommandException;
-import com.redhat.thermostat.common.cli.SimpleArguments;
-import com.redhat.thermostat.common.tools.ApplicationException;
-import com.redhat.thermostat.common.tools.ApplicationState;
-import com.redhat.thermostat.shared.config.CommonPaths;
-import com.redhat.thermostat.shared.config.InvalidConfigurationException;
-import com.redhat.thermostat.shared.config.internal.CommonPathsImpl;
-import com.redhat.thermostat.testutils.TestUtils;
-
-public class StorageCommandTest {
-    
-    private static final String PORT = "27518";
-    private static final String BIND = "127.0.0.1";
-    private static final String DB = "data/db";
-
-    private String tmpDir;
-    private ExitStatus exitStatus;
-    private CommonPaths paths;
-    
-    @Before
-    public void setup() {
-        exitStatus = mock(ExitStatus.class);
-        // need to create a dummy config file for the test
-        try {
-            Properties props = new Properties();
-            
-            props.setProperty(DBConfig.BIND.name(), BIND);
-            props.setProperty(DBConfig.PORT.name(), PORT);
-
-            tmpDir = TestUtils.setupStorageConfigs(props);
-        } catch (IOException e) {
-            Assert.fail("cannot setup tests: " + e);
-        }
-
-        paths = mock(CommonPathsImpl.class);
-        File baseDir = new File(tmpDir);
-        File userRuntimeDir = new File(baseDir, "run");
-        File userDataDir = new File(baseDir, "data");
-        File logsDir = new File(baseDir, "logs");
-        File confDir = new File(baseDir, "etc");
-
-        when(paths.getUserThermostatHome()).thenReturn(baseDir);
-        when(paths.getUserRuntimeDataDirectory()).thenReturn(userRuntimeDir);
-        when(paths.getUserPersistentDataDirectory()).thenReturn(userDataDir);
-        when(paths.getUserConfigurationDirectory()).thenReturn(confDir);
-        when(paths.getUserStorageDirectory()).thenCallRealMethod();
-        when(paths.getUserStorageConfigurationFile()).thenCallRealMethod();
-        when(paths.getUserLogDirectory()).thenReturn(logsDir);
-        when(paths.getUserStorageLogFile()).thenCallRealMethod();
-        when(paths.getUserStoragePidFile()).thenCallRealMethod();
-
-        when(paths.getSystemThermostatHome()).thenReturn(baseDir);
-        when(paths.getSystemConfigurationDirectory()).thenCallRealMethod();
-        when(paths.getSystemStorageConfigurationFile()).thenCallRealMethod();
-    }
-    
-    @After
-    public void tearDown() {
-        exitStatus = null;
-    }
-    
-    @Test
-    public void testConfig() throws CommandException {
-        SimpleArguments args = new SimpleArguments();
-        args.addArgument("quiet", null);
-        args.addArgument("start", null);
-        args.addArgument("dryRun", null);
-        CommandContext ctx = mock(CommandContext.class);
-        when(ctx.getArguments()).thenReturn(args);
-
-        StorageCommand service = new StorageCommand(exitStatus, paths) {
-            @Override
-            MongoProcessRunner createRunner() {
-                throw new AssertionError("dry run should never create an actual runner");
-            }
-        };
-
-        service.run(ctx);
-        
-        DBStartupConfiguration conf = service.getConfiguration();
-        
-        Assert.assertEquals(tmpDir + DB, conf.getDBPath().getPath());
-        Assert.assertEquals(Integer.parseInt(PORT), conf.getPort());
-        Assert.assertEquals("mongodb://" + BIND + ":" + PORT , conf.getDBConnectionString());
-    }
-    
-    private StorageCommand prepareService(boolean startSuccess) throws IOException,
-            InterruptedException, InvalidConfigurationException, ApplicationException
-    {
-        final MongoProcessRunner runner = mock(MongoProcessRunner.class);
-        if (!startSuccess) {
-           doThrow(new ApplicationException("mock exception")).when(runner).startService();
-        }
-        
-        // TODO: stop not tested yet, but be sure it's not called from the code
-        doThrow(new ApplicationException("mock exception")).when(runner).stopService();
-        
-        StorageCommand service = new StorageCommand(exitStatus, paths) {
-            @Override
-            MongoProcessRunner createRunner() {
-                return runner;
-            }
-        };
-        
-        return service;
-    }
-    
-    @Test
-    public void testListeners() throws InterruptedException, IOException, ApplicationException, InvalidConfigurationException, CommandException
-    {
-        StorageCommand service = prepareService(true);
-        
-        final CountDownLatch latch = new CountDownLatch(2);
-        
-        final boolean[] result = new boolean[2];
-        service.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
-            @SuppressWarnings("incomplete-switch")
-            @Override
-            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
-                switch (actionEvent.getActionId()) {
-                case FAIL:
-                    result[0] = false;
-                    latch.countDown();
-                    latch.countDown();
-                    break;
-                    
-                case SUCCESS:
-                    result[0] = true;
-                    latch.countDown();
-                    break;
-
-                case START:
-                    result[1] = true;
-                    latch.countDown();
-                    break;
-                }
-            }
-        });
-        
-        service.run(prepareContext());
-        latch.await();
-        
-        Assert.assertTrue(result[0]);
-        Assert.assertTrue(result[1]);
-    }
-    
-    @Test
-    public void testListenersFail() throws InterruptedException, IOException, ApplicationException, CommandException, InvalidConfigurationException
-    {
-        StorageCommand service = prepareService(false);
-        
-        final CountDownLatch latch = new CountDownLatch(1);
-        final boolean[] result = new boolean[1];
-        service.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
-            @SuppressWarnings("incomplete-switch")
-            @Override
-            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
-                switch (actionEvent.getActionId()) {
-                case FAIL:
-                    result[0] = true;
-                    break;
-                    
-                case SUCCESS:
-                    result[0] = false;
-                    break;
-                }
-                latch.countDown();
-            }
-        });
-        
-        service.run(prepareContext());
-        latch.await();
-        
-        Assert.assertTrue(result[0]);
-    }
-    
-    @Test
-    public void exceptionSetsExitStatusOnFailure() throws Exception {
-        this.exitStatus = new ExitStatus() {
-            
-            private int exitStatus = -1;
-            
-            @Override
-            public void setExitStatus(int newExitStatus) {
-                exitStatus = newExitStatus;
-            }
-            
-            @Override
-            public int getExitStatus() {
-                return exitStatus;
-            }
-        };
-        assertEquals(-1, this.exitStatus.getExitStatus());
-        StorageCommand command = prepareService(false);
-        final CountDownLatch latch = new CountDownLatch(1);
-        final boolean[] result = new boolean[1];
-        command.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
-            @SuppressWarnings("incomplete-switch")
-            @Override
-            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
-                switch (actionEvent.getActionId()) {
-                case FAIL:
-                    result[0] = true;
-                    break;
-                    
-                case SUCCESS:
-                    result[0] = false;
-                    break;
-                }
-                latch.countDown();
-            }
-        });
-        command.run(prepareContext());
-        latch.await();
-        // should have failed
-        assertTrue(result[0]);
-        assertEquals(ExitStatus.EXIT_ERROR, this.exitStatus.getExitStatus());
-    }
-    
-    @Test
-    public void exitStatusRemainsUntouchedOnSuccess() throws Exception {
-        this.exitStatus = new ExitStatus() {
-            
-            private int exitStatus = -1;
-            
-            @Override
-            public void setExitStatus(int newExitStatus) {
-                exitStatus = newExitStatus;
-            }
-            
-            @Override
-            public int getExitStatus() {
-                return exitStatus;
-            }
-        };
-        StorageCommand command = prepareService(true);
-        final CountDownLatch latch = new CountDownLatch(1);
-        final boolean[] result = new boolean[1];
-        command.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
-            @SuppressWarnings("incomplete-switch")
-            @Override
-            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
-                switch (actionEvent.getActionId()) {
-                case FAIL:
-                    result[0] = false;
-                    break;
-                    
-                case SUCCESS:
-                    result[0] = true;
-                    break;
-                }
-                latch.countDown();
-            }
-        });
-        command.run(prepareContext());
-        latch.await();
-        // should have worked
-        assertTrue(result[0]);
-        // this impl of ExitStatus has a default value of -1
-        assertEquals(-1, this.exitStatus.getExitStatus());
-    }
-
-    private CommandContext prepareContext() {
-        SimpleArguments args = new SimpleArguments();
-        args.addArgument("quiet", "--quiet");
-        args.addArgument("start", "--start");
-        CommandContext ctx = mock(CommandContext.class);
-        when(ctx.getArguments()).thenReturn(args);
-        return ctx;
-    }
-
-}
-
--- a/agent/cli/src/test/resources/brokenDbConfig.properties	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-PORT=27518
-# missing BIND
\ No newline at end of file
--- a/agent/cli/src/test/resources/brokenDbConfig2.properties	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-#PORT=27518
-BIND=127.0.0.1
\ No newline at end of file
--- a/agent/cli/src/test/resources/testDbConfig.properties	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-PORT=27518
-BIND=127.0.0.1
-
-## SSL config fluff
-SSL_ENABLE=true
-SSL_PEM_FILE=/path/to/some/pem/file.pem
-SSL_KEY_PASSWORD=somepassword
\ No newline at end of file
--- a/agent/cli/src/test/resources/testDbConfig2.properties	Fri Dec 20 11:42:37 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-PORT=27518
-BIND=127.0.0.1
-
-## SSL config fluff
-#SSL_ENABLE=true
-#SSL_PEM_FILE=/path/to/some/pem/file.pem
-#SSL_KEY_PASSWORD=somepassword
\ No newline at end of file
--- a/distribution/assembly/core-assembly.xml	Fri Dec 20 11:42:37 2013 +0100
+++ b/distribution/assembly/core-assembly.xml	Mon Feb 10 19:08:50 2014 -0500
@@ -66,6 +66,7 @@
         <include>com.redhat.thermostat:thermostat-common-core</include>
         <include>com.redhat.thermostat:thermostat-common-command</include>
         <include>com.redhat.thermostat:thermostat-osgi-process-handler</include>
+        <include>com.redhat.thermostat:thermostat-storage-cli</include>
         <include>com.redhat.thermostat:thermostat-keyring</include>
         <include>com.redhat.thermostat:thermostat-web-client</include>
         <include>com.redhat.thermostat:thermostat-web-cmd</include>
--- a/distribution/config/commands/service.properties	Fri Dec 20 11:42:37 2013 +0100
+++ b/distribution/config/commands/service.properties	Mon Feb 10 19:08:50 2014 -0500
@@ -8,6 +8,7 @@
           com.redhat.thermostat.process=${project.version}, \
           com.redhat.thermostat.common.command=${project.version}, \
           com.redhat.thermostat.agent.command=${project.version}, \
+          com.redhat.thermostat.storage.cli=${project.version}, \
           com.redhat.thermostat.agent.cli=${project.version}, \
           com.redhat.thermostat.agent.proxy.common=${project.version}, \
           org.jboss.netty=${netty.version}
--- a/distribution/config/commands/storage.properties	Fri Dec 20 11:42:37 2013 +0100
+++ b/distribution/config/commands/storage.properties	Mon Feb 10 19:08:50 2014 -0500
@@ -1,9 +1,6 @@
-bundles = com.redhat.thermostat.agent.core=${project.version}, \
-          com.redhat.thermostat.process=${project.version}, \
-          com.redhat.thermostat.agent.cli=${project.version}, \
+bundles = com.redhat.thermostat.process=${project.version}, \
           com.redhat.thermostat.common.command=${project.version}, \
-          com.redhat.thermostat.agent.command=${project.version}, \
-          com.redhat.thermostat.agent.proxy.common=${project.version}, \
+          com.redhat.thermostat.storage.cli=${project.version}, \
           org.apache.commons.codec=${commons-codec.osgi-version}, \
           org.jboss.netty=${netty.version}
 
--- a/distribution/pom.xml	Fri Dec 20 11:42:37 2013 +0100
+++ b/distribution/pom.xml	Mon Feb 10 19:08:50 2014 -0500
@@ -309,6 +309,11 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-storage-cli</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-agent-command</artifactId>
       <version>${project.version}</version>
     </dependency>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/pom.xml	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+
+ Copyright 2012, 2013 Red Hat, Inc.
+
+ This file is part of Thermostat.
+
+ Thermostat 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; either version 2, or (at your
+ option) any later version.
+
+ Thermostat 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 Thermostat; see the file COPYING.  If not see
+ <http://www.gnu.org/licenses />.
+
+ Linking this code with other modules is making a combined work
+ based on this code.  Thus, the terms and conditions of the GNU
+ General Public License cover the whole combination.
+
+ As a special exception, the copyright holders of this code give
+ you permission to link this code with independent modules to
+ produce an executable, regardless of the license terms of these
+ independent modules, and to copy and distribute the resulting
+ executable under terms of your choice, provided that you also
+ meet, for each linked independent module, the terms and conditions
+ of the license of that module.  An independent module is a module
+ which is not derived from or based on this code.  If you modify
+ this code, you may extend this exception to your version of the
+ library, but you are not obligated to do so.  If you do not wish
+ to do so, delete this exception statement from your version.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.redhat.thermostat</groupId>
+    <artifactId>thermostat-storage</artifactId>
+    <version>1.0.1-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>thermostat-storage-cli</artifactId>
+  <packaging>bundle</packaging>
+
+  <name>Thermostat Storage Command Line</name>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-osgi-process-handler</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-test</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-storage-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
+            <Bundle-Activator>com.redhat.thermostat.storage.cli.internal.Activator</Bundle-Activator>
+            <Bundle-SymbolicName>com.redhat.thermostat.storage.cli</Bundle-SymbolicName>
+            <Private-Package>
+              com.redhat.thermostat.storage.cli.internal,
+              com.redhat.thermostat.storage.cli.internal.locale,
+            </Private-Package>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/Activator.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.cli.internal;
+
+import java.util.Map;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+import com.redhat.thermostat.common.ExitStatus;
+import com.redhat.thermostat.common.MultipleServiceTracker;
+import com.redhat.thermostat.common.MultipleServiceTracker.Action;
+import com.redhat.thermostat.common.cli.CommandRegistry;
+import com.redhat.thermostat.common.cli.CommandRegistryImpl;
+import com.redhat.thermostat.shared.config.CommonPaths;
+
+public class Activator implements BundleActivator {
+
+    private CommandRegistry reg;
+    private MultipleServiceTracker tracker;
+
+    @Override
+    public void start(final BundleContext context) throws Exception {
+        reg = new CommandRegistryImpl(context);
+        
+        Class<?>[] deps = new Class<?>[] {
+                ExitStatus.class,
+                CommonPaths.class
+        };
+        tracker = new MultipleServiceTracker(context, deps, new Action() {
+            
+            @Override
+            public void dependenciesAvailable(Map<String, Object> services) {
+                ExitStatus exitStatus = (ExitStatus) services.get(ExitStatus.class.getName());
+                CommonPaths paths = (CommonPaths) services.get(CommonPaths.class.getName());
+                reg.registerCommand("storage", new StorageCommand(exitStatus, paths));
+            }
+
+            @Override
+            public void dependenciesUnavailable() {
+                reg.unregisterCommands();
+            }
+        });
+        tracker.open();
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        reg.unregisterCommands();
+        tracker.close();
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/DBConfig.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.cli.internal;
+
+
+/**
+ * Set of configuration options that the {@link StorageCommand} understands.
+ * Keys map to properties in $THERMOSTAT_HOME/storage/db.properties.
+ */
+public enum DBConfig {
+
+    /**
+     * The bind IP address.
+     */
+    BIND,
+    /**
+     * The port on which mongodb will be listening.
+     */
+    PORT,
+    /**
+     * Weather or not to start mongodb with SSL enabled.
+     */
+    SSL_ENABLE,
+    /**
+     * The PEM encoded SSL certificate + SSL key.
+     */
+    SSL_PEM_FILE,
+    /**
+     * The passphrase for the encrypted SSL key. Only used if the private key was encrypted.
+     */
+    SSL_KEY_PASSWORD,
+    
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/DBOptionParser.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.cli.internal;
+
+import com.redhat.thermostat.common.cli.Arguments;
+import com.redhat.thermostat.common.config.ThermostatOptionParser;
+import com.redhat.thermostat.shared.config.InvalidConfigurationException;
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.storage.cli.internal.locale.LocaleResources;
+
+public class DBOptionParser implements ThermostatOptionParser {
+    
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    private boolean quiet;
+    
+    private DBStartupConfiguration configuration;
+    
+    private Arguments args;
+
+    private DBArgs serviceAction;
+    
+    private boolean dryRun;
+    
+    public DBOptionParser(DBStartupConfiguration configuration, Arguments args) {
+        this.args = args;
+        this.configuration = configuration;
+    }
+    
+    @Override
+    public void parse() throws InvalidConfigurationException {
+
+        if (args.hasArgument(DBArgs.START.option)) {
+            serviceAction = DBArgs.START;
+        } else if (args.hasArgument(DBArgs.STOP.option)) {
+            serviceAction = DBArgs.STOP;
+        } else if (args.hasArgument(DBArgs.STATUS.option)) {
+            serviceAction = DBArgs.STATUS;
+        } else {
+            throw new InvalidConfigurationException(translator.localize(LocaleResources.COMMAND_STORAGE_ARGUMENT_REQUIRED));
+        }
+
+        if (args.hasArgument(DBArgs.DRY.option)) {
+            dryRun = true;
+        }
+        
+        if (args.hasArgument(DBArgs.QUIET.option)) {
+            quiet = true;
+        }
+        
+        // leave at the end, since it depends on the previous settings
+        String address = configuration.getBindIP();
+        long port = configuration.getPort();
+        configuration.setDBConnectionString("mongodb://" + address + ":" + port);
+    }
+
+    public boolean isDryRun() {
+        return dryRun;
+    }
+    
+    public DBArgs getAction() {
+        return serviceAction;
+    }
+
+    static enum DBArgs {
+                
+        DRY("dryRun"),
+        
+        HELP("help"),
+        
+        START("start"),
+        STOP("stop"),
+        
+        QUIET("quiet"),
+        
+        STATUS("status");
+        
+        private String option;
+        
+        DBArgs(String option) {
+            this.option = option;
+        }
+    }
+
+    public boolean isQuiet() {
+        return quiet;
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/DBStartupConfiguration.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.cli.internal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+import com.redhat.thermostat.shared.config.InvalidConfigurationException;
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.storage.cli.internal.locale.LocaleResources;
+import com.redhat.thermostat.storage.config.StartupConfiguration;
+
+public class DBStartupConfiguration implements StartupConfiguration {
+
+    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+
+    private File dbPath;
+    private File logFile;
+    private File pidFile;
+    private boolean sslEnabled = false;
+    private File sslPemFile;
+    private String sslKeyPassphrase;
+        
+    private long localPort;
+    
+    private String dbConnectionString;
+    
+    private String ip;
+        
+    public DBStartupConfiguration(File systemProperties, File userProperties,
+            File dbPath, File logFile, File pidFile) throws InvalidConfigurationException {
+        this.dbPath = dbPath;
+        this.logFile = logFile;
+        this.pidFile = pidFile;
+        readAndSetProperties(systemProperties, userProperties);
+    }
+    
+    public File getDBPath() {
+        return dbPath;
+    }
+    
+    public File getLogFile() {
+        return logFile;
+    }
+    
+    public File getPidFile() {
+        return pidFile;
+    }
+   
+    public void setPort(long localPort) {
+        this.localPort = localPort;
+    }
+    
+    public long getPort() {
+        return localPort;
+    }
+    
+    void setDBConnectionString(String dbConnectionString) {
+        this.dbConnectionString = dbConnectionString;
+    }
+    
+    @Override
+    public String getDBConnectionString() {
+        return dbConnectionString;
+    }
+
+    void setBindIP(String ip) {
+        this.ip = ip;
+    }
+    
+    public String getBindIP() {
+        return ip;
+    }
+
+    public boolean isSslEnabled() {
+        return sslEnabled;
+    }
+
+    void setSslEnabled(boolean sslEnabled) {
+        this.sslEnabled = sslEnabled;
+    }
+
+    /**
+     * 
+     * @return The file containing the server certificate and the private key in PEM format or null
+     *         if nothing was specified in $THERMOSTAT_HOME/storage/db.properties.
+     */
+    public File getSslPemFile() {
+        return sslPemFile;
+    }
+
+    void setSslPemFile(File sslPemFile) {
+        this.sslPemFile = sslPemFile;
+    }
+
+    /**
+     * 
+     * @return The passphrase for the encrypted server key or null if config was
+     *         not set.
+     */
+    public String getSslKeyPassphrase() {
+        return sslKeyPassphrase;
+    }
+
+    void setSslKeyPassphrase(String sslKeyPassphrase) {
+        this.sslKeyPassphrase = sslKeyPassphrase;
+    }
+    
+    private void readAndSetProperties(File systemPropertiesFile, File userPropertiesFile) throws InvalidConfigurationException {
+        
+        Properties systemProperties = new Properties();
+        try {
+            systemProperties.load(new FileInputStream(systemPropertiesFile));
+        } catch (IOException e) {
+            throw new InvalidConfigurationException(/* "Could not find system configuration", */e);
+        }
+        
+        Properties properties = new Properties(systemProperties);
+        try {
+            properties.load(new FileInputStream(userPropertiesFile));
+        } catch (IOException e) {
+            // that's fine. we will just rely on system properties
+        }
+
+        readAndSetProperties(properties);
+    }
+
+    private void readAndSetProperties(Properties properties) {
+        String port = properties.getProperty(DBConfig.PORT.name());
+        if (port != null) {
+            int localPort = Integer.parseInt(port);
+            setPort(localPort);
+        } else {
+            throw new InvalidConfigurationException(t.localize(LocaleResources.MISSING_PROPERTY, DBConfig.PORT.toString()));
+        }
+        
+        String ip = properties.getProperty(DBConfig.BIND.name());
+        if (ip != null) {
+            setBindIP(ip);
+        } else {
+            throw new InvalidConfigurationException(t.localize(LocaleResources.MISSING_PROPERTY, DBConfig.BIND.toString()));
+        }
+        
+        // optional config
+        String enableSSLConfig = properties.getProperty(DBConfig.SSL_ENABLE.name());
+        setSslEnabled(Boolean.parseBoolean(enableSSLConfig));
+        
+        String pemFile = properties.getProperty(DBConfig.SSL_PEM_FILE.name());
+        if (pemFile != null) {
+            setSslPemFile(new File(pemFile));
+        }
+        
+        String keyPassPhrase = properties.getProperty(DBConfig.SSL_KEY_PASSWORD.name());
+        setSslKeyPassphrase(keyPassPhrase);
+        
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/MongoProcessRunner.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.cli.internal;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.tools.ApplicationException;
+import com.redhat.thermostat.common.utils.LoggedExternalProcess;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.service.process.UnixProcessUtilities;
+import com.redhat.thermostat.shared.config.InvalidConfigurationException;
+import com.redhat.thermostat.shared.locale.LocalizedString;
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.storage.cli.internal.locale.LocaleResources;
+
+public class MongoProcessRunner {
+    
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+    private static final Logger logger = LoggingUtils.getLogger(MongoProcessRunner.class);
+
+    private static final String MONGO_PROCESS = "mongod";
+
+    private static final String [] MONGO_BASIC_ARGS = {
+        "mongod", "--quiet", "--fork", "--auth", "--nohttpinterface", "--bind_ip"
+    };
+
+    private static final String [] MONGO_SHUTDOWN_ARGS = {
+        "kill", "-s", "TERM"
+    };
+
+    private static final String NO_JOURNAL_ARGUMENT = "--nojournal";
+    static final String NO_JOURNAL_FIRST_VERSION = "1.9.2";
+
+    private DBStartupConfiguration configuration;
+    private boolean isQuiet;
+    private Integer pid;
+    
+    public MongoProcessRunner(DBStartupConfiguration configuration, boolean quiet) {
+        this.configuration = configuration;
+        this.isQuiet = quiet;
+    }
+
+    private boolean checkPid() {
+        File pidfile = configuration.getPidFile();
+        Charset charset = Charset.defaultCharset();
+        if (pidfile.exists()) {
+            try (BufferedReader reader = Files.newBufferedReader(pidfile.toPath(), charset)) {
+                pid = doGetPid(reader);
+            } catch (IOException ex) {
+                logger.log(Level.WARNING, "Exception while reading pid file", ex);
+                pid = null;
+            } catch (NumberFormatException e) {
+                logger.log(Level.WARNING, "Mongo PID file does not contain a valid PID", e);
+                pid = null;
+            }
+        } else {
+            pid = null;
+        }
+        return (pid != null);
+    }
+    
+    // package private for testing
+    Integer doGetPid(BufferedReader reader) throws IOException {
+        String line = reader.readLine();
+        // readLine() returns null on EOF
+        if (line == null || line.isEmpty()) {
+            return null;
+        }
+        else {
+            return Integer.parseInt(line);
+        }
+    }
+
+    private void deleteStalePidFile() {
+        pid = null;
+        LocalizedString message = translator.localize(LocaleResources.STALE_PID_FILE_NO_MATCHING_PROCESS, configuration.getPidFile().toString(), MONGO_PROCESS);
+        // Mongo didn't remove its PID file? Work around the issue. Log
+        // the event, remove the stale pid file and continue.
+        logger.log(Level.WARNING, message.getContents());
+        try {
+            Files.delete(configuration.getPidFile().toPath());
+        } catch (IOException benign) {
+            // ignore this benign error
+        }
+    }
+    
+    public boolean isStorageRunning() {
+        if (!checkPid()) {
+            return false;
+        }
+        
+        String processName = UnixProcessUtilities.getInstance().getProcessName(pid);
+        // TODO: check if we want mongos or mongod from the configs
+        boolean processIsRunning = processName != null && processName.equalsIgnoreCase(MONGO_PROCESS);
+        if (!processIsRunning) {
+            deleteStalePidFile();
+        }
+        return processIsRunning;
+    }
+    
+    /**
+     * Start the mongod process.
+     *
+     * @throws ApplicationException to signal an error starting the process. Callers should catch this and handle appropriately.
+     */
+    public void startService() throws IOException, InterruptedException,
+            ApplicationException, InvalidConfigurationException {
+
+        if (isStorageRunning()) {
+            LocalizedString message = translator.localize(LocaleResources.STORAGE_ALREADY_RUNNING_WITH_PID, String.valueOf(pid));
+            throw new StorageAlreadyRunningException(pid, message.getContents());
+        }
+        
+        String dbVersion;
+        try {
+            dbVersion = getDBVersion();
+        } catch (IOException e) {
+            LocalizedString message = translator.localize(
+                    LocaleResources.CANNOT_EXECUTE_PROCESS, MONGO_PROCESS);
+            throw new ApplicationException(message.getContents(), e);
+
+        }
+        List<String> commands = null;
+        commands = getStartupCommand(dbVersion);
+        
+        display(translator.localize(LocaleResources.STARTING_STORAGE_SERVER));
+        
+        LoggedExternalProcess process = new LoggedExternalProcess(commands);
+        int status = -1;
+        try {
+            status = process.runAndReturnResult();
+        } catch (ApplicationException ae) {
+            LocalizedString message = translator.localize(LocaleResources.CANNOT_EXECUTE_PROCESS, MONGO_PROCESS);
+            throw new ApplicationException(message.getContents(), ae);
+        }
+
+        Thread.sleep(500);
+
+        if (status == 0) {
+            if (!isStorageRunning()) {
+                status = -1;
+            }
+        }
+
+        if (status == 0) {
+            display(translator.localize(LocaleResources.SERVER_LISTENING_ON, configuration.getDBConnectionString()));
+            display(translator.localize(LocaleResources.LOG_FILE_AT, configuration.getLogFile().toString()));
+            display(translator.localize(LocaleResources.PID_IS, String.valueOf(pid)));
+            
+        } else {
+            // don't display anything when throwing an exception; whatever catches the exception will do so.
+            LocalizedString message = translator.localize(LocaleResources.CANNOT_START_SERVER,
+                             configuration.getDBPath().toString(),
+                             String.valueOf(status));
+            throw new StorageStartException(configuration.getDBPath(), status, message.getContents());
+        }
+    }
+    
+    /**
+     * Stop the mongod process.
+     *
+     * @throws ApplicationException to signal an error stopping the storage. Callers should catch this and handle appropriately.
+     */
+    public void stopService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
+ 
+        if (!isStorageRunning()) {
+            LocalizedString message = translator.localize(LocaleResources.STORAGE_NOT_RUNNING);
+            throw new StorageNotRunningException(message.getContents());
+        }
+        List<String> commands = new ArrayList<>(Arrays.asList(MONGO_SHUTDOWN_ARGS));
+        commands.add(String.valueOf(pid));
+
+        LoggedExternalProcess process = new LoggedExternalProcess(commands);
+        int status = process.runAndReturnResult();
+        if (status == 0) {
+            display(translator.localize(LocaleResources.SERVER_SHUTDOWN_COMPLETE, configuration.getDBPath().toString()));
+            display(translator.localize(LocaleResources.LOG_FILE_AT, configuration.getLogFile().toString()));
+            // all went well, make sure to remove pid file.
+            try {
+                Files.delete(configuration.getPidFile().toPath());
+            } catch (IOException e) {
+                // ignore
+            }
+        } else {
+            // don't display anything when throwing an exception; whatever catches the exception will do so.
+            LocalizedString message = translator.localize(LocaleResources.CANNOT_SHUTDOWN_SERVER,
+                    configuration.getDBPath().toString(),
+                    String.valueOf(status));
+            throw new StorageStopException(configuration.getDBPath(), status, message.getContents());
+        }
+    }
+    
+    List<String> getStartupCommand(String dbVersion) throws IOException, InvalidConfigurationException {
+        List<String> commands = new ArrayList<>(Arrays.asList(MONGO_BASIC_ARGS));
+        
+        if (dbVersion.compareTo(NO_JOURNAL_FIRST_VERSION) >= 0) {
+            commands.add(1, NO_JOURNAL_ARGUMENT);
+        }
+        commands.add(configuration.getBindIP());
+
+        commands.add("--dbpath");
+        commands.add(configuration.getDBPath().getCanonicalPath());
+
+        commands.add("--logpath");
+        commands.add(configuration.getLogFile().getCanonicalPath());
+
+        commands.add("--pidfilepath");
+        commands.add(configuration.getPidFile().getCanonicalPath());
+
+        commands.add("--port");
+        commands.add(Long.toString(configuration.getPort()));
+        
+        if (configuration.isSslEnabled()) {
+            // check for configuration which has a chance of working :)
+            if (configuration.getSslPemFile() == null) {
+                throw new InvalidConfigurationException(translator.localize(LocaleResources.MISSING_PEM));
+            } else if (configuration.getSslKeyPassphrase() == null) {
+                throw new InvalidConfigurationException(translator.localize(LocaleResources.MISSING_PASSPHRASE));
+            }
+            commands.add("--sslOnNormalPorts");
+            commands.add("--sslPEMKeyFile");
+            commands.add(configuration.getSslPemFile().getCanonicalPath());
+            commands.add("--sslPEMKeyPassword");
+            commands.add(configuration.getSslKeyPassphrase());
+        }
+        
+        return commands;
+    }
+ 
+    private String getDBVersion() throws IOException {
+        Process process = new ProcessBuilder(Arrays.asList("mongod", "--version")).start();
+        InputStream out = process.getInputStream();
+        return doGetDBVersion(out);
+    }
+    
+    // package private for testing
+    String doGetDBVersion(InputStream in) throws IOException {
+        // Default to no-journal first version if we can't parse the version
+        // output for some reason.
+        String versionString = NO_JOURNAL_FIRST_VERSION;
+        String firstLine = null;
+        try(InputStreamReader reader = new InputStreamReader(in)) {
+            BufferedReader bufReader = new BufferedReader(reader);
+            firstLine = bufReader.readLine();
+            int commaIdx = firstLine.indexOf(",", 12);
+            if (commaIdx != -1) {
+                versionString = firstLine.substring(12, commaIdx);
+            } else {
+                versionString = firstLine.substring(12);
+            }
+        } catch (Exception e) {
+            // catching Exception here in order to also catch potential NPEs or
+            // IndexOutOfBoundExceptions. If those conditions happen we fall
+            // back to the no journal first version.
+            logger.log(Level.WARNING, "Failed to parse mongodb version from: '" +
+                firstLine + "'. Assuming version " + NO_JOURNAL_FIRST_VERSION, e);
+        }
+        return versionString;
+    }
+
+    private void display(LocalizedString message) {
+        if (!isQuiet) {
+            System.out.println(message.getContents());
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/StalePidFileException.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+package com.redhat.thermostat.storage.cli.internal;
+
+import java.io.File;
+
+import com.redhat.thermostat.common.tools.ApplicationException;
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.storage.cli.internal.locale.LocaleResources;
+
+@SuppressWarnings("serial")
+public class StalePidFileException extends ApplicationException {
+
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    private final File pidFile;
+
+    public StalePidFileException(File pidFile) {
+        super(translator.localize(LocaleResources.STALE_PID_FILE, pidFile.toString()).getContents());
+        this.pidFile = pidFile;
+    }
+
+    public File getPidFile() {
+        return pidFile;
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/StorageAlreadyRunningException.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+package com.redhat.thermostat.storage.cli.internal;
+
+import com.redhat.thermostat.common.tools.ApplicationException;
+
+@SuppressWarnings("serial")
+public class StorageAlreadyRunningException extends ApplicationException {
+
+    private final int storagePid;
+
+    public StorageAlreadyRunningException(int pid, String message) {
+        super(message);
+        storagePid = pid;
+    }
+
+    public int getStoragePid() {
+        return storagePid;
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/StorageCommand.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.cli.internal;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.common.ExitStatus;
+import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand;
+import com.redhat.thermostat.common.cli.Arguments;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.tools.ApplicationException;
+import com.redhat.thermostat.common.tools.ApplicationState;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.shared.config.CommonPaths;
+import com.redhat.thermostat.shared.config.InvalidConfigurationException;
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.storage.cli.internal.locale.LocaleResources;
+
+public class StorageCommand extends AbstractStateNotifyingCommand {
+
+    private static final Logger logger = LoggingUtils.getLogger(StorageCommand.class);
+    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+
+    private DBStartupConfiguration configuration;
+    private DBOptionParser parser;
+    private final ExitStatus exitStatus;
+    private final CommonPaths paths;
+    
+    private MongoProcessRunner runner;
+    
+    public StorageCommand(ExitStatus exitStatus, CommonPaths paths) {
+        this.exitStatus = exitStatus;
+        this.paths = paths;
+    }
+    
+    private void parseArguments(Arguments args) throws InvalidConfigurationException {
+        File dbPath = paths.getUserStorageDirectory();
+        File logFile = paths.getUserStorageLogFile();
+        File pidFile = paths.getUserStoragePidFile();
+        File systemPropertyFile = paths.getSystemStorageConfigurationFile();
+        if (!systemPropertyFile.exists()) {
+            throw new InvalidConfigurationException(t.localize(LocaleResources.MISSING_DB_CONFIG, systemPropertyFile.toString()));
+        }
+        File userPropertyFile = paths.getUserStorageConfigurationFile();
+        // read everything that is in the configs
+        this.configuration = new DBStartupConfiguration(systemPropertyFile, userPropertyFile, dbPath, logFile, pidFile);
+        parser = new DBOptionParser(configuration, args);
+        parser.parse();
+    }
+    
+    @Override
+    public void run(CommandContext ctx) throws CommandException {
+        parseArgsAndRun(ctx);
+    }
+
+    private void parseArgsAndRun(CommandContext ctx)
+            throws InvalidConfigurationException {
+        parseArguments(ctx.getArguments());
+
+        // dry run means we don't do anything at all
+        if (parser.isDryRun()) return;
+        
+        runner = createRunner();
+        try {
+            switch (parser.getAction()) {
+            case START:
+                startService();
+                break;
+            case STOP:
+                stopService();
+                break;
+            case STATUS:
+                printServiceStatus(ctx);
+                break;
+             default:
+                break;
+            }
+            getNotifier().fireAction(ApplicationState.SUCCESS, configuration.getDBConnectionString());
+        } catch (InvalidConfigurationException e) {
+            // rethrow
+            throw e;
+        } catch (ApplicationException e) {
+            logger.log(Level.WARNING, e.getMessage());
+            getNotifier().fireAction(ApplicationState.FAIL, e);
+        } catch (Exception e) {
+            logger.log(Level.WARNING, e.getMessage(), e);
+            getNotifier().fireAction(ApplicationState.FAIL, e);
+        }
+    }
+    
+    private void startService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
+        try {
+            createNeededDirectories();
+            runner.startService();
+        } catch (ApplicationException | InvalidConfigurationException | IOException e) {
+            // something went wrong set status appropriately. This makes sure
+            // that the JVM exits with this status.
+            exitStatus.setExitStatus(ExitStatus.EXIT_ERROR);
+            // rethrow
+            throw e;
+        }
+        getNotifier().fireAction(ApplicationState.START, configuration.getDBConnectionString());
+    }
+    
+    private void createNeededDirectories() throws InvalidConfigurationException {
+        File[] requiredDirectories = new File[] {
+                configuration.getDBPath(),
+        };
+
+        for (File directory : requiredDirectories) {
+            if (!directory.isDirectory() && !directory.mkdirs()) {
+                throw new InvalidConfigurationException(t.localize(LocaleResources.MISSING_DB_DIR));
+            }
+        }
+
+        File[] requiredFiles = new File[] {
+                configuration.getLogFile(),
+                configuration.getPidFile(),
+        };
+
+        for (File file : requiredFiles) {
+            File directory = file.getParentFile();
+            if (!directory.isDirectory() && !directory.mkdirs()) {
+                throw new InvalidConfigurationException(t.localize(LocaleResources.MISSING_DB_DIR));
+            }
+        }
+    }
+    
+    private void stopService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
+        try {
+            check();
+            runner.stopService();
+        } catch (ApplicationException | InvalidConfigurationException | InterruptedException | IOException e) {
+            // something went wrong set status appropriately. This makes sure
+            // that the JVM exits with this status.
+            exitStatus.setExitStatus(ExitStatus.EXIT_ERROR);
+            throw e;
+        }
+        getNotifier().fireAction(ApplicationState.STOP);
+    }
+    
+    private void check() throws InvalidConfigurationException {
+        if (!configuration.getDBPath().exists() ||
+            !configuration.getLogFile().getParentFile().exists() ||
+            !configuration.getPidFile().getParentFile().exists())
+        {
+            throw new InvalidConfigurationException(t.localize(LocaleResources.MISSING_DB_DIR));
+        }
+    }
+
+    private void printServiceStatus(CommandContext ctx) {
+        if (runner.isStorageRunning()) {
+            ctx.getConsole().getOutput().println(t.localize(LocaleResources.STORAGE_RUNNING).getContents());
+        } else {
+            ctx.getConsole().getOutput().println(t.localize(LocaleResources.STORAGE_NOT_RUNNING).getContents());
+        }
+    }
+    
+    MongoProcessRunner createRunner() {
+        return new MongoProcessRunner(configuration, parser.isQuiet());
+    }
+
+    public DBStartupConfiguration getConfiguration() {
+        return configuration;
+    }
+
+    @Override
+    public boolean isStorageRequired() {
+        return false;
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/StorageNotRunningException.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.cli.internal;
+
+import com.redhat.thermostat.common.tools.ApplicationException;
+
+@SuppressWarnings("serial")
+public class StorageNotRunningException extends ApplicationException {
+
+    public StorageNotRunningException(String message) {
+        super(message);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/StorageStartException.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+package com.redhat.thermostat.storage.cli.internal;
+
+import java.io.File;
+
+import com.redhat.thermostat.common.tools.ApplicationException;
+
+@SuppressWarnings("serial")
+public class StorageStartException extends ApplicationException {
+
+    private final File dbFile;
+    private final int status;
+
+    public StorageStartException(File dbPath, int status, String message) {
+        super(message);
+        this.dbFile = dbPath;
+        this.status = status;
+    }
+
+    public File getDbPath() {
+        return dbFile;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/StorageStopException.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+package com.redhat.thermostat.storage.cli.internal;
+
+import java.io.File;
+
+import com.redhat.thermostat.common.tools.ApplicationException;
+
+@SuppressWarnings("serial")
+public class StorageStopException extends ApplicationException {
+
+    private final File dbConfig;
+    private final int status;
+
+    public StorageStopException(File dbConfig, int status, String message) {
+        super(message);
+        this.dbConfig = dbConfig;
+        this.status = status;
+    }
+
+    public File getDbConfig() {
+        return dbConfig;
+    }
+
+    public int getStatus() {
+        return status;
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/main/java/com/redhat/thermostat/storage/cli/internal/locale/LocaleResources.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.cli.internal.locale;
+
+import com.redhat.thermostat.shared.locale.Translate;
+
+public enum LocaleResources {
+
+    COMMAND_STORAGE_ARGUMENT_REQUIRED,
+
+    STORAGE_ALREADY_RUNNING_WITH_PID,
+    SERVER_SHUTDOWN_COMPLETE,
+    LOG_FILE_AT,
+    CANNOT_START_SERVER,
+    CANNOT_SHUTDOWN_SERVER,
+    STALE_PID_FILE,
+    STALE_PID_FILE_NO_MATCHING_PROCESS,
+    STARTING_STORAGE_SERVER,
+    CANNOT_EXECUTE_PROCESS,
+    SERVER_LISTENING_ON,
+    PID_IS,
+    MISSING_PROPERTY,
+    MISSING_PEM,
+    MISSING_PASSPHRASE,
+    MISSING_DB_CONFIG,
+    MISSING_DB_DIR,
+    STORAGE_RUNNING,
+    STORAGE_NOT_RUNNING
+    ;
+
+    static final String RESOURCE_BUNDLE = "com.redhat.thermostat.storage.cli.internal.strings";
+
+    public static Translate<LocaleResources> createLocalizer() {
+        return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class);
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/main/resources/com/redhat/thermostat/storage/cli/internal/strings.properties	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,20 @@
+COMMAND_STORAGE_ARGUMENT_REQUIRED = either --start or --stop must be given
+
+STORAGE_ALREADY_RUNNING_WITH_PID = Storage is already running with pid {0}
+SERVER_SHUTDOWN_COMPLETE = server shutdown complete: {0}
+LOG_FILE_AT = log file is here: {0}
+CANNOT_START_SERVER = cannot start server {0}, exit status: {1}. Please check that your configuration is valid
+CANNOT_SHUTDOWN_SERVER = cannot shutdown server {0}, exit status: {1}. Please check that your configuration is valid
+STALE_PID_FILE = stale pid file: {0}
+STALE_PID_FILE_NO_MATCHING_PROCESS = A stale pid file ({0}) is present but there is no matching {1} process. Removing stale pid file.
+STARTING_STORAGE_SERVER = starting storage server...
+CANNOT_EXECUTE_PROCESS = can not execute {0} process. is it installed?
+SERVER_LISTENING_ON = server listening on ip: {0}
+PID_IS = pid: {0}
+MISSING_PROPERTY = {0} property missing
+MISSING_PEM = No SSL PEM file specified!
+MISSING_PASSPHRASE = No SSL key passphrase set!
+MISSING_DB_CONFIG = can't access database configuration file {0}
+MISSING_DB_DIR = database directories do not exist...
+STORAGE_RUNNING = Storage is running
+STORAGE_NOT_RUNNING = Storage is not running
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/test/java/com/redhat/thermostat/storage/cli/internal/ActivatorTest.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.cli.internal;
+
+import static com.redhat.thermostat.testutils.Asserts.assertCommandIsRegistered;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.common.ExitStatus;
+import com.redhat.thermostat.shared.config.CommonPaths;
+import com.redhat.thermostat.testutils.StubBundleContext;
+
+public class ActivatorTest {
+
+    @Test
+    public void verifyActivatorRegistersCommands() throws Exception {        
+        StubBundleContext bundleContext = new StubBundleContext();
+
+        ExitStatus exitStatus = mock(ExitStatus.class);
+        CommonPaths paths = mock(CommonPaths.class);
+        bundleContext.registerService(ExitStatus.class, exitStatus, null);
+        bundleContext.registerService(CommonPaths.class, paths, null);
+        
+        Activator activator = new Activator();
+
+        assertEquals(0, bundleContext.getServiceListeners().size());
+        
+        activator.start(bundleContext);
+        
+        assertEquals(2, bundleContext.getServiceListeners().size());
+        
+        assertCommandIsRegistered(bundleContext, "storage", StorageCommand.class);
+
+        activator.stop(bundleContext);
+
+        assertEquals(0, bundleContext.getServiceListeners().size());
+        assertEquals(2, bundleContext.getAllServices().size());
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/test/java/com/redhat/thermostat/storage/cli/internal/DBStartupConfigurationTest.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.cli.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.shared.config.InvalidConfigurationException;
+import com.redhat.thermostat.storage.cli.internal.DBStartupConfiguration;
+
+public class DBStartupConfigurationTest {
+    
+    private File dbLogFile;
+    private File dbPidFile;
+    private File dbPath;
+    
+    @Before
+    public void setUp() {
+        dbLogFile = new File("db.log");
+        dbPidFile = new File("db.pid");
+        dbPath = new File("somepath");
+    }
+    
+    @After
+    public void tearDown() {
+        dbLogFile = null;
+        dbPidFile = null;
+        dbPath = null;
+    }
+
+    @Test
+    public void canGetConfigFromPropertiesFile() throws Exception {
+        File dbProps = new File(this.getClass().getResource("/testDbConfig.properties").getFile());
+        File canNotBeFoundFile = new File("");
+        DBStartupConfiguration dbConfig = new DBStartupConfiguration(dbProps, canNotBeFoundFile, dbPath, dbLogFile, dbPidFile);
+        
+        assertEquals(dbLogFile.getAbsolutePath(), dbConfig.getLogFile().getAbsolutePath());
+        assertEquals(dbPidFile.getAbsolutePath(), dbConfig.getPidFile().getAbsolutePath());
+        assertEquals(dbPath.getAbsolutePath(), dbConfig.getDBPath().getAbsolutePath());
+        assertEquals("127.0.0.1", dbConfig.getBindIP());
+        assertEquals(27518, dbConfig.getPort());
+        assertEquals(true, dbConfig.isSslEnabled());
+        assertEquals("/path/to/some/pem/file.pem", dbConfig.getSslPemFile().getAbsolutePath());
+        assertEquals("somepassword", dbConfig.getSslKeyPassphrase());
+    }
+    
+    @Test
+    public void canGetConfigFromPropertiesFile2() throws Exception {
+        File dbProps = new File(this.getClass().getResource("/testDbConfig2.properties").getFile());
+        File canNotBeFoundFile = new File("");
+        DBStartupConfiguration dbConfig = new DBStartupConfiguration(dbProps, canNotBeFoundFile, dbPath, dbLogFile, dbPidFile);
+        
+        assertEquals(dbLogFile.getAbsolutePath(), dbConfig.getLogFile().getAbsolutePath());
+        assertEquals(dbPidFile.getAbsolutePath(), dbConfig.getPidFile().getAbsolutePath());
+        assertEquals(dbPath.getAbsolutePath(), dbConfig.getDBPath().getAbsolutePath());
+        assertEquals("127.0.0.1", dbConfig.getBindIP());
+        assertEquals(27518, dbConfig.getPort());
+        assertEquals(false, dbConfig.isSslEnabled());
+        assertNull(dbConfig.getSslPemFile());
+        assertNull(dbConfig.getSslKeyPassphrase());
+    }
+    
+    @Test
+    public void missingBindThrowsConfigException() throws Exception {
+        File dbProps = new File(this.getClass().getResource("/brokenDbConfig.properties").getFile());
+        File canNotBeFoundFile = new File("");
+        try {
+            new DBStartupConfiguration(dbProps, canNotBeFoundFile, dbPath, dbLogFile, dbPidFile);
+            fail("BIND was not specified in properties file");
+        } catch (InvalidConfigurationException e) {
+            assertEquals("BIND property missing", e.getMessage());
+        }
+    }
+    
+    @Test
+    public void missingPortThrowsConfigException() throws Exception {
+        File dbProps = new File(this.getClass().getResource("/brokenDbConfig2.properties").getFile());
+        File canNotBeFoundFile = new File("");
+        try {
+            new DBStartupConfiguration(dbProps, canNotBeFoundFile, dbPath, dbLogFile, dbPidFile);
+            fail("PORT was not specified in properties file");
+        } catch (InvalidConfigurationException e) {
+            assertEquals("PORT property missing", e.getMessage());
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/test/java/com/redhat/thermostat/storage/cli/internal/MongoProcessRunnerTest.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,162 @@
+package com.redhat.thermostat.storage.cli.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.shared.config.InvalidConfigurationException;
+import com.redhat.thermostat.storage.cli.internal.DBStartupConfiguration;
+import com.redhat.thermostat.storage.cli.internal.MongoProcessRunner;
+
+public class MongoProcessRunnerTest {
+
+    private MongoProcessRunner runner;
+    private DBStartupConfiguration config;
+    private static final String NO_JOURNAL_MONGODB_VERSION = "2.0.0";
+    private static final String JOURNAL_MONGODB_VERSION = "1.8.0";
+    private static final String BIND_IP = "127.0.0.1";
+    private static final long PORT = 12456;
+    
+    @Before
+    public void setUp() {
+        File dbPath = new File("/path/to/db");
+        File logPath = new File("/path/to/log");
+        File pidFile = new File("/path/to/pid");
+        config = mock(DBStartupConfiguration.class);
+        when(config.getBindIP()).thenReturn(BIND_IP);
+        when(config.getPort()).thenReturn(PORT);
+        when(config.getDBPath()).thenReturn(dbPath);
+        when(config.getLogFile()).thenReturn(logPath);
+        when(config.getPidFile()).thenReturn(pidFile);
+        runner = new MongoProcessRunner(config, false);
+    }
+    
+    @After
+    public void tearDown() {
+        runner = null;
+        config = null;
+    }
+    
+    @Test
+    public void testCommandArgumentsWithJournalVersion() throws Exception {
+        String[] expected = { "mongod", "--nojournal", "--quiet", "--fork",
+                "--auth", "--nohttpinterface", "--bind_ip", config.getBindIP(),
+                "--dbpath", config.getDBPath().getCanonicalPath(), "--logpath",
+                config.getLogFile().getCanonicalPath(), "--pidfilepath",
+                config.getPidFile().getCanonicalPath(), "--port",
+                Long.toString(config.getPort()) };
+        List<String> cmds = runner.getStartupCommand(NO_JOURNAL_MONGODB_VERSION);
+        String[] actual = cmds.toArray(new String[0]);
+        verifyEquals(expected, actual);
+    }
+    
+    @Test
+    public void testCommandArgumentsWithNoJournalVersion() throws Exception {
+        String[] expected = { "mongod", "--quiet", "--fork", "--auth",
+                "--nohttpinterface", "--bind_ip", config.getBindIP(),
+                "--dbpath", config.getDBPath().getCanonicalPath(), "--logpath",
+                config.getLogFile().getCanonicalPath(), "--pidfilepath",
+                config.getPidFile().getCanonicalPath(), "--port",
+                Long.toString(config.getPort()) };
+        List<String> cmds = runner.getStartupCommand(JOURNAL_MONGODB_VERSION);
+        String[] actual = cmds.toArray(new String[0]);
+        verifyEquals(expected, actual);
+    }
+    
+    @Test
+    public void testCommandArgumentsWithSSLEnabled() throws Exception {
+        when(config.isSslEnabled()).thenReturn(true);
+        File pemFile = new File("/path/to/cert_and_key.pem");
+        when(config.getSslPemFile()).thenReturn(pemFile);
+        when(config.getSslKeyPassphrase()).thenReturn("non-null");
+        String[] expected = { "mongod", "--quiet", "--fork", "--auth",
+                "--nohttpinterface", "--bind_ip", config.getBindIP(),
+                "--dbpath", config.getDBPath().getCanonicalPath(), "--logpath",
+                config.getLogFile().getCanonicalPath(), "--pidfilepath",
+                config.getPidFile().getCanonicalPath(), "--port",
+                Long.toString(config.getPort()), "--sslOnNormalPorts",
+                "--sslPEMKeyFile", config.getSslPemFile().getCanonicalPath(),
+                "--sslPEMKeyPassword", config.getSslKeyPassphrase()
+        };
+        List<String> cmds = runner.getStartupCommand(JOURNAL_MONGODB_VERSION);
+        String[] actual = cmds.toArray(new String[0]);
+        verifyEquals(expected, actual);
+    }
+    
+    @Test
+    public void testCommandArgumentsWithSSLEnabledThrowsInvalidConfigException() throws IOException {
+        when(config.isSslEnabled()).thenReturn(true);
+        // PEM file can't be null when SSL == true
+        when(config.getSslPemFile()).thenReturn(null);
+        try {
+            runner.getStartupCommand(JOURNAL_MONGODB_VERSION);
+            fail("Should have thrown exception!");
+        } catch (InvalidConfigurationException e) {
+            assertEquals("No SSL PEM file specified!", e.getMessage());
+        }
+        // Key password can't be null when SSL == true and keyfile present
+        File pemFile = new File("/path/to/ssl.pem");
+        when(config.getSslPemFile()).thenReturn(pemFile);
+        when(config.getSslKeyPassphrase()).thenReturn(null);
+        try {
+            runner.getStartupCommand(JOURNAL_MONGODB_VERSION);
+            fail("Should have thrown exception!");
+        } catch (InvalidConfigurationException e) {
+            assertEquals("No SSL key passphrase set!", e.getMessage());
+        }
+    }
+    
+    @Test
+    public void testCheckPidNull() throws IOException {
+        BufferedReader reader = mock(BufferedReader.class);
+        when(reader.readLine()).thenReturn(null);
+        Integer pid = runner.doGetPid(reader);
+        assertNull(pid);
+    }
+    
+    @Test
+    public void canGetVersionFromVersionCmdOutputV22() throws IOException {
+        String versionOutput = "db version v2.2.4, pdfile version 4.5\n" +
+                               "Mon Aug 26 17:13:45 git version: nogitversion";
+        ByteArrayInputStream in = new ByteArrayInputStream(versionOutput.getBytes());
+        String version = runner.doGetDBVersion(in);
+        assertEquals("2.2.4", version);
+    }
+    
+    @Test
+    public void canGetVersionFromVersionCmdOutputV24() throws IOException {
+        String versionOutput = "db version v2.4.5\n" +
+                               "Mon Aug 26 18:01:28.404 git version: nogitversion";
+        ByteArrayInputStream in = new ByteArrayInputStream(versionOutput.getBytes());
+        String version = runner.doGetDBVersion(in);
+        assertEquals("2.4.5", version);
+    }
+    
+    @Test
+    public void canProceedIfGetDbVersionThrowsException() throws IOException {
+        String versionOutput = "foo\n" +
+                               "Mon Aug 26 18:01:28.404 git version: nogitversion";
+        ByteArrayInputStream in = new ByteArrayInputStream(versionOutput.getBytes());
+        String version = runner.doGetDBVersion(in);
+        assertEquals(MongoProcessRunner.NO_JOURNAL_FIRST_VERSION, version);
+    }
+
+    private void verifyEquals(String[] expected, String[] actual) {
+        assertEquals(expected.length, actual.length);
+        for (int i=0; i < expected.length; i++) {
+            assertEquals(expected[i], actual[i]);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/test/java/com/redhat/thermostat/storage/cli/internal/StorageCommandTest.java	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,336 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.cli.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+
+import junit.framework.Assert;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+import com.redhat.thermostat.common.ExitStatus;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.SimpleArguments;
+import com.redhat.thermostat.common.tools.ApplicationException;
+import com.redhat.thermostat.common.tools.ApplicationState;
+import com.redhat.thermostat.shared.config.CommonPaths;
+import com.redhat.thermostat.shared.config.InvalidConfigurationException;
+import com.redhat.thermostat.shared.config.internal.CommonPathsImpl;
+import com.redhat.thermostat.storage.cli.internal.DBConfig;
+import com.redhat.thermostat.storage.cli.internal.DBStartupConfiguration;
+import com.redhat.thermostat.storage.cli.internal.MongoProcessRunner;
+import com.redhat.thermostat.storage.cli.internal.StorageCommand;
+import com.redhat.thermostat.testutils.TestUtils;
+
+public class StorageCommandTest {
+    
+    private static final String PORT = "27518";
+    private static final String BIND = "127.0.0.1";
+    private static final String DB = "data/db";
+
+    private String tmpDir;
+    private ExitStatus exitStatus;
+    private CommonPaths paths;
+    
+    @Before
+    public void setup() {
+        exitStatus = mock(ExitStatus.class);
+        // need to create a dummy config file for the test
+        try {
+            Properties props = new Properties();
+            
+            props.setProperty(DBConfig.BIND.name(), BIND);
+            props.setProperty(DBConfig.PORT.name(), PORT);
+
+            tmpDir = TestUtils.setupStorageConfigs(props);
+        } catch (IOException e) {
+            Assert.fail("cannot setup tests: " + e);
+        }
+
+        paths = mock(CommonPathsImpl.class);
+        File baseDir = new File(tmpDir);
+        File userRuntimeDir = new File(baseDir, "run");
+        File userDataDir = new File(baseDir, "data");
+        File logsDir = new File(baseDir, "logs");
+        File confDir = new File(baseDir, "etc");
+
+        when(paths.getUserThermostatHome()).thenReturn(baseDir);
+        when(paths.getUserRuntimeDataDirectory()).thenReturn(userRuntimeDir);
+        when(paths.getUserPersistentDataDirectory()).thenReturn(userDataDir);
+        when(paths.getUserConfigurationDirectory()).thenReturn(confDir);
+        when(paths.getUserStorageDirectory()).thenCallRealMethod();
+        when(paths.getUserStorageConfigurationFile()).thenCallRealMethod();
+        when(paths.getUserLogDirectory()).thenReturn(logsDir);
+        when(paths.getUserStorageLogFile()).thenCallRealMethod();
+        when(paths.getUserStoragePidFile()).thenCallRealMethod();
+
+        when(paths.getSystemThermostatHome()).thenReturn(baseDir);
+        when(paths.getSystemConfigurationDirectory()).thenCallRealMethod();
+        when(paths.getSystemStorageConfigurationFile()).thenCallRealMethod();
+    }
+    
+    @After
+    public void tearDown() {
+        exitStatus = null;
+    }
+    
+    @Test
+    public void testConfig() throws CommandException {
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("quiet", null);
+        args.addArgument("start", null);
+        args.addArgument("dryRun", null);
+        CommandContext ctx = mock(CommandContext.class);
+        when(ctx.getArguments()).thenReturn(args);
+
+        StorageCommand service = new StorageCommand(exitStatus, paths) {
+            @Override
+            MongoProcessRunner createRunner() {
+                throw new AssertionError("dry run should never create an actual runner");
+            }
+        };
+
+        service.run(ctx);
+        
+        DBStartupConfiguration conf = service.getConfiguration();
+        
+        Assert.assertEquals(tmpDir + DB, conf.getDBPath().getPath());
+        Assert.assertEquals(Integer.parseInt(PORT), conf.getPort());
+        Assert.assertEquals("mongodb://" + BIND + ":" + PORT , conf.getDBConnectionString());
+    }
+    
+    private StorageCommand prepareService(boolean startSuccess) throws IOException,
+            InterruptedException, InvalidConfigurationException, ApplicationException
+    {
+        final MongoProcessRunner runner = mock(MongoProcessRunner.class);
+        if (!startSuccess) {
+           doThrow(new ApplicationException("mock exception")).when(runner).startService();
+        }
+        
+        // TODO: stop not tested yet, but be sure it's not called from the code
+        doThrow(new ApplicationException("mock exception")).when(runner).stopService();
+        
+        StorageCommand service = new StorageCommand(exitStatus, paths) {
+            @Override
+            MongoProcessRunner createRunner() {
+                return runner;
+            }
+        };
+        
+        return service;
+    }
+    
+    @Test
+    public void testListeners() throws InterruptedException, IOException, ApplicationException, InvalidConfigurationException, CommandException
+    {
+        StorageCommand service = prepareService(true);
+        
+        final CountDownLatch latch = new CountDownLatch(2);
+        
+        final boolean[] result = new boolean[2];
+        service.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
+            @SuppressWarnings("incomplete-switch")
+            @Override
+            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                case FAIL:
+                    result[0] = false;
+                    latch.countDown();
+                    latch.countDown();
+                    break;
+                    
+                case SUCCESS:
+                    result[0] = true;
+                    latch.countDown();
+                    break;
+
+                case START:
+                    result[1] = true;
+                    latch.countDown();
+                    break;
+                }
+            }
+        });
+        
+        service.run(prepareContext());
+        latch.await();
+        
+        Assert.assertTrue(result[0]);
+        Assert.assertTrue(result[1]);
+    }
+    
+    @Test
+    public void testListenersFail() throws InterruptedException, IOException, ApplicationException, CommandException, InvalidConfigurationException
+    {
+        StorageCommand service = prepareService(false);
+        
+        final CountDownLatch latch = new CountDownLatch(1);
+        final boolean[] result = new boolean[1];
+        service.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
+            @SuppressWarnings("incomplete-switch")
+            @Override
+            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                case FAIL:
+                    result[0] = true;
+                    break;
+                    
+                case SUCCESS:
+                    result[0] = false;
+                    break;
+                }
+                latch.countDown();
+            }
+        });
+        
+        service.run(prepareContext());
+        latch.await();
+        
+        Assert.assertTrue(result[0]);
+    }
+    
+    @Test
+    public void exceptionSetsExitStatusOnFailure() throws Exception {
+        this.exitStatus = new ExitStatus() {
+            
+            private int exitStatus = -1;
+            
+            @Override
+            public void setExitStatus(int newExitStatus) {
+                exitStatus = newExitStatus;
+            }
+            
+            @Override
+            public int getExitStatus() {
+                return exitStatus;
+            }
+        };
+        assertEquals(-1, this.exitStatus.getExitStatus());
+        StorageCommand command = prepareService(false);
+        final CountDownLatch latch = new CountDownLatch(1);
+        final boolean[] result = new boolean[1];
+        command.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
+            @SuppressWarnings("incomplete-switch")
+            @Override
+            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                case FAIL:
+                    result[0] = true;
+                    break;
+                    
+                case SUCCESS:
+                    result[0] = false;
+                    break;
+                }
+                latch.countDown();
+            }
+        });
+        command.run(prepareContext());
+        latch.await();
+        // should have failed
+        assertTrue(result[0]);
+        assertEquals(ExitStatus.EXIT_ERROR, this.exitStatus.getExitStatus());
+    }
+    
+    @Test
+    public void exitStatusRemainsUntouchedOnSuccess() throws Exception {
+        this.exitStatus = new ExitStatus() {
+            
+            private int exitStatus = -1;
+            
+            @Override
+            public void setExitStatus(int newExitStatus) {
+                exitStatus = newExitStatus;
+            }
+            
+            @Override
+            public int getExitStatus() {
+                return exitStatus;
+            }
+        };
+        StorageCommand command = prepareService(true);
+        final CountDownLatch latch = new CountDownLatch(1);
+        final boolean[] result = new boolean[1];
+        command.getNotifier().addActionListener(new ActionListener<ApplicationState>() {
+            @SuppressWarnings("incomplete-switch")
+            @Override
+            public void actionPerformed(ActionEvent<ApplicationState> actionEvent) {
+                switch (actionEvent.getActionId()) {
+                case FAIL:
+                    result[0] = false;
+                    break;
+                    
+                case SUCCESS:
+                    result[0] = true;
+                    break;
+                }
+                latch.countDown();
+            }
+        });
+        command.run(prepareContext());
+        latch.await();
+        // should have worked
+        assertTrue(result[0]);
+        // this impl of ExitStatus has a default value of -1
+        assertEquals(-1, this.exitStatus.getExitStatus());
+    }
+
+    private CommandContext prepareContext() {
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("quiet", "--quiet");
+        args.addArgument("start", "--start");
+        CommandContext ctx = mock(CommandContext.class);
+        when(ctx.getArguments()).thenReturn(args);
+        return ctx;
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/test/resources/brokenDbConfig.properties	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,2 @@
+PORT=27518
+# missing BIND
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/test/resources/brokenDbConfig2.properties	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,2 @@
+#PORT=27518
+BIND=127.0.0.1
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/test/resources/testDbConfig.properties	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,7 @@
+PORT=27518
+BIND=127.0.0.1
+
+## SSL config fluff
+SSL_ENABLE=true
+SSL_PEM_FILE=/path/to/some/pem/file.pem
+SSL_KEY_PASSWORD=somepassword
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/cli/src/test/resources/testDbConfig2.properties	Mon Feb 10 19:08:50 2014 -0500
@@ -0,0 +1,7 @@
+PORT=27518
+BIND=127.0.0.1
+
+## SSL config fluff
+#SSL_ENABLE=true
+#SSL_PEM_FILE=/path/to/some/pem/file.pem
+#SSL_KEY_PASSWORD=somepassword
\ No newline at end of file
--- a/storage/pom.xml	Fri Dec 20 11:42:37 2013 +0100
+++ b/storage/pom.xml	Mon Feb 10 19:08:50 2014 -0500
@@ -60,6 +60,7 @@
 
   <modules>
     <module>core</module>
+    <module>cli</module>
     <module>mongo</module>
   </modules>