changeset 899:40be72a1345c

Add SSL (option) to storage command. This is the first step towards adding TLS to mongo<-->agent|client|webservice communication. It adds appropriate options to the mongod command if thermostat is so configured. In order to test this better, I've refactored MongoProcessRunner a bit. Also, DBStartupConfiguration does the parsing of the db.properties file now. As such it's also easier to test. Tests have been added for it too. Finally, I've renamed the c.r.t.agent.cli.db package to c.r.t.agent.cli.impl.db to better reflect in the package name that it is an internal package. Note that you'll need to have a mongod available on your system which understands --ssl* options in order to be able to fire mongod up with SSL enabled. These options are only added if appropriate config is in place in db.properties. Since it defaults to false, it should be OK to be pushed now without breaking existing behaviour. What comes next is adding support on agent/client/webservice side so that they can talk SSL over the mongodb channel as well. Reviewed-by: vanaltj, rkennke Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2012-December/004940.html PR1243
author Severin Gehwolf <sgehwolf@redhat.com>
date Fri, 21 Dec 2012 14:19:06 +0100
parents 5a2d3c43b551
children 916ea399eb6b
files agent/cli/pom.xml agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBConfig.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBOptionParser.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBStartupConfiguration.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/MongoProcessRunner.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/StorageAlreadyRunningException.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/StorageStartException.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/StorageStopException.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/Activator.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.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/StorageCommand.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/StorageStartException.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageStopException.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ActivatorTest.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/AgentApplicationTest.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/StorageCommandTest.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/config/db.properties
diffstat 32 files changed, 1639 insertions(+), 1158 deletions(-) [+]
line wrap: on
line diff
--- a/agent/cli/pom.xml	Tue Jan 08 19:22:32 2013 +0100
+++ b/agent/cli/pom.xml	Fri Dec 21 14:19:06 2012 +0100
@@ -106,7 +106,7 @@
             <Private-Package>
               com.redhat.thermostat.agent.cli.impl,
               com.redhat.thermostat.agent.cli.impl.locale,
-              com.redhat.thermostat.agent.cli.db,
+              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/db/DBConfig.java	Tue Jan 08 19:22:32 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,49 +0,0 @@
-/*
- * Copyright 2012 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.db;
-
-import com.redhat.thermostat.agent.cli.impl.StorageCommand;
-
-/**
- * Set of configuration option that the {@link StorageCommand} understands.
- */
-public enum DBConfig {
-
-    BIND,
-    PORT,
-    
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBOptionParser.java	Tue Jan 08 19:22:32 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,121 +0,0 @@
-/*
- * Copyright 2012 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.db;
-
-import com.redhat.thermostat.common.cli.Arguments;
-import com.redhat.thermostat.agent.cli.impl.locale.LocaleResources;
-import com.redhat.thermostat.common.config.InvalidConfigurationException;
-import com.redhat.thermostat.common.config.ThermostatOptionParser;
-import com.redhat.thermostat.common.locale.Translate;
-import com.redhat.thermostat.common.tools.ApplicationState;
-
-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 {
-            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 ApplicationState getAction() {
-        return serviceAction.state;
-    }
-
-    static enum DBArgs {
-                
-        DRY("dryRun", ApplicationState.NONE),
-        
-        HELP("help", ApplicationState.HELP),
-        
-        START("start", ApplicationState.START),
-        STOP("stop", ApplicationState.STOP),
-        
-        QUIET("quiet", ApplicationState.NONE);
-        
-        private String option;
-        private ApplicationState state;
-        
-        DBArgs(String option, ApplicationState state) {
-            this.option = option;
-            this.state = state;
-        }
-    }
-
-    public boolean isQuiet() {
-        return quiet;
-    }
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/DBStartupConfiguration.java	Tue Jan 08 19:22:32 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,99 +0,0 @@
-/*
- * Copyright 2012 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.db;
-
-import java.io.File;
-
-import com.redhat.thermostat.common.config.ConfigUtils;
-import com.redhat.thermostat.common.config.InvalidConfigurationException;
-import com.redhat.thermostat.storage.config.StartupConfiguration;
-
-public class DBStartupConfiguration implements StartupConfiguration {
-    
-    private File dbPath;
-    private File logFile;
-    private File pidFile;
-        
-    private long localPort;
-    
-    private String dbConnectionString;
-    
-    private String ip;
-        
-    public DBStartupConfiguration() throws InvalidConfigurationException {
-        dbPath = ConfigUtils.getStorageDirectory();
-        logFile = ConfigUtils.getStorageLogFile();
-        pidFile = ConfigUtils.getStoragePidFile();
-    }
-    
-    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;
-    }
-
-    public void setBindIP(String ip) {
-        this.ip = ip;
-    }
-    
-    public String getBindIP() {
-        return ip;
-    }
-}
\ No newline at end of file
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/MongoProcessRunner.java	Tue Jan 08 19:22:32 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,240 +0,0 @@
-/*
- * Copyright 2012 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.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.config.InvalidConfigurationException;
-import com.redhat.thermostat.common.locale.Translate;
-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;
-
-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";
-    private static final String NO_JOURNAL_FIRST_VERSION = "1.9.2";
-
-    private DBStartupConfiguration configuration;
-    private boolean isQuiet;
-    
-    public MongoProcessRunner(DBStartupConfiguration configuration, boolean quiet) {
-        this.configuration = configuration;
-        this.isQuiet = quiet;
-    }
-
-    private String getPid() {
-        
-        String pid = null;
-        
-        File pidfile = configuration.getPidFile();
-        Charset charset = Charset.defaultCharset();
-        if (pidfile.exists()) {
-            try (BufferedReader reader = Files.newBufferedReader(pidfile.toPath(), charset)) {
-                pid = reader.readLine();
-                if (pid == null || pid.isEmpty()) {
-                    pid = null;
-                }
-            } catch (IOException ex) {
-                logger.log(Level.WARNING, "Exception while reading pid file", ex);
-                pid = null;
-            }
-        }
-        
-        return pid;
-    }
-    
-    public void stopService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
- 
-        List<String> commands = new ArrayList<>(Arrays.asList(MONGO_SHUTDOWN_ARGS));
-        commands.add(getPid());
-
-        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()));
-            
-        } else {
-            
-            String message = translator.localize(LocaleResources.CANNOT_SHUTDOWN_SERVER,
-                    configuration.getDBPath().toString(),
-                    String.valueOf(status));
-            display(message);
-            throw new StorageStopException(configuration.getDBPath(), status, message);
-        }
-    }
-    
-    private boolean checkExistingProcess() {
-        String pid = getPid();
-        if (pid == null)
-            return false;
-        
-        String processName = UnixProcessUtilities.getInstance().getProcessName(getPid());
-        // TODO: check if we want mongos or mongod from the configs
-        return processName != null && processName.equalsIgnoreCase(MONGO_PROCESS);
-    }
-    
-    public void startService() throws IOException, InterruptedException, ApplicationException {
-
-        String pid = getPid();
-        if (pid != null) {
-            String message = null;
-            if (!checkExistingProcess()) {
-                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);
-                try {
-                    Files.delete(configuration.getPidFile().toPath());
-                } catch (IOException benign) {
-                    // ignore this benign error
-                }
-            } else {
-                message = translator.localize(LocaleResources.STORAGE_ALREADY_RUNNING_WITH_PID, String.valueOf(pid));
-                display(message);
-                throw new StorageAlreadyRunningException(Integer.valueOf(pid), message);
-            }
-        }
-        
-        List<String> commands = new ArrayList<>(Arrays.asList(MONGO_BASIC_ARGS));
-        String dbVersion = getDBVersion();
-        if (dbVersion.compareTo(NO_JOURNAL_FIRST_VERSION) >= 0) {
-            commands.add(1, NO_JOURNAL_ARGUMENT);
-        }
-
-        // check that the db directory exist
-        display(translator.localize(LocaleResources.STARTING_STORAGE_SERVER));
-
-        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()));
-        
-        LoggedExternalProcess process = new LoggedExternalProcess(commands);
-        int status = -1;
-        try {
-            status = process.runAndReturnResult();
-        } catch (ApplicationException ae) {
-            String message = translator.localize(LocaleResources.CANNOT_EXECUTE_PROCESS, MONGO_PROCESS);
-            display(message);
-            throw ae;
-        }
-
-        Thread.sleep(500);
-
-        if (status == 0) {
-            pid = getPid();
-            if (pid == null) 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 {
-            
-            String message = translator.localize(LocaleResources.CANNOT_START_SERVER,
-                             configuration.getDBPath().toString(),
-                             String.valueOf(status));
-            display(message);
-            throw new StorageStartException(configuration.getDBPath(), status, message);
-        }
-    }
- 
-    private String getDBVersion() throws IOException {
-        Process process;
-        try {
-            process = new ProcessBuilder(Arrays.asList("mongod", "--version"))
-                    .start();
-        } catch (IOException e) {
-            String message = translator.localize(
-                    LocaleResources.CANNOT_EXECUTE_PROCESS, MONGO_PROCESS);
-            display(message);
-            throw e;
-        }
-        InputStream out = process.getInputStream();
-        InputStreamReader reader = new InputStreamReader(out);
-        BufferedReader bufReader = new BufferedReader(reader);
-        String firstLine = bufReader.readLine();
-        int commaIdx = firstLine.indexOf(",", 12);
-        String versionString = firstLine.substring(12, commaIdx);
-        return versionString;
-    }
-
-    private void display(String message) {
-        if (!isQuiet) {
-            System.out.println(message);
-        }
-    }
-}
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/db/StorageAlreadyRunningException.java	Tue Jan 08 19:22:32 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-/*
- * Copyright 2012 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.db;
-
-import com.redhat.thermostat.common.tools.ApplicationException;
-
-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/db/StorageStartException.java	Tue Jan 08 19:22:32 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/*
- * Copyright 2012 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.db;
-
-import java.io.File;
-
-import com.redhat.thermostat.common.tools.ApplicationException;
-
-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/db/StorageStopException.java	Tue Jan 08 19:22:32 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/*
- * Copyright 2012 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.db;
-
-import java.io.File;
-
-import com.redhat.thermostat.common.tools.ApplicationException;
-
-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/Activator.java	Tue Jan 08 19:22:32 2013 +0100
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/Activator.java	Fri Dec 21 14:19:06 2012 +0100
@@ -41,6 +41,7 @@
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
 
+import com.redhat.thermostat.agent.cli.impl.db.StorageCommand;
 import com.redhat.thermostat.common.cli.CommandRegistry;
 import com.redhat.thermostat.common.cli.CommandRegistryImpl;
 
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java	Tue Jan 08 19:22:32 2013 +0100
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/AgentApplication.java	Fri Dec 21 14:19:06 2012 +0100
@@ -140,7 +140,9 @@
         connection.connect();
         logger.fine("Connecting to storage...");
 
+        @SuppressWarnings("rawtypes")
         ServiceReference configServiceRef = bundleContext.getServiceReference(ConfigurationServer.class.getName());
+        @SuppressWarnings("unchecked")
         final ConfigurationServer configServer = (ConfigurationServer) bundleContext.getService(configServiceRef);
         configServer.startListening(configuration.getConfigListenAddress());
         
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/ServiceCommand.java	Tue Jan 08 19:22:32 2013 +0100
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/ServiceCommand.java	Fri Dec 21 14:19:06 2012 +0100
@@ -40,7 +40,8 @@
 import java.util.List;
 import java.util.concurrent.Semaphore;
 
-import com.redhat.thermostat.agent.cli.db.StorageAlreadyRunningException;
+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;
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/StorageCommand.java	Tue Jan 08 19:22:32 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,177 +0,0 @@
-/*
- * Copyright 2012 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;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Properties;
-
-import com.redhat.thermostat.agent.cli.db.DBConfig;
-import com.redhat.thermostat.agent.cli.db.DBOptionParser;
-import com.redhat.thermostat.agent.cli.db.DBStartupConfiguration;
-import com.redhat.thermostat.agent.cli.db.MongoProcessRunner;
-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.config.ConfigUtils;
-import com.redhat.thermostat.common.config.InvalidConfigurationException;
-import com.redhat.thermostat.common.tools.ApplicationException;
-import com.redhat.thermostat.common.tools.ApplicationState;
-import com.redhat.thermostat.common.tools.BasicCommand;
-
-public class StorageCommand extends BasicCommand {
-
-    private static final String NAME = "storage";
-
-    private DBStartupConfiguration configuration;
-    private DBOptionParser parser;
-    
-    private MongoProcessRunner runner;
-    
-    private void parseArguments(Arguments args) throws InvalidConfigurationException {
-    
-        this.configuration = new DBStartupConfiguration();
-        // configs, read everything that is in the configs
-        File propertyFile = ConfigUtils.getStorageConfigurationFile();
-        if (!propertyFile.exists()) {
-            throw new InvalidConfigurationException("can't access database configuration file " +
-                                                    propertyFile);
-        }
-        readAndSetProperties(propertyFile);
-        
-        parser = new DBOptionParser(configuration, args);
-        parser.parse();
-    }
-    
-    @Override
-    public void run(CommandContext ctx) throws CommandException {
-
-        try {
-            parseArgsAndRun(ctx);
-        } catch (InvalidConfigurationException e) {
-            throw new CommandException(e);
-        }
-    }
-
-    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;
-             default:
-                break;
-            }
-            getNotifier().fireAction(ApplicationState.SUCCESS);
-        } catch (Exception e) {
-            getNotifier().fireAction(ApplicationState.FAIL, e);
-        }
-    }
-    
-    private void readAndSetProperties(File propertyFile) throws InvalidConfigurationException {
-    
-        Properties properties = new Properties();
-        try {
-            properties.load(new FileInputStream(propertyFile));
-            
-        } catch (IOException e) {
-            throw new InvalidConfigurationException(e);
-        }
-        
-        if (properties.containsKey(DBConfig.PORT.name())) {
-            String port = (String) properties.get(DBConfig.PORT.name());
-            int localPort = Integer.parseInt(port);
-            configuration.setPort(localPort);
-        } else {
-            throw new InvalidConfigurationException(DBConfig.PORT + " property missing");
-        }
-        
-        if (properties.containsKey(DBConfig.BIND.name())) {
-            String ip = (String) properties.get(DBConfig.BIND.name());
-            configuration.setBindIP(ip);
-        } else {
-            throw new InvalidConfigurationException(DBConfig.BIND + " property missing");
-        }
-    }
-    
-    private void startService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
-        runner.startService();
-        getNotifier().fireAction(ApplicationState.START);
-    }
-    
-    
-    private void stopService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
-        check();
-        runner.stopService();
-        getNotifier().fireAction(ApplicationState.STOP);
-    }
-    
-    MongoProcessRunner createRunner() {
-        return new MongoProcessRunner(configuration, parser.isQuiet());
-    }
-
-    private void check() throws InvalidConfigurationException {
-        if (!configuration.getDBPath().exists() ||
-            !configuration.getLogFile().getParentFile().exists() || 
-            !configuration.getPidFile().getParentFile().exists())
-        {
-            throw new InvalidConfigurationException("database directories do not exist...");
-        }
-    }
-
-    @Override
-    public DBStartupConfiguration getConfiguration() {
-        return configuration;
-    }
-
-    @Override
-    public String getName() {
-        return NAME;
-    }
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/DBConfig.java	Fri Dec 21 14:19:06 2012 +0100
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2012 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,
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/DBOptionParser.java	Fri Dec 21 14:19:06 2012 +0100
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2012 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.InvalidConfigurationException;
+import com.redhat.thermostat.common.config.ThermostatOptionParser;
+import com.redhat.thermostat.common.locale.Translate;
+import com.redhat.thermostat.common.tools.ApplicationState;
+
+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 {
+            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 ApplicationState getAction() {
+        return serviceAction.state;
+    }
+
+    static enum DBArgs {
+                
+        DRY("dryRun", ApplicationState.NONE),
+        
+        HELP("help", ApplicationState.HELP),
+        
+        START("start", ApplicationState.START),
+        STOP("stop", ApplicationState.STOP),
+        
+        QUIET("quiet", ApplicationState.NONE);
+        
+        private String option;
+        private ApplicationState state;
+        
+        DBArgs(String option, ApplicationState state) {
+            this.option = option;
+            this.state = state;
+        }
+    }
+
+    public boolean isQuiet() {
+        return quiet;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/DBStartupConfiguration.java	Fri Dec 21 14:19:06 2012 +0100
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2012 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.common.config.InvalidConfigurationException;
+import com.redhat.thermostat.storage.config.StartupConfiguration;
+
+public class DBStartupConfiguration implements StartupConfiguration {
+    
+    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 properties, File dbPath, File logFile,
+            File pidFile) throws InvalidConfigurationException {
+        this.dbPath = dbPath;
+        this.logFile = logFile;
+        this.pidFile = pidFile;
+        readAndSetProperties(properties);
+    }
+    
+    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 propertyFile) throws InvalidConfigurationException {
+        
+        Properties properties = new Properties();
+        try {
+            properties.load(new FileInputStream(propertyFile));
+            
+        } catch (IOException e) {
+            throw new InvalidConfigurationException(e);
+        }
+        
+        if (properties.containsKey(DBConfig.PORT.name())) {
+            String port = (String) properties.get(DBConfig.PORT.name());
+            int localPort = Integer.parseInt(port);
+            setPort(localPort);
+        } else {
+            throw new InvalidConfigurationException(DBConfig.PORT + " property missing");
+        }
+        
+        if (properties.containsKey(DBConfig.BIND.name())) {
+            String ip = (String) properties.get(DBConfig.BIND.name());
+            setBindIP(ip);
+        } else {
+            throw new InvalidConfigurationException(DBConfig.BIND + " property missing");
+        }
+        
+        // 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);
+        
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/MongoProcessRunner.java	Fri Dec 21 14:19:06 2012 +0100
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2012 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.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.locale.Translate;
+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;
+
+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";
+    private static final String NO_JOURNAL_FIRST_VERSION = "1.9.2";
+
+    private DBStartupConfiguration configuration;
+    private boolean isQuiet;
+    
+    public MongoProcessRunner(DBStartupConfiguration configuration, boolean quiet) {
+        this.configuration = configuration;
+        this.isQuiet = quiet;
+    }
+
+    private String getPid() {
+        
+        String pid = null;
+        
+        File pidfile = configuration.getPidFile();
+        Charset charset = Charset.defaultCharset();
+        if (pidfile.exists()) {
+            try (BufferedReader reader = Files.newBufferedReader(pidfile.toPath(), charset)) {
+                pid = reader.readLine();
+                if (pid == null || pid.isEmpty()) {
+                    pid = null;
+                }
+            } catch (IOException ex) {
+                logger.log(Level.WARNING, "Exception while reading pid file", ex);
+                pid = null;
+            }
+        }
+        
+        return pid;
+    }
+    
+    public void stopService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
+ 
+        List<String> commands = new ArrayList<>(Arrays.asList(MONGO_SHUTDOWN_ARGS));
+        commands.add(getPid());
+
+        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 {
+            
+            String message = translator.localize(LocaleResources.CANNOT_SHUTDOWN_SERVER,
+                    configuration.getDBPath().toString(),
+                    String.valueOf(status));
+            display(message);
+            throw new StorageStopException(configuration.getDBPath(), status, message);
+        }
+    }
+    
+    private boolean checkExistingProcess() {
+        String pid = getPid();
+        if (pid == null)
+            return false;
+        
+        String processName = UnixProcessUtilities.getInstance().getProcessName(getPid());
+        // TODO: check if we want mongos or mongod from the configs
+        return processName != null && processName.equalsIgnoreCase(MONGO_PROCESS);
+    }
+    
+    public void startService() throws IOException, InterruptedException,
+            ApplicationException, InvalidConfigurationException {
+
+        String pid = getPid();
+        if (pid != null) {
+            String message = null;
+            if (!checkExistingProcess()) {
+                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);
+                try {
+                    Files.delete(configuration.getPidFile().toPath());
+                } catch (IOException benign) {
+                    // ignore this benign error
+                }
+            } else {
+                message = translator.localize(LocaleResources.STORAGE_ALREADY_RUNNING_WITH_PID, String.valueOf(pid));
+                display(message);
+                throw new StorageAlreadyRunningException(Integer.valueOf(pid), message);
+            }
+        }
+        
+        String dbVersion = getDBVersion();
+        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) {
+            String message = translator.localize(LocaleResources.CANNOT_EXECUTE_PROCESS, MONGO_PROCESS);
+            display(message);
+            throw ae;
+        }
+
+        Thread.sleep(500);
+
+        if (status == 0) {
+            pid = getPid();
+            if (pid == null) 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 {
+            
+            String message = translator.localize(LocaleResources.CANNOT_START_SERVER,
+                             configuration.getDBPath().toString(),
+                             String.valueOf(status));
+            display(message);
+            throw new StorageStartException(configuration.getDBPath(), status, message);
+        }
+    }
+    
+    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("No SSL PEM file specified!");
+            } else if (configuration.getSslKeyPassphrase() == null) {
+                throw new InvalidConfigurationException("No SSL key passphrase set!");
+            }
+            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;
+        try {
+            process = new ProcessBuilder(Arrays.asList("mongod", "--version"))
+                    .start();
+        } catch (IOException e) {
+            String message = translator.localize(
+                    LocaleResources.CANNOT_EXECUTE_PROCESS, MONGO_PROCESS);
+            display(message);
+            throw e;
+        }
+        InputStream out = process.getInputStream();
+        InputStreamReader reader = new InputStreamReader(out);
+        BufferedReader bufReader = new BufferedReader(reader);
+        String firstLine = bufReader.readLine();
+        int commaIdx = firstLine.indexOf(",", 12);
+        String versionString = firstLine.substring(12, commaIdx);
+        return versionString;
+    }
+
+    private void display(String message) {
+        if (!isQuiet) {
+            System.out.println(message);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StalePidFileException.java	Fri Dec 21 14:19:06 2012 +0100
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2012 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.locale.Translate;
+import com.redhat.thermostat.common.tools.ApplicationException;
+
+@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()));
+        this.pidFile = pidFile;
+    }
+
+    public File getPidFile() {
+        return pidFile;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageAlreadyRunningException.java	Fri Dec 21 14:19:06 2012 +0100
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012 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;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageCommand.java	Fri Dec 21 14:19:06 2012 +0100
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2012 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 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.config.ConfigUtils;
+import com.redhat.thermostat.common.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.tools.ApplicationException;
+import com.redhat.thermostat.common.tools.ApplicationState;
+import com.redhat.thermostat.common.tools.BasicCommand;
+
+public class StorageCommand extends BasicCommand {
+
+    private static final String NAME = "storage";
+
+    private DBStartupConfiguration configuration;
+    private DBOptionParser parser;
+    
+    private MongoProcessRunner runner;
+    
+    private void parseArguments(Arguments args) throws InvalidConfigurationException {
+    
+        File dbPath = ConfigUtils.getStorageDirectory();
+        File logFile = ConfigUtils.getStorageLogFile();
+        File pidFile = ConfigUtils.getStoragePidFile();
+        File propertyFile = ConfigUtils.getStorageConfigurationFile();
+        if (!propertyFile.exists()) {
+            throw new InvalidConfigurationException("can't access database configuration file " +
+                                                    propertyFile);
+        }
+        // read everything that is in the configs
+        this.configuration = new DBStartupConfiguration(propertyFile, dbPath, logFile, pidFile);
+        parser = new DBOptionParser(configuration, args);
+        parser.parse();
+    }
+    
+    @Override
+    public void run(CommandContext ctx) throws CommandException {
+
+        try {
+            parseArgsAndRun(ctx);
+        } catch (InvalidConfigurationException e) {
+            throw new CommandException(e);
+        }
+    }
+
+    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;
+             default:
+                break;
+            }
+            getNotifier().fireAction(ApplicationState.SUCCESS);
+        } catch (InvalidConfigurationException e) {
+            // rethrow
+            throw e;
+        } catch (Exception e) {
+            getNotifier().fireAction(ApplicationState.FAIL, e);
+        }
+    }
+    
+    private void startService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
+        runner.startService();
+        getNotifier().fireAction(ApplicationState.START);
+    }
+    
+    
+    private void stopService() throws IOException, InterruptedException, InvalidConfigurationException, ApplicationException {
+        check();
+        runner.stopService();
+        getNotifier().fireAction(ApplicationState.STOP);
+    }
+    
+    MongoProcessRunner createRunner() {
+        return new MongoProcessRunner(configuration, parser.isQuiet());
+    }
+
+    private void check() throws InvalidConfigurationException {
+        if (!configuration.getDBPath().exists() ||
+            !configuration.getLogFile().getParentFile().exists() || 
+            !configuration.getPidFile().getParentFile().exists())
+        {
+            throw new InvalidConfigurationException("database directories do not exist...");
+        }
+    }
+
+    @Override
+    public DBStartupConfiguration getConfiguration() {
+        return configuration;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageStartException.java	Fri Dec 21 14:19:06 2012 +0100
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 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;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/impl/db/StorageStopException.java	Fri Dec 21 14:19:06 2012 +0100
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012 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/test/java/com/redhat/thermostat/agent/cli/impl/ActivatorTest.java	Tue Jan 08 19:22:32 2013 +0100
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/ActivatorTest.java	Fri Dec 21 14:19:06 2012 +0100
@@ -41,6 +41,7 @@
 
 import org.junit.Test;
 
+import com.redhat.thermostat.agent.cli.impl.db.StorageCommand;
 import com.redhat.thermostat.common.cli.Command;
 import com.redhat.thermostat.test.StubBundleContext;
 
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/AgentApplicationTest.java	Tue Jan 08 19:22:32 2013 +0100
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/AgentApplicationTest.java	Fri Dec 21 14:19:06 2012 +0100
@@ -144,6 +144,7 @@
 
     // FIXME test the rest of AgentApplication
 
+    @SuppressWarnings("serial")
     private static class ThatsAllThatWeCareAbout extends RuntimeException {
 
     }
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/StorageCommandTest.java	Tue Jan 08 19:22:32 2013 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,295 +0,0 @@
-/*
- * Copyright 2012 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;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-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.FileOutputStream;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Properties;
-import java.util.Random;
-import java.util.concurrent.CountDownLatch;
-
-import junit.framework.Assert;
-
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.OptionGroup;
-import org.apache.commons.cli.Options;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import com.redhat.thermostat.agent.cli.db.DBConfig;
-import com.redhat.thermostat.agent.cli.db.DBStartupConfiguration;
-import com.redhat.thermostat.agent.cli.db.MongoProcessRunner;
-import com.redhat.thermostat.common.ActionEvent;
-import com.redhat.thermostat.common.ActionListener;
-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.config.InvalidConfigurationException;
-import com.redhat.thermostat.common.tools.ApplicationException;
-import com.redhat.thermostat.common.tools.ApplicationState;
-
-public class StorageCommandTest {
-    
-    private static final String PORT = "27518";
-    private static final String BIND = "127.0.0.1";
-    private static final String DB = "storage/db";
-
-    private String tmpDir;
-    
-    @Before
-    public void setup() {
-        // need to create a dummy config file for the test
-        try {
-            Random random = new Random();
-            
-            tmpDir = System.getProperty("java.io.tmpdir") + File.separatorChar +
-                     Math.abs(random.nextInt()) + File.separatorChar;
-            
-            System.setProperty("THERMOSTAT_HOME", tmpDir);
-            File base = new File(tmpDir + "storage");
-            base.mkdirs();
-                        
-            File tmpConfigs = new File(base, "db.properties");
-            
-            new File(base, "run").mkdirs();
-            new File(base, "logs").mkdirs();
-            new File(base, "db").mkdirs();
-            
-            Properties props = new Properties();
-            
-            props.setProperty(DBConfig.BIND.name(), BIND);
-            props.setProperty(DBConfig.PORT.name(), PORT);
-
-            props.store(new FileOutputStream(tmpConfigs), "thermostat test properties");
-            
-        } catch (IOException e) {
-            Assert.fail("cannot setup tests: " + e);
-        }
-    }
-    
-    @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() {
-            @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() {
-            @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]);
-    }
-
-    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;
-    }
-
-    @Test
-    public void testName() {
-        StorageCommand dbService = new StorageCommand();
-        String name = dbService.getName();
-        assertEquals("storage", name);
-    }
-
-    @Test
-    public void testDescAndUsage() {
-        StorageCommand dbService = new StorageCommand();
-        assertNotNull(dbService.getDescription());
-        assertNotNull(dbService.getUsage());
-    }
-
-    @Ignore
-    @Test
-    public void testOptions() {
-        StorageCommand dbService = new StorageCommand();
-        Options options = dbService.getOptions();
-        assertNotNull(options);
-        assertEquals(4, options.getOptions().size());
-
-        assertTrue(options.hasOption("dryRun"));
-        Option dry = options.getOption("dryRun");
-        assertEquals("d", dry.getOpt());
-        assertEquals("run the service in dry run mode", dry.getDescription());
-        assertFalse(dry.isRequired());
-        assertFalse(dry.hasArg());
-
-        assertTrue(options.hasOption("start"));
-        Option start = options.getOption("start");
-        assertEquals("start the database", start.getDescription());
-        assertFalse(start.isRequired());
-        assertFalse(start.hasArg());
-
-        assertTrue(options.hasOption("stop"));
-        Option stop = options.getOption("stop");
-        assertEquals("stop the database", stop.getDescription());
-        assertFalse(stop.isRequired());
-        assertFalse(stop.hasArg());
-
-        assertTrue(options.hasOption("quiet"));
-        Option quiet = options.getOption("quiet");
-        assertEquals("q", quiet.getOpt());
-        assertEquals("don't produce any output", quiet.getDescription());
-        assertFalse(quiet.isRequired());
-        assertFalse(quiet.hasArg());
-
-        OptionGroup startStop = options.getOptionGroup(start);
-        assertTrue(startStop.isRequired());
-        @SuppressWarnings("unchecked")
-        Collection<Option> groupOpts = startStop.getOptions();
-        assertEquals(2, groupOpts.size());
-        assertTrue(groupOpts.contains(start));
-        assertTrue(groupOpts.contains(stop));
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/db/DBStartupConfigurationTest.java	Fri Dec 21 14:19:06 2012 +0100
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2012 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.common.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());
+        DBStartupConfiguration dbConfig = new DBStartupConfiguration(dbProps, 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());
+        DBStartupConfiguration dbConfig = new DBStartupConfiguration(dbProps, 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());
+        try {
+            new DBStartupConfiguration(dbProps, 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());
+        try {
+            new DBStartupConfiguration(dbProps, 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/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/db/MongoProcessRunnerTest.java	Fri Dec 21 14:19:06 2012 +0100
@@ -0,0 +1,123 @@
+package com.redhat.thermostat.agent.cli.impl.db;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+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.common.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());
+        }
+    }
+
+    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/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/impl/db/StorageCommandTest.java	Fri Dec 21 14:19:06 2012 +0100
@@ -0,0 +1,293 @@
+/*
+ * Copyright 2012 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.assertFalse;
+import static org.junit.Assert.assertNotNull;
+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.FileOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Properties;
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+
+import junit.framework.Assert;
+
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionGroup;
+import org.apache.commons.cli.Options;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import com.redhat.thermostat.agent.cli.impl.db.MongoProcessRunner;
+import com.redhat.thermostat.common.ActionEvent;
+import com.redhat.thermostat.common.ActionListener;
+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.config.InvalidConfigurationException;
+import com.redhat.thermostat.common.tools.ApplicationException;
+import com.redhat.thermostat.common.tools.ApplicationState;
+
+public class StorageCommandTest {
+    
+    private static final String PORT = "27518";
+    private static final String BIND = "127.0.0.1";
+    private static final String DB = "storage/db";
+
+    private String tmpDir;
+    
+    @Before
+    public void setup() {
+        // need to create a dummy config file for the test
+        try {
+            Random random = new Random();
+            
+            tmpDir = System.getProperty("java.io.tmpdir") + File.separatorChar +
+                     Math.abs(random.nextInt()) + File.separatorChar;
+            
+            System.setProperty("THERMOSTAT_HOME", tmpDir);
+            File base = new File(tmpDir + "storage");
+            base.mkdirs();
+                        
+            File tmpConfigs = new File(base, "db.properties");
+            
+            new File(base, "run").mkdirs();
+            new File(base, "logs").mkdirs();
+            new File(base, "db").mkdirs();
+            
+            Properties props = new Properties();
+            
+            props.setProperty(DBConfig.BIND.name(), BIND);
+            props.setProperty(DBConfig.PORT.name(), PORT);
+
+            props.store(new FileOutputStream(tmpConfigs), "thermostat test properties");
+            
+        } catch (IOException e) {
+            Assert.fail("cannot setup tests: " + e);
+        }
+    }
+    
+    @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() {
+            @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() {
+            @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]);
+    }
+
+    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;
+    }
+
+    @Test
+    public void testName() {
+        StorageCommand dbService = new StorageCommand();
+        String name = dbService.getName();
+        assertEquals("storage", name);
+    }
+
+    @Test
+    public void testDescAndUsage() {
+        StorageCommand dbService = new StorageCommand();
+        assertNotNull(dbService.getDescription());
+        assertNotNull(dbService.getUsage());
+    }
+
+    @Ignore
+    @Test
+    public void testOptions() {
+        StorageCommand dbService = new StorageCommand();
+        Options options = dbService.getOptions();
+        assertNotNull(options);
+        assertEquals(4, options.getOptions().size());
+
+        assertTrue(options.hasOption("dryRun"));
+        Option dry = options.getOption("dryRun");
+        assertEquals("d", dry.getOpt());
+        assertEquals("run the service in dry run mode", dry.getDescription());
+        assertFalse(dry.isRequired());
+        assertFalse(dry.hasArg());
+
+        assertTrue(options.hasOption("start"));
+        Option start = options.getOption("start");
+        assertEquals("start the database", start.getDescription());
+        assertFalse(start.isRequired());
+        assertFalse(start.hasArg());
+
+        assertTrue(options.hasOption("stop"));
+        Option stop = options.getOption("stop");
+        assertEquals("stop the database", stop.getDescription());
+        assertFalse(stop.isRequired());
+        assertFalse(stop.hasArg());
+
+        assertTrue(options.hasOption("quiet"));
+        Option quiet = options.getOption("quiet");
+        assertEquals("q", quiet.getOpt());
+        assertEquals("don't produce any output", quiet.getDescription());
+        assertFalse(quiet.isRequired());
+        assertFalse(quiet.hasArg());
+
+        OptionGroup startStop = options.getOptionGroup(start);
+        assertTrue(startStop.isRequired());
+        @SuppressWarnings("unchecked")
+        Collection<Option> groupOpts = startStop.getOptions();
+        assertEquals(2, groupOpts.size());
+        assertTrue(groupOpts.contains(start));
+        assertTrue(groupOpts.contains(stop));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/cli/src/test/resources/brokenDbConfig.properties	Fri Dec 21 14:19:06 2012 +0100
@@ -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/agent/cli/src/test/resources/brokenDbConfig2.properties	Fri Dec 21 14:19:06 2012 +0100
@@ -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/agent/cli/src/test/resources/testDbConfig.properties	Fri Dec 21 14:19:06 2012 +0100
@@ -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/agent/cli/src/test/resources/testDbConfig2.properties	Fri Dec 21 14:19:06 2012 +0100
@@ -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/distribution/config/db.properties	Tue Jan 08 19:22:32 2013 +0100
+++ b/distribution/config/db.properties	Fri Dec 21 14:19:06 2012 +0100
@@ -1,2 +1,57 @@
+# Copyright 2012 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.
+
+###################################################################
+# Basic storage configuration
+###################################################################
+
 PORT=27518
 BIND=127.0.0.1
+
+###################################################################
+# SSL configuration
+###################################################################
+# Uncomment the following line in order to start storage (currently
+# mongodb) with SSL enabled.
+#SSL_ENABLE=true
+#
+# If SSL_ENABLE=true, the path to the PEM encoded private key and
+# the server certificate needs to be specified. Uncomment the
+# following line and set the path appropriately. 
+#SSL_PEM_FILE=/path/to/some/pem/file.pem
+#
+# If SSL_ENABLE=true, the passphrase for the server certificate
+# needs to be specified. If the server key was not encrypted any
+# non-empty password will work.
+#SSL_KEY_PASSWORD=somepassword
\ No newline at end of file