changeset 1537:3fe3b3b92be2

Agent-Info Command Addition Reviewed-by: omajid Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2014-November/011408.html
author Jie Kang <jkang@redhat.com>
date Wed, 05 Nov 2014 15:13:32 -0500
parents 97512f3b25a5
children fb69a13dd11c
files client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/Activator.java client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/AgentInfoCommand.java client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/AgentInfoFormatter.java client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/ListAgentsCommand.java client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/LocaleResources.java client/cli/src/main/resources/com/redhat/thermostat/client/cli/strings.properties client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/ActivatorTest.java client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/AgentInfoCommandTest.java client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/AgentInfoFormatterTest.java distribution/config/commands/agent-info.properties
diffstat 10 files changed, 635 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/Activator.java	Wed Nov 05 10:36:15 2014 -0500
+++ b/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/Activator.java	Wed Nov 05 15:13:32 2014 -0500
@@ -48,6 +48,7 @@
 import com.redhat.thermostat.common.config.ClientPreferences;
 import com.redhat.thermostat.shared.config.CommonPaths;
 import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.dao.BackendInfoDAO;
 import com.redhat.thermostat.utils.keyring.Keyring;
 
 public class Activator implements BundleActivator {
@@ -55,9 +56,12 @@
     private CommandRegistry reg = null;
     private MultipleServiceTracker tracker;
 
-    private MultipleServiceTracker agentTracker;
+    private MultipleServiceTracker listAgentTracker;
     private final ListAgentsCommand listAgentsCommand = new ListAgentsCommand();
 
+    private MultipleServiceTracker agentInfoTracker;
+    private final AgentInfoCommand agentInfoCommand = new AgentInfoCommand();
+
     @Override
     public void start(final BundleContext context) throws Exception {
         reg = new CommandRegistryImpl(context);
@@ -91,10 +95,10 @@
         });
         tracker.open();
 
-        Class<?>[] agentClasses = new Class[] {
+        Class<?>[] listAgentClasses = new Class[] {
                 AgentInfoDAO.class,
         };
-        agentTracker = new MultipleServiceTracker(context, agentClasses, new Action() {
+        listAgentTracker = new MultipleServiceTracker(context, listAgentClasses, new Action() {
             @Override
             public void dependenciesAvailable(Map<String, Object> services) {
                 AgentInfoDAO agentInfoDAO = (AgentInfoDAO) services.get(AgentInfoDAO.class.getName());
@@ -106,15 +110,38 @@
                 listAgentsCommand.setAgentInfoDAO(null);
             }
         });
-        agentTracker.open();
+        listAgentTracker.open();
 
         reg.registerCommand("list-agents", listAgentsCommand);
+
+        Class<?>[] agentInfoClasses = new Class[] {
+                AgentInfoDAO.class,
+                BackendInfoDAO.class,
+        };
+        agentInfoTracker = new MultipleServiceTracker(context, agentInfoClasses, new Action() {
+            @Override
+            public void dependenciesAvailable(Map<String, Object> services) {
+                AgentInfoDAO agentInfoDAO = (AgentInfoDAO) services.get(AgentInfoDAO.class.getName());
+                BackendInfoDAO backendInfoDAO = (BackendInfoDAO) services.get(BackendInfoDAO.class.getName());
+
+                agentInfoCommand.setServices(agentInfoDAO, backendInfoDAO);
+            }
+
+            @Override
+            public void dependenciesUnavailable() {
+                agentInfoCommand.setServices(null, null);
+            }
+        });
+        agentInfoTracker.open();
+
+        reg.registerCommand("agent-info", agentInfoCommand);
     }
 
     @Override
     public void stop(BundleContext context) throws Exception {
         tracker.close();
-        agentTracker.close();
+        listAgentTracker.close();
+        agentInfoTracker.close();
         reg.unregisterCommands();
     }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/AgentInfoCommand.java	Wed Nov 05 15:13:32 2014 -0500
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2012-2014 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.client.cli.internal;
+
+import java.io.PrintStream;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+import com.redhat.thermostat.common.cli.AbstractCommand;
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.CommandLineArgumentParseException;
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.storage.core.HostRef;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.dao.BackendInfoDAO;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.BackendInformation;
+
+public class AgentInfoCommand extends AbstractCommand {
+
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    private AgentInfoDAO agentInfoDAO;
+    private BackendInfoDAO backendInfoDAO;
+
+    private Semaphore servicesAvailable = new Semaphore(0);
+
+    @Override
+    public void run(CommandContext ctx) throws CommandException {
+        waitForServices(500l);
+
+        requireNonNull(agentInfoDAO, translator.localize(LocaleResources.AGENT_SERVICE_UNAVAILABLE));
+        requireNonNull(backendInfoDAO, translator.localize(LocaleResources.BACKEND_SERVICE_UNAVAILABLE));
+
+        String agentId = ctx.getArguments().getArgument("agentId");
+        if (agentId == null) {
+            throw new CommandLineArgumentParseException(translator.localize(LocaleResources.AGENT_ID_REQUIRED_MESSAGE));
+        }
+
+        displayAgentInfo(ctx.getConsole().getOutput(), agentId);
+    }
+
+    private void waitForServices(long timeout) {
+        try {
+            servicesAvailable.tryAcquire(timeout, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            translator.localize(LocaleResources.COMMAND_INTERRUPTED);
+        }
+    }
+
+    private void displayAgentInfo(PrintStream out, String agentId) throws CommandException {
+        HostRef dummyRef = new HostRef(agentId, "dummy");
+        AgentInformation info = agentInfoDAO.getAgentInformation(dummyRef);
+        requireNonNull(info, translator.localize(LocaleResources.AGENT_NOT_FOUND, agentId));
+
+        List<BackendInformation> backendList = backendInfoDAO.getBackendInformation(dummyRef);
+
+        AgentInfoFormatter formatter = new AgentInfoFormatter();
+
+        formatter.addAgent(info, backendList);
+        formatter.format(out);
+    }
+
+    public void setServices(AgentInfoDAO agentInfoDAO, BackendInfoDAO backendInfoDAO) {
+        this.agentInfoDAO = agentInfoDAO;
+        this.backendInfoDAO = backendInfoDAO;
+        if (agentInfoDAO == null || backendInfoDAO ==  null) {
+            servicesUnavailable();
+        } else {
+            serviceAvailable();
+        }
+    }
+
+    private void serviceAvailable() {
+        this.servicesAvailable.release();
+    }
+
+    private void servicesUnavailable() {
+        this.servicesAvailable.drainPermits();
+    }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/AgentInfoFormatter.java	Wed Nov 05 15:13:32 2014 -0500
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2012-2014 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.client.cli.internal;
+
+import java.io.PrintStream;
+import java.text.DateFormat;
+import java.util.Date;
+import java.util.List;
+
+import com.redhat.thermostat.common.cli.TableRenderer;
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.BackendInformation;
+
+public class AgentInfoFormatter {
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+    private static final int agentColumns = 2;
+    private static final int backendColumns = 3;
+
+    private final TableRenderer agentTable = new TableRenderer(agentColumns);
+    private final TableRenderer backendTable = new TableRenderer(backendColumns);
+    private final DateFormat dateFormat = DateFormat.getDateTimeInstance();
+
+    private static final String AGENT_ID = translator.localize(LocaleResources.AGENT_ID).getContents();
+    private static final String CONFIG_LISTEN_ADDRESS = translator.localize(LocaleResources.CONFIG_LISTEN_ADDRESS).getContents();
+    private static final String START_TIME = translator.localize(LocaleResources.START_TIME).getContents();
+    private static final String STOP_TIME = translator.localize(LocaleResources.STOP_TIME).getContents();
+
+    private static final String BACKEND = translator.localize(LocaleResources.BACKEND).getContents();
+    private static final String STATUS = translator.localize(LocaleResources.STATUS).getContents();
+    private static final String DESCIRPTION = translator.localize(LocaleResources.DESCRIPTION).getContents();
+
+    void format(PrintStream output) {
+        agentTable.render(output);
+        backendTable.render(output);
+    }
+
+    void addAgent(AgentInformation info, List<BackendInformation> backendList) {
+        printAgent(info, backendList);
+    }
+
+    private void printAgent(AgentInformation info, List<BackendInformation> backendList) {
+        printRow(AGENT_ID, info.getAgentId());
+        printRow(CONFIG_LISTEN_ADDRESS, info.getConfigListenAddress());
+        printRow(START_TIME, dateFormat.format(new Date(info.getStartTime())));
+        printRow(STOP_TIME, getStopString(info.getStopTime()));
+
+        if (!backendList.isEmpty()) {
+            printEmptyRow();
+
+            printRow(BACKEND, STATUS, DESCIRPTION);
+
+            for (BackendInformation backend : backendList) {
+                printRow(backend.getName(),
+                        backend.isActive() ?
+                                translator.localize(LocaleResources.AGENT_INFO_BACKEND_STATUS_ACTIVE).getContents()
+                                : translator.localize(LocaleResources.AGENT_INFO_BACKEND_STATUS_INACTIVE).getContents(),
+                        backend.getDescription()
+                );
+            }
+        }
+    }
+
+    private void printEmptyRow() {
+        printRow("", "");
+    }
+
+    private String getStopString(long stopTime) {
+        String stopString;
+        if (stopTime == 0) {
+            stopString = translator.localize(LocaleResources.AGENT_ACTIVE).getContents();
+        } else {
+            stopString = dateFormat.format(new Date(stopTime));
+        }
+
+        return stopString;
+    }
+
+    private void printRow(String title, String content) {
+        agentTable.printLine(title, content);
+    }
+
+    private void printRow(String title, String status, String description) {
+        backendTable.printLine(title, status, description);
+    }
+}
--- a/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/ListAgentsCommand.java	Wed Nov 05 10:36:15 2014 -0500
+++ b/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/ListAgentsCommand.java	Wed Nov 05 15:13:32 2014 -0500
@@ -55,7 +55,6 @@
     private AgentInfoDAO agentInfoDAO;
     private Semaphore servicesAvailable = new Semaphore(0);
 
-
     @Override
     public void run(CommandContext ctx) throws CommandException {
         waitForServices(500l);
--- a/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/LocaleResources.java	Wed Nov 05 10:36:15 2014 -0500
+++ b/client/cli/src/main/java/com/redhat/thermostat/client/cli/internal/LocaleResources.java	Wed Nov 05 15:13:32 2014 -0500
@@ -48,6 +48,7 @@
     VM_CPU_SERVICE_NOT_AVAILABLE,
     VM_MEMORY_SERVICE_NOT_AVAILABLE,
     AGENT_SERVICE_UNAVAILABLE,
+    BACKEND_SERVICE_UNAVAILABLE,
 
     COMMAND_CONNECT_ALREADY_CONNECTED,
     COMMAND_CONNECT_FAILED_TO_CONNECT,
@@ -80,6 +81,12 @@
     START_TIME,
     STOP_TIME,
     AGENT_ACTIVE,
+    BACKEND,
+    STATUS,
+    DESCRIPTION,
+
+    AGENT_INFO_BACKEND_STATUS_ACTIVE,
+    AGENT_INFO_BACKEND_STATUS_INACTIVE,
 
     COLUMN_HEADER_HOST_ID,
     COLUMN_HEADER_HOST,
@@ -95,6 +102,7 @@
 
     HOSTID_REQUIRED_MESSAGE,
     VMID_REQUIRED_MESSAGE,
+    AGENT_ID_REQUIRED_MESSAGE,
     
     PURGING_AGENT_DATA,
     STORAGE_UNAVAILABLE,
--- a/client/cli/src/main/resources/com/redhat/thermostat/client/cli/strings.properties	Wed Nov 05 10:36:15 2014 -0500
+++ b/client/cli/src/main/resources/com/redhat/thermostat/client/cli/strings.properties	Wed Nov 05 15:13:32 2014 -0500
@@ -6,6 +6,7 @@
 VM_CPU_SERVICE_NOT_AVAILABLE = Unable to access vm cpu information (VmCpuStats not available)
 VM_MEMORY_SERVICE_NOT_AVAILABLE = Unable to access vm memory information (VmCpuStats not available)
 AGENT_SERVICE_UNAVAILABLE = Agent Info Service is Unavailable (AgentInfoDAO)
+BACKEND_SERVICE_UNAVAILABLE = Backend Service is Unavailable (BackendInfoDAO)
 
 COMMAND_CONNECT_ALREADY_CONNECTED = Already connected to storage: URL = {0}\nPlease use disconnect command to disconnect.
 COMMAND_CONNECT_FAILED_TO_CONNECT = Could not connect to db {0}
@@ -38,6 +39,12 @@
 START_TIME = Start Time
 STOP_TIME = Stop Time
 AGENT_ACTIVE = Currently Active
+BACKEND = Backend
+STATUS = Status
+DESCRIPTION = Description
+
+AGENT_INFO_BACKEND_STATUS_ACTIVE = Active
+AGENT_INFO_BACKEND_STATUS_INACTIVE = Inactive
 
 
 COLUMN_HEADER_HOST_ID = HOST_ID
@@ -54,6 +61,7 @@
 
 HOSTID_REQUIRED_MESSAGE = a hostId is required
 VMID_REQUIRED_MESSAGE = a vmId is required
+AGENT_ID_REQUIRED_MESSAGE = an agentId is required
 
 PURGING_AGENT_DATA = Purging data for agent: 
 STORAGE_UNAVAILABLE = Storage is unavailable
--- a/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/ActivatorTest.java	Wed Nov 05 10:36:15 2014 -0500
+++ b/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/ActivatorTest.java	Wed Nov 05 15:13:32 2014 -0500
@@ -94,6 +94,7 @@
         assertCommandIsRegistered(ctx, "vm-info", VMInfoCommand.class);
         assertCommandIsRegistered(ctx, "vm-stat", VMStatCommand.class);
         assertCommandIsRegistered(ctx, "list-agents", ListAgentsCommand.class);
+        assertCommandIsRegistered(ctx, "agent-info", AgentInfoCommand.class);
 
         activator.stop(ctx);
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/AgentInfoCommandTest.java	Wed Nov 05 15:13:32 2014 -0500
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2012-2014 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.client.cli.internal;
+
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.text.DateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.redhat.thermostat.common.cli.CommandContext;
+import com.redhat.thermostat.common.cli.CommandException;
+import com.redhat.thermostat.common.cli.CommandLineArgumentParseException;
+import com.redhat.thermostat.common.cli.SimpleArguments;
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.storage.core.HostRef;
+import com.redhat.thermostat.storage.dao.AgentInfoDAO;
+import com.redhat.thermostat.storage.dao.BackendInfoDAO;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.BackendInformation;
+import com.redhat.thermostat.test.TestCommandContextFactory;
+
+public class AgentInfoCommandTest {
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    private AgentInfoCommand command;
+
+    private TestCommandContextFactory cmdCtxFactory;
+    private AgentInfoDAO agentInfoDAO;
+    private BackendInfoDAO backendInfoDAO;
+
+    private HostRef hostRef;
+
+    private DateFormat dateFormat;
+
+    @Before
+    public void setup() {
+        dateFormat = DateFormat.getDateTimeInstance();
+
+        cmdCtxFactory = new TestCommandContextFactory();
+        agentInfoDAO = mock(AgentInfoDAO.class);
+        backendInfoDAO = mock(BackendInfoDAO.class);
+
+        hostRef = new HostRef("liveAgent", "dummy");
+        command = new AgentInfoCommand();
+    }
+
+    @Test
+    public void testGetAgentInfo() throws CommandException {
+        String idOne = "liveAgent";
+        String address = "configListenAddress";
+        long startTime = 0;
+        long stopTime = 1;
+
+        AgentInformation agentOne = new AgentInformation();
+        agentOne.setAgentId(idOne);
+        agentOne.setConfigListenAddress(address);
+        agentOne.setStartTime(startTime);
+        agentOne.setStopTime(stopTime);
+
+        when(agentInfoDAO.getAgentInformation(hostRef)).thenReturn(agentOne);
+
+        String backendName = "backendInfo";
+        boolean status = true;
+        String backendDescription = "description";
+
+        BackendInformation backendInformation = mock(BackendInformation.class);
+        when(backendInformation.getName()).thenReturn(backendName);
+        when(backendInformation.isActive()).thenReturn(status);
+        when(backendInformation.getDescription()).thenReturn(backendDescription);
+        when(backendInfoDAO.getBackendInformation(hostRef)).thenReturn(Arrays.asList(new BackendInformation[] {backendInformation}));
+
+        setupServices();
+        CommandContext context = setupArgs(idOne);
+
+        command.run(context);
+
+        String output = cmdCtxFactory.getOutput();
+
+        verifyTitles(output);
+        verifyAgentContent(output, idOne, address, startTime, stopTime);
+        verifyBackendContent(output, backendName, status, backendDescription);
+    }
+
+    private void verifyTitles(String output) {
+        String AGENT_ID = translator.localize(LocaleResources.AGENT_ID).getContents();
+        String CONFIG_LISTEN_ADDRESS = translator.localize(LocaleResources.CONFIG_LISTEN_ADDRESS).getContents();
+        String START_TIME = translator.localize(LocaleResources.START_TIME).getContents();
+        String STOP_TIME = translator.localize(LocaleResources.STOP_TIME).getContents();
+
+        String BACKEND = translator.localize(LocaleResources.BACKEND).getContents();
+        String STATUS = translator.localize(LocaleResources.STATUS).getContents();
+
+
+        assertTrue(output.contains(AGENT_ID));
+        assertTrue(output.contains(CONFIG_LISTEN_ADDRESS));
+        assertTrue(output.contains(START_TIME));
+        assertTrue(output.contains(STOP_TIME));
+
+        assertTrue(output.contains(BACKEND));
+        assertTrue(output.contains(STATUS));
+    }
+
+    private void verifyAgentContent(String output, String agentId, String address, long startTime, long stopTime) {
+        assertTrue(output.contains(agentId));
+        assertTrue(output.contains(address));
+        assertTrue(output.contains(dateFormat.format(new Date(startTime))));
+        assertTrue(output.contains(dateFormat.format(new Date(stopTime))));
+    }
+
+    private void verifyBackendContent(String output, String name, boolean status, String description) {
+        String statusString = status ?
+                translator.localize(LocaleResources.AGENT_INFO_BACKEND_STATUS_ACTIVE).getContents()
+                : translator.localize(LocaleResources.AGENT_INFO_BACKEND_STATUS_INACTIVE).getContents();
+
+        assertTrue(output.contains(name));
+        assertTrue(output.contains(statusString));
+        assertTrue(output.contains(description));
+    }
+
+    @Test(expected = CommandException.class)
+    public void testGetNonexistentAgentInfo() throws CommandException {
+        String agentId = "nonexistentAgent";
+
+        setupServices();
+        CommandContext context = setupArgs(agentId);
+
+        command.run(context);
+    }
+
+    @Test(expected = CommandLineArgumentParseException.class)
+    public void testNoArgumentCommand() throws CommandException {
+        setupServices();
+
+        command.run(cmdCtxFactory.createContext(new SimpleArguments()));
+    }
+
+    @Test(expected = CommandException.class)
+    public void testListAgentsWithoutServices() throws CommandException {
+        CommandContext context = cmdCtxFactory.createContext(new SimpleArguments());
+
+        command.run(context);
+    }
+
+
+    private void setupServices() {
+        command.setServices(agentInfoDAO, backendInfoDAO);
+    }
+
+    private CommandContext setupArgs(String agentId) {
+        SimpleArguments args = new SimpleArguments();
+        args.addArgument("agentId", agentId);
+
+        return cmdCtxFactory.createContext(args);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/client/cli/src/test/java/com/redhat/thermostat/client/cli/internal/AgentInfoFormatterTest.java	Wed Nov 05 15:13:32 2014 -0500
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012-2014 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.client.cli.internal;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.text.DateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+import org.junit.Test;
+
+import com.redhat.thermostat.shared.locale.Translate;
+import com.redhat.thermostat.storage.model.AgentInformation;
+import com.redhat.thermostat.storage.model.BackendInformation;
+
+public class AgentInfoFormatterTest {
+    private static final Translate<LocaleResources> translator = LocaleResources.createLocalizer();
+
+    private final String AGENT_ID = translator.localize(LocaleResources.AGENT_ID).getContents();
+    private final String CONFIG_LISTEN_ADDRESS = translator.localize(LocaleResources.CONFIG_LISTEN_ADDRESS).getContents();
+    private final String START_TIME = translator.localize(LocaleResources.START_TIME).getContents();
+    private final String STOP_TIME = translator.localize(LocaleResources.STOP_TIME).getContents();
+
+    private final String BACKEND = translator.localize(LocaleResources.BACKEND).getContents();
+    private final String STATUS = translator.localize(LocaleResources.STATUS).getContents();
+
+    private final AgentInfoFormatter formatter = new AgentInfoFormatter();
+
+    private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    private final PrintStream out = new PrintStream(baos);
+
+
+    @Test
+    public void testPrintAgentInfo() {
+        String agentId = "liveAgent";
+        String address = "configListenAddress";
+        long startTime = 0;
+        long stopTime = 1;
+
+        AgentInformation info = new AgentInformation();
+        info.setAgentId(agentId);
+        info.setConfigListenAddress(address);
+        info.setStartTime(startTime);
+        info.setStopTime(stopTime);
+
+        String backendName = "backendInfo";
+        boolean status = true;
+        String backendDescription = "description";
+        BackendInformation backendInformation = mock(BackendInformation.class);
+        when(backendInformation.getName()).thenReturn(backendName);
+        when(backendInformation.isActive()).thenReturn(status);
+        when(backendInformation.getDescription()).thenReturn(backendDescription);
+
+        formatter.addAgent(info, Arrays.asList(new BackendInformation[] {backendInformation}));
+        formatter.format(out);
+
+        String output = new String(baos.toByteArray());
+
+        DateFormat dateFormat = DateFormat.getDateTimeInstance();
+
+        assertTrue(output.contains(AGENT_ID));
+        assertTrue(output.contains(CONFIG_LISTEN_ADDRESS));
+        assertTrue(output.contains(START_TIME));
+        assertTrue(output.contains(STOP_TIME));
+
+        assertTrue(output.contains(BACKEND));
+        assertTrue(output.contains(STATUS));
+
+
+        assertTrue(output.contains(agentId));
+        assertTrue(output.contains(address));
+        assertTrue(output.contains(dateFormat.format(new Date(startTime))));
+        assertTrue(output.contains(dateFormat.format(new Date(stopTime))));
+
+        String statusString = status ?
+                translator.localize(LocaleResources.AGENT_INFO_BACKEND_STATUS_ACTIVE).getContents()
+                : translator.localize(LocaleResources.AGENT_INFO_BACKEND_STATUS_INACTIVE).getContents();
+
+        assertTrue(output.contains(backendName));
+        assertTrue(output.contains(statusString));
+        assertTrue(output.contains(backendDescription));
+    }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/distribution/config/commands/agent-info.properties	Wed Nov 05 15:13:32 2014 -0500
@@ -0,0 +1,27 @@
+bundles = com.redhat.thermostat.client.cli=${project.version}, \
+          com.redhat.thermostat.storage.mongodb=${project.version}, \
+          com.redhat.thermostat.web.common=${project.version}, \
+          com.redhat.thermostat.web.client=${project.version}, \
+          org.apache.httpcomponents.httpcore=${httpcomponents.core.version}, \
+          org.apache.httpcomponents.httpclient=${httpcomponents.client.version}, \
+          ${osgi.compendium.bundle.symbolic-name}=${osgi.compendium.osgi-version}, \
+          com.google.gson=${gson.version}, \
+          org.mongodb.mongo-java-driver=${mongo-driver.osgi-version}, \
+          org.apache.commons.beanutils=${commons-beanutils.version}, \
+          org.apache.commons.codec=${commons-codec.osgi-version}, \
+          org.apache.commons.collections=${commons-collections.version}, \
+          org.apache.commons.logging=${commons-logging.version}, \
+
+description = shows info for specified agent
+
+usage = agent-info [-d <url>] [-l <level>] -a <agent-id>
+
+options = agentId, AUTO_DB_OPTIONS, AUTO_LOG_OPTION
+
+agentId.short = a
+agentId.long = agentId
+agentId.hasarg = true
+agentId.required = true
+agentId.description = agent ID
+
+environments = cli, shell