package com.netflix.discovery.converters; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import com.netflix.appinfo.DataCenterInfo; import com.netflix.appinfo.InstanceInfo; import com.netflix.appinfo.MyDataCenterInfo; import com.netflix.discovery.converters.wrappers.CodecWrapper; import com.netflix.discovery.converters.wrappers.CodecWrappers; import com.netflix.discovery.converters.wrappers.DecoderWrapper; import com.netflix.discovery.converters.wrappers.EncoderWrapper; import com.netflix.discovery.shared.Application; import com.netflix.discovery.shared.Applications; import com.netflix.discovery.util.EurekaEntityComparators; import com.netflix.discovery.util.InstanceInfoGenerator; import com.netflix.eureka.cluster.protocol.ReplicationInstance; import com.netflix.eureka.cluster.protocol.ReplicationInstanceResponse; import com.netflix.eureka.cluster.protocol.ReplicationList; import com.netflix.eureka.cluster.protocol.ReplicationListResponse; import com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl.Action; import org.junit.Test; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; /** * @author Tomasz Bak */ public class EurekaCodecCompatibilityTest { private static final List<CodecWrapper> availableJsonWrappers = new ArrayList<>(); private static final List<CodecWrapper> availableXmlWrappers = new ArrayList<>(); static { availableJsonWrappers.add(new CodecWrappers.XStreamJson()); availableJsonWrappers.add(new CodecWrappers.LegacyJacksonJson()); availableJsonWrappers.add(new CodecWrappers.JacksonJson()); availableXmlWrappers.add(new CodecWrappers.JacksonXml()); availableXmlWrappers.add(new CodecWrappers.XStreamXml()); } private final InstanceInfoGenerator infoGenerator = InstanceInfoGenerator.newBuilder(4, 2).withMetaData(true).build(); private final Iterator<InstanceInfo> infoIterator = infoGenerator.serviceIterator(); interface Action2 { void call(EncoderWrapper encodingCodec, DecoderWrapper decodingCodec) throws IOException; } /** * @deprecated see to do note in {@link com.netflix.appinfo.LeaseInfo} and delete once legacy is removed */ @Deprecated @Test public void testInstanceInfoEncodeDecodeLegacyJacksonToJackson() throws Exception { final InstanceInfo instanceInfo = infoIterator.next(); Action2 codingAction = new Action2() { @Override public void call(EncoderWrapper encodingCodec, DecoderWrapper decodingCodec) throws IOException { String encodedString = encodingCodec.encode(instanceInfo); // convert the field from the json string to what the legacy json would encode as encodedString = encodedString.replaceFirst("lastRenewalTimestamp", "renewalTimestamp"); InstanceInfo decodedValue = decodingCodec.decode(encodedString, InstanceInfo.class); assertThat(EurekaEntityComparators.equal(instanceInfo, decodedValue, new EurekaEntityComparators.RawIdEqualFunc()), is(true)); assertThat(EurekaEntityComparators.equal(instanceInfo, decodedValue), is(true)); } }; verifyForPair( codingAction, InstanceInfo.class, new CodecWrappers.LegacyJacksonJson(), new CodecWrappers.JacksonJson() ); } @Test public void testInstanceInfoEncodeDecodeJsonWithEmptyMetadataMap() throws Exception { final InstanceInfo base = infoIterator.next(); final InstanceInfo instanceInfo = new InstanceInfo.Builder(base) .setMetadata(Collections.EMPTY_MAP) .build(); Action2 codingAction = new Action2() { @Override public void call(EncoderWrapper encodingCodec, DecoderWrapper decodingCodec) throws IOException { String encodedString = encodingCodec.encode(instanceInfo); InstanceInfo decodedValue = decodingCodec.decode(encodedString, InstanceInfo.class); assertThat(EurekaEntityComparators.equal(instanceInfo, decodedValue), is(true)); } }; verifyAllPairs(codingAction, Application.class, availableJsonWrappers); verifyAllPairs(codingAction, Application.class, availableXmlWrappers); } /** * During deserialization process in compact mode not all fields might be filtered out. If JVM memory * is an issue, compact version of the encoder should be used on the server side. */ @Test public void testInstanceInfoFullEncodeMiniDecodeJackson() throws Exception { final InstanceInfo instanceInfo = infoIterator.next(); Action2 codingAction = new Action2() { @Override public void call(EncoderWrapper encodingCodec, DecoderWrapper decodingCodec) throws IOException { String encodedString = encodingCodec.encode(instanceInfo); InstanceInfo decodedValue = decodingCodec.decode(encodedString, InstanceInfo.class); assertThat(EurekaEntityComparators.equalMini(instanceInfo, decodedValue), is(true)); } }; verifyForPair( codingAction, InstanceInfo.class, new CodecWrappers.JacksonJson(), new CodecWrappers.JacksonJsonMini() ); } @Test public void testInstanceInfoFullEncodeMiniDecodeJacksonWithMyOwnDataCenterInfo() throws Exception { final InstanceInfo base = infoIterator.next(); final InstanceInfo instanceInfo = new InstanceInfo.Builder(base) .setDataCenterInfo(new MyDataCenterInfo(DataCenterInfo.Name.MyOwn)) .build(); Action2 codingAction = new Action2() { @Override public void call(EncoderWrapper encodingCodec, DecoderWrapper decodingCodec) throws IOException { String encodedString = encodingCodec.encode(instanceInfo); InstanceInfo decodedValue = decodingCodec.decode(encodedString, InstanceInfo.class); assertThat(EurekaEntityComparators.equalMini(instanceInfo, decodedValue), is(true)); } }; verifyForPair( codingAction, InstanceInfo.class, new CodecWrappers.JacksonJson(), new CodecWrappers.JacksonJsonMini() ); } @Test public void testInstanceInfoMiniEncodeMiniDecodeJackson() throws Exception { final InstanceInfo instanceInfo = infoIterator.next(); Action2 codingAction = new Action2() { @Override public void call(EncoderWrapper encodingCodec, DecoderWrapper decodingCodec) throws IOException { String encodedString = encodingCodec.encode(instanceInfo); InstanceInfo decodedValue = decodingCodec.decode(encodedString, InstanceInfo.class); assertThat(EurekaEntityComparators.equalMini(instanceInfo, decodedValue), is(true)); } }; verifyForPair( codingAction, InstanceInfo.class, new CodecWrappers.JacksonJsonMini(), new CodecWrappers.JacksonJsonMini() ); } @Test public void testInstanceInfoEncodeDecode() throws Exception { final InstanceInfo instanceInfo = infoIterator.next(); Action2 codingAction = new Action2() { @Override public void call(EncoderWrapper encodingCodec, DecoderWrapper decodingCodec) throws IOException { String encodedString = encodingCodec.encode(instanceInfo); InstanceInfo decodedValue = decodingCodec.decode(encodedString, InstanceInfo.class); assertThat(EurekaEntityComparators.equal(instanceInfo, decodedValue), is(true)); assertThat(EurekaEntityComparators.equal(instanceInfo, decodedValue, new EurekaEntityComparators.RawIdEqualFunc()), is(true)); } }; verifyAllPairs(codingAction, InstanceInfo.class, availableJsonWrappers); verifyAllPairs(codingAction, InstanceInfo.class, availableXmlWrappers); } @Test public void testApplicationEncodeDecode() throws Exception { final Application application = new Application("testApp"); application.addInstance(infoIterator.next()); application.addInstance(infoIterator.next()); Action2 codingAction = new Action2() { @Override public void call(EncoderWrapper encodingCodec, DecoderWrapper decodingCodec) throws IOException { String encodedString = encodingCodec.encode(application); Application decodedValue = decodingCodec.decode(encodedString, Application.class); assertThat(EurekaEntityComparators.equal(application, decodedValue), is(true)); } }; verifyAllPairs(codingAction, Application.class, availableJsonWrappers); verifyAllPairs(codingAction, Application.class, availableXmlWrappers); } @Test public void testApplicationsEncodeDecode() throws Exception { final Applications applications = infoGenerator.takeDelta(2); Action2 codingAction = new Action2() { @Override public void call(EncoderWrapper encodingCodec, DecoderWrapper decodingCodec) throws IOException { String encodedString = encodingCodec.encode(applications); Applications decodedValue = decodingCodec.decode(encodedString, Applications.class); assertThat(EurekaEntityComparators.equal(applications, decodedValue), is(true)); } }; verifyAllPairs(codingAction, Applications.class, availableJsonWrappers); verifyAllPairs(codingAction, Applications.class, availableXmlWrappers); } /** * For backward compatibility with LegacyJacksonJson codec single item arrays shall not be unwrapped. */ @Test public void testApplicationsJsonEncodeDecodeWithSingleAppItem() throws Exception { final Applications applications = infoGenerator.takeDelta(1); Action2 codingAction = new Action2() { @Override public void call(EncoderWrapper encodingCodec, DecoderWrapper decodingCodec) throws IOException { String encodedString = encodingCodec.encode(applications); assertThat(encodedString.contains("\"application\":[{"), is(true)); Applications decodedValue = decodingCodec.decode(encodedString, Applications.class); assertThat(EurekaEntityComparators.equal(applications, decodedValue), is(true)); } }; List<CodecWrapper> jsonCodes = Arrays.asList( new CodecWrappers.LegacyJacksonJson(), new CodecWrappers.JacksonJson() ); verifyAllPairs(codingAction, Applications.class, jsonCodes); } @Test public void testBatchRequestEncoding() throws Exception { InstanceInfo instance = InstanceInfoGenerator.takeOne(); List<ReplicationInstance> replicationInstances = new ArrayList<>(); replicationInstances.add(new ReplicationInstance( instance.getAppName(), instance.getId(), System.currentTimeMillis(), null, instance.getStatus().name(), instance, Action.Register )); final ReplicationList replicationList = new ReplicationList(replicationInstances); Action2 codingAction = new Action2() { @Override public void call(EncoderWrapper encodingCodec, DecoderWrapper decodingCodec) throws IOException { String encodedString = encodingCodec.encode(replicationList); ReplicationList decodedValue = decodingCodec.decode(encodedString, ReplicationList.class); assertThat(decodedValue.getReplicationList().size(), is(equalTo(1))); } }; // In replication channel we use JSON only List<CodecWrapper> jsonCodes = Arrays.asList( new CodecWrappers.JacksonJson(), new CodecWrappers.LegacyJacksonJson() ); verifyAllPairs(codingAction, ReplicationList.class, jsonCodes); } @Test public void testBatchResponseEncoding() throws Exception { List<ReplicationInstanceResponse> responseList = new ArrayList<>(); responseList.add(new ReplicationInstanceResponse(200, InstanceInfoGenerator.takeOne())); final ReplicationListResponse replicationListResponse = new ReplicationListResponse(responseList); Action2 codingAction = new Action2() { @Override public void call(EncoderWrapper encodingCodec, DecoderWrapper decodingCodec) throws IOException { String encodedString = encodingCodec.encode(replicationListResponse); ReplicationListResponse decodedValue = decodingCodec.decode(encodedString, ReplicationListResponse.class); assertThat(decodedValue.getResponseList().size(), is(equalTo(1))); } }; // In replication channel we use JSON only List<CodecWrapper> jsonCodes = Arrays.asList( new CodecWrappers.JacksonJson(), new CodecWrappers.LegacyJacksonJson() ); verifyAllPairs(codingAction, ReplicationListResponse.class, jsonCodes); } public void verifyAllPairs(Action2 codingAction, Class<?> typeToEncode, List<CodecWrapper> codecHolders) throws Exception { for (EncoderWrapper encodingCodec : codecHolders) { for (DecoderWrapper decodingCodec : codecHolders) { String pair = "{" + encodingCodec.codecName() + ',' + decodingCodec.codecName() + '}'; System.out.println("Encoding " + typeToEncode.getSimpleName() + " using " + pair); try { codingAction.call(encodingCodec, decodingCodec); } catch (Exception ex) { throw new Exception("Encoding failure for codec pair " + pair, ex); } } } } public void verifyForPair(Action2 codingAction, Class<?> typeToEncode, EncoderWrapper encodingCodec, DecoderWrapper decodingCodec) throws Exception { String pair = "{" + encodingCodec.codecName() + ',' + decodingCodec.codecName() + '}'; System.out.println("Encoding " + typeToEncode.getSimpleName() + " using " + pair); try { codingAction.call(encodingCodec, decodingCodec); } catch (Exception ex) { throw new Exception("Encoding failure for codec pair " + pair, ex); } } }