blob: 42a20766f83ddb83c2538e0ef2debe956f36e44e [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.internal.cache.versions;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.apache.geode.internal.InternalDataSerializer;
import org.apache.geode.internal.serialization.DeserializationContext;
import org.apache.geode.internal.serialization.Version;
import org.apache.geode.internal.serialization.VersionedDataInputStream;
import org.apache.geode.internal.tcp.ByteBufferInputStream;
public abstract class AbstractVersionTagTestBase {
Set<Integer> usedInts = new HashSet<>();
Random random = new Random();
@SuppressWarnings("rawtypes")
protected abstract VersionTag createVersionTag();
@SuppressWarnings("rawtypes")
protected abstract VersionSource createMemberID();
@SuppressWarnings("rawtypes")
private VersionTag vt;
@Before
public void setup() {
this.vt = createVersionTag();
}
int getRandomUnusedInt() {
int unusedInt;
do {
unusedInt = random.nextInt(60000);
} while (usedInts.contains(unusedInt));
usedInts.add(unusedInt);
return unusedInt;
}
@Test
public void testConcurrentCanonicalizationOfIDsAndSerialization() throws IOException {
VersionTag spy = spy(vt);
DataOutput dataOutput = mock(DataOutput.class);
spy.setMemberID(createMemberID());
spy.setPreviousMemberID(createMemberID());
final short[] flags = {0};
Answer myAnswer = new Answer() {
boolean firstInvocation = true;
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
if (firstInvocation) {
// save the argument - it's the "flags" int that we'll want to verify
flags[0] = (short) (((Integer) invocation.getArgument(0)).intValue() & 0xFFFF);
firstInvocation = false;
// canonicalize the member IDs. Once flags have been written the tag shouldn't examine
// previousMemberID again to see if it's the same as the memberID.
spy.setPreviousMemberID(spy.getMemberID());
}
return null;
}
};
doAnswer(myAnswer).when(dataOutput).writeShort(any(Integer.class));
spy.toData(dataOutput, true);
// verify that we only wrote the
verify(spy, times(2)).writeMember(isA(VersionSource.class), isA(DataOutput.class));
assertThat(flags[0] & VersionTag.HAS_MEMBER_ID).isEqualTo(VersionTag.HAS_MEMBER_ID);
assertThat(flags[0] & VersionTag.HAS_PREVIOUS_MEMBER_ID)
.isEqualTo(VersionTag.HAS_PREVIOUS_MEMBER_ID);
assertThat(flags[0] & VersionTag.DUPLICATE_MEMBER_IDS)
.isNotEqualTo(VersionTag.DUPLICATE_MEMBER_IDS);
}
@Test
public void testSerializationWritesNoMemberID() throws IOException {
VersionTag spy = spy(vt);
DataOutput dataOutput = mock(DataOutput.class);
spy.setMemberID(createMemberID());
spy.setPreviousMemberID(createMemberID());
final short[] flags = {0};
Answer myAnswer = new Answer() {
boolean firstInvocation = true;
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
if (firstInvocation) {
// save the argument - it's the "flags" int that we'll want to verify
flags[0] = (short) (((Integer) invocation.getArgument(0)).intValue() & 0xFFFF);
firstInvocation = false;
}
return null;
}
};
doAnswer(myAnswer).when(dataOutput).writeShort(any(Integer.class));
spy.toData(dataOutput, false);
// verify that we didn't write member IDs and the flags don't state that there are IDs in the
// tag
verify(spy, times(0)).writeMember(isA(VersionSource.class), isA(DataOutput.class));
assertThat(flags[0] & VersionTag.HAS_MEMBER_ID).isNotEqualTo(VersionTag.HAS_MEMBER_ID);
assertThat(flags[0] & VersionTag.HAS_PREVIOUS_MEMBER_ID)
.isNotEqualTo(VersionTag.HAS_PREVIOUS_MEMBER_ID);
assertThat(flags[0] & VersionTag.DUPLICATE_MEMBER_IDS)
.isNotEqualTo(VersionTag.DUPLICATE_MEMBER_IDS);
}
@Test
public void testBufferUnderflowFromOldVersionIsIgnored()
throws IOException, ClassNotFoundException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(1000);
DataOutputStream out = new DataOutputStream(outputStream);
short flags =
VersionTag.HAS_MEMBER_ID | VersionTag.HAS_PREVIOUS_MEMBER_ID | VersionTag.VERSION_TWO_BYTES;
out.writeShort(flags);
out.writeShort(0);
out.write(1);
out.writeShort(12345);
out.writeInt(12345);
InternalDataSerializer.writeUnsignedVL(1L, out);
VersionSource memberID = createMemberID();
vt.writeMember(memberID, out);
out.flush();
ByteBufferInputStream inputStream =
new ByteBufferInputStream(ByteBuffer.wrap(outputStream.toByteArray()));
DataInputStream in = new DataInputStream(inputStream);
VersionedDataInputStream versionedDataInputStream =
new VersionedDataInputStream(in, Version.GEODE_1_10_0);
DeserializationContext context =
InternalDataSerializer.createDeserializationContext(versionedDataInputStream);
// deserializing a version tag that's missing the "previous member id" should work for messages
// from older nodes but not post-1.10 because the serialization problem was fixed
vt = createVersionTag();
vt.fromData(versionedDataInputStream, context);
assertThat(vt.getMemberID()).isEqualTo(memberID);
inputStream.position(0);
final DataInputStream unversionedInputStream = new DataInputStream(inputStream);
final DeserializationContext unversionedContext =
InternalDataSerializer.createDeserializationContext(in);
vt = createVersionTag();
assertThatThrownBy(() -> vt.fromData(unversionedInputStream, unversionedContext))
.isExactlyInstanceOf(BufferUnderflowException.class);
}
@Test
public void testFromOtherMemberBit() {
assertEquals(false, vt.isFromOtherMember());
vt.setIsRemoteForTesting();
assertEquals(true, vt.isFromOtherMember());
}
@Test
public void testTimeStampUpdatedBit() {
assertEquals(false, vt.isTimeStampUpdated());
vt.setTimeStampApplied(true);
assertEquals(true, vt.isTimeStampUpdated());
vt.setTimeStampApplied(false);
assertEquals(false, vt.isTimeStampUpdated());
}
@Test
public void testGatewayTagBit() {
assertEquals(false, vt.isGatewayTag());
vt.setIsGatewayTag(true);
assertEquals(true, vt.isGatewayTag());
vt.setIsGatewayTag(false);
assertEquals(false, vt.isGatewayTag());
}
@Test
public void testRecordedBit() {
assertEquals(false, vt.isRecorded());
vt.setRecorded();
assertEquals(true, vt.isRecorded());
}
@SuppressWarnings("unchecked")
@Test
public void testPreviousMemberIDBit() {
assertEquals(false, vt.hasPreviousMemberID());
vt.setPreviousMemberID(null);
assertEquals(true, vt.hasPreviousMemberID());
}
@Test
public void testPosDupBit() {
assertEquals(false, vt.isPosDup());
vt.setPosDup(true);
assertEquals(true, vt.isPosDup());
vt.setPosDup(false);
assertEquals(false, vt.isPosDup());
}
@Test
public void testAllowedByResolverBit() {
assertEquals(false, vt.isAllowedByResolver());
vt.setAllowedByResolver(true);
assertEquals(true, vt.isAllowedByResolver());
vt.setAllowedByResolver(false);
assertEquals(false, vt.isAllowedByResolver());
}
}