changeset 2301:a2ac39aa092e

Initial version of byteman cli client. Reviewed-by: neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-May/018868.html
author Severin Gehwolf <sgehwolf@redhat.com>
date Mon, 09 May 2016 09:39:38 +0200
parents 6a578d5a1648
children 3bc74bccf849
files distribution/config/examples/thermostat-roles-example.properties distribution/config/thermostat-roles.properties setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/UserRoles.java vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAgentInfo.java vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAttacher.java vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanMetricsReceiver.java vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanRequestReceiver.java vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/PeriodicBytemanPollingBackend.java vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/UpdateRulePollingAction.java vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/VmBytemanBackend.java vm-byteman/agent/src/main/resources/bytemanRule.btm vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanMetricsReceiverTest.java vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanRequestReceiverTest.java vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/UpdateRulePollingActionTest.java vm-byteman/agent/src/test/resources/byteman-helper-root/vm-byteman/plugin-libs/thermostat-helper/not-really-a-jar-file.jar vm-byteman/client-cli/pom.xml vm-byteman/client-cli/src/main/java/com/redhat/thermostat/vm/byteman/client/cli/internal/Activator.java vm-byteman/client-cli/src/main/java/com/redhat/thermostat/vm/byteman/client/cli/internal/BytemanControlCommand.java vm-byteman/client-cli/src/main/java/com/redhat/thermostat/vm/byteman/client/cli/internal/BytemanRequestResponseListener.java vm-byteman/client-cli/src/main/java/com/redhat/thermostat/vm/byteman/client/cli/internal/LocaleResources.java vm-byteman/client-cli/src/main/resources/com/redhat/thermostat/vm/byteman/client/cli/internal/strings.properties vm-byteman/client-cli/src/test/java/com/redhat/thermostat/vm/byteman/client/cli/internal/BytemanControlCommandTest.java vm-byteman/client-cli/src/test/java/com/redhat/thermostat/vm/byteman/client/cli/internal/BytemanRequestResponseListenerTest.java vm-byteman/client-cli/src/test/java/com/redhat/thermostat/vm/byteman/client/cli/internal/LocaleResourcesTest.java vm-byteman/client-cli/src/test/resources/testRule.btm vm-byteman/common/pom.xml vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/VmBytemanDAO.java vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/VmBytemanMetricDAO.java vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/VmBytemanStatus.java vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/command/BytemanRequest.java vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/internal/VmBytemanDAOImpl.java vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/internal/VmBytemanMetricDAOCategoryRegistration.java vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/internal/VmBytemanMetricDAOImpl.java vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/internal/VmBytemanMetricDAOImplStatementDescriptorRegistration.java vm-byteman/common/src/test/java/com/redhat/thermostat/vm/byteman/common/command/BytemanRequestTest.java vm-byteman/distribution/pom.xml vm-byteman/distribution/thermostat-plugin.xml vm-byteman/pom.xml
diffstat 37 files changed, 2352 insertions(+), 528 deletions(-) [+]
line wrap: on
line diff
--- a/distribution/config/examples/thermostat-roles-example.properties	Wed May 18 12:13:13 2016 -0400
+++ b/distribution/config/examples/thermostat-roles-example.properties	Mon May 09 09:39:38 2016 +0200
@@ -57,4 +57,5 @@
                   thermostat-cmdc-grant-killvm, \
                   thermostat-cmdc-grant-profile-vm, \
                   thermostat-cmdc-grant-ping, \
-                  thermostat-cmdc-grant-jmx-toggle-notifications
+                  thermostat-cmdc-grant-jmx-toggle-notifications \
+                  thermostat-cmdc-grant-vm-byteman-instrument
--- a/distribution/config/thermostat-roles.properties	Wed May 18 12:13:13 2016 -0400
+++ b/distribution/config/thermostat-roles.properties	Mon May 09 09:39:38 2016 +0200
@@ -74,4 +74,5 @@
 #                  thermostat-cmdc-grant-killvm, \
 #                  thermostat-cmdc-grant-profile-vm, \
 #                  thermostat-cmdc-grant-ping, \
-#                  thermostat-cmdc-grant-jmx-toggle-notifications
+#                  thermostat-cmdc-grant-jmx-toggle-notifications \
+#                  thermostat-cmdc-grant-vm-byteman-instrument
--- a/setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/UserRoles.java	Wed May 18 12:13:13 2016 -0400
+++ b/setup/command/src/main/java/com/redhat/thermostat/setup/command/internal/model/UserRoles.java	Mon May 09 09:39:38 2016 +0200
@@ -69,7 +69,8 @@
     final String GRANT_CMD_CHANNEL_KILLVM = "thermostat-cmdc-grant-killvm";
     final String GRANT_CMD_CHANNEL_PING = "thermostat-cmdc-grant-ping";
     final String GRANT_CMD_CHANNEL_JMX_TOGGLE_NOTIFICATION = "thermostat-cmdc-grant-jmx-toggle-notifications";
-    final String GRANT_CMD_PROFILE_VM = "thermostat-cmdc-grant-profile-vm";
+    final String GRANT_CMD_CHANNEL_PROFILE_VM = "thermostat-cmdc-grant-profile-vm";
+    final String GRANT_CMD_CHANNEL_BYTEMAN_INSTRUMENT = "thermostat-cmdc-grant-vm-byteman-instrument";
 
     final String PREPARE_STATEMENT = "thermostat-prepare-statement";
     final String READ = "thermostat-query";
@@ -126,6 +127,7 @@
             UserRoles.GRANT_CMD_CHANNEL_JMX_TOGGLE_NOTIFICATION,
             UserRoles.GRANT_CMD_CHANNEL_KILLVM,
             UserRoles.GRANT_CMD_CHANNEL_PING,
-            UserRoles.GRANT_CMD_PROFILE_VM
+            UserRoles.GRANT_CMD_CHANNEL_PROFILE_VM,
+            UserRoles.GRANT_CMD_CHANNEL_BYTEMAN_INSTRUMENT
     };
 }
--- a/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAgentInfo.java	Wed May 18 12:13:13 2016 -0400
+++ b/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAgentInfo.java	Mon May 09 09:39:38 2016 +0200
@@ -42,12 +42,14 @@
     private final int agentListenPort;
     private final String listenHost;
     private final String vmId;
+    private final String writerId;
     
-    BytemanAgentInfo(int vmPid, int agentListenPort, String listenHost, String vmId) {
+    BytemanAgentInfo(int vmPid, int agentListenPort, String listenHost, String vmId, String writerId) {
         this.agentListenPort = agentListenPort;
         this.listenHost = listenHost;
         this.vmPid = vmPid;
         this.vmId = vmId;
+        this.writerId = writerId;
     }
 
     int getVmPid() {
@@ -65,4 +67,8 @@
     String getVmId() {
         return vmId;
     }
+    
+    String getWriterId() {
+        return writerId;
+    }
 }
--- a/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAttacher.java	Wed May 18 12:13:13 2016 -0400
+++ b/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanAttacher.java	Mon May 09 09:39:38 2016 +0200
@@ -91,7 +91,7 @@
             // Port might have changed here if agent rebooted and targed jvm
             // stayed alive
             if (actualPort > 0) {
-                return new BytemanAgentInfo(pid, actualPort, null, vmId);
+                return new BytemanAgentInfo(pid, actualPort, null, vmId, agentId);
             } else {
                 return null;
             }
--- a/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanMetricsReceiver.java	Wed May 18 12:13:13 2016 -0400
+++ b/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanMetricsReceiver.java	Mon May 09 09:39:38 2016 +0200
@@ -47,15 +47,15 @@
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.vm.byteman.agent.internal.typeadapter.BytemanMetricTypeAdapter;
 import com.redhat.thermostat.vm.byteman.common.BytemanMetric;
-import com.redhat.thermostat.vm.byteman.common.VmBytemanMetricDAO;
+import com.redhat.thermostat.vm.byteman.common.VmBytemanDAO;
 
 class BytemanMetricsReceiver implements ThermostatIPCCallbacks {
     
     private static final Logger logger = LoggingUtils.getLogger(BytemanMetricsReceiver.class);
-    private final VmBytemanMetricDAO dao;
+    private final VmBytemanDAO dao;
     private final VmSocketIdentifier socketId;
     
-    BytemanMetricsReceiver(VmBytemanMetricDAO dao, VmSocketIdentifier socketId) {
+    BytemanMetricsReceiver(VmBytemanDAO dao, VmSocketIdentifier socketId) {
         this.dao = dao;
         this.socketId = socketId;
     }
@@ -66,7 +66,7 @@
         logger.fine("Received metrics from byteman for socketId: " + socketId.getName() + ". Metric was: " + jsonMetric);
         List<BytemanMetric> metrics = convertFromJson(jsonMetric);
         for (BytemanMetric metric: metrics) {
-            dao.putMetric(metric);
+            dao.addMetric(metric);
         }
         return null; // No response to send back to the IPC endpoint
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanRequestReceiver.java	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2012-2016 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.vm.byteman.agent.internal;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.jboss.byteman.agent.submit.ScriptText;
+import org.jboss.byteman.agent.submit.Submit;
+
+import com.redhat.thermostat.agent.command.RequestReceiver;
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.Response;
+import com.redhat.thermostat.common.command.Response.ResponseType;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.shared.config.CommonPaths;
+import com.redhat.thermostat.storage.core.VmId;
+import com.redhat.thermostat.storage.core.WriterID;
+import com.redhat.thermostat.vm.byteman.common.VmBytemanDAO;
+import com.redhat.thermostat.vm.byteman.common.VmBytemanStatus;
+import com.redhat.thermostat.vm.byteman.common.command.BytemanRequest;
+import com.redhat.thermostat.vm.byteman.common.command.BytemanRequest.RequestAction;
+
+/**
+ * Receiver class for Byteman action command channel requests.
+ *
+ */
+@Component
+@Service(value = RequestReceiver.class)
+@Property(name = "servicename", value = "com.redhat.thermostat.vm.byteman.agent.internal.BytemanRequestReceiver")
+public class BytemanRequestReceiver implements RequestReceiver {
+    
+    private static final Logger logger = LoggingUtils.getLogger(BytemanRequestReceiver.class);
+    private static final Response ERROR_RESPONSE = new Response(ResponseType.ERROR);
+    private static final Response OK_RESPONSE = new Response(ResponseType.OK);
+    private static final String BYTEMAN_PLUGIN_DIR = System.getProperty("thermostat.plugin", "vm-byteman");
+    private static final String BYTEMAN_PLUGIN_LIBS_DIR = BYTEMAN_PLUGIN_DIR + File.separator + "plugin-libs";
+    private static final String BYTEMAN_HELPER_DIR = BYTEMAN_PLUGIN_LIBS_DIR + File.separator + "thermostat-helper";
+    
+    // package-private for testing
+    static List<String> helperJars;
+    
+    @Reference
+    private CommonPaths paths;
+    
+    @Reference
+    private VmBytemanDAO vmBytemanDao;
+    
+    @Reference
+    private WriterID writerId;
+
+    ////////////////////////////////////////////////
+    // methods used by DS
+    ////////////////////////////////////////////////
+    
+    protected void bindPaths(CommonPaths paths) {
+        File bytemanHelperDir = new File(paths.getSystemPluginRoot(), BYTEMAN_HELPER_DIR);
+        initListOfHelperJars(bytemanHelperDir);
+    }
+    
+    protected void unbindPaths(CommonPaths paths) {
+        synchronized (helperJars) {
+            helperJars = null;
+        }
+    }
+    
+    protected void bindWriterId(WriterID writerId) {
+        this.writerId = writerId;
+    }
+    
+    protected void unbindWriterId(WriterID writerId) {
+        this.writerId = null;
+    }
+    
+    protected void bindVmBytemanDao(VmBytemanDAO dao) {
+        this.vmBytemanDao = dao;
+    }
+    
+    protected void unbindVmBytemanDao(VmBytemanDAO dao) {
+        this.vmBytemanDao = null;
+    }
+    
+    ////////////////////////////////////////////////
+    // end methods used by DS
+    ////////////////////////////////////////////////
+    
+    @Override
+    public Response receive(Request request) {
+        String vmId = request.getParameter(BytemanRequest.VM_ID_PARAM_NAME);
+        String actionStr = request.getParameter(BytemanRequest.ACTION_PARAM_NAME);
+        String portStr = request.getParameter(BytemanRequest.LISTEN_PORT_PARAM_NAME);
+        RequestAction action;
+        try {
+            action = RequestAction.fromIntString(actionStr);
+        } catch (IllegalArgumentException e) {
+            logger.log(Level.WARNING, "Illegal action received", e);
+            return ERROR_RESPONSE;
+        }
+        logger.fine("Processing request for vmId: " + vmId + ", Action: " + action + ", port: " + portStr);
+        int port = Integer.parseInt(portStr);
+        Response response = null;
+        switch(action) {
+        case LOAD_RULES:
+            String rule = request.getParameter(BytemanRequest.RULE_PARAM_NAME);
+            response = loadRules(port, new VmId(vmId), rule);
+            break;
+        case UNLOAD_RULES:
+            response = unloadRules(port, new VmId(vmId));
+            break;
+        default:
+            logger.warning("Unknown action: " + action);
+            return ERROR_RESPONSE;
+        }
+        return response;
+    }
+
+    private Response loadRules(int listenPort, VmId vmId, String bytemanRules) {
+        Submit submit = getSubmit(listenPort);
+        try {
+            // Add jar files for Thermostat byteman helper:
+            String addJarsResult = submit.addJarsToSystemClassloader(helperJars);
+            logger.fine("Added jars for byteman helper with result: " + addJarsResult);
+            List<ScriptText> existingScripts = submit.getAllScripts();
+            if (existingScripts.size() > 0) {
+                String deleteResult = submit.deleteAllRules();
+                logger.fine("Deleted rules with result: " + deleteResult);
+            }
+            List<InputStream> list = Arrays.<InputStream>asList(new ByteArrayInputStream(bytemanRules.getBytes(Charset.forName("UTF-8"))));
+            String addRulesResult = submit.addRulesFromResources(list);
+            logger.info("Added byteman rules for VM with id '" + vmId.get() + "' with result: " + addRulesResult);
+            VmBytemanStatus status = new VmBytemanStatus(writerId.getWriterID());
+            status.setListenPort(listenPort);
+            status.setRule(bytemanRules);
+            status.setTimeStamp(System.currentTimeMillis());
+            status.setVmId(vmId.get());
+            vmBytemanDao.addOrReplaceBytemanStatus(status);
+            return OK_RESPONSE;
+        } catch (Exception e) {
+            logger.log(Level.WARNING, "Failed to submit byteman rules", e);
+            return ERROR_RESPONSE;
+        }
+    }
+
+    private Response unloadRules(int listenPort, VmId vmId) {
+        Submit submit = getSubmit(listenPort);
+        try {
+            List<ScriptText> list = submit.getAllScripts();
+            // Avoid no scripts to delete errors
+            if (!list.isEmpty()) {
+                submit.deleteAllRules();
+                // update the corresponding status in storage
+                VmBytemanStatus newStatus = new VmBytemanStatus(writerId.getWriterID());
+                newStatus.setListenPort(listenPort);
+                newStatus.setRule(null);
+                newStatus.setTimeStamp(System.currentTimeMillis());
+                newStatus.setVmId(vmId.get());
+                vmBytemanDao.addOrReplaceBytemanStatus(newStatus);
+            }
+            return OK_RESPONSE;
+        } catch (Exception e) {
+            logger.log(Level.WARNING, "Failed to delete rules", e);
+            return ERROR_RESPONSE;
+        }
+    }
+
+    protected Submit getSubmit(int listenPort) {
+        return new Submit(null /* localhost */, listenPort);
+    }
+    
+    // package private for testing
+    static synchronized List<String> initListOfHelperJars(File helperDir) {
+        if (helperJars == null) {
+            List<String> jars = new ArrayList<>();
+            for (File f: helperDir.listFiles()) {
+                jars.add(f.getAbsolutePath());
+            }
+            helperJars = jars;
+        }
+        return helperJars;
+    }
+
+}
--- a/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/PeriodicBytemanPollingBackend.java	Wed May 18 12:13:13 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/*
- * Copyright 2012-2016 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.vm.byteman.agent.internal;
-
-import java.util.concurrent.Executors;
-
-import com.redhat.thermostat.agent.VmStatusListenerRegistrar;
-import com.redhat.thermostat.backend.VmPollingBackend;
-import com.redhat.thermostat.common.Version;
-import com.redhat.thermostat.shared.config.CommonPaths;
-
-public class PeriodicBytemanPollingBackend extends VmPollingBackend {
-
-    public PeriodicBytemanPollingBackend(Version version,
-            VmStatusListenerRegistrar registrar, BytemanAgentInfo info, CommonPaths paths) {
-        super("VM Byteman backend (rule updater)",
-              "Updates a byteman rule periodically",
-              "Red Hat Inc.",
-              version, Executors.newSingleThreadScheduledExecutor(), registrar);
-        registerAction(new UpdateRulePollingAction(info, paths));
-    }
-
-    @Override
-    public int getOrderValue() {
-        return VmBytemanBackend.BACKEND_ORDER_VALUE + 1;
-    }
-
-}
--- a/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/UpdateRulePollingAction.java	Wed May 18 12:13:13 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,153 +0,0 @@
-/*
- * Copyright 2012-2016 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.vm.byteman.agent.internal;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.jboss.byteman.agent.submit.ScriptText;
-import org.jboss.byteman.agent.submit.Submit;
-
-import com.redhat.thermostat.backend.VmPollingAction;
-import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.common.utils.StreamUtils;
-import com.redhat.thermostat.shared.config.CommonPaths;
-
-class UpdateRulePollingAction implements VmPollingAction {
-    
-    private static final String BYTEMAN_PLUGIN_DIR = System.getProperty("thermostat.plugin", "vm-byteman");
-    private static final String BYTEMAN_PLUGIN_LIBS_DIR = BYTEMAN_PLUGIN_DIR + File.separator + "plugin-libs";
-    private static final String BYTEMAN_HELPER_DIR = BYTEMAN_PLUGIN_LIBS_DIR + File.separator + "thermostat-helper";
-    private static final String BYTEMAN_SCRIPT_NAME = "bytemanRule.btm";
-    private static final Logger logger = LoggingUtils.getLogger(UpdateRulePollingAction.class);
-    static final List<ScriptText> bmScripts;
-    static {
-        bmScripts = getBmScripts();
-    }
-    
-    private static List<String> helperJars = null;
-    private final BytemanAgentInfo agentInfo;
-    private final Map<String, Boolean> rulesSubmitted;
-    private boolean loggedRuleSubmission = false;
-    
-    UpdateRulePollingAction(BytemanAgentInfo agentInfo, CommonPaths paths) {
-        this.agentInfo = agentInfo;
-        this.rulesSubmitted = new HashMap<>();
-        File bytemanHelperDir = new File(paths.getSystemPluginRoot(), BYTEMAN_HELPER_DIR);
-        initListOfHelperJars(bytemanHelperDir);
-    }
-
-
-    @Override
-    public void run(String vmId, int pid) {
-        // As of yet we submit the fixed rule once and be done with it.
-        // A future enhancement, for example, would be to put the rule
-        // into storage, retrieve it and if changed submit the changed
-        // rules.
-        Boolean value = rulesSubmitted.get(vmId);
-        boolean ruleSubmitted = value == null ? false : value;
-        if (!ruleSubmitted) {
-            ByteArrayOutputStream bout = new ByteArrayOutputStream();
-            PrintStream bytemanOutputWriter = new PrintStream(bout); 
-            String host = agentInfo.getListenHost();
-            int port = agentInfo.getAgentListenPort();
-            Submit bytemanSubmit = new Submit(host, port, bytemanOutputWriter);
-            try {
-                // Add jar files for Thermostat byteman helper:
-                String addJarsResult = bytemanSubmit.addJarsToSystemClassloader(helperJars);
-                logger.fine("Added jars for byteman helper with result: " + addJarsResult);
-                List<ScriptText> existingScripts = bytemanSubmit.getAllScripts();
-                if (existingScripts.size() > 0) {
-                    String deleteResult = bytemanSubmit.deleteScripts(bmScripts);
-                    logger.fine("Deleted rule with result: " + deleteResult);
-                }
-                String deployResult = bytemanSubmit.addScripts(bmScripts);
-                logger.fine("Deployed rule with result: " + deployResult);
-                logger.finest("Byteman output for VM ID '" + vmId + "' was: " + new String(bout.toByteArray()));
-            } catch (Exception e) {
-                logger.log(Level.WARNING, "Failed to submit rules.", e);
-            }
-            rulesSubmitted.put(vmId, true);
-        } else {
-            if (!loggedRuleSubmission) {
-                logger.fine("Skipping byteman rule submission for VM '" + pid + "', since it's been submitted already.");
-                loggedRuleSubmission = true;
-            }
-        }
-    }
-    
-    private static List<ScriptText> getBmScripts() {
-        String scriptText = getScriptText();
-        ScriptText text = new ScriptText(BYTEMAN_SCRIPT_NAME, scriptText);
-        List<ScriptText> bmlist = new ArrayList<>();
-        bmlist.add(text);
-        return bmlist;
-    }
-
-    private static String getScriptText() {
-        try {
-            byte[] bytes = StreamUtils.readAll(UpdateRulePollingAction.class.getResourceAsStream("/" + BYTEMAN_SCRIPT_NAME));
-            String script = new String(bytes, Charset.forName("UTF-8"));
-            return script.trim(); // Byteman does not seem to like empty lines at the end of rule files
-        } catch (IOException e) {
-            logger.log(Level.WARNING, "Failed to read byteman rule file", e);
-            return null;
-        }
-    }
-    
-    // package private for testing
-    static List<String> initListOfHelperJars(File helperDir) {
-        if (helperJars == null) {
-            List<String> jars = new ArrayList<>();
-            for (File f: helperDir.listFiles()) {
-                jars.add(f.getAbsolutePath());
-            }
-            helperJars = jars;
-        }
-        return helperJars;
-    }
-
-}
--- a/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/VmBytemanBackend.java	Wed May 18 12:13:13 2016 -0400
+++ b/vm-byteman/agent/src/main/java/com/redhat/thermostat/vm/byteman/agent/internal/VmBytemanBackend.java	Mon May 09 09:39:38 2016 +0200
@@ -40,7 +40,9 @@
 import java.io.IOException;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -60,7 +62,8 @@
 import com.redhat.thermostat.common.utils.LoggingUtils;
 import com.redhat.thermostat.shared.config.CommonPaths;
 import com.redhat.thermostat.storage.core.WriterID;
-import com.redhat.thermostat.vm.byteman.common.VmBytemanMetricDAO;
+import com.redhat.thermostat.vm.byteman.common.VmBytemanDAO;
+import com.redhat.thermostat.vm.byteman.common.VmBytemanStatus;
 
 @Component
 @Service(value = Backend.class)
@@ -75,6 +78,7 @@
     private static final Logger logger = LoggingUtils.getLogger(VmBytemanBackend.class);
     static final int BACKEND_ORDER_VALUE = ORDER_CODE_GROUP + 3;
     private final Set<String> sockets = Collections.synchronizedSet(new HashSet<String>());
+    private final Map<String, BytemanAgentInfo> agentInfos = new ConcurrentHashMap<>();
     private boolean started;
     private boolean observeNewJVMs;
     private BytemanAttacher attacher;
@@ -84,7 +88,7 @@
     // Services
     
     @Reference
-    private VmBytemanMetricDAO dao;
+    private VmBytemanDAO dao;
     
     @Reference
     private CommonPaths paths;
@@ -113,8 +117,8 @@
     protected void activate(ComponentContext context) {
         BundleContext ctx = context.getBundleContext();
         Bundle thisBundle = ctx.getBundle();
-        this.version = new Version(thisBundle);
-        this.registrar = new VmStatusListenerRegistrar(ctx);
+        version = new Version(thisBundle);
+        registrar = new VmStatusListenerRegistrar(ctx);
     }
     
     protected void deactivate(ComponentContext context) {
@@ -181,26 +185,29 @@
         }
     }
 
-    private void attachBytemanToVm(String vmId, int pid) {
+    private synchronized void attachBytemanToVm(String vmId, int pid) {
         if (!started) {
             logger.fine(getName() +" not active. Thus not attaching Byteman agent to VM '" + pid + "'");
             return;
         }
         logger.fine("Attaching byteman agent to VM '" + pid + "'");
         BytemanAgentInfo info = attacher.attach(vmId, pid, writerId.getWriterID());
+        agentInfos.put(vmId, info);
         if (info == null) {
             logger.warning("Failed to attach byteman agent for VM '" + pid + "'. Skipping rule updater and IPC channel.");
             return;
         }
+        logger.fine("Attached byteman agent to VM '" + pid + "' at port: '" + info.getAgentListenPort());
         logger.fine("Starting IPC socket for byteman helper");
         VmSocketIdentifier socketId = new VmSocketIdentifier(vmId, pid, writerId.getWriterID());
         final ThermostatIPCCallbacks callback = new BytemanMetricsReceiver(dao, socketId);
         startIPCEndpoint(socketId, callback);
-        // Finally activate the rule submitter backend. The metrics will be sent
-        // back to us via the byteman helper and received via BytemanMetricsReceiver.
-        PeriodicBytemanPollingBackend ruleBackend = new PeriodicBytemanPollingBackend(version, registrar, info, paths);
-        logger.fine("Activating byteman rule updater backend for VM '" + pid + "'");
-        ruleBackend.activate();
+        // Add a status record to storage
+        VmBytemanStatus status = new VmBytemanStatus(writerId.getWriterID());
+        status.setListenPort(info.getAgentListenPort());
+        status.setTimeStamp(System.currentTimeMillis());
+        status.setVmId(vmId);
+        dao.addOrReplaceBytemanStatus(status);
     }
 
     private void startIPCEndpoint(VmSocketIdentifier identifier, ThermostatIPCCallbacks callback) {
--- a/vm-byteman/agent/src/main/resources/bytemanRule.btm	Wed May 18 12:13:13 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-RULE count sleep method invocations
-CLASS com.redhat.thermostat.byteman.test.InfiniteLoop
-METHOD sleep
-AT ENTRY
-IF true
-DO
-incrementCounter("sleep-counter")
-ENDRULE
-
-RULE send sleep method invocations to thermostat
-CLASS com.redhat.thermostat.byteman.test.InfiniteLoop
-METHOD sleep
-HELPER org.jboss.byteman.thermostat.helper.ThermostatHelper
-AT ENTRY
-BIND counterValue: int = readCounter("sleep-counter")
-IF counterValue % 10 == 0
-DO
-send("com.redhat.thermostat.byteman.test.InfiniteLoop",  "method sleep() count", counterValue);
-ENDRULE
--- a/vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanMetricsReceiverTest.java	Wed May 18 12:13:13 2016 -0400
+++ b/vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanMetricsReceiverTest.java	Mon May 09 09:39:38 2016 +0200
@@ -49,7 +49,7 @@
 import org.mockito.ArgumentCaptor;
 
 import com.redhat.thermostat.vm.byteman.common.BytemanMetric;
-import com.redhat.thermostat.vm.byteman.common.VmBytemanMetricDAO;
+import com.redhat.thermostat.vm.byteman.common.VmBytemanDAO;
 
 public class BytemanMetricsReceiverTest {
 
@@ -57,13 +57,13 @@
 
     @Test
     public void canSendDataToStorage() {
-        VmBytemanMetricDAO dao = mock(VmBytemanMetricDAO.class);
+        VmBytemanDAO dao = mock(VmBytemanDAO.class);
         ArgumentCaptor<BytemanMetric> metricsCaptor = ArgumentCaptor.forClass(BytemanMetric.class);
         VmSocketIdentifier sockId = new VmSocketIdentifier("vm-id", SOME_PID, "agent-id");
         BytemanMetricsReceiver receiver = new BytemanMetricsReceiver(dao, sockId);
         String jsonString = JsonHelper.buildJsonArray(3);
         receiver.dataReceived(jsonString.getBytes());
-        verify(dao, times(3)).putMetric(metricsCaptor.capture());
+        verify(dao, times(3)).addMetric(metricsCaptor.capture());
         List<BytemanMetric> metrics = metricsCaptor.getAllValues();
         assertEquals("vm-id", metrics.get(0).getVmId());
         assertEquals("agent-id", metrics.get(2).getAgentId());
@@ -71,4 +71,5 @@
         assertEquals("baz0", metrics.get(0).getMarker());
         assertNotNull(metrics.get(2).getData());
     }
+    
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/BytemanRequestReceiverTest.java	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2012-2016 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.vm.byteman.agent.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.net.InetSocketAddress;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.jboss.byteman.agent.submit.ScriptText;
+import org.jboss.byteman.agent.submit.Submit;
+import org.junit.After;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.Response;
+import com.redhat.thermostat.common.command.Request.RequestType;
+import com.redhat.thermostat.common.command.Response.ResponseType;
+import com.redhat.thermostat.shared.config.CommonPaths;
+import com.redhat.thermostat.storage.core.VmId;
+import com.redhat.thermostat.storage.core.WriterID;
+import com.redhat.thermostat.vm.byteman.common.VmBytemanDAO;
+import com.redhat.thermostat.vm.byteman.common.VmBytemanStatus;
+import com.redhat.thermostat.vm.byteman.common.command.BytemanRequest;
+import com.redhat.thermostat.vm.byteman.common.command.BytemanRequest.RequestAction;
+
+public class BytemanRequestReceiverTest {
+    
+    @After
+    public void tearDown() {
+        BytemanRequestReceiver.helperJars = null;
+    }
+
+    @Test
+    public void testLoadRules() throws Exception {
+        Submit submit = mock(Submit.class);
+        doLoadRulesTest(submit);
+        verify(submit, never()).deleteAllRules();
+    }
+    
+    @Test
+    public void testLoadRulesWithRulesExisting() throws Exception {
+        Submit submit = mock(Submit.class);
+        when(submit.getAllScripts()).thenReturn(Arrays.asList(mock(ScriptText.class)));
+        doLoadRulesTest(submit);
+        verify(submit).deleteAllRules();
+    }
+    
+    @Test
+    public void testUnLoadRulesWithNoExistingRules() throws Exception {
+        Submit submit = mock(Submit.class);
+        when(submit.getAllScripts()).thenReturn(Collections.<ScriptText>emptyList());
+        CommonPaths paths = mock(CommonPaths.class);
+        File helperRootFile = getHelperRootFile();
+        when(paths.getSystemPluginRoot()).thenReturn(helperRootFile);
+        BytemanRequestReceiver receiver = createReceiver(submit, null, paths, null);
+        Response response = receiver.receive(BytemanRequest.create(mock(InetSocketAddress.class), new VmId("ignored"), RequestAction.UNLOAD_RULES, -1));
+        assertEquals(ResponseType.OK, response.getType());
+        verify(submit, never()).deleteAllRules();
+    }
+    
+    @Test
+    public void testUnLoadRulesWithExistingRules() throws Exception {
+        Submit submit = mock(Submit.class);
+        when(submit.getAllScripts()).thenReturn(Arrays.asList(mock(ScriptText.class)));
+        CommonPaths paths = mock(CommonPaths.class);
+        File helperRootFile = getHelperRootFile();
+        when(paths.getSystemPluginRoot()).thenReturn(helperRootFile);
+        WriterID writerId = mock(WriterID.class);
+        String someAgentId = "some-agent-id";
+        String someVmId = "some-vm-id";
+        int someListenPort = 3333;
+        VmBytemanDAO bytemanDao = mock(VmBytemanDAO.class);
+        when(writerId.getWriterID()).thenReturn(someAgentId);
+        BytemanRequestReceiver receiver = createReceiver(submit, writerId, paths, bytemanDao);
+        ArgumentCaptor<VmBytemanStatus> statusCaptor = ArgumentCaptor.forClass(VmBytemanStatus.class);
+        Response response = receiver.receive(BytemanRequest.create(mock(InetSocketAddress.class), new VmId(someVmId), RequestAction.UNLOAD_RULES, someListenPort));
+        assertEquals(ResponseType.OK, response.getType());
+        verify(submit).deleteAllRules();
+        verify(bytemanDao).addOrReplaceBytemanStatus(statusCaptor.capture());
+        VmBytemanStatus status = statusCaptor.getValue();
+        assertEquals(someAgentId, status.getAgentId());
+        assertEquals(someVmId, status.getVmId());
+        assertEquals(someListenPort, status.getListenPort());
+        assertNull(status.getRule());
+    }
+    
+    @Test
+    public void testReceiveWithBadAction() {
+        Request badRequest = new Request(RequestType.RESPONSE_EXPECTED, mock(InetSocketAddress.class));
+        badRequest.setParameter(BytemanRequest.ACTION_PARAM_NAME, Integer.toString(-1));
+        BytemanRequestReceiver receiver = new BytemanRequestReceiver();
+        Response response = receiver.receive(badRequest);
+        assertEquals(ResponseType.ERROR, response.getType());
+    }
+
+    @SuppressWarnings("unchecked")
+    private void doLoadRulesTest(Submit submit) throws Exception {
+        File helperRootFile = getHelperRootFile();
+        CommonPaths paths = mock(CommonPaths.class);
+        when(paths.getSystemPluginRoot()).thenReturn(helperRootFile);
+        VmBytemanDAO bytemanDao = mock(VmBytemanDAO.class);
+        WriterID wid = mock(WriterID.class);
+        String writerId = "some-writer-id";
+        String someVmId = "some-id";
+        int listenPort = 333;
+        when(wid.getWriterID()).thenReturn(writerId);
+        BytemanRequestReceiver receiver = createReceiver(submit, wid, paths, bytemanDao);
+        String rule = "some-rule";
+        ArgumentCaptor<VmBytemanStatus> statusCaptor = ArgumentCaptor.forClass(VmBytemanStatus.class);
+        Response response = receiver.receive(BytemanRequest.create(mock(InetSocketAddress.class), new VmId(someVmId), RequestAction.LOAD_RULES, listenPort, rule));
+        verify(bytemanDao).addOrReplaceBytemanStatus(statusCaptor.capture());
+        VmBytemanStatus capturedStatus = statusCaptor.getValue();
+        assertEquals(someVmId, capturedStatus.getVmId());
+        assertEquals(writerId, capturedStatus.getAgentId());
+        assertEquals(rule, capturedStatus.getRule());
+        assertEquals(listenPort, capturedStatus.getListenPort());
+        List<String> expectedList = new ArrayList<>();
+        expectedList.add(helperRootFile.getAbsolutePath() + File.separator + "vm-byteman" + File.separator + "plugin-libs" + File.separator + "thermostat-helper" + File.separator + "not-really-a-jar-file.jar");
+        // Verify thermostat helper jars get added
+        verify(submit).addJarsToSystemClassloader(eq(expectedList));
+        verify(submit).addRulesFromResources(any(List.class));
+        assertEquals(ResponseType.OK, response.getType());
+    }
+
+    private File getHelperRootFile() {
+        URL rootFile = getClass().getResource("/byteman-helper-root");
+        File helperRootFile = new File(rootFile.getFile());
+        return helperRootFile;
+    }
+    
+    private BytemanRequestReceiver createReceiver(final Submit submit, WriterID writerId, CommonPaths paths, VmBytemanDAO dao) {
+        BytemanRequestReceiver receiver = new BytemanRequestReceiver() {
+            @Override 
+            protected Submit getSubmit(int port) {
+                return submit;
+            }
+            
+        };
+        receiver.bindPaths(paths);
+        receiver.bindVmBytemanDao(dao);
+        receiver.bindWriterId(writerId);
+        return receiver;
+    }
+    
+    @Test
+    public void canGetListOfJarsForBytemanHelper() {
+        String parent = "/foo";
+        File file = mock(File.class);
+        File[] mockFiles = new File[7];
+        for (int i = 0; i < 7; i++) {
+            mockFiles[i] = getFileMockWithName(parent, "test-file" + i + ".jar");
+        }
+        when(file.listFiles()).thenReturn(mockFiles);
+        List<String> jars = BytemanRequestReceiver.initListOfHelperJars(file);
+        assertEquals(7, jars.size());
+        for (int i = 0; i < 7; i++) {
+            assertEquals("/foo/test-file" + i + ".jar", jars.get(i));
+        }
+    }
+
+    private File getFileMockWithName(String parent, String name) {
+        File f = mock(File.class);
+        when(f.getAbsolutePath()).thenReturn(parent + "/" + name);
+        return f;
+    }
+}
--- a/vm-byteman/agent/src/test/java/com/redhat/thermostat/vm/byteman/agent/internal/UpdateRulePollingActionTest.java	Wed May 18 12:13:13 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-/*
- * Copyright 2012-2016 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.vm.byteman.agent.internal;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.io.File;
-import java.util.List;
-
-import org.jboss.byteman.agent.submit.ScriptText;
-import org.junit.Test;
-
-public class UpdateRulePollingActionTest {
-
-    @Test
-    public void canGetScriptText() {
-        List<ScriptText> text = UpdateRulePollingAction.bmScripts;
-        assertEquals(1, text.size());
-        ScriptText script = text.get(0);
-        String scriptText = script.getText();
-        assertTrue(scriptText.contains("ThermostatHelper"));
-    }
-    
-    @Test
-    public void canGetListOfJarsForBytemanHelper() {
-        String parent = "/foo";
-        File file = mock(File.class);
-        File[] mockFiles = new File[7];
-        for (int i = 0; i < 7; i++) {
-            mockFiles[i] = getFileMockWithName(parent, "test-file" + i + ".jar");
-        }
-        when(file.listFiles()).thenReturn(mockFiles);
-        List<String> jars = UpdateRulePollingAction.initListOfHelperJars(file);
-        assertEquals(7, jars.size());
-        for (int i = 0; i < 7; i++) {
-            assertEquals("/foo/test-file" + i + ".jar", jars.get(i));
-        }
-    }
-
-    private File getFileMockWithName(String parent, String name) {
-        File f = mock(File.class);
-        when(f.getAbsolutePath()).thenReturn(parent + "/" + name);
-        return f;
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/client-cli/pom.xml	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ Copyright 2012-2016 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>
+    <artifactId>thermostat-vm-byteman</artifactId>
+    <groupId>com.redhat.thermostat</groupId>
+    <version>1.99.10-SNAPSHOT</version>
+  </parent>
+  <artifactId>thermostat-vm-byteman-client-cli</artifactId>
+  <packaging>bundle</packaging>
+  <name>Thermostat VM Byteman plugin: CLI client</name>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-Activator>com.redhat.thermostat.vm.byteman.client.cli.internal.Activator</Bundle-Activator>
+            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
+            <Bundle-SymbolicName>com.redhat.thermostat.vm.byteman.client.cli</Bundle-SymbolicName>
+            <Export-Package />
+            <Private-Package>
+              com.redhat.thermostat.vm.byteman.client.cli.internal
+            </Private-Package>
+            <!-- Do not autogenerate uses clauses in Manifests -->
+            <_nouses>true</_nouses>
+          </instructions>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-scr-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>generate-scr-scrdescriptor</id>
+            <goals>
+              <goal>scr</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.compendium</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-client-cli</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-client-command</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-vm-byteman-common</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-storage-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-common-test</artifactId>
+      <version>${project.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.jboss.byteman</groupId>
+      <artifactId>byteman-submit</artifactId>
+      <version>${byteman.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jboss.byteman</groupId>
+      <artifactId>byteman-install</artifactId>
+      <version>${byteman.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>com.google.code.gson</groupId>
+      <artifactId>gson</artifactId>
+      <version>${gson.version}</version>
+    </dependency>
+    <!-- declarative services -->
+    <dependency>
+      <groupId>org.apache.felix</groupId>
+      <artifactId>org.apache.felix.scr.annotations</artifactId>
+    </dependency>
+  </dependencies>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/client-cli/src/main/java/com/redhat/thermostat/vm/byteman/client/cli/internal/Activator.java	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2012-2016 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.vm.byteman.client.cli.internal;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+import com.redhat.thermostat.client.command.RequestQueue;
+import com.redhat.thermostat.common.MultipleServiceTracker;
+import com.redhat.thermostat.common.MultipleServiceTracker.Action;
+import com.redhat.thermostat.common.cli.Command;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
+import com.redhat.thermostat.vm.byteman.common.VmBytemanDAO;
+
+public class Activator implements BundleActivator {
+
+    private ServiceRegistration<Command> commandReg;
+    private MultipleServiceTracker depsTracker;
+    
+    @Override
+    public void start(BundleContext context) throws Exception {
+        Hashtable<String, String> properties = new Hashtable<>();
+        properties.put(Command.NAME, BytemanControlCommand.COMMAND_NAME);
+        final BytemanControlCommand bytemanCommand = new BytemanControlCommand();
+        commandReg = context.registerService(Command.class, bytemanCommand, properties);
+        Class<?>[] deps = new Class[] {
+                VmInfoDAO.class, 
+                AgentInfoDAO.class,
+                VmBytemanDAO.class,
+                RequestQueue.class,
+        };
+        depsTracker = new MultipleServiceTracker(context, deps, new Action() {
+            
+            @Override
+            public void dependenciesUnavailable() {
+                bytemanCommand.unsetAgentInfoDao();
+                bytemanCommand.unsetRequestQueue();
+                bytemanCommand.unsetVmBytemanDao();
+                bytemanCommand.unsetVmInfoDao();
+            }
+            
+            @Override
+            public void dependenciesAvailable(Map<String, Object> services) {
+                VmInfoDAO vmInfo = (VmInfoDAO)services.get(VmInfoDAO.class.getName());
+                AgentInfoDAO agentInfo = (AgentInfoDAO)services.get(AgentInfoDAO.class.getName());
+                VmBytemanDAO vmBytemanDao = (VmBytemanDAO)services.get(VmBytemanDAO.class.getName());
+                RequestQueue queue = (RequestQueue)services.get(RequestQueue.class.getName());
+                bytemanCommand.setAgentInfoDao(agentInfo);
+                bytemanCommand.setVmInfoDao(vmInfo);
+                bytemanCommand.setVmBytemanDao(vmBytemanDao);
+                bytemanCommand.setRequestQueue(queue);
+            }
+        });
+        depsTracker.open();
+    }
+
+    @Override
+    public void stop(BundleContext context) throws Exception {
+        depsTracker.close();
+        commandReg.unregister();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/client-cli/src/main/java/com/redhat/thermostat/vm/byteman/client/cli/internal/BytemanControlCommand.java	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2012-2016 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.vm.byteman.client.cli.internal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.InetSocketAddress;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import com.redhat.thermostat.client.cli.VmArgument;
+import com.redhat.thermostat.client.command.RequestQueue;
+import com.redhat.thermostat.common.cli.AbstractCommand;
+import com.redhat.thermostat.common.cli.Arguments;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.DependencyServices;
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.model.Range;
+import com.redhat.thermostat.common.utils.StreamUtils;
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.storage.core.AgentId;
+import com.redhat.thermostat.storage.core.VmId;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.VmInfo;
+import com.redhat.thermostat.vm.byteman.common.BytemanMetric;
+import com.redhat.thermostat.vm.byteman.common.VmBytemanDAO;
+import com.redhat.thermostat.vm.byteman.common.VmBytemanStatus;
+import com.redhat.thermostat.vm.byteman.common.command.BytemanRequest;
+import com.redhat.thermostat.vm.byteman.common.command.BytemanRequest.RequestAction;
+
+public class BytemanControlCommand extends AbstractCommand {
+    
+    public static final String COMMAND_NAME = "byteman";
+    
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+    private static final String INJECT_RULE_ACTION = "load";
+    private static final String UNLOAD_RULE_ACTION = "unload";
+    private static final String STATUS_ACTION = "status";
+    private static final String SHOW_ACTION = "show-metrics";
+    private static final String RULES_FILE_OPTION = "rules";
+    private static final String NO_RULES_LOADED = "<no-loaded-rules>";
+    private static final Charset UTF_8_CHARSET = Charset.forName("UTF-8");
+    
+    private final DependencyServices depServices = new DependencyServices();
+
+    @Override
+    public void run(CommandContext ctx) throws CommandException {
+        VmArgument vmArgument = VmArgument.required(ctx.getArguments());
+        VmId vmId = vmArgument.getVmId();
+        
+        VmInfoDAO vmInfoDAO = depServices.getService(VmInfoDAO.class);
+        
+        requireNonNull(vmInfoDAO, translator.localize(LocaleResources.VM_SERVICE_UNAVAILABLE));
+        final VmInfo vmInfo = vmInfoDAO.getVmInfo(vmId);
+        requireNonNull(vmInfo, translator.localize(LocaleResources.VM_SERVICE_UNAVAILABLE));
+
+        AgentInfoDAO agentInfoDAO = depServices.getService(AgentInfoDAO.class);
+        final AgentId agentId = new AgentId(vmInfo.getAgentId());
+        requireNonNull(agentInfoDAO, translator.localize(LocaleResources.AGENT_SERVICE_UNAVAILABLE));
+
+        AgentInformation agentInfo = agentInfoDAO.getAgentInformation(agentId);
+        if (agentInfo == null) {
+            throw new CommandException(translator.localize(LocaleResources.AGENT_NOT_FOUND, agentId.get()));
+        }
+        if (!agentInfo.isAlive()) {
+            throw new CommandException(translator.localize(LocaleResources.AGENT_DEAD, agentId.get()));
+        }
+
+        InetSocketAddress target = agentInfo.getRequestQueueAddress();
+
+        List<String> nonOptionargs = ctx.getArguments().getNonOptionArguments();
+        if (nonOptionargs.size() != 1) {
+            throw new CommandException(translator.localize(LocaleResources.COMMAND_EXPECTED));
+        }
+        VmBytemanDAO bytemanDao = depServices.getService(VmBytemanDAO.class);
+        requireNonNull(bytemanDao, translator.localize(LocaleResources.BYTEMAN_METRICS_SERVICE_UNAVAILABLE));
+
+        String command = nonOptionargs.get(0);
+
+        switch (command) {
+        case INJECT_RULE_ACTION:
+            injectRules(target, vmId, ctx, bytemanDao);
+            break;
+        case UNLOAD_RULE_ACTION:
+            unloadRules(target, vmId, ctx, bytemanDao);
+            break;
+        case STATUS_ACTION:
+            showStatus(ctx, vmInfo, bytemanDao);
+            break;
+        case SHOW_ACTION:
+            showMetrics(ctx, vmId, agentId, bytemanDao);
+            break;
+        default:
+            throw new CommandException(translator.localize(LocaleResources.UNKNOWN_COMMAND, command));
+        }
+    }
+    
+    /* Unloads byteman rules */
+    private void unloadRules(InetSocketAddress target, VmId vmId, CommandContext ctx, VmBytemanDAO bytemanDao) throws CommandException {
+        RequestQueue requestQueue = getRequestQueue();
+        VmBytemanStatus status = getVmBytemanStatus(vmId, bytemanDao);
+        int listenPort = status.getListenPort();
+        Request unloadRequest = BytemanRequest.create(target, vmId, RequestAction.UNLOAD_RULES, listenPort);
+        submitRequest(ctx, requestQueue, unloadRequest);
+    }
+
+    
+    /* Injects byteman rules */
+    private void injectRules(InetSocketAddress target, VmId vmId, CommandContext ctx, VmBytemanDAO bytemanDao) throws CommandException {
+        Arguments args = ctx.getArguments();
+        if (!args.hasArgument(RULES_FILE_OPTION)) {
+            throw new CommandException(translator.localize(LocaleResources.NO_RULE_OPTION));
+        }
+        String ruleFile = args.getArgument(RULES_FILE_OPTION);
+        
+        byte[] rulesBytes;
+        try {
+            rulesBytes = StreamUtils.readAll(new FileInputStream(new File(ruleFile)));
+        } catch (FileNotFoundException e) {
+            throw new CommandException(translator.localize(LocaleResources.RULE_FILE_NOT_FOUND, ruleFile));
+        } catch (IOException e) {
+            throw new CommandException(translator.localize(LocaleResources.ERROR_READING_RULE_FILE, ruleFile));
+        }
+        String rulesContent = new String(rulesBytes, UTF_8_CHARSET);
+        VmBytemanStatus status = getVmBytemanStatus(vmId, bytemanDao);
+        int listenPort = status.getListenPort();
+        RequestQueue requestQueue = getRequestQueue();
+        Request request = BytemanRequest.create(target, vmId, RequestAction.LOAD_RULES, listenPort, rulesContent);
+        submitRequest(ctx, requestQueue, request);
+    }
+
+    /* Show metrics retrieved via byteman rules */
+    private void showMetrics(CommandContext ctx, VmId vmId, AgentId agentId, VmBytemanDAO bytemanDao) throws CommandException {
+        // TODO: Make this query configurable with arguments
+        long now = System.currentTimeMillis();
+        long from = now - TimeUnit.MINUTES.toMillis(5);
+        long to = now;
+        Range<Long> timeRange = new Range<Long>(from, to);
+        List<BytemanMetric> metrics = bytemanDao.findBytemanMetrics(timeRange, vmId, agentId);
+        PrintStream output = ctx.getConsole().getOutput();
+        PrintStream out = ctx.getConsole().getOutput();
+        if (metrics.isEmpty()) {
+            out.println(translator.localize(LocaleResources.NO_METRICS_AVAILABLE, vmId.get()).getContents());
+        } else {
+            for (BytemanMetric m: metrics) {
+                output.println(m.getData());
+            }
+        }
+    }
+
+    /* Show status of loaded byteman rules */
+    private void showStatus(CommandContext ctx, VmInfo vmInfo, VmBytemanDAO bytemanDao) throws CommandException {
+        VmBytemanStatus status = getVmBytemanStatus(new VmId(vmInfo.getVmId()), bytemanDao);
+        PrintStream out = ctx.getConsole().getOutput();
+        String rules = status.getRule();
+        if (rules == null || rules.isEmpty()) {
+            rules = NO_RULES_LOADED;
+        }
+        out.println(translator.localize(LocaleResources.BYTEMAN_STATUS_MSG,
+                                        vmInfo.getMainClass(),
+                                        Integer.toString(status.getListenPort()),
+                                        rules).getContents());
+    }
+
+    private void submitRequest(CommandContext ctx, RequestQueue requestQueue, Request request) {
+        CountDownLatch latch = new CountDownLatch(1);
+        request.addListener(new BytemanRequestResponseListener(latch, ctx));
+        requestQueue.putRequest(request);
+        waitWithTimeout(latch);
+    }
+    
+    // package-private for testing
+    void waitWithTimeout(CountDownLatch latch) {
+        try {
+            // wait for request to finish. The listener does the rest.
+            latch.await(5, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            // ignore
+        }
+    }
+    
+    private RequestQueue getRequestQueue() throws CommandException {
+        RequestQueue requestQueue = depServices.getService(RequestQueue.class);
+        requireNonNull(requestQueue, translator.localize(LocaleResources.QUEUE_SERVICE_UNAVAILABLE));
+        return requestQueue;
+    }
+    
+    private VmBytemanStatus getVmBytemanStatus(VmId vmId, VmBytemanDAO bytemanDao) throws CommandException {
+        VmBytemanStatus status = bytemanDao.findBytemanStatus(vmId);
+        if (status == null) {
+            throw new CommandException(translator.localize(LocaleResources.ERROR_NO_STATUS, vmId.get()));
+        }
+        return status;
+    }
+    
+    void setAgentInfoDao(AgentInfoDAO agentDao) {
+        depServices.addService(AgentInfoDAO.class, agentDao);
+    }
+    
+    void unsetAgentInfoDao() {
+        depServices.removeService(AgentInfoDAO.class);
+    }
+    
+    void setVmInfoDao(VmInfoDAO vmDao) {
+        depServices.addService(VmInfoDAO.class, vmDao);
+    }
+    
+    void unsetVmInfoDao() {
+        depServices.removeService(VmInfoDAO.class);
+    }
+    
+    void setVmBytemanDao(VmBytemanDAO metricDao) {
+        depServices.addService(VmBytemanDAO.class, metricDao);
+    }
+    
+    void unsetVmBytemanDao() {
+        depServices.removeService(VmBytemanDAO.class);
+    }
+    
+    void setRequestQueue(RequestQueue queue) {
+        depServices.addService(RequestQueue.class, queue);
+    }
+    
+    void unsetRequestQueue() {
+        depServices.removeService(RequestQueue.class);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/client-cli/src/main/java/com/redhat/thermostat/vm/byteman/client/cli/internal/BytemanRequestResponseListener.java	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2012-2016 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.vm.byteman.client.cli.internal;
+
+import java.io.PrintStream;
+import java.util.concurrent.CountDownLatch;
+
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.RequestResponseListener;
+import com.redhat.thermostat.common.command.Response;
+import com.redhat.thermostat.shared.locale.Translate;
+
+class BytemanRequestResponseListener implements RequestResponseListener {
+    
+    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+    private final CountDownLatch latch;
+    private final CommandContext ctx;
+    
+    BytemanRequestResponseListener(CountDownLatch latch, CommandContext ctx) {
+        this.latch = latch;
+        this.ctx = ctx;
+    }
+
+    @Override
+    public void fireComplete(Request request, Response response) {
+        boolean isError = false;
+        String errorMsg = "";
+        switch(response.getType()) {
+        case AUTH_FAILED:
+            isError = true;
+            errorMsg = t.localize(LocaleResources.REQUEST_FAILED_AUTH_ISSUE).getContents();
+            break;
+        case ERROR:
+            isError = true;
+            errorMsg = t.localize(LocaleResources.REQUEST_FAILED_UNKNOWN_ISSUE).getContents();
+            break;
+        case OK:
+            break;
+        default:
+            isError = true;
+            errorMsg = t.localize(LocaleResources.ERROR_UNKNOWN_RESPONSE, response.getType().toString()).getContents();
+        }
+        latch.countDown();
+        if (isError) {
+            PrintStream err = ctx.getConsole().getError();
+            err.println(errorMsg);
+        } else {
+            PrintStream out = ctx.getConsole().getOutput();
+            out.println(t.localize(LocaleResources.REQUEST_SUCCESS).getContents());
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/client-cli/src/main/java/com/redhat/thermostat/vm/byteman/client/cli/internal/LocaleResources.java	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012-2016 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.vm.byteman.client.cli.internal;
+
+import com.redhat.thermostat.shared.locale.Translate;
+
+public enum LocaleResources {
+
+    VM_SERVICE_UNAVAILABLE,
+    AGENT_SERVICE_UNAVAILABLE,
+    QUEUE_SERVICE_UNAVAILABLE,
+    BYTEMAN_METRICS_SERVICE_UNAVAILABLE,
+    AGENT_NOT_FOUND,
+    AGENT_DEAD,
+    COMMAND_EXPECTED,
+    UNKNOWN_COMMAND,
+    NO_RULE_OPTION,
+    RULE_FILE_NOT_FOUND,
+    ERROR_READING_RULE_FILE,
+    NO_METRICS_AVAILABLE,
+    ERROR_NO_STATUS,
+    BYTEMAN_STATUS_MSG,
+    REQUEST_FAILED_AUTH_ISSUE,
+    REQUEST_FAILED_UNKNOWN_ISSUE,
+    REQUEST_SUCCESS,
+    ERROR_UNKNOWN_RESPONSE,
+    ;
+
+    static final String RESOURCE_BUNDLE = LocaleResources.class.getPackage().getName() + ".strings";
+
+    public static Translate<LocaleResources> createLocalizer() {
+        return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/client-cli/src/main/resources/com/redhat/thermostat/vm/byteman/client/cli/internal/strings.properties	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,23 @@
+VM_SERVICE_UNAVAILABLE = VM information is not available
+AGENT_SERVICE_UNAVAILABLE = Agent information is not available
+QUEUE_SERVICE_UNAVAILABLE = Sending requests to the agent is not possible
+BYTEMAN_METRICS_SERVICE_UNAVAILABLE = Byteman metrics DAO service is not available
+COMMAND_EXPECTED = A valid subcommand is expected.
+UNKNOWN_COMMAND = Unknown command: {0}
+AGENT_NOT_FOUND = Agent with id {0} not found.
+AGENT_DEAD = Agent with id {0} is not alive.
+NO_RULE_OPTION = No rule option specified.
+RULE_FILE_NOT_FOUND = Specified rules file ''{0}'' not found.
+ERROR_READING_RULE_FILE = Error reading file ''{0}''.
+NO_METRICS_AVAILABLE = No metrics available for VM {0}.
+ERROR_NO_STATUS = No status info available for VM {0}.
+BYTEMAN_STATUS_MSG = Byteman status for VM: {0} \n\
+    Byteman agent listen port: {1} \n\
+    Loaded rules: \n\
+ --------------- \n\
+ {2} \n\
+ ---------------
+REQUEST_FAILED_AUTH_ISSUE = Request failed due to authentication/authorization issues.
+REQUEST_FAILED_UNKNOWN_ISSUE = Request failed for some unknown reason.
+REQUEST_SUCCESS = Request submitted successfully.
+ERROR_UNKNOWN_RESPONSE = Unknown response: {0}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/client-cli/src/test/java/com/redhat/thermostat/vm/byteman/client/cli/internal/BytemanControlCommandTest.java	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,383 @@
+/*
+ * Copyright 2012-2016 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.vm.byteman.client.cli.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.client.command.RequestQueue;
+import com.redhat.thermostat.common.cli.Arguments;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.RequestResponseListener;
+import com.redhat.thermostat.common.internal.test.TestCommandContextFactory;
+import com.redhat.thermostat.common.model.Range;
+import com.redhat.thermostat.common.utils.StreamUtils;
+import com.redhat.thermostat.storage.core.AgentId;
+import com.redhat.thermostat.storage.core.VmId;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.dao.VmInfoDAO;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.VmInfo;
+import com.redhat.thermostat.vm.byteman.common.BytemanMetric;
+import com.redhat.thermostat.vm.byteman.common.VmBytemanDAO;
+import com.redhat.thermostat.vm.byteman.common.VmBytemanStatus;
+import com.redhat.thermostat.vm.byteman.common.command.BytemanRequest;
+import com.redhat.thermostat.vm.byteman.common.command.BytemanRequest.RequestAction;
+
+public class BytemanControlCommandTest {
+
+    private static final String RULE_OPTION = "rules";
+    private static final String STATUS_ACTION = "status";
+    private static final String SHOW_METRICS_ACTION = "show-metrics";
+    private static final String UNLOAD_ACTION = "unload";
+    private static final String LOAD_ACTION = "load";
+    private static final String SOME_VM_ID = "some-vm-id";
+    private static final String SOME_AGENT_ID = "some-agent-id";
+    private static final String EMPTY_STRING = "";
+    private static final InetSocketAddress REQUEST_QUEUE_ADDRESS = mock(InetSocketAddress.class);
+    private BytemanControlCommand command;
+    private TestCommandContextFactory ctxFactory;
+    
+    @Before
+    public void setup() {
+        command = new BytemanControlCommand() {
+            @Override
+            void waitWithTimeout(CountDownLatch latch) {
+                // return immediately
+            }
+        };
+        VmInfoDAO vmInfoDAO = mock(VmInfoDAO.class);
+        VmInfo vmInfo = new VmInfo();
+        vmInfo.setAgentId(SOME_AGENT_ID);
+        vmInfo.setVmId(SOME_VM_ID);
+        when(vmInfoDAO.getVmInfo(any(VmId.class))).thenReturn(vmInfo);
+        command.setVmInfoDao(vmInfoDAO);
+        AgentInfoDAO agentInfoDAO = mock(AgentInfoDAO.class);
+        AgentInformation agentInfo = mock(AgentInformation.class);
+        when(agentInfo.isAlive()).thenReturn(true);
+        when(agentInfo.getRequestQueueAddress()).thenReturn(REQUEST_QUEUE_ADDRESS);
+        when(agentInfoDAO.getAgentInformation(any(AgentId.class))).thenReturn(agentInfo);
+        command.setAgentInfoDao(agentInfoDAO);
+        command.setVmBytemanDao(mock(VmBytemanDAO.class));
+        ctxFactory = new TestCommandContextFactory();
+    }
+    
+    @Test
+    public void testUnknownAction() {
+        String unknownAction = "some-action-that-doesn't-exist";
+        Arguments args = getBasicArgsWithAction(unknownAction);
+        CommandContext ctx = ctxFactory.createContext(args);
+        try {
+            command.run(ctx);
+            fail("Expected failure due to unknown action");
+        } catch (CommandException e) {
+            String msg = e.getMessage();
+            assertTrue(msg.contains(unknownAction));
+            assertTrue(msg.startsWith("Unknown command:"));
+        }
+    }
+    
+    @Test
+    public void testStatusActionNoRule() throws CommandException {
+        String rule = null;
+        String expectedLoadedRuleMsg = "<no-loaded-rules>";
+        basicStatusActionTest(rule, expectedLoadedRuleMsg);
+    }
+    
+    private void basicStatusActionTest(String rule, String expectedRuleMsg) throws CommandException {
+        VmBytemanDAO dao = mock(VmBytemanDAO.class);
+        VmBytemanStatus status = new VmBytemanStatus();
+        int listenPort = 333;
+        status.setVmId(SOME_VM_ID);
+        status.setAgentId(SOME_AGENT_ID);
+        status.setListenPort(listenPort);
+        status.setRule(rule);
+        when(dao.findBytemanStatus(eq(new VmId(SOME_VM_ID)))).thenReturn(status);
+        Arguments args = getBasicArgsWithAction(STATUS_ACTION);
+        CommandContext ctx = ctxFactory.createContext(args);
+        command.unsetVmBytemanDao();
+        command.setVmBytemanDao(dao);
+        command.run(ctx);
+        String stdErr = ctxFactory.getError();
+        String stdOut = ctxFactory.getOutput();
+        assertEquals(EMPTY_STRING, stdErr);
+        assertTrue(stdOut.contains("Byteman status for VM:"));
+        assertTrue(stdOut.contains("Byteman agent listen port: " + listenPort));
+        assertTrue(stdOut.contains("Loaded rules:"));
+        assertTrue(stdOut.contains(expectedRuleMsg));
+    }
+    
+    @Test
+    public void testStatusActionWithRule() throws CommandException {
+        String rule = "some-rule-string\nsome more rule lines\nfurther more";
+        basicStatusActionTest(rule, rule);
+    }
+    
+    @Test
+    public void testStatusActionNoStatus() {
+        Arguments args = getBasicArgsWithAction(STATUS_ACTION);
+        CommandContext ctx = ctxFactory.createContext(args);
+        try {
+            command.run(ctx);
+            fail("Expected command exception due to missing status");
+        } catch (CommandException e) {
+            String msg = e.getMessage();
+            assertEquals("No status info available for VM " + SOME_VM_ID + ".", msg);
+        }
+    }
+    
+    @Test
+    public void testAgentNotAlive() throws CommandException {
+        AgentInfoDAO agentInfoDAO = mock(AgentInfoDAO.class);
+        AgentInformation agentInfo = mock(AgentInformation.class);
+        when(agentInfo.isAlive()).thenReturn(false);
+        when(agentInfoDAO.getAgentInformation(any(AgentId.class))).thenReturn(agentInfo);
+        command.unsetAgentInfoDao();
+        command.setAgentInfoDao(agentInfoDAO);
+        Arguments args = getBasicArgsWithAction("no-matter");
+        CommandContext ctx = ctxFactory.createContext(args);
+        try {
+            command.run(ctx);
+            fail("Command should have thrown exception");
+        } catch (CommandException e) {
+            String msg = e.getMessage();
+            assertEquals("Agent with id " + SOME_AGENT_ID + " is not alive.", msg);
+        }
+    }
+    
+    @Test
+    public void testShowMetricsActionNoMetrics() throws CommandException {
+        String expectedStdOut = "No metrics available for VM " + SOME_VM_ID + ".\n";
+        List<BytemanMetric> returnedList = Collections.emptyList();
+        doShowMetricsTest(returnedList, expectedStdOut);
+    }
+    
+    @Test
+    public void testShowMetricsActionWithMetrics() throws CommandException {
+        String metricData1 = "{ \"foo\": \"bar\" }";
+        String metricData2 = "{ \"foo2\": -300 }";
+        String expectedStdOut = String.format("%s\n%s\n", metricData1, metricData2);
+        BytemanMetric metric1 = new BytemanMetric();
+        metric1.setData(metricData1);
+        BytemanMetric metric2 = new BytemanMetric();
+        metric2.setData(metricData2);
+        List<BytemanMetric> returnedList = Arrays.asList(metric1, metric2);
+        doShowMetricsTest(returnedList, expectedStdOut);
+    }
+    
+    @Test
+    public void testUnloadAction() throws CommandException {
+        VmBytemanDAO dao = mock(VmBytemanDAO.class);
+        VmBytemanStatus status = new VmBytemanStatus();
+        int listenPort = 333;
+        status.setVmId(SOME_VM_ID);
+        status.setAgentId(SOME_AGENT_ID);
+        status.setListenPort(listenPort);
+        status.setRule(null);
+        when(dao.findBytemanStatus(eq(new VmId(SOME_VM_ID)))).thenReturn(status);
+        Arguments args = getBasicArgsWithAction(UNLOAD_ACTION);
+        CommandContext ctx = ctxFactory.createContext(args);
+        command.unsetVmBytemanDao();
+        command.setVmBytemanDao(dao);
+        RequestQueue rQueue = mock(RequestQueue.class);
+        ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
+        command.setRequestQueue(rQueue);
+        command.run(ctx);
+        verify(rQueue).putRequest(requestCaptor.capture());
+        Request submittedRequest = requestCaptor.getValue();
+        assertEquals(1, submittedRequest.getListeners().size());
+        RequestResponseListener respListener = null; 
+        for (RequestResponseListener l: submittedRequest.getListeners()) {
+            respListener = l;
+            break;
+        }
+        assertTrue(respListener instanceof BytemanRequestResponseListener);
+        String rawAction = submittedRequest.getParameter(BytemanRequest.ACTION_PARAM_NAME);
+        RequestAction actualAction = RequestAction.fromIntString(rawAction);
+        assertEquals(RequestAction.UNLOAD_RULES, actualAction);
+        assertEquals(Integer.toString(listenPort), submittedRequest.getParameter(BytemanRequest.LISTEN_PORT_PARAM_NAME));
+        assertEquals(SOME_VM_ID, submittedRequest.getParameter(BytemanRequest.VM_ID_PARAM_NAME));
+        assertNull(submittedRequest.getParameter(BytemanRequest.RULE_PARAM_NAME));
+        assertSame(REQUEST_QUEUE_ADDRESS, submittedRequest.getTarget());
+    }
+    
+    @Test
+    public void testLoadActionNoRuleFile() throws CommandException {
+        VmBytemanDAO dao = mock(VmBytemanDAO.class);
+        VmBytemanStatus status = new VmBytemanStatus();
+        int listenPort = 333;
+        status.setVmId(SOME_VM_ID);
+        status.setAgentId(SOME_AGENT_ID);
+        status.setListenPort(listenPort);
+        status.setRule(null);
+        when(dao.findBytemanStatus(eq(new VmId(SOME_VM_ID)))).thenReturn(status);
+        Arguments args = getBasicArgsWithAction(LOAD_ACTION); // rule file arg missing
+        CommandContext ctx = ctxFactory.createContext(args);
+        command.unsetVmBytemanDao();
+        command.setVmBytemanDao(dao);
+        RequestQueue rQueue = mock(RequestQueue.class);
+        command.setRequestQueue(rQueue);
+        try {
+            command.run(ctx);
+            fail("Expected cmd exception due to missing rule argument");
+        } catch (CommandException e) {
+            String msg = e.getMessage();
+            assertEquals("No rule option specified.", msg);
+        }
+    }
+    
+    @Test
+    public void testLoadActionRuleFileDoesNotExist() throws CommandException {
+        VmBytemanDAO dao = mock(VmBytemanDAO.class);
+        VmBytemanStatus status = new VmBytemanStatus();
+        int listenPort = 333;
+        status.setVmId(SOME_VM_ID);
+        status.setAgentId(SOME_AGENT_ID);
+        status.setListenPort(listenPort);
+        status.setRule(null);
+        when(dao.findBytemanStatus(eq(new VmId(SOME_VM_ID)))).thenReturn(status);
+        Arguments args = getBasicArgsWithAction(LOAD_ACTION);
+        when(args.hasArgument(RULE_OPTION)).thenReturn(true);
+        String file = "i-do-not-exist";
+        when(args.getArgument(RULE_OPTION)).thenReturn(file);
+        CommandContext ctx = ctxFactory.createContext(args);
+        command.unsetVmBytemanDao();
+        command.setVmBytemanDao(dao);
+        RequestQueue rQueue = mock(RequestQueue.class);
+        command.setRequestQueue(rQueue);
+        try {
+            command.run(ctx);
+            fail("Expected cmd exception due to rule file not existing");
+        } catch (CommandException e) {
+            String msg = e.getMessage();
+            assertEquals("Specified rules file '" + file + "' not found.", msg);
+        }
+    }
+    
+    @Test
+    public void testLoadActionSuccess() throws CommandException, FileNotFoundException, IOException {
+        VmBytemanDAO dao = mock(VmBytemanDAO.class);
+        VmBytemanStatus status = new VmBytemanStatus();
+        int listenPort = 333;
+        status.setVmId(SOME_VM_ID);
+        status.setAgentId(SOME_AGENT_ID);
+        status.setListenPort(listenPort);
+        status.setRule(null);
+        when(dao.findBytemanStatus(eq(new VmId(SOME_VM_ID)))).thenReturn(status);
+        Arguments args = getBasicArgsWithAction(LOAD_ACTION);
+        when(args.hasArgument(RULE_OPTION)).thenReturn(true);
+        String file = getClass().getResource("/testRule.btm").getFile();
+        when(args.getArgument(RULE_OPTION)).thenReturn(file);
+        CommandContext ctx = ctxFactory.createContext(args);
+        command.unsetVmBytemanDao();
+        command.setVmBytemanDao(dao);
+        RequestQueue rQueue = mock(RequestQueue.class);
+        command.setRequestQueue(rQueue);
+        ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
+        command.run(ctx);
+        verify(rQueue).putRequest(requestCaptor.capture());
+        Request submittedRequest = requestCaptor.getValue();
+        assertEquals(1, submittedRequest.getListeners().size());
+        RequestResponseListener respListener = null; 
+        for (RequestResponseListener l: submittedRequest.getListeners()) {
+            respListener = l;
+            break;
+        }
+        assertTrue(respListener instanceof BytemanRequestResponseListener);
+        String rawAction = submittedRequest.getParameter(BytemanRequest.ACTION_PARAM_NAME);
+        RequestAction actualAction = RequestAction.fromIntString(rawAction);
+        assertEquals(RequestAction.LOAD_RULES, actualAction);
+        assertEquals(Integer.toString(listenPort), submittedRequest.getParameter(BytemanRequest.LISTEN_PORT_PARAM_NAME));
+        assertEquals(SOME_VM_ID, submittedRequest.getParameter(BytemanRequest.VM_ID_PARAM_NAME));
+        String expectedRule = new String(StreamUtils.readAll(new FileInputStream(new File(file))));
+        assertEquals(expectedRule, submittedRequest.getParameter(BytemanRequest.RULE_PARAM_NAME));
+        assertSame(REQUEST_QUEUE_ADDRESS, submittedRequest.getTarget());
+    }
+    
+    @SuppressWarnings("unchecked")
+    private void doShowMetricsTest(List<BytemanMetric> metricsToReturn, String stdOutExpected) throws CommandException {
+        VmBytemanDAO dao = mock(VmBytemanDAO.class);
+        VmBytemanStatus status = new VmBytemanStatus();
+        int listenPort = 333;
+        status.setVmId(SOME_VM_ID);
+        status.setAgentId(SOME_AGENT_ID);
+        status.setListenPort(listenPort);
+        status.setRule(null);
+        when(dao.findBytemanStatus(eq(new VmId(SOME_VM_ID)))).thenReturn(status);
+        when(dao.findBytemanMetrics(any(Range.class), any(VmId.class), any(AgentId.class))).thenReturn(metricsToReturn);
+        Arguments args = getBasicArgsWithAction(SHOW_METRICS_ACTION);
+        CommandContext ctx = ctxFactory.createContext(args);
+        command.unsetVmBytemanDao();
+        command.setVmBytemanDao(dao);
+        command.run(ctx);
+        String stdErr = ctxFactory.getError();
+        String stdOut = ctxFactory.getOutput();
+        assertEquals(EMPTY_STRING, stdErr);
+        assertEquals(stdOutExpected, stdOut);
+    }
+
+    private Arguments getBasicArgsWithAction(String action) {
+        Arguments args = mock(Arguments.class);
+        when(args.getArgument("vmId")).thenReturn(SOME_VM_ID);
+        when(args.getNonOptionArguments()).thenReturn(Arrays.asList(action));
+        return args;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/client-cli/src/test/java/com/redhat/thermostat/vm/byteman/client/cli/internal/BytemanRequestResponseListenerTest.java	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2012-2016 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.vm.byteman.client.cli.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.concurrent.CountDownLatch;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.Console;
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.Response;
+import com.redhat.thermostat.common.command.Response.ResponseType;
+
+public class BytemanRequestResponseListenerTest {
+
+    private static final String EMPTY_STRING = "";
+    private BytemanRequestResponseListener listener;
+    private ByteArrayOutputStream bout;
+    private ByteArrayOutputStream berr;
+    private CountDownLatch latch;
+    
+    @Before
+    public void setup() {
+        bout = new ByteArrayOutputStream();
+        berr = new ByteArrayOutputStream();
+        CommandContext ctx = mock(CommandContext.class);
+        Console console = mock(Console.class);
+        when(ctx.getConsole()).thenReturn(console);
+        PrintStream outStream = new PrintStream(bout);
+        PrintStream errStream = new PrintStream(berr);
+        when(console.getError()).thenReturn(errStream);
+        when(console.getOutput()).thenReturn(outStream);
+        latch = mock(CountDownLatch.class);
+        listener = new BytemanRequestResponseListener(latch, ctx);
+    }
+    
+    @Test
+    public void testAuthIssue() {
+        listener.fireComplete(mock(Request.class), new Response(ResponseType.AUTH_FAILED));
+        verify(latch).countDown();
+        String stdOut = getOutAsString();
+        String errOut = getErrAsString(); 
+        assertEquals(EMPTY_STRING, stdOut);
+        assertTrue(errOut.contains("authentication"));
+        assertTrue(errOut.contains("issue"));
+    }
+    
+    private String getOutAsString() {
+        return new String(bout.toByteArray());
+    }
+    
+    private String getErrAsString() {
+        return new String(berr.toByteArray());
+    }
+
+    @Test
+    public void testSuccess() {
+        listener.fireComplete(mock(Request.class), new Response(ResponseType.OK));
+        verify(latch).countDown();
+        String stdOut = getOutAsString();
+        String errOut = getErrAsString(); 
+        assertEquals("Request submitted successfully.\n", stdOut);
+        assertEquals(EMPTY_STRING, errOut);
+    }
+    
+    @Test
+    public void testUnknownError() {
+        listener.fireComplete(mock(Request.class), new Response(ResponseType.ERROR));
+        verify(latch).countDown();
+        String stdOut = getOutAsString();
+        String errOut = getErrAsString(); 
+        assertEquals(EMPTY_STRING, stdOut);
+        assertTrue(errOut.contains("unknown"));
+        assertTrue(errOut.contains("reason"));
+    }
+    
+    @Test
+    public void testUnknownType() {
+        listener.fireComplete(mock(Request.class), new Response(ResponseType.NOK));
+        verify(latch).countDown();
+        String stdOut = getOutAsString();
+        String errOut = getErrAsString(); 
+        assertEquals(EMPTY_STRING, stdOut);
+        assertEquals("Unknown response: NOK\n", errOut);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/client-cli/src/test/java/com/redhat/thermostat/vm/byteman/client/cli/internal/LocaleResourcesTest.java	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2012-2016 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.vm.byteman.client.cli.internal;
+
+import com.redhat.thermostat.testutils.AbstractLocaleResourcesTest;
+
+public class LocaleResourcesTest extends AbstractLocaleResourcesTest<LocaleResources> {
+
+    @Override
+    protected Class<LocaleResources> getEnumClass() {
+        return LocaleResources.class;
+    }
+
+    @Override
+    protected String getResourceBundle() {
+        return LocaleResources.RESOURCE_BUNDLE;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/client-cli/src/test/resources/testRule.btm	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,19 @@
+RULE count sleep method invocations
+CLASS com.redhat.thermostat.byteman.test.InfiniteLoop
+METHOD sleep
+AT ENTRY
+IF true
+DO
+incrementCounter("sleep-counter")
+ENDRULE
+
+RULE send sleep method invocations to thermostat
+CLASS com.redhat.thermostat.byteman.test.InfiniteLoop
+METHOD sleep
+HELPER org.jboss.byteman.thermostat.helper.ThermostatHelper
+AT ENTRY
+BIND counterValue: int = readCounter("sleep-counter")
+IF counterValue % 10 == 0
+DO
+send("com.redhat.thermostat.byteman.test.InfiniteLoop",  "method sleep() count", counterValue);
+ENDRULE
\ No newline at end of file
--- a/vm-byteman/common/pom.xml	Wed May 18 12:13:13 2016 -0400
+++ b/vm-byteman/common/pom.xml	Mon May 09 09:39:38 2016 +0200
@@ -58,6 +58,7 @@
             <Bundle-SymbolicName>com.redhat.thermostat.vm.byteman.common</Bundle-SymbolicName>
             <Export-Package>
               com.redhat.thermostat.vm.byteman.common,
+              com.redhat.thermostat.vm.byteman.common.command,
             </Export-Package>
             <Private-Package>
               com.redhat.thermostat.vm.byteman.common.internal
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/VmBytemanDAO.java	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2012-2016 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.vm.byteman.common;
+
+import java.util.List;
+
+import com.redhat.thermostat.common.model.Range;
+import com.redhat.thermostat.storage.core.AgentId;
+import com.redhat.thermostat.storage.core.VmId;
+
+public interface VmBytemanDAO {
+
+    void addMetric(BytemanMetric metric);
+    
+    void addOrReplaceBytemanStatus(VmBytemanStatus status);
+    
+    VmBytemanStatus findBytemanStatus(VmId vmId);
+    
+    List<BytemanMetric> findBytemanMetrics(Range<Long> timeRange, VmId vmId, AgentId agentId);
+}
--- a/vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/VmBytemanMetricDAO.java	Wed May 18 12:13:13 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,60 +0,0 @@
-/*
- * Copyright 2012-2016 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.vm.byteman.common;
-
-import java.util.List;
-
-import com.redhat.thermostat.storage.core.AgentId;
-import com.redhat.thermostat.storage.core.Category;
-import com.redhat.thermostat.storage.core.Key;
-import com.redhat.thermostat.storage.core.VmId;
-
-public interface VmBytemanMetricDAO {
-    
-    public static final Key<String> MARKER = new Key<>("marker");
-    public static final Key<String> DATA = new Key<>("data");
-    
-    public static final Category<BytemanMetric> CATEGORY = new Category<>(
-            "vm-byteman-metrics",
-            BytemanMetric.class,
-            Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP,
-            MARKER, DATA);
-
-    void putMetric(BytemanMetric metric);
-    
-    List<BytemanMetric> findBytemanMetrics(long from, long to, VmId vmId, AgentId agentId);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/VmBytemanStatus.java	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2012-2016 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.vm.byteman.common;
+
+import com.redhat.thermostat.storage.core.Entity;
+import com.redhat.thermostat.storage.core.Persist;
+import com.redhat.thermostat.storage.model.BasePojo;
+import com.redhat.thermostat.storage.model.TimeStampedPojo;
+
+@Entity
+public class VmBytemanStatus extends BasePojo implements TimeStampedPojo {
+
+    private String vmId;
+    private long timestamp;
+    private String rule;
+    private int listenPort;
+    
+    public VmBytemanStatus(String writerId) {
+        super(writerId);
+    }
+    
+    public VmBytemanStatus() {
+        super(null);
+    }
+    
+    @Persist
+    public String getVmId() {
+        return vmId;
+    }
+
+    @Persist
+    public void setVmId(String vmId) {
+        this.vmId = vmId;
+    }
+    
+    @Persist
+    public void setTimeStamp(long timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    @Persist
+    @Override
+    public long getTimeStamp() {
+        return timestamp;
+    }
+    
+    @Persist
+    public void setRule(String rule) {
+        this.rule = rule;
+    }
+    
+    @Persist
+    public String getRule() {
+        return rule;
+    }
+    
+    @Persist
+    public void setListenPort(int port) {
+        this.listenPort = port;
+    }
+    
+    @Persist
+    public int getListenPort() {
+        return listenPort;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/command/BytemanRequest.java	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2012-2016 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.vm.byteman.common.command;
+
+import java.net.InetSocketAddress;
+
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.Request.RequestType;
+import com.redhat.thermostat.storage.core.VmId;
+
+public class BytemanRequest {
+    
+    public static enum RequestAction {
+        LOAD_RULES(0),
+        UNLOAD_RULES(1),
+        ;
+        
+        private int actionId;
+        private RequestAction(int id) {
+            this.actionId = id;
+        }
+        
+        public static RequestAction fromIntString(String id) {
+            int intId;
+            try {
+                intId = Integer.parseInt(id);
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("Unknown action: " + id);
+            }
+            switch(intId) {
+            case(0): return LOAD_RULES;
+            case(1): return UNLOAD_RULES;
+            default:
+                throw new IllegalArgumentException("Unknown action: " + id);
+            }
+        }
+        
+        private String toIntString() {
+            return Integer.toString(actionId);
+        }
+    }
+    
+    private static final String CMD_CHANN_ACTION_NAME = "vm-byteman-instrument";
+    private static final String RECEIVER = "com.redhat.thermostat.vm.byteman.agent.internal.BytemanRequestReceiver";
+    
+    public static final String VM_ID_PARAM_NAME = "vm-id";
+    public static final String ACTION_PARAM_NAME = "byteman-action";
+    public static final String LISTEN_PORT_PARAM_NAME = "listen-port";
+    public static final String RULE_PARAM_NAME = "byteman-rule";
+
+    public static Request create(InetSocketAddress address, VmId vmId, RequestAction action, int listenPort) {
+        Request req = new Request(RequestType.RESPONSE_EXPECTED, address);
+        req.setReceiver(RECEIVER);
+        req.setParameter(Request.ACTION, CMD_CHANN_ACTION_NAME);
+        req.setParameter(VM_ID_PARAM_NAME, vmId.get());
+        req.setParameter(ACTION_PARAM_NAME, action.toIntString());
+        req.setParameter(LISTEN_PORT_PARAM_NAME, Integer.toString(listenPort));
+        return req;
+    }
+    
+    public static Request create(InetSocketAddress address, VmId vmId, RequestAction action, int listenPort, String rule) {
+        Request req = create(address, vmId, action, listenPort);
+        req.setParameter(RULE_PARAM_NAME, rule);
+        return req;
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/internal/VmBytemanDAOImpl.java	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2012-2016 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.vm.byteman.common.internal;
+
+import java.util.List;
+import java.util.logging.Logger;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+
+import com.redhat.thermostat.common.model.Range;
+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.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.VmTimeIntervalPojoListGetter;
+import com.redhat.thermostat.storage.dao.AbstractDao;
+import com.redhat.thermostat.storage.dao.AbstractDaoQuery;
+import com.redhat.thermostat.storage.dao.AbstractDaoStatement;
+import com.redhat.thermostat.vm.byteman.common.BytemanMetric;
+import com.redhat.thermostat.vm.byteman.common.VmBytemanDAO;
+import com.redhat.thermostat.vm.byteman.common.VmBytemanStatus;
+
+@Component
+@Service(value = VmBytemanDAO.class)
+public class VmBytemanDAOImpl extends AbstractDao implements VmBytemanDAO {
+    
+    static final Key<String> MARKER = new Key<>("marker");
+    static final Key<String> DATA = new Key<>("data");
+    static final Key<String> RULE = new Key<>("rule");
+    static final Key<Integer> PORT = new Key<>("listenPort");
+    
+    static final Category<BytemanMetric> VM_BYTEMAN_METRICS_CATEGORY = new Category<>(
+            "vm-byteman-metrics",
+            BytemanMetric.class,
+            Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP,
+            MARKER, DATA);
+    
+    static final Category<VmBytemanStatus> VM_BYTEMAN_STATUS_CATEGORY = new Category<>(
+            "vm-byteman-status",
+            VmBytemanStatus.class,
+            Key.AGENT_ID, Key.VM_ID, Key.TIMESTAMP,
+            RULE, PORT);
+    
+    static final String REPLACE_OR_ADD_STATUS_DESC = "REPLACE " + VM_BYTEMAN_STATUS_CATEGORY.getName() +
+            " SET '" + Key.AGENT_ID.getName() + "' = ?s , " +
+                 "'" + Key.VM_ID.getName() + "' = ?s , " +
+                 "'" + Key.TIMESTAMP.getName() + "' = ?l , " +
+                 "'" + RULE.getName() + "' = ?s , " +
+                 "'" + PORT.getName() + "' = ?i WHERE "
+                         + "'" + Key.VM_ID.getName() + "' = ?s";
+    
+    static final String ADD_METRIC_DESC = "ADD " + VM_BYTEMAN_METRICS_CATEGORY.getName() +
+            " SET '" + Key.AGENT_ID.getName() + "' = ?s , " +
+                 "'" + Key.VM_ID.getName() + "' = ?s , " +
+                 "'" + Key.TIMESTAMP.getName() + "' = ?l , " +
+                 "'" + MARKER.getName() + "' = ?s , " +
+                 "'" + DATA.getName() + "' = ?s";
+    
+    static final String QUERY_VM_BYTEMAN_STATUS = "QUERY " + VM_BYTEMAN_STATUS_CATEGORY.getName() +
+            " WHERE '" + Key.VM_ID.getName() + "' = ?s LIMIT 1";
+    
+    @Reference
+    private Storage storage;
+    private VmTimeIntervalPojoListGetter<BytemanMetric> intervalGetter;
+    
+    public VmBytemanDAOImpl() {
+        // Default constructor for DS
+    }
+    
+    VmBytemanDAOImpl(Storage storage) {
+        bindStorage(storage);
+    }
+    
+    protected void bindStorage(Storage storage) {
+        this.storage = storage;
+    }
+    
+    protected void unbindStorage(Storage storage) {
+        this.storage = null;
+    }
+    
+    @Activate
+    private void activate() {
+        storage.registerCategory(VM_BYTEMAN_METRICS_CATEGORY);
+        storage.registerCategory(VM_BYTEMAN_STATUS_CATEGORY);
+        intervalGetter = new VmTimeIntervalPojoListGetter<>(storage, VM_BYTEMAN_METRICS_CATEGORY);
+    }
+
+    @Override
+    public void addMetric(final BytemanMetric metric) {
+        executeStatement(new AbstractDaoStatement<BytemanMetric>(storage, VM_BYTEMAN_METRICS_CATEGORY, ADD_METRIC_DESC) {
+
+            @Override
+            public PreparedStatement<BytemanMetric> customize(PreparedStatement<BytemanMetric> preparedStatement) {
+                preparedStatement.setString(0, metric.getAgentId());
+                preparedStatement.setString(1, metric.getVmId());
+                preparedStatement.setLong(2, metric.getTimeStamp());
+                preparedStatement.setString(3, metric.getMarker());
+                preparedStatement.setString(4, metric.getData());
+                return preparedStatement;
+            }
+        });
+
+    }
+
+    @Override
+    public List<BytemanMetric> findBytemanMetrics(Range<Long> timeRange,
+            VmId vmId, AgentId agentId) {
+        return intervalGetter.getLatest(agentId, vmId, timeRange.getMin(), timeRange.getMax());
+    }
+
+    @Override
+    protected Logger getLogger() {
+        return LoggingUtils.getLogger(VmBytemanDAOImpl.class);
+    }
+
+    @Override
+    public void addOrReplaceBytemanStatus(final VmBytemanStatus status) {
+        executeStatement(new AbstractDaoStatement<VmBytemanStatus>(storage, VM_BYTEMAN_STATUS_CATEGORY, REPLACE_OR_ADD_STATUS_DESC) {
+
+            @Override
+            public PreparedStatement<VmBytemanStatus> customize(PreparedStatement<VmBytemanStatus> preparedStatement) {
+                preparedStatement.setString(0, status.getAgentId());
+                preparedStatement.setString(1, status.getVmId());
+                preparedStatement.setLong(2, status.getTimeStamp());
+                preparedStatement.setString(3, status.getRule());
+                preparedStatement.setInt(4, status.getListenPort());
+                preparedStatement.setString(5, status.getVmId());
+                return preparedStatement;
+            }
+        });
+        
+    }
+
+    @Override
+    public VmBytemanStatus findBytemanStatus(final VmId vmId) {
+        List<VmBytemanStatus> result = executeQuery(new AbstractDaoQuery<VmBytemanStatus>(storage, VM_BYTEMAN_STATUS_CATEGORY, QUERY_VM_BYTEMAN_STATUS) {
+
+            @Override
+            public PreparedStatement<VmBytemanStatus> customize(PreparedStatement<VmBytemanStatus> preparedStatement) {
+                preparedStatement.setString(0, vmId.get());
+                return preparedStatement;
+            }
+        }).asList();
+        if (result.isEmpty()) {
+            return null;
+        }
+        return result.get(0);
+    }
+
+}
--- a/vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/internal/VmBytemanMetricDAOCategoryRegistration.java	Wed May 18 12:13:13 2016 -0400
+++ b/vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/internal/VmBytemanMetricDAOCategoryRegistration.java	Mon May 09 09:39:38 2016 +0200
@@ -40,7 +40,6 @@
 import java.util.Set;
 
 import com.redhat.thermostat.storage.core.auth.CategoryRegistration;
-import com.redhat.thermostat.vm.byteman.common.VmBytemanMetricDAO;
 
 public class VmBytemanMetricDAOCategoryRegistration
         implements CategoryRegistration {
@@ -48,7 +47,8 @@
     @Override
     public Set<String> getCategoryNames() {
         Set<String> categories = new HashSet<>(1);
-        categories.add(VmBytemanMetricDAO.CATEGORY.getName());
+        categories.add(VmBytemanDAOImpl.VM_BYTEMAN_METRICS_CATEGORY.getName());
+        categories.add(VmBytemanDAOImpl.VM_BYTEMAN_STATUS_CATEGORY.getName());
         return categories;
     }
 
--- a/vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/internal/VmBytemanMetricDAOImpl.java	Wed May 18 12:13:13 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,124 +0,0 @@
-/*
- * Copyright 2012-2016 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.vm.byteman.common.internal;
-
-import java.util.List;
-import java.util.logging.Logger;
-
-import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.storage.core.AgentId;
-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.VmTimeIntervalPojoListGetter;
-import com.redhat.thermostat.storage.dao.AbstractDao;
-import com.redhat.thermostat.storage.dao.AbstractDaoStatement;
-import com.redhat.thermostat.vm.byteman.common.BytemanMetric;
-import com.redhat.thermostat.vm.byteman.common.VmBytemanMetricDAO;
-
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-
-@Component
-@Service(value = VmBytemanMetricDAO.class)
-public class VmBytemanMetricDAOImpl extends AbstractDao implements VmBytemanMetricDAO {
-    
-    static final String STATEMENT_DESCRIPTOR = "ADD " + CATEGORY.getName() +
-            " SET '" + Key.AGENT_ID.getName() + "' = ?s , " +
-                 "'" + Key.VM_ID.getName() + "' = ?s , " +
-                 "'" + Key.TIMESTAMP.getName() + "' = ?l , " +
-                 "'" + MARKER.getName() + "' = ?s , " +
-                 "'" + DATA.getName() + "' = ?s";
-    
-    @Reference
-    private Storage storage;
-    private VmTimeIntervalPojoListGetter<BytemanMetric> intervalGetter;
-    
-    public VmBytemanMetricDAOImpl() {
-        // Default constructor for DS
-    }
-    
-    VmBytemanMetricDAOImpl(Storage storage) {
-        bindStorage(storage);
-    }
-    
-    protected void bindStorage(Storage storage) {
-        this.storage = storage;
-    }
-    
-    protected void unbindStorage(Storage storage) {
-        this.storage = null;
-    }
-    
-    @Activate
-    private void activate() {
-        storage.registerCategory(CATEGORY);
-        intervalGetter = new VmTimeIntervalPojoListGetter<>(storage, CATEGORY);
-    }
-
-    @Override
-    public void putMetric(final BytemanMetric metric) {
-        executeStatement(new AbstractDaoStatement<BytemanMetric>(storage, CATEGORY, STATEMENT_DESCRIPTOR) {
-
-            @Override
-            public PreparedStatement<BytemanMetric> customize(PreparedStatement<BytemanMetric> preparedStatement) {
-                preparedStatement.setString(0, metric.getAgentId());
-                preparedStatement.setString(1, metric.getVmId());
-                preparedStatement.setLong(2, metric.getTimeStamp());
-                preparedStatement.setString(3, metric.getMarker());
-                preparedStatement.setString(4, metric.getData());
-                return preparedStatement;
-            }
-        });
-
-    }
-
-    @Override
-    public List<BytemanMetric> findBytemanMetrics(long from, long to,
-            VmId vmId, AgentId agentId) {
-        return intervalGetter.getLatest(agentId, vmId, from, to);
-    }
-
-    @Override
-    protected Logger getLogger() {
-        return LoggingUtils.getLogger(VmBytemanMetricDAOImpl.class);
-    }
-
-}
--- a/vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/internal/VmBytemanMetricDAOImplStatementDescriptorRegistration.java	Wed May 18 12:13:13 2016 -0400
+++ b/vm-byteman/common/src/main/java/com/redhat/thermostat/vm/byteman/common/internal/VmBytemanMetricDAOImplStatementDescriptorRegistration.java	Mon May 09 09:39:38 2016 +0200
@@ -41,20 +41,21 @@
 
 import com.redhat.thermostat.storage.core.VmTimeIntervalPojoListGetter;
 import com.redhat.thermostat.storage.core.auth.StatementDescriptorRegistration;
-import com.redhat.thermostat.vm.byteman.common.VmBytemanMetricDAO;
 
 public class VmBytemanMetricDAOImplStatementDescriptorRegistration
         implements StatementDescriptorRegistration {
     
     static final String intervalDescriptor = String.format(
             VmTimeIntervalPojoListGetter.VM_INTERVAL_QUERY_FORMAT,
-            VmBytemanMetricDAO.CATEGORY.getName());
+            VmBytemanDAOImpl.VM_BYTEMAN_METRICS_CATEGORY.getName());
 
     @Override
     public Set<String> getStatementDescriptors() {
         Set<String> descs = new HashSet<>(7);
-        descs.add(VmBytemanMetricDAOImpl.STATEMENT_DESCRIPTOR);
+        descs.add(VmBytemanDAOImpl.ADD_METRIC_DESC);
         descs.add(intervalDescriptor);
+        descs.add(VmBytemanDAOImpl.REPLACE_OR_ADD_STATUS_DESC);
+        descs.add(VmBytemanDAOImpl.QUERY_VM_BYTEMAN_STATUS);
         return descs;
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vm-byteman/common/src/test/java/com/redhat/thermostat/vm/byteman/common/command/BytemanRequestTest.java	Mon May 09 09:39:38 2016 +0200
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2012-2016 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.vm.byteman.common.command;
+
+import static org.junit.Assert.assertEquals;
+
+import java.net.InetSocketAddress;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.common.command.Request;
+import com.redhat.thermostat.common.command.Request.RequestType;
+import com.redhat.thermostat.storage.core.VmId;
+import com.redhat.thermostat.vm.byteman.common.command.BytemanRequest.RequestAction;
+
+public class BytemanRequestTest {
+
+    @Test
+    public void testCreateRequestWithRule() {
+        Request req = BytemanRequest.create(new InetSocketAddress("127.0.0.1", 12000), new VmId("vmId"), RequestAction.LOAD_RULES, 3, "foo-bar");
+        assertEquals(RequestType.RESPONSE_EXPECTED, req.getType());
+    }
+}
--- a/vm-byteman/distribution/pom.xml	Wed May 18 12:13:13 2016 -0400
+++ b/vm-byteman/distribution/pom.xml	Mon May 09 09:39:38 2016 +0200
@@ -98,6 +98,11 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
+      <groupId>com.redhat.thermostat</groupId>
+      <artifactId>thermostat-vm-byteman-client-cli</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
       <groupId>org.jboss.byteman</groupId>
       <artifactId>byteman-submit</artifactId>
       <version>${byteman.version}</version>
--- a/vm-byteman/distribution/thermostat-plugin.xml	Wed May 18 12:13:13 2016 -0400
+++ b/vm-byteman/distribution/thermostat-plugin.xml	Mon May 09 09:39:38 2016 +0200
@@ -37,6 +37,67 @@
 
 -->
 <plugin xmlns="http://icedtea.classpath.org/thermostat/plugins/v1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://icedtea.classpath.org/thermostat/plugins/v1.0 http://icedtea.classpath.org/thermostat/docs/1.0/thermostat-plugin.xsd">
+    <commands>
+        <command>
+            <name>byteman</name>
+            <summary>instrument a target VM with Byteman rules</summary>
+            <description>
+            Instrument a target VM with Byteman rules.
+            This command takes a subcommand that describes what to do.
+            'load' loads rule as specified by the command option -r 
+            into a VM. 'unload' unloads all loaded Byteman rules from a VM.
+            'status' shows the currently loaded rules for a VM. 'show-metrics'
+            displays raw metrics gathered via an injected Byteman rule.
+            </description>
+            <arguments>
+            	<argument>action</argument>
+            </arguments>
+            <options>
+              <option>
+                <long>vmId</long>
+                <short>v</short>
+                <argument>vm</argument>
+                <required>true</required>
+                <description>the ID of the VM to instrument</description>
+              </option>
+              <option>
+                <long>rules</long>
+                <short>r</short>
+                <argument>file</argument>
+                <required>false</required>
+                <description>a file with Byteman rules to load into a VM</description>
+              </option>
+            </options>
+            <environments>
+                <environment>cli</environment>
+                <environment>shell</environment>
+            </environments>
+            <bundles>
+                <bundle><symbolic-name>com.redhat.thermostat.client.cli</symbolic-name><version>${project.version}</version></bundle>
+                <bundle><symbolic-name>io.netty.common</symbolic-name><version>${netty.version}</version></bundle>
+                <bundle><symbolic-name>io.netty.buffer</symbolic-name><version>${netty.version}</version></bundle>
+                <bundle><symbolic-name>io.netty.codec</symbolic-name><version>${netty.version}</version></bundle>
+                <bundle><symbolic-name>io.netty.transport</symbolic-name><version>${netty.version}</version></bundle>
+                <bundle><symbolic-name>io.netty.handler</symbolic-name><version>${netty.version}</version></bundle>
+                <bundle><symbolic-name>com.redhat.thermostat.client.command</symbolic-name><version>${project.version}</version></bundle>
+                <bundle><symbolic-name>com.redhat.thermostat.common.command</symbolic-name><version>${project.version}</version></bundle>
+                <bundle><symbolic-name>com.redhat.thermostat.vm.byteman.common</symbolic-name><version>${project.version}</version></bundle>
+                <bundle><symbolic-name>com.redhat.thermostat.vm.byteman.client.cli</symbolic-name><version>${project.version}</version></bundle>
+                <bundle><symbolic-name>com.google.gson</symbolic-name><version>${gson.version}</version></bundle>
+                <bundle><symbolic-name>com.redhat.thermostat.storage.mongodb</symbolic-name><version>${project.version}</version></bundle>
+                <bundle><symbolic-name>com.redhat.thermostat.web.common</symbolic-name><version>${project.version}</version></bundle>
+                <bundle><symbolic-name>com.redhat.thermostat.web.client</symbolic-name><version>${project.version}</version></bundle>
+                <bundle><symbolic-name>org.mongodb.mongo-java-driver</symbolic-name><version>${mongo-driver.osgi-version}</version></bundle>
+                <bundle><symbolic-name>org.apache.commons.beanutils</symbolic-name><version>${commons-beanutils.version}</version></bundle>
+                <bundle><symbolic-name>org.apache.commons.codec</symbolic-name><version>${commons-codec.osgi-version}</version></bundle>
+                <bundle><symbolic-name>org.apache.commons.collections</symbolic-name><version>${commons-collections.version}</version></bundle>
+                <bundle><symbolic-name>org.apache.commons.logging</symbolic-name><version>${commons-logging.version}</version></bundle>
+                <bundle><symbolic-name>org.apache.httpcomponents.httpcore</symbolic-name><version>${httpcomponents.core.version}</version></bundle>
+                <bundle><symbolic-name>org.apache.httpcomponents.httpclient</symbolic-name><version>${httpcomponents.client.version}</version></bundle>
+                <bundle><symbolic-name>${osgi.compendium.bundle.symbolic-name}</symbolic-name><version>${osgi.compendium.osgi-version}</version></bundle>
+            </bundles>
+        </command>
+    </commands>
     <extensions>
         <extension>
             <name>agent</name>
--- a/vm-byteman/pom.xml	Wed May 18 12:13:13 2016 -0400
+++ b/vm-byteman/pom.xml	Mon May 09 09:39:38 2016 +0200
@@ -55,6 +55,7 @@
     <module>byteman-helper-distro</module>
     <module>agent</module>
     <module>common</module>
+    <module>client-cli</module>
     <module>distribution</module>
   </modules>