blob: 2d937ff1f8954a0b22a36706747aec4d00d30fa1 [file] [log] [blame]
/*
* 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.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.web.client.RestTemplate;
import org.apache.geode.internal.cache.BucketDump;
import org.apache.geode.internal.cache.InternalCache;
import org.apache.geode.internal.cache.PartitionedRegion;
import org.apache.geode.redis.internal.RegionProvider;
import org.apache.geode.redis.internal.data.RedisHash;
import org.apache.geode.test.awaitility.GeodeAwaitility;
import org.apache.geode.test.dunit.rules.ClusterStartupRule;
public class SessionExpirationDUnitTest extends SessionDUnitTest {
protected static final int SHORT_SESSION_TIMEOUT = 5;
@BeforeClass
public static void setup() {
SessionDUnitTest.setup();
setupSpringApps(SHORT_SESSION_TIMEOUT);
}
@Test
public void sessionShouldTimeout_whenRequestedFromSameServer() throws Exception {
String sessionCookie = createNewSessionWithNote(APP1, "note1");
String sessionId = getSessionId(sessionCookie);
waitForTheSessionToExpire(sessionId);
assertThat(getSessionNotes(APP1, sessionCookie)).isNull();
}
@Test
public void sessionShouldTimeout_OnSecondaryServer() throws Exception {
String sessionCookie = createNewSessionWithNote(APP1, "note1");
String sessionId = getSessionId(sessionCookie);
waitForTheSessionToExpire(sessionId);
assertThat(getSessionNotes(APP2, sessionCookie)).isNull();
}
@Test
public void sessionShouldNotTimeoutOnFirstServer_whenAccessedOnSecondaryServer()
throws Exception {
String sessionCookie = createNewSessionWithNote(APP1, "note1");
String sessionId = getSessionId(sessionCookie);
refreshSession(sessionCookie, APP2);
assertThat(commands.ttl("spring:session:sessions:expires:" + sessionId))
.isGreaterThan(0);
assertThat(getSessionNotes(APP1, sessionCookie)).isNotNull();
}
@Test
public void sessionShouldTimeout_whenAppFailsOverToAnotherRedisServer() throws Exception {
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);
}
}
@Test
public void sessionShouldNotTimeout_whenPersisted() throws Exception {
String sessionCookie = createNewSessionWithNote(APP2, "note1");
setMaxInactiveInterval(APP2, sessionCookie, -1);
compareMaxInactiveIntervals();
}
private void waitForTheSessionToExpire(String sessionId) {
GeodeAwaitility.await().ignoreExceptions().atMost((SHORT_SESSION_TIMEOUT + 5), TimeUnit.SECONDS)
.until(() -> commands.ttl("spring:session:sessions:expires:" + sessionId) == -2);
}
private void refreshSession(String sessionCookie, int sessionApp) {
GeodeAwaitility.await()
.during(SHORT_SESSION_TIMEOUT + 2, TimeUnit.SECONDS)
.until(() -> getSessionNotes(sessionApp, sessionCookie) != null);
}
void setMaxInactiveInterval(int sessionApp, String sessionCookie, int maxInactiveInterval) {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add("Cookie", sessionCookie);
HttpEntity<Integer> request = new HttpEntity<>(maxInactiveInterval, requestHeaders);
new RestTemplate()
.postForEntity(
"http://localhost:" + ports.get(sessionApp) + "/setMaxInactiveInterval",
request,
Integer.class)
.getHeaders();
}
private void compareMaxInactiveIntervals() {
cluster.getVM(1).invoke(() -> {
InternalCache cache = ClusterStartupRule.getCache();
PartitionedRegion region =
(PartitionedRegion) cache.getRegion(RegionProvider.REDIS_DATA_REGION);
for (int j = 0; j < region.getTotalNumberOfBuckets(); j++) {
List<BucketDump> buckets = region.getAllBucketEntries(j);
if (buckets.isEmpty()) {
continue;
}
assertThat(buckets.size()).isEqualTo(2);
Map<Object, Object> bucket1 = buckets.get(0).getValues();
Map<Object, Object> bucket2 = buckets.get(1).getValues();
bucket1.keySet().forEach(key -> {
if (bucket1.get(key) instanceof RedisHash) {
RedisHash value1 = (RedisHash) bucket1.get(key);
RedisHash value2 = (RedisHash) bucket2.get(key);
assertThat(getIntFromBytes(value1)).isEqualTo(getIntFromBytes(value2));
}
});
}
});
}
private static int getIntFromBytes(RedisHash redisHash) {
if (redisHash == null) {
return 0;
}
ObjectInputStream inputStream;
byte[] bytes = redisHash.hget("maxInactiveInterval".getBytes());
ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
try {
inputStream = new ObjectInputStream(byteStream);
return (int) inputStream.readObject();
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}