changeset 2735:08726e4b6d43

Convert Agent-Core to Declarative Services This patch also merges the agent-cli and agent-core bundles. Reviewed-by: jerboaa, neugens Review-Thread: http://icedtea.classpath.org/pipermail/thermostat/2017-August/024462.html
author Joshua Matsuoka <jmatsuok@redhat.com>
date Fri, 11 Aug 2017 14:03:38 -0400
parents 90361ef466fb
children 9cca55959e18
files agent/cli/pom.xml agent/cli/src/main/java/com/redhat/thermostat/agent/cli/internal/Activator.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/internal/AgentApplication.java agent/cli/src/main/java/com/redhat/thermostat/agent/cli/internal/locale/LocaleResources.java agent/cli/src/main/resources/com/redhat/thermostat/agent/cli/internal/strings.properties agent/cli/src/test/java/com/redhat/thermostat/agent/cli/internal/ActivatorTest.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/internal/AgentApplicationTest.java agent/cli/src/test/java/com/redhat/thermostat/agent/cli/internal/locale/TranslateTest.java agent/core/pom.xml agent/core/src/main/java/com/redhat/thermostat/agent/http/HttpRequestService.java agent/core/src/main/java/com/redhat/thermostat/agent/internal/Activator.java agent/core/src/main/java/com/redhat/thermostat/agent/internal/AgentApplication.java agent/core/src/main/java/com/redhat/thermostat/agent/internal/AgentInfoDAOImpl.java agent/core/src/main/java/com/redhat/thermostat/agent/internal/BackendInfoDAOImpl.java agent/core/src/main/java/com/redhat/thermostat/agent/internal/locale/LocaleResources.java agent/core/src/main/resources/com/redhat/thermostat/agent/internal/strings.properties agent/core/src/test/java/com/redhat/thermostat/agent/http/HttpRequestServiceTest.java agent/core/src/test/java/com/redhat/thermostat/agent/internal/ActivatorTest.java agent/core/src/test/java/com/redhat/thermostat/agent/internal/AgentApplicationTest.java agent/core/src/test/java/com/redhat/thermostat/agent/internal/locale/TranslateTest.java agent/pom.xml distribution/assembly/core-assembly-macosx.xml distribution/assembly/core-assembly-windows.xml distribution/assembly/core-assembly.xml distribution/config/commands/agent.properties distribution/pom.xml
diffstat 26 files changed, 720 insertions(+), 1181 deletions(-) [+]
line wrap: on
line diff
--- a/agent/cli/pom.xml	Thu Aug 10 09:41:24 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,135 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!--
-
- Copyright 2012-2017 Red Hat, Inc.
-
- This file is part of Thermostat.
-
- Thermostat is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published
- by the Free Software Foundation; either version 2, or (at your
- option) any later version.
-
- Thermostat is distributed in the hope that it will be useful, but
- WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with Thermostat; see the file COPYING.  If not see
- <http://www.gnu.org/licenses/>.
-
- Linking this code with other modules is making a combined work
- based on this code.  Thus, the terms and conditions of the GNU
- General Public License cover the whole combination.
-
- As a special exception, the copyright holders of this code give
- you permission to link this code with independent modules to
- produce an executable, regardless of the license terms of these
- independent modules, and to copy and distribute the resulting
- executable under terms of your choice, provided that you also
- meet, for each linked independent module, the terms and conditions
- of the license of that module.  An independent module is a module
- which is not derived from or based on this code.  If you modify
- this code, you may extend this exception to your version of the
- library, but you are not obligated to do so.  If you do not wish
- to do so, delete this exception statement from your version.
-
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <modelVersion>4.0.0</modelVersion>
-
-  <parent>
-    <groupId>com.redhat.thermostat</groupId>
-    <artifactId>thermostat-agent</artifactId>
-    <version>1.99.12-SNAPSHOT</version>
-  </parent>
-
-  <artifactId>thermostat-agent-cli</artifactId>
-  <packaging>bundle</packaging>
-
-  <name>Thermostat Agent Command Line</name>
-
-  <dependencies>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.mockito</groupId>
-      <artifactId>mockito-core</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.powermock</groupId>
-      <artifactId>powermock-api-mockito</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.powermock</groupId>
-      <artifactId>powermock-module-junit4</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-agent-core</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-common-core</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-launcher</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-process-handler</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>org.osgi</groupId>
-      <artifactId>org.osgi.core</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-common-test</artifactId>
-      <version>${project.version}</version>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-storage-core</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-  </dependencies>
-
-  <build>
-    <plugins>
-      <plugin>
-        <groupId>org.apache.felix</groupId>
-        <artifactId>maven-bundle-plugin</artifactId>
-        <extensions>true</extensions>
-        <configuration>
-          <instructions>
-            <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
-            <Bundle-Activator>com.redhat.thermostat.agent.cli.internal.Activator</Bundle-Activator>
-            <Bundle-SymbolicName>com.redhat.thermostat.agent.cli</Bundle-SymbolicName>
-            <Private-Package>
-              com.redhat.thermostat.agent.cli.internal,
-              com.redhat.thermostat.agent.cli.internal.locale,
-            </Private-Package>
-            <!-- Do not autogenerate uses clauses in Manifests -->
-            <_nouses>true</_nouses>
-          </instructions>
-        </configuration>
-      </plugin>
-    </plugins>
-  </build>
-
-</project>
-
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/internal/Activator.java	Thu Aug 10 09:41:24 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-/*
- * Copyright 2012-2017 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.cli.internal;
-
-import com.redhat.thermostat.agent.dao.AgentInfoDAO;
-import com.redhat.thermostat.agent.dao.BackendInfoDAO;
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-
-import com.redhat.thermostat.common.ExitStatus;
-import com.redhat.thermostat.common.MultipleServiceTracker;
-import com.redhat.thermostat.common.MultipleServiceTracker.Action;
-import com.redhat.thermostat.common.MultipleServiceTracker.DependencyProvider;
-import com.redhat.thermostat.common.cli.CommandRegistry;
-import com.redhat.thermostat.common.cli.CommandRegistryImpl;
-import com.redhat.thermostat.shared.config.SSLConfiguration;
-import com.redhat.thermostat.storage.core.WriterID;
-
-public class Activator implements BundleActivator {
-
-    private CommandRegistry reg;
-    private AgentApplication agentApplication;
-    private MultipleServiceTracker tracker;
-
-    @Override
-    public void start(final BundleContext context) throws Exception {
-        reg = new CommandRegistryImpl(context);
-        
-        Class<?>[] deps = new Class<?>[] {
-                ExitStatus.class,
-                WriterID.class,
-                SSLConfiguration.class,
-                AgentInfoDAO.class,
-                BackendInfoDAO.class,
-        };
-        tracker = new MultipleServiceTracker(context, deps, new Action() {
-            
-            @Override
-            public void dependenciesAvailable(DependencyProvider services) {
-                ExitStatus exitStatus = services.get(ExitStatus.class);
-                WriterID writerID = services.get(WriterID.class);
-                agentApplication = new AgentApplication(context, exitStatus, writerID);
-                reg.registerCommand("agent", agentApplication);
-            }
-
-            @Override
-            public void dependenciesUnavailable() {
-                agentApplication.shutdown(ExitStatus.EXIT_SUCCESS);
-                reg.unregisterCommands();
-            }
-        });
-        tracker.open();
-    }
-
-    @Override
-    public void stop(BundleContext context) throws Exception {
-        if (agentApplication != null) {
-            // Bundle may be shut down *before* deps become available and
-            // app is set.
-            agentApplication.shutdown(ExitStatus.EXIT_SUCCESS);
-        }
-        reg.unregisterCommands();
-        tracker.close();
-    }
-}
-
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/internal/AgentApplication.java	Thu Aug 10 09:41:24 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,275 +0,0 @@
-/*
- * Copyright 2012-2017 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.cli.internal;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import com.redhat.thermostat.agent.dao.AgentInfoDAO;
-import com.redhat.thermostat.agent.dao.BackendInfoDAO;
-import org.osgi.framework.BundleContext;
-import org.osgi.util.tracker.ServiceTracker;
-
-import com.redhat.thermostat.agent.Agent;
-import com.redhat.thermostat.agent.config.AgentConfigsUtils;
-import com.redhat.thermostat.agent.config.AgentOptionParser;
-import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
-import com.redhat.thermostat.backend.BackendRegistry;
-import com.redhat.thermostat.backend.BackendService;
-import com.redhat.thermostat.common.ExitStatus;
-import com.redhat.thermostat.common.LaunchException;
-import com.redhat.thermostat.common.MultipleServiceTracker;
-import com.redhat.thermostat.common.MultipleServiceTracker.Action;
-import com.redhat.thermostat.common.MultipleServiceTracker.DependencyProvider;
-import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand;
-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.tools.ApplicationState;
-import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.shared.config.InvalidConfigurationException;
-import com.redhat.thermostat.storage.core.WriterID;
-import sun.misc.Signal;
-import sun.misc.SignalHandler;
-
-@SuppressWarnings("restriction")
-public final class AgentApplication extends AbstractStateNotifyingCommand {
-
-    /**
-     * Property for turning on verbose mode. This is there so as to be able to
-     * run integration tests independent of log levels.
-     */
-    private static final String VERBOSE_MODE_PROPERTY = "thermostat.agent.verbose";
-    // Messages printed in verbose mode. Integration tests use this. Be careful
-    // when you change those!
-    private static final String VERBOSE_MODE_AGENT_STOPPED_MSG = "Agent stopped.";
-    private static final String VERBOSE_MODE_AGENT_STARTED_MSG = "Agent started.";
-
-    private static final String SIGINT_NAME = "INT";
-    private static final String SIGTERM_NAME = "TERM";
-
-    private static final Logger logger = LoggingUtils.getLogger(AgentApplication.class);
-    
-    private final BundleContext bundleContext;
-    private final ConfigurationCreator configurationCreator;
-
-    private AgentStartupConfiguration configuration;
-    private AgentOptionParser parser;
-    @SuppressWarnings("rawtypes")
-    private ServiceTracker configServerTracker;
-    private MultipleServiceTracker depTracker;
-    private final ExitStatus exitStatus;
-    private final WriterID writerId;
-    private CountDownLatch shutdownLatch;
-
-    private CustomSignalHandler handler;
-
-    public AgentApplication(BundleContext bundleContext, ExitStatus exitStatus, WriterID writerId) {
-        this(bundleContext, exitStatus, writerId, new ConfigurationCreator());
-    }
-
-    AgentApplication(BundleContext bundleContext, ExitStatus exitStatus, WriterID writerId, ConfigurationCreator configurationCreator) {
-        this.bundleContext = bundleContext;
-        this.configurationCreator = configurationCreator;
-        this.exitStatus = exitStatus;
-        this.writerId = writerId;
-    }
-    
-    private void parseArguments(Arguments args) throws InvalidConfigurationException {
-        parser = new AgentOptionParser(configuration, args);
-        parser.parse();
-    }
-    
-    private void runAgent(CommandContext ctx) throws CommandException {
-        long startTime = System.currentTimeMillis();
-        configuration.setStartTime(startTime);
-
-        shutdownLatch = new CountDownLatch(1);
-        prepareAgent();
-        
-        try {
-            // Wait for either SIGINT or SIGTERM
-            shutdownLatch.await();
-            logger.fine("terminating agent cmd");
-        } catch (InterruptedException e) {
-            // Ensure proper shutdown if interrupted
-            handler.handle(new Signal(SIGINT_NAME));
-            return;
-        }
-    }
-
-    @Override
-    public void run(CommandContext ctx) throws CommandException {
-        configuration = configurationCreator.create();
-
-        parseArguments(ctx.getArguments());
-        if (!parser.isHelp()) {
-            runAgent(ctx);
-        }
-    }
-    
-    public void shutdown(int shutDownStatus) {
-        // Exit application
-        if (shutdownLatch != null) {
-            shutdownLatch.countDown();
-        }
-        
-        if (depTracker != null) {
-            depTracker.close();
-        }
-        if (configServerTracker != null) {
-            configServerTracker.close();
-        }
-        this.exitStatus.setExitStatus(shutDownStatus);
-        if (shutDownStatus == ExitStatus.EXIT_SUCCESS) {
-            getNotifier().fireAction(ApplicationState.STOP);
-        } else {
-            getNotifier().fireAction(ApplicationState.FAIL);
-        }
-    }
-    
-    private class CustomSignalHandler implements SignalHandler {
-        
-        private Agent agent;
-
-        public CustomSignalHandler(Agent agent) {
-            this.agent = agent;
-        }
-        
-        @Override
-        public void handle(Signal arg0) {
-            try {
-                agent.stop();
-            } catch (Exception ex) {
-                // We don't want any exception to hold back the signal handler, otherwise
-                // there will be no way to actually stop Thermostat.
-                ex.printStackTrace();
-            }
-            logger.fine("Agent stopped.");
-            // Hook for integration tests. Print a well known message to stdout
-            // if verbose mode is turned on via the system property.
-            if (Boolean.getBoolean(VERBOSE_MODE_PROPERTY)) {
-                System.out.println(VERBOSE_MODE_AGENT_STOPPED_MSG);
-            }
-            shutdown(ExitStatus.EXIT_SUCCESS);
-        }
-        
-    }
-
-    Agent startAgent(AgentInfoDAO agentInfoDAO, BackendInfoDAO backendInfoDAO) {
-        BackendRegistry backendRegistry = null;
-        try {
-            backendRegistry = new BackendRegistry(bundleContext);
-            
-        } catch (Exception e) {
-            logger.log(Level.SEVERE, "Could not get BackendRegistry instance.", e);
-            shutdown(ExitStatus.EXIT_ERROR);
-            // Since this would throw NPE's down the line if we continue in this
-            // method, let's fail right and early :)
-            throw new RuntimeException(e);
-        }
-
-        final Agent agent = new Agent(backendRegistry, configuration, agentInfoDAO, backendInfoDAO, writerId);
-        try {
-            logger.fine("Starting agent.");
-            agent.start();
-            
-            bundleContext.registerService(BackendService.class, new BackendService(), null);
-            
-        } catch (LaunchException le) {
-            logger.log(Level.SEVERE,
-                    "Agent could not start, probably because a configured backend could not be activated.",
-                    le);
-            shutdown(ExitStatus.EXIT_ERROR);
-        }
-        logger.fine("Agent started.");
-        // Hook for integration tests. Print a well known message to stdout
-        // if verbose mode is turned on via the system property.
-        if (Boolean.getBoolean(VERBOSE_MODE_PROPERTY)) {
-            System.out.println(VERBOSE_MODE_AGENT_STARTED_MSG);
-        }
-
-        logger.info("Agent id: " + agent.getId());
-        getNotifier().fireAction(ApplicationState.START, agent.getId());
-        return agent;
-    }
-    
-    private void prepareAgent() {
-        Class<?>[] deps = new Class<?>[] {
-                AgentInfoDAO.class,
-                BackendInfoDAO.class
-        };
-        depTracker = new MultipleServiceTracker(bundleContext, deps, new Action() {
-
-            @Override
-            public void dependenciesAvailable(DependencyProvider services) {
-                AgentInfoDAO agentInfoDAO = services.get(AgentInfoDAO.class);
-                BackendInfoDAO backendInfoDAO = services.get(BackendInfoDAO.class);
-
-                Agent agent = startAgent(agentInfoDAO, backendInfoDAO);
-                handler = new CustomSignalHandler(agent);
-                Signal.handle(new Signal(SIGINT_NAME), handler);
-                Signal.handle(new Signal(SIGTERM_NAME), handler);
-            }
-
-            @Override
-            public void dependenciesUnavailable() {
-                if (shutdownLatch.getCount() > 0) {
-                    // In the rare case we lose one of our deps, gracefully shutdown
-                    logger.severe("Dependencies unexpectedly became unavailable");
-                    shutdown(ExitStatus.EXIT_ERROR);
-                }
-            }
-            
-        });
-        depTracker.open();
-    }
-
-    static class ConfigurationCreator {
-        public AgentStartupConfiguration create() throws InvalidConfigurationException {
-            return AgentConfigsUtils.createAgentConfigs();
-        }
-    }
-
-    @Override
-    public boolean isStorageRequired() {
-        return false;
-    }
-    
-}
-
--- a/agent/cli/src/main/java/com/redhat/thermostat/agent/cli/internal/locale/LocaleResources.java	Thu Aug 10 09:41:24 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,56 +0,0 @@
-/*
- * Copyright 2012-2017 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.cli.internal.locale;
-
-import com.redhat.thermostat.shared.locale.Translate;
-
-public enum LocaleResources {
-
-    SERVICE_FAILED_TO_START_DB,
-    LAUNCHER_UNAVAILABLE,
-    UNEXPECTED_RESULT_STORAGE,
-    STARTING_AGENT_FAILED,
-    ;
-
-    static final String RESOURCE_BUNDLE = "com.redhat.thermostat.agent.cli.internal.strings";
-
-    public static Translate<LocaleResources> createLocalizer() {
-        return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class);
-    }
-
-}
-
--- a/agent/cli/src/main/resources/com/redhat/thermostat/agent/cli/internal/strings.properties	Thu Aug 10 09:41:24 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4 +0,0 @@
-SERVICE_FAILED_TO_START_DB = Service failed to start due to error starting storage.
-LAUNCHER_UNAVAILABLE = Launcher is not available
-UNEXPECTED_RESULT_STORAGE = Unexpected result from storage.
-STARTING_AGENT_FAILED = Thermostat agent failed to start. See logs for details.
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/internal/ActivatorTest.java	Thu Aug 10 09:41:24 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-/*
- * Copyright 2012-2017 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.cli.internal;
-
-import static com.redhat.thermostat.testutils.Asserts.assertCommandIsRegistered;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
-
-import com.redhat.thermostat.agent.dao.AgentInfoDAO;
-import com.redhat.thermostat.agent.dao.BackendInfoDAO;
-import org.junit.Test;
-
-import com.redhat.thermostat.common.ExitStatus;
-import com.redhat.thermostat.shared.config.SSLConfiguration;
-import com.redhat.thermostat.storage.core.WriterID;
-import com.redhat.thermostat.testutils.StubBundleContext;
-
-public class ActivatorTest {
-
-    @Test
-    public void verifyActivatorRegistersCommands() throws Exception {        
-        StubBundleContext bundleContext = new StubBundleContext();
-
-        ExitStatus exitStatus = mock(ExitStatus.class);
-        WriterID writerID = mock(WriterID.class);
-        bundleContext.registerService(WriterID.class, writerID, null);
-        bundleContext.registerService(ExitStatus.class, exitStatus, null);
-        bundleContext.registerService(SSLConfiguration.class, mock(SSLConfiguration.class), null);
-        bundleContext.registerService(AgentInfoDAO.class, mock(AgentInfoDAO.class), null);
-        bundleContext.registerService(BackendInfoDAO.class, mock(BackendInfoDAO.class), null);
-        
-        Activator activator = new Activator();
-
-        assertEquals(0, bundleContext.getServiceListeners().size());
-        
-        activator.start(bundleContext);
-        
-        assertEquals(5, bundleContext.getServiceListeners().size());
-        
-        assertCommandIsRegistered(bundleContext, "agent", AgentApplication.class);
-
-        activator.stop(bundleContext);
-
-        assertEquals(0, bundleContext.getServiceListeners().size());
-        assertEquals(5, bundleContext.getAllServices().size());
-    }
-}
-
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/internal/AgentApplicationTest.java	Thu Aug 10 09:41:24 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,228 +0,0 @@
-/*
- * Copyright 2012-2017 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.cli.internal;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.powermock.api.mockito.PowerMockito.whenNew;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.Filter;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.InvalidSyntaxException;
-import org.powermock.api.mockito.PowerMockito;
-import org.powermock.core.classloader.annotations.PrepareForTest;
-import org.powermock.modules.junit4.PowerMockRunner;
-
-import com.redhat.thermostat.agent.Agent;
-import com.redhat.thermostat.agent.cli.internal.AgentApplication.ConfigurationCreator;
-import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
-import com.redhat.thermostat.agent.dao.AgentInfoDAO;
-import com.redhat.thermostat.agent.dao.BackendInfoDAO;
-import com.redhat.thermostat.backend.BackendRegistry;
-import com.redhat.thermostat.common.ExitStatus;
-import com.redhat.thermostat.common.LaunchException;
-import com.redhat.thermostat.common.Version;
-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.shared.config.InvalidConfigurationException;
-import com.redhat.thermostat.storage.core.WriterID;
-import com.redhat.thermostat.testutils.StubBundleContext;
-
-@RunWith(PowerMockRunner.class)
-public class AgentApplicationTest {
-
-    private StubBundleContext context;
-
-    private ConfigurationCreator configCreator;
-    private ExitStatus exitStatus;
-    private WriterID writerId;
-    
-    @Before
-    public void setUp() throws InvalidConfigurationException {
-        
-        context = new StubBundleContext();
-        
-        AgentStartupConfiguration config = mock(AgentStartupConfiguration.class);
-        when(config.getDBConnectionString()).thenReturn("test string; please ignore");
-
-        configCreator = mock(ConfigurationCreator.class);
-        when(configCreator.create()).thenReturn(config);
-
-        AgentInfoDAO agentInfoDAO = mock(AgentInfoDAO.class);
-        context.registerService(AgentInfoDAO.class.getName(), agentInfoDAO, null);
-        BackendInfoDAO backendInfoDAO = mock(BackendInfoDAO.class);
-        context.registerService(BackendInfoDAO.class.getName(), backendInfoDAO, null);
-        writerId = mock(WriterID.class);
-
-        exitStatus = mock(ExitStatus.class);
-    }
-
-    @After
-    public void tearDown() {
-        context = null;
-        configCreator = null;
-        exitStatus = null;
-    }
-
-    @PrepareForTest({ FrameworkUtil.class, Agent.class })
-    @Test
-    public void testAgentStartup() throws CommandException, InterruptedException, InvalidSyntaxException {
-        final AgentApplication agent = new AgentApplication(context, exitStatus, writerId, configCreator);
-        final CountDownLatch latch = new CountDownLatch(1);
-        final CommandException[] ce = new CommandException[1];
-        final long timeoutMillis = 5000L;
-        
-        Bundle mockBundle = createBundle();
-        PowerMockito.mockStatic(FrameworkUtil.class);
-        when(FrameworkUtil.getBundle(Agent.class)).thenReturn(mockBundle);
-        when(FrameworkUtil.createFilter(any(String.class))).thenReturn(mock(Filter.class));
-        
-        startAgentRunThread(timeoutMillis, agent, ce, latch);
-        
-        boolean ret = latch.await(timeoutMillis, TimeUnit.MILLISECONDS);
-        if (ce[0] != null) {
-            throw ce[0];
-        }
-        if (!ret) {
-            fail("Timeout expired!");
-        }
-    }
-    
-    private Bundle createBundle() {
-        String qualifier = "201207241700";
-        Bundle sysBundle = mock(Bundle.class);
-        org.osgi.framework.Version ver = org.osgi.framework.Version
-                .parseVersion(String.format(Version.VERSION_NUMBER_FORMAT,
-                        1, 2, 3) + "." + qualifier);
-        when(sysBundle.getVersion()).thenReturn(ver);
-        when(sysBundle.getBundleContext()).thenReturn(context);
-        return sysBundle;
-    }
-    
-    /*
-     * Having the PrepareForTest annotation on method level does not seem to
-     * deadlock the test, which seems to be more or less reliably reproducible
-     * if this annotation is at class level instead. Steps to reproduce the
-     * deadlock is:
-     * 1. Attach the PrepareForTest annotation to the class (over the test
-     *    method)
-     * 2. Run the test multiple times. 5-20 times seemed sufficient for me to
-     *    make the deadlock show up. This deadlock does not seem to happen
-     *    otherwise (can run up to 30 times head-to-head without deadlock).
-     *    
-     */
-    @PrepareForTest({ AgentApplication.class })
-    @SuppressWarnings("unchecked")
-    @Test
-    public void verifyBackendRegistryProblemsSetsExitStatus() throws Exception {
-        whenNew(BackendRegistry.class).withParameterTypes(BundleContext.class)
-                .withArguments(any(BundleContext.class))
-                .thenThrow(InvalidSyntaxException.class);
-        final AgentApplication agent = new AgentApplication(context,
-                exitStatus, writerId, configCreator);
-        try {
-            agent.startAgent(null, null);
-        } catch (RuntimeException e) {
-            assertEquals(InvalidSyntaxException.class, e.getCause().getClass());
-        }
-        verify(exitStatus).setExitStatus(ExitStatus.EXIT_ERROR);
-    }
-    
-    @PrepareForTest({ AgentApplication.class })
-    @Test
-    public void verifyAgentLaunchExceptionSetsExitStatus() throws Exception {
-        whenNew(BackendRegistry.class).withParameterTypes(BundleContext.class)
-                .withArguments(any(BundleContext.class))
-                .thenReturn(mock(BackendRegistry.class));
-        Agent mockAgent = mock(Agent.class);
-        whenNew(Agent.class).withParameterTypes(BackendRegistry.class,
-                AgentStartupConfiguration.class,
-                AgentInfoDAO.class, BackendInfoDAO.class, WriterID.class).withArguments(
-                any(BackendRegistry.class),
-                any(AgentStartupConfiguration.class),
-                any(AgentInfoDAO.class), any(BackendInfoDAO.class), 
-                any(WriterID.class)).thenReturn(mockAgent);
-        doThrow(LaunchException.class).when(mockAgent).start();
-        final AgentApplication agent = new AgentApplication(context,
-                exitStatus, writerId,  configCreator);
-        try {
-            agent.startAgent(null, null);
-        } catch (RuntimeException e) {
-            fail("Should not have thrown RuntimeException");
-        }
-        verify(exitStatus).setExitStatus(ExitStatus.EXIT_ERROR);
-    }
-
-    private void startAgentRunThread(final long timoutMillis, final AgentApplication agent, final CommandException[] ce, final CountDownLatch latch) throws InterruptedException {
-        Arguments args = mock(Arguments.class);
-        final CommandContext commandContext = mock(CommandContext.class);
-        when(commandContext.getArguments()).thenReturn(args);
-        
-        // Run agent in a new thread so we can timeout on failure
-        Thread t = new Thread(new Runnable() {
-            
-            @Override
-            public void run() {
-                try {
-                    latch.countDown();
-                    agent.run(commandContext);
-                } catch (CommandException e) {
-                    ce[0] = e;
-                }
-            }
-        });
-        
-        t.start();
-    }
-
-}
-
--- a/agent/cli/src/test/java/com/redhat/thermostat/agent/cli/internal/locale/TranslateTest.java	Thu Aug 10 09:41:24 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-/*
- * Copyright 2012-2017 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.cli.internal.locale;
-
-import java.io.IOException;
-import java.util.Locale;
-import java.util.Properties;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-
-public class TranslateTest {
-
-    private Locale lang;
-
-    @Before
-    public void setUp() {
-        this.lang = Locale.getDefault();
-        Locale.setDefault(Locale.US);
-    }
-
-    @After
-    public void tearDown() {
-        Locale.setDefault(lang);
-    }
-
-    @Test
-    public void testLocalizedStringsArePresent() throws IOException {
-
-        String stringsResource = "/" + LocaleResources.RESOURCE_BUNDLE.replace(".", "/") + ".properties";
-
-        Properties props = new Properties();
-        props.load(getClass().getResourceAsStream(stringsResource));
-
-        Assert.assertEquals(LocaleResources.values().length, props.values().size());
-        for (LocaleResources resource : LocaleResources.values()) {
-            Assert.assertTrue("missing property from resource bound file: " + resource,
-                              props.containsKey(resource.name()));
-        }
-    }
-}
-
--- a/agent/core/pom.xml	Thu Aug 10 09:41:24 2017 -0400
+++ b/agent/core/pom.xml	Fri Aug 11 14:03:38 2017 -0400
@@ -62,6 +62,16 @@
       <scope>test</scope>
     </dependency>
     <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-api-mockito</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-module-junit4</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
       <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-common-test</artifactId>
       <version>${project.version}</version>
@@ -117,7 +127,6 @@
           <instructions>
             <Bundle-Vendor>Red Hat, Inc.</Bundle-Vendor>
             <Bundle-SymbolicName>com.redhat.thermostat.agent.core</Bundle-SymbolicName>
-            <Bundle-Activator>com.redhat.thermostat.agent.internal.Activator</Bundle-Activator>
             <Export-Package>
                 com.redhat.thermostat.agent,
                 com.redhat.thermostat.agent.http,
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/http/HttpRequestService.java	Thu Aug 10 09:41:24 2017 -0400
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/http/HttpRequestService.java	Fri Aug 11 14:03:38 2017 -0400
@@ -61,6 +61,7 @@
 import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
 import com.redhat.thermostat.agent.http.internal.keycloak.KeycloakAccessToken;
 import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.shared.config.CommonPaths;
 import com.redhat.thermostat.shared.config.SSLConfiguration;
 
 @Component
@@ -73,6 +74,7 @@
     private static final String KEYCLOAK_CONTENT_TYPE = "application/x-www-form-urlencoded";
 
     private final HttpClientCreator httpClientCreator;
+    private final ConfigCreator configCreator;
     private Gson gson = new GsonBuilder().create();
     private HttpClientFacade client;
     private AgentStartupConfiguration agentStartupConfiguration;
@@ -80,19 +82,22 @@
     private KeycloakAccessToken keycloakAccessToken;
     @Reference
     private SSLConfiguration sslConfig;
+    @Reference
+    private CommonPaths commonPaths;
 
     public HttpRequestService() {
-        this(new HttpClientCreator(), AgentConfigsUtils.createAgentConfigs());
+        this(new HttpClientCreator(), new ConfigCreator());
     }
 
-    HttpRequestService(HttpClientCreator clientCreator, AgentStartupConfiguration agentStartupConfiguration) {
+    HttpRequestService(HttpClientCreator clientCreator, ConfigCreator configCreator) {
         this.httpClientCreator = clientCreator;
-        this.agentStartupConfiguration = agentStartupConfiguration;
+        this.configCreator = configCreator;
     }
 
     @Activate
     public void activate() {
         try {
+            agentStartupConfiguration = configCreator.create(commonPaths);
             client = httpClientCreator.create(sslConfig);
             client.start();
             logger.log(Level.FINE, "HttpRequestService activated");
@@ -205,7 +210,12 @@
         return "grant_type=refresh_token&client_id=" + agentStartupConfiguration.getKeycloakClient() +
                 "&refresh_token=" + keycloakAccessToken.getRefreshToken();
     }
-    
+
+    // Package-private for testing
+   void setConfiguration(AgentStartupConfiguration configuration) {
+        this.agentStartupConfiguration = configuration;
+    }
+
     static class HttpClientCreator {
     
         HttpClientFacade create(SSLConfiguration config) {
@@ -213,6 +223,14 @@
         }
 
     }
+
+    static class ConfigCreator {
+        AgentStartupConfiguration create(CommonPaths commonPaths) {
+            AgentConfigsUtils.setConfigFiles(commonPaths.getSystemAgentConfigurationFile(),
+                    commonPaths.getUserAgentConfigurationFile());
+            return AgentConfigsUtils.createAgentConfigs();
+        }
+    }
     
     @SuppressWarnings("serial")
     public static class RequestFailedException extends Exception {
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/internal/Activator.java	Thu Aug 10 09:41:24 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,122 +0,0 @@
-/*
- * Copyright 2012-2017 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.internal;
-
-import java.io.File;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.osgi.framework.BundleActivator;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceReference;
-import org.osgi.util.tracker.ServiceTracker;
-import org.osgi.util.tracker.ServiceTrackerCustomizer;
-
-import com.redhat.thermostat.agent.config.AgentConfigsUtils;
-import com.redhat.thermostat.agent.dao.AgentInfoDAO;
-import com.redhat.thermostat.agent.dao.BackendInfoDAO;
-import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.shared.config.CommonPaths;
-import com.redhat.thermostat.shared.config.InvalidConfigurationException;
-
-public class Activator implements BundleActivator {
-    
-    private static final Logger logger = LoggingUtils.getLogger(Activator.class);
-    
-    private final AgentConfigSetter configSetter;
-    private ServiceTracker<CommonPaths, CommonPaths> commonPathsTracker;
-    
-    public Activator() {
-        this(new AgentConfigSetter());
-    }
-    
-    Activator(AgentConfigSetter configSetter) {
-        this.configSetter = configSetter;
-    }
-
-    @Override
-    public void start(final BundleContext context) throws Exception {
-        AgentInfoDAO agentInfoDAO = new AgentInfoDAOImpl();
-        context.registerService(AgentInfoDAO.class, agentInfoDAO, null);
-
-        BackendInfoDAO backendInfoDAO = new BackendInfoDAOImpl();
-        context.registerService(BackendInfoDAO.class, backendInfoDAO, null);
-
-        // Track common paths separately and register storage credentials quickly
-        // We need to do this since otherwise no storage credentials will be
-        // available by the time they're used in DbService
-        commonPathsTracker = new ServiceTracker<>(context, CommonPaths.class, new ServiceTrackerCustomizer<CommonPaths, CommonPaths>() {
-
-            @Override
-            public CommonPaths addingService(ServiceReference<CommonPaths> ref) {
-                CommonPaths paths = context.getService(ref);
-                try {
-                    configSetter.setConfigFiles(paths.getSystemAgentConfigurationFile(), paths.getUserAgentConfigurationFile());
-                } catch (InvalidConfigurationException e) {
-                    logger.log(Level.SEVERE, "Failed to start agent services", e);
-                }
-                return paths;
-            }
-
-            @Override
-            public void modifiedService(ServiceReference<CommonPaths> arg0, CommonPaths arg1) {
-                // nothing
-            }
-
-            @Override
-            public void removedService(ServiceReference<CommonPaths> ref, CommonPaths service) {
-                context.ungetService(ref);
-            }
-        });
-        
-        commonPathsTracker.open();
-    }
-
-    @Override
-    public void stop(BundleContext context) throws Exception {
-        commonPathsTracker.close();
-    }
-    
-    // For testing purposes
-    static class AgentConfigSetter {
-        void setConfigFiles(File systemConfigFile, File userConfigFile) {
-            AgentConfigsUtils.setConfigFiles(systemConfigFile, userConfigFile);
-        }
-    }
-
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/internal/AgentApplication.java	Fri Aug 11 14:03:38 2017 -0400
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.agent.internal;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.agent.dao.AgentInfoDAO;
+import com.redhat.thermostat.agent.dao.BackendInfoDAO;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.osgi.framework.BundleContext;
+
+import com.redhat.thermostat.agent.Agent;
+import com.redhat.thermostat.agent.config.AgentConfigsUtils;
+import com.redhat.thermostat.agent.config.AgentOptionParser;
+import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
+import com.redhat.thermostat.backend.BackendRegistry;
+import com.redhat.thermostat.backend.BackendService;
+import com.redhat.thermostat.common.ExitStatus;
+import com.redhat.thermostat.common.LaunchException;
+import com.redhat.thermostat.common.cli.AbstractStateNotifyingCommand;
+import com.redhat.thermostat.common.cli.Arguments;
+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.CommandRegistry;
+import com.redhat.thermostat.common.cli.CommandRegistryImpl;
+import com.redhat.thermostat.common.tools.ApplicationState;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.shared.config.InvalidConfigurationException;
+import com.redhat.thermostat.storage.core.WriterID;
+import sun.misc.Signal;
+import sun.misc.SignalHandler;
+
+@Component(immediate = true)
+@Service(value = Command.class)
+@SuppressWarnings("restriction")
+public final class AgentApplication extends AbstractStateNotifyingCommand {
+
+    /**
+     * Property for turning on verbose mode. This is there so as to be able to
+     * run integration tests independent of log levels.
+     */
+    private static final String VERBOSE_MODE_PROPERTY = "thermostat.agent.verbose";
+    // Messages printed in verbose mode. Integration tests use this. Be careful
+    // when you change those!
+    private static final String VERBOSE_MODE_AGENT_STOPPED_MSG = "Agent stopped.";
+    private static final String VERBOSE_MODE_AGENT_STARTED_MSG = "Agent started.";
+
+    private static final String SIGINT_NAME = "INT";
+    private static final String SIGTERM_NAME = "TERM";
+
+    private static final Logger logger = LoggingUtils.getLogger(AgentApplication.class);
+
+    private final ConfigurationCreator configurationCreator;
+
+    private AgentStartupConfiguration configuration;
+    private AgentOptionParser parser;
+
+    private BundleContext context;
+
+    @Reference(bind = "bindExitStatus")
+    private ExitStatus exitStatus;
+    @Reference(bind = "bindWriterId")
+    private WriterID writerId;
+    @Reference
+    private AgentInfoDAO agentInfoDAO;
+    @Reference
+    private BackendInfoDAO backendInfoDAO;
+
+    private CommandRegistry reg;
+
+    private CountDownLatch shutdownLatch;
+
+    private CustomSignalHandler handler;
+
+    private AgentApplication instance;
+
+    public AgentApplication() {
+        this(new ConfigurationCreator());
+    }
+
+    AgentApplication(ConfigurationCreator configurationCreator) {
+        this.configurationCreator = configurationCreator;
+    }
+
+    @Activate
+    public void activate(BundleContext context) {
+        this.context = context;
+        reg = new CommandRegistryImpl(context);
+        instance = this;
+        reg.registerCommand("agent", instance);
+    }
+
+    @Deactivate
+    public void deactivate(BundleContext context) {
+        if (instance != null) {
+            // Bundle may be shut down *before* deps become available and
+            // app is set.
+            instance.shutdown(ExitStatus.EXIT_SUCCESS);
+        }
+        reg.unregisterCommands();
+    }
+
+    private void parseArguments(Arguments args) throws InvalidConfigurationException {
+        parser = new AgentOptionParser(configuration, args);
+        parser.parse();
+    }
+
+    private void runAgent(CommandContext ctx) throws CommandException {
+        long startTime = System.currentTimeMillis();
+        configuration.setStartTime(startTime);
+
+        shutdownLatch = new CountDownLatch(1);
+        Agent agent = startAgent(agentInfoDAO, backendInfoDAO);
+        handler = new CustomSignalHandler(agent);
+        Signal.handle(new Signal(SIGINT_NAME), handler);
+        Signal.handle(new Signal(SIGTERM_NAME), handler);
+
+        try {
+            // Wait for either SIGINT or SIGTERM
+            shutdownLatch.await();
+            logger.fine("terminating agent cmd");
+        } catch (InterruptedException e) {
+            // Ensure proper shutdown if interrupted
+            handler.handle(new Signal(SIGINT_NAME));
+            return;
+        }
+    }
+
+    @Override
+    public void run(CommandContext ctx) throws CommandException {
+        configuration = configurationCreator.create();
+
+        parseArguments(ctx.getArguments());
+        if (!parser.isHelp()) {
+            runAgent(ctx);
+        }
+    }
+
+    public void shutdown(int shutDownStatus) {
+        // Exit application
+        if (shutdownLatch != null) {
+            shutdownLatch.countDown();
+        }
+        this.exitStatus.setExitStatus(shutDownStatus);
+        if (shutDownStatus == ExitStatus.EXIT_SUCCESS) {
+            getNotifier().fireAction(ApplicationState.STOP);
+        } else {
+            getNotifier().fireAction(ApplicationState.FAIL);
+        }
+    }
+
+    private class CustomSignalHandler implements SignalHandler {
+
+        private Agent agent;
+
+        public CustomSignalHandler(Agent agent) {
+            this.agent = agent;
+        }
+
+        @Override
+        public void handle(Signal arg0) {
+            try {
+                agent.stop();
+            } catch (Exception ex) {
+                // We don't want any exception to hold back the signal handler, otherwise
+                // there will be no way to actually stop Thermostat.
+                ex.printStackTrace();
+            }
+            logger.fine("Agent stopped.");
+            // Hook for integration tests. Print a well known message to stdout
+            // if verbose mode is turned on via the system property.
+            if (Boolean.getBoolean(VERBOSE_MODE_PROPERTY)) {
+                System.out.println(VERBOSE_MODE_AGENT_STOPPED_MSG);
+            }
+            shutdown(ExitStatus.EXIT_SUCCESS);
+        }
+
+    }
+
+    Agent startAgent(AgentInfoDAO agentInfoDAO, BackendInfoDAO backendInfoDAO) {
+        BackendRegistry backendRegistry = null;
+        try {
+            backendRegistry = new BackendRegistry(context);
+
+        } catch (Exception e) {
+            logger.log(Level.SEVERE, "Could not get BackendRegistry instance.", e);
+            shutdown(ExitStatus.EXIT_ERROR);
+            // Since this would throw NPE's down the line if we continue in this
+            // method, let's fail right and early :)
+            throw new RuntimeException(e);
+        }
+
+        final Agent agent = new Agent(backendRegistry, configuration, agentInfoDAO, backendInfoDAO, writerId);
+        try {
+            logger.fine("Starting agent.");
+            agent.start();
+
+            context.registerService(BackendService.class, new BackendService(), null);
+
+        } catch (LaunchException le) {
+            logger.log(Level.SEVERE,
+                    "Agent could not start, probably because a configured backend could not be activated.",
+                    le);
+            shutdown(ExitStatus.EXIT_ERROR);
+        }
+        logger.fine("Agent started.");
+        // Hook for integration tests. Print a well known message to stdout
+        // if verbose mode is turned on via the system property.
+        if (Boolean.getBoolean(VERBOSE_MODE_PROPERTY)) {
+            System.out.println(VERBOSE_MODE_AGENT_STARTED_MSG);
+        }
+
+        logger.info("Agent id: " + agent.getId());
+        getNotifier().fireAction(ApplicationState.START, agent.getId());
+        return agent;
+    }
+
+    static class ConfigurationCreator {
+        public AgentStartupConfiguration create() throws InvalidConfigurationException {
+            return AgentConfigsUtils.createAgentConfigs();
+        }
+    }
+
+    @Override
+    public boolean isStorageRequired() {
+        return false;
+    }
+
+    // DS runtime bind methods
+    protected void bindExitStatus(ExitStatus status) {
+        this.exitStatus = status;
+    }
+
+    protected void bindWriterId(WriterID id) {
+        this.writerId = id;
+    }
+
+}
+
+
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/internal/AgentInfoDAOImpl.java	Thu Aug 10 09:41:24 2017 -0400
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/internal/AgentInfoDAOImpl.java	Fri Aug 11 14:03:38 2017 -0400
@@ -49,6 +49,9 @@
 
 import com.redhat.thermostat.agent.dao.AgentInfoDAO;
 import com.redhat.thermostat.agent.internal.AgentInformationTypeAdapter.AgentInformationUpdateTypeAdapter;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
 import org.eclipse.jetty.client.HttpClient;
 import org.eclipse.jetty.client.HttpContentResponse;
 import org.eclipse.jetty.client.HttpRequest;
@@ -62,6 +65,8 @@
 import com.redhat.thermostat.storage.core.AgentId;
 import com.redhat.thermostat.storage.model.AgentInformation;
 
+@Component
+@Service(value = AgentInfoDAO.class)
 public class AgentInfoDAOImpl implements AgentInfoDAO {
 
     private static final Logger logger = LoggingUtils.getLogger(AgentInfoDAOImpl.class);
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/internal/BackendInfoDAOImpl.java	Thu Aug 10 09:41:24 2017 -0400
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/internal/BackendInfoDAOImpl.java	Fri Aug 11 14:03:38 2017 -0400
@@ -48,6 +48,9 @@
 import java.util.logging.Logger;
 
 import com.redhat.thermostat.agent.dao.BackendInfoDAO;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
 import org.eclipse.jetty.client.HttpClient;
 import org.eclipse.jetty.client.HttpContentResponse;
 import org.eclipse.jetty.client.HttpRequest;
@@ -61,6 +64,8 @@
 
 import com.redhat.thermostat.storage.model.BackendInformation;
 
+@Component
+@Service(value = BackendInfoDAO.class)
 public class BackendInfoDAOImpl implements BackendInfoDAO {
     
     private static final Logger logger = LoggingUtils.getLogger(BackendInfoDAOImpl.class);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/internal/locale/LocaleResources.java	Fri Aug 11 14:03:38 2017 -0400
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.agent.internal.locale;
+
+import com.redhat.thermostat.shared.locale.Translate;
+
+public enum LocaleResources {
+
+    SERVICE_FAILED_TO_START_DB,
+    LAUNCHER_UNAVAILABLE,
+    UNEXPECTED_RESULT_STORAGE,
+    STARTING_AGENT_FAILED,
+    ;
+
+    static final String RESOURCE_BUNDLE = "com.redhat.thermostat.agent.internal.strings";
+
+    public static Translate<LocaleResources> createLocalizer() {
+        return new Translate<>(RESOURCE_BUNDLE, LocaleResources.class);
+    }
+
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/resources/com/redhat/thermostat/agent/internal/strings.properties	Fri Aug 11 14:03:38 2017 -0400
@@ -0,0 +1,4 @@
+SERVICE_FAILED_TO_START_DB = Service failed to start due to error starting storage.
+LAUNCHER_UNAVAILABLE = Launcher is not available
+UNEXPECTED_RESULT_STORAGE = Unexpected result from storage.
+STARTING_AGENT_FAILED = Thermostat agent failed to start. See logs for details.
--- a/agent/core/src/test/java/com/redhat/thermostat/agent/http/HttpRequestServiceTest.java	Thu Aug 10 09:41:24 2017 -0400
+++ b/agent/core/src/test/java/com/redhat/thermostat/agent/http/HttpRequestServiceTest.java	Fri Aug 11 14:03:38 2017 -0400
@@ -63,8 +63,10 @@
 import org.mockito.ArgumentCaptor;
 
 import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
+import com.redhat.thermostat.agent.http.HttpRequestService.ConfigCreator;
 import com.redhat.thermostat.agent.http.HttpRequestService.HttpClientCreator;
 import com.redhat.thermostat.agent.http.HttpRequestService.RequestFailedException;
+import com.redhat.thermostat.shared.config.CommonPaths;
 import com.redhat.thermostat.shared.config.SSLConfiguration;
 import org.eclipse.jetty.http.HttpMethod;
 
@@ -75,6 +77,7 @@
     private static final String keycloakUrl = "http://127.0.0.1:31000/keycloak";
 
     private HttpClientCreator clientCreator;
+    private ConfigCreator configCreator;
     private HttpClientFacade client;
     private Request httpRequest;
     
@@ -88,6 +91,7 @@
         when(httpRequest.send()).thenReturn(response);
         clientCreator = mock(HttpClientCreator.class);
         when(clientCreator.create(any(SSLConfiguration.class))).thenReturn(client);
+        configCreator = mock(ConfigCreator.class);
     }
 
     @Test
@@ -176,7 +180,8 @@
     }
     
     private HttpRequestService createAndActivateRequestService(AgentStartupConfiguration configuration) throws Exception {
-        HttpRequestService service = new HttpRequestService(clientCreator, configuration);
+        when(configCreator.create(any(CommonPaths.class))).thenReturn(configuration);
+        HttpRequestService service = new HttpRequestService(clientCreator, configCreator);
         service.activate();
         verify(client).start();
         return service;
@@ -189,7 +194,9 @@
         HttpClientFacade getClient = setupHttpClient(creator, getContent);
         
         AgentStartupConfiguration configuration = createNoKeycloakConfig();
-        HttpRequestService service = new HttpRequestService(creator, configuration);
+        ConfigCreator configCreator = mock(ConfigCreator.class);
+        when(configCreator.create(any(CommonPaths.class))).thenReturn(configuration);
+        HttpRequestService service = new HttpRequestService(creator, configCreator);
         service.activate();
         String content = service.sendHttpRequest(null, GET_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.GET);
         verify(getClient).newRequest(GET_URI);
@@ -203,7 +210,9 @@
         HttpClientFacade getClient = setupHttpClient(creator, getContent);
         
         AgentStartupConfiguration configuration = createNoKeycloakConfig();
-        HttpRequestService service = new HttpRequestService(creator, configuration);
+        ConfigCreator configCreator = mock(ConfigCreator.class);
+        when(configCreator.create(any(CommonPaths.class))).thenReturn(configuration);
+        HttpRequestService service = new HttpRequestService(creator, configCreator);
         service.activate();
         
         // Add extra slashes to URI
--- a/agent/core/src/test/java/com/redhat/thermostat/agent/internal/ActivatorTest.java	Thu Aug 10 09:41:24 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,83 +0,0 @@
-/*
- * Copyright 2012-2017 Red Hat, Inc.
- *
- * This file is part of Thermostat.
- *
- * Thermostat is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published
- * by the Free Software Foundation; either version 2, or (at your
- * option) any later version.
- *
- * Thermostat is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Thermostat; see the file COPYING.  If not see
- * <http://www.gnu.org/licenses/>.
- *
- * Linking this code with other modules is making a combined work
- * based on this code.  Thus, the terms and conditions of the GNU
- * General Public License cover the whole combination.
- *
- * As a special exception, the copyright holders of this code give
- * you permission to link this code with independent modules to
- * produce an executable, regardless of the license terms of these
- * independent modules, and to copy and distribute the resulting
- * executable under terms of your choice, provided that you also
- * meet, for each linked independent module, the terms and conditions
- * of the license of that module.  An independent module is a module
- * which is not derived from or based on this code.  If you modify
- * this code, you may extend this exception to your version of the
- * library, but you are not obligated to do so.  If you do not wish
- * to do so, delete this exception statement from your version.
- */
-
-package com.redhat.thermostat.agent.internal;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.*;
-
-import java.io.File;
-
-import org.junit.Test;
-
-import com.redhat.thermostat.agent.dao.AgentInfoDAO;
-import com.redhat.thermostat.agent.dao.BackendInfoDAO;
-import com.redhat.thermostat.agent.internal.Activator.AgentConfigSetter;
-import com.redhat.thermostat.shared.config.CommonPaths;
-import com.redhat.thermostat.testutils.StubBundleContext;
-
-public class ActivatorTest {
-    
-    @Test
-    public void verifyServiceIsRegistered() throws Exception {
-        StubBundleContext context = new StubBundleContext();
-        Activator activator = new Activator();
-        activator.start(context);
-
-        assertTrue(context.isServiceRegistered(AgentInfoDAO.class.getName(), AgentInfoDAOImpl.class));
-        assertTrue(context.isServiceRegistered(BackendInfoDAO.class.getName(), BackendInfoDAOImpl.class));
-    }
-    
-    @Test
-    public void verifyAgentConfig() throws Exception {
-        StubBundleContext context = new StubBundleContext();
-        
-        CommonPaths paths = mock(CommonPaths.class);
-        File sysPropFile = mock(File.class);
-        when(paths.getSystemAgentConfigurationFile()).thenReturn(sysPropFile);
-        File userPropFile = mock(File.class);
-        when(paths.getUserAgentConfigurationFile()).thenReturn(userPropFile);
-        context.registerService(CommonPaths.class.getName(), paths, null);
-        
-        AgentConfigSetter configSetter = mock(AgentConfigSetter.class);
-        Activator activator = new Activator(configSetter);
-        activator.start(context);
-        
-        verify(configSetter).setConfigFiles(sysPropFile, userPropFile);
-    }
-
-}
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/test/java/com/redhat/thermostat/agent/internal/AgentApplicationTest.java	Fri Aug 11 14:03:38 2017 -0400
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.agent.internal;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.InvalidSyntaxException;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import static com.redhat.thermostat.testutils.Asserts.assertCommandIsRegistered;
+
+import com.redhat.thermostat.agent.Agent;
+import com.redhat.thermostat.agent.internal.AgentApplication.ConfigurationCreator;
+import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
+import com.redhat.thermostat.agent.dao.AgentInfoDAO;
+import com.redhat.thermostat.agent.dao.BackendInfoDAO;
+import com.redhat.thermostat.backend.BackendRegistry;
+import com.redhat.thermostat.common.ExitStatus;
+import com.redhat.thermostat.common.LaunchException;
+import com.redhat.thermostat.common.Version;
+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.shared.config.InvalidConfigurationException;
+import com.redhat.thermostat.storage.core.WriterID;
+import com.redhat.thermostat.testutils.StubBundleContext;
+
+@RunWith(PowerMockRunner.class)
+public class AgentApplicationTest {
+
+    private StubBundleContext context;
+
+    private ConfigurationCreator configCreator;
+    private ExitStatus exitStatus;
+    private WriterID writerId;
+
+    @Before
+    public void setUp() throws InvalidConfigurationException {
+
+        context = new StubBundleContext();
+
+        AgentStartupConfiguration config = mock(AgentStartupConfiguration.class);
+        when(config.getDBConnectionString()).thenReturn("test string; please ignore");
+
+        configCreator = mock(ConfigurationCreator.class);
+        when(configCreator.create()).thenReturn(config);
+
+        AgentInfoDAO agentInfoDAO = mock(AgentInfoDAO.class);
+        context.registerService(AgentInfoDAO.class.getName(), agentInfoDAO, null);
+        BackendInfoDAO backendInfoDAO = mock(BackendInfoDAO.class);
+        context.registerService(BackendInfoDAO.class.getName(), backendInfoDAO, null);
+        writerId = mock(WriterID.class);
+
+        exitStatus = mock(ExitStatus.class);
+    }
+
+    @After
+    public void tearDown() {
+        context = null;
+        configCreator = null;
+        exitStatus = null;
+    }
+
+    @PrepareForTest({ FrameworkUtil.class, Agent.class })
+    @Test
+    public void testAgentStartup() throws CommandException, InterruptedException, InvalidSyntaxException {
+        final AgentApplication agent = new AgentApplication(configCreator);
+        agent.bindExitStatus(exitStatus);
+        agent.bindWriterId(writerId);
+        agent.activate(context);
+        final CountDownLatch latch = new CountDownLatch(1);
+        final CommandException[] ce = new CommandException[1];
+        final long timeoutMillis = 5000L;
+
+        Bundle mockBundle = createBundle();
+        PowerMockito.mockStatic(FrameworkUtil.class);
+        when(FrameworkUtil.getBundle(Agent.class)).thenReturn(mockBundle);
+        when(FrameworkUtil.createFilter(any(String.class))).thenReturn(mock(Filter.class));
+
+        startAgentRunThread(timeoutMillis, agent, ce, latch);
+
+        boolean ret = latch.await(timeoutMillis, TimeUnit.MILLISECONDS);
+        if (ce[0] != null) {
+            throw ce[0];
+        }
+        if (!ret) {
+            fail("Timeout expired!");
+        }
+    }
+
+    private Bundle createBundle() {
+        String qualifier = "201207241700";
+        Bundle sysBundle = mock(Bundle.class);
+        org.osgi.framework.Version ver = org.osgi.framework.Version
+                .parseVersion(String.format(Version.VERSION_NUMBER_FORMAT,
+                        1, 2, 3) + "." + qualifier);
+        when(sysBundle.getVersion()).thenReturn(ver);
+        when(sysBundle.getBundleContext()).thenReturn(context);
+        return sysBundle;
+    }
+
+    /*
+     * Having the PrepareForTest annotation on method level does not seem to
+     * deadlock the test, which seems to be more or less reliably reproducible
+     * if this annotation is at class level instead. Steps to reproduce the
+     * deadlock is:
+     * 1. Attach the PrepareForTest annotation to the class (over the test
+     *    method)
+     * 2. Run the test multiple times. 5-20 times seemed sufficient for me to
+     *    make the deadlock show up. This deadlock does not seem to happen
+     *    otherwise (can run up to 30 times head-to-head without deadlock).
+     *
+     */
+    @PrepareForTest({ AgentApplication.class })
+    @SuppressWarnings("unchecked")
+    @Test
+    public void verifyBackendRegistryProblemsSetsExitStatus() throws Exception {
+        PowerMockito.whenNew(BackendRegistry.class).withParameterTypes(BundleContext.class)
+                .withArguments(any(BundleContext.class))
+                .thenThrow(InvalidSyntaxException.class);
+        final AgentApplication agent = new AgentApplication(configCreator);
+        agent.bindExitStatus(exitStatus);
+        agent.bindWriterId(writerId);
+        agent.activate(context);
+        try {
+            agent.startAgent(null, null);
+        } catch (RuntimeException e) {
+            assertEquals(InvalidSyntaxException.class, e.getCause().getClass());
+        }
+        verify(exitStatus).setExitStatus(ExitStatus.EXIT_ERROR);
+    }
+
+    @PrepareForTest({ AgentApplication.class })
+    @Test
+    public void verifyAgentLaunchExceptionSetsExitStatus() throws Exception {
+        PowerMockito.whenNew(BackendRegistry.class).withParameterTypes(BundleContext.class)
+                .withArguments(any(BundleContext.class))
+                .thenReturn(mock(BackendRegistry.class));
+        Agent mockAgent = mock(Agent.class);
+        PowerMockito.whenNew(Agent.class).withParameterTypes(BackendRegistry.class,
+                AgentStartupConfiguration.class,
+                AgentInfoDAO.class, BackendInfoDAO.class, WriterID.class).withArguments(
+                any(BackendRegistry.class),
+                any(AgentStartupConfiguration.class),
+                any(AgentInfoDAO.class), any(BackendInfoDAO.class),
+                any(WriterID.class)).thenReturn(mockAgent);
+        doThrow(LaunchException.class).when(mockAgent).start();
+        final AgentApplication agent = new AgentApplication(configCreator);
+        agent.bindExitStatus(exitStatus);
+        agent.bindWriterId(writerId);
+        agent.activate(context);
+        try {
+            agent.startAgent(null, null);
+        } catch (RuntimeException e) {
+            fail("Should not have thrown RuntimeException");
+        }
+        verify(exitStatus).setExitStatus(ExitStatus.EXIT_ERROR);
+    }
+
+    private void startAgentRunThread(final long timoutMillis, final AgentApplication agent, final CommandException[] ce, final CountDownLatch latch) throws InterruptedException {
+        Arguments args = mock(Arguments.class);
+        final CommandContext commandContext = mock(CommandContext.class);
+        when(commandContext.getArguments()).thenReturn(args);
+
+        // Run agent in a new thread so we can timeout on failure
+        Thread t = new Thread(new Runnable() {
+
+            @Override
+            public void run() {
+                try {
+                    latch.countDown();
+                    agent.run(commandContext);
+                } catch (CommandException e) {
+                    ce[0] = e;
+                }
+            }
+        });
+
+        t.start();
+    }
+
+    @PrepareForTest({ AgentApplication.class })
+    @Test
+    public void verifyAgentCommandIsRegistered() {
+        final AgentApplication agent = new AgentApplication(configCreator);
+        agent.bindExitStatus(exitStatus);
+        agent.bindWriterId(writerId);
+        agent.activate(context);
+        assertCommandIsRegistered(context, "agent", AgentApplication.class);
+    }
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/test/java/com/redhat/thermostat/agent/internal/locale/TranslateTest.java	Fri Aug 11 14:03:38 2017 -0400
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.agent.internal.locale;
+
+import java.io.IOException;
+import java.util.Locale;
+import java.util.Properties;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TranslateTest {
+
+    private Locale lang;
+
+    @Before
+    public void setUp() {
+        this.lang = Locale.getDefault();
+        Locale.setDefault(Locale.US);
+    }
+
+    @After
+    public void tearDown() {
+        Locale.setDefault(lang);
+    }
+
+    @Test
+    public void testLocalizedStringsArePresent() throws IOException {
+
+        String stringsResource = "/" + LocaleResources.RESOURCE_BUNDLE.replace(".", "/") + ".properties";
+
+        Properties props = new Properties();
+        props.load(getClass().getResourceAsStream(stringsResource));
+
+        Assert.assertEquals(LocaleResources.values().length, props.values().size());
+        for (LocaleResources resource : LocaleResources.values()) {
+            Assert.assertTrue("missing property from resource bound file: " + resource,
+                              props.containsKey(resource.name()));
+        }
+    }
+}
+
--- a/agent/pom.xml	Thu Aug 10 09:41:24 2017 -0400
+++ b/agent/pom.xml	Fri Aug 11 14:03:38 2017 -0400
@@ -59,7 +59,6 @@
   </dependencies>
 
   <modules>
-    <module>cli</module>
     <module>core</module>
     <module>ipc</module>
   </modules>
--- a/distribution/assembly/core-assembly-macosx.xml	Thu Aug 10 09:41:24 2017 -0400
+++ b/distribution/assembly/core-assembly-macosx.xml	Fri Aug 11 14:03:38 2017 -0400
@@ -52,7 +52,6 @@
         <include>com.redhat.thermostat:thermostat-main</include>
         <include>com.redhat.thermostat:thermostat-launcher</include>
         <include>com.redhat.thermostat:thermostat-agent-core</include>
-        <include>com.redhat.thermostat:thermostat-agent-cli</include>
         <include>com.redhat.thermostat:thermostat-agent-command</include>
         <include>com.redhat.thermostat:thermostat-agent-command-server</include>
         <include>com.redhat.thermostat:thermostat-agent-proxy-server</include>
--- a/distribution/assembly/core-assembly-windows.xml	Thu Aug 10 09:41:24 2017 -0400
+++ b/distribution/assembly/core-assembly-windows.xml	Fri Aug 11 14:03:38 2017 -0400
@@ -52,7 +52,6 @@
         <include>com.redhat.thermostat:thermostat-main</include>
         <include>com.redhat.thermostat:thermostat-launcher</include>
         <include>com.redhat.thermostat:thermostat-agent-core</include>
-        <include>com.redhat.thermostat:thermostat-agent-cli</include>
         <include>com.redhat.thermostat:thermostat-agent-command</include>
         <include>com.redhat.thermostat:thermostat-agent-command-server</include>
         <include>com.redhat.thermostat:thermostat-agent-proxy-server</include>
--- a/distribution/assembly/core-assembly.xml	Thu Aug 10 09:41:24 2017 -0400
+++ b/distribution/assembly/core-assembly.xml	Fri Aug 11 14:03:38 2017 -0400
@@ -52,7 +52,6 @@
         <include>com.redhat.thermostat:thermostat-main</include>
         <include>com.redhat.thermostat:thermostat-launcher</include>
         <include>com.redhat.thermostat:thermostat-agent-core</include>
-        <include>com.redhat.thermostat:thermostat-agent-cli</include>
         <include>com.redhat.thermostat:thermostat-agent-proxy-server</include>
         <include>com.redhat.thermostat:thermostat-agent-ipc-tcpsocket-server</include>
         <include>com.redhat.thermostat:thermostat-agent-ipc-tcpsocket-client</include>
--- a/distribution/config/commands/agent.properties	Thu Aug 10 09:41:24 2017 -0400
+++ b/distribution/config/commands/agent.properties	Fri Aug 11 14:03:38 2017 -0400
@@ -1,4 +1,4 @@
-bundles = com.redhat.thermostat.agent.cli=@project.version@, \
+bundles = com.redhat.thermostat.agent.core=@project.version@, \
           com.redhat.thermostat.agent.ipc.tcpsocket.server=@project.version@, \
           com.redhat.thermostat.process=@project.version@ \
           @agent_extra_bundles@
--- a/distribution/pom.xml	Thu Aug 10 09:41:24 2017 -0400
+++ b/distribution/pom.xml	Fri Aug 11 14:03:38 2017 -0400
@@ -418,11 +418,6 @@
     </dependency>
     <dependency>
       <groupId>com.redhat.thermostat</groupId>
-      <artifactId>thermostat-agent-cli</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    <dependency>
-      <groupId>com.redhat.thermostat</groupId>
       <artifactId>thermostat-agent-ipc-tcpsocket-server</artifactId>
       <version>${project.version}</version>
     </dependency>