changeset 2751:f31a81430347

Split HttpRequestService and KeycloakAccessTokenService Reviewed-by: jkang Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-September/024882.html
author Severin Gehwolf <sgehwolf@redhat.com>
date Wed, 06 Sep 2017 19:47:49 +0200
parents f39efe6bebd7
children 7d4b09df34d4
files agent/core/pom.xml agent/core/src/main/java/com/redhat/thermostat/agent/http/HttpClientFacade.java agent/core/src/main/java/com/redhat/thermostat/agent/http/HttpRequestService.java agent/core/src/main/java/com/redhat/thermostat/agent/http/RequestFailedException.java agent/core/src/main/java/com/redhat/thermostat/agent/http/internal/keycloak/KeycloakAccessToken.java agent/core/src/main/java/com/redhat/thermostat/agent/internal/http/BasicHttpService.java agent/core/src/main/java/com/redhat/thermostat/agent/internal/http/HttpClientFacade.java agent/core/src/main/java/com/redhat/thermostat/agent/internal/http/HttpRequestServiceImpl.java agent/core/src/main/java/com/redhat/thermostat/agent/internal/http/KeycloakAccessTokenServiceImpl.java agent/core/src/main/java/com/redhat/thermostat/agent/keycloak/KeycloakAccessToken.java agent/core/src/main/java/com/redhat/thermostat/agent/keycloak/KeycloakAccessTokenService.java agent/core/src/test/java/com/redhat/thermostat/agent/http/HttpRequestServiceTest.java agent/core/src/test/java/com/redhat/thermostat/agent/internal/http/HttpRequestServiceImplTest.java agent/core/src/test/java/com/redhat/thermostat/agent/internal/http/KeycloakAccessTokenServiceImplTest.java agent/core/src/test/java/com/redhat/thermostat/agent/keycloak/KeycloakAccessTokenTest.java common/plugin/src/main/java/com/redhat/thermostat/common/plugin/PluginDAOBase.java plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoDAOImpl.java plugins/vm-gc/agent/src/main/java/com/redhat/thermostat/vm/gc/agent/internal/models/VmGcStatDAOImpl.java plugins/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/models/VmMemoryStatDAOImpl.java
diffstat 19 files changed, 1313 insertions(+), 815 deletions(-) [+]
line wrap: on
line diff
--- a/agent/core/pom.xml	Thu Sep 07 18:41:40 2017 +0200
+++ b/agent/core/pom.xml	Wed Sep 06 19:47:49 2017 +0200
@@ -130,7 +130,7 @@
             <Export-Package>
                 com.redhat.thermostat.agent,
                 com.redhat.thermostat.agent.http,
-                com.redhat.thermostat.agent.http.internal.keycloak,
+                com.redhat.thermostat.agent.keycloak,
                 com.redhat.thermostat.agent.config,
                 com.redhat.thermostat.agent.dao,
                 com.redhat.thermostat.agent.utils,
@@ -139,6 +139,8 @@
             </Export-Package>
             <Private-Package>
               com.redhat.thermostat.agent.internal,
+              com.redhat.thermostat.agent.internal.http,
+              com.redhat.thermostat.agent.internal.locale,
               com.redhat.thermostat.backend.internal,
               com.redhat.thermostat.utils.management.internal
             </Private-Package>
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/http/HttpClientFacade.java	Thu Sep 07 18:41:40 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,98 +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.http;
-
-import java.net.URI;
-import java.util.concurrent.TimeUnit;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import javax.net.ssl.SSLContext;
-
-import org.eclipse.jetty.client.HttpClient;
-import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-
-import com.redhat.thermostat.common.ssl.SSLContextFactory;
-import com.redhat.thermostat.common.ssl.SslInitException;
-import com.redhat.thermostat.common.utils.LoggingUtils;
-import com.redhat.thermostat.shared.config.SSLConfiguration;
-
-class HttpClientFacade {
-
-    private static final Logger logger = LoggingUtils.getLogger(HttpClientFacade.class);
-    private static final long PER_REQUEST_TIMEOUT_SEC = 5;
-    private final HttpClient httpsClient;
-    
-    HttpClientFacade(SSLConfiguration sslConfig) {
-        httpsClient = createHttpsClient(sslConfig);
-    }
-
-    private static HttpClient createHttpsClient(SSLConfiguration config) {
-        try {
-            SSLContext context = SSLContextFactory.getClientContext(config);
-            SslContextFactory sslFactory = new SslContextFactory();
-            sslFactory.setSslContext(context);
-            // Don't send SSLv2 Client Hello. Some servers will refuse to
-            // accept it. So does the web-gateway with our self-signed cert.
-            sslFactory.setIncludeProtocols("TLSv1", "TLSv1.2");
-            if (config.disableHostnameVerification()) {
-                logger.fine("HTTPS endpoint verification disabled.");
-            } else {
-                sslFactory.setEndpointIdentificationAlgorithm("HTTPS");
-            }
-            HttpClient client = new HttpClient(sslFactory);
-            return client;
-        } catch (SslInitException e) {
-            logger.log(Level.INFO, "Failed to initialize SSL context.", e);
-            logger.severe("Failed to initialize SSL context. Reason: " + e.getMessage());
-            throw new RuntimeException(e);
-        } 
-    }
-    
-    void start() throws Exception {
-        httpsClient.start();
-    }
-    
-    Request newRequest(URI uri) {
-        return httpsClient.newRequest(uri).timeout(PER_REQUEST_TIMEOUT_SEC, TimeUnit.SECONDS);
-    }
-    
-    Request newRequest(String url) {
-        return httpsClient.newRequest(url).timeout(PER_REQUEST_TIMEOUT_SEC, TimeUnit.SECONDS);
-    }
-}
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/http/HttpRequestService.java	Thu Sep 07 18:41:40 2017 +0200
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/http/HttpRequestService.java	Wed Sep 06 19:47:49 2017 +0200
@@ -37,82 +37,14 @@
 package com.redhat.thermostat.agent.http;
 
 
-import java.io.IOException;
 import java.net.URI;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.logging.Level;
-import java.util.logging.Logger;
 
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-import org.eclipse.jetty.client.api.ContentResponse;
-import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.client.util.StringContentProvider;
-import org.eclipse.jetty.http.HttpHeader;
 import org.eclipse.jetty.http.HttpMethod;
-import org.eclipse.jetty.http.HttpStatus;
-
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.redhat.thermostat.agent.config.AgentConfigsUtils;
-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;
-import com.redhat.thermostat.storage.config.FileStorageCredentials;
-import com.redhat.thermostat.storage.core.StorageCredentials;
-
-@Component
-@Service(value = HttpRequestService.class)
-public class HttpRequestService {
-    
-    private static final Logger logger = LoggingUtils.getLogger(HttpRequestService.class);
 
-    private static final String UNKNOWN_CREDS = "UNKNOWN:UNKNOWN";
-    private static final String KEYCLOAK_TOKEN_SERVICE = "/auth/realms/__REALM__/protocol/openid-connect/token";
-    private static final String KEYCLOAK_CONTENT_TYPE = "application/x-www-form-urlencoded";
-
-    private final CredentialsCreator credsCreator; // Used for BASIC auth
-    private final HttpClientCreator httpClientCreator;
-    private final ConfigCreator configCreator;
-    private Gson gson = new GsonBuilder().create();
-    private HttpClientFacade client;
-    private AgentStartupConfiguration agentStartupConfiguration;
-    private StorageCredentials creds; // Used for BASIC auth
-    private KeycloakAccessToken keycloakAccessToken;
-    
-    @Reference
-    private SSLConfiguration sslConfig;
-    @Reference
-    private CommonPaths commonPaths;
+import com.redhat.thermostat.annotations.Service;
 
-    public HttpRequestService() {
-        this(new HttpClientCreator(), new ConfigCreator(), new CredentialsCreator());
-    }
-
-    HttpRequestService(HttpClientCreator clientCreator, ConfigCreator configCreator, CredentialsCreator credsCreator) {
-        this.httpClientCreator = clientCreator;
-        this.configCreator = configCreator;
-        this.credsCreator = credsCreator;
-    }
-
-    @Activate
-    public void activate() {
-        try {
-            agentStartupConfiguration = configCreator.create(commonPaths);
-            client = httpClientCreator.create(sslConfig);
-            creds = credsCreator.create(commonPaths);
-            client.start();
-            logger.log(Level.FINE, "HttpRequestService activated");
-        } catch (Exception e) {
-            logger.log(Level.SEVERE, "HttpRequestService failed to start correctly. Behaviour undefined.", e);
-        }
-    }
+@Service
+public interface HttpRequestService {
 
     /**
      * Send a HTTP request
@@ -121,186 +53,8 @@
      * @param requestMethod The HTTP request type: GET, PUT, POST or DELETE
      * @return The returned body for GET requests. {@code null} otherwise.
      */
-    public String sendHttpRequest(String jsonPayload, URI uri, Method requestMethod) throws RequestFailedException {
-        // Normalize URI to ensure any duplicate slashes are removed
-        uri = uri.normalize();
-        Request request = client.newRequest(uri);
-        if (jsonPayload != null) {
-            request.content(new StringContentProvider(jsonPayload), "application/json");
-        }
-        request.method(requestMethod.getHttpMethod());
-
-        try {
-            if (agentStartupConfiguration.isKeycloakEnabled()) {
-                request.header(HttpHeader.AUTHORIZATION.asString(), "Bearer " + getAccessToken());
-            } else if (agentStartupConfiguration.isBasicAuthEnabled()) {
-                request.header(HttpHeader.AUTHORIZATION.asString(),
-                               getBasicAuthHeaderValue());
-            } else {
-                logger.warning("Neither KEYCLOAK_ENABLED=true nor BASIC_AUTH_ENABLED=true. Requests will probably fail.");
-            }
-            ContentResponse response =  request.send();
-            int status = response.getStatus();
-            if (status != HttpStatus.OK_200) {
-                throw new RequestFailedException(status, "Request to gateway failed. Reason: " + response.getReason());
-            }
-            if (requestMethod == Method.GET) {
-                return response.getContentAsString();
-            } else {
-                return null;
-            }
-        } catch (InterruptedException | TimeoutException | IOException | ExecutionException e) {
-            throw new RequestFailedException(e);
-        }
-    }
-
-    private String getAccessToken() throws IOException {
-        if (keycloakAccessToken == null) {
-            keycloakAccessToken = acquireKeycloakToken();
-        } else if (isKeycloakTokenExpired()) {
-            logger.log(Level.FINE, "Keycloak Token expired attempting to reacquire via refresh_token");
-            keycloakAccessToken = refreshKeycloakToken();
-
-            if (keycloakAccessToken == null) {
-                logger.log(Level.WARNING, "Unable to refresh Keycloak token, attempting to acquire new token");
-                keycloakAccessToken = acquireKeycloakToken();
-            }
-
-            if (keycloakAccessToken == null) {
-                logger.log(Level.SEVERE, "Unable to reacquire KeycloakToken.");
-                throw new IOException("Keycloak token expired and attempt to refresh and reacquire Keycloak token failed.");
-            }
-        }
-
-        return keycloakAccessToken.getAccessToken();
-    }
-
-    private boolean isKeycloakTokenExpired() {
-        return System.nanoTime() > TimeUnit.NANOSECONDS.convert(keycloakAccessToken.getExpiresIn(), TimeUnit.SECONDS) + keycloakAccessToken.getAcquireTime();
-    }
-
-    private KeycloakAccessToken acquireKeycloakToken() {
-        return requestKeycloakToken(getKeycloakAccessPayload());
-    }
-
-    private KeycloakAccessToken refreshKeycloakToken() {
-        return requestKeycloakToken(getKeycloakRefreshPayload());
-    }
-
-    private KeycloakAccessToken requestKeycloakToken(String payload) {
-        String url = agentStartupConfiguration.getKeycloakUrl() + KEYCLOAK_TOKEN_SERVICE.replace("__REALM__", agentStartupConfiguration.getKeycloakRealm());
-        Request request = client.newRequest(url);
-        request.content(new StringContentProvider(payload), KEYCLOAK_CONTENT_TYPE);
-        request.method(HttpMethod.POST);
-
-        try {
-            ContentResponse response = request.send();
-            if (response.getStatus() == HttpStatus.OK_200) {
-
-                String content = response.getContentAsString();
-
-                keycloakAccessToken = gson.fromJson(content, KeycloakAccessToken.class);
-                keycloakAccessToken.setAcquireTime(System.nanoTime());
-
-                logger.log(Level.FINE, "Keycloak Token acquired");
-                return keycloakAccessToken;
-            } else {
-                logger.log(Level.WARNING, "Failed to acquire Keycloak token: " + response.getStatus() + " " + response.getReason());
-            }
-        } catch (Exception e) {
-            logger.log(Level.WARNING, "Failed to acquire Keycloak token", e);
-        }
-        return null;
-    }
-
-    private String getKeycloakAccessPayload() {
-        String username = creds.getUsername();
-        String password = new String(creds.getPassword());
-        return "grant_type=password&client_id=" + agentStartupConfiguration.getKeycloakClient() +
-                "&username=" + username +
-                "&password=" + password;
-    }
-
-    private String getKeycloakRefreshPayload() {
-        return "grant_type=refresh_token&client_id=" + agentStartupConfiguration.getKeycloakClient() +
-                "&refresh_token=" + keycloakAccessToken.getRefreshToken();
-    }
+    public String sendHttpRequest(String jsonPayload, URI uri, Method requestMethod) throws RequestFailedException;
     
-    private String getBasicAuthHeaderValue() {
-        String username = creds.getUsername();
-        char[] pwdChar = creds.getPassword();
-        String userpassword;
-        if (username == null || username.isEmpty() || pwdChar == null) {
-            logger.warning("No credentials specified in " + commonPaths.getUserAgentAuthConfigFile() + ". The connection will fail.");
-            userpassword = UNKNOWN_CREDS;
-        } else {
-            String pwd = new String(pwdChar);
-            userpassword = username + ":" + pwd;
-        }
-        
-        @SuppressWarnings("restriction")
-        String encodedAuthorization = new sun.misc.BASE64Encoder()
-                .encode(userpassword.getBytes());
-        return "Basic " + encodedAuthorization;
-    }
-
-    // Package-private for testing
-    void setConfiguration(AgentStartupConfiguration configuration) {
-        this.agentStartupConfiguration = configuration;
-    }
-
-    static class HttpClientCreator {
-    
-        HttpClientFacade create(SSLConfiguration config) {
-            return new HttpClientFacade(config);
-        }
-
-    }
-
-    static class ConfigCreator {
-        AgentStartupConfiguration create(CommonPaths commonPaths) {
-            AgentConfigsUtils.setConfigFiles(commonPaths.getSystemAgentConfigurationFile(),
-                    commonPaths.getUserAgentConfigurationFile());
-            return AgentConfigsUtils.createAgentConfigs();
-        }
-    }
-    
-    static class CredentialsCreator {
-        StorageCredentials create(CommonPaths paths) {
-            return new FileStorageCredentials(paths.getUserAgentAuthConfigFile());
-        }
-    }
-    
-    @SuppressWarnings("serial")
-    public static class RequestFailedException extends Exception {
-        
-        public static final int UNKNOWN_RESPONSE_CODE = -1;
-        private final int responseCode;
-        private final String reasonStr;
-        
-        private RequestFailedException(Throwable e) {
-            this(UNKNOWN_RESPONSE_CODE, e.getMessage(), e);
-        }
-        
-        private RequestFailedException(int responseCode, String reason) {
-            this(responseCode, reason, null);
-        }
-        
-        private RequestFailedException(int responseCode, String reason, Throwable cause) {
-            super(reason, cause);
-            this.reasonStr = reason;
-            this.responseCode = responseCode;
-        }
-        
-        public int getResponseCode() {
-            return responseCode;
-        }
-        
-        public String getReason() {
-            return reasonStr;
-        }
-    }
-
     /**
      * HTTP methods for microservice requests.
      * @author mzezulka
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/http/RequestFailedException.java	Wed Sep 06 19:47:49 2017 +0200
@@ -0,0 +1,71 @@
+/*
+ * 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.http;
+
+@SuppressWarnings("serial")
+public class RequestFailedException extends Exception {
+    
+    public static final int UNKNOWN_RESPONSE_CODE = -1;
+    private final int responseCode;
+    private final String reasonStr;
+    
+    public RequestFailedException(Throwable e) {
+        this(UNKNOWN_RESPONSE_CODE, e.getMessage(), e);
+    }
+    
+    public RequestFailedException(String message) {
+        this(UNKNOWN_RESPONSE_CODE, message);
+    }
+    
+    public RequestFailedException(int responseCode, String reason) {
+        this(responseCode, reason, null);
+    }
+    
+    public RequestFailedException(int responseCode, String reason, Throwable cause) {
+        super(reason, cause);
+        this.reasonStr = reason;
+        this.responseCode = responseCode;
+    }
+    
+    public int getResponseCode() {
+        return responseCode;
+    }
+    
+    public String getReason() {
+        return reasonStr;
+    }
+}
\ No newline at end of file
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/http/internal/keycloak/KeycloakAccessToken.java	Thu Sep 07 18:41:40 2017 +0200
+++ /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.http.internal.keycloak;
-
-import com.google.gson.annotations.SerializedName;
-
-public class KeycloakAccessToken {
-    @SerializedName("access_token")
-    private String accessToken;
-
-    // In seconds
-    @SerializedName("expires_in")
-    private long expiresIn;
-
-    // In seconds
-    @SerializedName("refresh_expires_in")
-    private long refreshExpiresIn;
-
-    @SerializedName("refresh_token")
-    private String refreshToken;
-
-    // In nanoseconds
-    private transient long acquireTime;
-
-
-    public String getAccessToken() {
-        return accessToken;
-    }
-
-    public Long getExpiresIn() {
-        return expiresIn;
-    }
-
-    public Long getRefreshExpiresIn() {
-        return refreshExpiresIn;
-    }
-
-    public String getRefreshToken() {
-        return refreshToken;
-    }
-
-    public long getAcquireTime() {
-        return acquireTime;
-    }
-
-    public void setAcquireTime(long acquireTime) {
-        this.acquireTime = acquireTime;
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/internal/http/BasicHttpService.java	Wed Sep 06 19:47:49 2017 +0200
@@ -0,0 +1,103 @@
+/*
+ * 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.http;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.redhat.thermostat.agent.config.AgentConfigsUtils;
+import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.shared.config.CommonPaths;
+import com.redhat.thermostat.shared.config.SSLConfiguration;
+import com.redhat.thermostat.storage.config.FileStorageCredentials;
+import com.redhat.thermostat.storage.core.StorageCredentials;
+
+class BasicHttpService {
+
+    private static final Logger logger = LoggingUtils.getLogger(BasicHttpService.class);
+    protected final CredentialsCreator credsCreator;
+    protected final HttpClientCreator httpClientCreator;
+    protected final ConfigCreator configCreator;
+    protected HttpClientFacade client;
+    protected AgentStartupConfiguration agentStartupConfiguration;
+    protected StorageCredentials creds;
+    
+    BasicHttpService() {
+        this(new HttpClientCreator(), new ConfigCreator(), new CredentialsCreator());
+    }
+
+    BasicHttpService(HttpClientCreator clientCreator, ConfigCreator configCreator, CredentialsCreator credsCreator) {
+        this.httpClientCreator = clientCreator;
+        this.configCreator = configCreator;
+        this.credsCreator = credsCreator;
+    }
+    
+    protected void doActivate(CommonPaths commonPaths, SSLConfiguration sslConfig) {
+        try {
+            agentStartupConfiguration = configCreator.create(commonPaths);
+            client = httpClientCreator.create(sslConfig);
+            creds = credsCreator.create(commonPaths);
+            client.start();
+            logger.log(Level.FINE, "HttpRequestService activated");
+        } catch (Exception e) {
+            logger.log(Level.SEVERE, "HttpRequestService failed to start correctly. Behaviour undefined.", e);
+        }
+    }
+    
+    static class ConfigCreator {
+        AgentStartupConfiguration create(CommonPaths commonPaths) {
+            AgentConfigsUtils.setConfigFiles(commonPaths.getSystemAgentConfigurationFile(),
+                    commonPaths.getUserAgentConfigurationFile());
+            return AgentConfigsUtils.createAgentConfigs();
+        }
+    }
+    
+    static class CredentialsCreator {
+        StorageCredentials create(CommonPaths paths) {
+            return new FileStorageCredentials(paths.getUserAgentAuthConfigFile());
+        }
+    }
+    
+    static class HttpClientCreator {
+
+        HttpClientFacade create(SSLConfiguration config) {
+            return new HttpClientFacade(config);
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/internal/http/HttpClientFacade.java	Wed Sep 06 19:47:49 2017 +0200
@@ -0,0 +1,98 @@
+/*
+ * 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.http;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLContext;
+
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+import com.redhat.thermostat.common.ssl.SSLContextFactory;
+import com.redhat.thermostat.common.ssl.SslInitException;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.shared.config.SSLConfiguration;
+
+class HttpClientFacade {
+
+    private static final Logger logger = LoggingUtils.getLogger(HttpClientFacade.class);
+    private static final long PER_REQUEST_TIMEOUT_SEC = 5;
+    private final HttpClient httpsClient;
+    
+    HttpClientFacade(SSLConfiguration sslConfig) {
+        httpsClient = createHttpsClient(sslConfig);
+    }
+
+    private static HttpClient createHttpsClient(SSLConfiguration config) {
+        try {
+            SSLContext context = SSLContextFactory.getClientContext(config);
+            SslContextFactory sslFactory = new SslContextFactory();
+            sslFactory.setSslContext(context);
+            // Don't send SSLv2 Client Hello. Some servers will refuse to
+            // accept it. So does the web-gateway with our self-signed cert.
+            sslFactory.setIncludeProtocols("TLSv1", "TLSv1.2");
+            if (config.disableHostnameVerification()) {
+                logger.fine("HTTPS endpoint verification disabled.");
+            } else {
+                sslFactory.setEndpointIdentificationAlgorithm("HTTPS");
+            }
+            HttpClient client = new HttpClient(sslFactory);
+            return client;
+        } catch (SslInitException e) {
+            logger.log(Level.INFO, "Failed to initialize SSL context.", e);
+            logger.severe("Failed to initialize SSL context. Reason: " + e.getMessage());
+            throw new RuntimeException(e);
+        } 
+    }
+    
+    void start() throws Exception {
+        httpsClient.start();
+    }
+    
+    Request newRequest(URI uri) {
+        return httpsClient.newRequest(uri).timeout(PER_REQUEST_TIMEOUT_SEC, TimeUnit.SECONDS);
+    }
+    
+    Request newRequest(String url) {
+        return httpsClient.newRequest(url).timeout(PER_REQUEST_TIMEOUT_SEC, TimeUnit.SECONDS);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/internal/http/HttpRequestServiceImpl.java	Wed Sep 06 19:47:49 2017 +0200
@@ -0,0 +1,156 @@
+/*
+ * 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.http;
+
+
+import java.net.URI;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Logger;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+
+import com.redhat.thermostat.agent.http.HttpRequestService;
+import com.redhat.thermostat.agent.http.RequestFailedException;
+import com.redhat.thermostat.agent.keycloak.KeycloakAccessToken;
+import com.redhat.thermostat.agent.keycloak.KeycloakAccessTokenService;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.shared.config.CommonPaths;
+import com.redhat.thermostat.shared.config.SSLConfiguration;
+
+@Component
+@Service(value = HttpRequestService.class)
+public class HttpRequestServiceImpl extends BasicHttpService implements HttpRequestService {
+    
+    private static final Logger logger = LoggingUtils.getLogger(HttpRequestServiceImpl.class);
+
+    private static final String UNKNOWN_CREDS = "UNKNOWN:UNKNOWN";
+
+    @Reference
+    private SSLConfiguration sslConfig;
+    @Reference
+    private CommonPaths commonPaths;
+    @Reference
+    private KeycloakAccessTokenService tokenService;
+
+    public HttpRequestServiceImpl() {
+        this(new HttpClientCreator(), new ConfigCreator(), new CredentialsCreator());
+    }
+
+    // For testing purposes
+    HttpRequestServiceImpl(HttpClientCreator clientCreator, ConfigCreator configCreator, CredentialsCreator credsCreator) {
+        super(clientCreator, configCreator, credsCreator);
+    }
+
+    @Activate
+    public void activate() {
+        super.doActivate(commonPaths, sslConfig);
+    }
+
+    /**
+     * Send a HTTP request
+     * @param jsonPayload The payload to send, or null if no payload
+     * @param uri The complete URI to send to
+     * @param requestMethod The HTTP request type: GET, PUT, POST or DELETE
+     * @return The returned body for GET requests. {@code null} otherwise.
+     */
+    public String sendHttpRequest(String jsonPayload, URI uri, Method requestMethod) throws RequestFailedException {
+        // Normalize URI to ensure any duplicate slashes are removed
+        uri = uri.normalize();
+        Request request = client.newRequest(uri);
+        if (jsonPayload != null) {
+            request.content(new StringContentProvider(jsonPayload), "application/json");
+        }
+        request.method(requestMethod.getHttpMethod());
+
+        try {
+            if (agentStartupConfiguration.isKeycloakEnabled()) {
+                KeycloakAccessToken accessToken = tokenService.getAccessToken();
+                request.header(HttpHeader.AUTHORIZATION.asString(), "Bearer " + accessToken.getAccessToken());
+            } else if (agentStartupConfiguration.isBasicAuthEnabled()) {
+                request.header(HttpHeader.AUTHORIZATION.asString(),
+                               getBasicAuthHeaderValue());
+            } else {
+                logger.warning("Neither KEYCLOAK_ENABLED=true nor BASIC_AUTH_ENABLED=true. Requests will probably fail.");
+            }
+            ContentResponse response =  request.send();
+            int status = response.getStatus();
+            if (status != HttpStatus.OK_200) {
+                throw new RequestFailedException(status, "Request to gateway failed. Reason: " + response.getReason());
+            }
+            if (requestMethod == Method.GET) {
+                return response.getContentAsString();
+            } else {
+                return null;
+            }
+        } catch (InterruptedException | TimeoutException | ExecutionException e) {
+            throw new RequestFailedException(e);
+        }
+    }
+    
+    private String getBasicAuthHeaderValue() {
+        String username = creds.getUsername();
+        char[] pwdChar = creds.getPassword();
+        String userpassword;
+        if (username == null || username.isEmpty() || pwdChar == null) {
+            logger.warning("No credentials specified in " + commonPaths.getUserAgentAuthConfigFile() + ". The connection will fail.");
+            userpassword = UNKNOWN_CREDS;
+        } else {
+            String pwd = new String(pwdChar);
+            userpassword = username + ":" + pwd;
+        }
+        
+        @SuppressWarnings("restriction")
+        String encodedAuthorization = new sun.misc.BASE64Encoder()
+                .encode(userpassword.getBytes());
+        return "Basic " + encodedAuthorization;
+    }
+    
+    // DS bind methods
+    
+    protected void bindTokenService(KeycloakAccessTokenService tokenService) {
+        this.tokenService = tokenService;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/internal/http/KeycloakAccessTokenServiceImpl.java	Wed Sep 06 19:47:49 2017 +0200
@@ -0,0 +1,168 @@
+/*
+ * 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.http;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.redhat.thermostat.agent.http.RequestFailedException;
+import com.redhat.thermostat.agent.keycloak.KeycloakAccessToken;
+import com.redhat.thermostat.agent.keycloak.KeycloakAccessTokenService;
+import com.redhat.thermostat.common.utils.LoggingUtils;
+import com.redhat.thermostat.shared.config.CommonPaths;
+import com.redhat.thermostat.shared.config.SSLConfiguration;
+
+@Component
+@Service(value = KeycloakAccessTokenService.class)
+public class KeycloakAccessTokenServiceImpl extends BasicHttpService implements KeycloakAccessTokenService {
+
+    private static final Logger logger = LoggingUtils.getLogger(KeycloakAccessTokenServiceImpl.class);
+    private static final String KEYCLOAK_TOKEN_SERVICE = "/auth/realms/__REALM__/protocol/openid-connect/token";
+    private static final String KEYCLOAK_CONTENT_TYPE = "application/x-www-form-urlencoded";
+    private final Object accessTokenLock = new Object(); 
+    private final Gson gson = new GsonBuilder().create();
+    private KeycloakAccessToken keycloakAccessToken;
+    
+    @Reference
+    private SSLConfiguration sslConfig;
+    @Reference
+    private CommonPaths commonPaths;
+    
+    public KeycloakAccessTokenServiceImpl() {
+        this(new HttpClientCreator(), new ConfigCreator(), new CredentialsCreator());
+    }
+
+    // For testing purposes
+    KeycloakAccessTokenServiceImpl(HttpClientCreator clientCreator, ConfigCreator configCreator, CredentialsCreator credsCreator) {
+        super(clientCreator, configCreator, credsCreator);
+    }
+    
+    @Activate
+    public void activate() {
+        super.doActivate(commonPaths, sslConfig);
+    }
+    
+    @Override
+    public KeycloakAccessToken getAccessToken() throws RequestFailedException {
+        synchronized(accessTokenLock) {
+            if (keycloakAccessToken == null) {
+                keycloakAccessToken = acquireKeycloakToken();
+            } else if (keycloakAccessToken.isKeycloakTokenExpired()) {
+                logger.log(Level.FINE, "Keycloak Token expired attempting to reacquire via refresh_token");
+                keycloakAccessToken = refreshKeycloakToken();
+                
+                if (keycloakAccessToken == null) {
+                    logger.log(Level.WARNING, "Unable to refresh Keycloak token, attempting to acquire new token");
+                    keycloakAccessToken = acquireKeycloakToken();
+                }
+                
+                if (keycloakAccessToken == null) {
+                    logger.log(Level.SEVERE, "Unable to reacquire KeycloakToken.");
+                    throw new RequestFailedException("Keycloak token expired and attempt to refresh and reacquire Keycloak token failed.");
+                }
+            }
+                
+        }
+        return keycloakAccessToken;
+    }
+
+    private KeycloakAccessToken acquireKeycloakToken() {
+        return requestKeycloakToken(getKeycloakAccessPayload());
+    }
+
+    private KeycloakAccessToken refreshKeycloakToken() {
+        return requestKeycloakToken(getKeycloakRefreshPayload());
+    }
+
+    private KeycloakAccessToken requestKeycloakToken(String payload) {
+        String url = agentStartupConfiguration.getKeycloakUrl() + KEYCLOAK_TOKEN_SERVICE.replace("__REALM__", agentStartupConfiguration.getKeycloakRealm());
+        Request request = client.newRequest(url);
+        request.content(new StringContentProvider(payload), KEYCLOAK_CONTENT_TYPE);
+        request.method(HttpMethod.POST);
+
+        try {
+            ContentResponse response = request.send();
+            if (response.getStatus() == HttpStatus.OK_200) {
+
+                String content = response.getContentAsString();
+
+                keycloakAccessToken = gson.fromJson(content, KeycloakAccessToken.class);
+                keycloakAccessToken.setAcquireTime(System.nanoTime());
+
+                logger.log(Level.FINE, "Keycloak Token acquired");
+                return keycloakAccessToken;
+            } else {
+                logger.log(Level.WARNING, "Failed to acquire Keycloak token: " + response.getStatus() + " " + response.getReason());
+            }
+        } catch (Exception e) {
+            logger.log(Level.WARNING, "Failed to acquire Keycloak token", e);
+        }
+        return null;
+    }
+
+    private String getKeycloakAccessPayload() {
+        String username = creds.getUsername();
+        String password = new String(creds.getPassword());
+        return "grant_type=password&client_id=" + agentStartupConfiguration.getKeycloakClient() +
+                "&username=" + username +
+                "&password=" + password;
+    }
+
+    private String getKeycloakRefreshPayload() {
+        return "grant_type=refresh_token&client_id=" + agentStartupConfiguration.getKeycloakClient() +
+                "&refresh_token=" + keycloakAccessToken.getRefreshToken();
+    }
+    
+    // For testing purpuses
+    void setKeycloakAccessToken(KeycloakAccessToken token) {
+        synchronized (accessTokenLock) {
+            this.keycloakAccessToken = token;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/keycloak/KeycloakAccessToken.java	Wed Sep 06 19:47:49 2017 +0200
@@ -0,0 +1,98 @@
+/*
+ * 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.keycloak;
+
+import java.util.concurrent.TimeUnit;
+
+import com.google.gson.annotations.SerializedName;
+
+public class KeycloakAccessToken {
+    
+    @SerializedName("access_token")
+    private String accessToken;
+
+    // In seconds
+    @SerializedName("expires_in")
+    private long expiresIn;
+
+    // In seconds
+    @SerializedName("refresh_expires_in")
+    private long refreshExpiresIn;
+
+    @SerializedName("refresh_token")
+    private String refreshToken;
+
+    // In nanoseconds
+    private transient long acquireTime;
+
+    public KeycloakAccessToken() {
+        // default constructor
+    }
+    
+    // for testing
+    KeycloakAccessToken(Long expiresIn) {
+        this.expiresIn = expiresIn;
+    }
+
+    public String getAccessToken() {
+        return accessToken;
+    }
+
+    public Long getExpiresIn() {
+        return expiresIn;
+    }
+
+    public Long getRefreshExpiresIn() {
+        return refreshExpiresIn;
+    }
+
+    public String getRefreshToken() {
+        return refreshToken;
+    }
+
+    public long getAcquireTime() {
+        return acquireTime;
+    }
+
+    public void setAcquireTime(long acquireTime) {
+        this.acquireTime = acquireTime;
+    }
+    
+    public boolean isKeycloakTokenExpired() {
+        return System.nanoTime() > TimeUnit.NANOSECONDS.convert(getExpiresIn(), TimeUnit.SECONDS) + getAcquireTime();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/main/java/com/redhat/thermostat/agent/keycloak/KeycloakAccessTokenService.java	Wed Sep 06 19:47:49 2017 +0200
@@ -0,0 +1,50 @@
+/*
+ * 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.keycloak;
+
+import com.redhat.thermostat.agent.http.RequestFailedException;
+import com.redhat.thermostat.annotations.Service;
+
+/**
+ * Service for retrieving a Keycloak access token.
+ */
+@Service
+public interface KeycloakAccessTokenService {
+
+    KeycloakAccessToken getAccessToken() throws RequestFailedException;
+
+}
--- a/agent/core/src/test/java/com/redhat/thermostat/agent/http/HttpRequestServiceTest.java	Thu Sep 07 18:41:40 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,379 +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.http;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.io.IOException;
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
-
-import org.eclipse.jetty.client.api.ContentResponse;
-import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.client.util.StringContentProvider;
-import org.eclipse.jetty.http.HttpStatus;
-import org.junit.Before;
-import org.junit.Test;
-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.CredentialsCreator;
-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 com.redhat.thermostat.storage.core.StorageCredentials;
-
-import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.http.HttpMethod;
-
-public class HttpRequestServiceTest {
-    private static final URI GATEWAY_URI = URI.create("http://127.0.0.1:30000/test/");
-    private static final URI GET_URI = GATEWAY_URI.resolve("?q=foo&l=3");
-    private static final String payload = "{}";
-    private static final String keycloakUrl = "http://127.0.0.1:31000/keycloak";
-    private static final char[] PASSWORD = new char[] { 'p', 'a', 's', 's' };
-    private static final String USERNAME = "testing";
-
-    private HttpClientCreator clientCreator;
-    private ConfigCreator configCreator;
-    private CredentialsCreator credsCreator;
-    private HttpClientFacade client;
-    private Request httpRequest;
-    
-    @Before
-    public void setup() throws InterruptedException, ExecutionException, TimeoutException {
-        client = mock(HttpClientFacade.class);
-        httpRequest = mock(Request.class);
-        when(client.newRequest(eq(GATEWAY_URI))).thenReturn(httpRequest);
-        ContentResponse response = mock(ContentResponse.class);
-        when(response.getStatus()).thenReturn(HttpStatus.OK_200);
-        when(httpRequest.send()).thenReturn(response);
-        clientCreator = mock(HttpClientCreator.class);
-        when(clientCreator.create(any(SSLConfiguration.class))).thenReturn(client);
-        configCreator = mock(ConfigCreator.class);
-        credsCreator = mock(CredentialsCreator.class);
-        StorageCredentials creds = mock(StorageCredentials.class);
-        when(creds.getPassword()).thenReturn(PASSWORD);
-        when(creds.getUsername()).thenReturn(USERNAME);
-        when(credsCreator.create(any(CommonPaths.class))).thenReturn(creds);
-    }
-
-    @Test
-    public void testRequestWithoutKeycloak() throws Exception {
-        AgentStartupConfiguration configuration = createBasicAuthConfig();
-
-        HttpRequestService service = createAndActivateRequestService(configuration);
-
-        service.sendHttpRequest(payload, GATEWAY_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.POST);
-
-        verify(configuration).isKeycloakEnabled();
-        verifyHttpPostRequest(httpRequest);
-    }
-
-    @Test
-    public void testRequestWithKeycloak() throws Exception {
-        AgentStartupConfiguration configuration = mock(AgentStartupConfiguration.class);
-        setupKeycloakConfig(configuration);
-
-        HttpRequestService service = createAndActivateRequestService(configuration);
-
-        Request keycloakRequest = mock(Request.class);
-        setupKeycloakRequest(keycloakRequest);
-
-        service.sendHttpRequest(payload, GATEWAY_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.POST);
-
-        verify(configuration).isKeycloakEnabled();
-        verifyHttpPostRequest(httpRequest);
-
-        verify(httpRequest).header(eq("Authorization"), eq("Bearer access"));
-        verifyKeycloakAcquire(keycloakRequest);
-    }
-
-    @Test
-    public void testRequestWithKeycloakRefresh() throws Exception {
-        AgentStartupConfiguration configuration = mock(AgentStartupConfiguration.class);
-        setupKeycloakConfig(configuration);
-
-        HttpRequestService service = createAndActivateRequestService(configuration);
-
-        Request keycloakRequest = mock(Request.class);
-        setupKeycloakRequest(keycloakRequest);
-
-        service.sendHttpRequest(payload, GATEWAY_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.POST);
-
-        verify(configuration).isKeycloakEnabled();
-        verifyHttpPostRequest(httpRequest);
-
-        verify(httpRequest).header(eq("Authorization"), eq("Bearer access"));
-
-        verifyKeycloakAcquire(keycloakRequest);
-
-        service.sendHttpRequest(payload, GATEWAY_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.POST);
-
-
-        ArgumentCaptor<StringContentProvider> payloadCaptor = ArgumentCaptor.forClass(StringContentProvider.class);
-        verify(keycloakRequest, times(2)).content(payloadCaptor.capture(), eq("application/x-www-form-urlencoded"));
-        verify(keycloakRequest, times(2)).method(eq(HttpMethod.POST));
-        verify(keycloakRequest, times(2)).send();
-
-        String expected = "grant_type=refresh_token&client_id=client&refresh_token=refresh";
-
-        StringContentProvider provider = payloadCaptor.getValue();
-        for (ByteBuffer buffer : provider) {
-            byte[] bytes = buffer.array();
-            String content = new String(bytes, StandardCharsets.UTF_8);
-            assertEquals(expected, content);
-        }
-    }
-
-    @Test
-    public void testRequestWithNullPayload() throws Exception {
-        AgentStartupConfiguration configuration = createBasicAuthConfig();
-
-        HttpRequestService service = createAndActivateRequestService(configuration);
-
-        String response = service.sendHttpRequest(null, GATEWAY_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.POST);
-        assertNull(response);
-
-        verify(client).newRequest(GATEWAY_URI);
-        verify(configuration).isKeycloakEnabled();
-
-        verify(httpRequest, times(0)).content(any(StringContentProvider.class), anyString());
-        verify(httpRequest).method(eq(HttpMethod.POST));
-        verify(httpRequest).send();
-    }
-    
-    @Test
-    public void verifyBasicAuthConfig() throws Exception {
-        AgentStartupConfiguration configuration = createBasicAuthConfig();
-
-        HttpRequestService service = createAndActivateRequestService(configuration);
-
-        service.sendHttpRequest(null, GATEWAY_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.GET);
-
-        verify(client).newRequest(GATEWAY_URI);
-        verify(configuration).isKeycloakEnabled();
-
-        ArgumentCaptor<String> authValueCaptor = ArgumentCaptor.forClass(String.class);
-        verify(httpRequest).header(eq(HttpHeader.AUTHORIZATION.asString()), authValueCaptor.capture());
-        String authValueEncoded = authValueCaptor.getValue();
-        assertTrue(authValueEncoded.startsWith("Basic "));
-        String userPassEncoded = authValueEncoded.substring("Basic ".length());
-        String decodedUserPass = getDecodedUserPass(userPassEncoded);
-        String expectedCreds = USERNAME + ":" + new String(PASSWORD);
-        assertEquals(expectedCreds, decodedUserPass);
-        verify(httpRequest).method(eq(HttpMethod.GET));
-        verify(httpRequest).send();
-    }
-    
-    /**
-     * If no authentication settings are done, no authorization headers should
-     * get added.
-     * 
-     * @throws Exception
-     */
-    @Test
-    public void verifyNoAuthConfig() throws Exception {
-        AgentStartupConfiguration configuration = createNoAuthConfig();
-
-        HttpRequestService service = createAndActivateRequestService(configuration);
-
-        service.sendHttpRequest(null, GATEWAY_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.GET);
-
-        verify(client).newRequest(GATEWAY_URI);
-        verify(configuration).isKeycloakEnabled();
-        verify(configuration).isBasicAuthEnabled();
-
-        verify(httpRequest, times(0)).header(eq(HttpHeader.AUTHORIZATION.asString()), anyString());
-        verify(httpRequest).method(eq(HttpMethod.GET));
-        verify(httpRequest).send();
-    }
-    
-    private String getDecodedUserPass(String userPassEncoded) throws IOException {
-        @SuppressWarnings("restriction")
-        byte[] decodedBytes = new sun.misc.BASE64Decoder().decodeBuffer(userPassEncoded);
-        return new String(decodedBytes);
-    }
-
-    private HttpRequestService createAndActivateRequestService(AgentStartupConfiguration configuration) throws Exception {
-        when(configCreator.create(any(CommonPaths.class))).thenReturn(configuration);
-        HttpRequestService service = new HttpRequestService(clientCreator, configCreator, credsCreator);
-        service.activate();
-        verify(client).start();
-        return service;
-    }
-
-    @Test
-    public void testGetRequestWithResponse() throws Exception {
-        String getContent = "foo bar";
-        HttpClientCreator creator = mock(HttpClientCreator.class);
-        HttpClientFacade getClient = setupHttpClient(creator, getContent);
-        
-        AgentStartupConfiguration configuration = createBasicAuthConfig();
-        ConfigCreator configCreator = mock(ConfigCreator.class);
-        when(configCreator.create(any(CommonPaths.class))).thenReturn(configuration);
-        HttpRequestService service = new HttpRequestService(creator, configCreator, credsCreator);
-        service.activate();
-        String content = service.sendHttpRequest(null, GET_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.GET);
-        verify(getClient).newRequest(GET_URI);
-        assertEquals(getContent, content);
-    }
-    
-    @Test
-    public void testGetRequestNormalizesURI() throws Exception {
-        String getContent = "foo bar";
-        HttpClientCreator creator = mock(HttpClientCreator.class);
-        HttpClientFacade getClient = setupHttpClient(creator, getContent);
-        
-        AgentStartupConfiguration configuration = createBasicAuthConfig();
-        ConfigCreator configCreator = mock(ConfigCreator.class);
-        when(configCreator.create(any(CommonPaths.class))).thenReturn(configuration);
-        HttpRequestService service = new HttpRequestService(creator, configCreator, credsCreator);
-        service.activate();
-        
-        // Add extra slashes to URI
-        URI uri = URI.create("http://127.0.0.1:30000//test//?q=bar&l=5");
-        URI normalized = URI.create("http://127.0.0.1:30000/test/?q=bar&l=5");
-        String content = service.sendHttpRequest(null, uri, com.redhat.thermostat.agent.http.HttpRequestService.Method.GET);
-        verify(getClient).newRequest(normalized);
-        assertEquals(getContent, content);
-    }
-
-    private HttpClientFacade setupHttpClient(HttpClientCreator creator, String getContent) throws Exception {
-        Request request = mock(Request.class);
-        ContentResponse contentResponse = mock(ContentResponse.class);
-        when(contentResponse.getStatus()).thenReturn(HttpStatus.OK_200);
-        when(contentResponse.getContentAsString()).thenReturn(getContent);
-        when(request.send()).thenReturn(contentResponse);
-        HttpClientFacade getClient = mock(HttpClientFacade.class);
-        when(getClient.newRequest(any(URI.class))).thenReturn(request);
-        when(creator.create(any(SSLConfiguration.class))).thenReturn(getClient);
-        return getClient;
-    }
-    
-    @Test(expected = RequestFailedException.class)
-    public void failureThrowsRequestFailedException() throws Exception {
-        Request request = mock(Request.class);
-        when(client.newRequest(any(URI.class))).thenReturn(request);
-        AgentStartupConfiguration configuration = createBasicAuthConfig();
-        doThrow(IOException.class).when(request).send();
-        HttpRequestService service = createAndActivateRequestService(configuration);
-        service.sendHttpRequest("foo", GATEWAY_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.DELETE /*any valid method*/);
-    }
-
-    private AgentStartupConfiguration createBasicAuthConfig() {
-        return createAuthConfig(true, false);
-    }
-    
-    private AgentStartupConfiguration createNoAuthConfig() {
-        return createAuthConfig(false, false);
-    }
-    
-    private AgentStartupConfiguration createAuthConfig(boolean isBasicAuthEnabled, boolean isKeycloakEnabled) {
-        AgentStartupConfiguration configuration = mock(AgentStartupConfiguration.class);
-        when(configuration.isKeycloakEnabled()).thenReturn(isKeycloakEnabled);
-        when(configuration.isBasicAuthEnabled()).thenReturn(isBasicAuthEnabled);
-        return configuration;
-    }
-
-    private void verifyHttpPostRequest(Request httpRequest) throws InterruptedException, ExecutionException, TimeoutException {
-        verify(client).newRequest(GATEWAY_URI);
-        verify(httpRequest).content(any(StringContentProvider.class), eq("application/json"));
-        verify(httpRequest).method(eq(HttpMethod.POST));
-        verify(httpRequest).send();
-    }
-
-    private void setupKeycloakConfig(AgentStartupConfiguration configuration) {
-        when(configuration.isKeycloakEnabled()).thenReturn(true);
-        when(configuration.getKeycloakUrl()).thenReturn(keycloakUrl);
-        when(configuration.getKeycloakClient()).thenReturn("client");
-        when(configuration.getKeycloakRealm()).thenReturn("realm");
-    }
-
-    private void setupKeycloakRequest(Request keycloakRequest) throws InterruptedException, ExecutionException, TimeoutException {
-        when(client.newRequest(keycloakUrl + "/auth/realms/realm/protocol/openid-connect/token")).thenReturn(keycloakRequest);
-
-        ContentResponse response = mock(ContentResponse.class);
-        when(response.getStatus()).thenReturn(HttpStatus.OK_200);
-
-        String keycloakToken = "{" +
-                "\"access_token\": \"access\"," +
-                "\"expires_in\": 0," +
-                "\"refresh_expires_in\": 2," +
-                "\"refresh_token\": \"refresh\"," +
-                "\"token_type\": \"bearer\"," +
-                "\"id_token\": \"id\"," +
-                "\"not-before-policy\": 3," +
-                "\"session_state\": \"state\"" +
-                "}";
-
-        when(response.getContentAsString()).thenReturn(keycloakToken);
-        when(keycloakRequest.send()).thenReturn(response);
-    }
-
-    private void verifyKeycloakAcquire(Request keycloakRequest) throws InterruptedException, ExecutionException, TimeoutException {
-        ArgumentCaptor<StringContentProvider> payloadCaptor = ArgumentCaptor.forClass(StringContentProvider.class);
-        verify(keycloakRequest).content(payloadCaptor.capture(), eq("application/x-www-form-urlencoded"));
-        verify(keycloakRequest).method(eq(HttpMethod.POST));
-        verify(keycloakRequest).send();
-
-        String expected = "grant_type=password&client_id=client&username=" + USERNAME + "&password=" + new String(PASSWORD);
-
-        StringContentProvider provider = payloadCaptor.getValue();
-        for (ByteBuffer buffer : provider) {
-            byte[] bytes = buffer.array();
-            String content = new String(bytes, StandardCharsets.UTF_8);
-            assertEquals(expected, content);
-        }
-
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/test/java/com/redhat/thermostat/agent/internal/http/HttpRequestServiceImplTest.java	Wed Sep 06 19:47:49 2017 +0200
@@ -0,0 +1,301 @@
+/*
+ * 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.http;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
+import com.redhat.thermostat.agent.http.HttpRequestService;
+import com.redhat.thermostat.agent.http.RequestFailedException;
+import com.redhat.thermostat.agent.internal.http.BasicHttpService.ConfigCreator;
+import com.redhat.thermostat.agent.internal.http.BasicHttpService.CredentialsCreator;
+import com.redhat.thermostat.agent.internal.http.BasicHttpService.HttpClientCreator;
+import com.redhat.thermostat.agent.keycloak.KeycloakAccessToken;
+import com.redhat.thermostat.agent.keycloak.KeycloakAccessTokenService;
+import com.redhat.thermostat.shared.config.CommonPaths;
+import com.redhat.thermostat.shared.config.SSLConfiguration;
+import com.redhat.thermostat.storage.core.StorageCredentials;
+
+public class HttpRequestServiceImplTest {
+    private static final URI GATEWAY_URI = URI.create("http://127.0.0.1:30000/test/");
+    private static final URI GET_URI = GATEWAY_URI.resolve("?q=foo&l=3");
+    private static final String payload = "{}";
+    private static final char[] PASSWORD = new char[] { 'p', 'a', 's', 's' };
+    private static final String USERNAME = "testing";
+    private static final String KEYCLOAK_ACCESS_TOKEN_STRING = "keycloak-bearer-token-value";
+
+    private HttpClientCreator clientCreator;
+    private ConfigCreator configCreator;
+    private CredentialsCreator credsCreator;
+    private HttpClientFacade client;
+    private Request httpRequest;
+    private KeycloakAccessTokenService tokenService;
+    
+    @Before
+    public void setup() throws InterruptedException, ExecutionException, TimeoutException, RequestFailedException {
+        client = mock(HttpClientFacade.class);
+        httpRequest = mock(Request.class);
+        when(client.newRequest(eq(GATEWAY_URI))).thenReturn(httpRequest);
+        ContentResponse response = mock(ContentResponse.class);
+        when(response.getStatus()).thenReturn(HttpStatus.OK_200);
+        when(httpRequest.send()).thenReturn(response);
+        clientCreator = mock(HttpClientCreator.class);
+        when(clientCreator.create(any(SSLConfiguration.class))).thenReturn(client);
+        configCreator = mock(ConfigCreator.class);
+        credsCreator = mock(CredentialsCreator.class);
+        StorageCredentials creds = mock(StorageCredentials.class);
+        when(creds.getPassword()).thenReturn(PASSWORD);
+        when(creds.getUsername()).thenReturn(USERNAME);
+        when(credsCreator.create(any(CommonPaths.class))).thenReturn(creds);
+        tokenService = mock(KeycloakAccessTokenService.class);
+        KeycloakAccessToken keycloakAccessToken = mock(KeycloakAccessToken.class);
+        when(keycloakAccessToken.getAccessToken()).thenReturn(KEYCLOAK_ACCESS_TOKEN_STRING);
+        when(tokenService.getAccessToken()).thenReturn(keycloakAccessToken);
+    }
+
+    @Test
+    public void testRequestWithBasicAuth() throws Exception {
+        AgentStartupConfiguration configuration = createBasicAuthConfig();
+
+        HttpRequestService service = createAndActivateRequestService(configuration);
+
+        service.sendHttpRequest(payload, GATEWAY_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.POST);
+
+        verify(configuration).isKeycloakEnabled();
+        verifyHttpPostRequest(httpRequest);
+        verify(tokenService, times(0)).getAccessToken();
+    }
+
+    @Test
+    public void testRequestWithKeycloakAuth() throws Exception {
+        AgentStartupConfiguration configuration = mock(AgentStartupConfiguration.class);
+        when(configuration.isKeycloakEnabled()).thenReturn(true);
+
+        HttpRequestService service = createAndActivateRequestService(configuration);
+
+        service.sendHttpRequest(payload, GATEWAY_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.POST);
+
+        verify(configuration).isKeycloakEnabled();
+        verifyHttpPostRequest(httpRequest);
+
+        verify(httpRequest).header(eq("Authorization"), eq("Bearer " + KEYCLOAK_ACCESS_TOKEN_STRING));
+        verify(tokenService).getAccessToken();
+    }
+
+    @Test
+    public void testRequestWithNullPayload() throws Exception {
+        AgentStartupConfiguration configuration = createBasicAuthConfig();
+
+        HttpRequestService service = createAndActivateRequestService(configuration);
+
+        String response = service.sendHttpRequest(null, GATEWAY_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.POST);
+        assertNull(response);
+
+        verify(client).newRequest(GATEWAY_URI);
+        verify(configuration).isKeycloakEnabled();
+
+        verify(httpRequest, times(0)).content(any(StringContentProvider.class), anyString());
+        verify(httpRequest).method(eq(HttpMethod.POST));
+        verify(httpRequest).send();
+    }
+    
+    @Test
+    public void verifyBasicAuthConfig() throws Exception {
+        AgentStartupConfiguration configuration = createBasicAuthConfig();
+
+        HttpRequestService service = createAndActivateRequestService(configuration);
+
+        service.sendHttpRequest(null, GATEWAY_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.GET);
+
+        verify(client).newRequest(GATEWAY_URI);
+        verify(configuration).isKeycloakEnabled();
+
+        ArgumentCaptor<String> authValueCaptor = ArgumentCaptor.forClass(String.class);
+        verify(httpRequest).header(eq(HttpHeader.AUTHORIZATION.asString()), authValueCaptor.capture());
+        String authValueEncoded = authValueCaptor.getValue();
+        assertTrue(authValueEncoded.startsWith("Basic "));
+        String userPassEncoded = authValueEncoded.substring("Basic ".length());
+        String decodedUserPass = getDecodedUserPass(userPassEncoded);
+        String expectedCreds = USERNAME + ":" + new String(PASSWORD);
+        assertEquals(expectedCreds, decodedUserPass);
+        verify(httpRequest).method(eq(HttpMethod.GET));
+        verify(httpRequest).send();
+    }
+    
+    /**
+     * If no authentication settings are done, no authorization headers should
+     * get added.
+     * 
+     * @throws Exception
+     */
+    @Test
+    public void verifyNoAuthConfig() throws Exception {
+        AgentStartupConfiguration configuration = createNoAuthConfig();
+
+        HttpRequestService service = createAndActivateRequestService(configuration);
+
+        service.sendHttpRequest(null, GATEWAY_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.GET);
+
+        verify(client).newRequest(GATEWAY_URI);
+        verify(configuration).isKeycloakEnabled();
+        verify(configuration).isBasicAuthEnabled();
+
+        verify(httpRequest, times(0)).header(eq(HttpHeader.AUTHORIZATION.asString()), anyString());
+        verify(httpRequest).method(eq(HttpMethod.GET));
+        verify(httpRequest).send();
+    }
+    
+    private String getDecodedUserPass(String userPassEncoded) throws IOException {
+        @SuppressWarnings("restriction")
+        byte[] decodedBytes = new sun.misc.BASE64Decoder().decodeBuffer(userPassEncoded);
+        return new String(decodedBytes);
+    }
+
+    private HttpRequestService createAndActivateRequestService(AgentStartupConfiguration configuration) throws Exception {
+        when(configCreator.create(any(CommonPaths.class))).thenReturn(configuration);
+        HttpRequestServiceImpl service = new HttpRequestServiceImpl(clientCreator, configCreator, credsCreator);
+        service.bindTokenService(tokenService);
+        service.activate();
+        verify(client).start();
+        return service;
+    }
+
+    @Test
+    public void testGetRequestWithResponse() throws Exception {
+        String getContent = "foo bar";
+        HttpClientCreator creator = mock(HttpClientCreator.class);
+        HttpClientFacade getClient = setupHttpClient(creator, getContent);
+        
+        AgentStartupConfiguration configuration = createBasicAuthConfig();
+        ConfigCreator configCreator = mock(ConfigCreator.class);
+        when(configCreator.create(any(CommonPaths.class))).thenReturn(configuration);
+        HttpRequestServiceImpl service = new HttpRequestServiceImpl(creator, configCreator, credsCreator);
+        service.activate();
+        String content = service.sendHttpRequest(null, GET_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.GET);
+        verify(getClient).newRequest(GET_URI);
+        assertEquals(getContent, content);
+    }
+    
+    @Test
+    public void testGetRequestNormalizesURI() throws Exception {
+        String getContent = "foo bar";
+        HttpClientCreator creator = mock(HttpClientCreator.class);
+        HttpClientFacade getClient = setupHttpClient(creator, getContent);
+        
+        AgentStartupConfiguration configuration = createBasicAuthConfig();
+        ConfigCreator configCreator = mock(ConfigCreator.class);
+        when(configCreator.create(any(CommonPaths.class))).thenReturn(configuration);
+        HttpRequestServiceImpl service = new HttpRequestServiceImpl(creator, configCreator, credsCreator);
+        service.activate();
+        
+        // Add extra slashes to URI
+        URI uri = URI.create("http://127.0.0.1:30000//test//?q=bar&l=5");
+        URI normalized = URI.create("http://127.0.0.1:30000/test/?q=bar&l=5");
+        String content = service.sendHttpRequest(null, uri, com.redhat.thermostat.agent.http.HttpRequestService.Method.GET);
+        verify(getClient).newRequest(normalized);
+        assertEquals(getContent, content);
+    }
+
+    private HttpClientFacade setupHttpClient(HttpClientCreator creator, String getContent) throws Exception {
+        Request request = mock(Request.class);
+        ContentResponse contentResponse = mock(ContentResponse.class);
+        when(contentResponse.getStatus()).thenReturn(HttpStatus.OK_200);
+        when(contentResponse.getContentAsString()).thenReturn(getContent);
+        when(request.send()).thenReturn(contentResponse);
+        HttpClientFacade getClient = mock(HttpClientFacade.class);
+        when(getClient.newRequest(any(URI.class))).thenReturn(request);
+        when(creator.create(any(SSLConfiguration.class))).thenReturn(getClient);
+        return getClient;
+    }
+    
+    @Test(expected = RequestFailedException.class)
+    public void failureThrowsRequestFailedException() throws Exception {
+        Request request = mock(Request.class);
+        when(client.newRequest(any(URI.class))).thenReturn(request);
+        AgentStartupConfiguration configuration = createBasicAuthConfig();
+        doThrow(RequestFailedException.class).when(request).send();
+        HttpRequestService service = createAndActivateRequestService(configuration);
+        service.sendHttpRequest("foo", GATEWAY_URI, com.redhat.thermostat.agent.http.HttpRequestService.Method.DELETE /*any valid method*/);
+    }
+
+    private AgentStartupConfiguration createBasicAuthConfig() {
+        return createAuthConfig(true, false);
+    }
+    
+    private AgentStartupConfiguration createNoAuthConfig() {
+        return createAuthConfig(false, false);
+    }
+    
+    private AgentStartupConfiguration createAuthConfig(boolean isBasicAuthEnabled, boolean isKeycloakEnabled) {
+        AgentStartupConfiguration configuration = mock(AgentStartupConfiguration.class);
+        when(configuration.isKeycloakEnabled()).thenReturn(isKeycloakEnabled);
+        when(configuration.isBasicAuthEnabled()).thenReturn(isBasicAuthEnabled);
+        return configuration;
+    }
+
+    private void verifyHttpPostRequest(Request httpRequest) throws InterruptedException, ExecutionException, TimeoutException {
+        verify(client).newRequest(GATEWAY_URI);
+        verify(httpRequest).content(any(StringContentProvider.class), eq("application/json"));
+        verify(httpRequest).method(eq(HttpMethod.POST));
+        verify(httpRequest).send();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/test/java/com/redhat/thermostat/agent/internal/http/KeycloakAccessTokenServiceImplTest.java	Wed Sep 06 19:47:49 2017 +0200
@@ -0,0 +1,189 @@
+/*
+ * 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.http;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import com.redhat.thermostat.agent.config.AgentStartupConfiguration;
+import com.redhat.thermostat.agent.http.RequestFailedException;
+import com.redhat.thermostat.agent.internal.http.BasicHttpService.ConfigCreator;
+import com.redhat.thermostat.agent.internal.http.BasicHttpService.CredentialsCreator;
+import com.redhat.thermostat.agent.internal.http.BasicHttpService.HttpClientCreator;
+import com.redhat.thermostat.agent.keycloak.KeycloakAccessToken;
+import com.redhat.thermostat.agent.keycloak.KeycloakAccessTokenService;
+import com.redhat.thermostat.shared.config.CommonPaths;
+import com.redhat.thermostat.shared.config.SSLConfiguration;
+import com.redhat.thermostat.storage.core.StorageCredentials;
+
+public class KeycloakAccessTokenServiceImplTest {
+
+    private static final String KEYCLOAK_URL = "http://127.0.0.1:31000/keycloak";
+    private static final String KEYCLOAK_CLIENT = "client";
+    private static final String KEYCLOAK_REALM = "realm";
+    private static final char[] PASSWORD = new char[] { 'p', 'a', 's', 's' };
+    private static final String USERNAME = "testing";
+
+    private HttpClientCreator clientCreator;
+    private ConfigCreator configCreator;
+    private CredentialsCreator credsCreator;
+    private HttpClientFacade client;
+    private AgentStartupConfiguration configuration;
+    private Request keycloakRequest = mock(Request.class);
+    
+    @Before
+    public void setup() throws InterruptedException, ExecutionException, TimeoutException, RequestFailedException {
+        client = mock(HttpClientFacade.class);
+        clientCreator = mock(HttpClientCreator.class);
+        when(clientCreator.create(any(SSLConfiguration.class))).thenReturn(client);
+        configCreator = mock(ConfigCreator.class);
+        credsCreator = mock(CredentialsCreator.class);
+        StorageCredentials creds = mock(StorageCredentials.class);
+        when(creds.getPassword()).thenReturn(PASSWORD);
+        when(creds.getUsername()).thenReturn(USERNAME);
+        when(credsCreator.create(any(CommonPaths.class))).thenReturn(creds);
+        configuration = mock(AgentStartupConfiguration.class);
+        setupKeycloakConfig(configuration);
+        keycloakRequest = mock(Request.class);
+        setupKeycloakRequest(keycloakRequest);
+    }
+
+    @Test
+    public void verifyGetAccessToken() throws Exception {
+        KeycloakAccessTokenService service = createAndActivateRequestService();
+
+        service.getAccessToken();
+
+        verifyKeycloakAcquire();
+    }
+    
+    @Test
+    public void verifyKeycloakAccessTokenRefresh() throws Exception {
+        String refreshTokenValue = "refresh";
+        KeycloakAccessToken expiredToken = mock(KeycloakAccessToken.class);
+        when(expiredToken.isKeycloakTokenExpired()).thenReturn(true);
+        when(expiredToken.getRefreshToken()).thenReturn(refreshTokenValue);
+        KeycloakAccessTokenService service = createAndActivateRequestService(expiredToken);
+    
+        service.getAccessToken();
+    
+        verifyKeycloakRefresh(refreshTokenValue);
+    }
+
+    private KeycloakAccessTokenService createAndActivateRequestService() throws Exception {
+        return createAndActivateRequestService(null);
+    }
+
+    private KeycloakAccessTokenService createAndActivateRequestService(KeycloakAccessToken accessTokenInitial) throws Exception {
+        when(configCreator.create(any(CommonPaths.class))).thenReturn(configuration);
+        KeycloakAccessTokenServiceImpl service = new KeycloakAccessTokenServiceImpl(clientCreator, configCreator, credsCreator);
+        service.setKeycloakAccessToken(accessTokenInitial);
+        service.activate();
+        verify(client).start();
+        return service;
+    }
+
+    private void setupKeycloakConfig(AgentStartupConfiguration configuration) {
+        when(configuration.isKeycloakEnabled()).thenReturn(true);
+        when(configuration.getKeycloakUrl()).thenReturn(KEYCLOAK_URL);
+        when(configuration.getKeycloakClient()).thenReturn(KEYCLOAK_CLIENT);
+        when(configuration.getKeycloakRealm()).thenReturn(KEYCLOAK_REALM);
+    }
+
+    private void setupKeycloakRequest(Request keycloakRequest) throws InterruptedException, ExecutionException, TimeoutException {
+        when(client.newRequest(KEYCLOAK_URL + "/auth/realms/" + KEYCLOAK_REALM + "/protocol/openid-connect/token")).thenReturn(keycloakRequest);
+
+        ContentResponse response = mock(ContentResponse.class);
+        when(response.getStatus()).thenReturn(HttpStatus.OK_200);
+
+        String keycloakToken = "{" +
+                "\"access_token\": \"access\"," +
+                "\"expires_in\": 0," +
+                "\"refresh_expires_in\": 2," +
+                "\"refresh_token\": \"refresh\"," +
+                "\"token_type\": \"bearer\"," +
+                "\"id_token\": \"id\"," +
+                "\"not-before-policy\": 3," +
+                "\"session_state\": \"state\"" +
+                "}";
+
+        when(response.getContentAsString()).thenReturn(keycloakToken);
+        when(keycloakRequest.send()).thenReturn(response);
+    }
+
+    private void verifyKeycloakAcquire() throws InterruptedException, ExecutionException, TimeoutException {
+        String expected = "grant_type=password&client_id=" + KEYCLOAK_CLIENT + "&username=" + USERNAME + "&password=" + new String(PASSWORD);
+        doKeycloakVerification(expected);
+    }
+    
+    private void verifyKeycloakRefresh(String refreshTokenValue) throws InterruptedException, TimeoutException, ExecutionException {
+        String expected = "grant_type=refresh_token&client_id=" + KEYCLOAK_CLIENT + "&refresh_token=" + refreshTokenValue;
+        doKeycloakVerification(expected);
+    }
+    
+    private void doKeycloakVerification(final String expectedPayload) throws InterruptedException, TimeoutException, ExecutionException {
+        ArgumentCaptor<StringContentProvider> payloadCaptor = ArgumentCaptor.forClass(StringContentProvider.class);
+        verify(keycloakRequest).content(payloadCaptor.capture(), eq("application/x-www-form-urlencoded"));
+        verify(keycloakRequest).method(eq(HttpMethod.POST));
+        verify(keycloakRequest).send();
+    
+        StringContentProvider provider = payloadCaptor.getValue();
+        for (ByteBuffer buffer : provider) {
+            byte[] bytes = buffer.array();
+            String content = new String(bytes, StandardCharsets.UTF_8);
+            assertEquals(expectedPayload, content);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/core/src/test/java/com/redhat/thermostat/agent/keycloak/KeycloakAccessTokenTest.java	Wed Sep 06 19:47:49 2017 +0200
@@ -0,0 +1,68 @@
+/*
+ * 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.keycloak;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+import org.junit.Test;
+
+public class KeycloakAccessTokenTest {
+
+    private static final int MINUTES = 60;
+
+    @Test
+    public void verifyExpiredToken() {
+        Long expiresIn = 3L;
+        Long acquireTime = 3L;
+        KeycloakAccessToken token = new KeycloakAccessToken(expiresIn);
+        token.setAcquireTime(acquireTime);
+        assumeTrue(System.nanoTime() > TimeUnit.NANOSECONDS.convert(expiresIn, TimeUnit.SECONDS) + acquireTime);
+        assertEquals("token is expired", true, token.isKeycloakTokenExpired());
+    }
+    
+    @Test
+    public void verifyValidToken() {
+        Long expiresIn = 10L * MINUTES;
+        Long acquireTime = System.nanoTime();
+        KeycloakAccessToken token = new KeycloakAccessToken(expiresIn);
+        token.setAcquireTime(acquireTime);
+        assumeTrue(System.nanoTime() <= TimeUnit.NANOSECONDS.convert(expiresIn, TimeUnit.SECONDS) + acquireTime);
+        assertEquals("token should still be valid", false, token.isKeycloakTokenExpired());
+    }
+}
--- a/common/plugin/src/main/java/com/redhat/thermostat/common/plugin/PluginDAOBase.java	Thu Sep 07 18:41:40 2017 +0200
+++ b/common/plugin/src/main/java/com/redhat/thermostat/common/plugin/PluginDAOBase.java	Wed Sep 06 19:47:49 2017 +0200
@@ -42,7 +42,7 @@
 import java.util.logging.Logger;
 
 import com.redhat.thermostat.agent.http.HttpRequestService;
-import com.redhat.thermostat.agent.http.HttpRequestService.RequestFailedException;
+import com.redhat.thermostat.agent.http.RequestFailedException;
 
 abstract public class PluginDAOBase<Tobj> {
 
--- a/plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoDAOImpl.java	Thu Sep 07 18:41:40 2017 +0200
+++ b/plugins/jvm-overview/agent/src/main/java/com/redhat/thermostat/jvm/overview/agent/internal/model/VmInfoDAOImpl.java	Wed Sep 06 19:47:49 2017 +0200
@@ -51,7 +51,7 @@
 import org.apache.felix.scr.annotations.Service;
 
 import com.redhat.thermostat.agent.http.HttpRequestService;
-import com.redhat.thermostat.agent.http.HttpRequestService.RequestFailedException;
+import com.redhat.thermostat.agent.http.RequestFailedException;
 import com.redhat.thermostat.common.config.experimental.ConfigurationInfoSource;
 import com.redhat.thermostat.common.plugin.PluginConfiguration;
 import com.redhat.thermostat.common.plugin.SystemID;
--- a/plugins/vm-gc/agent/src/main/java/com/redhat/thermostat/vm/gc/agent/internal/models/VmGcStatDAOImpl.java	Thu Sep 07 18:41:40 2017 +0200
+++ b/plugins/vm-gc/agent/src/main/java/com/redhat/thermostat/vm/gc/agent/internal/models/VmGcStatDAOImpl.java	Wed Sep 06 19:47:49 2017 +0200
@@ -50,7 +50,7 @@
 import org.apache.felix.scr.annotations.Service;
 
 import com.redhat.thermostat.agent.http.HttpRequestService;
-import com.redhat.thermostat.agent.http.HttpRequestService.RequestFailedException;
+import com.redhat.thermostat.agent.http.RequestFailedException;
 import com.redhat.thermostat.common.config.experimental.ConfigurationInfoSource;
 import com.redhat.thermostat.common.plugin.PluginConfiguration;
 import com.redhat.thermostat.common.utils.LoggingUtils;
--- a/plugins/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/models/VmMemoryStatDAOImpl.java	Thu Sep 07 18:41:40 2017 +0200
+++ b/plugins/vm-memory/agent/src/main/java/com/redhat/thermostat/vm/memory/agent/internal/models/VmMemoryStatDAOImpl.java	Wed Sep 06 19:47:49 2017 +0200
@@ -50,7 +50,7 @@
 import org.apache.felix.scr.annotations.Service;
 
 import com.redhat.thermostat.agent.http.HttpRequestService;
-import com.redhat.thermostat.agent.http.HttpRequestService.RequestFailedException;
+import com.redhat.thermostat.agent.http.RequestFailedException;
 import com.redhat.thermostat.common.config.experimental.ConfigurationInfoSource;
 import com.redhat.thermostat.common.plugin.PluginConfiguration;
 import com.redhat.thermostat.common.utils.LoggingUtils;