changeset 2448:f3616b75be2c

Add facilities for implementing cli commands and CompleterServices at once Implement this dual-functionality for BytemanControlCommand and StoragePopulatorCommand as well. Reviewed-by: neugens Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-July/020212.html Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-August/020597.html Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2016-September/020814.html
author Andrew Azores <aazores@redhat.com>
date Wed, 14 Sep 2016 11:05:00 -0400
parents b620db651e19
children bce16ab0a34a
files common/core/src/main/java/com/redhat/thermostat/common/cli/AbstractCompleterCommand.java common/core/src/main/java/com/redhat/thermostat/common/cli/LocaleResources.java common/core/src/main/resources/com/redhat/thermostat/common/cli/locale/strings.properties common/core/src/test/java/com/redhat/thermostat/common/cli/AbstractCompleterCommandTest.java dev/storage-populator/command/pom.xml dev/storage-populator/command/src/main/java/com/redhat/thermostat/storage/populator/StoragePopulatorCommand.java dev/storage-populator/command/src/main/java/com/redhat/thermostat/storage/populator/internal/Activator.java dev/storage-populator/command/src/main/java/com/redhat/thermostat/storage/populator/internal/StoragePopulatorCompleterService.java dev/storage-populator/command/src/test/java/com/redhat/thermostat/storage/populator/StoragePopulatorCommandTest.java dev/storage-populator/command/src/test/java/com/redhat/thermostat/storage/populator/internal/ActivatorTest.java dev/storage-populator/command/src/test/java/com/redhat/thermostat/storage/populator/internal/StoragePopulatorCompleterServiceTest.java vm-byteman/client-cli/pom.xml vm-byteman/client-cli/src/main/java/com/redhat/thermostat/vm/byteman/client/cli/BytemanControlCommand.java 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/BytemanCompleterService.java vm-byteman/client-cli/src/main/java/com/redhat/thermostat/vm/byteman/client/cli/internal/BytemanControlCommand.java vm-byteman/client-cli/src/test/java/com/redhat/thermostat/vm/byteman/client/cli/BytemanControlCommandTest.java vm-byteman/client-cli/src/test/java/com/redhat/thermostat/vm/byteman/client/cli/internal/ActivatorTest.java vm-byteman/client-cli/src/test/java/com/redhat/thermostat/vm/byteman/client/cli/internal/BytemanCompleterServiceTest.java vm-byteman/client-cli/src/test/java/com/redhat/thermostat/vm/byteman/client/cli/internal/BytemanControlCommandTest.java
diffstat 20 files changed, 1168 insertions(+), 1524 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/main/java/com/redhat/thermostat/common/cli/AbstractCompleterCommand.java	Wed Sep 14 11:05:00 2016 -0400
@@ -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.common.cli;
+
+import com.redhat.thermostat.shared.locale.Translate;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * An abstract class to help with implementing commands which also provide tab completions for arguments within
+ * Thermostat shell. This is most useful in cases where an argument and its completions are unique to a single
+ * command; if your plugin provides multiple commands which have shared options and shared arguments/completions,
+ * more flexibility in the implementation can be had by implementing a {@link CompleterService} directly and separately
+ * from the Command implementations.
+ *
+ * This class is meant for use with OSGi Declarative Services. Subclasses should be annotated with @Component, @Service,
+ * and @Property(name = Command.NAME, value = "command-name-here") at minimum.
+ */
+public abstract class AbstractCompleterCommand extends AbstractCommand implements CompleterService {
+
+    private static final Translate<LocaleResources> t = LocaleResources.createLocalizer();
+
+    private String commandName = null;
+
+    /**
+     * If implementation subclasses override this method, they <b>must</b> call through to this super implementation as
+     * well, or else tab completions will fail to be registered correctly.
+     */
+    @Activate
+    protected void activate(ComponentContext context) {
+        commandName = validateCommandName((String) context.getProperties().get(Command.NAME));
+    }
+
+    /**
+     * The set of command names for which tab completions are provided.
+     *
+     * Implementations of this class only provide completions for a single command name value.
+     * This value is expected to be provided by an @Property annotation defining a {@link Command.NAME} property.
+     * @return a singleton set containing the command name
+     */
+    @Override
+    public final Set<String> getCommands() {
+        return Collections.singleton(validateCommandName(commandName));
+    }
+
+    private String validateCommandName(String name) {
+        return Objects.requireNonNull(name, t.localize(LocaleResources.MISSING_COMMAND_NAME, getClass().getName()).getContents());
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/main/java/com/redhat/thermostat/common/cli/LocaleResources.java	Wed Sep 14 11:05:00 2016 -0400
@@ -0,0 +1,51 @@
+/*
+ * 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.common.cli;
+
+import com.redhat.thermostat.shared.locale.Translate;
+
+public enum LocaleResources {
+    MISSING_COMMAND_NAME,
+    ;
+
+    public static final String RESOURCE_BUNDLE =
+            "com.redhat.thermostat.common.cli.locale.strings";
+
+    public static Translate<LocaleResources> createLocalizer() {
+        return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/main/resources/com/redhat/thermostat/common/cli/locale/strings.properties	Wed Sep 14 11:05:00 2016 -0400
@@ -0,0 +1,1 @@
+MISSING_COMMAND_NAME=The implementation class {0} does not define an OSGi property for COMMAND_NAME, which is required.
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/test/java/com/redhat/thermostat/common/cli/AbstractCompleterCommandTest.java	Wed Sep 14 11:05:00 2016 -0400
@@ -0,0 +1,117 @@
+/*
+ * 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.common.cli;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.osgi.service.component.ComponentContext;
+
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class AbstractCompleterCommandTest {
+
+    public static final String COMMAND_NAME = "StubCompleterCommand";
+
+    @Rule
+    public ExpectedException thrown = ExpectedException.none();
+
+    private AbstractCompleterCommand command;
+    private ComponentContext componentContext;
+
+    @Before
+    public void setup() {
+        command = new StubCompleterCommand();
+        componentContext = mock(ComponentContext.class);
+        Dictionary dict = mock(Dictionary.class);
+        when(dict.get(Command.NAME)).thenReturn(COMMAND_NAME);
+        when(componentContext.getProperties()).thenReturn(dict);
+    }
+
+    @Test
+    public void testActivationSetsCommandName() {
+        command.activate(componentContext);
+        assertThat(command.getCommands(), is(equalTo(Collections.singleton(COMMAND_NAME))));
+    }
+
+    @Test
+    public void testThrowsExceptionIfGetCommandsCalledBeforeActivation() {
+        thrown.expect(NullPointerException.class);
+        thrown.expectMessage("The implementation class " +
+                "com.redhat.thermostat.common.cli.AbstractCompleterCommandTest$StubCompleterCommand does not define an" +
+                " OSGi property for COMMAND_NAME, which is required.");
+        command.getCommands();
+    }
+
+    @Test
+    public void testActivationFailsIfCommandNamePropertyNotSet() {
+        Dictionary dict = mock(Dictionary.class);
+        when(componentContext.getProperties()).thenReturn(dict);
+        thrown.expect(NullPointerException.class);
+        thrown.expectMessage("The implementation class " +
+                "com.redhat.thermostat.common.cli.AbstractCompleterCommandTest$StubCompleterCommand does not define an" +
+                " OSGi property for COMMAND_NAME, which is required.");
+        command.activate(componentContext);
+    }
+
+    private static class StubCompleterCommand extends AbstractCompleterCommand {
+        @Override
+        public void run(CommandContext ctx) throws CommandException {
+            // no-op
+        }
+
+        @Override
+        public Map<CliCommandOption, ? extends TabCompleter> getOptionCompleters() {
+            return Collections.emptyMap();
+        }
+
+        @Override
+        public Map<String, Map<CliCommandOption, ? extends TabCompleter>> getSubcommandCompleters() {
+            return Collections.emptyMap();
+        }
+    }
+
+}
--- a/dev/storage-populator/command/pom.xml	Tue Sep 13 11:35:25 2016 -0400
+++ b/dev/storage-populator/command/pom.xml	Wed Sep 14 11:05:00 2016 -0400
@@ -61,7 +61,6 @@
           <instructions>
             <Bundle-SymbolicName>com.redhat.thermostat.storage.populator</Bundle-SymbolicName>
             <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
-            <Bundle-Activator>com.redhat.thermostat.storage.populator.internal.Activator</Bundle-Activator>
             <Export-Package>
                 com.redhat.thermostat.storage.populator,
             </Export-Package>
--- a/dev/storage-populator/command/src/main/java/com/redhat/thermostat/storage/populator/StoragePopulatorCommand.java	Tue Sep 13 11:35:25 2016 -0400
+++ b/dev/storage-populator/command/src/main/java/com/redhat/thermostat/storage/populator/StoragePopulatorCommand.java	Wed Sep 14 11:05:00 2016 -0400
@@ -39,23 +39,27 @@
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.logging.Logger;
 
-import com.redhat.thermostat.common.cli.AbstractCommand;
+import com.redhat.thermostat.common.cli.AbstractCompleterCommand;
 import com.redhat.thermostat.common.cli.Arguments;
+import com.redhat.thermostat.common.cli.CliCommandOption;
+import com.redhat.thermostat.common.cli.Command;
 import com.redhat.thermostat.common.cli.CommandContext;
 import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.CompletionFinderTabCompleter;
 import com.redhat.thermostat.common.cli.Console;
 import com.redhat.thermostat.common.cli.DependencyServices;
-import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.common.cli.TabCompleter;
 import com.redhat.thermostat.shared.config.CommonPaths;
 import com.redhat.thermostat.shared.locale.Translate;
 import com.redhat.thermostat.storage.dao.AgentInfoDAO;
 import com.redhat.thermostat.storage.dao.HostInfoDAO;
 import com.redhat.thermostat.storage.dao.NetworkInterfaceInfoDAO;
 import com.redhat.thermostat.storage.dao.VmInfoDAO;
+import com.redhat.thermostat.storage.populator.internal.StoragePopulatorConfigFinder;
 import com.redhat.thermostat.storage.populator.internal.config.ConfigItem;
 import com.redhat.thermostat.storage.populator.internal.config.PopulationConfig;
 import com.redhat.thermostat.storage.populator.internal.dependencies.SharedState;
@@ -67,62 +71,105 @@
 import com.redhat.thermostat.storage.populator.internal.VmInfoPopulator;
 import com.redhat.thermostat.storage.populator.internal.LocaleResources;
 import com.redhat.thermostat.thread.dao.ThreadDao;
+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.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.Service;
 
-public class StoragePopulatorCommand extends AbstractCommand {
-
-    public static final String COMMAND_NAME = "storage-populator";
+@Component
+@Service // Command and CompleterService
+@Property(name = Command.NAME, value = "storage-populator")
+public class StoragePopulatorCommand extends AbstractCompleterCommand {
 
     private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
-    private static final Logger logger = LoggingUtils.getLogger(StoragePopulatorCommand.class);
 
-    private static final String CONFIG_FILE_NAME = "config";
+    static final CliCommandOption CONFIG_OPTION = new CliCommandOption("c", "config", true, "the json config file to use", true);
 
     private final Map<String, CollectionPopulator> populators = new HashMap<>();
     private final DependencyServices dependencyServices = new DependencyServices();
 
+    @Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL_UNARY)
     private CommonPaths paths;
+
+    @Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL_UNARY)
     private HostInfoDAO hostInfoDAO;
+
+    @Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL_UNARY)
     private AgentInfoDAO agentInfoDAO;
+
+    @Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL_UNARY)
     private VmInfoDAO vmInfoDAO;
+
+    @Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL_UNARY)
     private NetworkInterfaceInfoDAO networkInfoDAO;
-    private ThreadDao threadDao;
+
+    @Reference(policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL_UNARY)
+    private ThreadDao threadDAO;
 
     private Console console;
 
-    public void setPaths(CommonPaths paths) {
+    public void bindPaths(CommonPaths paths) {
         dependencyServices.addService(CommonPaths.class, paths);
     }
 
-    public void setHostInfoDAO(HostInfoDAO dao) {
+    public void unbindPaths(CommonPaths paths) {
+        dependencyServices.removeService(CommonPaths.class);
+    }
+
+    public void bindHostInfoDAO(HostInfoDAO dao) {
         dependencyServices.addService(HostInfoDAO.class, dao);
     }
 
-    public void setAgentInfoDAO(AgentInfoDAO dao) {
+    public void unbindHostInfoDAO(HostInfoDAO dao) {
+        dependencyServices.removeService(HostInfoDAO.class);
+    }
+
+    public void bindAgentInfoDAO(AgentInfoDAO dao) {
         dependencyServices.addService(AgentInfoDAO.class, dao);
     }
 
-    public void setVmInfoDAO(VmInfoDAO dao) {
+    public void unbindAgentInfoDAO(AgentInfoDAO dao) {
+        dependencyServices.removeService(AgentInfoDAO.class);
+    }
+
+    public void bindVmInfoDAO(VmInfoDAO dao) {
         dependencyServices.addService(VmInfoDAO.class, dao);
     }
 
-    public void setNetworkInfoDAO(NetworkInterfaceInfoDAO dao) {
+    public void unbindVmInfoDAO(VmInfoDAO dao) {
+        dependencyServices.removeService(VmInfoDAO.class);
+    }
+
+    public void bindNetworkInfoDAO(NetworkInterfaceInfoDAO dao) {
         dependencyServices.addService(NetworkInterfaceInfoDAO.class, dao);
     }
 
-    public void setThreadDao(ThreadDao dao) {
+    public void unbindNetworkInfoDAO(NetworkInterfaceInfoDAO dao) {
+        dependencyServices.removeService(NetworkInterfaceInfoDAO.class);
+    }
+
+    public void bindThreadDAO(ThreadDao dao) {
         dependencyServices.addService(ThreadDao.class, dao);
     }
 
-    public void setServicesUnavailable() {
-        dependencyServices.removeService(CommonPaths.class);
-        dependencyServices.removeService(HostInfoDAO.class);
-        dependencyServices.removeService(AgentInfoDAO.class);
-        dependencyServices.removeService(VmInfoDAO.class);
-        dependencyServices.removeService(NetworkInterfaceInfoDAO.class);
+    public void unbindThreadDAO(ThreadDao dao) {
         dependencyServices.removeService(ThreadDao.class);
     }
 
     @Override
+    public Map<CliCommandOption, ? extends TabCompleter> getOptionCompleters() {
+        CompletionFinderTabCompleter completer = new CompletionFinderTabCompleter(new StoragePopulatorConfigFinder(dependencyServices));
+        return Collections.singletonMap(CONFIG_OPTION, completer);
+    }
+
+    @Override
+    public Map<String, Map<CliCommandOption, ? extends TabCompleter>> getSubcommandCompleters() {
+        return Collections.emptyMap();
+    }
+
+    @Override
     public void run(CommandContext ctx) throws CommandException {
         console = ctx.getConsole();
 
@@ -144,8 +191,8 @@
         requireNonNull(networkInfoDAO,
                 translator.localize(LocaleResources.NETWORK_SERVICE_UNAVAILABLE));
 
-        threadDao = dependencyServices.getService(ThreadDao.class);
-        requireNonNull(threadDao, translator.localize(LocaleResources.THREAD_SERVICE_UNAVAILABLE));
+        threadDAO = dependencyServices.getService(ThreadDao.class);
+        requireNonNull(threadDAO, translator.localize(LocaleResources.THREAD_SERVICE_UNAVAILABLE));
 
         HostInfoPopulator hostInfoPopulator = new HostInfoPopulator(hostInfoDAO);
         populators.put(hostInfoPopulator.getHandledCollection(), hostInfoPopulator);
@@ -159,7 +206,7 @@
         NetworkInfoPopulator networkInfoPopulator = new NetworkInfoPopulator(networkInfoDAO);
         populators.put(networkInfoPopulator.getHandledCollection(), networkInfoPopulator);
 
-        ThreadPopulator threadPopulator = new ThreadPopulator(threadDao);
+        ThreadPopulator threadPopulator = new ThreadPopulator(threadDAO);
         populators.put(threadPopulator.getHandledCollection(), threadPopulator);
 
         try {
@@ -193,7 +240,7 @@
      * Package-private to allow overriding for testing.
      */
     File getConfigFile(Arguments args) {
-        return new File(getConfigFileDirectoryPath(paths) + args.getArgument(CONFIG_FILE_NAME));
+        return new File(getConfigFileDirectoryPath(paths) + args.getArgument(CONFIG_OPTION.getLongOpt()));
     }
 
     public static String getConfigFileDirectoryPath(CommonPaths paths) {
--- a/dev/storage-populator/command/src/main/java/com/redhat/thermostat/storage/populator/internal/Activator.java	Tue Sep 13 11:35:25 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,110 +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.storage.populator.internal;
-
-import com.redhat.thermostat.common.MultipleServiceTracker;
-import com.redhat.thermostat.common.cli.Command;
-import com.redhat.thermostat.shared.config.CommonPaths;
-import com.redhat.thermostat.storage.dao.AgentInfoDAO;
-import com.redhat.thermostat.storage.dao.HostInfoDAO;
-import com.redhat.thermostat.storage.dao.NetworkInterfaceInfoDAO;
-import com.redhat.thermostat.storage.dao.VmInfoDAO;
-import com.redhat.thermostat.storage.populator.StoragePopulatorCommand;
-import com.redhat.thermostat.thread.dao.ThreadDao;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-
-import java.util.Hashtable;
-import java.util.Map;
-
-public class Activator implements BundleActivator {
-
-    private MultipleServiceTracker cmdDepsTracker;
-
-    @Override
-    public void start(final BundleContext context) throws Exception {
-
-        final StoragePopulatorCommand command = new StoragePopulatorCommand();
-        Hashtable<String,String> properties = new Hashtable<>();
-        properties.put(Command.NAME, "storage-populator");
-        context.registerService(Command.class, command, properties);
-
-        Class<?>[] deps = new Class<?>[] {
-                CommonPaths.class,
-                HostInfoDAO.class,
-                AgentInfoDAO.class,
-                VmInfoDAO.class,
-                NetworkInterfaceInfoDAO.class,
-                ThreadDao.class,
-        };
-
-        cmdDepsTracker = new MultipleServiceTracker(context, deps, new MultipleServiceTracker.Action() {
-
-            @Override
-            public void dependenciesAvailable(Map<String, Object> services) {
-                CommonPaths paths = (CommonPaths) services.get(CommonPaths.class.getName());
-                HostInfoDAO hostInfoDAO = (HostInfoDAO) services.get(HostInfoDAO.class.getName());
-                AgentInfoDAO agentInfoDAO = (AgentInfoDAO) services.get(AgentInfoDAO.class.getName());
-                VmInfoDAO vmInfoDAO = (VmInfoDAO) services.get(VmInfoDAO.class.getName());
-                NetworkInterfaceInfoDAO networkInfoDAO = (NetworkInterfaceInfoDAO)
-                        services.get(NetworkInterfaceInfoDAO.class.getName());
-                ThreadDao threadDao = (ThreadDao) services.get(ThreadDao.class.getName());
-
-                command.setPaths(paths);
-                command.setHostInfoDAO(hostInfoDAO);
-                command.setAgentInfoDAO(agentInfoDAO);
-                command.setVmInfoDAO(vmInfoDAO);
-                command.setNetworkInfoDAO(networkInfoDAO);
-                command.setThreadDao(threadDao);
-            }
-
-            @Override
-            public void dependenciesUnavailable() {
-                command.setServicesUnavailable();
-            }
-        });
-        cmdDepsTracker.open();
-    }
-
-    @Override
-    public void stop(BundleContext context) throws Exception {
-        if (cmdDepsTracker != null) {
-            cmdDepsTracker.close();
-        }
-    }
-
-}
--- a/dev/storage-populator/command/src/main/java/com/redhat/thermostat/storage/populator/internal/StoragePopulatorCompleterService.java	Tue Sep 13 11:35:25 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +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.storage.populator.internal;
-
-import com.redhat.thermostat.common.cli.AbstractCompleterService;
-import com.redhat.thermostat.common.cli.CliCommandOption;
-import com.redhat.thermostat.common.cli.CompleterService;
-import com.redhat.thermostat.common.cli.CompletionFinderTabCompleter;
-import com.redhat.thermostat.common.cli.TabCompleter;
-import com.redhat.thermostat.shared.config.CommonPaths;
-import com.redhat.thermostat.storage.populator.StoragePopulatorCommand;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-
-@Component
-@Service(CompleterService.class)
-@Reference(name = "commonPaths", referenceInterface = CommonPaths.class)
-public class StoragePopulatorCompleterService extends AbstractCompleterService {
-
-    static final CliCommandOption CONFIG_OPTION = new CliCommandOption("c", "config", true, "the json config file to use", true);
-
-    @Override
-    public Set<String> getCommands() {
-        return Collections.singleton(StoragePopulatorCommand.COMMAND_NAME);
-    }
-
-    @Override
-    public Map<CliCommandOption, ? extends TabCompleter> getOptionCompleters() {
-        CompletionFinderTabCompleter completer = new CompletionFinderTabCompleter(new StoragePopulatorConfigFinder(dependencyServices));
-        return Collections.singletonMap(CONFIG_OPTION, completer);
-    }
-
-    public void bindCommonPaths(CommonPaths commonPaths) {
-        setService(CommonPaths.class, commonPaths);
-    }
-
-    public void unbindCommonPaths(CommonPaths commonPaths) {
-        unsetService(CommonPaths.class);
-    }
-
-}
--- a/dev/storage-populator/command/src/test/java/com/redhat/thermostat/storage/populator/StoragePopulatorCommandTest.java	Tue Sep 13 11:35:25 2016 -0400
+++ b/dev/storage-populator/command/src/test/java/com/redhat/thermostat/storage/populator/StoragePopulatorCommandTest.java	Wed Sep 14 11:05:00 2016 -0400
@@ -36,7 +36,11 @@
 
 package com.redhat.thermostat.storage.populator;
 
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Matchers.any;
@@ -49,7 +53,11 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.PrintStream;
+import java.util.Collections;
+import java.util.Map;
 
+import com.redhat.thermostat.common.cli.CliCommandOption;
+import com.redhat.thermostat.common.cli.TabCompleter;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -110,7 +118,6 @@
     @Test
     public void testCommandFailsWhenDependenciesUnavailable() {
         command = new StoragePopulatorCommand();
-        command.setServicesUnavailable();
 
         try {
             command.run(ctx);
@@ -210,18 +217,37 @@
         verify(vmInfoDAO, times(vmCount)).putVmInfo(any(VmInfo.class));
     }
 
+    @Test
+    public void testProvidesConfigFileCompletions() {
+        command = new StoragePopulatorCommand();
+        setUpServices();
+        Map<CliCommandOption, ? extends TabCompleter> map = command.getOptionCompleters();
+        assertThat(map.keySet(), is(equalTo(Collections.singleton(StoragePopulatorCommand.CONFIG_OPTION))));
+        assertThat(map.get(StoragePopulatorCommand.CONFIG_OPTION), is(not(equalTo(null))));
+    }
+
+    @Test
+    public void testProvidesTabCompletionsEvenWhenCommonPathsUnavailable() {
+        command = new StoragePopulatorCommand();
+        setUpServices();
+        command.unbindPaths(mock(CommonPaths.class));
+        Map<CliCommandOption, ? extends TabCompleter> map = command.getOptionCompleters();
+        assertThat(map.keySet(), is(equalTo(Collections.singleton(StoragePopulatorCommand.CONFIG_OPTION))));
+        assertThat(map.get(StoragePopulatorCommand.CONFIG_OPTION), is(not(equalTo(null))));
+    }
+
     private void setUpServices () {
         paths = mock(CommonPaths.class);
-        command.setPaths(paths);
+        command.bindPaths(paths);
         hostInfoDAO = mock(HostInfoDAO.class);
-        command.setHostInfoDAO(hostInfoDAO);
+        command.bindHostInfoDAO(hostInfoDAO);
         agentInfoDAO = mock(AgentInfoDAO.class);
-        command.setAgentInfoDAO(agentInfoDAO);
+        command.bindAgentInfoDAO(agentInfoDAO);
         vmInfoDAO = mock(VmInfoDAO.class);
-        command.setVmInfoDAO(vmInfoDAO);
+        command.bindVmInfoDAO(vmInfoDAO);
         networkInfoDAO = mock(NetworkInterfaceInfoDAO.class);
-        command.setNetworkInfoDAO(networkInfoDAO);
+        command.bindNetworkInfoDAO(networkInfoDAO);
         threadDao = mock(ThreadDao.class);
-        command.setThreadDao(threadDao);
+        command.bindThreadDAO(threadDao);
     }
 }
--- a/dev/storage-populator/command/src/test/java/com/redhat/thermostat/storage/populator/internal/ActivatorTest.java	Tue Sep 13 11:35:25 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,65 +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.storage.populator.internal;
-
-import static com.redhat.thermostat.testutils.Asserts.assertCommandIsRegistered;
-import static com.redhat.thermostat.testutils.Asserts.assertServiceIsNotRegistered;
-import static com.redhat.thermostat.testutils.Asserts.assertServiceIsRegistered;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-
-import com.redhat.thermostat.common.cli.CompleterService;
-import com.redhat.thermostat.shared.config.CommonPaths;
-import org.junit.Test;
-
-import com.redhat.thermostat.storage.populator.StoragePopulatorCommand;
-import com.redhat.thermostat.testutils.StubBundleContext;
-
-public class ActivatorTest {
-    @Test
-    public void verifyActivatorRegistersServices() throws Exception {
-        StubBundleContext ctx = new StubBundleContext();
-        Activator activator = new Activator();
-
-        activator.start(ctx);
-        assertCommandIsRegistered(ctx, "storage-populator", StoragePopulatorCommand.class);
-        activator.stop(ctx);
-
-        assertEquals(0, ctx.getServiceListeners().size());
-    }
-
-}
--- a/dev/storage-populator/command/src/test/java/com/redhat/thermostat/storage/populator/internal/StoragePopulatorCompleterServiceTest.java	Tue Sep 13 11:35:25 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +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.storage.populator.internal;
-
-import com.redhat.thermostat.common.cli.CliCommandOption;
-import com.redhat.thermostat.common.cli.TabCompleter;
-import com.redhat.thermostat.shared.config.CommonPaths;
-import com.redhat.thermostat.storage.populator.StoragePopulatorCommand;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Collections;
-import java.util.Map;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.mock;
-
-public class StoragePopulatorCompleterServiceTest {
-
-    private StoragePopulatorCompleterService service;
-
-    @Before
-    public void setup() {
-        service = new StoragePopulatorCompleterService();
-        service.bindCommonPaths(mock(CommonPaths.class));
-    }
-
-    @Test
-    public void testOnlyProvidesCompletionForStoragePopulatorCommand() {
-        assertThat(service.getCommands(), is(equalTo(Collections.singleton(StoragePopulatorCommand.COMMAND_NAME))));
-    }
-
-    @Test
-    public void testProvidesCompleterOnlyForConfigOption() {
-        Map<CliCommandOption, ? extends TabCompleter> completerMap = service.getOptionCompleters();
-        assertThat(completerMap.keySet(), is(equalTo(Collections.singleton(StoragePopulatorCompleterService.CONFIG_OPTION))));
-        assertThat(completerMap.get(StoragePopulatorCompleterService.CONFIG_OPTION), is(not(equalTo(null))));
-    }
-
-    @Test
-    public void testConfigOptionProperties() {
-        assertThat(StoragePopulatorCompleterService.CONFIG_OPTION.getOpt(), is("c"));
-        assertThat(StoragePopulatorCompleterService.CONFIG_OPTION.getLongOpt(), is("config"));
-        assertThat(StoragePopulatorCompleterService.CONFIG_OPTION.hasArg(), is(true));
-        assertThat(StoragePopulatorCompleterService.CONFIG_OPTION.isRequired(), is(true));
-    }
-
-}
--- a/vm-byteman/client-cli/pom.xml	Tue Sep 13 11:35:25 2016 -0400
+++ b/vm-byteman/client-cli/pom.xml	Wed Sep 14 11:05:00 2016 -0400
@@ -54,10 +54,11 @@
         <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 />
+            <Export-Package>
+              com.redhat.thermostat.vm.byteman.client.cli
+            </Export-Package>
             <Private-Package>
               com.redhat.thermostat.vm.byteman.client.cli.internal
             </Private-Package>
--- /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/BytemanControlCommand.java	Wed Sep 14 11:05:00 2016 -0400
@@ -0,0 +1,346 @@
+/*
+ * 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;
+
+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.Collections;
+import java.util.List;
+import java.util.Map;
+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.AbstractCompleterCommand;
+import com.redhat.thermostat.common.cli.Arguments;
+import com.redhat.thermostat.common.cli.CliCommandOption;
+import com.redhat.thermostat.common.cli.Command;
+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.cli.FileNameTabCompleter;
+import com.redhat.thermostat.common.cli.TabCompleter;
+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.client.cli.internal.LocaleResources;
+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.BytemanRequestResponseListener;
+import com.redhat.thermostat.vm.byteman.common.command.BytemanRequest.RequestAction;
+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.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+import org.apache.felix.scr.annotations.References;
+import org.apache.felix.scr.annotations.Service;
+
+@Component
+@Service
+@Property(name = Command.NAME, value = "byteman")
+@References({
+        @Reference(name = "agentInfoDao", referenceInterface = AgentInfoDAO.class, policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL_UNARY),
+        @Reference(name = "vmInfoDao", referenceInterface = VmInfoDAO.class, policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL_UNARY),
+        @Reference(name = "vmBytemanDao", referenceInterface = VmBytemanDAO.class, policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL_UNARY),
+        @Reference(name = "requestQueue", referenceInterface = RequestQueue.class, policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL_UNARY),
+        @Reference(name = "fileNameTabCompleter", referenceInterface = FileNameTabCompleter.class, policy = ReferencePolicy.DYNAMIC, cardinality = ReferenceCardinality.OPTIONAL_UNARY)
+})
+public class BytemanControlCommand extends AbstractCompleterCommand {
+
+    static final CliCommandOption RULES_OPTION = new CliCommandOption("r", "rules", true,
+            "a file with Byteman rules to load into a VM", false);
+    
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+    static final String INJECT_RULE_ACTION = "load";
+    static final String UNLOAD_RULE_ACTION = "unload";
+    static final String STATUS_ACTION = "status";
+    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 String UNSET_PORT = "<unset>";
+    private static final Charset UTF_8_CHARSET = Charset.forName("UTF-8");
+
+    
+    private final DependencyServices depServices = new DependencyServices();
+
+    @Override
+    public Map<CliCommandOption, ? extends TabCompleter> getOptionCompleters() {
+        if (!depServices.hasService(FileNameTabCompleter.class)) {
+            return Collections.emptyMap();
+        }
+        return Collections.singletonMap(RULES_OPTION, depServices.getService(FileNameTabCompleter.class));
+    }
+
+    @Override
+    public Map<String, Map<CliCommandOption, ? extends TabCompleter>> getSubcommandCompleters() {
+        return Collections.emptyMap();
+    }
+
+    @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, vmInfo, ctx, bytemanDao);
+            break;
+        case UNLOAD_RULE_ACTION:
+            unloadRules(target, vmInfo, 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, VmInfo vmInfo, CommandContext ctx, VmBytemanDAO bytemanDao) throws CommandException {
+        VmId vmId = new VmId(vmInfo.getVmId());
+        RequestQueue requestQueue = getRequestQueue();
+        VmBytemanStatus status = getVmBytemanStatus(vmId, bytemanDao);
+        int listenPort = status.getListenPort();
+        Request unloadRequest = BytemanRequest.create(target, vmInfo, RequestAction.UNLOAD_RULES, listenPort);
+        submitRequest(ctx, requestQueue, unloadRequest);
+    }
+
+    
+    /* Injects byteman rules */
+    private void injectRules(InetSocketAddress target, VmInfo vmInfo, CommandContext ctx, VmBytemanDAO bytemanDao) throws CommandException {
+        VmId vmId = new VmId(vmInfo.getVmId());
+        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 = bytemanDao.findBytemanStatus(vmId);
+        int listenPort = BytemanRequest.NOT_ATTACHED_PORT;
+        if (status != null) {
+            listenPort = status.getListenPort();
+        }
+        RequestQueue requestQueue = getRequestQueue();
+        Request request = BytemanRequest.create(target, vmInfo, 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.getDataAsJson());
+            }
+        }
+    }
+
+    /* Show status of loaded byteman rules */
+    private void showStatus(CommandContext ctx, VmInfo vmInfo, VmBytemanDAO bytemanDao) throws CommandException {
+        // Byteman status might be null if no agent has been attached yet. Treat
+        // this similar to no-rules loaded
+        VmBytemanStatus status = bytemanDao.findBytemanStatus(new VmId(vmInfo.getVmId()));
+        PrintStream out = ctx.getConsole().getOutput();
+        String rules;
+        if (status == null || status.getRule() == null || status.getRule().isEmpty()) {
+            rules = NO_RULES_LOADED;
+        } else {
+            rules = status.getRule();
+        }
+        String listenPort;
+        if (status == null) {
+            listenPort = UNSET_PORT;
+        } else {
+            listenPort = Integer.toString(status.getListenPort());
+        }
+        out.println(translator.localize(LocaleResources.BYTEMAN_STATUS_MSG,
+                                        vmInfo.getMainClass(),
+                                        listenPort,
+                                        rules).getContents());
+    }
+
+    private void submitRequest(CommandContext ctx, RequestQueue requestQueue, Request request) {
+        CountDownLatch latch = new CountDownLatch(1);
+        BytemanRequestResponseListener listener = new BytemanRequestResponseListener(latch);
+        request.addListener(listener);
+        requestQueue.putRequest(request);
+        waitWithTimeout(latch);
+        printResponse(listener, ctx);
+    }
+    
+    private void printResponse(BytemanRequestResponseListener listener, CommandContext ctx) {
+        if (listener.isError()) {
+            PrintStream err = ctx.getConsole().getError();
+            err.println(listener.getErrorMessage());
+        } else {
+            PrintStream out = ctx.getConsole().getOutput();
+            out.println(translator.localize(LocaleResources.REQUEST_SUCCESS)
+                    .getContents());
+        }
+    }
+    
+    // package-private for testing
+    void waitWithTimeout(CountDownLatch latch) {
+        try {
+            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 bindAgentInfoDao(AgentInfoDAO agentDao) {
+        depServices.addService(AgentInfoDAO.class, agentDao);
+    }
+    
+    void unbindAgentInfoDao(AgentInfoDAO agentDao) {
+        depServices.removeService(AgentInfoDAO.class);
+    }
+    
+    void bindVmInfoDao(VmInfoDAO vmDao) {
+        depServices.addService(VmInfoDAO.class, vmDao);
+    }
+    
+    void unbindVmInfoDao(VmInfoDAO vmDao) {
+        depServices.removeService(VmInfoDAO.class);
+    }
+    
+    void bindVmBytemanDao(VmBytemanDAO metricDao) {
+        depServices.addService(VmBytemanDAO.class, metricDao);
+    }
+    
+    void unbindVmBytemanDao(VmBytemanDAO metricDao) {
+        depServices.removeService(VmBytemanDAO.class);
+    }
+    
+    void bindRequestQueue(RequestQueue queue) {
+        depServices.addService(RequestQueue.class, queue);
+    }
+    
+    void unbindRequestQueue(RequestQueue queue) {
+        depServices.removeService(RequestQueue.class);
+    }
+
+    void bindFileNameTabCompleter(FileNameTabCompleter fileNameTabCompleter) {
+        depServices.addService(FileNameTabCompleter.class, fileNameTabCompleter);
+    }
+
+    void unbindFileNameTabCompleter(FileNameTabCompleter fileNameTabCompleter) {
+        depServices.removeService(FileNameTabCompleter.class);
+    }
+
+}
--- a/vm-byteman/client-cli/src/main/java/com/redhat/thermostat/vm/byteman/client/cli/internal/Activator.java	Tue Sep 13 11:35:25 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,146 +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.client.cli.internal;
-
-import java.util.Hashtable;
-import java.util.Map;
-
-import com.redhat.thermostat.common.cli.CompleterService;
-import com.redhat.thermostat.common.cli.FileNameTabCompleter;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-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;
-import org.osgi.util.tracker.ServiceTracker;
-import org.osgi.util.tracker.ServiceTrackerCustomizer;
-
-public class Activator implements BundleActivator {
-
-    private ServiceRegistration<Command> commandReg;
-    private ServiceRegistration completerRegistration;
-    private MultipleServiceTracker commandDepsTracker;
-    private ServiceTracker fileNameTabCompleterTracker;
-    private BytemanCompleterService completerService;
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public void start(final 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,
-        };
-        commandDepsTracker = 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);
-            }
-        });
-        commandDepsTracker.open();
-
-        completerService = new BytemanCompleterService();
-
-        fileNameTabCompleterTracker = new ServiceTracker(context, FileNameTabCompleter.class, new ServiceTrackerCustomizer() {
-            @Override
-            public Object addingService(ServiceReference serviceReference) {
-                FileNameTabCompleter fileNameTabCompleter = (FileNameTabCompleter) context.getService(serviceReference);
-                completerService.setFileNameTabCompleter(fileNameTabCompleter);
-                completerRegistration = context.registerService(CompleterService.class.getName(), completerService, null);
-                return context.getService(serviceReference);
-            }
-
-            @Override
-            public void modifiedService(ServiceReference serviceReference, Object o) {
-            }
-
-            @Override
-            public void removedService(ServiceReference serviceReference, Object o) {
-                completerService.setFileNameTabCompleter(null);
-            }
-        });
-        fileNameTabCompleterTracker.open();
-    }
-
-    @Override
-    public void stop(BundleContext context) throws Exception {
-        if (commandDepsTracker != null) {
-            commandDepsTracker.close();
-        }
-        if (commandReg != null) {
-            commandReg.unregister();
-        }
-        if (fileNameTabCompleterTracker != null) {
-            fileNameTabCompleterTracker.close();
-        }
-        if (completerRegistration != null) {
-            completerRegistration.unregister();
-        }
-        if (completerService != null) {
-            completerService.setFileNameTabCompleter(null);
-        }
-    }
-
-}
--- a/vm-byteman/client-cli/src/main/java/com/redhat/thermostat/vm/byteman/client/cli/internal/BytemanCompleterService.java	Tue Sep 13 11:35:25 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,75 +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.client.cli.internal;
-
-import com.redhat.thermostat.common.cli.AbstractCompleterService;
-import com.redhat.thermostat.common.cli.CliCommandOption;
-import com.redhat.thermostat.common.cli.FileNameTabCompleter;
-import com.redhat.thermostat.common.cli.TabCompleter;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-public class BytemanCompleterService extends AbstractCompleterService {
-
-    static final CliCommandOption RULES_OPTION = new CliCommandOption("r", "rules", true,
-            "a file with Byteman rules to load into a VM", false);
-
-    @Override
-    public Set<String> getCommands() {
-        return Collections.singleton(BytemanControlCommand.COMMAND_NAME);
-    }
-
-    @Override
-    public Map<String, Map<CliCommandOption, ? extends TabCompleter>> getSubcommandCompleters() {
-        if (!dependencyServices.hasService(FileNameTabCompleter.class)) {
-            return Collections.emptyMap();
-        }
-        Map<CliCommandOption, ? extends TabCompleter> loadMap =
-                Collections.singletonMap(RULES_OPTION, dependencyServices.getService(FileNameTabCompleter.class));
-        Map<String, Map<CliCommandOption, ? extends TabCompleter>> map = new HashMap<>();
-        map.put(BytemanControlCommand.INJECT_RULE_ACTION, loadMap);
-        return map;
-    }
-
-    public void setFileNameTabCompleter(FileNameTabCompleter tabCompleter) {
-        setService(FileNameTabCompleter.class, tabCompleter);
-    }
-
-}
--- a/vm-byteman/client-cli/src/main/java/com/redhat/thermostat/vm/byteman/client/cli/internal/BytemanControlCommand.java	Tue Sep 13 11:35:25 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,300 +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.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.BytemanRequestResponseListener;
-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();
-    static final String INJECT_RULE_ACTION = "load";
-    static final String UNLOAD_RULE_ACTION = "unload";
-    static final String STATUS_ACTION = "status";
-    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 String UNSET_PORT = "<unset>";
-    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, vmInfo, ctx, bytemanDao);
-            break;
-        case UNLOAD_RULE_ACTION:
-            unloadRules(target, vmInfo, 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, VmInfo vmInfo, CommandContext ctx, VmBytemanDAO bytemanDao) throws CommandException {
-        VmId vmId = new VmId(vmInfo.getVmId());
-        RequestQueue requestQueue = getRequestQueue();
-        VmBytemanStatus status = getVmBytemanStatus(vmId, bytemanDao);
-        int listenPort = status.getListenPort();
-        Request unloadRequest = BytemanRequest.create(target, vmInfo, RequestAction.UNLOAD_RULES, listenPort);
-        submitRequest(ctx, requestQueue, unloadRequest);
-    }
-
-    
-    /* Injects byteman rules */
-    private void injectRules(InetSocketAddress target, VmInfo vmInfo, CommandContext ctx, VmBytemanDAO bytemanDao) throws CommandException {
-        VmId vmId = new VmId(vmInfo.getVmId());
-        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 = bytemanDao.findBytemanStatus(vmId);
-        int listenPort = BytemanRequest.NOT_ATTACHED_PORT;
-        if (status != null) {
-            listenPort = status.getListenPort();
-        }
-        RequestQueue requestQueue = getRequestQueue();
-        Request request = BytemanRequest.create(target, vmInfo, 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.getDataAsJson());
-            }
-        }
-    }
-
-    /* Show status of loaded byteman rules */
-    private void showStatus(CommandContext ctx, VmInfo vmInfo, VmBytemanDAO bytemanDao) throws CommandException {
-        // Byteman status might be null if no agent has been attached yet. Treat
-        // this similar to no-rules loaded
-        VmBytemanStatus status = bytemanDao.findBytemanStatus(new VmId(vmInfo.getVmId()));
-        PrintStream out = ctx.getConsole().getOutput();
-        String rules;
-        if (status == null || status.getRule() == null || status.getRule().isEmpty()) {
-            rules = NO_RULES_LOADED;
-        } else {
-            rules = status.getRule();
-        }
-        String listenPort;
-        if (status == null) {
-            listenPort = UNSET_PORT;
-        } else {
-            listenPort = Integer.toString(status.getListenPort());
-        }
-        out.println(translator.localize(LocaleResources.BYTEMAN_STATUS_MSG,
-                                        vmInfo.getMainClass(),
-                                        listenPort,
-                                        rules).getContents());
-    }
-
-    private void submitRequest(CommandContext ctx, RequestQueue requestQueue, Request request) {
-        CountDownLatch latch = new CountDownLatch(1);
-        BytemanRequestResponseListener listener = new BytemanRequestResponseListener(latch);
-        request.addListener(listener);
-        requestQueue.putRequest(request);
-        waitWithTimeout(latch);
-        printResponse(listener, ctx);
-    }
-    
-    private void printResponse(BytemanRequestResponseListener listener, CommandContext ctx) {
-        if (listener.isError()) {
-            PrintStream err = ctx.getConsole().getError();
-            err.println(listener.getErrorMessage());
-        } else {
-            PrintStream out = ctx.getConsole().getOutput();
-            out.println(translator.localize(LocaleResources.REQUEST_SUCCESS)
-                    .getContents());
-        }
-    }
-    
-    // package-private for testing
-    void waitWithTimeout(CountDownLatch latch) {
-        try {
-            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/test/java/com/redhat/thermostat/vm/byteman/client/cli/BytemanControlCommandTest.java	Wed Sep 14 11:05:00 2016 -0400
@@ -0,0 +1,457 @@
+/*
+ * 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;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+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.Map;
+import java.util.concurrent.CountDownLatch;
+
+import com.redhat.thermostat.common.cli.CliCommandOption;
+import com.redhat.thermostat.common.cli.FileNameTabCompleter;
+import com.redhat.thermostat.common.cli.TabCompleter;
+import com.redhat.thermostat.vm.byteman.client.cli.BytemanControlCommand;
+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.BytemanRequestResponseListener;
+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 int SOME_LISTEN_PORT = 333;
+    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.bindVmInfoDao(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.bindAgentInfoDao(agentInfoDAO);
+        command.bindVmBytemanDao(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>";
+        VmBytemanStatus status = new VmBytemanStatus();
+        status.setVmId(SOME_VM_ID);
+        status.setAgentId(SOME_AGENT_ID);
+        status.setListenPort(SOME_LISTEN_PORT);
+        status.setRule(rule);
+        basicStatusActionTest(rule, expectedLoadedRuleMsg, status);
+    }
+    
+    @Test
+    public void testStatusActionNotAttached() throws CommandException {
+        String rule = null;
+        String expectedLoadedRuleMsg = "<no-loaded-rules>";
+        basicStatusActionTest(rule, expectedLoadedRuleMsg, null);
+    }
+    
+    private void basicStatusActionTest(String rule, String expectedRuleMsg, VmBytemanStatus status) throws CommandException {
+        VmBytemanDAO dao = mock(VmBytemanDAO.class);
+        when(dao.findBytemanStatus(eq(new VmId(SOME_VM_ID)))).thenReturn(status);
+        Arguments args = getBasicArgsWithAction(STATUS_ACTION);
+        CommandContext ctx = ctxFactory.createContext(args);
+        command.unbindVmBytemanDao(dao);
+        command.bindVmBytemanDao(dao);
+        command.run(ctx);
+        String stdErr = ctxFactory.getError();
+        String stdOut = ctxFactory.getOutput();
+        assertEquals(EMPTY_STRING, stdErr);
+        assertTrue(stdOut.contains("Byteman status for VM:"));
+        String listenPort = "<unset>";
+        if (status != null) {
+            listenPort = Integer.toString(status.getListenPort());
+        }
+        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";
+        VmBytemanStatus status = new VmBytemanStatus();
+        status.setVmId(SOME_VM_ID);
+        status.setAgentId(SOME_AGENT_ID);
+        status.setListenPort(SOME_LISTEN_PORT);
+        status.setRule(rule);
+        basicStatusActionTest(rule, rule, status);
+    }
+    
+    @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.unbindAgentInfoDao(agentInfoDAO);
+        command.bindAgentInfoDao(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.unbindVmBytemanDao(dao);
+        command.bindVmBytemanDao(dao);
+        RequestQueue rQueue = mock(RequestQueue.class);
+        ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
+        command.bindRequestQueue(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.unbindVmBytemanDao(dao);
+        command.bindVmBytemanDao(dao);
+        RequestQueue rQueue = mock(RequestQueue.class);
+        command.bindRequestQueue(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.unbindVmBytemanDao(dao);
+        command.bindVmBytemanDao(dao);
+        RequestQueue rQueue = mock(RequestQueue.class);
+        command.bindRequestQueue(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 testLoadActionSuccessAgentAttached() 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.unbindVmBytemanDao(dao);
+        command.bindVmBytemanDao(dao);
+        RequestQueue rQueue = mock(RequestQueue.class);
+        command.bindRequestQueue(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());
+        String out = ctxFactory.getOutput();
+        assertEquals("Request submitted successfully.\n", out);
+    }
+    
+    @Test
+    public void testLoadActionSuccessAgentNotAttached() throws CommandException, FileNotFoundException, IOException {
+        VmBytemanDAO dao = mock(VmBytemanDAO.class);
+        // mimic no-agent-attached, by returning a null status
+        when(dao.findBytemanStatus(eq(new VmId(SOME_VM_ID)))).thenReturn(null);
+        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.unbindVmBytemanDao(dao);
+        command.bindVmBytemanDao(dao);
+        RequestQueue rQueue = mock(RequestQueue.class);
+        command.bindRequestQueue(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(BytemanRequest.NOT_ATTACHED_PORT), 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());
+        String out = ctxFactory.getOutput();
+        assertEquals("Request submitted successfully.\n", out);
+    }
+
+    @Test
+    public void testProvidesRulesFileCompletion() {
+        FileNameTabCompleter fileNameTabCompleter = mock(FileNameTabCompleter.class);
+        command.bindFileNameTabCompleter(fileNameTabCompleter);
+        Map<CliCommandOption, ? extends TabCompleter> completerMap = command.getOptionCompleters();
+        assertThat(completerMap.size(), is(1));
+        assertThat(completerMap.keySet(), is(equalTo(Collections.singleton(BytemanControlCommand.RULES_OPTION))));
+        assertThat(BytemanControlCommand.RULES_OPTION.getLongOpt(), is("rules"));
+        assertThat(BytemanControlCommand.RULES_OPTION.getOpt(), is("r"));
+        assertThat(completerMap.get(BytemanControlCommand.RULES_OPTION), is(not(equalTo(null))));
+        assertThat(completerMap.get(BytemanControlCommand.RULES_OPTION), is(instanceOf(FileNameTabCompleter.class)));
+    }
+
+    @Test
+    public void testProvidesNoCompletionsIfFileNameTabCompleterIsUnavailable() {
+        FileNameTabCompleter fileNameTabCompleter = mock(FileNameTabCompleter.class);
+        command.unbindFileNameTabCompleter(fileNameTabCompleter);
+        Map<CliCommandOption, ? extends TabCompleter> completerMap = command.getOptionCompleters();
+        assertThat(completerMap.size(), is(0));
+    }
+    
+    @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.unbindVmBytemanDao(dao);
+        command.bindVmBytemanDao(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;
+    }
+}
--- a/vm-byteman/client-cli/src/test/java/com/redhat/thermostat/vm/byteman/client/cli/internal/ActivatorTest.java	Tue Sep 13 11:35:25 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,87 +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.client.cli.internal;
-
-import com.redhat.thermostat.common.cli.Command;
-import com.redhat.thermostat.common.cli.CompleterService;
-import com.redhat.thermostat.common.cli.FileNameTabCompleter;
-import com.redhat.thermostat.testutils.StubBundleContext;
-import org.junit.Test;
-
-import java.util.List;
-
-import static com.redhat.thermostat.testutils.Asserts.assertCommandIsRegistered;
-import static com.redhat.thermostat.testutils.Asserts.assertServiceIsRegistered;
-import static org.junit.Assert.assertEquals;
-
-public class ActivatorTest {
-
-    @Test
-    public void testCommandsRegistered() throws Exception {
-        StubBundleContext ctx = new StubBundleContext();
-
-        Activator activator = new Activator();
-
-        activator.start(ctx);
-
-        assertCommandIsRegistered(ctx, "byteman", BytemanControlCommand.class);
-
-        activator.stop(ctx);
-
-        assertEquals(0, ctx.getAllServices().size());
-    }
-
-    @Test
-    public void testCompleterBecomesAvailableWhenFileNameCompleterAppears() throws Exception {
-        StubBundleContext ctx = new StubBundleContext();
-        ctx.registerService(FileNameTabCompleter.class, new StubFileNameTabCompleter(), null);
-        Activator activator = new Activator();
-        activator.start(ctx);
-        assertEquals(3, ctx.getAllServices().size());
-        assertServiceIsRegistered(ctx, FileNameTabCompleter.class, StubFileNameTabCompleter.class);
-        assertServiceIsRegistered(ctx, CompleterService.class, BytemanCompleterService.class);
-        assertServiceIsRegistered(ctx, Command.class, BytemanControlCommand.class);
-    }
-
-    private static class StubFileNameTabCompleter implements FileNameTabCompleter {
-        @Override
-        public int complete(String buffer, int cursor, List<CharSequence> candidates) {
-            return 0;
-        }
-    }
-
-}
--- a/vm-byteman/client-cli/src/test/java/com/redhat/thermostat/vm/byteman/client/cli/internal/BytemanCompleterServiceTest.java	Tue Sep 13 11:35:25 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,115 +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.client.cli.internal;
-
-import com.redhat.thermostat.common.Pair;
-import com.redhat.thermostat.common.cli.CliCommandOption;
-import com.redhat.thermostat.common.cli.FileNameTabCompleter;
-import com.redhat.thermostat.common.cli.TabCompleter;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import static com.redhat.thermostat.vm.byteman.client.cli.internal.BytemanCompleterService.RULES_OPTION;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Mockito.mock;
-
-public class BytemanCompleterServiceTest {
-
-    private BytemanCompleterService completerService;
-
-    @Before
-    public void setup() {
-        completerService = new BytemanCompleterService();
-        completerService.setFileNameTabCompleter(mock(FileNameTabCompleter.class));
-    }
-
-    @Test
-    public void testOnlyProvidesCompletionForBytemanCommand() {
-        assertThat(completerService.getCommands(), is(equalTo(Collections.singleton(BytemanControlCommand.COMMAND_NAME))));
-    }
-
-    @Test
-    public void testProvidesNoTopLevelOptionCompletions() {
-        assertThat(completerService.getOptionCompleters().size(), is(0));
-    }
-
-    @Test
-    public void testProvidesLoadSubcommandCompletionForRulesArgument() {
-        Map<String, Map<CliCommandOption, ? extends TabCompleter>> map = completerService.getSubcommandCompleters();
-        assertThat(map.keySet(), is(equalTo(Collections.singleton(BytemanControlCommand.INJECT_RULE_ACTION))));
-        Collection<Map<CliCommandOption, ? extends TabCompleter>> values = map.values();
-        assertThat(values.size(), is(1));
-        Map<CliCommandOption, ? extends TabCompleter> submap = (Map<CliCommandOption, ? extends TabCompleter>) values.toArray(new Object[1])[0];
-        assertThat(submap.keySet(), is(equalTo(Collections.singleton(BytemanCompleterService.RULES_OPTION))));
-        assertThat(RULES_OPTION.getLongOpt(), is("rules"));
-        assertThat(RULES_OPTION.getOpt(), is("r"));
-        assertThat(RULES_OPTION.isRequired(), is(false));
-        assertThat(RULES_OPTION.hasArg(), is(true));
-    }
-
-    @Test
-    public void testProvidesOnlyLoadSubcommandCompletion() {
-        Map<String, Map<CliCommandOption, ? extends TabCompleter>> map = completerService.getSubcommandCompleters();
-        assertThat(map.size(), is(1));
-        for (String key : map.keySet()) {
-            assertThat(key, is(equalTo(BytemanControlCommand.INJECT_RULE_ACTION)));
-        }
-    }
-
-    @Test
-    public void testCompleterIsNotNull() {
-        Map<String, Map<CliCommandOption, ? extends TabCompleter>> map = completerService.getSubcommandCompleters();
-        assertThat(map.get(BytemanControlCommand.INJECT_RULE_ACTION).get(BytemanCompleterService.RULES_OPTION),
-                is(not(equalTo(null))));
-    }
-
-    @Test
-    public void testProvidesNoCompletionWhenFileNameTabCompleterNotAvailable() {
-        completerService.setFileNameTabCompleter(null);
-        Map<String, Map<CliCommandOption, ? extends TabCompleter>> map = completerService.getSubcommandCompleters();
-        assertThat(map.isEmpty(), is(true));
-    }
-
-}
--- a/vm-byteman/client-cli/src/test/java/com/redhat/thermostat/vm/byteman/client/cli/internal/BytemanControlCommandTest.java	Tue Sep 13 11:35:25 2016 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,426 +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.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.BytemanRequestResponseListener;
-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 int SOME_LISTEN_PORT = 333;
-    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>";
-        VmBytemanStatus status = new VmBytemanStatus();
-        status.setVmId(SOME_VM_ID);
-        status.setAgentId(SOME_AGENT_ID);
-        status.setListenPort(SOME_LISTEN_PORT);
-        status.setRule(rule);
-        basicStatusActionTest(rule, expectedLoadedRuleMsg, status);
-    }
-    
-    @Test
-    public void testStatusActionNotAttached() throws CommandException {
-        String rule = null;
-        String expectedLoadedRuleMsg = "<no-loaded-rules>";
-        basicStatusActionTest(rule, expectedLoadedRuleMsg, null);
-    }
-    
-    private void basicStatusActionTest(String rule, String expectedRuleMsg, VmBytemanStatus status) throws CommandException {
-        VmBytemanDAO dao = mock(VmBytemanDAO.class);
-        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:"));
-        String listenPort = "<unset>";
-        if (status != null) {
-            listenPort = Integer.toString(status.getListenPort());
-        }
-        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";
-        VmBytemanStatus status = new VmBytemanStatus();
-        status.setVmId(SOME_VM_ID);
-        status.setAgentId(SOME_AGENT_ID);
-        status.setListenPort(SOME_LISTEN_PORT);
-        status.setRule(rule);
-        basicStatusActionTest(rule, rule, status);
-    }
-    
-    @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 testLoadActionSuccessAgentAttached() 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());
-        String out = ctxFactory.getOutput();
-        assertEquals("Request submitted successfully.\n", out);
-    }
-    
-    @Test
-    public void testLoadActionSuccessAgentNotAttached() throws CommandException, FileNotFoundException, IOException {
-        VmBytemanDAO dao = mock(VmBytemanDAO.class);
-        // mimic no-agent-attached, by returning a null status
-        when(dao.findBytemanStatus(eq(new VmId(SOME_VM_ID)))).thenReturn(null);
-        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(BytemanRequest.NOT_ATTACHED_PORT), 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());
-        String out = ctxFactory.getOutput();
-        assertEquals("Request submitted successfully.\n", out);
-    }
-    
-    @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;
-    }
-}