Mercurial > hg > thermostat-ng > agent
changeset 2697:7184fbd4e41f
Add http service with support for keycloak auth
Reviewed-by: jerboaa, neugens
Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-June/023707.html
line wrap: on
line diff
--- a/agent/core/pom.xml Tue Jun 20 14:44:16 2017 -0400 +++ b/agent/core/pom.xml Wed Jun 21 14:02:39 2017 -0400 @@ -105,6 +105,11 @@ <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> + <!-- declarative services --> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.scr.annotations</artifactId> + </dependency> </dependencies> <build> @@ -120,6 +125,8 @@ <Bundle-Activator>com.redhat.thermostat.agent.internal.Activator</Bundle-Activator> <Export-Package> com.redhat.thermostat.agent, + com.redhat.thermostat.agent.http, + com.redhat.thermostat.agent.http.internal.keycloak, com.redhat.thermostat.agent.config, com.redhat.thermostat.agent.dao, com.redhat.thermostat.agent.utils, @@ -138,6 +145,19 @@ </plugin> <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-scr-plugin</artifactId> + <executions> + <execution> + <id>generate-scr-scrdescriptor</id> + <goals> + <goal>scr</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> <executions>
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/config/AgentConfigsUtils.java Tue Jun 20 14:44:16 2017 -0400 +++ b/agent/core/src/main/java/com/redhat/thermostat/agent/config/AgentConfigsUtils.java Wed Jun 21 14:02:39 2017 -0400 @@ -92,6 +92,18 @@ configuration.setPurge(true); } + Boolean keycloakEnabled = Boolean.valueOf(properties.getProperty(AgentProperties.KEYCLOAK_ENABLED.name())); + if (keycloakEnabled != null) { + configuration.setKeycloakEnabled(keycloakEnabled); + + configuration.setKeycloakRealm(properties.getProperty(AgentProperties.KEYCLOAK_REALM.name())); + configuration.setKeycloakUrl(properties.getProperty(AgentProperties.KEYCLOAK_URL.name())); + configuration.setKeycloakClient(properties.getProperty(AgentProperties.KEYCLOAK_CLIENT.name())); + configuration.setKeycloakUsername(properties.getProperty(AgentProperties.KEYCLOAK_USERNAME.name())); + configuration.setKeycloakPassword(properties.getProperty(AgentProperties.KEYCLOAK_PASSWORD.name())); + } else { + configuration.setKeycloakEnabled(false); + } } }
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/config/AgentProperties.java Tue Jun 20 14:44:16 2017 -0400 +++ b/agent/core/src/main/java/com/redhat/thermostat/agent/config/AgentProperties.java Wed Jun 21 14:02:39 2017 -0400 @@ -40,5 +40,12 @@ DB_URL, SAVE_ON_EXIT, + + KEYCLOAK_ENABLED, + KEYCLOAK_URL, + KEYCLOAK_REALM, + KEYCLOAK_CLIENT, + KEYCLOAK_USERNAME, + KEYCLOAK_PASSWORD }
--- a/agent/core/src/main/java/com/redhat/thermostat/agent/config/AgentStartupConfiguration.java Tue Jun 20 14:44:16 2017 -0400 +++ b/agent/core/src/main/java/com/redhat/thermostat/agent/config/AgentStartupConfiguration.java Wed Jun 21 14:02:39 2017 -0400 @@ -41,19 +41,26 @@ public class AgentStartupConfiguration implements StartupConfiguration { private boolean purge; - private String url; + private String dbUrl; private long startTime; + + private boolean keycloakEnabled; + private String keycloakUrl; + private String keycloakRealm; + private String keycloakClient; + private String keycloakUsername; + private String keycloakPassword; AgentStartupConfiguration() { } @Override public String getDBConnectionString() { - return url; + return dbUrl; } public void setDatabaseURL(String url) { - this.url = url; + this.dbUrl = url; } // TODO: that should be a friend, we only want the Service to set this value @@ -72,5 +79,53 @@ public boolean purge() { return purge; } + + public String getKeycloakUrl() { + return keycloakUrl; + } + + public void setKeycloakUrl(String keycloakUrl) { + this.keycloakUrl = keycloakUrl; + } + + public String getKeycloakClient() { + return keycloakClient; + } + + public void setKeycloakClient(String keycloakClient) { + this.keycloakClient = keycloakClient; + } + + public String getKeycloakUsername() { + return keycloakUsername; + } + + public void setKeycloakUsername(String keycloakUsername) { + this.keycloakUsername = keycloakUsername; + } + + public String getKeycloakPassword() { + return keycloakPassword; + } + + public void setKeycloakPassword(String keycloakPassword) { + this.keycloakPassword = keycloakPassword; + } + + public boolean isKeycloakEnabled() { + return keycloakEnabled; + } + + public void setKeycloakEnabled(boolean keycloakEnabled) { + this.keycloakEnabled = keycloakEnabled; + } + + public String getKeycloakRealm() { + return keycloakRealm; + } + + public void setKeycloakRealm(String keycloakRealm) { + this.keycloakRealm = keycloakRealm; + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/core/src/main/java/com/redhat/thermostat/agent/http/HttpRequestService.java Wed Jun 21 14:02:39 2017 -0400 @@ -0,0 +1,194 @@ +/* + * 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.io.IOException; +import java.net.ConnectException; +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.Service; +import org.eclipse.jetty.client.HttpClient; +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.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; + +@Component +@Service(value = HttpRequestService.class) +public class HttpRequestService { + private static final Logger logger = LoggingUtils.getLogger(HttpRequestService.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 Gson gson = new GsonBuilder().create(); + private HttpClient client; + private AgentStartupConfiguration agentStartupConfiguration; + + private KeycloakAccessToken keycloakAccessToken; + + public HttpRequestService() { + this(new HttpClient(), AgentConfigsUtils.createAgentConfigs()); + } + + HttpRequestService(HttpClient client, AgentStartupConfiguration agentStartupConfiguration) { + this.client = client; + this.agentStartupConfiguration = agentStartupConfiguration; + } + + @Activate + public void activate() { + try { + client.start(); + + logger.log(Level.FINE, "HttpRequestService activated"); + } catch (Exception e) { + logger.log(Level.SEVERE, "HttpRequestService failed to start correctly. Behaviour undefined.", e); + } + } + + /** + * Send a HTTP request + * @param jsonPayload The payload to send, or null if no payload + * @param url The complete url to send to + * @param requestType The HTTP request type: GET, PUT, POST or DELETE + * @return + */ + public ContentResponse sendHttpRequest(String jsonPayload, String url, HttpMethod requestType) throws IOException { + Request request = client.newRequest(url); + if (jsonPayload != null) { + request.content(new StringContentProvider(jsonPayload), "application/json"); + } + + request.method(requestType); + + if (agentStartupConfiguration.isKeycloakEnabled()) { + request.header("Authorization", "Bearer " + getAccessToken()); + } + + try { + return request.send(); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + return null; + } + } + + 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() { + return "grant_type=password&client_id=" + agentStartupConfiguration.getKeycloakClient() + + "&username=" + agentStartupConfiguration.getKeycloakUsername() + + "&password=" + agentStartupConfiguration.getKeycloakPassword(); + } + + private String getKeycloakRefreshPayload() { + return "grant_type=refresh_token&client_id=" + agentStartupConfiguration.getKeycloakClient() + + "&refresh_token=" + keycloakAccessToken.getRefreshToken(); + } +} \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/agent/core/src/main/java/com/redhat/thermostat/agent/http/internal/keycloak/KeycloakAccessToken.java Wed Jun 21 14:02:39 2017 -0400 @@ -0,0 +1,83 @@ +/* + * 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/test/java/com/redhat/thermostat/agent/http/HttpRequestServiceTest.java Wed Jun 21 14:02:39 2017 -0400 @@ -0,0 +1,222 @@ +/* + * 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.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +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.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.eclipse.jetty.client.HttpClient; +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; + +public class HttpRequestServiceTest { + private String payload = "{}"; + private String url = "http://127.0.0.1:30000/test"; + private HttpMethod method = HttpMethod.POST; + private String keycloakUrl = "http://127.0.0.1:31000/keycloak"; + + private HttpClient client; + private Request httpRequest; + + @Before + public void setup() throws InterruptedException, ExecutionException, TimeoutException { + client = mock(HttpClient.class); + httpRequest = mock(Request.class); + when(client.newRequest(eq(url))).thenReturn(httpRequest); + when(httpRequest.send()).thenReturn(mock(ContentResponse.class)); + } + + @Test + public void testRequestWithoutKeycloak() throws InterruptedException, ExecutionException, TimeoutException, IOException { + AgentStartupConfiguration configuration = mock(AgentStartupConfiguration.class); + when(configuration.isKeycloakEnabled()).thenReturn(false); + + HttpRequestService service = new HttpRequestService(client, configuration); + + service.sendHttpRequest(payload, url, method); + + verify(configuration).isKeycloakEnabled(); + verifyHttpRequest(httpRequest); + } + + @Test + public void testRequestWithKeycloak() throws InterruptedException, ExecutionException, TimeoutException, IOException { + AgentStartupConfiguration configuration = mock(AgentStartupConfiguration.class); + setupKeycloakConfig(configuration); + + HttpRequestService service = new HttpRequestService(client, configuration); + + Request keycloakRequest = mock(Request.class); + setupKeycloakRequest(keycloakRequest); + + service.sendHttpRequest(payload, url, method); + + verify(configuration).isKeycloakEnabled(); + verifyHttpRequest(httpRequest); + + verify(httpRequest).header(eq("Authorization"), eq("Bearer access")); + verifyKeycloakAcquire(keycloakRequest); + } + + @Test + public void testRequestWithKeycloakRefresh() throws InterruptedException, ExecutionException, TimeoutException, IOException { + AgentStartupConfiguration configuration = mock(AgentStartupConfiguration.class); + setupKeycloakConfig(configuration); + + HttpRequestService service = new HttpRequestService(client, configuration); + + Request keycloakRequest = mock(Request.class); + setupKeycloakRequest(keycloakRequest); + + service.sendHttpRequest(payload, url, method); + + verify(configuration).isKeycloakEnabled(); + verifyHttpRequest(httpRequest); + + verify(httpRequest).header(eq("Authorization"), eq("Bearer access")); + + verifyKeycloakAcquire(keycloakRequest); + + service.sendHttpRequest(payload, url, method); + + + 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(method)); + 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 InterruptedException, ExecutionException, TimeoutException, IOException { + AgentStartupConfiguration configuration = mock(AgentStartupConfiguration.class); + when(configuration.isKeycloakEnabled()).thenReturn(false); + + HttpRequestService service = new HttpRequestService(client, configuration); + + service.sendHttpRequest(null, url, method); + + verify(client).newRequest(url); + verify(configuration).isKeycloakEnabled(); + + verify(httpRequest, times(0)).content(any(StringContentProvider.class), anyString()); + verify(httpRequest).method(eq(method)); + verify(httpRequest).send(); + } + + private void verifyHttpRequest(Request httpRequest) throws InterruptedException, ExecutionException, TimeoutException { + verify(client).newRequest(url); + verify(httpRequest).content(any(StringContentProvider.class), eq("application/json")); + verify(httpRequest).method(eq(method)); + 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"); + when(configuration.getKeycloakUsername()).thenReturn("username"); + when(configuration.getKeycloakPassword()).thenReturn("password"); + } + + 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(method)); + verify(keycloakRequest).send(); + + String expected = "grant_type=password&client_id=client&username=username&password=password"; + + StringContentProvider provider = payloadCaptor.getValue(); + for (ByteBuffer buffer : provider) { + byte[] bytes = buffer.array(); + String content = new String(bytes, StandardCharsets.UTF_8); + assertEquals(expected, content); + } + + } +}
--- a/distribution/config/agent.properties Tue Jun 20 14:44:16 2017 -0400 +++ b/distribution/config/agent.properties Wed Jun 21 14:02:39 2017 -0400 @@ -1,3 +1,11 @@ # Indicates if this agent will save its data to the database on exit # or rather will purge the db SAVE_ON_EXIT=true + +# Keycloak server URL used for authentication of http requests +KEYCLOAK_ENABLED=false +KEYCLOAK_URL=http://127.0.0.1:31000 +KEYCLOAK_REALM=thermostat +KEYCLOAK_CLIENT=thermostat-web-client +KEYCLOAK_USERNAME=tms-write +KEYCLOAK_PASSWORD=tms-pass \ No newline at end of file
--- a/plugins/vm-gc/common/pom.xml Tue Jun 20 14:44:16 2017 -0400 +++ b/plugins/vm-gc/common/pom.xml Wed Jun 21 14:02:39 2017 -0400 @@ -131,6 +131,11 @@ </dependency> <dependency> <groupId>com.redhat.thermostat</groupId> + <artifactId>thermostat-agent-core</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>com.redhat.thermostat</groupId> <artifactId>thermostat-storage-core</artifactId> <version>${project.version}</version> </dependency>
--- a/plugins/vm-gc/common/src/main/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImpl.java Tue Jun 20 14:44:16 2017 -0400 +++ b/plugins/vm-gc/common/src/main/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImpl.java Wed Jun 21 14:02:39 2017 -0400 @@ -39,28 +39,24 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; -import java.util.concurrent.ExecutionException; -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.http.HttpMethod; +import org.eclipse.jetty.http.HttpStatus; + +import com.redhat.thermostat.agent.http.HttpRequestService; import com.redhat.thermostat.common.config.experimental.ConfigurationInfoSource; import com.redhat.thermostat.common.plugins.PluginConfiguration; import com.redhat.thermostat.common.utils.LoggingUtils; import com.redhat.thermostat.vm.gc.common.VmGcStatDAO; import com.redhat.thermostat.vm.gc.common.model.VmGcStat; -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.HttpClient; -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; - @Component @Service(value = VmGcStatDAO.class) public class VmGcStatDAOImpl implements VmGcStatDAO { @@ -70,24 +66,21 @@ static final String CONTENT_TYPE = "application/json"; private final JsonHelper jsonHelper; - private final HttpHelper httpHelper; - private final HttpClient httpClient; private final ConfigurationCreator configCreator; @Reference private ConfigurationInfoSource configInfoSource; private String gatewayURL; + @Reference + private HttpRequestService httpRequestService; + public VmGcStatDAOImpl() { - this(new HttpClient(), new JsonHelper(new VmGcStatTypeAdapter()), new HttpHelper(), - new ConfigurationCreator(), null); + this(new JsonHelper(new VmGcStatTypeAdapter()), new ConfigurationCreator(), null); } - VmGcStatDAOImpl(HttpClient client, JsonHelper jh, HttpHelper hh, ConfigurationCreator creator, - ConfigurationInfoSource source) { - this.httpClient = client; + VmGcStatDAOImpl(JsonHelper jh, ConfigurationCreator creator, ConfigurationInfoSource source) { this.jsonHelper = jh; - this.httpHelper = hh; this.configCreator = creator; this.configInfoSource = source; } @@ -96,34 +89,37 @@ void activate() throws Exception { PluginConfiguration config = configCreator.create(configInfoSource); this.gatewayURL = config.getGatewayURL(); - - httpHelper.startClient(httpClient); + } + + protected void bindHttpRequestService(HttpRequestService httpRequestService) { + this.httpRequestService = httpRequestService; + } + + protected void unbindHttpRequestService(HttpRequestService httpRequestService) { + this.httpRequestService = null; } @Override public void putVmGcStat(final VmGcStat stat) { try { String json = jsonHelper.toJson(Arrays.asList(stat)); - StringContentProvider provider = httpHelper.createContentProvider(json); + + if (null != httpRequestService) { + ContentResponse response = httpRequestService.sendHttpRequest(json, gatewayURL, HttpMethod.POST); - Request httpRequest = httpClient.newRequest(gatewayURL); - httpRequest.method(HttpMethod.POST); - httpRequest.content(provider, CONTENT_TYPE); - sendRequest(httpRequest); + int status = response.getStatus(); + if (status != HttpStatus.OK_200) { + throw new IOException("Gateway returned HTTP status " + String.valueOf(status) + " - " + response.getReason()); + } + + } else { + logger.log(Level.WARNING, "Failed to send VmGcStat information to web gateway. Http service unavailable."); + } } catch (Exception e) { logger.log(Level.WARNING, "Failed to send VmGcStat information to web gateway", e); } } - private void sendRequest(Request httpRequest) - throws InterruptedException, TimeoutException, ExecutionException, IOException { - ContentResponse resp = httpRequest.send(); - int status = resp.getStatus(); - if (status != HttpStatus.OK_200) { - throw new IOException("Gateway returned HTTP status " + String.valueOf(status) + " - " + resp.getReason()); - } - } - // For Testing purposes static class JsonHelper { @@ -140,18 +136,6 @@ } // For Testing purposes - static class HttpHelper { - - void startClient(HttpClient httpClient) throws Exception { - httpClient.start(); - } - - StringContentProvider createContentProvider(String content) { - return new StringContentProvider(content); - } - } - - // For Testing purposes static class ConfigurationCreator { PluginConfiguration create(ConfigurationInfoSource source) {
--- a/plugins/vm-gc/common/src/test/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImplTest.java Tue Jun 20 14:44:16 2017 -0400 +++ b/plugins/vm-gc/common/src/test/java/com/redhat/thermostat/vm/gc/common/internal/VmGcStatDAOImplTest.java Wed Jun 21 14:02:39 2017 -0400 @@ -36,28 +36,27 @@ package com.redhat.thermostat.vm.gc.common.internal; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyListOf; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; 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.util.Arrays; -import org.eclipse.jetty.client.HttpClient; 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 com.redhat.thermostat.agent.http.HttpRequestService; import com.redhat.thermostat.common.config.experimental.ConfigurationInfoSource; import com.redhat.thermostat.common.plugins.PluginConfiguration; import com.redhat.thermostat.vm.gc.common.internal.VmGcStatDAOImpl.ConfigurationCreator; -import com.redhat.thermostat.vm.gc.common.internal.VmGcStatDAOImpl.HttpHelper; import com.redhat.thermostat.vm.gc.common.internal.VmGcStatDAOImpl.JsonHelper; import com.redhat.thermostat.vm.gc.common.model.VmGcStat; @@ -68,14 +67,11 @@ private static final String GATEWAY_URL = "http://example.com/jvm-gc"; private VmGcStat stat; - private HttpClient httpClient; - private HttpHelper httpHelper; private JsonHelper jsonHelper; - private StringContentProvider contentProvider; - private Request request; - private ContentResponse response; private VmGcStatDAOImpl dao; + private HttpRequestService httpRequestService; + @Before public void setup() throws Exception { stat = new VmGcStat(); @@ -86,25 +82,22 @@ stat.setVmId("Vm-1"); stat.setCollectorName("Collector"); - httpClient = mock(HttpClient.class); - request = mock(Request.class); - when(httpClient.newRequest(anyString())).thenReturn(request); - response = mock(ContentResponse.class); - when(response.getStatus()).thenReturn(HttpStatus.OK_200); - when(request.send()).thenReturn(response); - jsonHelper = mock(JsonHelper.class); when(jsonHelper.toJson(anyListOf(VmGcStat.class))).thenReturn(JSON); - httpHelper = mock(HttpHelper.class); - contentProvider = mock(StringContentProvider.class); - when(httpHelper.createContentProvider(anyString())).thenReturn(contentProvider); - + ConfigurationInfoSource source = mock(ConfigurationInfoSource.class); PluginConfiguration config = mock(PluginConfiguration.class); when(config.getGatewayURL()).thenReturn(GATEWAY_URL); ConfigurationCreator creator = mock(ConfigurationCreator.class); when(creator.create(source)).thenReturn(config); - dao = new VmGcStatDAOImpl(httpClient, jsonHelper, httpHelper, creator, source); + + httpRequestService = mock(HttpRequestService.class); + ContentResponse contentResponse = mock(ContentResponse.class); + when(httpRequestService.sendHttpRequest(anyString(), anyString(), any(HttpMethod.class))).thenReturn(contentResponse); + when(contentResponse.getStatus()).thenReturn(HttpStatus.OK_200); + + dao = new VmGcStatDAOImpl(jsonHelper, creator, source); + dao.bindHttpRequestService(httpRequestService); } @Test @@ -112,13 +105,9 @@ dao.activate(); dao.putVmGcStat(stat); - verify(httpClient).newRequest(GATEWAY_URL); - verify(request).method(HttpMethod.POST); verify(jsonHelper).toJson(eq(Arrays.asList(stat))); - verify(httpHelper).createContentProvider(JSON); - verify(request).content(contentProvider, VmGcStatDAOImpl.CONTENT_TYPE); - verify(request).send(); - verify(response).getStatus(); + + verify(httpRequestService.sendHttpRequest(JSON, GATEWAY_URL, HttpMethod.POST), times(1)); } }