changeset 1280:e88531d6fdc6

Add build steps in order to allow stand-alone itest runs. Reviewed-by: omajid Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2013-April/006317.html
author Severin Gehwolf <sgehwolf@redhat.com>
date Fri, 05 Apr 2013 18:57:56 +0200
parents 7b91182a8455
children 7a4e7c3c4a1d
files integration-tests/itest-run/pom.xml integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/CliTest.java integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/IntegrationTest.java integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/MongoQueriesTest.java integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/PluginTest.java integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/StorageConnectionTest.java integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/StorageTest.java integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/VmCommandsTest.java integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/WebAppTest.java integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/WebAppTestStatementDescriptorRegistration.java integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/standalone/AllStandaloneTests.java integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/standalone/ItestRunner.java integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/standalone/StandaloneReportsListener.java integration-tests/itest-run/src/test/resources/META-INF/services/com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration integration-tests/pom.xml integration-tests/src/test/java/com/redhat/thermostat/itest/CliTest.java integration-tests/src/test/java/com/redhat/thermostat/itest/IntegrationTest.java integration-tests/src/test/java/com/redhat/thermostat/itest/MongoQueriesTest.java integration-tests/src/test/java/com/redhat/thermostat/itest/PluginTest.java integration-tests/src/test/java/com/redhat/thermostat/itest/StorageConnectionTest.java integration-tests/src/test/java/com/redhat/thermostat/itest/StorageTest.java integration-tests/src/test/java/com/redhat/thermostat/itest/VmCommandsTest.java integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTest.java integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTestStatementDescriptorRegistration.java integration-tests/src/test/resources/META-INF/services/com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration integration-tests/standalone/pom.xml pom.xml
diffstat 27 files changed, 3639 insertions(+), 3070 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/integration-tests/itest-run/pom.xml	Fri Apr 05 18:57:56 2013 +0200
@@ -0,0 +1,224 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+
+ Copyright 2013 Red Hat, Inc.
+
+ This file is part of Thermostat.
+
+ Thermostat is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2, or (at your
+ option) any later version.
+
+ Thermostat is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Thermostat; see the file COPYING.  If not see
+ <http://www.gnu.org/licenses />.
+
+ Linking this code with other modules is making a combined work
+ based on this code.  Thus, the terms and conditions of the GNU
+ General Public License cover the whole combination.
+
+ As a special exception, the copyright holders of this code give
+ you permission to link this code with independent modules to
+ produce an executable, regardless of the license terms of these
+ independent modules, and to copy and distribute the resulting
+ executable under terms of your choice, provided that you also
+ meet, for each linked independent module, the terms and conditions
+ of the license of that module.  An independent module is a module
+ which is not derived from or based on this code.  If you modify
+ this code, you may extend this exception to your version of the
+ library, but you are not obligated to do so.  If you do not wish
+ to do so, delete this exception statement from your version.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.redhat.thermostat</groupId>
+    <artifactId>thermostat-integration-tests</artifactId>
+    <version>0.16.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>thermostat-integration-tests-run</artifactId>
+  <packaging>jar</packaging>
+
+  <name>Thermostat Integration Tests (Runner)</name>
+
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+
+  <build>
+    <plugins>
+      <!-- jacoco:report insists to have a target/classes dir. since this
+           module only contains test classes it won't exist after a build.
+           Hence, create manually in order to unbreak the build. For some
+           reason skipping both, report AND prepare-agent goals for the
+           jacoco plugin does not work. -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <version>1.6</version>
+        <executions>
+          <execution>
+            <id>make-target-classes-dir</id>
+            <phase>prepare-package</phase>
+            <configuration>
+              <target>
+                <mkdir dir="${project.build.directory}/classes" />
+              </target>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <!-- skip unit test run, tests to be executed during integration-test -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <argLine>-Djava.security.auth.login.config=${thermostat.home}/etc/thermostat_jaas.conf ${coverageAgent}</argLine>
+          <skip>true</skip>
+        </configuration>
+        <executions>
+          <execution>
+            <id>run-integration-tests</id>
+            <phase>integration-test</phase>
+            <goals>
+              <goal>test</goal>
+            </goals>
+            <configuration>
+              <skip>false</skip>
+              <!-- Don't run the suite class. We only have it for the stand-alone itest thingy -->
+              <excludes>
+                <exclude>**/AllStandaloneTests.java</exclude>
+              </excludes>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <version>2.4</version>
+        <executions>
+          <execution>
+            <id>copy-deps</id>
+            <phase>package</phase>
+            <goals>
+              <goal>copy-dependencies</goal>
+            </goals>
+            <configuration>
+              <excludeTransitive>true</excludeTransitive>
+              <excludeGroupIds>org.osgi,junit,org.hamcrest</excludeGroupIds>
+              <outputDirectory>${project.build.directory}/libs</outputDirectory>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <!-- produce a jar with the integration tests, which the standalone module can consume -->
+      <plugin>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>test-jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+    <pluginManagement>
+    	<plugins>
+		<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
+    		<plugin>
+    			<groupId>org.eclipse.m2e</groupId>
+    			<artifactId>lifecycle-mapping</artifactId>
+    			<version>1.0.0</version>
+    			<configuration>
+    				<lifecycleMappingMetadata>
+    					<pluginExecutions>
+    						<pluginExecution>
+    							<pluginExecutionFilter>
+    								<groupId>
+    									org.apache.maven.plugins
+    								</groupId>
+    								<artifactId>
+    									maven-dependency-plugin
+    								</artifactId>
+    								<versionRange>[2.4,)</versionRange>
+    								<goals>
+    									<goal>copy-dependencies</goal>
+    								</goals>
+    							</pluginExecutionFilter>
+    							<action>
+    								<ignore></ignore>
+    							</action>
+    						</pluginExecution>
+    					</pluginExecutions>
+    				</lifecycleMappingMetadata>
+    			</configuration>
+    		</plugin>
+    	</plugins>
+    </pluginManagement>
+  </build>
+  <dependencies>
+
+    <!-- integration tests -->
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>net.sourceforge.expectj</groupId>
+      <artifactId>expectj</artifactId>
+      <scope>test</scope>
+    </dependency>
+
+    <!-- thermostat parts -->
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-distribution</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-web-war</artifactId>
+      <version>${project.version}</version>
+      <type>war</type>
+      <scope>test</scope>
+    </dependency>
+
+    <!--  Embedded jetty for testing the WAR (no peace from here on..). -->
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-server</artifactId>
+      <version>${jetty.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-plus</artifactId>
+      <version>${jetty.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.eclipse.jetty</groupId>
+      <artifactId>jetty-webapp</artifactId>
+      <version>${jetty.version}</version>
+      <scope>test</scope>
+    </dependency>
+
+  </dependencies>
+</project>
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/CliTest.java	Fri Apr 05 18:57:56 2013 +0200
@@ -0,0 +1,257 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.itest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+import expectj.Spawn;
+
+/**
+ * Integration tests to exercise the basics of the thermostat command line.
+ */
+public class CliTest extends IntegrationTest {
+
+    @Test
+    public void testExpectIsSane() throws Exception {
+        Spawn shell = spawnThermostat();
+
+        try {
+            shell.expect("some-random-text-that-is-not-really-possible");
+            fail("should never match");
+        } catch (IOException endOfStream) {
+            assertTrue(endOfStream.getMessage().contains("End of stream reached, no match found"));
+        }
+        shell.expectClose();
+    }
+
+    @Test
+    public void testSimpleInvocationPrintsHelp() throws Exception {
+        Spawn shell = spawnThermostat();
+        shell.expectClose();
+
+        String stdOut = shell.getCurrentStandardOutContents();
+
+        assertMatchesHelpCommandList(stdOut);
+
+        String stdErr = shell.getCurrentStandardErrContents();
+        assertEquals(stdErr, "");
+    }
+
+    @Test
+    public void testHelpCommandInvocation() throws Exception {
+        Spawn shell = spawnThermostat("help");
+        shell.expectClose();
+
+        String stdOut = shell.getCurrentStandardOutContents();
+        String stdErr = shell.getCurrentStandardErrContents();
+
+        assertMatchesHelpCommandList(stdOut);
+        assertEquals(stdErr, "");
+    }
+
+    @Test
+    public void testHelpOnHelp() throws Exception {
+        Spawn shell = spawnThermostat("help", "help");
+        shell.expectClose();
+
+        String stdOut = shell.getCurrentStandardOutContents();
+        String stdErr = shell.getCurrentStandardErrContents();
+
+        String[] lines = stdOut.split("\n");
+        String usage = lines[0];
+        assertEquals("usage: thermostat help [command-name]", usage);
+
+        assertEquals(stdErr, "");
+    }
+
+    @Test
+    public void testVersionArgument() throws Exception {
+        Spawn shell = spawnThermostat("--version");
+        shell.expectClose();
+
+        String stdOut = shell.getCurrentStandardOutContents();
+        String stdErr = shell.getCurrentStandardErrContents();
+
+        assertTrue(stdOut.matches("Thermostat version \\d+\\.\\d+\\.\\d+\n"));
+        assertEquals(stdErr, "");
+    }
+
+    @Test
+    public void testShell() throws Exception {
+        Spawn shell = spawnThermostat("shell");
+
+        shell.expect(SHELL_PROMPT);
+        shell.send("help\n");
+
+        shell.expect(SHELL_PROMPT);
+
+        assertMatchesShellHelpCommandList(shell.getCurrentStandardOutContents());
+
+        shell.send("exit\n");
+
+        shell.expectClose();
+    }
+
+    @Test
+    public void testShellPrintsVersionOnStartup() throws Exception {
+        Spawn shell = spawnThermostat("shell");
+
+        shell.expect(SHELL_PROMPT);
+
+        String stdOut = shell.getCurrentStandardOutContents();
+        assertTrue(stdOut.contains("Thermostat version "));
+    }
+    
+    @Test
+    public void versionArgumentInShellIsNotAllowed() throws Exception {
+        Spawn shell = spawnThermostat("shell");
+
+        shell.expect(SHELL_PROMPT);
+        shell.send("--version\n");
+
+        shell.expect(SHELL_PROMPT);
+
+        String stdOut = shell.getCurrentStandardOutContents();
+        String stdErr = shell.getCurrentStandardErrContents();
+
+        assertMatchesShellHelpCommandList(shell.getCurrentStandardOutContents());
+        // use the Pattern.DOTALL flag (?s) so that line terminators match with
+        // ".*". stdOut contains the SHELL_PROMPT too.
+        assertTrue(stdOut.matches("(?s)^.*\nunknown command '--version'\n.*$"));
+        assertEquals(stdErr, "");
+        
+        shell.send("exit\n");
+
+        shell.expectClose();
+    }
+
+    @Test
+    public void testShellHelp() throws Exception {
+        Spawn shell = spawnThermostat("help", "shell");
+        shell.expectClose();
+
+        String stdOut = shell.getCurrentStandardOutContents();
+
+        String[] lines = stdOut.split("\n");
+        String usage = lines[0];
+        assertTrue(usage.matches("^usage: thermostat shell$"));
+        String description = lines[1];
+        assertTrue(description.matches("^\\s+launches the Thermostat interactive shell$"));
+        assertTrue(lines[3].matches("thermostat shell"));
+    }
+
+    @Test
+    public void testShellHelpArgument() throws Exception {
+        Spawn shell = spawnThermostat("shell", "--help");
+        shell.expectClose();
+
+        String stdOut = shell.getCurrentStandardOutContents();
+
+        String[] lines = stdOut.split("\n");
+        String usage = lines[0];
+        assertTrue(usage.matches("^usage: thermostat shell$"));
+        String description = lines[1];
+        assertTrue(description.matches("^\\s+launches the Thermostat interactive shell$"));
+        assertTrue(lines[3].matches("thermostat shell"));
+    }
+
+    @Test
+    public void testShellUnrecognizedArgument() throws Exception {
+        Spawn shell = spawnThermostat("shell", "--foo");
+        shell.expectClose();
+        String stdOut = shell.getCurrentStandardOutContents();
+        String expectedOut = "Could not parse options: Unrecognized option: --foo\n"
+                           + "usage: thermostat shell\n"
+                           + "                  launches the Thermostat interactive shell\n"
+                           + "\n"
+                           + "thermostat shell\n\n";
+        assertEquals(expectedOut, stdOut);
+    }
+
+    @Test
+    public void testUnrecognizedEventsInShell() throws Exception {
+        // test '!' events
+        Spawn shell = spawnThermostat("shell");
+
+        shell.expect(SHELL_PROMPT);
+        shell.send("what!?!\n");
+        shell.expect(SHELL_PROMPT);
+        shell.send("exit\n");
+        shell.expectClose();
+
+        assertTrue(shell.getCurrentStandardErrContents().contains("!?!: event not found"));
+        assertNoExceptions(shell.getCurrentStandardOutContents(), shell.getCurrentStandardErrContents());
+    }
+
+    @Test
+    public void testInvalidCommand() throws Exception {
+        Spawn shell = spawnThermostat("foobar", "baz");
+
+        // TODO should this be stderr?
+        shell.expect("unknown command 'foobar'");
+        shell.expectClose();
+
+        String stdOut = shell.getCurrentStandardOutContents();
+
+        assertMatchesHelpCommandList(stdOut);
+    }
+
+    private static void assertMatchesHelpCommandList(String actual) {
+        assertTrue(actual.contains("list of commands"));
+        assertTrue(actual.contains("help"));
+        assertTrue(actual.contains("agent"));
+        assertTrue(actual.contains("gui"));
+        assertTrue(actual.contains("ping"));
+        assertTrue(actual.contains("shell"));
+    }
+
+    private static void assertMatchesShellHelpCommandList(String actual) {
+        assertTrue(actual.contains("list of commands"));
+        assertTrue(actual.contains("help"));
+        assertTrue(actual.contains("connect"));
+        assertTrue(actual.contains("disconnect"));
+        assertTrue(actual.contains("ping"));
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/IntegrationTest.java	Fri Apr 05 18:57:56 2013 +0200
@@ -0,0 +1,391 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.itest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import com.redhat.thermostat.common.utils.StreamUtils;
+
+import expectj.Executor;
+import expectj.ExpectJ;
+import expectj.Spawn;
+import expectj.TimeoutException;
+
+/**
+ * Helper methods to support writing an integration test.
+ * <p>
+ * This class should be used by all integration tests to start
+ * thermostat and to obtain paths to various locations. Starting
+ * thermostat manually will cause issues with wrong paths being
+ * used.
+ */
+public class IntegrationTest {
+    
+    public static final String ITEST_USER_HOME_PROP = "com.redhat.thermostat.itest.thermostatUserHome";
+    public static final String ITEST_THERMOSTAT_HOME_PROP = "com.redhat.thermostat.itest.thermostatHome";
+
+    public static class SpawnResult {
+        final Process process;
+        final Spawn spawn;
+
+        public SpawnResult(Process process, Spawn spawn) {
+            this.process = process;
+            this.spawn = spawn;
+        }
+    }
+
+    // FIXME Make sure all methods are using a sane environment that's set up correctly
+
+    public static final long TIMEOUT_IN_SECONDS = 30;
+
+    public static final String SHELL_PROMPT = "Thermostat >";
+
+    private static final String THERMOSTAT_HOME = "THERMOSTAT_HOME";
+    private static final String USER_THERMOSTAT_HOME = "USER_THERMOSTAT_HOME";
+
+    /* This is a mirror of paths from c.r.t.shared.Configuration */
+
+    private static String getThermostatExecutable() {
+        return getThermostatHome() + "/bin/thermostat";
+    }
+    
+    public static String getThermostatHome() {
+        String propHome = System.getProperty(ITEST_THERMOSTAT_HOME_PROP);
+        if (propHome == null) {
+            return "../../distribution/target/image";
+        } else {
+            return propHome;
+        }
+    }
+
+    public static String getSystemPluginHome() {
+        return getThermostatHome() + "/plugins";
+    }
+
+    public static String getConfigurationDir() {
+        return getThermostatHome() + "/etc";
+    }
+
+    public static String getUserThermostatHome() {
+        String userHomeProp = System.getProperty(ITEST_USER_HOME_PROP);
+        if (userHomeProp == null) {
+            return "../../distribution/target/user-home";
+        } else {
+            return userHomeProp;
+        }
+    }
+
+    public static String getStorageDataDirectory() {
+        return getUserThermostatHome() + "/data/db";
+    }
+
+    public static void clearStorageDataDirectory() throws IOException {
+        File storageDir = new File(getStorageDataDirectory());
+        if (storageDir.exists()) {
+            if (storageDir.isDirectory()) {
+                deleteFilesRecursivelyUnder(storageDir);
+            } else {
+                throw new IllegalStateException(storageDir + " exists but is not a directory");
+            }
+        }
+    }
+
+    public static Process runThermostat(String... args) throws IOException {
+        List<String> completeArgs = new ArrayList<String>(args.length+1);
+        completeArgs.add(getThermostatExecutable());
+        completeArgs.addAll(Arrays.asList(args));
+        ProcessBuilder builder = buildThermostatProcess(completeArgs);
+
+        return builder.start();
+    }
+
+    public static Spawn spawnThermostat(String... args) throws IOException {
+        return spawnThermostat(false, args);
+    }
+    
+    public static Spawn startStorage() throws Exception {
+        clearStorageDataDirectory();
+
+        Spawn storage = spawnThermostat("storage", "--start");
+        try {
+            storage.expect("pid:");
+        } catch (IOException e) {
+            // this may happen if storage is already running.
+            e.printStackTrace();
+            String stdOutContents = storage.getCurrentStandardOutContents();
+            
+            System.err.flush();
+            System.out.flush();
+            System.err.println("stdout was: -->" + stdOutContents +"<--");
+            System.err.println("stderr was: -->" + storage.getCurrentStandardErrContents() + "<--");
+            System.err.flush();
+            assertFalse(stdOutContents.contains("Storage is already running with pid"));
+            throw new Exception("Something funny is going on when trying to start storage!", e);
+        }
+        storage.expectClose();
+
+        assertNoExceptions(storage.getCurrentStandardOutContents(), storage.getCurrentStandardErrContents());
+        return storage;
+    }
+    
+    public static Spawn stopStorage() throws Exception {
+        Spawn storage = spawnThermostat("storage", "--stop");
+        storage.expect("server shutdown complete");
+        storage.expectClose();
+        assertNoExceptions(storage.getCurrentStandardOutContents(), storage.getCurrentStandardErrContents());
+        return storage;
+    }
+
+    public static Spawn spawnThermostat(boolean localeDependent, String... args) throws IOException {
+        ExpectJ expect = new ExpectJ(TIMEOUT_IN_SECONDS);
+        StringBuilder result = new StringBuilder(getThermostatExecutable());
+        if (args != null) {
+            for (String arg : args) {
+                result.append(" ").append(arg);
+            }
+        }
+        String toExecute = result.toString();
+        Executor exec = null;
+        if (localeDependent) {
+            exec = new LocaleExecutor(toExecute);
+        } else {
+            exec = new SimpleExecutor(toExecute);
+        }
+        return expect.spawn(exec);
+    }
+
+    public static SpawnResult spawnThermostatAndGetProcess(String... args) throws IOException {
+        final List<String> completeArgs = new ArrayList<String>(args.length+1);
+        completeArgs.add(getThermostatExecutable());
+        completeArgs.addAll(Arrays.asList(args));
+
+        final Process[] process = new Process[1];
+
+        ExpectJ expect = new ExpectJ(TIMEOUT_IN_SECONDS);
+
+        Spawn spawn = expect.spawn(new Executor() {
+            @Override
+            public Process execute() throws IOException {
+                ProcessBuilder builder = buildThermostatProcess(completeArgs);
+                Process service = builder.start();
+                process[0] = service;
+                return service;
+            }
+        });
+
+        return new SpawnResult(process[0], spawn);
+    }
+
+    private static ProcessBuilder buildThermostatProcess(List<String> args) {
+        ProcessBuilder builder = new ProcessBuilder(args);
+        builder.environment().put(THERMOSTAT_HOME, getThermostatHome());
+        builder.environment().put(USER_THERMOSTAT_HOME, getUserThermostatHome());
+
+        return builder;
+    }
+
+    /**
+     * Generic method to run a program.
+     * <p>
+     * DO NOT USE THIS TO RUN THERMOSTAT ITSELF. It does not set up the
+     * environment correctly, using incorrect data and possibly overwriting
+     * important data.
+     */
+    public static Spawn spawn(List<String> args) throws IOException {
+        ExpectJ expect = new ExpectJ(TIMEOUT_IN_SECONDS);
+        StringBuilder result = new StringBuilder();
+        for (String arg : args) {
+            result.append(arg).append(" ");
+        }
+        return expect.spawn(result.substring(0, result.length() - 1));
+    }
+
+    /**
+     * Kill the process and all its children, recursively. Sends SIGTERM.
+     */
+    public static void killRecursively(Process process) throws Exception {
+        killRecursively(getPid(process));
+    }
+
+    private static void killRecursively(int pid) throws Exception {
+        List<Integer> childPids = findChildPids(pid);
+        for (Integer childPid : childPids) {
+            killRecursively(childPid);
+        }
+        killProcess(pid);
+    }
+
+    private static void killProcess(int processId) throws Exception {
+        System.err.println("Killing process with pid: " + processId);
+        Runtime.getRuntime().exec("kill " + processId).waitFor();
+    }
+
+    private static List<Integer> findChildPids(int processId) throws IOException {
+        String children = new String(StreamUtils.readAll(Runtime.getRuntime().exec("ps --ppid " + processId + " -o pid=").getInputStream()));
+        String[] childPids = children.split("\n");
+        List<Integer> result = new ArrayList<>();
+        for (String childPid : childPids) {
+            String pidString = childPid.trim();
+            if (pidString.length() == 0) {
+                continue;
+            }
+            try {
+                result.add(Integer.parseInt(pidString));
+            } catch (NumberFormatException nfe) {
+                System.err.println(nfe);
+            }
+        }
+        return result;
+    }
+
+    private static int getPid(Process process) throws Exception {
+        final String UNIX_PROCESS_CLASS = "java.lang.UNIXProcess";
+        if (!process.getClass().getName().equals(UNIX_PROCESS_CLASS)) {
+            throw new IllegalArgumentException("can only kill " + UNIX_PROCESS_CLASS + "; input is a " + process.getClass());
+        }
+
+        Class<?> processClass = process.getClass();
+        Field pidField = processClass.getDeclaredField("pid");
+        pidField.setAccessible(true);
+        return (int) pidField.get(process);
+    }
+
+    private static void deleteFilesRecursivelyUnder(File path) throws IOException {
+        if (!path.isDirectory()) {
+            throw new IOException("Cannot delete files under a non-directory: " + path);
+        }
+        File[] filesToDelete = path.listFiles();
+        if (filesToDelete == null) {
+            throw new IOException("Error getting directory listing: " + path);
+        }
+        for (File theFile : filesToDelete) {
+            if (theFile.isDirectory()) {
+                deleteFilesRecursivelyUnder(theFile);
+            }
+            Files.deleteIfExists(theFile.toPath());
+        }
+    }
+
+    /** Confirm that there are no 'command not found'-like messages in the spawn's stdout/stderr */
+    public static void assertCommandIsFound(Spawn spawn) {
+        assertCommandIsFound(spawn.getCurrentStandardOutContents(), spawn.getCurrentStandardErrContents());
+    }
+
+    public static void assertCommandIsFound(String stdOutContents, String stdErrContents) {
+        assertFalse(stdOutContents.contains("unknown command"));
+        assertFalse(stdErrContents.contains("unknown command"));
+    }
+
+    /** Confirm that there are no exception stack traces in the spawn's stdout/stderr */
+    public static void assertNoExceptions(Spawn spawn) {
+        assertNoExceptions(spawn.getCurrentStandardOutContents(), spawn.getCurrentStandardErrContents());
+    }
+
+    public static void assertNoExceptions(String stdOutContents, String stdErrContents) {
+        assertFalse(stdOutContents.contains("Exception"));
+        assertFalse(stdErrContents.contains("Exception"));
+    }
+
+    public static void assertOutputEndsWith(String stdOutContents, String expectedOutput) {
+        String endOfOut = stdOutContents.substring(stdOutContents.length() - expectedOutput.length());
+        assertEquals(expectedOutput, endOfOut);
+    }
+
+    public static void handleAuthPrompt(Spawn spawn, String url, String user, String password) throws IOException, TimeoutException {
+        spawn.expect("Please enter username for storage at " + url + ":");
+        spawn.send(user + "\r");
+        spawn.expect("Please enter password for storage at " + url + ":");
+        spawn.send(password + "\r");
+    }
+
+    private static class LocaleExecutor extends EnvironmentExecutor {
+
+        public static final String[] ENV_WITH_LANG_C = {
+                THERMOSTAT_HOME + "=" + getThermostatHome(),
+                USER_THERMOSTAT_HOME + "=" + getUserThermostatHome(),
+                "LANG=C"
+        };
+
+        public LocaleExecutor(String process) {
+            super(process, ENV_WITH_LANG_C);
+        }
+
+    }
+
+    private static class SimpleExecutor extends EnvironmentExecutor {
+
+        public static final String[] ENV_WITH = {
+                THERMOSTAT_HOME + "=" + getThermostatHome(),
+                USER_THERMOSTAT_HOME + "=" + getUserThermostatHome(),
+        };
+
+        public SimpleExecutor(String process) {
+            super(process, ENV_WITH);
+        }
+    }
+
+    private static class EnvironmentExecutor implements Executor {
+
+        private final String[] env;
+        private final String process;
+
+        public EnvironmentExecutor(String process, String[] env) {
+            this.process = process;
+            this.env = env;
+        }
+
+        @Override
+        public Process execute() throws IOException {
+            return Runtime.getRuntime().exec(process, env);
+        }
+
+        @Override
+        public String toString() {
+            return process;
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/MongoQueriesTest.java	Fri Apr 05 18:57:56 2013 +0200
@@ -0,0 +1,543 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.itest;
+
+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 java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.redhat.thermostat.host.cpu.common.CpuStatDAO;
+import com.redhat.thermostat.host.cpu.common.model.CpuStat;
+import com.redhat.thermostat.storage.config.StartupConfiguration;
+import com.redhat.thermostat.storage.core.Add;
+import com.redhat.thermostat.storage.core.BackingStorage;
+import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
+import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
+import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.Query;
+import com.redhat.thermostat.storage.core.Query.SortDirection;
+import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.storage.mongodb.internal.MongoStorage;
+import com.redhat.thermostat.storage.query.Expression;
+import com.redhat.thermostat.storage.query.ExpressionFactory;
+import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
+import com.redhat.thermostat.vm.classstat.common.model.VmClassStat;
+import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
+import com.redhat.thermostat.vm.cpu.common.model.VmCpuStat;
+
+/*
+ * This test class starts up a mongod instance and tests if thermostat
+ * queries with expressions return what they supposed to return. 
+ * 
+ * Tests should make their own connection to storage, probably by making use of
+ * one of the getAndConnectStorage() method variants.
+ * 
+ * Because the storage instance is shared among all of the tests, it is
+ * necessary to take precautions to avoid introducing data dependencies
+ * between tests.  Such precautions could include: using a different
+ * category (ie mongod collection) than any other existing test; setting
+ * a unique agent-id for all data written and then deleting the data
+ * at the end of the test; <insert other clever idea here>.
+ * 
+ */
+public class MongoQueriesTest extends IntegrationTest {
+
+    private static class CountdownConnectionListener implements ConnectionListener {
+
+        private final ConnectionStatus target;
+        private final CountDownLatch latch;
+
+        private CountdownConnectionListener(ConnectionStatus target, CountDownLatch latch) {
+            this.target = target;
+            this.latch = latch;
+        }
+
+        @Override
+        public void changed(ConnectionStatus newStatus) {
+            assertEquals(target, newStatus);
+            latch.countDown();
+        }
+    }
+
+    private static final double EQUALS_DELTA = 0.00000000000001;
+    private static final String VM_ID1 = "vmId1";
+    private static final String VM_ID2 = "vmId2";
+    private static final String VM_ID3 = "vmId3";
+
+    private ExpressionFactory factory = new ExpressionFactory();
+
+    @BeforeClass
+    public static void setUpOnce() throws Exception {
+        startStorage();
+
+        addCpuData(4);
+    }
+
+    @AfterClass
+    public static void tearDownOnce() throws Exception {
+        deleteCpuData();
+
+        stopStorage();
+    }
+
+    /*
+     * Make a connection to mongo storage (returning the Storage object). Before
+     * initiating the connection, add the ConnectionListener to Storage.
+     */
+    private static BackingStorage getAndConnectStorage(ConnectionListener listener) {
+        final String url = "mongodb://127.0.0.1:27518";
+        StartupConfiguration config = new StartupConfiguration() {
+
+            @Override
+            public String getDBConnectionString() {
+                return url;
+            }
+            
+        };
+        BackingStorage storage = new MongoStorage(config);
+        if (listener != null) {
+            storage.getConnection().addListener(listener);
+        }
+        storage.getConnection().connect();
+        return storage;
+    }
+
+    private static void addCpuData(int numberOfItems) throws InterruptedException {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        BackingStorage storage = getAndConnectStorage(listener);
+        latch.await();
+        storage.getConnection().removeListener(listener);
+        
+        storage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        for (int i = 0; i < numberOfItems; i++) {
+            CpuStat pojo = new CpuStat("test-agent-id", i, new double[] {i, i*2});
+            Add<CpuStat> add = storage.createAdd(CpuStatDAO.cpuStatCategory);
+            add.set(Key.AGENT_ID.getName(), pojo.getAgentId());
+            add.set(CpuStatDAO.cpuLoadKey.getName(), pojo.getPerProcessorUsage());
+            add.set(Key.TIMESTAMP.getName(), pojo.getTimeStamp());
+            add.apply();
+        }
+
+        storage.getConnection().disconnect();
+    }
+
+    private static void deleteCpuData() throws InterruptedException {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        Storage storage = getAndConnectStorage(listener);
+        latch.await();
+        storage.getConnection().removeListener(listener);
+        storage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        storage.purge("test-agent-id");
+
+        storage.getConnection().disconnect();
+    }
+
+    private void executeAndVerifyQuery(Query<CpuStat> query, List<Long> expectedTimestamps) {
+        Cursor<CpuStat> cursor = query.execute();
+
+        for (Long time : expectedTimestamps) {
+            assertTrue(cursor.hasNext());
+            CpuStat pojo = cursor.next();
+            assertEquals("test-agent-id", pojo.getAgentId());
+            assertEquals(time.longValue(), pojo.getTimeStamp());
+            double[] data = pojo.getPerProcessorUsage();
+            assertEquals(time, data[0], EQUALS_DELTA);
+            assertEquals(time*2, data[1], EQUALS_DELTA);
+        }
+        assertFalse(cursor.hasNext());
+    }
+
+    @Test
+    public void testMongoAdd() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
+        latch.await();
+        mongoStorage.getConnection().removeListener(listener);
+        mongoStorage.registerCategory(VmClassStatDAO.vmClassStatsCategory);
+        
+        Add<VmClassStat> add = mongoStorage.createAdd(VmClassStatDAO.vmClassStatsCategory);
+        VmClassStat pojo = new VmClassStat();
+        pojo.setAgentId("fluff");
+        pojo.setLoadedClasses(12345);
+        pojo.setTimeStamp(42);
+        pojo.setVmId(VM_ID1);
+        addVmClassStat(add, pojo);
+        
+        // Add another couple of entries
+        add = mongoStorage.createAdd(VmClassStatDAO.vmClassStatsCategory);
+        pojo = new VmClassStat();
+        pojo.setAgentId("fluff");
+        pojo.setLoadedClasses(67890);
+        pojo.setTimeStamp(42);
+        pojo.setVmId(VM_ID2);
+        addVmClassStat(add, pojo);
+        
+        add = mongoStorage.createAdd(VmClassStatDAO.vmClassStatsCategory);
+        pojo = new VmClassStat();
+        pojo.setAgentId("fluff");
+        pojo.setLoadedClasses(34567);
+        pojo.setTimeStamp(42);
+        pojo.setVmId(VM_ID3);
+        addVmClassStat(add, pojo);
+
+        mongoStorage.getConnection().disconnect();
+    }
+
+    private void addVmClassStat(Add<VmClassStat> add, VmClassStat pojo) {
+        add.set(Key.AGENT_ID.getName(), pojo.getAgentId());
+        add.set(Key.VM_ID.getName(), pojo.getVmId());
+        add.set(Key.TIMESTAMP.getName(), pojo.getTimeStamp());
+        add.set(VmClassStatDAO.loadedClassesKey.getName(), pojo.getLoadedClasses());
+        add.apply();
+    }
+
+    @Test
+    public void canQueryNoWhere() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
+        latch.await();
+        mongoStorage.getConnection().removeListener(listener);
+        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+        
+        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
+        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
+
+        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 2l, 3l));
+
+        mongoStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void canQueryEqualTo() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
+        latch.await();
+        mongoStorage.getConnection().removeListener(listener);
+        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
+        Expression expr = factory.equalTo(Key.TIMESTAMP, 2l);
+        query.where(expr);
+        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
+
+        executeAndVerifyQuery(query, Arrays.asList(2l));
+
+        mongoStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void canQueryNotEqualTo() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
+        latch.await();
+        mongoStorage.getConnection().removeListener(listener);
+        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
+        Expression expr = factory.notEqualTo(Key.TIMESTAMP, 2l);
+        query.where(expr);
+        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
+
+        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 3l));
+
+        mongoStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void canQueryGreaterThan() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
+        latch.await();
+        mongoStorage.getConnection().removeListener(listener);
+        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
+        Expression expr = factory.greaterThan(Key.TIMESTAMP, 2l);
+        query.where(expr);
+        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
+
+        executeAndVerifyQuery(query, Arrays.asList(3l));
+
+        mongoStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void canQueryGreaterThanOrEqualTo() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
+        latch.await();
+        mongoStorage.getConnection().removeListener(listener);
+        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
+        Expression expr = factory.greaterThanOrEqualTo(Key.TIMESTAMP, 2l);
+        query.where(expr);
+        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
+
+        executeAndVerifyQuery(query, Arrays.asList(2l, 3l));
+
+        mongoStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void canQueryLessThan() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
+        latch.await();
+        mongoStorage.getConnection().removeListener(listener);
+        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
+        Expression expr = factory.lessThan(Key.TIMESTAMP, 2l);
+        query.where(expr);
+        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
+
+        executeAndVerifyQuery(query, Arrays.asList(0l, 1l));
+
+        mongoStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void canQueryLessThanOrEqualTo() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
+        latch.await();
+        mongoStorage.getConnection().removeListener(listener);
+        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
+        Expression expr = factory.lessThanOrEqualTo(Key.TIMESTAMP, 2l);
+        query.where(expr);
+        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
+
+        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 2l));
+
+        mongoStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void canQueryIn() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
+        latch.await();
+        mongoStorage.getConnection().removeListener(listener);
+        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        List<Long> times = Arrays.asList(0l, 2l);
+        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
+        Expression expr = factory.in(Key.TIMESTAMP, new HashSet<>(times), Long.class);
+        query.where(expr);
+        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
+
+        executeAndVerifyQuery(query, times);
+
+        mongoStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void canQueryNotIn() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
+        latch.await();
+        mongoStorage.getConnection().removeListener(listener);
+        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
+        Expression expr = factory.notIn(Key.TIMESTAMP, new HashSet<>(Arrays.asList(0l, 2l)), Long.class);
+        query.where(expr);
+        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
+
+        executeAndVerifyQuery(query, Arrays.asList(1l, 3l));
+
+        mongoStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void canQueryNot() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
+        latch.await();
+        mongoStorage.getConnection().removeListener(listener);
+        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
+        Expression expr = factory.not(factory.greaterThan(Key.TIMESTAMP, 2l));
+        query.where(expr);
+        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
+
+        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 2l));
+
+        mongoStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void canQueryAnd() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
+        latch.await();
+        mongoStorage.getConnection().removeListener(listener);
+        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
+        Expression expr = factory.and(factory.greaterThan(Key.TIMESTAMP, 0l),
+                                      factory.lessThan(Key.TIMESTAMP, 2l));
+        query.where(expr);
+        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
+
+        executeAndVerifyQuery(query, Arrays.asList(1l));
+
+        mongoStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void canQueryOr() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
+        latch.await();
+        mongoStorage.getConnection().removeListener(listener);
+        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
+        Expression expr = factory.or(factory.greaterThan(Key.TIMESTAMP, 2l),
+                                      factory.lessThan(Key.TIMESTAMP, 1l));
+        query.where(expr);
+        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
+
+        executeAndVerifyQuery(query, Arrays.asList(0l, 3l));
+
+        mongoStorage.getConnection().disconnect();
+    }
+
+    @Test
+    public void canLoadSave() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        Storage mongoStorage = getAndConnectStorage(listener);
+        latch.await();
+        mongoStorage.getConnection().removeListener(listener);
+        
+        byte[] data = "Hello World".getBytes();
+        mongoStorage.saveFile("test", new ByteArrayInputStream(data));
+        // Note: On the server side, the file is saved into mongodb
+        // via GridFS.  The save operation returns before write is
+        // complete, and there is no callback mechanism to find out
+        // when the write is complete.  So, we try a few times to
+        // load it before considering it a failure.
+        InputStream loadStream = null;
+        int loadAttempts = 0;
+        while (loadStream == null && loadAttempts < 3) {
+            Thread.sleep(300);
+            loadStream = mongoStorage.loadFile("test");
+            loadAttempts++;
+        }
+        assertNotNull(loadStream);
+        StringBuilder str = new StringBuilder();
+        int i = loadStream.read();
+        while (i != -1) {
+            str.append((char) i);
+            i = loadStream.read();
+        }
+        assertEquals("Hello World", str.toString());
+
+        mongoStorage.getConnection().disconnect();
+    }
+
+    @Test
+    public void storagePurge() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
+        BackingStorage mongoStorage = getAndConnectStorage(listener);
+        latch.await();
+        mongoStorage.getConnection().removeListener(listener);
+        UUID uuid = new UUID(42, 24);
+
+        mongoStorage.registerCategory(VmCpuStatDAO.vmCpuStatCategory);
+        long timeStamp = 5;
+        double cpuLoad = 0.15;
+        VmCpuStat pojo = new VmCpuStat(uuid.toString(), timeStamp, VM_ID1, cpuLoad);
+        Add<VmCpuStat> add = mongoStorage.createAdd(VmCpuStatDAO.vmCpuStatCategory);
+        add.set(Key.AGENT_ID.getName(), pojo.getAgentId());
+        add.set(Key.VM_ID.getName(), pojo.getVmId());
+        add.set(Key.TIMESTAMP.getName(), pojo.getTimeStamp());
+        add.set(VmCpuStatDAO.vmCpuLoadKey.getName(), pojo.getCpuLoad());
+        add.apply();
+
+        Query<VmCpuStat> query = mongoStorage.createQuery(VmCpuStatDAO.vmCpuStatCategory);
+        Cursor<VmCpuStat> cursor = query.execute();
+        assertTrue(cursor.hasNext());
+        pojo = cursor.next();
+        assertFalse(cursor.hasNext());
+
+        assertEquals(timeStamp, pojo.getTimeStamp());
+        assertEquals(VM_ID1, pojo.getVmId());
+        assertEquals(cpuLoad, pojo.getCpuLoad(), EQUALS_DELTA);
+        assertEquals(uuid.toString(), pojo.getAgentId());
+
+        mongoStorage.purge(uuid.toString());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/PluginTest.java	Fri Apr 05 18:57:56 2013 +0200
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.itest;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import expectj.Spawn;
+
+public class PluginTest extends IntegrationTest {
+
+    private static final String PLUGIN_HOME = getSystemPluginHome();
+
+    private static NewCommandPlugin fooPlugin = new NewCommandPlugin("foo", "provides foo command", PLUGIN_HOME + File.separator + "new");
+    private static NewCommandPlugin userPlugin = new NewCommandPlugin(
+            "user",
+            "a plugin that is provided by the user",
+            getUserThermostatHome() + File.separator + "data" + File.separator + "plugins" + File.separator + "user");
+    private static UnknownExtendsPlugin unknownExtension = new UnknownExtendsPlugin(PLUGIN_HOME + File.separator + "unknown");
+
+    @BeforeClass
+    public static void setUpOnce() {
+        fooPlugin.install();
+        userPlugin.install();
+        unknownExtension.install();
+    }
+
+    @AfterClass
+    public static void tearDownOnce() {
+        unknownExtension.uninstall();
+        userPlugin.uninstall();
+        fooPlugin.uninstall();
+    }
+
+    @Test
+    public void testHelpIsOkay() throws Exception {
+        Spawn shell = spawnThermostat("help");
+        shell.expectClose();
+
+        String stdOut = shell.getCurrentStandardOutContents();
+
+        assertTrue(stdOut.contains("list of commands"));
+        assertTrue(stdOut.contains("help"));
+        assertTrue(stdOut.contains("agent"));
+        assertTrue(stdOut.contains("gui"));
+        assertTrue(stdOut.contains("ping"));
+        assertTrue(stdOut.contains("shell"));
+
+        assertTrue(stdOut.contains(fooPlugin.command));
+        assertTrue(stdOut.contains(fooPlugin.description));
+
+        assertTrue(stdOut.contains(userPlugin.command));
+
+        assertFalse(stdOut.contains(unknownExtension.command));
+
+        // TODO assertEquals("", stdErr);
+    }
+
+    /**
+     * This plugin provides a new command
+     */
+    private static class NewCommandPlugin {
+
+        private final String pluginHome;
+        private final String command;
+        private final String description;
+
+        public NewCommandPlugin(String command, String description, String pluginLocation) {
+            this.pluginHome = pluginLocation;
+
+            this.command = command;
+            this.description = description;
+        }
+
+        private void install() {
+            File home = new File(pluginHome);
+            if (!home.isDirectory() && !home.mkdirs()) {
+                throw new AssertionError("could not create directory: " + pluginHome);
+            }
+
+            String pluginContents = "" +
+                    "<?xml version=\"1.0\"?>\n" +
+                    "<plugin xmlns=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\"\n" +
+                    " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+                    " xsi:schemaLocation=\"http://icedtea.classpath.org/thermostat/plugins/v1.0 thermost-plugin.xsd\">\n" +
+                    "  <commands>" +
+                    "    <command>" +
+                    "      <name>" + command + "</name>" +
+                    "      <description>" + description + "</description>" +
+                    "      <options>" +
+                    "        <option>" +
+                    "         <long>aaaaa</long>" +
+                    "         <short>a</short>" +
+                    "        </option>" +
+                    "      </options>" +
+                    "      <environments>" +
+                    "        <environment>shell</environment>" +
+                    "        <environment>cli</environment>" +
+                    "      </environments>" +
+                    "      <bundles>" +
+                    "        <bundle>" +
+                    "          <symbolic-name>bar</symbolic-name>" +
+                    "          <version>0.1.0</version>" +
+                    "        </bundle>" +
+                    "      </bundles>" +
+                    "    </command>" +
+                    "  </commands>" +
+                    "</plugin>";
+
+            try (FileWriter writer = new FileWriter(pluginHome + File.separator + "thermostat-plugin.xml")) {
+                writer.write(pluginContents);
+            } catch (IOException e) {
+                throw new AssertionError("unable to write plugin configuration", e);
+            }
+
+        }
+
+        private void uninstall() {
+            if (!new File(pluginHome).exists()) {
+                return;
+            }
+            if (!new File(pluginHome, "thermostat-plugin.xml").delete()) {
+                throw new AssertionError("Could not delete plugin file");
+            }
+            if (!new File(pluginHome).delete()) {
+                throw new AssertionError("Could not delete plugin directory");
+            }
+        }
+    }
+
+    /**
+     * This plugin extends an unknown command
+     */
+    private static class UnknownExtendsPlugin {
+
+        private final String pluginHome;
+        private final String command;
+
+        public UnknownExtendsPlugin(String pluginLocation) {
+            this.pluginHome = pluginLocation;
+
+            this.command = "unknown-command";
+        }
+
+        private void install() {
+            File home = new File(pluginHome);
+            if (!home.isDirectory() && !home.mkdir()) {
+                throw new AssertionError("could not create directory: " + pluginHome);
+            }
+
+            String pluginContents = "" +
+                    "<?xml version=\"1.0\"?>\n" +
+                    "<plugin xmlns=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\"\n" +
+                    " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
+                    " xsi:schemaLocation=\"http://icedtea.classpath.org/thermostat/plugins/v1.0 thermost-plugin.xsd\">\n" +
+                    "  <extensions>" +
+                    "    <extension>" +
+                    "      <name>" + command + "</name>" +
+                    "      <bundles>" +
+                    "        <bundle>" +
+                    "          <symbolic-name>bar</symbolic-name>" +
+                    "          <version>0.1.0</version>" +
+                    "        </bundle>" +
+                    "      </bundles>" +
+                    "    </extension>" +
+                    "  </extensions>" +
+                    "</plugin>";
+
+            try (FileWriter writer = new FileWriter(pluginHome + File.separator + "thermostat-plugin.xml")) {
+                writer.write(pluginContents);
+            } catch (IOException e) {
+                throw new AssertionError("unable to write plugin configuration", e);
+            }
+
+        }
+
+        private void uninstall() {
+            if (!new File(pluginHome).exists()) {
+                return;
+            }
+            if (!new File(pluginHome, "thermostat-plugin.xml").delete()) {
+                throw new AssertionError("Could not delete plugin file");
+            }
+            if (!new File(pluginHome).delete()) {
+                throw new AssertionError("Could not delete plugin directory");
+            }
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/StorageConnectionTest.java	Fri Apr 05 18:57:56 2013 +0200
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.itest;
+
+import java.io.IOException;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import expectj.ExpectJException;
+import expectj.Spawn;
+import expectj.TimeoutException;
+
+public class StorageConnectionTest extends IntegrationTest {
+
+    // @BeforeClass // reinstate once we actually need storage running (see ignored tests)
+    public static void setUpOnce() throws Exception {
+        startStorage();
+    }
+
+    // @AfterClass // reinstate once we actually need storage running
+    public static void tearDownOnce() throws Exception {
+        stopStorage();
+    }
+
+    @Ignore //FIXME when keyring/preferences improvements have been made, un-Ignore
+    @Test
+    public void testConnect() throws ExpectJException, TimeoutException, IOException {
+        Spawn shell = spawnThermostat(true, "shell");
+
+        shell.expect(SHELL_PROMPT);
+        shell.send("connect -d mongodb://127.0.0.1:27518\n");
+        handleAuthPrompt(shell, "mongodb://127.0.0.1:27518", "", "");
+        shell.expect(SHELL_PROMPT);
+        shell.send("exit\n");
+        shell.expectClose();
+
+        assertCommandIsFound(shell.getCurrentStandardOutContents(), shell.getCurrentStandardErrContents());
+        assertNoExceptions(shell.getCurrentStandardOutContents(), shell.getCurrentStandardErrContents());
+    }
+
+    @Test
+    public void testDisconnectWithoutConnecting() throws ExpectJException, TimeoutException, IOException {
+        Spawn shell = spawnThermostat("shell");
+
+        shell.expect(SHELL_PROMPT);
+        shell.send("disconnect\n");
+        shell.expect(SHELL_PROMPT);
+        shell.send("exit\n");
+        shell.expectClose();
+
+        assertCommandIsFound(shell.getCurrentStandardOutContents(), shell.getCurrentStandardErrContents());
+        assertNoExceptions(shell.getCurrentStandardOutContents(), shell.getCurrentStandardErrContents());
+    }
+
+    @Ignore //FIXME when keyring/preferences improvements have been made, un-Ignore
+    @Test
+    public void testConnectAndDisconnectInShell() throws IOException, TimeoutException, ExpectJException {
+        Spawn shell = spawnThermostat(true, "shell");
+
+        shell.expect(SHELL_PROMPT);
+        shell.send("connect -d mongodb://127.0.0.1:27518\n");
+        handleAuthPrompt(shell, "mongodb://127.0.0.1:27518", "", "");
+        shell.expect(SHELL_PROMPT);
+        shell.send("disconnect\n");
+        shell.send("exit\n");
+        shell.expectClose();
+
+        assertNoExceptions(shell.getCurrentStandardOutContents(), shell.getCurrentStandardErrContents());
+    }
+
+    // TODO add a test to make sure connect/disconnect is not visible outside the shell
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/StorageTest.java	Fri Apr 05 18:57:56 2013 +0200
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.itest;
+
+import org.junit.Test;
+
+import expectj.Spawn;
+
+public class StorageTest extends IntegrationTest {
+
+    @Test
+    public void startAndStopStorage() throws Exception {
+        Spawn storage;
+
+        storage = startStorage();
+
+        storage = spawnThermostat("storage", "--status");
+        storage.expect("Storage is running");
+        storage.expectClose();
+        
+        assertNoExceptions(storage.getCurrentStandardOutContents(), storage.getCurrentStandardErrContents());
+        
+        storage = stopStorage();
+        
+        storage = spawnThermostat("storage", "--status");
+        storage.expect("Storage is not running");
+        storage.expectClose();
+
+        assertNoExceptions(storage.getCurrentStandardOutContents(), storage.getCurrentStandardErrContents());
+    }
+
+    @Test
+    public void testServiceStartAndKilling() throws Exception {
+
+        SpawnResult spawnResult = spawnThermostatAndGetProcess("service");
+        Spawn service = spawnResult.spawn;
+
+        try {
+            service.expectErr("agent started");
+        } finally {
+            killRecursively(spawnResult.process);
+        }
+
+        service.stop();
+        service.expectClose();
+
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/VmCommandsTest.java	Fri Apr 05 18:57:56 2013 +0200
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.itest;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import expectj.Spawn;
+
+/** Integration tests for the various vm commands */
+public class VmCommandsTest extends IntegrationTest {
+
+    @BeforeClass
+    public static void setUpOnce() throws Exception {
+        startStorage();
+
+        // TODO insert actual data into the database and test that
+    }
+
+    @AfterClass
+    public static void tearDownOnce() throws Exception {
+        stopStorage();
+    }
+
+    @Ignore //FIXME when keyring/preferences improvements have been made, un-Ignore
+    @Test
+    public void testListVms() throws Exception {
+        Spawn vmList = commandAgainstMongo("list-vms");
+        handleAuthPrompt(vmList, "mongodb://127.0.0.1:27518", "", "");
+        vmList.expectClose();
+        assertOutputEndsWith(vmList.getCurrentStandardOutContents(), "HOST_ID HOST VM_ID STATUS VM_NAME\n\n");
+    }
+
+    @Test
+    public void testVmStat() throws Exception {
+        Spawn vmStat = commandAgainstMongo("vm-stat");
+        // TODO include required options to test meaningfully
+        //handleAuthPrompt(vmStat, "mongodb://127.0.0.1:27518", "", "");
+        vmStat.expectClose();
+
+        System.out.println(vmStat.getCurrentStandardOutContents());
+        assertCommandIsFound(vmStat.getCurrentStandardOutContents(), vmStat.getCurrentStandardErrContents());
+        assertNoExceptions(vmStat.getCurrentStandardOutContents(), vmStat.getCurrentStandardErrContents());
+    }
+
+    @Test
+    public void testVmInfo() throws Exception {
+        Spawn vmInfo = commandAgainstMongo("vm-info");
+        // TODO include required options to test meaningfully
+        // handleAuthPrompt(vmInfo, "mongodb://127.0.0.1:27518", "", "");
+        vmInfo.expectClose();
+
+        assertNoExceptions(vmInfo.getCurrentStandardOutContents(), vmInfo.getCurrentStandardErrContents());
+    }
+
+    @Ignore //FIXME when keyring/preferences improvements have been made, un-Ignore
+    @Test
+    public void testHeapCommands() throws Exception {
+        String[] commands = new String[] {
+                "dump-heap",
+                "list-heap-dumps",
+                "save-heap-dump-to-file",
+                "show-heap-histogram",
+                "find-objects",
+                "object-info",
+                "find-root"
+        };
+
+        for (String command : commands) {
+            Spawn heapCommand = commandAgainstMongo(command);
+            // TODO include required options to test each command meaningfully
+            if (command.equals("list-heap-dumps")) {
+                // No missing options, times out waiting for user/pass input without the following:
+                handleAuthPrompt(heapCommand, "mongodb://127.0.0.1:27518", "", "");
+            }
+            heapCommand.expectClose();
+
+            assertCommandIsFound(
+                    heapCommand.getCurrentStandardOutContents(),
+                    heapCommand.getCurrentStandardErrContents());
+            assertNoExceptions(
+                    heapCommand.getCurrentStandardOutContents(),
+                    heapCommand.getCurrentStandardErrContents());
+        }
+    }
+
+    @Test
+    public void testNormalCommandAndPluginInShell() throws Exception {
+        Spawn shell = spawnThermostat("shell");
+
+        shell.expect(SHELL_PROMPT);
+        shell.send("list-vms\n");
+
+        shell.expect(SHELL_PROMPT);
+
+        shell.send("dump-heap\n");
+
+        shell.expect(SHELL_PROMPT);
+
+        shell.send("exit\n");
+        shell.expectClose();
+
+        assertCommandIsFound(shell);
+        assertNoExceptions(shell);
+    }
+
+    private static Spawn commandAgainstMongo(String... args) throws IOException {
+        if (args == null || args.length == 0) {
+            throw new IllegalArgumentException("args must be an array with something");
+        }
+        List<String> completeArgs = new ArrayList<>();
+        completeArgs.addAll(Arrays.asList(args));
+        completeArgs.add("-d");
+        completeArgs.add("mongodb://127.0.0.1:27518");
+        return spawnThermostat(true, completeArgs.toArray(new String[0]));
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/WebAppTest.java	Fri Apr 05 18:57:56 2013 +0200
@@ -0,0 +1,1092 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.itest;
+
+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.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Properties;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.component.LifeCycle;
+import org.eclipse.jetty.util.component.LifeCycle.Listener;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.redhat.thermostat.common.ApplicationInfo;
+import com.redhat.thermostat.host.cpu.common.CpuStatDAO;
+import com.redhat.thermostat.host.cpu.common.model.CpuStat;
+import com.redhat.thermostat.storage.config.ConnectionConfiguration;
+import com.redhat.thermostat.storage.config.StartupConfiguration;
+import com.redhat.thermostat.storage.core.Add;
+import com.redhat.thermostat.storage.core.BackingStorage;
+import com.redhat.thermostat.storage.core.Category;
+import com.redhat.thermostat.storage.core.CategoryAdapter;
+import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
+import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
+import com.redhat.thermostat.storage.core.Cursor;
+import com.redhat.thermostat.storage.core.DescriptorParsingException;
+import com.redhat.thermostat.storage.core.IllegalDescriptorException;
+import com.redhat.thermostat.storage.core.Key;
+import com.redhat.thermostat.storage.core.PreparedStatement;
+import com.redhat.thermostat.storage.core.Remove;
+import com.redhat.thermostat.storage.core.StatementDescriptor;
+import com.redhat.thermostat.storage.core.StatementExecutionException;
+import com.redhat.thermostat.storage.core.Storage;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.dao.HostInfoDAO;
+import com.redhat.thermostat.storage.model.AggregateCount;
+import com.redhat.thermostat.storage.model.HostInfo;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.mongodb.internal.MongoStorage;
+import com.redhat.thermostat.storage.query.Expression;
+import com.redhat.thermostat.storage.query.ExpressionFactory;
+import com.redhat.thermostat.test.FreePortFinder;
+import com.redhat.thermostat.test.FreePortFinder.TryPort;
+import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
+import com.redhat.thermostat.vm.classstat.common.model.VmClassStat;
+import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
+import com.redhat.thermostat.vm.cpu.common.model.VmCpuStat;
+import com.redhat.thermostat.web.client.internal.WebStorage;
+import com.redhat.thermostat.web.server.auth.Roles;
+
+/**
+ * This test class starts up a mongod instance and a web storage instance
+ * (in jetty container) in front of that.  Tests should make their own
+ * connection to the web storage, probably by making use of one of the
+ * getAndConnectStorage() method variants.
+ * 
+ * Because the storage instance is shared among all of the tests, it is
+ * necessary to take precautions to avoid introducing data dependencies
+ * between tests.  Such precautions could include: using a different
+ * category (ie mongod collection) than any other existing test; setting
+ * a unique agent-id for all data written and then deleting the data
+ * at the end of the test; <insert other clever idea here>.
+ * 
+ * Please don't introduce any more sporadic test failures to this
+ * integration test!!!
+ */
+public class WebAppTest extends IntegrationTest {
+
+    /*
+     * Registry of descriptors this test needs to allow in order to avoid
+     * illegal statement descriptor exceptions being thrown. See also:
+     * WebAppTestStatementDescriptorRegistration
+     */
+    public static final Set<String> TRUSTED_DESCRIPTORS;
+    /*
+     * Map which maps a string descriptor to DescriptorMetadata.
+     * See also: WebAppTestStatementDescriptorRegistration
+     * 
+     */
+    public static final Map<String, DescriptorMetadata> METADATA_MAPPING;
+    // descriptive name -> descriptor mapping
+    private static final Map<String, String> DESCRIPTOR_MAP;
+    
+    private static final String KEY_AUTHORIZED_QUERY = "authorizedQuery";
+    private static final String KEY_AUTHORIZED_QUERY_EQUAL_TO = "authorizedQueryEqualTo";
+    private static final String KEY_AUTHORIZED_QUERY_NOT_EQUAL_TO = "authorizedQueryNotEqualTo";
+    private static final String KEY_AUTHORIZED_QUERY_GREATER_THAN = "authorizedQueryGreaterThan";
+    private static final String KEY_AUTHORIZED_QUERY_GREATER_THAN_OR_EQUAL_TO = "authorizedQueryGreaterThanOrEqualTo";
+    private static final String KEY_AUTHORIZED_QUERY_LESS_THAN = "authorizedQueryLessThan";
+    private static final String KEY_AUTHORIZED_QUERY_LESS_THAN_OR_EQUAL_TO = "authorizedQueryLessThanOrEqualTo";
+    private static final String KEY_AUTHORIZED_QUERY_NOT = "authorizedQueryNot";
+    private static final String KEY_AUTHORIZED_QUERY_AND = "authorizedQueryAnd";
+    private static final String KEY_AUTHORIZED_QUERY_OR = "authorizedQueryOr";
+    private static final String KEY_STORAGE_PURGE = "storagePurge";
+    private static final String KEY_AUTHORIZED_FILTERED_QUERY = "authorizedFilteredQuerySubset";
+    
+    static {
+        Map<String, String> descMap = new HashMap<>();
+        descMap.put(KEY_AUTHORIZED_FILTERED_QUERY, "QUERY agent-config");
+        descMap.put(KEY_AUTHORIZED_QUERY, "QUERY cpu-stats SORT ?s ASC");
+        descMap.put(KEY_AUTHORIZED_QUERY_EQUAL_TO, "QUERY cpu-stats WHERE 'timeStamp' = ?l SORT 'timeStamp' ASC");
+        descMap.put(KEY_AUTHORIZED_QUERY_NOT_EQUAL_TO, "QUERY cpu-stats WHERE 'timeStamp' != ?l SORT 'timeStamp' ASC");
+        descMap.put(KEY_AUTHORIZED_QUERY_GREATER_THAN, "QUERY cpu-stats WHERE 'timeStamp' > ?l SORT 'timeStamp' ASC");
+        descMap.put(KEY_AUTHORIZED_QUERY_GREATER_THAN_OR_EQUAL_TO, "QUERY cpu-stats WHERE 'timeStamp' >= ?l SORT 'timeStamp' ASC");
+        descMap.put(KEY_AUTHORIZED_QUERY_LESS_THAN, "QUERY cpu-stats WHERE 'timeStamp' < ?l SORT 'timeStamp' ASC");
+        descMap.put(KEY_AUTHORIZED_QUERY_LESS_THAN_OR_EQUAL_TO, "QUERY cpu-stats WHERE 'timeStamp' <= ?l SORT 'timeStamp' ASC");
+        descMap.put(KEY_AUTHORIZED_QUERY_NOT, "QUERY cpu-stats WHERE NOT 'timeStamp' > ?l SORT 'timeStamp' ASC");
+        descMap.put(KEY_AUTHORIZED_QUERY_AND, "QUERY cpu-stats WHERE 'timeStamp' > 0 AND 'timeStamp' < ?l SORT 'timeStamp' ASC");
+        descMap.put(KEY_AUTHORIZED_QUERY_OR, "QUERY cpu-stats WHERE 'timeStamp' > ?l OR 'timeStamp' < ?l SORT 'timeStamp' ASC");
+        descMap.put(KEY_STORAGE_PURGE, "QUERY vm-cpu-stats");
+        Set<String> trustedDescriptors = new HashSet<>();
+        Map<String, DescriptorMetadata> metadata = new HashMap<>();
+        DescriptorMetadata descMetadata = new DescriptorMetadata();
+        for (String val: descMap.values()) {
+            trustedDescriptors.add(val);
+            metadata.put(val, descMetadata);
+        }
+        TRUSTED_DESCRIPTORS = trustedDescriptors;
+        DESCRIPTOR_MAP = descMap;
+        METADATA_MAPPING = metadata;
+    }
+    
+    
+    private static class CountdownConnectionListener implements ConnectionListener {
+
+        private final ConnectionStatus target;
+        private final CountDownLatch latch;
+        private final AtomicBoolean indicator;
+
+        private CountdownConnectionListener(ConnectionStatus target, CountDownLatch latch, AtomicBoolean indicator) {
+            this.target = target;
+            this.latch = latch;
+            this.indicator = indicator;
+        }
+
+        @Override
+        public void changed(ConnectionStatus newStatus) {
+            indicator.set(true);
+            assertEquals(target, newStatus);
+            latch.countDown();
+        }
+    }
+
+    private static class WebAppContextListener implements Listener {
+        private Throwable cause;
+        private boolean failed = false;
+        private final CountDownLatch contextStartedLatch;
+        private WebAppContextListener(CountDownLatch latch) {
+            this.contextStartedLatch = latch;
+        }
+
+        @Override
+        public void lifeCycleStarting(LifeCycle event) {
+            // nothing
+        }
+
+        @Override
+        public void lifeCycleStarted(LifeCycle event) {
+            contextStartedLatch.countDown();
+        }
+
+        @Override
+        public void lifeCycleFailure(LifeCycle event, Throwable cause) {
+            this.failed = true;
+            this.cause = cause;
+            contextStartedLatch.countDown();
+        }
+
+        @Override
+        public void lifeCycleStopping(LifeCycle event) {
+            // nothing
+        }
+
+        @Override
+        public void lifeCycleStopped(LifeCycle event) {
+            // nothing
+        }
+    }
+
+    private static final String TEST_USER = "testuser";
+    private static final String TEST_PASSWORD = "testpassword";
+    private static final String PREP_USER = "prepuser";
+    private static final String PREP_PASSWORD = "preppassword";
+    private static final double EQUALS_DELTA = 0.00000000000001;
+    private static final String THERMOSTAT_USERS_FILE = getConfigurationDir() + "/thermostat-users.properties";
+    private static final String THERMOSTAT_ROLES_FILE = getConfigurationDir() + "/thermostat-roles.properties";
+    private static final String VM_ID1 = "vmId1";
+    private static final String VM_ID2 = "vmId2";
+    private static final String VM_ID3 = "vmId3";
+
+    private static Server server;
+    private static int port;
+    private static Path backupUsers;
+    private static Path backupRoles;
+
+    @BeforeClass
+    public static void setUpOnce() throws Exception {
+        startStorage();
+
+        backupUsers = Files.createTempFile("itest-backup-thermostat-users", "");
+        backupRoles = Files.createTempFile("itest-backup-thermostat-roles", "");
+        backupRoles.toFile().deleteOnExit();
+        backupUsers.toFile().deleteOnExit();
+        Files.copy(new File(THERMOSTAT_USERS_FILE).toPath(), backupUsers, StandardCopyOption.REPLACE_EXISTING);
+        Files.copy(new File(THERMOSTAT_ROLES_FILE).toPath(), backupRoles, StandardCopyOption.REPLACE_EXISTING);
+
+
+        // start the server, deploy the war
+        port = FreePortFinder.findFreePort(new TryPort() {
+            
+            @Override
+            public void tryPort(int port) throws Exception {
+                startServer(port);
+            }
+        });
+
+        addCpuData(4);
+    }
+
+    @AfterClass
+    public static void tearDownOnce() throws Exception {
+        deleteCpuData();
+
+        server.stop();
+        server.join();
+        
+        stopStorage();
+
+        Files.copy(backupUsers, new File(THERMOSTAT_USERS_FILE).toPath(), StandardCopyOption.REPLACE_EXISTING);
+        Files.copy(backupRoles, new File(THERMOSTAT_ROLES_FILE).toPath(), StandardCopyOption.REPLACE_EXISTING);
+    }
+    
+    /*
+     * Queries tests use write operations to put things into storage. For them
+     * we don't want to go through the hassles of using prepared writes. Instead
+     * use mongo-storage directly (which is a BackingStorage). 
+     */
+    private static BackingStorage getAndConnectBackingStorage() {
+        String url = "mongodb://127.0.0.1:27518";
+        StartupConfiguration config = new ConnectionConfiguration(url, "", "");
+        BackingStorage storage = new MongoStorage(config);
+        storage.getConnection().connect();
+        return storage;
+    }
+
+    /*
+     * Using the given username and password, set up a user for JAAS in the web app,
+     * with the given roles, and make a storage connection to the web app (returning
+     * the Storage object).
+     */
+    private static Storage getAndConnectStorage(String username, String password,
+                                                String[] roleNames) throws IOException {
+        return getAndConnectStorage(username, password, roleNames, null);
+    }
+
+    /*
+     * Using the given username and password, set up a user for JAAS in the web app,
+     * with the given roles, and make a connection to the web app (returning the
+     * Storage object).  Before initiating the connection, add the ConnectionListener
+     * to Storage.
+     */
+    private static Storage getAndConnectStorage(String username, String password,
+                                                String[] roleNames,
+                                                ConnectionListener listener) throws IOException {
+        setupJAASForUser(roleNames, username, password);
+        String url = "http://localhost:" + port + "/thermostat/storage";
+        StartupConfiguration config = new ConnectionConfiguration(url, username, password);
+        Storage storage = new WebStorage(config);
+        if (listener != null) {
+            storage.getConnection().addListener(listener);
+        }
+        storage.getConnection().connect();
+        return storage;
+    }
+
+    private static void setupJAASForUser(String[] roleNames, String user,
+            String password) throws IOException {
+        Properties userProps = new Properties();
+        userProps.put(user, password);
+        Properties roleProps = new Properties();
+        StringBuffer roles = new StringBuffer();
+        for (int i = 0; i < roleNames.length - 1; i++) {
+            roles.append(roleNames[i] + ", ");
+        }
+        roles.append(roleNames[roleNames.length - 1]);
+        roleProps.put(user, roles.toString());
+        writeThermostatUsersRolesFile(userProps, roleProps);
+    }
+    
+    private static void writeThermostatUsersRolesFile(Properties usersContent, Properties rolesContent) throws IOException {
+        File thermostatUsers = new File(THERMOSTAT_USERS_FILE);
+        File thermostatRoles = new File(THERMOSTAT_ROLES_FILE);
+        try (FileOutputStream usersStream = new FileOutputStream(thermostatUsers)) {
+            usersContent.store(usersStream, "integration-test users");
+        }
+        try (FileOutputStream rolesStream = new FileOutputStream(thermostatRoles)) {
+            rolesContent.store(rolesStream, "integration-test roles");
+        }
+    }
+
+    private static void startServer(int port) throws Exception {
+        final CountDownLatch contextStartedLatch = new CountDownLatch(1);
+        server = new Server(port);
+        ApplicationInfo appInfo = new ApplicationInfo();
+        String version = appInfo.getMavenVersion();
+        String warfile = "target/libs/thermostat-web-war-" + version + ".war";
+        WebAppContext ctx = new WebAppContext(warfile, "/thermostat");
+        
+        // We need to set this to true in order for WebStorageEndPoint to pick
+        // up the descriptor registrations from WebAppTestStatementDescriptorRegistration
+        // which would result in 
+        // "java.util.ServiceConfigurationError: com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration: Provider com.redhat.thermostat.itest.WebAppTestStatementDescriptorRegistration not a subtype"
+        // errors.
+        ctx.setParentLoaderPriority(true);
+        
+        WebAppContextListener listener = new WebAppContextListener(contextStartedLatch);
+        ctx.addLifeCycleListener(listener);
+        /* The web archive has a jetty-web.xml config file which sets up the
+         * JAAS config. If done in code, this would look like this:
+         *
+         * JAASLoginService loginS = new JAASLoginService();
+         * loginS.setLoginModuleName("ThermostatJAASLogin");
+         * loginS.setName("Thermostat Realm");
+         * loginS.setRoleClassNames(new String[] {
+         * WrappedRolePrincipal.class.getName(),
+         *       RolePrincipal.class.getName(),
+         *       UserPrincipal.class.getName()
+         * });
+         * ctx.getSecurityHandler().setLoginService(loginS);
+         * 
+         */
+        server.setHandler(ctx);
+        server.start();
+        // wait for context to start
+        contextStartedLatch.await();
+        if (listener.failed) {
+            throw new IllegalStateException(listener.cause);
+        }
+    }
+
+    private static void addCpuData(int numberOfItems) throws IOException {
+        BackingStorage storage = getAndConnectBackingStorage();
+        storage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        for (int i = 0; i < numberOfItems; i++) {
+            CpuStat pojo = new CpuStat("test-agent-id", i, new double[] {i, i*2});
+            Add<CpuStat> add = storage.createAdd(CpuStatDAO.cpuStatCategory);
+            add.set(Key.AGENT_ID.getName(), pojo.getAgentId());
+            add.set(CpuStatDAO.cpuLoadKey.getName(), pojo.getPerProcessorUsage());
+            add.set(Key.TIMESTAMP.getName(), pojo.getTimeStamp());
+            add.apply();
+        }
+
+        storage.getConnection().disconnect();
+    }
+    
+    private static void addHostInfoData(int numberOfItems) throws IOException {
+        BackingStorage storage = getAndConnectBackingStorage();
+        storage.registerCategory(HostInfoDAO.hostInfoCategory);
+
+        for (int i = 0; i < numberOfItems; i++) {
+            HostInfo hostInfo = new HostInfo("test-host-agent-id", "foo " + i, "linux " + i, "kernel", "t8", i, i * 1000);
+            Add<HostInfo> add = storage.createAdd(HostInfoDAO.hostInfoCategory);
+            add.set(Key.AGENT_ID.getName(), hostInfo.getAgentId());
+            add.set(HostInfoDAO.hostNameKey.getName(), hostInfo.getHostname());
+            add.set(HostInfoDAO.cpuCountKey.getName(), hostInfo.getCpuCount());
+            add.set(HostInfoDAO.cpuModelKey.getName(), hostInfo.getCpuModel());
+            add.set(HostInfoDAO.hostMemoryTotalKey.getName(), hostInfo.getTotalMemory());
+            add.set(HostInfoDAO.osKernelKey.getName(), hostInfo.getOsKernel());
+            add.set(HostInfoDAO.osNameKey.getName(), hostInfo.getOsName());
+            add.apply();
+        }
+
+        storage.getConnection().disconnect();
+    }
+    
+    private static void addAgentConfigData(List<AgentInformation> items) throws IOException {
+        BackingStorage storage = getAndConnectBackingStorage();
+        storage.registerCategory(AgentInfoDAO.CATEGORY);
+
+        for (AgentInformation info: items) {
+            Add<AgentInformation> add = storage.createAdd(AgentInfoDAO.CATEGORY);
+            add.set(Key.AGENT_ID.getName(), info.getAgentId());
+            add.set(AgentInfoDAO.ALIVE_KEY.getName(), info.isAlive());
+            add.set(AgentInfoDAO.CONFIG_LISTEN_ADDRESS.getName(), info.getConfigListenAddress());
+            add.set(AgentInfoDAO.START_TIME_KEY.getName(), info.getStartTime());
+            add.set(AgentInfoDAO.STOP_TIME_KEY.getName(), info.getStopTime());
+            add.apply();
+        }
+
+        storage.getConnection().disconnect();
+    }
+
+    private static void deleteCpuData() throws IOException {
+        doDeleteData(CpuStatDAO.cpuStatCategory, "test-agent-id");
+    }
+    
+    private static void deleteHostInfoData() throws IOException {
+        doDeleteData(HostInfoDAO.hostInfoCategory, "test-host-agent-id");
+    }
+    
+    private static void doDeleteData(Category<?> category, String agentId) throws IOException {
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.ACCESS_REALM,
+                Roles.LOGIN,
+                Roles.PURGE
+        };
+        Storage storage = getAndConnectStorage(PREP_USER, PREP_PASSWORD, roleNames);
+        storage.registerCategory(category);
+        storage.purge(agentId);
+        storage.getConnection().disconnect();
+    }
+    
+    private static void deleteAgentConfigData(List<AgentInformation> items) throws IOException {
+        BackingStorage storage = getAndConnectBackingStorage();
+        storage.registerCategory(AgentInfoDAO.CATEGORY);
+        ExpressionFactory factory = new ExpressionFactory();
+        Remove<AgentInformation> remove = storage.createRemove(AgentInfoDAO.CATEGORY);
+        Set<String> agentIds = new HashSet<>();
+        for (AgentInformation info: items) {
+            agentIds.add(info.getAgentId());
+        }
+        Expression expression = factory.in(Key.AGENT_ID, agentIds, String.class);
+        remove.where(expression);
+        remove.apply();
+
+        storage.getConnection().disconnect();
+    }
+
+    private void executeAndVerifyQuery(PreparedStatement<CpuStat> query, List<Long> expectedTimestamps) throws StatementExecutionException {
+        Cursor<CpuStat> cursor = query.executeQuery();
+
+        for (Long time : expectedTimestamps) {
+            assertTrue(cursor.hasNext());
+            CpuStat pojo = cursor.next();
+            assertEquals("test-agent-id", pojo.getAgentId());
+            assertEquals(time.longValue(), pojo.getTimeStamp());
+            double[] data = pojo.getPerProcessorUsage();
+            assertEquals(time, data[0], EQUALS_DELTA);
+            assertEquals(time*2, data[1], EQUALS_DELTA);
+        }
+        assertFalse(cursor.hasNext());
+    }
+
+    @Test
+    public void authorizedPreparedAdd() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.WRITE,
+                Roles.LOGIN,
+                Roles.ACCESS_REALM,
+                Roles.PREPARE_STATEMENT,
+        };
+        
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        webStorage.registerCategory(VmClassStatDAO.vmClassStatsCategory);
+        
+        // This is the same descriptor as VmClassStatDAOImpl uses. It also
+        // gets registered automatically for that reason, no need to do it
+        // manually for this test.
+        String strDesc = "ADD vm-class-stats SET 'agentId' = ?s , " +
+                                "'vmId' = ?s , " +
+                                "'timeStamp' = ?l , " + 
+                                "'loadedClasses' = ?l";
+        StatementDescriptor<VmClassStat> desc = new StatementDescriptor<>(VmClassStatDAO.vmClassStatsCategory, strDesc);
+        VmClassStat pojo = new VmClassStat();
+        pojo.setAgentId("fluff");
+        pojo.setLoadedClasses(12345);
+        pojo.setTimeStamp(42);
+        pojo.setVmId(VM_ID1);
+        PreparedStatement<VmClassStat> add;
+        add = webStorage.prepareStatement(desc);
+        addPreparedVmClassStat(pojo, add);
+        
+        // Add another couple of entries
+        pojo = new VmClassStat();
+        pojo.setAgentId("fluff");
+        pojo.setLoadedClasses(67890);
+        pojo.setTimeStamp(42);
+        pojo.setVmId(VM_ID2);
+        addPreparedVmClassStat(pojo, add);
+        
+        pojo = new VmClassStat();
+        pojo.setAgentId("fluff");
+        pojo.setLoadedClasses(34567);
+        pojo.setTimeStamp(42);
+        pojo.setVmId(VM_ID3);
+        addPreparedVmClassStat(pojo, add);
+        
+        webStorage.getConnection().disconnect();
+    }
+    
+    private void addPreparedVmClassStat(VmClassStat pojo,
+            PreparedStatement<VmClassStat> add)
+            throws StatementExecutionException {
+        add.setString(0, pojo.getAgentId());
+        add.setString(1, pojo.getVmId());
+        add.setLong(2, pojo.getTimeStamp());
+        add.setLong(3, pojo.getLoadedClasses());
+        add.execute();
+    }
+    
+    /*
+     * Tests whether a query only returns results which a user is allowed to see.
+     * 
+     * In particular, multiple agent-config records available in the DB, but
+     * only a subset are allowed to be seen by the user.
+     */
+    @Test
+    public void authorizedFilteredQuerySubset() throws Exception {
+        // add agent records into the DB
+        List<AgentInformation> items = Collections.emptyList();
+        try {
+            String agentIdGrantPrefix = "thermostat-agents-grant-read-agentId-";
+            String agent1Id = "agent1";
+            String agent2Id = "agent2";
+            items = getAgentInformationItemsIncluding(new String[] { agent1Id, agent2Id });
+            // assert pre-condition. records in db need to be more than expected
+            // result set size.
+            assertTrue(items.size() > 2);
+            addAgentConfigData(items);
+            String[] roleNames = new String[] {
+                    Roles.REGISTER_CATEGORY,
+                    Roles.READ,
+                    Roles.LOGIN,
+                    Roles.ACCESS_REALM,
+                    Roles.PREPARE_STATEMENT,
+                    // Grant read access only for "agent1" and "agent2" agend IDs
+                    agentIdGrantPrefix + agent1Id,
+                    agentIdGrantPrefix + agent2Id
+            };
+            
+            Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+            webStorage.registerCategory(AgentInfoDAO.CATEGORY);
+            
+            String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_FILTERED_QUERY);
+            StatementDescriptor<AgentInformation> queryDesc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, strDesc);
+            PreparedStatement<AgentInformation> query = webStorage.prepareStatement(queryDesc);
+            Cursor<AgentInformation> cursor = query.executeQuery();
+            assertTrue(cursor.hasNext());
+            List<AgentInformation> actual = new ArrayList<>();
+            while (cursor.hasNext()) {
+                AgentInformation info = cursor.next();
+                actual.add(info);
+            }
+            assertEquals(2, actual.size());
+            assertFalse("Returned agentIds should be different!", actual.get(0).getAgentId().equals(actual.get(1).getAgentId()));
+            for (AgentInformation info: actual) {
+                assertTrue(info.getAgentId().equals(agent1Id) || info.getAgentId().equals(agent2Id));
+            }
+        } finally {
+           deleteAgentConfigData(items); 
+        }
+    }
+    
+    
+    private List<AgentInformation> getAgentInformationItemsIncluding(
+            String[] includeItems) {
+        List<AgentInformation> infos = new ArrayList<>();
+        for (int i = 0; i < 5; i++) {
+            String agentId = UUID.randomUUID().toString() + "--" + i;
+            if (i < includeItems.length) {
+                agentId = includeItems[i];
+            }
+            AgentInformation info = new AgentInformation();
+            info.setAgentId(agentId);
+            info.setAlive((i % 2) == 0);
+            info.setConfigListenAddress("127.0.0." + i + ":88888");
+            info.setStartTime(i * 300);
+            info.setStopTime((i + 1) * 400);
+            infos.add(info);
+        }
+        return infos;
+    }
+
+    /*
+     * Tests whether no query results are returned for a user which lacks *any*
+     * granting roles for reads.
+     */
+    @Test
+    public void authorizedFilteredQueryNone() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.READ, // this is just the stop-gap role.
+                Roles.LOGIN,
+                Roles.ACCESS_REALM,
+                Roles.PREPARE_STATEMENT,
+                // lacking read grant roles
+        };
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+        
+        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY);
+        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
+        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
+
+        query.setString(0, "timeStamp");
+        // Note: with read-all granted, this returns 4 records. See authorized
+        //       query test.
+        // For this test, however, it should come back empty.
+        
+        Cursor<CpuStat> cursor = query.executeQuery();
+        assertFalse(cursor.hasNext());
+        try {
+            cursor.next();
+            fail("cursor should have thrown exception!");
+        } catch (NoSuchElementException e) {
+            // pass
+        }
+
+        webStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void authorizedQuery() throws Exception {
+
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.READ,
+                Roles.LOGIN,
+                Roles.ACCESS_REALM,
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
+        };
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+        
+        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY);
+        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
+        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
+
+        query.setString(0, "timeStamp");
+        
+        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 2l, 3l));
+
+        webStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void authorizedAggregateCount() throws Exception {
+        try {
+            int count = 2;
+            // registers host info category
+            addHostInfoData(count);
+            
+            String[] roleNames = new String[] {
+                    Roles.REGISTER_CATEGORY,
+                    Roles.READ,
+                    Roles.LOGIN,
+                    Roles.ACCESS_REALM,
+                    Roles.PREPARE_STATEMENT,
+                    Roles.GRANT_READ_ALL // don't want to test filtered results
+            };
+            Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+            Category<AggregateCount> adapted = new CategoryAdapter<HostInfo, AggregateCount>(HostInfoDAO.hostInfoCategory).getAdapted(AggregateCount.class);
+            // register non-adapted + adapted category in that order. Adapted
+            // category needs to be registered, since it gets it's own mapped id
+            webStorage.registerCategory(HostInfoDAO.hostInfoCategory);
+            webStorage.registerCategory(adapted);
+            
+            // storage-core registers this descriptor. no need to do it in this
+            // test.
+            String strDesc = "QUERY-COUNT host-info";
+            StatementDescriptor<AggregateCount> queryDesc = new StatementDescriptor<>(adapted, strDesc);
+            PreparedStatement<AggregateCount> query = webStorage.prepareStatement(queryDesc);
+    
+            Cursor<AggregateCount> cursor = query.executeQuery();
+            assertTrue(cursor.hasNext());
+            AggregateCount c = cursor.next();
+            assertFalse(cursor.hasNext());
+            assertEquals(count, c.getCount());
+    
+            webStorage.getConnection().disconnect();
+        } finally {
+            deleteHostInfoData();
+        }
+    }
+    
+    @Test
+    public void authorizedQueryEqualTo() throws Exception {
+
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.READ,
+                Roles.LOGIN,
+                Roles.ACCESS_REALM,
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
+        };
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_EQUAL_TO);
+        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
+        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
+        query.setLong(0, 2l);
+        
+        executeAndVerifyQuery(query, Arrays.asList(2l));
+
+        webStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void authorizedQueryNotEqualTo() throws Exception {
+
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.READ,
+                Roles.LOGIN,
+                Roles.ACCESS_REALM,
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
+        };
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_NOT_EQUAL_TO);
+        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
+        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
+        query.setLong(0, 2l);
+        
+        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 3l));
+
+        webStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void authorizedQueryGreaterThan() throws Exception {
+
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.READ,
+                Roles.LOGIN,
+                Roles.ACCESS_REALM,
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
+        };
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_GREATER_THAN);
+        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
+        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
+        query.setLong(0, 2l);
+        
+        executeAndVerifyQuery(query, Arrays.asList(3l));
+
+        webStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void authorizedQueryGreaterThanOrEqualTo() throws Exception {
+
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.READ,
+                Roles.LOGIN,
+                Roles.ACCESS_REALM,
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
+        };
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_GREATER_THAN_OR_EQUAL_TO);
+        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
+        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
+        query.setLong(0, 2l);
+        
+        executeAndVerifyQuery(query, Arrays.asList(2l, 3l));
+
+        webStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void authorizedQueryLessThan() throws Exception {
+
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.READ,
+                Roles.LOGIN,
+                Roles.ACCESS_REALM,
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
+        };
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_LESS_THAN);
+        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
+        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
+        query.setLong(0, 2l);
+        
+        executeAndVerifyQuery(query, Arrays.asList(0l, 1l));
+
+        webStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void authorizedQueryLessThanOrEqualTo() throws Exception {
+
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.READ,
+                Roles.LOGIN,
+                Roles.ACCESS_REALM,
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
+        };
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_LESS_THAN_OR_EQUAL_TO);
+        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
+        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
+        query.setLong(0, 2l);
+
+        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 2l));
+
+        webStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void authorizedQueryNot() throws Exception {
+
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.READ,
+                Roles.LOGIN,
+                Roles.ACCESS_REALM,
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
+        };
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_NOT);
+        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
+        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
+        query.setLong(0, 2l);
+        
+        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 2l));
+
+        webStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void authorizedQueryAnd() throws Exception {
+
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.READ,
+                Roles.LOGIN,
+                Roles.ACCESS_REALM,
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
+        };
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_AND);
+        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
+        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
+        query.setLong(0, 2l);
+        
+        executeAndVerifyQuery(query, Arrays.asList(1l));
+
+        webStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void authorizedQueryOr() throws Exception {
+
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.READ,
+                Roles.LOGIN,
+                Roles.ACCESS_REALM,
+                Roles.PREPARE_STATEMENT,
+                Roles.GRANT_READ_ALL
+        };
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_OR);
+        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
+        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
+        query.setLong(0, 2);
+        query.setLong(1, 1);
+        
+        executeAndVerifyQuery(query, Arrays.asList(0l, 3l));
+
+        webStorage.getConnection().disconnect();
+    }
+    
+    @Test
+    public void refuseUnknownQueryDescriptor() throws IOException {
+
+        String[] roleNames = new String[] {
+                Roles.REGISTER_CATEGORY,
+                Roles.READ,
+                Roles.LOGIN,
+                Roles.ACCESS_REALM,
+                Roles.PREPARE_STATEMENT
+        };
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
+
+        String strDesc = "QUERY cpu-stats WHERE 'fooBarTest' = ?s";
+        assertFalse("wanted this descriptor to be untrusted!", TRUSTED_DESCRIPTORS.contains(strDesc));
+        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
+        
+        try {
+            webStorage.prepareStatement(queryDesc);
+        } catch (IllegalDescriptorException e) {
+            // pass
+            String expectedMsg = "Unknown query descriptor which endpoint of com.redhat.thermostat.web.client.internal.WebStorage refused to accept!";
+            assertEquals(expectedMsg, e.getMessage());
+        } catch (DescriptorParsingException e) {
+            // should have been able to parse the descriptor
+            fail(e.getMessage());
+        }
+        
+        webStorage.getConnection().disconnect();
+    }
+
+    @Test
+    public void authorizedLoadSave() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.LOAD_FILE,
+                Roles.SAVE_FILE,
+                Roles.ACCESS_REALM,
+                Roles.LOGIN,
+                Roles.GRANT_FILES_WRITE_ALL,
+                Roles.GRANT_FILES_READ_ALL
+        };
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        
+        byte[] data = "Hello World".getBytes();
+        webStorage.saveFile("test", new ByteArrayInputStream(data));
+        // Note: On the server side, the file is saved into mongodb
+        // via GridFS.  The save operation returns before write is
+        // complete, and there is no callback mechanism to find out
+        // when the write is complete.  So, we try a few times to
+        // load it before considering it a failure.
+        InputStream loadStream = null;
+        int loadAttempts = 0;
+        while (loadStream == null && loadAttempts < 3) {
+            Thread.sleep(300);
+            loadStream = webStorage.loadFile("test");
+            loadAttempts++;
+        }
+        assertNotNull(loadStream);
+        StringBuilder str = new StringBuilder();
+        int i = loadStream.read();
+        while (i != -1) {
+            str.append((char) i);
+            i = loadStream.read();
+        }
+        assertEquals("Hello World", str.toString());
+
+        webStorage.getConnection().disconnect();
+    }
+
+    @Test
+    public void unauthorizedLogin() throws Exception {
+        String[] roleNames = new String[] {
+                Roles.ACCESS_REALM
+        };
+
+        CountDownLatch statusLatch = new CountDownLatch(1);
+        AtomicBoolean listenerTriggered = new AtomicBoolean(false);
+        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.FAILED_TO_CONNECT, statusLatch, listenerTriggered);
+        @SuppressWarnings("unused")
+        Storage storage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames, listener);
+        statusLatch.await();
+        assertTrue(listenerTriggered.get());
+    }
+
+    @Test
+    public void storagePurge() throws Exception {
+        // Add some data to purge (uses backing storage)
+        UUID uuid = new UUID(42, 24);
+        long timeStamp = 5;
+        double cpuLoad = 0.15;
+        VmCpuStat pojo = new VmCpuStat(uuid.toString(), timeStamp, VM_ID1, cpuLoad);
+        addVmCpuStat(pojo);
+
+        String[] roleNames = new String[] {
+                Roles.ACCESS_REALM,
+                Roles.LOGIN,
+                Roles.PURGE,
+                Roles.PREPARE_STATEMENT,
+                Roles.READ,
+                Roles.GRANT_READ_ALL,
+                Roles.REGISTER_CATEGORY
+        };
+        
+        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
+        webStorage.registerCategory(VmCpuStatDAO.vmCpuStatCategory);
+        
+        String strDesc = DESCRIPTOR_MAP.get(KEY_STORAGE_PURGE);
+        StatementDescriptor<VmCpuStat> queryDesc = new StatementDescriptor<>(VmCpuStatDAO.vmCpuStatCategory, strDesc);
+        PreparedStatement<VmCpuStat> query = webStorage.prepareStatement(queryDesc);
+        Cursor<VmCpuStat> cursor = query.executeQuery();
+        assertTrue(cursor.hasNext());
+        pojo = cursor.next();
+        assertFalse(cursor.hasNext());
+
+        assertEquals(timeStamp, pojo.getTimeStamp());
+        assertEquals(VM_ID1, pojo.getVmId());
+        assertEquals(cpuLoad, pojo.getCpuLoad(), EQUALS_DELTA);
+        assertEquals(uuid.toString(), pojo.getAgentId());
+
+        webStorage.purge(uuid.toString());
+    }
+
+    private void addVmCpuStat(VmCpuStat pojo) {
+        BackingStorage storage = getAndConnectBackingStorage();
+        storage.registerCategory(VmCpuStatDAO.vmCpuStatCategory);
+        Add<VmCpuStat> add = storage.createAdd(VmCpuStatDAO.vmCpuStatCategory);
+        add.set(Key.AGENT_ID.getName(), pojo.getAgentId());
+        add.set(Key.VM_ID.getName(), pojo.getVmId());
+        add.set(Key.TIMESTAMP.getName(), pojo.getTimeStamp());
+        add.set(VmCpuStatDAO.vmCpuLoadKey.getName(), pojo.getCpuLoad());
+        add.apply();
+        storage.getConnection().disconnect();        
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/WebAppTestStatementDescriptorRegistration.java	Fri Apr 05 18:57:56 2013 +0200
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.itest;
+
+import java.util.Set;
+
+import com.redhat.thermostat.storage.core.PreparedParameter;
+import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
+import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
+
+public class WebAppTestStatementDescriptorRegistration implements
+        StatementDescriptorRegistration {
+
+    @Override
+    public Set<String> getStatementDescriptors() {
+        return WebAppTest.TRUSTED_DESCRIPTORS;
+    }
+
+    @Override
+    public DescriptorMetadata getDescriptorMetadata(String descriptor,
+            PreparedParameter[] params) {
+        return WebAppTest.METADATA_MAPPING.get(descriptor);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/standalone/AllStandaloneTests.java	Fri Apr 05 18:57:56 2013 +0200
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.itest.standalone;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+import com.redhat.thermostat.itest.CliTest;
+import com.redhat.thermostat.itest.PluginTest;
+import com.redhat.thermostat.itest.StorageConnectionTest;
+import com.redhat.thermostat.itest.StorageTest;
+import com.redhat.thermostat.itest.VmCommandsTest;
+
+
+/**
+ * <p>
+ * Suite class in order to facilitate running integration tests from CLI. In
+ * particular this has been added so as to be able to quickly test downstream
+ * packaged thermostat.
+ * </p>
+ * <p>
+ * After building thermostat from the same sources than packaged thermostat
+ * locally via "mvn clean package", this produces the
+ * thermostat-integration-tests-standalone-&lt;VERSION&gt;.jar in folder
+ * integration-tests/standalone/target.
+ * </p>
+ * <p>
+ * Concrete steps are:
+ * 
+ * <pre>
+ *   $ wget http://icedtea.wildebeest.org/download/thermostat/thermostat-&lt;VERSION&gt.tar.gz
+ *   $ tar -xzf thermostat-&lt;VERSION&gt.tar.gz
+ *   $ cd thermostat-&lt;VERSION&gt
+ *   $ mvn clean package 
+ *   $ java -Dcom.redhat.thermostat.itest.thermostatHome=/path/to/thermostat/install \
+ *            -Dcom.redhat.thermostat.itest.thermostatUserHome=$(echo ~/.thermostat) \
+ *            -cp $(ls integration-tests/standalone/target/thermostat-integration-tests-standalone-*.jar)
+ *            com.redhat.thermostat.itest.standalone.ItestRunner
+ * </pre>
+ * 
+ * This should produce a human readable test report in
+ * $(pwd)/thermostat-itest-reports/summary.txt
+ * </p>
+ * <p>
+ * If you add more test classes, please add those classes to the set of
+ * SuiteClasses.
+ * </p>
+ * <p>
+ * Note that it's only useful to add expectj based tests to
+ * the suite. MongoQueriesTest + WebAppTest don't seem to be suitable for
+ * downstream packaged thermostat. They would require to be adjusted and/or
+ * would need more jars to be on the class path. In particular packaged
+ * storage-core, storage-mongodb etc. jars.
+ * </p>
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    CliTest.class,
+    StorageConnectionTest.class,
+    StorageTest.class,
+    VmCommandsTest.class,
+    PluginTest.class,
+})
+public class AllStandaloneTests {
+    // nothing
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/standalone/ItestRunner.java	Fri Apr 05 18:57:56 2013 +0200
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.itest.standalone;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Result;
+import org.junit.runners.Suite;
+import org.junit.runners.Suite.SuiteClasses;
+
+/**
+ * Custom JUnitCore runner which produces human readable reports.
+ *
+ * @see StandaloneReportsListener
+ * @see AllStandaloneTests
+ */
+public class ItestRunner {
+    
+    private static class Runner {
+        
+        private static final String PASS_TOKEN = "PASSED.";
+        private static final String FAIL_TOKEN = "FAILED.";
+        private static final File REPORTS_FOLDER = new File("thermostat-itest-reports");
+        static final File MAIN_REPORT_FILE = new File(REPORTS_FOLDER, "summary.txt");
+        private PrintStream printStream;
+        
+        private Runner() {
+            printStream = prepareReportsFolder();
+            String msg = "Running Thermostat standalone integration tests ...";
+            printStream.println(msg + "\n");
+        }
+        
+        private PrintStream prepareReportsFolder() {
+            if (REPORTS_FOLDER.exists()) {
+                deleteReportsFolder();
+            }
+            REPORTS_FOLDER.mkdir();
+            PrintStream ps;
+            try {
+                ps = new PrintStream(MAIN_REPORT_FILE);
+            } catch (FileNotFoundException e) {
+                // can't continue
+                throw new RuntimeException(e);
+            }
+            return ps;
+        }
+
+        private void deleteReportsFolder() {
+            try {
+                Files.walkFileTree(REPORTS_FOLDER.toPath(), new FileVisitor<Path>() {
+
+                    @Override
+                    public FileVisitResult preVisitDirectory(Path dir,
+                            BasicFileAttributes attrs) throws IOException {
+                        return FileVisitResult.CONTINUE;
+                    }
+
+                    @Override
+                    public FileVisitResult visitFile(Path file,
+                            BasicFileAttributes attrs) throws IOException {
+                        Files.delete(file);
+                        return FileVisitResult.TERMINATE;
+                    }
+
+                    @Override
+                    public FileVisitResult visitFileFailed(Path file,
+                            IOException exc) throws IOException {
+                        return FileVisitResult.CONTINUE;
+                    }
+
+                    @Override
+                    public FileVisitResult postVisitDirectory(Path dir,
+                            IOException exc) throws IOException {
+                        return FileVisitResult.CONTINUE;
+                    }
+                    
+                });
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        
+        private void startTest(Class<?> clazz) {
+            printStream.print("  Running " + clazz.getName() + " ");
+        }
+        
+        private void testFinished(Result result) {
+            String testToken = PASS_TOKEN;
+            if (!result.wasSuccessful()) {
+                testToken = FAIL_TOKEN;
+            }
+            printStream.println(testToken);
+        }
+        
+        private void finished() {
+            printStream.println("\n");
+            printStream.println("Done.");
+            printStream.close();
+        }
+        
+    }
+    
+    public static void main(String args[]) {
+        if (args.length != 0) {
+            usage();
+        }
+        Runner myRunner = new Runner();
+        System.out.print("Running Thermostat standalone integration tests ");
+        SuiteClasses cls = AllStandaloneTests.class.getAnnotation(Suite.SuiteClasses.class);
+        boolean allPass = true;
+        for (Class<?> c: cls.value()) {
+            myRunner.startTest(c);
+            JUnitCore core = new JUnitCore();
+            core.addListener(new StandaloneReportsListener(new File(Runner.REPORTS_FOLDER, c.getName() + ".txt")));
+            Result result = core.run(c);
+            allPass = allPass && result.wasSuccessful();
+            String testToken = result.wasSuccessful() ? "." : "F";
+            System.out.print(testToken);
+            myRunner.testFinished(result);
+        }
+        System.out.println(" Done.\n\nSee " + Runner.MAIN_REPORT_FILE.getAbsolutePath() + " for details.");
+        myRunner.finished();
+    }
+
+    private static void usage() {
+        System.err.println("Usage: " + ItestRunner.class.getName());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/integration-tests/itest-run/src/test/java/com/redhat/thermostat/itest/standalone/StandaloneReportsListener.java	Fri Apr 05 18:57:56 2013 +0200
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2012, 2013 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.itest.standalone;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+
+/**
+ * Stack trace printer for failed tests.
+ *
+ */
+public class StandaloneReportsListener extends RunListener {
+
+    private final File currentTestFailureFile;
+    
+    public StandaloneReportsListener(File testFile) {
+        currentTestFailureFile = testFile;
+    }
+    
+    @Override
+    public void testFailure(Failure failure) {
+        writeStackTraceToErrorFile(failure);
+    }
+    
+    private void writeStackTraceToErrorFile(Failure failure) {
+        Throwable expn = failure.getException();
+        // make sure we append to any existing file so as to not overwrite
+        // earlier failed tests.
+        try (FileWriter fw = new FileWriter(currentTestFailureFile, true);
+                PrintWriter pw = new PrintWriter(fw)) {
+            expn.printStackTrace(pw);
+        } catch (IOException e) {
+            e.printStackTrace(System.err);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/integration-tests/itest-run/src/test/resources/META-INF/services/com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration	Fri Apr 05 18:57:56 2013 +0200
@@ -0,0 +1,1 @@
+com.redhat.thermostat.itest.WebAppTestStatementDescriptorRegistration
\ No newline at end of file
--- a/integration-tests/pom.xml	Wed Oct 16 13:19:53 2013 -0400
+++ b/integration-tests/pom.xml	Fri Apr 05 18:57:56 2013 +0200
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- 
 
- Copyright 2013 Red Hat, Inc.
+ Copyright 2012, 2013 Red Hat, Inc.
 
  This file is part of Thermostat.
 
@@ -46,163 +46,14 @@
   </parent>
 
   <artifactId>thermostat-integration-tests</artifactId>
-  <packaging>jar</packaging>
-
-  <name>Thermostat Integration Tests</name>
+  <packaging>pom</packaging>
 
-  <properties>
-    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-  </properties>
+  <name>Thermostat Integration Tests (parent)</name>
 
-  <build>
-    <plugins>
-      <!-- jacoco:report insists to have a target/classes dir. since this
-           module only contains test classes it won't exist after a build.
-           Hence, create manually in order to unbreak the build. For some
-           reason skipping both, report AND prepare-agent goals for the
-           jacoco plugin does not work. -->
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-antrun-plugin</artifactId>
-        <version>1.6</version>
-        <executions>
-          <execution>
-            <id>make-target-classes-dir</id>
-            <phase>prepare-package</phase>
-            <configuration>
-              <target>
-                <mkdir dir="${project.build.directory}/classes" />
-              </target>
-            </configuration>
-            <goals>
-              <goal>run</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
-      <!-- skip unit test run, tests to be executed during integration-test -->
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-surefire-plugin</artifactId>
-        <configuration>
-          <argLine>-Djava.security.auth.login.config=${thermostat.home}/etc/thermostat_jaas.conf ${coverageAgent}</argLine>
-          <skip>true</skip>
-        </configuration>
-        <executions>
-          <execution>
-            <id>run-integration-tests</id>
-            <phase>integration-test</phase>
-            <goals>
-              <goal>test</goal>
-            </goals>
-            <configuration>
-              <skip>false</skip>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
-        <artifactId>maven-dependency-plugin</artifactId>
-        <version>2.4</version>
-        <executions>
-          <execution>
-            <id>copy-deps</id>
-            <phase>package</phase>
-            <goals>
-              <goal>copy-dependencies</goal>
-            </goals>
-            <configuration>
-              <excludeTransitive>true</excludeTransitive>
-              <excludeGroupIds>org.osgi,junit,org.hamcrest</excludeGroupIds>
-              <outputDirectory>${project.build.directory}/libs</outputDirectory>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-    </plugins>
-    <pluginManagement>
-    	<plugins>
-		<!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
-    		<plugin>
-    			<groupId>org.eclipse.m2e</groupId>
-    			<artifactId>lifecycle-mapping</artifactId>
-    			<version>1.0.0</version>
-    			<configuration>
-    				<lifecycleMappingMetadata>
-    					<pluginExecutions>
-    						<pluginExecution>
-    							<pluginExecutionFilter>
-    								<groupId>
-    									org.apache.maven.plugins
-    								</groupId>
-    								<artifactId>
-    									maven-dependency-plugin
-    								</artifactId>
-    								<versionRange>[2.4,)</versionRange>
-    								<goals>
-    									<goal>copy-dependencies</goal>
-    								</goals>
-    							</pluginExecutionFilter>
-    							<action>
-    								<ignore></ignore>
-    							</action>
-    						</pluginExecution>
-    					</pluginExecutions>
-    				</lifecycleMappingMetadata>
-    			</configuration>
-    		</plugin>
-    	</plugins>
-    </pluginManagement>
-  </build>
-  <dependencies>
+  <modules>
+    <module>itest-run</module>
+    <module>standalone</module>
+  </modules>
 
-    <!-- integration tests -->
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>net.sourceforge.expectj</groupId>
-      <artifactId>expectj</artifactId>
-      <scope>test</scope>
-    </dependency>
-
-    <!-- thermostat parts -->
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-distribution</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-web-war</artifactId>
-      <version>${project.version}</version>
-      <type>war</type>
-      <scope>test</scope>
-    </dependency>
-
-    <!--  Embedded jetty for testing the WAR (no peace from here on..). -->
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-server</artifactId>
-      <version>${jetty.version}</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-plus</artifactId>
-      <version>${jetty.version}</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.eclipse.jetty</groupId>
-      <artifactId>jetty-webapp</artifactId>
-      <version>${jetty.version}</version>
-      <scope>test</scope>
-    </dependency>
-
-  </dependencies>
 </project>
 
--- a/integration-tests/src/test/java/com/redhat/thermostat/itest/CliTest.java	Wed Oct 16 13:19:53 2013 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,257 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.itest;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import java.io.IOException;
-
-import org.junit.Test;
-
-import expectj.Spawn;
-
-/**
- * Integration tests to exercise the basics of the thermostat command line.
- */
-public class CliTest extends IntegrationTest {
-
-    @Test
-    public void testExpectIsSane() throws Exception {
-        Spawn shell = spawnThermostat();
-
-        try {
-            shell.expect("some-random-text-that-is-not-really-possible");
-            fail("should never match");
-        } catch (IOException endOfStream) {
-            assertTrue(endOfStream.getMessage().contains("End of stream reached, no match found"));
-        }
-        shell.expectClose();
-    }
-
-    @Test
-    public void testSimpleInvocationPrintsHelp() throws Exception {
-        Spawn shell = spawnThermostat();
-        shell.expectClose();
-
-        String stdOut = shell.getCurrentStandardOutContents();
-
-        assertMatchesHelpCommandList(stdOut);
-
-        String stdErr = shell.getCurrentStandardErrContents();
-        assertEquals(stdErr, "");
-    }
-
-    @Test
-    public void testHelpCommandInvocation() throws Exception {
-        Spawn shell = spawnThermostat("help");
-        shell.expectClose();
-
-        String stdOut = shell.getCurrentStandardOutContents();
-        String stdErr = shell.getCurrentStandardErrContents();
-
-        assertMatchesHelpCommandList(stdOut);
-        assertEquals(stdErr, "");
-    }
-
-    @Test
-    public void testHelpOnHelp() throws Exception {
-        Spawn shell = spawnThermostat("help", "help");
-        shell.expectClose();
-
-        String stdOut = shell.getCurrentStandardOutContents();
-        String stdErr = shell.getCurrentStandardErrContents();
-
-        String[] lines = stdOut.split("\n");
-        String usage = lines[0];
-        assertEquals("usage: thermostat help [command-name]", usage);
-
-        assertEquals(stdErr, "");
-    }
-
-    @Test
-    public void testVersionArgument() throws Exception {
-        Spawn shell = spawnThermostat("--version");
-        shell.expectClose();
-
-        String stdOut = shell.getCurrentStandardOutContents();
-        String stdErr = shell.getCurrentStandardErrContents();
-
-        assertTrue(stdOut.matches("Thermostat version \\d+\\.\\d+\\.\\d+\n"));
-        assertEquals(stdErr, "");
-    }
-
-    @Test
-    public void testShell() throws Exception {
-        Spawn shell = spawnThermostat("shell");
-
-        shell.expect(SHELL_PROMPT);
-        shell.send("help\n");
-
-        shell.expect(SHELL_PROMPT);
-
-        assertMatchesShellHelpCommandList(shell.getCurrentStandardOutContents());
-
-        shell.send("exit\n");
-
-        shell.expectClose();
-    }
-
-    @Test
-    public void testShellPrintsVersionOnStartup() throws Exception {
-        Spawn shell = spawnThermostat("shell");
-
-        shell.expect(SHELL_PROMPT);
-
-        String stdOut = shell.getCurrentStandardOutContents();
-        assertTrue(stdOut.contains("Thermostat version "));
-    }
-    
-    @Test
-    public void versionArgumentInShellIsNotAllowed() throws Exception {
-        Spawn shell = spawnThermostat("shell");
-
-        shell.expect(SHELL_PROMPT);
-        shell.send("--version\n");
-
-        shell.expect(SHELL_PROMPT);
-
-        String stdOut = shell.getCurrentStandardOutContents();
-        String stdErr = shell.getCurrentStandardErrContents();
-
-        assertMatchesShellHelpCommandList(shell.getCurrentStandardOutContents());
-        // use the Pattern.DOTALL flag (?s) so that line terminators match with
-        // ".*". stdOut contains the SHELL_PROMPT too.
-        assertTrue(stdOut.matches("(?s)^.*\nunknown command '--version'\n.*$"));
-        assertEquals(stdErr, "");
-        
-        shell.send("exit\n");
-
-        shell.expectClose();
-    }
-
-    @Test
-    public void testShellHelp() throws Exception {
-        Spawn shell = spawnThermostat("help", "shell");
-        shell.expectClose();
-
-        String stdOut = shell.getCurrentStandardOutContents();
-
-        String[] lines = stdOut.split("\n");
-        String usage = lines[0];
-        assertTrue(usage.matches("^usage: thermostat shell$"));
-        String description = lines[1];
-        assertTrue(description.matches("^\\s+launches the Thermostat interactive shell$"));
-        assertTrue(lines[3].matches("thermostat shell"));
-    }
-
-    @Test
-    public void testShellHelpArgument() throws Exception {
-        Spawn shell = spawnThermostat("shell", "--help");
-        shell.expectClose();
-
-        String stdOut = shell.getCurrentStandardOutContents();
-
-        String[] lines = stdOut.split("\n");
-        String usage = lines[0];
-        assertTrue(usage.matches("^usage: thermostat shell$"));
-        String description = lines[1];
-        assertTrue(description.matches("^\\s+launches the Thermostat interactive shell$"));
-        assertTrue(lines[3].matches("thermostat shell"));
-    }
-
-    @Test
-    public void testShellUnrecognizedArgument() throws Exception {
-        Spawn shell = spawnThermostat("shell", "--foo");
-        shell.expectClose();
-        String stdOut = shell.getCurrentStandardOutContents();
-        String expectedOut = "Could not parse options: Unrecognized option: --foo\n"
-                           + "usage: thermostat shell\n"
-                           + "                  launches the Thermostat interactive shell\n"
-                           + "\n"
-                           + "thermostat shell\n\n";
-        assertEquals(expectedOut, stdOut);
-    }
-
-    @Test
-    public void testUnrecognizedEventsInShell() throws Exception {
-        // test '!' events
-        Spawn shell = spawnThermostat("shell");
-
-        shell.expect(SHELL_PROMPT);
-        shell.send("what!?!\n");
-        shell.expect(SHELL_PROMPT);
-        shell.send("exit\n");
-        shell.expectClose();
-
-        assertTrue(shell.getCurrentStandardErrContents().contains("!?!: event not found"));
-        assertNoExceptions(shell.getCurrentStandardOutContents(), shell.getCurrentStandardErrContents());
-    }
-
-    @Test
-    public void testInvalidCommand() throws Exception {
-        Spawn shell = spawnThermostat("foobar", "baz");
-
-        // TODO should this be stderr?
-        shell.expect("unknown command 'foobar'");
-        shell.expectClose();
-
-        String stdOut = shell.getCurrentStandardOutContents();
-
-        assertMatchesHelpCommandList(stdOut);
-    }
-
-    private static void assertMatchesHelpCommandList(String actual) {
-        assertTrue(actual.contains("list of commands"));
-        assertTrue(actual.contains("help"));
-        assertTrue(actual.contains("agent"));
-        assertTrue(actual.contains("gui"));
-        assertTrue(actual.contains("ping"));
-        assertTrue(actual.contains("shell"));
-    }
-
-    private static void assertMatchesShellHelpCommandList(String actual) {
-        assertTrue(actual.contains("list of commands"));
-        assertTrue(actual.contains("help"));
-        assertTrue(actual.contains("connect"));
-        assertTrue(actual.contains("disconnect"));
-        assertTrue(actual.contains("ping"));
-    }
-
-}
-
--- a/integration-tests/src/test/java/com/redhat/thermostat/itest/IntegrationTest.java	Wed Oct 16 13:19:53 2013 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,378 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.itest;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.nio.file.Files;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import com.redhat.thermostat.common.utils.StreamUtils;
-
-import expectj.Executor;
-import expectj.ExpectJ;
-import expectj.Spawn;
-import expectj.TimeoutException;
-
-/**
- * Helper methods to support writing an integration test.
- * <p>
- * This class should be used by all integration tests to start
- * thermostat and to obtain paths to various locations. Starting
- * thermostat manually will cause issues with wrong paths being
- * used.
- */
-public class IntegrationTest {
-
-    public static class SpawnResult {
-        final Process process;
-        final Spawn spawn;
-
-        public SpawnResult(Process process, Spawn spawn) {
-            this.process = process;
-            this.spawn = spawn;
-        }
-    }
-
-    // FIXME Make sure all methods are using a sane environment that's set up correctly
-
-    public static final long TIMEOUT_IN_SECONDS = 30;
-
-    public static final String SHELL_PROMPT = "Thermostat >";
-
-    private static final String THERMOSTAT_HOME = "THERMOSTAT_HOME";
-    private static final String USER_THERMOSTAT_HOME = "USER_THERMOSTAT_HOME";
-
-    /* This is a mirror of paths from c.r.t.shared.Configuration */
-
-    private static String getThermostatExecutable() {
-        return getThermostatHome() + "/bin/thermostat";
-    }
-    
-    public static String getThermostatHome() {
-        return "../distribution/target/image";
-    }
-
-    public static String getSystemPluginHome() {
-        return getThermostatHome() + "/plugins";
-    }
-
-    public static String getConfigurationDir() {
-        return getThermostatHome() + "/etc";
-    }
-
-    public static String getUserThermostatHome() {
-        return "../distribution/target/user-home";
-    }
-
-    public static String getStorageDataDirectory() {
-        return getUserThermostatHome() + "/data/db";
-    }
-
-    public static void clearStorageDataDirectory() throws IOException {
-        File storageDir = new File(getStorageDataDirectory());
-        if (storageDir.exists()) {
-            if (storageDir.isDirectory()) {
-                deleteFilesRecursivelyUnder(storageDir);
-            } else {
-                throw new IllegalStateException(storageDir + " exists but is not a directory");
-            }
-        }
-    }
-
-    public static Process runThermostat(String... args) throws IOException {
-        List<String> completeArgs = new ArrayList<String>(args.length+1);
-        completeArgs.add(getThermostatExecutable());
-        completeArgs.addAll(Arrays.asList(args));
-        ProcessBuilder builder = buildThermostatProcess(completeArgs);
-
-        return builder.start();
-    }
-
-    public static Spawn spawnThermostat(String... args) throws IOException {
-        return spawnThermostat(false, args);
-    }
-    
-    public static Spawn startStorage() throws Exception {
-        clearStorageDataDirectory();
-
-        Spawn storage = spawnThermostat("storage", "--start");
-        try {
-            storage.expect("pid:");
-        } catch (IOException e) {
-            // this may happen if storage is already running.
-            e.printStackTrace();
-            String stdOutContents = storage.getCurrentStandardOutContents();
-            
-            System.err.flush();
-            System.out.flush();
-            System.err.println("stdout was: -->" + stdOutContents +"<--");
-            System.err.println("stderr was: -->" + storage.getCurrentStandardErrContents() + "<--");
-            System.err.flush();
-            assertFalse(stdOutContents.contains("Storage is already running with pid"));
-            throw new Exception("Something funny is going on when trying to start storage!", e);
-        }
-        storage.expectClose();
-
-        assertNoExceptions(storage.getCurrentStandardOutContents(), storage.getCurrentStandardErrContents());
-        return storage;
-    }
-    
-    public static Spawn stopStorage() throws Exception {
-        Spawn storage = spawnThermostat("storage", "--stop");
-        storage.expect("server shutdown complete");
-        storage.expectClose();
-        assertNoExceptions(storage.getCurrentStandardOutContents(), storage.getCurrentStandardErrContents());
-        return storage;
-    }
-
-    public static Spawn spawnThermostat(boolean localeDependent, String... args) throws IOException {
-        ExpectJ expect = new ExpectJ(TIMEOUT_IN_SECONDS);
-        StringBuilder result = new StringBuilder(getThermostatExecutable());
-        if (args != null) {
-            for (String arg : args) {
-                result.append(" ").append(arg);
-            }
-        }
-        String toExecute = result.toString();
-        Executor exec = null;
-        if (localeDependent) {
-            exec = new LocaleExecutor(toExecute);
-        } else {
-            exec = new SimpleExecutor(toExecute);
-        }
-        return expect.spawn(exec);
-    }
-
-    public static SpawnResult spawnThermostatAndGetProcess(String... args) throws IOException {
-        final List<String> completeArgs = new ArrayList<String>(args.length+1);
-        completeArgs.add(getThermostatExecutable());
-        completeArgs.addAll(Arrays.asList(args));
-
-        final Process[] process = new Process[1];
-
-        ExpectJ expect = new ExpectJ(TIMEOUT_IN_SECONDS);
-
-        Spawn spawn = expect.spawn(new Executor() {
-            @Override
-            public Process execute() throws IOException {
-                ProcessBuilder builder = buildThermostatProcess(completeArgs);
-                Process service = builder.start();
-                process[0] = service;
-                return service;
-            }
-        });
-
-        return new SpawnResult(process[0], spawn);
-    }
-
-    private static ProcessBuilder buildThermostatProcess(List<String> args) {
-        ProcessBuilder builder = new ProcessBuilder(args);
-        builder.environment().put(THERMOSTAT_HOME, getThermostatHome());
-        builder.environment().put(USER_THERMOSTAT_HOME, getUserThermostatHome());
-
-        return builder;
-    }
-
-    /**
-     * Generic method to run a program.
-     * <p>
-     * DO NOT USE THIS TO RUN THERMOSTAT ITSELF. It does not set up the
-     * environment correctly, using incorrect data and possibly overwriting
-     * important data.
-     */
-    public static Spawn spawn(List<String> args) throws IOException {
-        ExpectJ expect = new ExpectJ(TIMEOUT_IN_SECONDS);
-        StringBuilder result = new StringBuilder();
-        for (String arg : args) {
-            result.append(arg).append(" ");
-        }
-        return expect.spawn(result.substring(0, result.length() - 1));
-    }
-
-    /**
-     * Kill the process and all its children, recursively. Sends SIGTERM.
-     */
-    public static void killRecursively(Process process) throws Exception {
-        killRecursively(getPid(process));
-    }
-
-    private static void killRecursively(int pid) throws Exception {
-        List<Integer> childPids = findChildPids(pid);
-        for (Integer childPid : childPids) {
-            killRecursively(childPid);
-        }
-        killProcess(pid);
-    }
-
-    private static void killProcess(int processId) throws Exception {
-        System.err.println("Killing process with pid: " + processId);
-        Runtime.getRuntime().exec("kill " + processId).waitFor();
-    }
-
-    private static List<Integer> findChildPids(int processId) throws IOException {
-        String children = new String(StreamUtils.readAll(Runtime.getRuntime().exec("ps --ppid " + processId + " -o pid=").getInputStream()));
-        String[] childPids = children.split("\n");
-        List<Integer> result = new ArrayList<>();
-        for (String childPid : childPids) {
-            String pidString = childPid.trim();
-            if (pidString.length() == 0) {
-                continue;
-            }
-            try {
-                result.add(Integer.parseInt(pidString));
-            } catch (NumberFormatException nfe) {
-                System.err.println(nfe);
-            }
-        }
-        return result;
-    }
-
-    private static int getPid(Process process) throws Exception {
-        final String UNIX_PROCESS_CLASS = "java.lang.UNIXProcess";
-        if (!process.getClass().getName().equals(UNIX_PROCESS_CLASS)) {
-            throw new IllegalArgumentException("can only kill " + UNIX_PROCESS_CLASS + "; input is a " + process.getClass());
-        }
-
-        Class<?> processClass = process.getClass();
-        Field pidField = processClass.getDeclaredField("pid");
-        pidField.setAccessible(true);
-        return (int) pidField.get(process);
-    }
-
-    private static void deleteFilesRecursivelyUnder(File path) throws IOException {
-        if (!path.isDirectory()) {
-            throw new IOException("Cannot delete files under a non-directory: " + path);
-        }
-        File[] filesToDelete = path.listFiles();
-        if (filesToDelete == null) {
-            throw new IOException("Error getting directory listing: " + path);
-        }
-        for (File theFile : filesToDelete) {
-            if (theFile.isDirectory()) {
-                deleteFilesRecursivelyUnder(theFile);
-            }
-            Files.deleteIfExists(theFile.toPath());
-        }
-    }
-
-    /** Confirm that there are no 'command not found'-like messages in the spawn's stdout/stderr */
-    public static void assertCommandIsFound(Spawn spawn) {
-        assertCommandIsFound(spawn.getCurrentStandardOutContents(), spawn.getCurrentStandardErrContents());
-    }
-
-    public static void assertCommandIsFound(String stdOutContents, String stdErrContents) {
-        assertFalse(stdOutContents.contains("unknown command"));
-        assertFalse(stdErrContents.contains("unknown command"));
-    }
-
-    /** Confirm that there are no exception stack traces in the spawn's stdout/stderr */
-    public static void assertNoExceptions(Spawn spawn) {
-        assertNoExceptions(spawn.getCurrentStandardOutContents(), spawn.getCurrentStandardErrContents());
-    }
-
-    public static void assertNoExceptions(String stdOutContents, String stdErrContents) {
-        assertFalse(stdOutContents.contains("Exception"));
-        assertFalse(stdErrContents.contains("Exception"));
-    }
-
-    public static void assertOutputEndsWith(String stdOutContents, String expectedOutput) {
-        String endOfOut = stdOutContents.substring(stdOutContents.length() - expectedOutput.length());
-        assertEquals(expectedOutput, endOfOut);
-    }
-
-    public static void handleAuthPrompt(Spawn spawn, String url, String user, String password) throws IOException, TimeoutException {
-        spawn.expect("Please enter username for storage at " + url + ":");
-        spawn.send(user + "\r");
-        spawn.expect("Please enter password for storage at " + url + ":");
-        spawn.send(password + "\r");
-    }
-
-    private static class LocaleExecutor extends EnvironmentExecutor {
-
-        public static final String[] ENV_WITH_LANG_C = {
-                THERMOSTAT_HOME + "=" + getThermostatHome(),
-                USER_THERMOSTAT_HOME + "=" + getUserThermostatHome(),
-                "LANG=C"
-        };
-
-        public LocaleExecutor(String process) {
-            super(process, ENV_WITH_LANG_C);
-        }
-
-    }
-
-    private static class SimpleExecutor extends EnvironmentExecutor {
-
-        public static final String[] ENV_WITH = {
-                THERMOSTAT_HOME + "=" + getThermostatHome(),
-                USER_THERMOSTAT_HOME + "=" + getUserThermostatHome(),
-        };
-
-        public SimpleExecutor(String process) {
-            super(process, ENV_WITH);
-        }
-    }
-
-    private static class EnvironmentExecutor implements Executor {
-
-        private final String[] env;
-        private final String process;
-
-        public EnvironmentExecutor(String process, String[] env) {
-            this.process = process;
-            this.env = env;
-        }
-
-        @Override
-        public Process execute() throws IOException {
-            return Runtime.getRuntime().exec(process, env);
-        }
-
-        @Override
-        public String toString() {
-            return process;
-        }
-    }
-}
-
--- a/integration-tests/src/test/java/com/redhat/thermostat/itest/MongoQueriesTest.java	Wed Oct 16 13:19:53 2013 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,543 +0,0 @@
-/*
- * Copyright 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.itest;
-
-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 java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import com.redhat.thermostat.host.cpu.common.CpuStatDAO;
-import com.redhat.thermostat.host.cpu.common.model.CpuStat;
-import com.redhat.thermostat.storage.config.StartupConfiguration;
-import com.redhat.thermostat.storage.core.Add;
-import com.redhat.thermostat.storage.core.BackingStorage;
-import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
-import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
-import com.redhat.thermostat.storage.core.Cursor;
-import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.Query;
-import com.redhat.thermostat.storage.core.Query.SortDirection;
-import com.redhat.thermostat.storage.core.Storage;
-import com.redhat.thermostat.storage.mongodb.internal.MongoStorage;
-import com.redhat.thermostat.storage.query.Expression;
-import com.redhat.thermostat.storage.query.ExpressionFactory;
-import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
-import com.redhat.thermostat.vm.classstat.common.model.VmClassStat;
-import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
-import com.redhat.thermostat.vm.cpu.common.model.VmCpuStat;
-
-/*
- * This test class starts up a mongod instance and tests if thermostat
- * queries with expressions return what they supposed to return. 
- * 
- * Tests should make their own connection to storage, probably by making use of
- * one of the getAndConnectStorage() method variants.
- * 
- * Because the storage instance is shared among all of the tests, it is
- * necessary to take precautions to avoid introducing data dependencies
- * between tests.  Such precautions could include: using a different
- * category (ie mongod collection) than any other existing test; setting
- * a unique agent-id for all data written and then deleting the data
- * at the end of the test; <insert other clever idea here>.
- * 
- */
-public class MongoQueriesTest extends IntegrationTest {
-
-    private static class CountdownConnectionListener implements ConnectionListener {
-
-        private final ConnectionStatus target;
-        private final CountDownLatch latch;
-
-        private CountdownConnectionListener(ConnectionStatus target, CountDownLatch latch) {
-            this.target = target;
-            this.latch = latch;
-        }
-
-        @Override
-        public void changed(ConnectionStatus newStatus) {
-            assertEquals(target, newStatus);
-            latch.countDown();
-        }
-    }
-
-    private static final double EQUALS_DELTA = 0.00000000000001;
-    private static final String VM_ID1 = "vmId1";
-    private static final String VM_ID2 = "vmId2";
-    private static final String VM_ID3 = "vmId3";
-
-    private ExpressionFactory factory = new ExpressionFactory();
-
-    @BeforeClass
-    public static void setUpOnce() throws Exception {
-        startStorage();
-
-        addCpuData(4);
-    }
-
-    @AfterClass
-    public static void tearDownOnce() throws Exception {
-        deleteCpuData();
-
-        stopStorage();
-    }
-
-    /*
-     * Make a connection to mongo storage (returning the Storage object). Before
-     * initiating the connection, add the ConnectionListener to Storage.
-     */
-    private static BackingStorage getAndConnectStorage(ConnectionListener listener) {
-        final String url = "mongodb://127.0.0.1:27518";
-        StartupConfiguration config = new StartupConfiguration() {
-
-            @Override
-            public String getDBConnectionString() {
-                return url;
-            }
-            
-        };
-        BackingStorage storage = new MongoStorage(config);
-        if (listener != null) {
-            storage.getConnection().addListener(listener);
-        }
-        storage.getConnection().connect();
-        return storage;
-    }
-
-    private static void addCpuData(int numberOfItems) throws InterruptedException {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        BackingStorage storage = getAndConnectStorage(listener);
-        latch.await();
-        storage.getConnection().removeListener(listener);
-        
-        storage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        for (int i = 0; i < numberOfItems; i++) {
-            CpuStat pojo = new CpuStat("test-agent-id", i, new double[] {i, i*2});
-            Add<CpuStat> add = storage.createAdd(CpuStatDAO.cpuStatCategory);
-            add.set(Key.AGENT_ID.getName(), pojo.getAgentId());
-            add.set(CpuStatDAO.cpuLoadKey.getName(), pojo.getPerProcessorUsage());
-            add.set(Key.TIMESTAMP.getName(), pojo.getTimeStamp());
-            add.apply();
-        }
-
-        storage.getConnection().disconnect();
-    }
-
-    private static void deleteCpuData() throws InterruptedException {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage storage = getAndConnectStorage(listener);
-        latch.await();
-        storage.getConnection().removeListener(listener);
-        storage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        storage.purge("test-agent-id");
-
-        storage.getConnection().disconnect();
-    }
-
-    private void executeAndVerifyQuery(Query<CpuStat> query, List<Long> expectedTimestamps) {
-        Cursor<CpuStat> cursor = query.execute();
-
-        for (Long time : expectedTimestamps) {
-            assertTrue(cursor.hasNext());
-            CpuStat pojo = cursor.next();
-            assertEquals("test-agent-id", pojo.getAgentId());
-            assertEquals(time.longValue(), pojo.getTimeStamp());
-            double[] data = pojo.getPerProcessorUsage();
-            assertEquals(time, data[0], EQUALS_DELTA);
-            assertEquals(time*2, data[1], EQUALS_DELTA);
-        }
-        assertFalse(cursor.hasNext());
-    }
-
-    @Test
-    public void testMongoAdd() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        BackingStorage mongoStorage = getAndConnectStorage(listener);
-        latch.await();
-        mongoStorage.getConnection().removeListener(listener);
-        mongoStorage.registerCategory(VmClassStatDAO.vmClassStatsCategory);
-        
-        Add<VmClassStat> add = mongoStorage.createAdd(VmClassStatDAO.vmClassStatsCategory);
-        VmClassStat pojo = new VmClassStat();
-        pojo.setAgentId("fluff");
-        pojo.setLoadedClasses(12345);
-        pojo.setTimeStamp(42);
-        pojo.setVmId(VM_ID1);
-        addVmClassStat(add, pojo);
-        
-        // Add another couple of entries
-        add = mongoStorage.createAdd(VmClassStatDAO.vmClassStatsCategory);
-        pojo = new VmClassStat();
-        pojo.setAgentId("fluff");
-        pojo.setLoadedClasses(67890);
-        pojo.setTimeStamp(42);
-        pojo.setVmId(VM_ID2);
-        addVmClassStat(add, pojo);
-        
-        add = mongoStorage.createAdd(VmClassStatDAO.vmClassStatsCategory);
-        pojo = new VmClassStat();
-        pojo.setAgentId("fluff");
-        pojo.setLoadedClasses(34567);
-        pojo.setTimeStamp(42);
-        pojo.setVmId(VM_ID3);
-        addVmClassStat(add, pojo);
-
-        mongoStorage.getConnection().disconnect();
-    }
-
-    private void addVmClassStat(Add<VmClassStat> add, VmClassStat pojo) {
-        add.set(Key.AGENT_ID.getName(), pojo.getAgentId());
-        add.set(Key.VM_ID.getName(), pojo.getVmId());
-        add.set(Key.TIMESTAMP.getName(), pojo.getTimeStamp());
-        add.set(VmClassStatDAO.loadedClassesKey.getName(), pojo.getLoadedClasses());
-        add.apply();
-    }
-
-    @Test
-    public void canQueryNoWhere() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        BackingStorage mongoStorage = getAndConnectStorage(listener);
-        latch.await();
-        mongoStorage.getConnection().removeListener(listener);
-        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-        
-        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
-        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
-
-        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 2l, 3l));
-
-        mongoStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void canQueryEqualTo() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        BackingStorage mongoStorage = getAndConnectStorage(listener);
-        latch.await();
-        mongoStorage.getConnection().removeListener(listener);
-        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
-        Expression expr = factory.equalTo(Key.TIMESTAMP, 2l);
-        query.where(expr);
-        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
-
-        executeAndVerifyQuery(query, Arrays.asList(2l));
-
-        mongoStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void canQueryNotEqualTo() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        BackingStorage mongoStorage = getAndConnectStorage(listener);
-        latch.await();
-        mongoStorage.getConnection().removeListener(listener);
-        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
-        Expression expr = factory.notEqualTo(Key.TIMESTAMP, 2l);
-        query.where(expr);
-        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
-
-        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 3l));
-
-        mongoStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void canQueryGreaterThan() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        BackingStorage mongoStorage = getAndConnectStorage(listener);
-        latch.await();
-        mongoStorage.getConnection().removeListener(listener);
-        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
-        Expression expr = factory.greaterThan(Key.TIMESTAMP, 2l);
-        query.where(expr);
-        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
-
-        executeAndVerifyQuery(query, Arrays.asList(3l));
-
-        mongoStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void canQueryGreaterThanOrEqualTo() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        BackingStorage mongoStorage = getAndConnectStorage(listener);
-        latch.await();
-        mongoStorage.getConnection().removeListener(listener);
-        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
-        Expression expr = factory.greaterThanOrEqualTo(Key.TIMESTAMP, 2l);
-        query.where(expr);
-        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
-
-        executeAndVerifyQuery(query, Arrays.asList(2l, 3l));
-
-        mongoStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void canQueryLessThan() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        BackingStorage mongoStorage = getAndConnectStorage(listener);
-        latch.await();
-        mongoStorage.getConnection().removeListener(listener);
-        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
-        Expression expr = factory.lessThan(Key.TIMESTAMP, 2l);
-        query.where(expr);
-        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
-
-        executeAndVerifyQuery(query, Arrays.asList(0l, 1l));
-
-        mongoStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void canQueryLessThanOrEqualTo() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        BackingStorage mongoStorage = getAndConnectStorage(listener);
-        latch.await();
-        mongoStorage.getConnection().removeListener(listener);
-        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
-        Expression expr = factory.lessThanOrEqualTo(Key.TIMESTAMP, 2l);
-        query.where(expr);
-        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
-
-        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 2l));
-
-        mongoStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void canQueryIn() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        BackingStorage mongoStorage = getAndConnectStorage(listener);
-        latch.await();
-        mongoStorage.getConnection().removeListener(listener);
-        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        List<Long> times = Arrays.asList(0l, 2l);
-        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
-        Expression expr = factory.in(Key.TIMESTAMP, new HashSet<>(times), Long.class);
-        query.where(expr);
-        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
-
-        executeAndVerifyQuery(query, times);
-
-        mongoStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void canQueryNotIn() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        BackingStorage mongoStorage = getAndConnectStorage(listener);
-        latch.await();
-        mongoStorage.getConnection().removeListener(listener);
-        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
-        Expression expr = factory.notIn(Key.TIMESTAMP, new HashSet<>(Arrays.asList(0l, 2l)), Long.class);
-        query.where(expr);
-        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
-
-        executeAndVerifyQuery(query, Arrays.asList(1l, 3l));
-
-        mongoStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void canQueryNot() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        BackingStorage mongoStorage = getAndConnectStorage(listener);
-        latch.await();
-        mongoStorage.getConnection().removeListener(listener);
-        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
-        Expression expr = factory.not(factory.greaterThan(Key.TIMESTAMP, 2l));
-        query.where(expr);
-        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
-
-        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 2l));
-
-        mongoStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void canQueryAnd() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        BackingStorage mongoStorage = getAndConnectStorage(listener);
-        latch.await();
-        mongoStorage.getConnection().removeListener(listener);
-        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
-        Expression expr = factory.and(factory.greaterThan(Key.TIMESTAMP, 0l),
-                                      factory.lessThan(Key.TIMESTAMP, 2l));
-        query.where(expr);
-        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
-
-        executeAndVerifyQuery(query, Arrays.asList(1l));
-
-        mongoStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void canQueryOr() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        BackingStorage mongoStorage = getAndConnectStorage(listener);
-        latch.await();
-        mongoStorage.getConnection().removeListener(listener);
-        mongoStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        Query<CpuStat> query = mongoStorage.createQuery(CpuStatDAO.cpuStatCategory);
-        Expression expr = factory.or(factory.greaterThan(Key.TIMESTAMP, 2l),
-                                      factory.lessThan(Key.TIMESTAMP, 1l));
-        query.where(expr);
-        query.sort(Key.TIMESTAMP, SortDirection.ASCENDING);
-
-        executeAndVerifyQuery(query, Arrays.asList(0l, 3l));
-
-        mongoStorage.getConnection().disconnect();
-    }
-
-    @Test
-    public void canLoadSave() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        Storage mongoStorage = getAndConnectStorage(listener);
-        latch.await();
-        mongoStorage.getConnection().removeListener(listener);
-        
-        byte[] data = "Hello World".getBytes();
-        mongoStorage.saveFile("test", new ByteArrayInputStream(data));
-        // Note: On the server side, the file is saved into mongodb
-        // via GridFS.  The save operation returns before write is
-        // complete, and there is no callback mechanism to find out
-        // when the write is complete.  So, we try a few times to
-        // load it before considering it a failure.
-        InputStream loadStream = null;
-        int loadAttempts = 0;
-        while (loadStream == null && loadAttempts < 3) {
-            Thread.sleep(300);
-            loadStream = mongoStorage.loadFile("test");
-            loadAttempts++;
-        }
-        assertNotNull(loadStream);
-        StringBuilder str = new StringBuilder();
-        int i = loadStream.read();
-        while (i != -1) {
-            str.append((char) i);
-            i = loadStream.read();
-        }
-        assertEquals("Hello World", str.toString());
-
-        mongoStorage.getConnection().disconnect();
-    }
-
-    @Test
-    public void storagePurge() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.CONNECTED, latch);
-        BackingStorage mongoStorage = getAndConnectStorage(listener);
-        latch.await();
-        mongoStorage.getConnection().removeListener(listener);
-        UUID uuid = new UUID(42, 24);
-
-        mongoStorage.registerCategory(VmCpuStatDAO.vmCpuStatCategory);
-        long timeStamp = 5;
-        double cpuLoad = 0.15;
-        VmCpuStat pojo = new VmCpuStat(uuid.toString(), timeStamp, VM_ID1, cpuLoad);
-        Add<VmCpuStat> add = mongoStorage.createAdd(VmCpuStatDAO.vmCpuStatCategory);
-        add.set(Key.AGENT_ID.getName(), pojo.getAgentId());
-        add.set(Key.VM_ID.getName(), pojo.getVmId());
-        add.set(Key.TIMESTAMP.getName(), pojo.getTimeStamp());
-        add.set(VmCpuStatDAO.vmCpuLoadKey.getName(), pojo.getCpuLoad());
-        add.apply();
-
-        Query<VmCpuStat> query = mongoStorage.createQuery(VmCpuStatDAO.vmCpuStatCategory);
-        Cursor<VmCpuStat> cursor = query.execute();
-        assertTrue(cursor.hasNext());
-        pojo = cursor.next();
-        assertFalse(cursor.hasNext());
-
-        assertEquals(timeStamp, pojo.getTimeStamp());
-        assertEquals(VM_ID1, pojo.getVmId());
-        assertEquals(cpuLoad, pojo.getCpuLoad(), EQUALS_DELTA);
-        assertEquals(uuid.toString(), pojo.getAgentId());
-
-        mongoStorage.purge(uuid.toString());
-    }
-}
--- a/integration-tests/src/test/java/com/redhat/thermostat/itest/PluginTest.java	Wed Oct 16 13:19:53 2013 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,232 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.itest;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import expectj.Spawn;
-
-public class PluginTest extends IntegrationTest {
-
-    private static final String PLUGIN_HOME = getSystemPluginHome();
-
-    private static NewCommandPlugin fooPlugin = new NewCommandPlugin("foo", "provides foo command", PLUGIN_HOME + File.separator + "new");
-    private static NewCommandPlugin userPlugin = new NewCommandPlugin(
-            "user",
-            "a plugin that is provided by the user",
-            getUserThermostatHome() + File.separator + "data" + File.separator + "plugins" + File.separator + "user");
-    private static UnknownExtendsPlugin unknownExtension = new UnknownExtendsPlugin(PLUGIN_HOME + File.separator + "unknown");
-
-    @BeforeClass
-    public static void setUpOnce() {
-        fooPlugin.install();
-        userPlugin.install();
-        unknownExtension.install();
-    }
-
-    @AfterClass
-    public static void tearDownOnce() {
-        unknownExtension.uninstall();
-        userPlugin.uninstall();
-        fooPlugin.uninstall();
-    }
-
-    @Test
-    public void testHelpIsOkay() throws Exception {
-        Spawn shell = spawnThermostat("help");
-        shell.expectClose();
-
-        String stdOut = shell.getCurrentStandardOutContents();
-
-        assertTrue(stdOut.contains("list of commands"));
-        assertTrue(stdOut.contains("help"));
-        assertTrue(stdOut.contains("agent"));
-        assertTrue(stdOut.contains("gui"));
-        assertTrue(stdOut.contains("ping"));
-        assertTrue(stdOut.contains("shell"));
-
-        assertTrue(stdOut.contains(fooPlugin.command));
-        assertTrue(stdOut.contains(fooPlugin.description));
-
-        assertTrue(stdOut.contains(userPlugin.command));
-
-        assertFalse(stdOut.contains(unknownExtension.command));
-
-        // TODO assertEquals("", stdErr);
-    }
-
-    /**
-     * This plugin provides a new command
-     */
-    private static class NewCommandPlugin {
-
-        private final String pluginHome;
-        private final String command;
-        private final String description;
-
-        public NewCommandPlugin(String command, String description, String pluginLocation) {
-            this.pluginHome = pluginLocation;
-
-            this.command = command;
-            this.description = description;
-        }
-
-        private void install() {
-            File home = new File(pluginHome);
-            if (!home.isDirectory() && !home.mkdirs()) {
-                throw new AssertionError("could not create directory: " + pluginHome);
-            }
-
-            String pluginContents = "" +
-                    "<?xml version=\"1.0\"?>\n" +
-                    "<plugin xmlns=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\"\n" +
-                    " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
-                    " xsi:schemaLocation=\"http://icedtea.classpath.org/thermostat/plugins/v1.0 thermost-plugin.xsd\">\n" +
-                    "  <commands>" +
-                    "    <command>" +
-                    "      <name>" + command + "</name>" +
-                    "      <description>" + description + "</description>" +
-                    "      <options>" +
-                    "        <option>" +
-                    "         <long>aaaaa</long>" +
-                    "         <short>a</short>" +
-                    "        </option>" +
-                    "      </options>" +
-                    "      <environments>" +
-                    "        <environment>shell</environment>" +
-                    "        <environment>cli</environment>" +
-                    "      </environments>" +
-                    "      <bundles>" +
-                    "        <bundle>" +
-                    "          <symbolic-name>bar</symbolic-name>" +
-                    "          <version>0.1.0</version>" +
-                    "        </bundle>" +
-                    "      </bundles>" +
-                    "    </command>" +
-                    "  </commands>" +
-                    "</plugin>";
-
-            try (FileWriter writer = new FileWriter(pluginHome + File.separator + "thermostat-plugin.xml")) {
-                writer.write(pluginContents);
-            } catch (IOException e) {
-                throw new AssertionError("unable to write plugin configuration", e);
-            }
-
-        }
-
-        private void uninstall() {
-            if (!new File(pluginHome).exists()) {
-                return;
-            }
-            if (!new File(pluginHome, "thermostat-plugin.xml").delete()) {
-                throw new AssertionError("Could not delete plugin file");
-            }
-            if (!new File(pluginHome).delete()) {
-                throw new AssertionError("Could not delete plugin directory");
-            }
-        }
-    }
-
-    /**
-     * This plugin extends an unknown command
-     */
-    private static class UnknownExtendsPlugin {
-
-        private final String pluginHome;
-        private final String command;
-
-        public UnknownExtendsPlugin(String pluginLocation) {
-            this.pluginHome = pluginLocation;
-
-            this.command = "unknown-command";
-        }
-
-        private void install() {
-            File home = new File(pluginHome);
-            if (!home.isDirectory() && !home.mkdir()) {
-                throw new AssertionError("could not create directory: " + pluginHome);
-            }
-
-            String pluginContents = "" +
-                    "<?xml version=\"1.0\"?>\n" +
-                    "<plugin xmlns=\"http://icedtea.classpath.org/thermostat/plugins/v1.0\"\n" +
-                    " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
-                    " xsi:schemaLocation=\"http://icedtea.classpath.org/thermostat/plugins/v1.0 thermost-plugin.xsd\">\n" +
-                    "  <extensions>" +
-                    "    <extension>" +
-                    "      <name>" + command + "</name>" +
-                    "      <bundles>" +
-                    "        <bundle>" +
-                    "          <symbolic-name>bar</symbolic-name>" +
-                    "          <version>0.1.0</version>" +
-                    "        </bundle>" +
-                    "      </bundles>" +
-                    "    </extension>" +
-                    "  </extensions>" +
-                    "</plugin>";
-
-            try (FileWriter writer = new FileWriter(pluginHome + File.separator + "thermostat-plugin.xml")) {
-                writer.write(pluginContents);
-            } catch (IOException e) {
-                throw new AssertionError("unable to write plugin configuration", e);
-            }
-
-        }
-
-        private void uninstall() {
-            if (!new File(pluginHome).exists()) {
-                return;
-            }
-            if (!new File(pluginHome, "thermostat-plugin.xml").delete()) {
-                throw new AssertionError("Could not delete plugin file");
-            }
-            if (!new File(pluginHome).delete()) {
-                throw new AssertionError("Could not delete plugin directory");
-            }
-        }
-    }
-
-}
--- a/integration-tests/src/test/java/com/redhat/thermostat/itest/StorageConnectionTest.java	Wed Oct 16 13:19:53 2013 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.itest;
-
-import java.io.IOException;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
-import expectj.ExpectJException;
-import expectj.Spawn;
-import expectj.TimeoutException;
-
-public class StorageConnectionTest extends IntegrationTest {
-
-    // @BeforeClass // reinstate once we actually need storage running (see ignored tests)
-    public static void setUpOnce() throws Exception {
-        startStorage();
-    }
-
-    // @AfterClass // reinstate once we actually need storage running
-    public static void tearDownOnce() throws Exception {
-        stopStorage();
-    }
-
-    @Ignore //FIXME when keyring/preferences improvements have been made, un-Ignore
-    @Test
-    public void testConnect() throws ExpectJException, TimeoutException, IOException {
-        Spawn shell = spawnThermostat(true, "shell");
-
-        shell.expect(SHELL_PROMPT);
-        shell.send("connect -d mongodb://127.0.0.1:27518\n");
-        handleAuthPrompt(shell, "mongodb://127.0.0.1:27518", "", "");
-        shell.expect(SHELL_PROMPT);
-        shell.send("exit\n");
-        shell.expectClose();
-
-        assertCommandIsFound(shell.getCurrentStandardOutContents(), shell.getCurrentStandardErrContents());
-        assertNoExceptions(shell.getCurrentStandardOutContents(), shell.getCurrentStandardErrContents());
-    }
-
-    @Test
-    public void testDisconnectWithoutConnecting() throws ExpectJException, TimeoutException, IOException {
-        Spawn shell = spawnThermostat("shell");
-
-        shell.expect(SHELL_PROMPT);
-        shell.send("disconnect\n");
-        shell.expect(SHELL_PROMPT);
-        shell.send("exit\n");
-        shell.expectClose();
-
-        assertCommandIsFound(shell.getCurrentStandardOutContents(), shell.getCurrentStandardErrContents());
-        assertNoExceptions(shell.getCurrentStandardOutContents(), shell.getCurrentStandardErrContents());
-    }
-
-    @Ignore //FIXME when keyring/preferences improvements have been made, un-Ignore
-    @Test
-    public void testConnectAndDisconnectInShell() throws IOException, TimeoutException, ExpectJException {
-        Spawn shell = spawnThermostat(true, "shell");
-
-        shell.expect(SHELL_PROMPT);
-        shell.send("connect -d mongodb://127.0.0.1:27518\n");
-        handleAuthPrompt(shell, "mongodb://127.0.0.1:27518", "", "");
-        shell.expect(SHELL_PROMPT);
-        shell.send("disconnect\n");
-        shell.send("exit\n");
-        shell.expectClose();
-
-        assertNoExceptions(shell.getCurrentStandardOutContents(), shell.getCurrentStandardErrContents());
-    }
-
-    // TODO add a test to make sure connect/disconnect is not visible outside the shell
-}
-
--- a/integration-tests/src/test/java/com/redhat/thermostat/itest/StorageTest.java	Wed Oct 16 13:19:53 2013 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,84 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.itest;
-
-import org.junit.Test;
-
-import expectj.Spawn;
-
-public class StorageTest extends IntegrationTest {
-
-    @Test
-    public void startAndStopStorage() throws Exception {
-        Spawn storage;
-
-        storage = startStorage();
-
-        storage = spawnThermostat("storage", "--status");
-        storage.expect("Storage is running");
-        storage.expectClose();
-        
-        assertNoExceptions(storage.getCurrentStandardOutContents(), storage.getCurrentStandardErrContents());
-        
-        storage = stopStorage();
-        
-        storage = spawnThermostat("storage", "--status");
-        storage.expect("Storage is not running");
-        storage.expectClose();
-
-        assertNoExceptions(storage.getCurrentStandardOutContents(), storage.getCurrentStandardErrContents());
-    }
-
-    @Test
-    public void testServiceStartAndKilling() throws Exception {
-
-        SpawnResult spawnResult = spawnThermostatAndGetProcess("service");
-        Spawn service = spawnResult.spawn;
-
-        try {
-            service.expectErr("agent started");
-        } finally {
-            killRecursively(spawnResult.process);
-        }
-
-        service.stop();
-        service.expectClose();
-
-    }
-
-}
-
--- a/integration-tests/src/test/java/com/redhat/thermostat/itest/VmCommandsTest.java	Wed Oct 16 13:19:53 2013 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.itest;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import expectj.Spawn;
-
-/** Integration tests for the various vm commands */
-public class VmCommandsTest extends IntegrationTest {
-
-    @BeforeClass
-    public static void setUpOnce() throws Exception {
-        startStorage();
-
-        // TODO insert actual data into the database and test that
-    }
-
-    @AfterClass
-    public static void tearDownOnce() throws Exception {
-        stopStorage();
-    }
-
-    @Ignore //FIXME when keyring/preferences improvements have been made, un-Ignore
-    @Test
-    public void testListVms() throws Exception {
-        Spawn vmList = commandAgainstMongo("list-vms");
-        handleAuthPrompt(vmList, "mongodb://127.0.0.1:27518", "", "");
-        vmList.expectClose();
-        assertOutputEndsWith(vmList.getCurrentStandardOutContents(), "HOST_ID HOST VM_ID STATUS VM_NAME\n\n");
-    }
-
-    @Test
-    public void testVmStat() throws Exception {
-        Spawn vmStat = commandAgainstMongo("vm-stat");
-        // TODO include required options to test meaningfully
-        //handleAuthPrompt(vmStat, "mongodb://127.0.0.1:27518", "", "");
-        vmStat.expectClose();
-
-        System.out.println(vmStat.getCurrentStandardOutContents());
-        assertCommandIsFound(vmStat.getCurrentStandardOutContents(), vmStat.getCurrentStandardErrContents());
-        assertNoExceptions(vmStat.getCurrentStandardOutContents(), vmStat.getCurrentStandardErrContents());
-    }
-
-    @Test
-    public void testVmInfo() throws Exception {
-        Spawn vmInfo = commandAgainstMongo("vm-info");
-        // TODO include required options to test meaningfully
-        // handleAuthPrompt(vmInfo, "mongodb://127.0.0.1:27518", "", "");
-        vmInfo.expectClose();
-
-        assertNoExceptions(vmInfo.getCurrentStandardOutContents(), vmInfo.getCurrentStandardErrContents());
-    }
-
-    @Ignore //FIXME when keyring/preferences improvements have been made, un-Ignore
-    @Test
-    public void testHeapCommands() throws Exception {
-        String[] commands = new String[] {
-                "dump-heap",
-                "list-heap-dumps",
-                "save-heap-dump-to-file",
-                "show-heap-histogram",
-                "find-objects",
-                "object-info",
-                "find-root"
-        };
-
-        for (String command : commands) {
-            Spawn heapCommand = commandAgainstMongo(command);
-            // TODO include required options to test each command meaningfully
-            if (command.equals("list-heap-dumps")) {
-                // No missing options, times out waiting for user/pass input without the following:
-                handleAuthPrompt(heapCommand, "mongodb://127.0.0.1:27518", "", "");
-            }
-            heapCommand.expectClose();
-
-            assertCommandIsFound(
-                    heapCommand.getCurrentStandardOutContents(),
-                    heapCommand.getCurrentStandardErrContents());
-            assertNoExceptions(
-                    heapCommand.getCurrentStandardOutContents(),
-                    heapCommand.getCurrentStandardErrContents());
-        }
-    }
-
-    @Test
-    public void testNormalCommandAndPluginInShell() throws Exception {
-        Spawn shell = spawnThermostat("shell");
-
-        shell.expect(SHELL_PROMPT);
-        shell.send("list-vms\n");
-
-        shell.expect(SHELL_PROMPT);
-
-        shell.send("dump-heap\n");
-
-        shell.expect(SHELL_PROMPT);
-
-        shell.send("exit\n");
-        shell.expectClose();
-
-        assertCommandIsFound(shell);
-        assertNoExceptions(shell);
-    }
-
-    private static Spawn commandAgainstMongo(String... args) throws IOException {
-        if (args == null || args.length == 0) {
-            throw new IllegalArgumentException("args must be an array with something");
-        }
-        List<String> completeArgs = new ArrayList<>();
-        completeArgs.addAll(Arrays.asList(args));
-        completeArgs.add("-d");
-        completeArgs.add("mongodb://127.0.0.1:27518");
-        return spawnThermostat(true, completeArgs.toArray(new String[0]));
-    }
-
-}
-
--- a/integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTest.java	Wed Oct 16 13:19:53 2013 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1092 +0,0 @@
-/*
- * Copyright 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.itest;
-
-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.junit.Assert.fail;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Properties;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.util.component.LifeCycle;
-import org.eclipse.jetty.util.component.LifeCycle.Listener;
-import org.eclipse.jetty.webapp.WebAppContext;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-
-import com.redhat.thermostat.common.ApplicationInfo;
-import com.redhat.thermostat.host.cpu.common.CpuStatDAO;
-import com.redhat.thermostat.host.cpu.common.model.CpuStat;
-import com.redhat.thermostat.storage.config.ConnectionConfiguration;
-import com.redhat.thermostat.storage.config.StartupConfiguration;
-import com.redhat.thermostat.storage.core.Add;
-import com.redhat.thermostat.storage.core.BackingStorage;
-import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.CategoryAdapter;
-import com.redhat.thermostat.storage.core.Connection.ConnectionListener;
-import com.redhat.thermostat.storage.core.Connection.ConnectionStatus;
-import com.redhat.thermostat.storage.core.Cursor;
-import com.redhat.thermostat.storage.core.DescriptorParsingException;
-import com.redhat.thermostat.storage.core.IllegalDescriptorException;
-import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.PreparedStatement;
-import com.redhat.thermostat.storage.core.Remove;
-import com.redhat.thermostat.storage.core.StatementDescriptor;
-import com.redhat.thermostat.storage.core.StatementExecutionException;
-import com.redhat.thermostat.storage.core.Storage;
-import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
-import com.redhat.thermostat.storage.dao.HostInfoDAO;
-import com.redhat.thermostat.storage.model.AggregateCount;
-import com.redhat.thermostat.storage.model.HostInfo;
-import com.redhat.thermostat.storage.dao.AgentInfoDAO;
-import com.redhat.thermostat.storage.model.AgentInformation;
-import com.redhat.thermostat.storage.mongodb.internal.MongoStorage;
-import com.redhat.thermostat.storage.query.Expression;
-import com.redhat.thermostat.storage.query.ExpressionFactory;
-import com.redhat.thermostat.test.FreePortFinder;
-import com.redhat.thermostat.test.FreePortFinder.TryPort;
-import com.redhat.thermostat.vm.classstat.common.VmClassStatDAO;
-import com.redhat.thermostat.vm.classstat.common.model.VmClassStat;
-import com.redhat.thermostat.vm.cpu.common.VmCpuStatDAO;
-import com.redhat.thermostat.vm.cpu.common.model.VmCpuStat;
-import com.redhat.thermostat.web.client.internal.WebStorage;
-import com.redhat.thermostat.web.server.auth.Roles;
-
-/**
- * This test class starts up a mongod instance and a web storage instance
- * (in jetty container) in front of that.  Tests should make their own
- * connection to the web storage, probably by making use of one of the
- * getAndConnectStorage() method variants.
- * 
- * Because the storage instance is shared among all of the tests, it is
- * necessary to take precautions to avoid introducing data dependencies
- * between tests.  Such precautions could include: using a different
- * category (ie mongod collection) than any other existing test; setting
- * a unique agent-id for all data written and then deleting the data
- * at the end of the test; <insert other clever idea here>.
- * 
- * Please don't introduce any more sporadic test failures to this
- * integration test!!!
- */
-public class WebAppTest extends IntegrationTest {
-
-    /*
-     * Registry of descriptors this test needs to allow in order to avoid
-     * illegal statement descriptor exceptions being thrown. See also:
-     * WebAppTestStatementDescriptorRegistration
-     */
-    public static final Set<String> TRUSTED_DESCRIPTORS;
-    /*
-     * Map which maps a string descriptor to DescriptorMetadata.
-     * See also: WebAppTestStatementDescriptorRegistration
-     * 
-     */
-    public static final Map<String, DescriptorMetadata> METADATA_MAPPING;
-    // descriptive name -> descriptor mapping
-    private static final Map<String, String> DESCRIPTOR_MAP;
-    
-    private static final String KEY_AUTHORIZED_QUERY = "authorizedQuery";
-    private static final String KEY_AUTHORIZED_QUERY_EQUAL_TO = "authorizedQueryEqualTo";
-    private static final String KEY_AUTHORIZED_QUERY_NOT_EQUAL_TO = "authorizedQueryNotEqualTo";
-    private static final String KEY_AUTHORIZED_QUERY_GREATER_THAN = "authorizedQueryGreaterThan";
-    private static final String KEY_AUTHORIZED_QUERY_GREATER_THAN_OR_EQUAL_TO = "authorizedQueryGreaterThanOrEqualTo";
-    private static final String KEY_AUTHORIZED_QUERY_LESS_THAN = "authorizedQueryLessThan";
-    private static final String KEY_AUTHORIZED_QUERY_LESS_THAN_OR_EQUAL_TO = "authorizedQueryLessThanOrEqualTo";
-    private static final String KEY_AUTHORIZED_QUERY_NOT = "authorizedQueryNot";
-    private static final String KEY_AUTHORIZED_QUERY_AND = "authorizedQueryAnd";
-    private static final String KEY_AUTHORIZED_QUERY_OR = "authorizedQueryOr";
-    private static final String KEY_STORAGE_PURGE = "storagePurge";
-    private static final String KEY_AUTHORIZED_FILTERED_QUERY = "authorizedFilteredQuerySubset";
-    
-    static {
-        Map<String, String> descMap = new HashMap<>();
-        descMap.put(KEY_AUTHORIZED_FILTERED_QUERY, "QUERY agent-config");
-        descMap.put(KEY_AUTHORIZED_QUERY, "QUERY cpu-stats SORT ?s ASC");
-        descMap.put(KEY_AUTHORIZED_QUERY_EQUAL_TO, "QUERY cpu-stats WHERE 'timeStamp' = ?l SORT 'timeStamp' ASC");
-        descMap.put(KEY_AUTHORIZED_QUERY_NOT_EQUAL_TO, "QUERY cpu-stats WHERE 'timeStamp' != ?l SORT 'timeStamp' ASC");
-        descMap.put(KEY_AUTHORIZED_QUERY_GREATER_THAN, "QUERY cpu-stats WHERE 'timeStamp' > ?l SORT 'timeStamp' ASC");
-        descMap.put(KEY_AUTHORIZED_QUERY_GREATER_THAN_OR_EQUAL_TO, "QUERY cpu-stats WHERE 'timeStamp' >= ?l SORT 'timeStamp' ASC");
-        descMap.put(KEY_AUTHORIZED_QUERY_LESS_THAN, "QUERY cpu-stats WHERE 'timeStamp' < ?l SORT 'timeStamp' ASC");
-        descMap.put(KEY_AUTHORIZED_QUERY_LESS_THAN_OR_EQUAL_TO, "QUERY cpu-stats WHERE 'timeStamp' <= ?l SORT 'timeStamp' ASC");
-        descMap.put(KEY_AUTHORIZED_QUERY_NOT, "QUERY cpu-stats WHERE NOT 'timeStamp' > ?l SORT 'timeStamp' ASC");
-        descMap.put(KEY_AUTHORIZED_QUERY_AND, "QUERY cpu-stats WHERE 'timeStamp' > 0 AND 'timeStamp' < ?l SORT 'timeStamp' ASC");
-        descMap.put(KEY_AUTHORIZED_QUERY_OR, "QUERY cpu-stats WHERE 'timeStamp' > ?l OR 'timeStamp' < ?l SORT 'timeStamp' ASC");
-        descMap.put(KEY_STORAGE_PURGE, "QUERY vm-cpu-stats");
-        Set<String> trustedDescriptors = new HashSet<>();
-        Map<String, DescriptorMetadata> metadata = new HashMap<>();
-        DescriptorMetadata descMetadata = new DescriptorMetadata();
-        for (String val: descMap.values()) {
-            trustedDescriptors.add(val);
-            metadata.put(val, descMetadata);
-        }
-        TRUSTED_DESCRIPTORS = trustedDescriptors;
-        DESCRIPTOR_MAP = descMap;
-        METADATA_MAPPING = metadata;
-    }
-    
-    
-    private static class CountdownConnectionListener implements ConnectionListener {
-
-        private final ConnectionStatus target;
-        private final CountDownLatch latch;
-        private final AtomicBoolean indicator;
-
-        private CountdownConnectionListener(ConnectionStatus target, CountDownLatch latch, AtomicBoolean indicator) {
-            this.target = target;
-            this.latch = latch;
-            this.indicator = indicator;
-        }
-
-        @Override
-        public void changed(ConnectionStatus newStatus) {
-            indicator.set(true);
-            assertEquals(target, newStatus);
-            latch.countDown();
-        }
-    }
-
-    private static class WebAppContextListener implements Listener {
-        private Throwable cause;
-        private boolean failed = false;
-        private final CountDownLatch contextStartedLatch;
-        private WebAppContextListener(CountDownLatch latch) {
-            this.contextStartedLatch = latch;
-        }
-
-        @Override
-        public void lifeCycleStarting(LifeCycle event) {
-            // nothing
-        }
-
-        @Override
-        public void lifeCycleStarted(LifeCycle event) {
-            contextStartedLatch.countDown();
-        }
-
-        @Override
-        public void lifeCycleFailure(LifeCycle event, Throwable cause) {
-            this.failed = true;
-            this.cause = cause;
-            contextStartedLatch.countDown();
-        }
-
-        @Override
-        public void lifeCycleStopping(LifeCycle event) {
-            // nothing
-        }
-
-        @Override
-        public void lifeCycleStopped(LifeCycle event) {
-            // nothing
-        }
-    }
-
-    private static final String TEST_USER = "testuser";
-    private static final String TEST_PASSWORD = "testpassword";
-    private static final String PREP_USER = "prepuser";
-    private static final String PREP_PASSWORD = "preppassword";
-    private static final double EQUALS_DELTA = 0.00000000000001;
-    private static final String THERMOSTAT_USERS_FILE = getConfigurationDir() + "/thermostat-users.properties";
-    private static final String THERMOSTAT_ROLES_FILE = getConfigurationDir() + "/thermostat-roles.properties";
-    private static final String VM_ID1 = "vmId1";
-    private static final String VM_ID2 = "vmId2";
-    private static final String VM_ID3 = "vmId3";
-
-    private static Server server;
-    private static int port;
-    private static Path backupUsers;
-    private static Path backupRoles;
-
-    @BeforeClass
-    public static void setUpOnce() throws Exception {
-        startStorage();
-
-        backupUsers = Files.createTempFile("itest-backup-thermostat-users", "");
-        backupRoles = Files.createTempFile("itest-backup-thermostat-roles", "");
-        backupRoles.toFile().deleteOnExit();
-        backupUsers.toFile().deleteOnExit();
-        Files.copy(new File(THERMOSTAT_USERS_FILE).toPath(), backupUsers, StandardCopyOption.REPLACE_EXISTING);
-        Files.copy(new File(THERMOSTAT_ROLES_FILE).toPath(), backupRoles, StandardCopyOption.REPLACE_EXISTING);
-
-
-        // start the server, deploy the war
-        port = FreePortFinder.findFreePort(new TryPort() {
-            
-            @Override
-            public void tryPort(int port) throws Exception {
-                startServer(port);
-            }
-        });
-
-        addCpuData(4);
-    }
-
-    @AfterClass
-    public static void tearDownOnce() throws Exception {
-        deleteCpuData();
-
-        server.stop();
-        server.join();
-        
-        stopStorage();
-
-        Files.copy(backupUsers, new File(THERMOSTAT_USERS_FILE).toPath(), StandardCopyOption.REPLACE_EXISTING);
-        Files.copy(backupRoles, new File(THERMOSTAT_ROLES_FILE).toPath(), StandardCopyOption.REPLACE_EXISTING);
-    }
-    
-    /*
-     * Queries tests use write operations to put things into storage. For them
-     * we don't want to go through the hassles of using prepared writes. Instead
-     * use mongo-storage directly (which is a BackingStorage). 
-     */
-    private static BackingStorage getAndConnectBackingStorage() {
-        String url = "mongodb://127.0.0.1:27518";
-        StartupConfiguration config = new ConnectionConfiguration(url, "", "");
-        BackingStorage storage = new MongoStorage(config);
-        storage.getConnection().connect();
-        return storage;
-    }
-
-    /*
-     * Using the given username and password, set up a user for JAAS in the web app,
-     * with the given roles, and make a storage connection to the web app (returning
-     * the Storage object).
-     */
-    private static Storage getAndConnectStorage(String username, String password,
-                                                String[] roleNames) throws IOException {
-        return getAndConnectStorage(username, password, roleNames, null);
-    }
-
-    /*
-     * Using the given username and password, set up a user for JAAS in the web app,
-     * with the given roles, and make a connection to the web app (returning the
-     * Storage object).  Before initiating the connection, add the ConnectionListener
-     * to Storage.
-     */
-    private static Storage getAndConnectStorage(String username, String password,
-                                                String[] roleNames,
-                                                ConnectionListener listener) throws IOException {
-        setupJAASForUser(roleNames, username, password);
-        String url = "http://localhost:" + port + "/thermostat/storage";
-        StartupConfiguration config = new ConnectionConfiguration(url, username, password);
-        Storage storage = new WebStorage(config);
-        if (listener != null) {
-            storage.getConnection().addListener(listener);
-        }
-        storage.getConnection().connect();
-        return storage;
-    }
-
-    private static void setupJAASForUser(String[] roleNames, String user,
-            String password) throws IOException {
-        Properties userProps = new Properties();
-        userProps.put(user, password);
-        Properties roleProps = new Properties();
-        StringBuffer roles = new StringBuffer();
-        for (int i = 0; i < roleNames.length - 1; i++) {
-            roles.append(roleNames[i] + ", ");
-        }
-        roles.append(roleNames[roleNames.length - 1]);
-        roleProps.put(user, roles.toString());
-        writeThermostatUsersRolesFile(userProps, roleProps);
-    }
-    
-    private static void writeThermostatUsersRolesFile(Properties usersContent, Properties rolesContent) throws IOException {
-        File thermostatUsers = new File(THERMOSTAT_USERS_FILE);
-        File thermostatRoles = new File(THERMOSTAT_ROLES_FILE);
-        try (FileOutputStream usersStream = new FileOutputStream(thermostatUsers)) {
-            usersContent.store(usersStream, "integration-test users");
-        }
-        try (FileOutputStream rolesStream = new FileOutputStream(thermostatRoles)) {
-            rolesContent.store(rolesStream, "integration-test roles");
-        }
-    }
-
-    private static void startServer(int port) throws Exception {
-        final CountDownLatch contextStartedLatch = new CountDownLatch(1);
-        server = new Server(port);
-        ApplicationInfo appInfo = new ApplicationInfo();
-        String version = appInfo.getMavenVersion();
-        String warfile = "target/libs/thermostat-web-war-" + version + ".war";
-        WebAppContext ctx = new WebAppContext(warfile, "/thermostat");
-        
-        // We need to set this to true in order for WebStorageEndPoint to pick
-        // up the descriptor registrations from WebAppTestStatementDescriptorRegistration
-        // which would result in 
-        // "java.util.ServiceConfigurationError: com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration: Provider com.redhat.thermostat.itest.WebAppTestStatementDescriptorRegistration not a subtype"
-        // errors.
-        ctx.setParentLoaderPriority(true);
-        
-        WebAppContextListener listener = new WebAppContextListener(contextStartedLatch);
-        ctx.addLifeCycleListener(listener);
-        /* The web archive has a jetty-web.xml config file which sets up the
-         * JAAS config. If done in code, this would look like this:
-         *
-         * JAASLoginService loginS = new JAASLoginService();
-         * loginS.setLoginModuleName("ThermostatJAASLogin");
-         * loginS.setName("Thermostat Realm");
-         * loginS.setRoleClassNames(new String[] {
-         * WrappedRolePrincipal.class.getName(),
-         *       RolePrincipal.class.getName(),
-         *       UserPrincipal.class.getName()
-         * });
-         * ctx.getSecurityHandler().setLoginService(loginS);
-         * 
-         */
-        server.setHandler(ctx);
-        server.start();
-        // wait for context to start
-        contextStartedLatch.await();
-        if (listener.failed) {
-            throw new IllegalStateException(listener.cause);
-        }
-    }
-
-    private static void addCpuData(int numberOfItems) throws IOException {
-        BackingStorage storage = getAndConnectBackingStorage();
-        storage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        for (int i = 0; i < numberOfItems; i++) {
-            CpuStat pojo = new CpuStat("test-agent-id", i, new double[] {i, i*2});
-            Add<CpuStat> add = storage.createAdd(CpuStatDAO.cpuStatCategory);
-            add.set(Key.AGENT_ID.getName(), pojo.getAgentId());
-            add.set(CpuStatDAO.cpuLoadKey.getName(), pojo.getPerProcessorUsage());
-            add.set(Key.TIMESTAMP.getName(), pojo.getTimeStamp());
-            add.apply();
-        }
-
-        storage.getConnection().disconnect();
-    }
-    
-    private static void addHostInfoData(int numberOfItems) throws IOException {
-        BackingStorage storage = getAndConnectBackingStorage();
-        storage.registerCategory(HostInfoDAO.hostInfoCategory);
-
-        for (int i = 0; i < numberOfItems; i++) {
-            HostInfo hostInfo = new HostInfo("test-host-agent-id", "foo " + i, "linux " + i, "kernel", "t8", i, i * 1000);
-            Add<HostInfo> add = storage.createAdd(HostInfoDAO.hostInfoCategory);
-            add.set(Key.AGENT_ID.getName(), hostInfo.getAgentId());
-            add.set(HostInfoDAO.hostNameKey.getName(), hostInfo.getHostname());
-            add.set(HostInfoDAO.cpuCountKey.getName(), hostInfo.getCpuCount());
-            add.set(HostInfoDAO.cpuModelKey.getName(), hostInfo.getCpuModel());
-            add.set(HostInfoDAO.hostMemoryTotalKey.getName(), hostInfo.getTotalMemory());
-            add.set(HostInfoDAO.osKernelKey.getName(), hostInfo.getOsKernel());
-            add.set(HostInfoDAO.osNameKey.getName(), hostInfo.getOsName());
-            add.apply();
-        }
-
-        storage.getConnection().disconnect();
-    }
-    
-    private static void addAgentConfigData(List<AgentInformation> items) throws IOException {
-        BackingStorage storage = getAndConnectBackingStorage();
-        storage.registerCategory(AgentInfoDAO.CATEGORY);
-
-        for (AgentInformation info: items) {
-            Add<AgentInformation> add = storage.createAdd(AgentInfoDAO.CATEGORY);
-            add.set(Key.AGENT_ID.getName(), info.getAgentId());
-            add.set(AgentInfoDAO.ALIVE_KEY.getName(), info.isAlive());
-            add.set(AgentInfoDAO.CONFIG_LISTEN_ADDRESS.getName(), info.getConfigListenAddress());
-            add.set(AgentInfoDAO.START_TIME_KEY.getName(), info.getStartTime());
-            add.set(AgentInfoDAO.STOP_TIME_KEY.getName(), info.getStopTime());
-            add.apply();
-        }
-
-        storage.getConnection().disconnect();
-    }
-
-    private static void deleteCpuData() throws IOException {
-        doDeleteData(CpuStatDAO.cpuStatCategory, "test-agent-id");
-    }
-    
-    private static void deleteHostInfoData() throws IOException {
-        doDeleteData(HostInfoDAO.hostInfoCategory, "test-host-agent-id");
-    }
-    
-    private static void doDeleteData(Category<?> category, String agentId) throws IOException {
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.ACCESS_REALM,
-                Roles.LOGIN,
-                Roles.PURGE
-        };
-        Storage storage = getAndConnectStorage(PREP_USER, PREP_PASSWORD, roleNames);
-        storage.registerCategory(category);
-        storage.purge(agentId);
-        storage.getConnection().disconnect();
-    }
-    
-    private static void deleteAgentConfigData(List<AgentInformation> items) throws IOException {
-        BackingStorage storage = getAndConnectBackingStorage();
-        storage.registerCategory(AgentInfoDAO.CATEGORY);
-        ExpressionFactory factory = new ExpressionFactory();
-        Remove<AgentInformation> remove = storage.createRemove(AgentInfoDAO.CATEGORY);
-        Set<String> agentIds = new HashSet<>();
-        for (AgentInformation info: items) {
-            agentIds.add(info.getAgentId());
-        }
-        Expression expression = factory.in(Key.AGENT_ID, agentIds, String.class);
-        remove.where(expression);
-        remove.apply();
-
-        storage.getConnection().disconnect();
-    }
-
-    private void executeAndVerifyQuery(PreparedStatement<CpuStat> query, List<Long> expectedTimestamps) throws StatementExecutionException {
-        Cursor<CpuStat> cursor = query.executeQuery();
-
-        for (Long time : expectedTimestamps) {
-            assertTrue(cursor.hasNext());
-            CpuStat pojo = cursor.next();
-            assertEquals("test-agent-id", pojo.getAgentId());
-            assertEquals(time.longValue(), pojo.getTimeStamp());
-            double[] data = pojo.getPerProcessorUsage();
-            assertEquals(time, data[0], EQUALS_DELTA);
-            assertEquals(time*2, data[1], EQUALS_DELTA);
-        }
-        assertFalse(cursor.hasNext());
-    }
-
-    @Test
-    public void authorizedPreparedAdd() throws Exception {
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.WRITE,
-                Roles.LOGIN,
-                Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT,
-        };
-        
-        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        webStorage.registerCategory(VmClassStatDAO.vmClassStatsCategory);
-        
-        // This is the same descriptor as VmClassStatDAOImpl uses. It also
-        // gets registered automatically for that reason, no need to do it
-        // manually for this test.
-        String strDesc = "ADD vm-class-stats SET 'agentId' = ?s , " +
-                                "'vmId' = ?s , " +
-                                "'timeStamp' = ?l , " + 
-                                "'loadedClasses' = ?l";
-        StatementDescriptor<VmClassStat> desc = new StatementDescriptor<>(VmClassStatDAO.vmClassStatsCategory, strDesc);
-        VmClassStat pojo = new VmClassStat();
-        pojo.setAgentId("fluff");
-        pojo.setLoadedClasses(12345);
-        pojo.setTimeStamp(42);
-        pojo.setVmId(VM_ID1);
-        PreparedStatement<VmClassStat> add;
-        add = webStorage.prepareStatement(desc);
-        addPreparedVmClassStat(pojo, add);
-        
-        // Add another couple of entries
-        pojo = new VmClassStat();
-        pojo.setAgentId("fluff");
-        pojo.setLoadedClasses(67890);
-        pojo.setTimeStamp(42);
-        pojo.setVmId(VM_ID2);
-        addPreparedVmClassStat(pojo, add);
-        
-        pojo = new VmClassStat();
-        pojo.setAgentId("fluff");
-        pojo.setLoadedClasses(34567);
-        pojo.setTimeStamp(42);
-        pojo.setVmId(VM_ID3);
-        addPreparedVmClassStat(pojo, add);
-        
-        webStorage.getConnection().disconnect();
-    }
-    
-    private void addPreparedVmClassStat(VmClassStat pojo,
-            PreparedStatement<VmClassStat> add)
-            throws StatementExecutionException {
-        add.setString(0, pojo.getAgentId());
-        add.setString(1, pojo.getVmId());
-        add.setLong(2, pojo.getTimeStamp());
-        add.setLong(3, pojo.getLoadedClasses());
-        add.execute();
-    }
-    
-    /*
-     * Tests whether a query only returns results which a user is allowed to see.
-     * 
-     * In particular, multiple agent-config records available in the DB, but
-     * only a subset are allowed to be seen by the user.
-     */
-    @Test
-    public void authorizedFilteredQuerySubset() throws Exception {
-        // add agent records into the DB
-        List<AgentInformation> items = Collections.emptyList();
-        try {
-            String agentIdGrantPrefix = "thermostat-agents-grant-read-agentId-";
-            String agent1Id = "agent1";
-            String agent2Id = "agent2";
-            items = getAgentInformationItemsIncluding(new String[] { agent1Id, agent2Id });
-            // assert pre-condition. records in db need to be more than expected
-            // result set size.
-            assertTrue(items.size() > 2);
-            addAgentConfigData(items);
-            String[] roleNames = new String[] {
-                    Roles.REGISTER_CATEGORY,
-                    Roles.READ,
-                    Roles.LOGIN,
-                    Roles.ACCESS_REALM,
-                    Roles.PREPARE_STATEMENT,
-                    // Grant read access only for "agent1" and "agent2" agend IDs
-                    agentIdGrantPrefix + agent1Id,
-                    agentIdGrantPrefix + agent2Id
-            };
-            
-            Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-            webStorage.registerCategory(AgentInfoDAO.CATEGORY);
-            
-            String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_FILTERED_QUERY);
-            StatementDescriptor<AgentInformation> queryDesc = new StatementDescriptor<>(AgentInfoDAO.CATEGORY, strDesc);
-            PreparedStatement<AgentInformation> query = webStorage.prepareStatement(queryDesc);
-            Cursor<AgentInformation> cursor = query.executeQuery();
-            assertTrue(cursor.hasNext());
-            List<AgentInformation> actual = new ArrayList<>();
-            while (cursor.hasNext()) {
-                AgentInformation info = cursor.next();
-                actual.add(info);
-            }
-            assertEquals(2, actual.size());
-            assertFalse("Returned agentIds should be different!", actual.get(0).getAgentId().equals(actual.get(1).getAgentId()));
-            for (AgentInformation info: actual) {
-                assertTrue(info.getAgentId().equals(agent1Id) || info.getAgentId().equals(agent2Id));
-            }
-        } finally {
-           deleteAgentConfigData(items); 
-        }
-    }
-    
-    
-    private List<AgentInformation> getAgentInformationItemsIncluding(
-            String[] includeItems) {
-        List<AgentInformation> infos = new ArrayList<>();
-        for (int i = 0; i < 5; i++) {
-            String agentId = UUID.randomUUID().toString() + "--" + i;
-            if (i < includeItems.length) {
-                agentId = includeItems[i];
-            }
-            AgentInformation info = new AgentInformation();
-            info.setAgentId(agentId);
-            info.setAlive((i % 2) == 0);
-            info.setConfigListenAddress("127.0.0." + i + ":88888");
-            info.setStartTime(i * 300);
-            info.setStopTime((i + 1) * 400);
-            infos.add(info);
-        }
-        return infos;
-    }
-
-    /*
-     * Tests whether no query results are returned for a user which lacks *any*
-     * granting roles for reads.
-     */
-    @Test
-    public void authorizedFilteredQueryNone() throws Exception {
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.READ, // this is just the stop-gap role.
-                Roles.LOGIN,
-                Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT,
-                // lacking read grant roles
-        };
-        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-        
-        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY);
-        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
-        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
-
-        query.setString(0, "timeStamp");
-        // Note: with read-all granted, this returns 4 records. See authorized
-        //       query test.
-        // For this test, however, it should come back empty.
-        
-        Cursor<CpuStat> cursor = query.executeQuery();
-        assertFalse(cursor.hasNext());
-        try {
-            cursor.next();
-            fail("cursor should have thrown exception!");
-        } catch (NoSuchElementException e) {
-            // pass
-        }
-
-        webStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void authorizedQuery() throws Exception {
-
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.READ,
-                Roles.LOGIN,
-                Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT,
-                Roles.GRANT_READ_ALL
-        };
-        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-        
-        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY);
-        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
-        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
-
-        query.setString(0, "timeStamp");
-        
-        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 2l, 3l));
-
-        webStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void authorizedAggregateCount() throws Exception {
-        try {
-            int count = 2;
-            // registers host info category
-            addHostInfoData(count);
-            
-            String[] roleNames = new String[] {
-                    Roles.REGISTER_CATEGORY,
-                    Roles.READ,
-                    Roles.LOGIN,
-                    Roles.ACCESS_REALM,
-                    Roles.PREPARE_STATEMENT,
-                    Roles.GRANT_READ_ALL // don't want to test filtered results
-            };
-            Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-            Category<AggregateCount> adapted = new CategoryAdapter<HostInfo, AggregateCount>(HostInfoDAO.hostInfoCategory).getAdapted(AggregateCount.class);
-            // register non-adapted + adapted category in that order. Adapted
-            // category needs to be registered, since it gets it's own mapped id
-            webStorage.registerCategory(HostInfoDAO.hostInfoCategory);
-            webStorage.registerCategory(adapted);
-            
-            // storage-core registers this descriptor. no need to do it in this
-            // test.
-            String strDesc = "QUERY-COUNT host-info";
-            StatementDescriptor<AggregateCount> queryDesc = new StatementDescriptor<>(adapted, strDesc);
-            PreparedStatement<AggregateCount> query = webStorage.prepareStatement(queryDesc);
-    
-            Cursor<AggregateCount> cursor = query.executeQuery();
-            assertTrue(cursor.hasNext());
-            AggregateCount c = cursor.next();
-            assertFalse(cursor.hasNext());
-            assertEquals(count, c.getCount());
-    
-            webStorage.getConnection().disconnect();
-        } finally {
-            deleteHostInfoData();
-        }
-    }
-    
-    @Test
-    public void authorizedQueryEqualTo() throws Exception {
-
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.READ,
-                Roles.LOGIN,
-                Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT,
-                Roles.GRANT_READ_ALL
-        };
-        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_EQUAL_TO);
-        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
-        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
-        query.setLong(0, 2l);
-        
-        executeAndVerifyQuery(query, Arrays.asList(2l));
-
-        webStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void authorizedQueryNotEqualTo() throws Exception {
-
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.READ,
-                Roles.LOGIN,
-                Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT,
-                Roles.GRANT_READ_ALL
-        };
-        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_NOT_EQUAL_TO);
-        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
-        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
-        query.setLong(0, 2l);
-        
-        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 3l));
-
-        webStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void authorizedQueryGreaterThan() throws Exception {
-
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.READ,
-                Roles.LOGIN,
-                Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT,
-                Roles.GRANT_READ_ALL
-        };
-        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_GREATER_THAN);
-        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
-        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
-        query.setLong(0, 2l);
-        
-        executeAndVerifyQuery(query, Arrays.asList(3l));
-
-        webStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void authorizedQueryGreaterThanOrEqualTo() throws Exception {
-
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.READ,
-                Roles.LOGIN,
-                Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT,
-                Roles.GRANT_READ_ALL
-        };
-        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_GREATER_THAN_OR_EQUAL_TO);
-        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
-        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
-        query.setLong(0, 2l);
-        
-        executeAndVerifyQuery(query, Arrays.asList(2l, 3l));
-
-        webStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void authorizedQueryLessThan() throws Exception {
-
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.READ,
-                Roles.LOGIN,
-                Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT,
-                Roles.GRANT_READ_ALL
-        };
-        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_LESS_THAN);
-        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
-        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
-        query.setLong(0, 2l);
-        
-        executeAndVerifyQuery(query, Arrays.asList(0l, 1l));
-
-        webStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void authorizedQueryLessThanOrEqualTo() throws Exception {
-
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.READ,
-                Roles.LOGIN,
-                Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT,
-                Roles.GRANT_READ_ALL
-        };
-        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_LESS_THAN_OR_EQUAL_TO);
-        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
-        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
-        query.setLong(0, 2l);
-
-        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 2l));
-
-        webStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void authorizedQueryNot() throws Exception {
-
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.READ,
-                Roles.LOGIN,
-                Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT,
-                Roles.GRANT_READ_ALL
-        };
-        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_NOT);
-        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
-        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
-        query.setLong(0, 2l);
-        
-        executeAndVerifyQuery(query, Arrays.asList(0l, 1l, 2l));
-
-        webStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void authorizedQueryAnd() throws Exception {
-
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.READ,
-                Roles.LOGIN,
-                Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT,
-                Roles.GRANT_READ_ALL
-        };
-        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_AND);
-        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
-        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
-        query.setLong(0, 2l);
-        
-        executeAndVerifyQuery(query, Arrays.asList(1l));
-
-        webStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void authorizedQueryOr() throws Exception {
-
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.READ,
-                Roles.LOGIN,
-                Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT,
-                Roles.GRANT_READ_ALL
-        };
-        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        String strDesc = DESCRIPTOR_MAP.get(KEY_AUTHORIZED_QUERY_OR);
-        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
-        PreparedStatement<CpuStat> query = webStorage.prepareStatement(queryDesc);
-        query.setLong(0, 2);
-        query.setLong(1, 1);
-        
-        executeAndVerifyQuery(query, Arrays.asList(0l, 3l));
-
-        webStorage.getConnection().disconnect();
-    }
-    
-    @Test
-    public void refuseUnknownQueryDescriptor() throws IOException {
-
-        String[] roleNames = new String[] {
-                Roles.REGISTER_CATEGORY,
-                Roles.READ,
-                Roles.LOGIN,
-                Roles.ACCESS_REALM,
-                Roles.PREPARE_STATEMENT
-        };
-        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        webStorage.registerCategory(CpuStatDAO.cpuStatCategory);
-
-        String strDesc = "QUERY cpu-stats WHERE 'fooBarTest' = ?s";
-        assertFalse("wanted this descriptor to be untrusted!", TRUSTED_DESCRIPTORS.contains(strDesc));
-        StatementDescriptor<CpuStat> queryDesc = new StatementDescriptor<>(CpuStatDAO.cpuStatCategory, strDesc);
-        
-        try {
-            webStorage.prepareStatement(queryDesc);
-        } catch (IllegalDescriptorException e) {
-            // pass
-            String expectedMsg = "Unknown query descriptor which endpoint of com.redhat.thermostat.web.client.internal.WebStorage refused to accept!";
-            assertEquals(expectedMsg, e.getMessage());
-        } catch (DescriptorParsingException e) {
-            // should have been able to parse the descriptor
-            fail(e.getMessage());
-        }
-        
-        webStorage.getConnection().disconnect();
-    }
-
-    @Test
-    public void authorizedLoadSave() throws Exception {
-        String[] roleNames = new String[] {
-                Roles.LOAD_FILE,
-                Roles.SAVE_FILE,
-                Roles.ACCESS_REALM,
-                Roles.LOGIN,
-                Roles.GRANT_FILES_WRITE_ALL,
-                Roles.GRANT_FILES_READ_ALL
-        };
-        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        
-        byte[] data = "Hello World".getBytes();
-        webStorage.saveFile("test", new ByteArrayInputStream(data));
-        // Note: On the server side, the file is saved into mongodb
-        // via GridFS.  The save operation returns before write is
-        // complete, and there is no callback mechanism to find out
-        // when the write is complete.  So, we try a few times to
-        // load it before considering it a failure.
-        InputStream loadStream = null;
-        int loadAttempts = 0;
-        while (loadStream == null && loadAttempts < 3) {
-            Thread.sleep(300);
-            loadStream = webStorage.loadFile("test");
-            loadAttempts++;
-        }
-        assertNotNull(loadStream);
-        StringBuilder str = new StringBuilder();
-        int i = loadStream.read();
-        while (i != -1) {
-            str.append((char) i);
-            i = loadStream.read();
-        }
-        assertEquals("Hello World", str.toString());
-
-        webStorage.getConnection().disconnect();
-    }
-
-    @Test
-    public void unauthorizedLogin() throws Exception {
-        String[] roleNames = new String[] {
-                Roles.ACCESS_REALM
-        };
-
-        CountDownLatch statusLatch = new CountDownLatch(1);
-        AtomicBoolean listenerTriggered = new AtomicBoolean(false);
-        ConnectionListener listener = new CountdownConnectionListener(ConnectionStatus.FAILED_TO_CONNECT, statusLatch, listenerTriggered);
-        @SuppressWarnings("unused")
-        Storage storage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames, listener);
-        statusLatch.await();
-        assertTrue(listenerTriggered.get());
-    }
-
-    @Test
-    public void storagePurge() throws Exception {
-        // Add some data to purge (uses backing storage)
-        UUID uuid = new UUID(42, 24);
-        long timeStamp = 5;
-        double cpuLoad = 0.15;
-        VmCpuStat pojo = new VmCpuStat(uuid.toString(), timeStamp, VM_ID1, cpuLoad);
-        addVmCpuStat(pojo);
-
-        String[] roleNames = new String[] {
-                Roles.ACCESS_REALM,
-                Roles.LOGIN,
-                Roles.PURGE,
-                Roles.PREPARE_STATEMENT,
-                Roles.READ,
-                Roles.GRANT_READ_ALL,
-                Roles.REGISTER_CATEGORY
-        };
-        
-        Storage webStorage = getAndConnectStorage(TEST_USER, TEST_PASSWORD, roleNames);
-        webStorage.registerCategory(VmCpuStatDAO.vmCpuStatCategory);
-        
-        String strDesc = DESCRIPTOR_MAP.get(KEY_STORAGE_PURGE);
-        StatementDescriptor<VmCpuStat> queryDesc = new StatementDescriptor<>(VmCpuStatDAO.vmCpuStatCategory, strDesc);
-        PreparedStatement<VmCpuStat> query = webStorage.prepareStatement(queryDesc);
-        Cursor<VmCpuStat> cursor = query.executeQuery();
-        assertTrue(cursor.hasNext());
-        pojo = cursor.next();
-        assertFalse(cursor.hasNext());
-
-        assertEquals(timeStamp, pojo.getTimeStamp());
-        assertEquals(VM_ID1, pojo.getVmId());
-        assertEquals(cpuLoad, pojo.getCpuLoad(), EQUALS_DELTA);
-        assertEquals(uuid.toString(), pojo.getAgentId());
-
-        webStorage.purge(uuid.toString());
-    }
-
-    private void addVmCpuStat(VmCpuStat pojo) {
-        BackingStorage storage = getAndConnectBackingStorage();
-        storage.registerCategory(VmCpuStatDAO.vmCpuStatCategory);
-        Add<VmCpuStat> add = storage.createAdd(VmCpuStatDAO.vmCpuStatCategory);
-        add.set(Key.AGENT_ID.getName(), pojo.getAgentId());
-        add.set(Key.VM_ID.getName(), pojo.getVmId());
-        add.set(Key.TIMESTAMP.getName(), pojo.getTimeStamp());
-        add.set(VmCpuStatDAO.vmCpuLoadKey.getName(), pojo.getCpuLoad());
-        add.apply();
-        storage.getConnection().disconnect();        
-    }
-}
--- a/integration-tests/src/test/java/com/redhat/thermostat/itest/WebAppTestStatementDescriptorRegistration.java	Wed Oct 16 13:19:53 2013 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/*
- * Copyright 2012, 2013 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.itest;
-
-import java.util.Set;
-
-import com.redhat.thermostat.storage.core.PreparedParameter;
-import com.redhat.thermostat.storage.core.auth.DescriptorMetadata;
-import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
-
-public class WebAppTestStatementDescriptorRegistration implements
-        StatementDescriptorRegistration {
-
-    @Override
-    public Set<String> getStatementDescriptors() {
-        return WebAppTest.TRUSTED_DESCRIPTORS;
-    }
-
-    @Override
-    public DescriptorMetadata getDescriptorMetadata(String descriptor,
-            PreparedParameter[] params) {
-        return WebAppTest.METADATA_MAPPING.get(descriptor);
-    }
-
-}
--- a/integration-tests/src/test/resources/META-INF/services/com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration	Wed Oct 16 13:19:53 2013 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-com.redhat.thermostat.itest.WebAppTestStatementDescriptorRegistration
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/integration-tests/standalone/pom.xml	Fri Apr 05 18:57:56 2013 +0200
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+
+ Copyright 2013 Red Hat, Inc.
+
+ This file is part of Thermostat.
+
+ Thermostat is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation; either version 2, or (at your
+ option) any later version.
+
+ Thermostat is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Thermostat; see the file COPYING.  If not see
+ <http://www.gnu.org/licenses />.
+
+ Linking this code with other modules is making a combined work
+ based on this code.  Thus, the terms and conditions of the GNU
+ General Public License cover the whole combination.
+
+ As a special exception, the copyright holders of this code give
+ you permission to link this code with independent modules to
+ produce an executable, regardless of the license terms of these
+ independent modules, and to copy and distribute the resulting
+ executable under terms of your choice, provided that you also
+ meet, for each linked independent module, the terms and conditions
+ of the license of that module.  An independent module is a module
+ which is not derived from or based on this code.  If you modify
+ this code, you may extend this exception to your version of the
+ library, but you are not obligated to do so.  If you do not wish
+ to do so, delete this exception statement from your version.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>com.redhat.thermostat</groupId>
+    <artifactId>thermostat-integration-tests</artifactId>
+    <version>0.16.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>thermostat-integration-tests-standalone</artifactId>
+  <packaging>jar</packaging>
+
+  <name>Thermostat Integration Tests (Standalone Package)</name>
+
+  <build>
+    <plugins>
+      <!-- jacoco:report insists to have a target/classes dir. since this
+           module only contains test classes it won't exist after a build.
+           Hence, create manually in order to unbreak the build. For some
+           reason skipping both, report AND prepare-agent goals for the
+           jacoco plugin does not work. -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <version>1.6</version>
+        <executions>
+          <execution>
+            <id>make-target-classes-dir</id>
+            <phase>prepare-package</phase>
+            <configuration>
+              <target>
+                <mkdir dir="${project.build.directory}/classes" />
+              </target>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <!-- skip unit tests. The itest-run module runs them if required -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <configuration>
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+      <!-- As a downstream packaging aid bundle a jar of stand-alone
+           integration tests -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-shade-plugin</artifactId>
+        <version>2.1</version>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>shade</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>net.sourceforge.expectj</groupId>
+      <artifactId>expectj</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-integration-tests-run</artifactId>
+      <version>${project.version}</version>
+      <type>test-jar</type>
+    </dependency>
+
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+
+  </dependencies>
+</project>
--- a/pom.xml	Wed Oct 16 13:19:53 2013 -0400
+++ b/pom.xml	Fri Apr 05 18:57:56 2013 +0200
@@ -209,6 +209,11 @@
           <artifactId>maven-assembly-plugin</artifactId>
           <version>2.3</version>
         </plugin>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-jar-plugin</artifactId>
+          <version>2.3</version>
+        </plugin>
         <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
         <plugin>
           <groupId>org.eclipse.m2e</groupId>