changeset 2638:6d871a711de2

Update VmInfoDAO to communicate with web gateway Reviewed-by: neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-May/022963.html
author Elliott Baron <ebaron@redhat.com>
date Fri, 05 May 2017 12:16:53 -0400
parents 8781afc57d5e
children 0ed4aea0d8c2
files storage/core/src/main/java/com/redhat/thermostat/storage/dao/VmInfoDAO.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/VmInfoDAOImpl.java storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/VmInfoTypeAdapter.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/VmInfoDAOTest.java storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/VmInfoTypeAdapterTest.java system-backend/src/main/java/com/redhat/thermostat/backend/system/internal/JvmStatHostListener.java
diffstat 6 files changed, 565 insertions(+), 602 deletions(-) [+]
line wrap: on
line diff
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/dao/VmInfoDAO.java	Fri May 05 12:16:15 2017 -0400
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/dao/VmInfoDAO.java	Fri May 05 12:16:53 2017 -0400
@@ -36,23 +36,18 @@
 
 package com.redhat.thermostat.storage.dao;
 
-import java.util.Collection;
-import java.util.List;
 import java.util.Set;
 
 import com.redhat.thermostat.annotations.Service;
 import com.redhat.thermostat.storage.core.AgentId;
 import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.Countable;
-import com.redhat.thermostat.storage.core.HostRef;
 import com.redhat.thermostat.storage.core.Key;
 import com.redhat.thermostat.storage.core.VmId;
-import com.redhat.thermostat.storage.core.VmRef;
 import com.redhat.thermostat.storage.model.VmInfo;
 import com.redhat.thermostat.storage.model.VmInfo.KeyValuePair;
 
 @Service
-public interface VmInfoDAO extends Countable {
+public interface VmInfoDAO {
 
     static final Key<Integer> vmPidKey = new Key<>("vmPid");
     static final Key<String> runtimeVersionKey = new Key<>("javaVersion");
@@ -79,28 +74,9 @@
             startTimeKey, stopTimeKey,
             uidKey, usernameKey);
 
-    /** @return information on all known VMs */
-    List<VmInfo> getAllVmInfos();
-
-    /** @return information on all known VMs monitored by give agent */
-    List<VmInfo> getAllVmInfosForAgent(AgentId agentId);
-
     /** @return {@code null} if no information can be found */
     VmInfo getVmInfo(VmId id);
 
-    /** @return {@code null} if no information can be found */
-    VmInfo getVmInfo(VmRef ref);
-
-    /**
-     *
-     * @param host The host to get the VM(s) for.
-     * @return A collection of the VM(s) as VmRef(s).
-     *
-     * @deprecated use {@link #getVmIds(AgentId)}
-     */
-    @Deprecated
-    Collection<VmRef> getVMs(HostRef host);
-
     /**
      *
      * @param agentId The id of host to get the VM(s) for.
@@ -110,6 +86,6 @@
 
     void putVmInfo(VmInfo info);
 
-    void putVmStoppedTime(String vmId, long since);
+    void putVmStoppedTime(String agentId, String vmId, long since);
 }
 
--- a/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/VmInfoDAOImpl.java	Fri May 05 12:16:15 2017 -0400
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/VmInfoDAOImpl.java	Fri May 05 12:16:53 2017 -0400
@@ -36,238 +36,214 @@
 
 package com.redhat.thermostat.storage.internal.dao;
 
-import java.util.ArrayList;
-import java.util.Collection;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Level;
 import java.util.logging.Logger;
 
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.HttpContentResponse;
+import org.eclipse.jetty.client.HttpRequest;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.storage.core.AgentId;
-import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.CategoryAdapter;
-import com.redhat.thermostat.storage.core.HostRef;
-import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.PreparedStatement;
-import com.redhat.thermostat.storage.core.Storage;
 import com.redhat.thermostat.storage.core.VmId;
-import com.redhat.thermostat.storage.core.VmRef;
-import com.redhat.thermostat.storage.dao.AbstractDaoQuery;
-import com.redhat.thermostat.storage.dao.AbstractDaoStatement;
-import com.redhat.thermostat.storage.dao.BaseCountable;
-import com.redhat.thermostat.storage.dao.SimpleDaoQuery;
 import com.redhat.thermostat.storage.dao.VmInfoDAO;
-import com.redhat.thermostat.storage.model.AggregateCount;
+import com.redhat.thermostat.storage.internal.dao.VmInfoTypeAdapter.VmInfoUpdateTypeAdapter;
 import com.redhat.thermostat.storage.model.VmInfo;
 
-public class VmInfoDAOImpl extends BaseCountable implements VmInfoDAO {
+public class VmInfoDAOImpl implements VmInfoDAO {
     
     private final Logger logger = LoggingUtils.getLogger(VmInfoDAOImpl.class);
-    static final String QUERY_VM_INFO = "QUERY "
-            + vmInfoCategory.getName() + " WHERE '" 
-            + Key.AGENT_ID.getName() + "' = ?s AND '"
-            + Key.VM_ID.getName() + "' = ?s LIMIT 1";
-    static final String QUERY_ALL_VMS_FOR_AGENT = "QUERY "
-            + vmInfoCategory.getName() + " WHERE '" 
-            + Key.AGENT_ID.getName() + "' = ?s";
-
-    static final String QUERY_VM_FROM_ID = "QUERY "
-            + vmInfoCategory.getName() + " WHERE '"
-            + Key.VM_ID.getName() + "' = ?s";
+    
+    private static final String GATEWAY_URL = "http://localhost:26000/api/v100"; // TODO configurable
+    private static final String GATEWAY_PATH = "/vm-info/systems/*/agents/";
+    private static final String GATEWAY_PATH_JVM_SUFFIX = "/jvms/";
+    private static final String CONTENT_TYPE = "application/json";
+    
+    private final HttpHelper httpHelper;
+    private final JsonHelper jsonHelper;
 
-    static final String QUERY_ALL_VMS = "QUERY " + vmInfoCategory.getName();
-    static final String AGGREGATE_COUNT_ALL_VMS = "QUERY-COUNT " + vmInfoCategory.getName();
-    // ADD vm-info SET 'agentId' = ?s , \
-    //                 'vmId' = ?s , \
-    //                 'vmPid' = ?i , \
-    //                 'startTimeStamp' = ?l , \
-    //                 'stopTimeStamp' = ?l , \
-    //                 'javaVersion' = ?s , \
-    //                 'javaHome' = ?s , \
-    //                 'mainClass' = ?s , \
-    //                 'javaCommandLine' = ?s , \
-    //                 'vmName' = ?s , \
-    //                 'vmArguments' = ?s , \
-    //                 'vmInfo' = ?s , \
-    //                 'vmVersion' = ?s , \
-    //                 'propertiesAsArray' = ?p[ , \
-    //                 'environmentAsArray' = ?p[ , \
-    //                 'loadedNativeLibraries' = ?s[ , \
-    //                 'uid' = ?l , \
-    //                 'username' = ?s
-    static final String DESC_ADD_VM_INFO = "ADD " + vmInfoCategory.getName() + " SET " +
-                        "'" + Key.AGENT_ID.getName() + "' = ?s , " +
-                        "'" + Key.VM_ID.getName() + "' = ?s , " +
-                        "'" + vmPidKey.getName() + "' = ?i , " +
-                        "'" + startTimeKey.getName() + "' = ?l , " +
-                        "'" + stopTimeKey.getName() + "' = ?l , " +
-                        "'" + runtimeVersionKey.getName() + "' = ?s , " +
-                        "'" + javaHomeKey.getName() + "' = ?s , " +
-                        "'" + mainClassKey.getName() + "' = ?s , " +
-                        "'" + commandLineKey.getName() + "' = ?s , " +
-                        "'" + vmNameKey.getName() + "' = ?s , " +
-                        "'" + vmArgumentsKey.getName() + "' = ?s , " +
-                        "'" + vmInfoKey.getName() + "' = ?s , " +
-                        "'" + vmVersionKey.getName() + "' = ?s , " +
-                        "'" + propertiesKey.getName() + "' = ?p[ , " +
-                        "'" + environmentKey.getName() + "' = ?p[ , " +
-                        "'" + librariesKey.getName() + "' = ?s[ , " +
-                        "'" + uidKey.getName() + "' = ?l , " +
-                        "'" + usernameKey.getName() + "' = ?s";
-    // UPDATE vm-info SET 'stopTimeStamp' = ?l WHERE 'vmId' = ?s
-    static final String DESC_UPDATE_VM_STOP_TIME = "UPDATE " + vmInfoCategory.getName() +
-            " SET '" + VmInfoDAO.stopTimeKey.getName() + "' = ?l" +
-            " WHERE '" + Key.VM_ID.getName() + "' = ?s";
-    
-    private final Storage storage;
-    private final Category<AggregateCount> aggregateCategory;
-
-    public VmInfoDAOImpl(Storage storage) {
-        this.storage = storage;
-        // Adapt category to the aggregate form
-        CategoryAdapter<VmInfo, AggregateCount> adapter = new CategoryAdapter<>(vmInfoCategory);
-        this.aggregateCategory = adapter.getAdapted(AggregateCount.class);
-        storage.registerCategory(vmInfoCategory);
-        storage.registerCategory(aggregateCategory);
+    public VmInfoDAOImpl() throws Exception {
+        this(new HttpHelper(new HttpClient()), new JsonHelper(new VmInfoTypeAdapter(), new VmInfoUpdateTypeAdapter()));
     }
 
-    @Override
-    public List<VmInfo> getAllVmInfos() {
-        return executeQuery(new SimpleDaoQuery<>(storage, vmInfoCategory, QUERY_ALL_VMS)).asList();
+    VmInfoDAOImpl(HttpHelper httpHelper, JsonHelper jsonHelper) throws Exception {
+        this.jsonHelper = jsonHelper;
+        this.httpHelper = httpHelper;
+        
+        this.httpHelper.startClient();
     }
 
     @Override
     public VmInfo getVmInfo(final VmId id) {
-        return executeQuery(
-                new AbstractDaoQuery<VmInfo>(storage, vmInfoCategory, QUERY_VM_FROM_ID) {
-                    @Override
-                    public PreparedStatement<VmInfo> customize(PreparedStatement<VmInfo> preparedStatement) {
-                        preparedStatement.setString(0, id.get());
-                        return preparedStatement;
-                    }
-                }).head();
-    }
-
-    @Override
-    public VmInfo getVmInfo(final VmRef ref) {
-        return executeQuery(
-                new AbstractDaoQuery<VmInfo>(storage, vmInfoCategory, QUERY_VM_INFO) {
-                    @Override
-                    public PreparedStatement<VmInfo> customize(PreparedStatement<VmInfo> preparedStatement) {
-                        preparedStatement.setString(0, ref.getHostRef().getAgentId());
-                        preparedStatement.setString(1, ref.getVmId());
-                        return preparedStatement;
-                    }
-                }).head();
-    }
-
-    @Override
-    public Collection<VmRef> getVMs(HostRef host) {
-        AgentId agentId = new AgentId(host.getAgentId());
-
-        Collection<VmInfo> vmInfos = getAllVmInfosForAgent(agentId);
-        if (vmInfos.equals(Collections.emptyList())) {
-            return Collections.emptyList();
-        }
-
-        return buildVMsFromQuery(vmInfos, host);
-    }
-
-    @Deprecated
-    private Collection<VmRef> buildVMsFromQuery(Collection<VmInfo> vmInfos, HostRef host) {
-        List<VmRef> vmRefs = new ArrayList<>();
-        for (VmInfo vmInfo : vmInfos) {
-            VmRef vm = buildVmRefFromChunk(vmInfo, host);
-            vmRefs.add(vm);
-        }
-
-        return vmRefs;
-    }
-
-    @Deprecated
-    private VmRef buildVmRefFromChunk(VmInfo vmInfo, HostRef host) {
-        String id = vmInfo.getVmId();
-        Integer pid = vmInfo.getVmPid();
-        // TODO can we do better than the main class?
-        String mainClass = vmInfo.getMainClass();
-        return new VmRef(host, id, pid, mainClass);
+        return null; // TODO Remove once VM Id completer is removed
     }
 
     @Override
     public Set<VmId> getVmIds(AgentId agentId) {
-        Set<VmId> vmIds = new HashSet<>();
-        Collection<VmInfo> vmInfos = getAllVmInfosForAgent(agentId);
-        for (VmInfo vmInfo : vmInfos) {
-            vmIds.add(new VmId(vmInfo.getVmId()));
-        }
-
-        return vmIds;
-    }
-
-    @Override
-    public List<VmInfo> getAllVmInfosForAgent(final AgentId agentId) {
-        return executeQuery(
-                new AbstractDaoQuery<VmInfo>(storage, vmInfoCategory, QUERY_ALL_VMS_FOR_AGENT) {
-                    @Override
-                    public PreparedStatement<VmInfo> customize(PreparedStatement<VmInfo> preparedStatement) {
-                        preparedStatement.setString(0, agentId.get());
-                        return preparedStatement;
-                    }
-                }).asList();
-    }
-
-    @Override
-    public long getCount() {
-        return getCount(storage, aggregateCategory, AGGREGATE_COUNT_ALL_VMS);
+        return Collections.emptySet(); // TODO Remove once VM Id completer is removed
     }
 
     @Override
     public void putVmInfo(final VmInfo info) {
-        executeStatement(
-                new AbstractDaoStatement<VmInfo>(storage, vmInfoCategory, DESC_ADD_VM_INFO) {
-                    @Override
-                    public PreparedStatement<VmInfo> customize(PreparedStatement<VmInfo> preparedStatement) {
-                        preparedStatement.setString(0, info.getAgentId());
-                        preparedStatement.setString(1, info.getVmId());
-                        preparedStatement.setInt(2, info.getVmPid());
-                        preparedStatement.setLong(3, info.getStartTimeStamp());
-                        preparedStatement.setLong(4, info.getStopTimeStamp());
-                        preparedStatement.setString(5, info.getJavaVersion());
-                        preparedStatement.setString(6, info.getJavaHome());
-                        preparedStatement.setString(7, info.getMainClass());
-                        preparedStatement.setString(8, info.getJavaCommandLine());
-                        preparedStatement.setString(9, info.getVmName());
-                        preparedStatement.setString(10, info.getVmArguments());
-                        preparedStatement.setString(11, info.getVmInfo());
-                        preparedStatement.setString(12, info.getVmVersion());
-                        preparedStatement.setPojoList(13, info.getPropertiesAsArray());
-                        preparedStatement.setPojoList(14, info.getEnvironmentAsArray());
-                        preparedStatement.setStringList(15, info.getLoadedNativeLibraries());
-                        preparedStatement.setLong(16, info.getUid());
-                        preparedStatement.setString(17, info.getUsername());
-                        return preparedStatement;
-                    }
-                });
+        try {
+            // Encode as JSON and send as POST request
+            String json = jsonHelper.toJson(Arrays.asList(info));
+            StringContentProvider provider = httpHelper.createContentProvider(json);
+            
+            String url = getAddURL(info.getAgentId());
+            Request httpRequest = httpHelper.newRequest(url);
+            httpRequest.method(HttpMethod.POST);
+            httpRequest.content(provider, CONTENT_TYPE);
+            sendRequest(httpRequest);
+        } catch (IOException | InterruptedException | TimeoutException | ExecutionException e) {
+           logger.log(Level.WARNING, "Failed to send JVM information to web gateway", e);
+        }
     }
 
     @Override
-    public void putVmStoppedTime(final String vmId, final long timestamp) {
-        executeStatement(
-                new AbstractDaoStatement<VmInfo>(storage, vmInfoCategory, DESC_UPDATE_VM_STOP_TIME) {
-                    @Override
-                    public PreparedStatement<VmInfo> customize(PreparedStatement<VmInfo> preparedStatement) {
-                        preparedStatement.setLong(0, timestamp);
-                        preparedStatement.setString(1, vmId);
-                        return preparedStatement;
-                    }
-                });
+    public void putVmStoppedTime(final String agentId, final String vmId, final long timestamp) {
+        try {
+            // Encode as JSON and send as PUT request
+            VmInfoUpdate update = new VmInfoUpdate(timestamp);
+            String json = jsonHelper.toJson(update);
+            StringContentProvider provider = httpHelper.createContentProvider(json);
+            
+            String url = getUpdateURL(agentId, vmId);
+            Request httpRequest = httpHelper.newRequest(url);
+            httpRequest.method(HttpMethod.PUT);
+            httpRequest.content(provider, CONTENT_TYPE);
+            sendRequest(httpRequest);
+        } catch (IOException | InterruptedException | TimeoutException | ExecutionException e) {
+           logger.log(Level.WARNING, "Failed to send JVM information update to web gateway", e);
+        }
+    }
+
+    private void sendRequest(Request httpRequest)
+            throws InterruptedException, TimeoutException, ExecutionException, IOException {
+        ContentResponse resp = httpRequest.send();
+        int status = resp.getStatus();
+        if (status != HttpStatus.OK_200) {
+            throw new IOException("Gateway returned HTTP status " + String.valueOf(status) + " - " + resp.getReason());
+        }
+    }
+    
+    private String getAddURL(String agentId) {
+        StringBuilder builder = buildURL(agentId);
+        return builder.toString();
     }
 
-    @Override
-    protected Logger getLogger() {
-        return logger;
+    private StringBuilder buildURL(String agentId) {
+        StringBuilder builder = new StringBuilder();
+        builder.append(GATEWAY_URL);
+        builder.append(GATEWAY_PATH);
+        builder.append(agentId);
+        return builder;
+    }
+    
+    private String getUpdateURL(String agentId, String vmId) {
+        StringBuilder builder = buildURL(agentId);
+        builder.append(GATEWAY_PATH_JVM_SUFFIX);
+        builder.append(vmId);
+        return builder.toString();
+    }
+    
+    static class VmInfoUpdate {
+        
+        private final long stoppedTime;
+        
+        VmInfoUpdate(long stoppedTime) {
+           this.stoppedTime = stoppedTime;
+        }
+        
+        long getStoppedTime() {
+            return stoppedTime;
+        }
+    }
+    
+    
+    // For testing purposes
+    static class JsonHelper {
+        
+        private final VmInfoTypeAdapter typeAdapter;
+        private final VmInfoUpdateTypeAdapter updateTypeAdapter;
+        
+        public JsonHelper(VmInfoTypeAdapter typeAdapter, VmInfoUpdateTypeAdapter updateTypeAdapter) {
+            this.typeAdapter = typeAdapter;
+            this.updateTypeAdapter = updateTypeAdapter;
+        }
+        
+        String toJson(List<VmInfo> infos) throws IOException {
+            return typeAdapter.toJson(infos);
+        }
+        
+        String toJson(VmInfoUpdate update) throws IOException {
+            return updateTypeAdapter.toJson(update);
+        }
+        
+    }
+    
+    // For testing purposes
+    static class HttpHelper {
+        
+        private final HttpClient httpClient;
+
+        HttpHelper(HttpClient httpClient) {
+            this.httpClient = httpClient;
+        }
+        
+        void startClient() throws Exception {
+            httpClient.start();
+        }
+        
+        StringContentProvider createContentProvider(String content) {
+            return new StringContentProvider(content);
+        }
+        
+        Request newRequest(String url) {
+            return new MockRequest(httpClient, URI.create(url));
+        }
+        
+    }
+    
+    // FIXME This class should be removed when the web gateway has a microservice for this DAO
+    private static class MockRequest extends HttpRequest {
+
+        MockRequest(HttpClient client, URI uri) {
+            super(client, uri);
+        }
+        
+        @Override
+        public ContentResponse send() throws InterruptedException, TimeoutException, ExecutionException {
+            return new MockResponse();
+        }
+        
+    }
+    
+    // FIXME This class should be removed when the web gateway has a microservice for this DAO
+    private static class MockResponse extends HttpContentResponse {
+
+        MockResponse() {
+            super(null, null, null);
+        }
+        
+        @Override
+        public int getStatus() {
+            return HttpStatus.OK_200;
+        }
+        
     }
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/main/java/com/redhat/thermostat/storage/internal/dao/VmInfoTypeAdapter.java	Fri May 05 12:16:53 2017 -0400
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.internal.dao;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import com.redhat.thermostat.storage.internal.dao.VmInfoDAOImpl.VmInfoUpdate;
+import com.redhat.thermostat.storage.model.VmInfo;
+
+public class VmInfoTypeAdapter extends TypeAdapter<List<VmInfo>> {
+    
+    private static final String AGENT_ID = "agentId";
+    private static final String VM_ID = "vmId";
+    private static final String VM_PID = "vmPid";
+    private static final String JAVA_VERSION = "javaVersion";
+    private static final String JAVA_HOME = "javaHome";
+    private static final String MAIN_CLASS = "mainClass";
+    private static final String JAVA_COMMAND_LINE = "javaCommandLine";
+    private static final String VM_ARGUMENTS = "vmArguments";
+    private static final String VM_NAME = "vmName";
+    private static final String VM_INFO = "vmInfo";
+    private static final String VM_VERSION = "vmVersion";
+    private static final String PROPERTIES_AS_ARRAY = "propertiesAsArray";
+    private static final String ENVIRONMENT_AS_ARRAY = "environmentAsArray";
+    private static final String LOADED_NATIVE_LIBRARIES = "loadedNativeLibraries";
+    private static final String START_TIME_STAMP = "startTimeStamp";
+    private static final String STOP_TIME_STAMP = "stopTimeStamp";
+    private static final String UID = "uid";
+    private static final String USERNAME = "username";
+    private static final String TYPE_LONG = "$numberLong";
+    private static final String KEY = "key";
+    private static final String VALUE = "value";
+    
+    @Override
+    public void write(JsonWriter out, List<VmInfo> value) throws IOException {
+        // Request is an array of VmInfo objects
+        out.beginArray();
+        
+        for (VmInfo info : value) {
+            writeVmInfo(out, info);
+        }
+        
+        out.endArray();
+    }
+
+    private void writeVmInfo(JsonWriter out, VmInfo info) throws IOException {
+        out.beginObject();
+        
+        // Write each field of VmInfo as part of a JSON object
+        out.name(AGENT_ID);
+        out.value(info.getAgentId());
+        out.name(VM_ID);
+        out.value(info.getVmId());
+        out.name(VM_PID);
+        out.value(info.getVmPid());
+        out.name(START_TIME_STAMP);
+        writeLong(out, info.getStartTimeStamp());
+        out.name(STOP_TIME_STAMP);
+        writeLong(out, info.getStopTimeStamp());
+        out.name(JAVA_VERSION);
+        out.value(info.getJavaVersion());
+        out.name(JAVA_HOME);
+        out.value(info.getJavaHome());
+        out.name(MAIN_CLASS);
+        out.value(info.getMainClass());
+        out.name(JAVA_COMMAND_LINE);
+        out.value(info.getJavaCommandLine());
+        out.name(VM_NAME);
+        out.value(info.getVmName());
+        out.name(VM_ARGUMENTS);
+        out.value(info.getVmArguments());
+        out.name(VM_INFO);
+        out.value(info.getVmInfo());
+        out.name(VM_VERSION);
+        out.value(info.getVmVersion());
+        out.name(PROPERTIES_AS_ARRAY);
+        writeStringMap(out, info.getProperties());
+        out.name(ENVIRONMENT_AS_ARRAY);
+        writeStringMap(out, info.getEnvironment());
+        out.name(LOADED_NATIVE_LIBRARIES);
+        writeStringArray(out, info.getLoadedNativeLibraries());
+        out.name(UID);
+        writeLong(out, info.getUid());
+        out.name(USERNAME);
+        out.value(info.getUsername());
+        
+        out.endObject();
+    }
+    
+    private void writeStringMap(JsonWriter out, Map<String, String> map) throws IOException {
+        // Write contents of Map as an array of JSON objects
+        out.beginArray();
+        
+        Set<Entry<String, String>> entries = map.entrySet();
+        for (Map.Entry<String, String> entry : entries) {
+            // Create JSON object with key and value labeled as JSON names
+            out.beginObject();
+            out.name(KEY);
+            out.value(entry.getKey());
+            out.name(VALUE);
+            out.value(entry.getValue());
+            out.endObject();
+        }
+        
+        out.endArray();
+    }
+    
+    private void writeStringArray(JsonWriter out, String[] array) throws IOException {
+        // Write String[] as JSON array
+        out.beginArray();
+        
+        for (String item : array) {
+            out.value(item);
+        }
+        
+        out.endArray();
+    }
+    
+    private static void writeLong(JsonWriter out, long value) throws IOException {
+        // Write MongoDB representation of a Long
+        out.beginObject();
+        out.name(TYPE_LONG);
+        out.value(String.valueOf(value));
+        out.endObject();
+    }
+
+    @Override
+    public List<VmInfo> read(JsonReader in) throws IOException {
+        throw new UnsupportedOperationException();
+    }
+    
+    static class VmInfoUpdateTypeAdapter extends TypeAdapter<VmInfoUpdate> {
+
+        private static final String SET = "set";
+        
+        @Override
+        public void write(JsonWriter out, VmInfoUpdate value) throws IOException {
+            // List fields to update as part of a JSON object with name "set"
+            out.beginObject();
+            out.name(SET);
+            
+            out.beginObject();
+            out.name(STOP_TIME_STAMP);
+            writeLong(out, value.getStoppedTime());
+            out.endObject();
+            
+            out.endObject();
+        }
+
+        @Override
+        public VmInfoUpdate read(JsonReader in) throws IOException {
+            throw new UnsupportedOperationException();
+        }
+        
+    }
+    
+}
--- a/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/VmInfoDAOTest.java	Fri May 05 12:16:15 2017 -0400
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/VmInfoDAOTest.java	Fri May 05 12:16:53 2017 -0400
@@ -37,117 +37,90 @@
 package com.redhat.thermostat.storage.internal.dao;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyListOf;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Set;
 
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 
-import com.redhat.thermostat.storage.core.AgentId;
-import com.redhat.thermostat.storage.core.Cursor;
-import com.redhat.thermostat.storage.core.DescriptorParsingException;
-import com.redhat.thermostat.storage.core.HostRef;
 import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.PreparedStatement;
-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.VmId;
-import com.redhat.thermostat.storage.core.VmRef;
 import com.redhat.thermostat.storage.dao.VmInfoDAO;
-import com.redhat.thermostat.storage.model.AggregateCount;
+import com.redhat.thermostat.storage.internal.dao.VmInfoDAOImpl.HttpHelper;
+import com.redhat.thermostat.storage.internal.dao.VmInfoDAOImpl.JsonHelper;
+import com.redhat.thermostat.storage.internal.dao.VmInfoDAOImpl.VmInfoUpdate;
 import com.redhat.thermostat.storage.model.VmInfo;
 import com.redhat.thermostat.storage.model.VmInfo.KeyValuePair;
 
 public class VmInfoDAOTest {
 
-    private String vmId;
-    private int vmPid;
-    private long startTime;
-    private long stopTime;
-    private String jVersion;
-    private String jHome;
-    private String mainClass;
-    private String commandLine;
-    private String vmName;
-    private String vmInfo;
-    private String vmVersion;
-    private String vmArgs;
-    private Map<String, String> props;
-    private Map<String, String> env;
-    private String[] libs;
-    private long uid;
-    private String username;
+    private static final String URL = "http://localhost:26000/api/v100/vm-info/systems/*/agents/foo-agent";
+    private static final String UPDATE_URL = URL + "/jvms/vmId";
+    private static final String SOME_JSON = "{\"some\" : \"json\"}";
+    private static final String SOME_OTHER_JSON = "{\"some\" : {\"other\" : \"json\"}}";
+    private static final String CONTENT_TYPE = "application/json";
+    
+    private VmInfo info;
+    private JsonHelper jsonHelper;
+    private HttpHelper httpHelper;
+    private StringContentProvider contentProvider;
+    private Request request;
+    private ContentResponse response;
 
     @Before
-    public void setUp() {
-        vmId = "vmId";
-        vmPid = 1;
-        startTime = 2;
-        stopTime = 3;
-        jVersion = "java 1.0";
-        jHome = "/path/to/jdk/home";
-        mainClass = "Hello.class";
-        commandLine = "World";
-        vmArgs = "-XX=+FastestJITPossible";
-        vmName = "Hotspot";
-        vmInfo = "Some info";
-        vmVersion = "1.0";
-        props = new HashMap<>();
-        env = new HashMap<>();
-        libs = new String[0];
-        uid = 2000;
-        username = "myUser";
+    public void setUp() throws Exception {
+        String vmId = "vmId";
+        int vmPid = 1;
+        long startTime = 2L;
+        long stopTime = Long.MIN_VALUE;
+        String jVersion = "java 1.0";
+        String jHome = "/path/to/jdk/home";
+        String mainClass = "Hello.class";
+        String commandLine = "World";
+        String vmArgs = "-XX=+FastestJITPossible";
+        String vmName = "Hotspot";
+        String vmInfo = "Some info";
+        String vmVersion = "1.0";
+        Map<String, String> props = new HashMap<>();
+        Map<String, String> env = new HashMap<>();
+        String[] libs = new String[0];
+        long uid = 2000L;
+        String username = "myUser";
+        info = new VmInfo("foo-agent", vmId, vmPid, startTime, stopTime, jVersion, jHome,
+                mainClass, commandLine, vmName, vmInfo, vmVersion, vmArgs,
+                props, env, libs, uid, username);
+        
+        httpHelper = mock(HttpHelper.class);
+        contentProvider = mock(StringContentProvider.class);
+        when(httpHelper.createContentProvider(anyString())).thenReturn(contentProvider);
+        request = mock(Request.class);
+        when(httpHelper.newRequest(anyString())).thenReturn(request);
+        response = mock(ContentResponse.class);
+        when(response.getStatus()).thenReturn(HttpStatus.OK_200);
+        when(request.send()).thenReturn(response);
+        
+        jsonHelper = mock(JsonHelper.class);
+        when(jsonHelper.toJson(anyListOf(VmInfo.class))).thenReturn(SOME_JSON);
+        when(jsonHelper.toJson(any(VmInfoUpdate.class))).thenReturn(SOME_OTHER_JSON);
     }
     
     @Test
-    public void preparedQueryDescriptorsAreSane() {
-        String expectedVmInfo = "QUERY vm-info WHERE 'agentId' = ?s AND 'vmId' = ?s LIMIT 1";
-        assertEquals(expectedVmInfo, VmInfoDAOImpl.QUERY_VM_INFO);
-        String expectedVmInfoAll = "QUERY vm-info WHERE 'agentId' = ?s";
-        assertEquals(expectedVmInfoAll, VmInfoDAOImpl.QUERY_ALL_VMS_FOR_AGENT);
-        String expectedAllVms = "QUERY vm-info";
-        assertEquals(expectedAllVms, VmInfoDAOImpl.QUERY_ALL_VMS);
-        String aggregateAllVms = "QUERY-COUNT vm-info";
-        assertEquals(aggregateAllVms, VmInfoDAOImpl.AGGREGATE_COUNT_ALL_VMS);
-        String addVmInfo = "ADD vm-info SET 'agentId' = ?s , " +
-                                            "'vmId' = ?s , " +
-                                            "'vmPid' = ?i , " +
-                                            "'startTimeStamp' = ?l , " +
-                                            "'stopTimeStamp' = ?l , " +
-                                            "'javaVersion' = ?s , " +
-                                            "'javaHome' = ?s , " +
-                                            "'mainClass' = ?s , " +
-                                            "'javaCommandLine' = ?s , " +
-                                            "'vmName' = ?s , " +
-                                            "'vmArguments' = ?s , " +
-                                            "'vmInfo' = ?s , " +
-                                            "'vmVersion' = ?s , " +
-                                            "'propertiesAsArray' = ?p[ , " +
-                                            "'environmentAsArray' = ?p[ , " +
-                                            "'loadedNativeLibraries' = ?s[ , " +
-                                            "'uid' = ?l , " +
-                                            "'username' = ?s";
-        assertEquals(addVmInfo, VmInfoDAOImpl.DESC_ADD_VM_INFO);
-        String updateVmStopTime = "UPDATE vm-info SET 'stopTimeStamp' = ?l WHERE 'vmId' = ?s";
-        assertEquals(updateVmStopTime, VmInfoDAOImpl.DESC_UPDATE_VM_STOP_TIME);
-    }
-
-    @Test
     public void testCategory() {
         assertEquals("vm-info", VmInfoDAO.vmInfoCategory.getName());
         Collection<Key<?>> keys = VmInfoDAO.vmInfoCategory.getKeys();
@@ -173,303 +146,36 @@
     }
 
     @Test
-    public void testGetVMsDescriptorException() throws DescriptorParsingException {
-        Storage storage = mock(Storage.class);
-        VmInfoDAO vmInfoDao = new VmInfoDAOImpl(storage);
-        HostRef host = new HostRef("123", "fluffhost");
-
-        when(storage.prepareStatement(anyDescriptor())).thenThrow(new DescriptorParsingException("testException"));
-
-        Collection<VmRef> vmRefs = vmInfoDao.getVMs(host);
-        assertEquals(Collections.emptyList(), vmRefs);
-    }
-
-    @Test
-    public void testGetVmIdsDescriptorException() throws DescriptorParsingException {
-        Storage storage = mock(Storage.class);
-        VmInfoDAO vmInfoDao = new VmInfoDAOImpl(storage);
-        AgentId agentId = new AgentId("agentId");
-
-        when(storage.prepareStatement(anyDescriptor())).thenThrow(new DescriptorParsingException("testException"));
-
-        Set<VmId> vmIds = vmInfoDao.getVmIds(agentId);
-        assertEquals(Collections.emptySet(), vmIds);
-    }
-
-    @Test
-    public void testGetVmInfo() throws DescriptorParsingException, StatementExecutionException {
-        Storage storage = mock(Storage.class);
-        @SuppressWarnings("unchecked")
-        PreparedStatement<VmInfo> stmt = (PreparedStatement<VmInfo>) mock(PreparedStatement.class);
-        when(storage.prepareStatement(anyDescriptor())).thenReturn(stmt);
-        VmInfo expected = new VmInfo("foo-agent", vmId, vmPid, startTime, stopTime, jVersion, jHome, mainClass, commandLine, vmName, vmInfo, vmVersion, vmArgs, props, env, libs, uid, username);
-        @SuppressWarnings("unchecked")
-        Cursor<VmInfo> cursor = (Cursor<VmInfo>) mock(Cursor.class);
-        when(cursor.hasNext()).thenReturn(true).thenReturn(false);
-        when(cursor.next()).thenReturn(expected).thenReturn(null);
-        when(stmt.executeQuery()).thenReturn(cursor);
-
-        HostRef hostRef = mock(HostRef.class);
-        when(hostRef.getAgentId()).thenReturn("system");
-
-        VmRef vmRef = mock(VmRef.class);
-        when(vmRef.getHostRef()).thenReturn(hostRef);
-        when(vmRef.getVmId()).thenReturn("vmId");
-
-        VmInfoDAO dao = new VmInfoDAOImpl(storage);
-        VmInfo info = dao.getVmInfo(vmRef);
-        assertEquals(expected, info);
-        
-        verify(storage).prepareStatement(anyDescriptor());
-        verify(stmt).setString(0, "system");
-        verify(stmt).setString(1, "vmId");
-        verify(stmt).executeQuery();
-    }
-
-    @SuppressWarnings("unchecked")
-    private StatementDescriptor<VmInfo> anyDescriptor() {
-        return (StatementDescriptor<VmInfo>) any(StatementDescriptor.class);
-    }
-
-    @Test
-    public void testGetVmInfoUnknownVM() throws DescriptorParsingException, StatementExecutionException {
-        Storage storage = mock(Storage.class);
-        @SuppressWarnings("unchecked")
-        PreparedStatement<VmInfo> stmt = (PreparedStatement<VmInfo>) mock(PreparedStatement.class);
-        when(storage.prepareStatement(anyDescriptor())).thenReturn(stmt);
-        @SuppressWarnings("unchecked")
-        Cursor<VmInfo> cursor = (Cursor<VmInfo>) mock(Cursor.class);
-        when(stmt.executeQuery()).thenReturn(cursor);
+    public void testPutVmInfo() throws Exception {
+        VmInfoDAO dao = new VmInfoDAOImpl(httpHelper, jsonHelper);
+        dao.putVmInfo(info);
         
-        HostRef hostRef = mock(HostRef.class);
-        when(hostRef.getAgentId()).thenReturn("system");
-
-        VmRef vmRef = mock(VmRef.class);
-        when(vmRef.getVmId()).thenReturn("noVm");
-        when(vmRef.getHostRef()).thenReturn(hostRef);
-
-        VmInfoDAO dao = new VmInfoDAOImpl(storage);
-        VmInfo result = dao.getVmInfo(vmRef);
-
-        assertNull(result);
-
-        verify(storage).prepareStatement(anyDescriptor());
-        verify(stmt).setString(0, "system");
-        verify(stmt).setString(1, "noVm");
-        verify(stmt).executeQuery();
-    }
-
-    @Test
-    public void testGetVMsWithSingleVM() throws DescriptorParsingException, StatementExecutionException {
-        Storage storage = setupStorageForSingleVM();
-        VmInfoDAO dao = new VmInfoDAOImpl(storage);
-        HostRef host = new HostRef("123", "fluffhost");
-
-        Collection<VmRef> vms = dao.getVMs(host);
-
-        assertVmRefCollection(vms, new VmRef(host, "vmId", 123, "mainClass1"));
-    }
-
-    @Test
-    public void testGetVmIdsWithSingleVM() throws DescriptorParsingException, StatementExecutionException {
-        Storage storage = setupStorageForSingleVM();
-        VmInfoDAO dao = new VmInfoDAOImpl(storage);
-        AgentId agentId = new AgentId("123");
-
-        Set<VmId> vmIds = dao.getVmIds(agentId);
-
-        assertVmIdCollection(vmIds, new VmId("vmId"));
-    }
-
-    private Storage setupStorageForSingleVM() throws DescriptorParsingException, StatementExecutionException {
-
-      VmInfo vm1 = new VmInfo();
-      vm1.setVmId("vmId");
-      vm1.setVmPid(123);
-      vm1.setMainClass("mainClass1");
-
-      @SuppressWarnings("unchecked")
-      Cursor<VmInfo> singleVMCursor = (Cursor<VmInfo>) mock(Cursor.class);
-      when(singleVMCursor.hasNext()).thenReturn(true).thenReturn(false);
-      when(singleVMCursor.next()).thenReturn(vm1);
-
-      Storage storage = mock(Storage.class);
-      @SuppressWarnings("unchecked")
-      PreparedStatement<VmInfo> stmt = (PreparedStatement<VmInfo>) mock(PreparedStatement.class);
-      when(storage.prepareStatement(anyDescriptor())).thenReturn(stmt);
-      when(stmt.executeQuery()).thenReturn(singleVMCursor);
-      return storage;
-  }
-
-    @Test
-    public void testGetVMsWithMultiVMs() throws DescriptorParsingException, StatementExecutionException {
-        Storage storage = setupStorageForMultiVM();
-        VmInfoDAO dao = new VmInfoDAOImpl(storage);
-        HostRef host = new HostRef("456", "fluffhost");
-
-        Collection<VmRef> vms = dao.getVMs(host);
-
-        assertVmRefCollection(vms, new VmRef(host, "vmId1", 123, "mainClass1"), new VmRef(host, "vmId2", 456, "mainClass2"));
+        verify(httpHelper).newRequest(URL);
+        verify(request).method(HttpMethod.POST);
+        verify(jsonHelper).toJson(eq(Arrays.asList(info)));
+        verify(httpHelper).createContentProvider(SOME_JSON);
+        verify(request).content(contentProvider, CONTENT_TYPE);
+        verify(request).send();
+        verify(response).getStatus();
     }
 
     @Test
-    public void testGetVmIdsWithMultiVMs() throws DescriptorParsingException, StatementExecutionException {
-        Storage storage = setupStorageForMultiVM();
-        VmInfoDAO dao = new VmInfoDAOImpl(storage);
-        AgentId agentId = new AgentId("456");
-
-        Set<VmId> vmIds = dao.getVmIds(agentId);
-
-        assertVmIdCollection(vmIds, new VmId("vmId1"), new VmId("vmId2"));
-    }
-
-
-    @Test
-    public void testGetAllVmInfos() throws DescriptorParsingException, StatementExecutionException {
-        Storage storage = setupStorageForMultiVM();
-        VmInfoDAO dao = new VmInfoDAOImpl(storage);
-
-        List<VmInfo> vmInfos = dao.getAllVmInfos();
-
-        assertTrue(vmInfos.size() == 2);
-    }
-
-    @Test
-    public void testGetAllVmInfosForAgent() throws DescriptorParsingException, StatementExecutionException {
-        Storage storage = setupStorageForMultiVM();
-        VmInfoDAO dao = new VmInfoDAOImpl(storage);
-        AgentId agentId = new AgentId("456");
-
-        List<VmInfo> vmInfos = dao.getAllVmInfosForAgent(agentId);
-
-        assertTrue(vmInfos.size() == 2);
-    }
-
-    private Storage setupStorageForMultiVM() throws DescriptorParsingException, StatementExecutionException {
-      VmInfo vm1 = new VmInfo();
-      vm1.setVmId("vmId1");
-      vm1.setVmPid(123);
-      vm1.setMainClass("mainClass1");
-
-      VmInfo vm2 = new VmInfo();
-      vm2.setVmId("vmId2");
-      vm2.setVmPid(456);
-      vm2.setMainClass("mainClass2");
-
-      @SuppressWarnings("unchecked")
-      Cursor<VmInfo> multiVMsCursor = mock(Cursor.class);
-      when(multiVMsCursor.hasNext()).thenReturn(true).thenReturn(true).thenReturn(false);
-      when(multiVMsCursor.next()).thenReturn(vm1).thenReturn(vm2);
-
-      Storage storage = mock(Storage.class);
-      @SuppressWarnings("unchecked")
-      PreparedStatement<VmInfo> stmt = (PreparedStatement<VmInfo>) mock(PreparedStatement.class);
-      when(storage.prepareStatement(anyDescriptor())).thenReturn(stmt);
-      when(stmt.executeQuery()).thenReturn(multiVMsCursor);
-      return storage;
-  }
-
-    private void assertVmRefCollection(Collection<VmRef> vms, VmRef... expectedVMs) {
-        assertEquals(expectedVMs.length, vms.size());
-        for (VmRef expectedVM : expectedVMs) {
-            assertTrue(vms.contains(expectedVM));
-        }
-    }
-
-    private void assertVmIdCollection(Collection<VmId> vms, VmId... expectedVmIds) {
-        assertEquals(expectedVmIds.length, vms.size());
-        for (VmId expectedVmId : expectedVmIds) {
-            assertTrue(vms.contains(expectedVmId));
-        }
-    }
-
-    @Test
-    public void testGetCount()
-            throws DescriptorParsingException, StatementExecutionException {
-        AggregateCount count = new AggregateCount();
-        count.setCount(2);
+    public void testPutVmStoppedTime() throws Exception {
+        VmInfoDAO dao = new VmInfoDAOImpl(httpHelper, jsonHelper);
+        dao.putVmStoppedTime("foo-agent", "vmId", 3L);
 
-        @SuppressWarnings("unchecked")
-        Cursor<AggregateCount> c = (Cursor<AggregateCount>) mock(Cursor.class);
-        when(c.hasNext()).thenReturn(true).thenReturn(false);
-        when(c.next()).thenReturn(count).thenThrow(new NoSuchElementException());
-
-        Storage storage = mock(Storage.class);
-        @SuppressWarnings("unchecked")
-        PreparedStatement<AggregateCount> stmt = (PreparedStatement<AggregateCount>) mock(PreparedStatement.class);
-        @SuppressWarnings("unchecked")
-        StatementDescriptor<AggregateCount> desc = any(StatementDescriptor.class);
-        when(storage.prepareStatement(desc)).thenReturn(stmt);
-        when(stmt.executeQuery()).thenReturn(c);
-        VmInfoDAOImpl dao = new VmInfoDAOImpl(storage);
-
-        assertEquals(2, dao.getCount());
-    }
-
-    @SuppressWarnings("unchecked")
-    @Test
-    public void testPutVmInfo() throws DescriptorParsingException, StatementExecutionException {
-        Storage storage = mock(Storage.class);
-        PreparedStatement<VmInfo> add = mock(PreparedStatement.class);
-        when(storage.prepareStatement(any(StatementDescriptor.class))).thenReturn(add);
-        
-        VmInfo info = new VmInfo("foo-agent", vmId, vmPid, startTime, stopTime, jVersion, jHome,
-                mainClass, commandLine, vmName, vmInfo, vmVersion, vmArgs,
-                props, env, libs, uid, username);
-        VmInfoDAO dao = new VmInfoDAOImpl(storage);
-        dao.putVmInfo(info);
-        
-        @SuppressWarnings("rawtypes")
-        ArgumentCaptor<StatementDescriptor> captor = ArgumentCaptor.forClass(StatementDescriptor.class);
+        verify(httpHelper).newRequest(UPDATE_URL);
+        verify(request).method(HttpMethod.PUT);
         
-        verify(storage).prepareStatement(captor.capture());
-        StatementDescriptor<?> desc = captor.getValue();
-        assertEquals(VmInfoDAOImpl.DESC_ADD_VM_INFO, desc.getDescriptor());
-
-        verify(add).setString(0, info.getAgentId());
-        verify(add).setString(1, info.getVmId());
-        verify(add).setInt(2, info.getVmPid());
-        verify(add).setLong(3, info.getStartTimeStamp());
-        verify(add).setLong(4, info.getStopTimeStamp());
-        verify(add).setString(5, info.getJavaVersion());
-        verify(add).setString(6, info.getJavaHome());
-        verify(add).setString(7, info.getMainClass());
-        verify(add).setString(8, info.getJavaCommandLine());
-        verify(add).setString(9, info.getVmName());
-        verify(add).setString(10, info.getVmArguments());
-        verify(add).setString(11, info.getVmInfo());
-        verify(add).setString(12, info.getVmVersion());
-        verify(add).setPojoList(13, info.getPropertiesAsArray());
-        verify(add).setPojoList(14, info.getEnvironmentAsArray());
-        verify(add).setStringList(15, info.getLoadedNativeLibraries());
-        verify(add).setLong(16, info.getUid());
-        verify(add).setString(17, info.getUsername());
-        verify(add).execute();
-        verifyNoMoreInteractions(add);
-    }
-
-    @SuppressWarnings("unchecked")
-    @Test
-    public void testPutVmStoppedTime() throws DescriptorParsingException,
-            StatementExecutionException {
-        Storage storage = mock(Storage.class);
-        PreparedStatement<VmInfo> update = mock(PreparedStatement.class);
-        when(storage.prepareStatement(any(StatementDescriptor.class))).thenReturn(update);
-
-        VmInfoDAO dao = new VmInfoDAOImpl(storage);
-        dao.putVmStoppedTime(vmId, stopTime);
-
-        @SuppressWarnings("rawtypes")
-        ArgumentCaptor<StatementDescriptor> captor = ArgumentCaptor.forClass(StatementDescriptor.class);
-        
-        verify(storage).prepareStatement(captor.capture());
-        StatementDescriptor<?> desc = captor.getValue();
-        assertEquals(VmInfoDAOImpl.DESC_UPDATE_VM_STOP_TIME, desc.getDescriptor());
-        
-        verify(update).setLong(0, stopTime);
-        verify(update).setString(1, vmId);
-        verify(update).execute();
-        verifyNoMoreInteractions(update);
+        ArgumentCaptor<VmInfoUpdate> updateCaptor = ArgumentCaptor.forClass(VmInfoUpdate.class);
+        verify(jsonHelper).toJson(updateCaptor.capture());
+        VmInfoUpdate update = updateCaptor.getValue();
+        assertEquals(3L, update.getStoppedTime());
+                
+        verify(httpHelper).createContentProvider(SOME_OTHER_JSON);
+        verify(request).content(contentProvider, CONTENT_TYPE);
+        verify(request).send();
+        verify(response).getStatus();
     }
 
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/storage/core/src/test/java/com/redhat/thermostat/storage/internal/dao/VmInfoTypeAdapterTest.java	Fri May 05 12:16:53 2017 -0400
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.storage.internal.dao;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.storage.internal.dao.VmInfoDAOImpl.VmInfoUpdate;
+import com.redhat.thermostat.storage.internal.dao.VmInfoTypeAdapter.VmInfoUpdateTypeAdapter;
+import com.redhat.thermostat.storage.model.VmInfo;
+
+public class VmInfoTypeAdapterTest {
+    
+    @Test
+    public void testWrite() throws Exception {
+        VmInfoTypeAdapter adapter = new VmInfoTypeAdapter();
+        final String expected = "[{\"agentId\":\"agent1\",\"vmId\":\"vm1\",\"vmPid\":8000,"
+                + "\"startTimeStamp\":{\"$numberLong\":\"50000\"},\"stopTimeStamp\":"
+                + "{\"$numberLong\":\"-9223372036854775808\"},\"javaVersion\":\"1.8.0\","
+                + "\"javaHome\":\"/path/to/java\",\"mainClass\":\"myClass\",\"javaCommandLine\":\"java myClass\","
+                + "\"vmName\":\"myJVM\",\"vmArguments\":\"-Dhello\",\"vmInfo\":\"interesting\","
+                + "\"vmVersion\":\"1800\",\"propertiesAsArray\":[{\"key\":\"A\",\"value\":\"B\"},"
+                + "{\"key\":\"C\",\"value\":\"D\"}],\"environmentAsArray\":[{\"key\":\"E\",\"value\":\"F\"},"
+                + "{\"key\":\"G\",\"value\":\"H\"}],\"loadedNativeLibraries\":[],\"uid\":{\"$numberLong\":\"1234\"},"
+                + "\"username\":\"test\"},"
+                + "{\"agentId\":\"agent2\",\"vmId\":\"vm2\",\"vmPid\":9000,\"startTimeStamp\":"
+                + "{\"$numberLong\":\"100000\"},\"stopTimeStamp\":{\"$numberLong\":\"200000\"},"
+                + "\"javaVersion\":\"1.7.0\",\"javaHome\":\"/path/to/jre\",\"mainClass\":\"myOtherClass\","
+                + "\"javaCommandLine\":\"otherClass.sh\",\"vmName\":\"myOtherJVM\",\"vmArguments\":\"-Dworld\","
+                + "\"vmInfo\":\"info\",\"vmVersion\":\"1700\",\"propertiesAsArray\":[],\"environmentAsArray\":"
+                + "[{\"key\":\"A\",\"value\":\"B\"},{\"key\":\"C\",\"value\":\"D\"}],"
+                + "\"loadedNativeLibraries\":[\"libhello\",\"libworld\"],\"uid\":{\"$numberLong\":\"5678\"}"
+                + ",\"username\":\"user\"}]";
+        
+        final Map<String, String> props = new HashMap<>();
+        props.put("A", "B");
+        props.put("C", "D");
+        final Map<String, String> env = new HashMap<>();
+        env.put("E", "F");
+        env.put("G", "H");
+        VmInfo first = new VmInfo("agent1", "vm1", 8000, 50000L, Long.MIN_VALUE, "1.8.0", "/path/to/java", "myClass", 
+                "java myClass", "myJVM", "interesting", "1800", "-Dhello", props, env, new String[0], 1234L, "test");
+        
+        final Map<String, String> props2 = new HashMap<>();
+        final Map<String, String> env2 = new HashMap<>();
+        env2.put("A", "B");
+        env2.put("C", "D");
+        final String[] libs = { "libhello", "libworld" };
+        VmInfo second = new VmInfo("agent2", "vm2", 9000, 100000L, 200000L, "1.7.0", "/path/to/jre", "myOtherClass", 
+                "otherClass.sh", "myOtherJVM", "info", "1700", "-Dworld", props2, env2, libs, 5678L, "user");
+        List<VmInfo> infos = Arrays.asList(first, second);
+        
+        String json = adapter.toJson(infos);
+        assertEquals(expected, json);
+    }
+    
+    @Test
+    public void testUpdate() throws Exception {
+        VmInfoUpdateTypeAdapter adapter = new VmInfoUpdateTypeAdapter();
+        final String expected = "{\"set\":{\"stopTimeStamp\":{\"$numberLong\":\"5000\"}}}";
+        
+        VmInfoUpdate update = new VmInfoUpdate(5000L);
+        String json = adapter.toJson(update);
+        assertEquals(expected, json);
+    }
+    
+}
--- a/system-backend/src/main/java/com/redhat/thermostat/backend/system/internal/JvmStatHostListener.java	Fri May 05 12:16:15 2017 -0400
+++ b/system-backend/src/main/java/com/redhat/thermostat/backend/system/internal/JvmStatHostListener.java	Fri May 05 12:16:53 2017 -0400
@@ -171,7 +171,7 @@
             notifier.notifyVmStatusChange(Status.VM_STOPPED, vmId, vmPid);
 
             long stopTime = System.currentTimeMillis();
-            vmInfoDAO.putVmStoppedTime(vmId, stopTime);
+            vmInfoDAO.putVmStoppedTime(writerId.getWriterID(), vmId, stopTime);
 
             MonitoredVm vm = vmData.getSecond();
             vm.detach();