This is an automated email from the ASF dual-hosted git repository. jensdeppe pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/geode.git
The following commit(s) were added to refs/heads/develop by this push: new 4fff1f9 GEODE-8094: Create HA test to ensure Spring Boot sessions expire correctly (#5079) 4fff1f9 is described below commit 4fff1f9fde1b1401c68722c015f65267bf5da971 Author: Jens Deppe <jde...@pivotal.io> AuthorDate: Fri May 15 08:29:25 2020 -0700 GEODE-8094: Create HA test to ensure Spring Boot sessions expire correctly (#5079) Co-authored-by: Murtuza Boxwala <mboxw...@vmware.com> Co-authored-by: Raymond Ingles <ring...@vmware.com> Co-authored-by: Jens Deppe <jde...@vmware.com> --- .../src/test/resources/expected-pom.xml | 6 + .../gradle/plugins/DependencyConstraints.groovy | 1 + geode-redis/build.gradle | 7 +- .../redis/session/RedisSessionDistDUnitTest.java | 118 ++++++++++++++ .../SessionDUnitTest.java} | 169 ++++++--------------- .../redis/session/SessionExpirationDUnitTest.java | 103 +++++++++++++ .../RedisSpringTestApplication.java | 2 +- .../SessionController.java | 15 +- .../config/DUnitSocketAddressResolver.java | 2 +- .../config/SessionListener.java | 13 +- .../config/WebMvcConfig.java | 2 +- .../org/apache/geode/redis/internal/Command.java | 17 ++- .../redis/internal/ExecutionHandlerContext.java | 32 ++++ 13 files changed, 354 insertions(+), 133 deletions(-) diff --git a/boms/geode-all-bom/src/test/resources/expected-pom.xml b/boms/geode-all-bom/src/test/resources/expected-pom.xml index 000c051..9eba879 100644 --- a/boms/geode-all-bom/src/test/resources/expected-pom.xml +++ b/boms/geode-all-bom/src/test/resources/expected-pom.xml @@ -861,6 +861,12 @@ </dependency> <dependency> <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-jetty</artifactId> + <version>2.2.1.RELEASE</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.2.1.RELEASE</version> <scope>compile</scope> diff --git a/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy b/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy index eae28bd..15f3c09 100644 --- a/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy +++ b/buildSrc/src/main/groovy/org/apache/geode/gradle/plugins/DependencyConstraints.groovy @@ -263,6 +263,7 @@ class DependencyConstraints implements Plugin<Project> { dependencySet(group: 'org.springframework.boot', version: '2.2.1.RELEASE') { entry('spring-boot-starter') + entry('spring-boot-starter-jetty') entry('spring-boot-starter-web') entry('spring-boot-starter-data-redis') } diff --git a/geode-redis/build.gradle b/geode-redis/build.gradle index 3ef3ba7..97d5d54 100644 --- a/geode-redis/build.gradle +++ b/geode-redis/build.gradle @@ -58,11 +58,16 @@ dependencies { acceptanceTestImplementation('org.testcontainers:testcontainers') acceptanceTestRuntime(project(':geode-log4j')) + distributedTestCompile('org.apache.logging.log4j:log4j-core') distributedTestImplementation(project(':geode-dunit')) distributedTestImplementation(sourceSets.commonTest.output) distributedTestImplementation('redis.clients:jedis') - distributedTestImplementation('org.springframework.boot:spring-boot-starter-web') + distributedTestCompile('javax.servlet:javax.servlet-api') + distributedTestImplementation('org.springframework.boot:spring-boot-starter-jetty') + distributedTestImplementation('org.springframework.boot:spring-boot-starter-web') { + exclude module: 'spring-boot-starter-tomcat' + } distributedTestImplementation('org.springframework.boot:spring-boot-starter') distributedTestImplementation('org.springframework.boot:spring-boot-starter-data-redis') distributedTestImplementation('org.springframework.session:spring-session-data-redis') diff --git a/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/RedisSessionDistDUnitTest.java b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/RedisSessionDistDUnitTest.java new file mode 100644 index 0000000..eedc990 --- /dev/null +++ b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/RedisSessionDistDUnitTest.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package org.apache.geode.redis.session; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; + +import org.apache.geode.test.junit.categories.RedisTest; + +@Category({RedisTest.class}) +public class RedisSessionDistDUnitTest extends SessionDUnitTest { + + @BeforeClass + public static void setup() { + SessionDUnitTest.setup(); + startSpringApp(APP1, SERVER1, SERVER2, DEFAULT_SESSION_TIMEOUT); + startSpringApp(APP2, SERVER2, SERVER1, DEFAULT_SESSION_TIMEOUT); + } + + @Test + public void should_beAbleToCreateASession_storedInRedis() { + String sessionCookie = createNewSessionWithNote(APP1, "note1"); + String sessionId = getSessionId(sessionCookie); + + Map<String, String> sessionInfo = + jedisConnetedToServer1.hgetAll("spring:session:sessions:" + sessionId); + + assertThat(sessionInfo.get("sessionAttr:NOTES")).contains("note1"); + } + + @Test + public void should_storeSession() { + String sessionCookie = createNewSessionWithNote(APP1, "note1"); + + String[] sessionNotes = getSessionNotes(APP2, sessionCookie); + + assertThat(sessionNotes).containsExactly("note1"); + } + + @Test + public void should_propagateSession_toOtherServers() { + String sessionCookie = createNewSessionWithNote(APP1, "noteFromClient1"); + + String[] sessionNotes = getSessionNotes(APP2, sessionCookie); + + assertThat(sessionNotes).containsExactly("noteFromClient1"); + } + + @Test + public void should_getSessionFromServer1_whenServer2GoesDown() { + String sessionCookie = createNewSessionWithNote(APP2, "noteFromClient2"); + cluster.crashVM(SERVER2); + try { + String[] sessionNotes = getSessionNotes(APP1, sessionCookie); + + assertThat(sessionNotes).containsExactly("noteFromClient2"); + } finally { + startRedisServer(SERVER2); + } + } + + @Test + public void should_getSessionFromServer_whenServerGoesDownAndIsRestarted() { + String sessionCookie = createNewSessionWithNote(APP2, "noteFromClient2"); + cluster.crashVM(SERVER2); + addNoteToSession(APP1, sessionCookie, "noteFromClient1"); + startRedisServer(SERVER2); + + String[] sessionNotes = getSessionNotes(APP2, sessionCookie); + + assertThat(sessionNotes).containsExactly("noteFromClient2", "noteFromClient1"); + } + + @Test + public void should_getSession_whenServer2GoesDown_andAppFailsOverToServer1() { + String sessionCookie = createNewSessionWithNote(APP2, "noteFromClient2"); + cluster.crashVM(SERVER2); + + try { + String[] sessionNotes = getSessionNotes(APP2, sessionCookie); + + assertThat(sessionNotes).containsExactly("noteFromClient2"); + } finally { + startRedisServer(SERVER2); + } + } + + @Test + public void should_getSessionCreatedByApp2_whenApp2GoesDown_andClientConnectsToApp1() { + String sessionCookie = createNewSessionWithNote(APP2, "noteFromClient2"); + stopSpringApp(APP2); + + try { + String[] sessionNotes = getSessionNotes(APP1, sessionCookie); + + assertThat(sessionNotes).containsExactly("noteFromClient2"); + } finally { + startSpringApp(APP2, SERVER1, SERVER2, DEFAULT_SESSION_TIMEOUT); + } + } +} diff --git a/geode-redis/src/distributedTest/java/org/apache/geode/redis/RedisSessionDistDUnitTest.java b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/SessionDUnitTest.java similarity index 53% rename from geode-redis/src/distributedTest/java/org/apache/geode/redis/RedisSessionDistDUnitTest.java rename to geode-redis/src/distributedTest/java/org/apache/geode/redis/session/SessionDUnitTest.java index 3982d56..80114d4 100644 --- a/geode-redis/src/distributedTest/java/org/apache/geode/redis/RedisSessionDistDUnitTest.java +++ b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/SessionDUnitTest.java @@ -12,11 +12,9 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ -package org.apache.geode.redis; -import static org.assertj.core.api.Assertions.assertThat; +package org.apache.geode.redis.session; -import java.io.Serializable; import java.net.HttpCookie; import java.util.Base64; import java.util.HashMap; @@ -24,13 +22,15 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.Configurator; import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; -import org.junit.Test; -import org.junit.experimental.categories.Category; import org.springframework.boot.SpringApplication; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.http.HttpEntity; @@ -40,33 +40,37 @@ import org.springframework.web.client.RestTemplate; import redis.clients.jedis.Jedis; import org.apache.geode.internal.AvailablePortHelper; -import org.apache.geode.redis.springRedisTestApplication.RedisSpringTestApplication; +import org.apache.geode.logging.internal.log4j.api.FastLogger; +import org.apache.geode.redis.session.springRedisTestApplication.RedisSpringTestApplication; import org.apache.geode.test.awaitility.GeodeAwaitility; import org.apache.geode.test.dunit.VM; import org.apache.geode.test.dunit.rules.ClusterStartupRule; import org.apache.geode.test.dunit.rules.DistributedRestoreSystemProperties; -import org.apache.geode.test.junit.categories.RedisTest; -@Category({RedisTest.class}) -public class RedisSessionDistDUnitTest implements Serializable { +public abstract class SessionDUnitTest { @ClassRule - public static ClusterStartupRule cluster = new ClusterStartupRule(5); + public static ClusterStartupRule cluster = new ClusterStartupRule(); @Rule public DistributedRestoreSystemProperties restoreSystemProperties = new DistributedRestoreSystemProperties(); - public static ConfigurableApplicationContext springApplicationContext; - private static final int LOCATOR = 0; - private static final int SERVER1 = 1; - private static final int SERVER2 = 2; - private static final int APP1 = 3; - private static final int APP2 = 4; + // 30 minutes + protected static final int DEFAULT_SESSION_TIMEOUT = 60 * 30; + + protected static final int LOCATOR = 0; + protected static final int SERVER1 = 1; + protected static final int SERVER2 = 2; + protected static final int APP1 = 3; + protected static final int APP2 = 4; + + private static final Map<Integer, Integer> ports = new HashMap<>(); + public static ConfigurableApplicationContext springApplicationContext; - private static Jedis jedis; + protected static Jedis jedisConnetedToServer1; private static final int JEDIS_TIMEOUT = Math.toIntExact(GeodeAwaitility.getTimeout().toMillis()); @BeforeClass @@ -80,137 +84,62 @@ public class RedisSessionDistDUnitTest implements Serializable { cluster.startLocatorVM(LOCATOR); startRedisServer(SERVER1); startRedisServer(SERVER2); - startSpringApp(APP1, SERVER1, SERVER2); - startSpringApp(APP2, SERVER2, SERVER1); - jedis = new Jedis("localhost", ports.get(SERVER1), JEDIS_TIMEOUT); - } - - @After - public void cleanupAfterTest() { - jedis.flushAll(); + jedisConnetedToServer1 = new Jedis("localhost", ports.get(SERVER1), JEDIS_TIMEOUT); } @AfterClass public static void cleanupAfterClass() { - jedis.disconnect(); - } - - @Test - public void should_beAbleToCreateASession_storedInRedis() { - String sessionCookie = createNewSessionWithNote(APP1, "note1"); - String sessionId = getSessionId(sessionCookie); - - Map<String, String> sessionInfo = - jedis.hgetAll("spring:session:sessions:" + sessionId); - - assertThat(sessionInfo.get("sessionAttr:NOTES")).contains("note1"); - } - - @Test - public void should_storeSession() { - String sessionCookie = createNewSessionWithNote(APP1, "note1"); - - String[] sessionNotes = getSessionNotes(APP2, sessionCookie); - - assertThat(sessionNotes).containsExactly("note1"); - } - - @Test - public void should_propagateSession_toOtherServers() { - String sessionCookie = createNewSessionWithNote(APP1, "noteFromClient1"); - - String[] sessionNotes = getSessionNotes(APP2, sessionCookie); - - assertThat(sessionNotes).containsExactly("noteFromClient1"); - } - - @Test - public void should_getSessionFromServer1_whenServer2GoesDown() { - String sessionCookie = createNewSessionWithNote(APP2, "noteFromClient2"); - cluster.crashVM(SERVER2); - try { - String[] sessionNotes = getSessionNotes(APP1, sessionCookie); - - assertThat(sessionNotes).containsExactly("noteFromClient2"); - } finally { - startRedisServer(SERVER2); - } - } - - @Test - public void should_getSessionFromServer_whenServerGoesDownAndIsRestarted() { - String sessionCookie = createNewSessionWithNote(APP2, "noteFromClient2"); - cluster.crashVM(SERVER2); - addNoteToSession(APP1, sessionCookie); - startRedisServer(SERVER2); - - String[] sessionNotes = getSessionNotes(APP2, sessionCookie); - - assertThat(sessionNotes).containsExactly("noteFromClient2", "noteFromClient1"); - } - - @Test - public void should_getSession_whenServer2GoesDown_andAppFailsOverToServer1() { - String sessionCookie = createNewSessionWithNote(APP2, "noteFromClient2"); - cluster.crashVM(SERVER2); - - try { - String[] sessionNotes = getSessionNotes(APP2, sessionCookie); - - assertThat(sessionNotes).containsExactly("noteFromClient2"); - } finally { - startRedisServer(SERVER2); - } - } - - @Test - public void should_getSessionCreatedByApp2_whenApp2GoesDown_andClientConnectsToApp1() { - String sessionCookie = createNewSessionWithNote(APP2, "noteFromClient2"); + jedisConnetedToServer1.disconnect(); + stopSpringApp(APP1); stopSpringApp(APP2); + } - try { - String[] sessionNotes = getSessionNotes(APP1, sessionCookie); - - assertThat(sessionNotes).containsExactly("noteFromClient2"); - } finally { - startSpringApp(APP2, SERVER1, SERVER2); - } - + @After + public void cleanupAfterTest() { + jedisConnetedToServer1.flushAll(); } - private static void startRedisServer(int server1) { - cluster.startServerVM(server1, redisProperties(server1), + protected static void startRedisServer(int server) { + cluster.startServerVM(server, redisProperties(server), cluster.getMember(LOCATOR).getPort()); + + cluster.getVM(server).invoke("Set logging level to DEBUG", () -> { + Logger logger = LogManager.getLogger("org.apache.geode.redis.internal"); + Configurator.setAllLevels(logger.getName(), Level.getLevel("DEBUG")); + FastLogger.setDelegating(true); + }); } - private static Properties redisProperties(int server2) { + private static Properties redisProperties(int server) { Properties redisPropsForServer = new Properties(); redisPropsForServer.setProperty("redis-bind-address", "localHost"); - redisPropsForServer.setProperty("redis-port", "" + ports.get(server2)); - redisPropsForServer.setProperty("log-level", "warn"); + redisPropsForServer.setProperty("redis-port", "" + ports.get(server)); + redisPropsForServer.setProperty("log-level", "info"); return redisPropsForServer; } - private static void startSpringApp(int sessionApp, int primaryServer, int secondaryServer) { + static void startSpringApp(int sessionApp, int primaryServer, int secondaryServer, + long sessionTimeout) { int primaryRedisPort = ports.get(primaryServer); int failoverRedisPort = ports.get(secondaryServer); int httpPort = ports.get(sessionApp); VM host = cluster.getVM(sessionApp); - host.invoke("start a spring app", () -> { + host.invoke("Start a Spring app", () -> { System.setProperty("server.port", "" + httpPort); System.setProperty("spring.redis.port", "" + primaryRedisPort); + System.setProperty("server.servlet.session.timeout", "" + sessionTimeout + "s"); springApplicationContext = SpringApplication.run( RedisSpringTestApplication.class, "" + primaryRedisPort, "" + failoverRedisPort); }); } - private static void stopSpringApp(int sessionApp) { + static void stopSpringApp(int sessionApp) { cluster.getVM(sessionApp).invoke(() -> springApplicationContext.close()); } - private String createNewSessionWithNote(int sessionApp, String note) { + protected String createNewSessionWithNote(int sessionApp, String note) { HttpEntity<String> request = new HttpEntity<>(note); HttpHeaders resultHeaders = new RestTemplate() .postForEntity( @@ -222,7 +151,7 @@ public class RedisSessionDistDUnitTest implements Serializable { return resultHeaders.getFirst("Set-Cookie"); } - private String[] getSessionNotes(int sessionApp, String sessionCookie) { + protected String[] getSessionNotes(int sessionApp, String sessionCookie) { HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.add("Cookie", sessionCookie); HttpEntity<String> request2 = new HttpEntity<>("", requestHeaders); @@ -236,10 +165,10 @@ public class RedisSessionDistDUnitTest implements Serializable { .getBody(); } - private void addNoteToSession(int sessionApp, String sessionCookie) { + void addNoteToSession(int sessionApp, String sessionCookie, String note) { HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.add("Cookie", sessionCookie); - HttpEntity<String> request = new HttpEntity<>("noteFromClient1", requestHeaders); + HttpEntity<String> request = new HttpEntity<>(note, requestHeaders); new RestTemplate() .postForEntity( "http://localhost:" + ports.get(sessionApp) + "/addSessionNote", @@ -248,7 +177,7 @@ public class RedisSessionDistDUnitTest implements Serializable { .getHeaders(); } - private String getSessionId(String sessionCookie) { + protected String getSessionId(String sessionCookie) { List<HttpCookie> cookies = HttpCookie.parse(sessionCookie); byte[] decodedCookie = Base64.getDecoder().decode(cookies.get(0).getValue()); return new String(decodedCookie); diff --git a/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/SessionExpirationDUnitTest.java b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/SessionExpirationDUnitTest.java new file mode 100644 index 0000000..0d6c000 --- /dev/null +++ b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/SessionExpirationDUnitTest.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package org.apache.geode.redis.session; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.TimeUnit; + +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; + +import org.apache.geode.test.awaitility.GeodeAwaitility; + +public class SessionExpirationDUnitTest extends SessionDUnitTest { + + private static final int SHORT_SESSION_TIMEOUT = 5; + + @BeforeClass + public static void setup() { + SessionDUnitTest.setup(); + startSpringApp(APP1, SERVER1, SERVER2, SHORT_SESSION_TIMEOUT); + startSpringApp(APP2, SERVER2, SERVER1, SHORT_SESSION_TIMEOUT); + } + + @Test + public void sessionShouldTimeout_whenRequestedFromSameServer() { + String sessionCookie = createNewSessionWithNote(APP1, "note1"); + String sessionId = getSessionId(sessionCookie); + + waitForTheSessionToExpire(sessionId); + + assertThat(getSessionNotes(APP1, sessionCookie)).isNull(); + } + + @Test + public void sessionShouldTimeout_OnSecondaryServer() { + String sessionCookie = createNewSessionWithNote(APP1, "note1"); + String sessionId = getSessionId(sessionCookie); + + waitForTheSessionToExpire(sessionId); + + assertThat(getSessionNotes(APP2, sessionCookie)).isNull(); + } + + @Test + @Ignore("GEODE-8058: this test needs to pass to have feature parity with native redis") + public void sessionShouldNotTimeoutOnFirstServer_whenAccessedOnSecondaryServer() { + String sessionCookie = createNewSessionWithNote(APP1, "note1"); + String sessionId = getSessionId(sessionCookie); + + refreshSession(sessionCookie, APP2); + + assertThat(jedisConnetedToServer1.ttl("spring:session:sessions:expires:" + sessionId)) + .isGreaterThan(0); + + assertThat(getSessionNotes(APP1, sessionCookie)).isNotNull(); + } + + @Test + @Ignore("GEODE-8058: this test needs to pass to have feature parity with native redis") + public void sessionShouldTimeout_whenAppFailsOverToAnotherRedisServer() { + String sessionCookie = createNewSessionWithNote(APP2, "note1"); + String sessionId = getSessionId(sessionCookie); + + cluster.crashVM(SERVER2); + + try { + waitForTheSessionToExpire(sessionId); + + assertThat(getSessionNotes(APP1, sessionCookie)).isNull(); + assertThat(getSessionNotes(APP2, sessionCookie)).isNull(); + + } finally { + startRedisServer(SERVER2); + } + } + + private void waitForTheSessionToExpire(String sessionId) { + GeodeAwaitility.await().atMost((SHORT_SESSION_TIMEOUT + 5), TimeUnit.SECONDS) + .until( + () -> jedisConnetedToServer1.ttl("spring:session:sessions:expires:" + sessionId) < 0); + } + + private void refreshSession(String sessionCookie, int sessionApp) { + GeodeAwaitility.await() + .during(SHORT_SESSION_TIMEOUT + 2, TimeUnit.SECONDS) + .until(() -> getSessionNotes(sessionApp, sessionCookie) != null); + } +} diff --git a/geode-redis/src/distributedTest/java/org/apache/geode/redis/springRedisTestApplication/RedisSpringTestApplication.java b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/springRedisTestApplication/RedisSpringTestApplication.java similarity index 94% rename from geode-redis/src/distributedTest/java/org/apache/geode/redis/springRedisTestApplication/RedisSpringTestApplication.java rename to geode-redis/src/distributedTest/java/org/apache/geode/redis/session/springRedisTestApplication/RedisSpringTestApplication.java index 088bee5..7c72528 100755 --- a/geode-redis/src/distributedTest/java/org/apache/geode/redis/springRedisTestApplication/RedisSpringTestApplication.java +++ b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/springRedisTestApplication/RedisSpringTestApplication.java @@ -12,7 +12,7 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ -package org.apache.geode.redis.springRedisTestApplication; +package org.apache.geode.redis.session.springRedisTestApplication; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/geode-redis/src/distributedTest/java/org/apache/geode/redis/springRedisTestApplication/SessionController.java b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/springRedisTestApplication/SessionController.java similarity index 85% rename from geode-redis/src/distributedTest/java/org/apache/geode/redis/springRedisTestApplication/SessionController.java rename to geode-redis/src/distributedTest/java/org/apache/geode/redis/session/springRedisTestApplication/SessionController.java index 8d80fab..d354618 100644 --- a/geode-redis/src/distributedTest/java/org/apache/geode/redis/springRedisTestApplication/SessionController.java +++ b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/springRedisTestApplication/SessionController.java @@ -12,12 +12,13 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ -package org.apache.geode.redis.springRedisTestApplication; +package org.apache.geode.redis.session.springRedisTestApplication; import java.util.ArrayList; import java.util.List; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -29,9 +30,13 @@ public class SessionController { @SuppressWarnings("unchecked") @GetMapping("/getSessionNotes") public List<String> getSessionNotes(HttpServletRequest request) { - List<String> notes = - (List<String>) request.getSession().getAttribute("NOTES"); - return notes; + HttpSession session = request.getSession(false); + + if (session == null) { + return null; + } else { + return (List<String>) session.getAttribute("NOTES"); + } } @SuppressWarnings("unchecked") @@ -55,6 +60,6 @@ public class SessionController { @PostMapping("/invalidateSession") public void invalidateSession(HttpServletRequest request) { - request.getSession(false).invalidate(); + request.getSession().invalidate(); } } diff --git a/geode-redis/src/distributedTest/java/org/apache/geode/redis/springRedisTestApplication/config/DUnitSocketAddressResolver.java b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/springRedisTestApplication/config/DUnitSocketAddressResolver.java similarity index 95% rename from geode-redis/src/distributedTest/java/org/apache/geode/redis/springRedisTestApplication/config/DUnitSocketAddressResolver.java rename to geode-redis/src/distributedTest/java/org/apache/geode/redis/session/springRedisTestApplication/config/DUnitSocketAddressResolver.java index 5f141d4..baacdc1 100644 --- a/geode-redis/src/distributedTest/java/org/apache/geode/redis/springRedisTestApplication/config/DUnitSocketAddressResolver.java +++ b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/springRedisTestApplication/config/DUnitSocketAddressResolver.java @@ -13,7 +13,7 @@ * the License. */ -package org.apache.geode.redis.springRedisTestApplication.config; +package org.apache.geode.redis.session.springRedisTestApplication.config; import java.net.InetAddress; import java.net.InetSocketAddress; diff --git a/geode-redis/src/distributedTest/java/org/apache/geode/redis/springRedisTestApplication/config/SessionListener.java b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/springRedisTestApplication/config/SessionListener.java similarity index 71% rename from geode-redis/src/distributedTest/java/org/apache/geode/redis/springRedisTestApplication/config/SessionListener.java rename to geode-redis/src/distributedTest/java/org/apache/geode/redis/session/springRedisTestApplication/config/SessionListener.java index 209d43e..519b16e 100644 --- a/geode-redis/src/distributedTest/java/org/apache/geode/redis/springRedisTestApplication/config/SessionListener.java +++ b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/springRedisTestApplication/config/SessionListener.java @@ -12,7 +12,10 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ -package org.apache.geode.redis.springRedisTestApplication.config; +package org.apache.geode.redis.session.springRedisTestApplication.config; + +import java.util.HashSet; +import java.util.concurrent.atomic.AtomicLong; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; @@ -21,15 +24,21 @@ import org.springframework.context.annotation.Configuration; @Configuration public class SessionListener implements HttpSessionListener { + public static AtomicLong sessionCount = new AtomicLong(0); + public static HashSet<String> sessionIds = new HashSet<>(); @Override public void sessionCreated(HttpSessionEvent event) { + sessionCount.getAndIncrement(); + sessionIds.add(event.getSession().getId()); System.out.println("session created: " + event.getSession().getId()); event.getSession().setMaxInactiveInterval(15); } @Override public void sessionDestroyed(HttpSessionEvent event) { - System.out.println("session destroyed " + event.getSession().getId()); + sessionIds.remove(event.getSession().getId()); + sessionCount.getAndDecrement(); + System.out.println("session destroyed: " + event.getSession().getId()); } } diff --git a/geode-redis/src/distributedTest/java/org/apache/geode/redis/springRedisTestApplication/config/WebMvcConfig.java b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/springRedisTestApplication/config/WebMvcConfig.java similarity index 95% rename from geode-redis/src/distributedTest/java/org/apache/geode/redis/springRedisTestApplication/config/WebMvcConfig.java rename to geode-redis/src/distributedTest/java/org/apache/geode/redis/session/springRedisTestApplication/config/WebMvcConfig.java index 5ea5569..909c4e7 100755 --- a/geode-redis/src/distributedTest/java/org/apache/geode/redis/springRedisTestApplication/config/WebMvcConfig.java +++ b/geode-redis/src/distributedTest/java/org/apache/geode/redis/session/springRedisTestApplication/config/WebMvcConfig.java @@ -13,7 +13,7 @@ * the License. */ -package org.apache.geode.redis.springRedisTestApplication.config; +package org.apache.geode.redis.session.springRedisTestApplication.config; import io.lettuce.core.resource.ClientResources; import org.springframework.boot.ApplicationArguments; diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/Command.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/Command.java index 5b1749c..5dd8fa4 100755 --- a/geode-redis/src/main/java/org/apache/geode/redis/internal/Command.java +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/Command.java @@ -151,13 +151,26 @@ public class Command { @Override public String toString() { StringBuilder b = new StringBuilder(); - for (byte[] bs : this.commandElems) { - b.append(Coder.bytesToString(bs)); + for (byte[] rawCommand : this.commandElems) { + b.append(getHexEncodedString(rawCommand)); b.append(' '); } return b.toString(); } + private String getHexEncodedString(byte[] data) { + StringBuilder builder = new StringBuilder(); + for (byte aByte : data) { + if (aByte > 31 && aByte < 127) { + builder.append((char) aByte); + } else { + builder.append(String.format("\\x%02x", aByte)); + } + } + + return builder.toString(); + } + public void execute(ExecutionHandlerContext executionHandlerContext) { RedisCommandType type = getCommandType(); type.executeCommand(this, executionHandlerContext); diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/ExecutionHandlerContext.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/ExecutionHandlerContext.java index 1ada736..93e8978 100644 --- a/geode-redis/src/main/java/org/apache/geode/redis/internal/ExecutionHandlerContext.java +++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/ExecutionHandlerContext.java @@ -144,6 +144,7 @@ public class ExecutionHandlerContext extends ChannelInboundHandlerAdapter { if (logger.isDebugEnabled()) { logger.debug("Executing Redis command: {}", command); } + executeCommand(ctx, command); } catch (Exception e) { logger.warn("Execution of Redis command {} failed: {}", command, e); @@ -215,6 +216,8 @@ public class ExecutionHandlerContext extends ChannelInboundHandlerAdapter { executeWithoutTransaction(command); } + logResponse(command); + if (hasTransaction() && command.isOfType(RedisCommandType.MULTI)) { writeToChannel( Coder.getSimpleStringResponse(this.byteBufAllocator, RedisConstants.COMMAND_QUEUED)); @@ -240,6 +243,35 @@ public class ExecutionHandlerContext extends ChannelInboundHandlerAdapter { } } + private void logResponse(Command command) { + if (logger.isDebugEnabled() && command.getResponse() != null) { + ByteBuf response = null; + try { + response = command.getResponse() + .copy(0, Math.min(command.getResponse().readableBytes(), 100)); + logger.debug("Redis command returned: {}", getPrintableByteBuf(response)); + } finally { + if (response != null) { + response.release(); + } + } + } + } + + private String getPrintableByteBuf(ByteBuf buf) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < buf.readableBytes(); i++) { + byte aByte = buf.getByte(i); + if (aByte > 31 && aByte < 127) { + builder.append((char) aByte); + } else { + builder.append(String.format("\\x%02x", aByte)); + } + } + + return builder.toString(); + } + /** * Private helper method to execute a command without a transaction, done for special exception