PageRenderTime 44ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/clients/src/test/java/org/apache/kafka/clients/FetchSessionHandlerTest.java

https://bitbucket.org/mrshukla/mshukla.kafka
Java | 356 lines | 285 code | 31 blank | 40 comment | 10 complexity | 6ebd326b2eee7b372841c6b850ed78ec MD5 | raw file
Possible License(s): Apache-2.0
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package org.apache.kafka.clients;
  18. import org.apache.kafka.common.TopicPartition;
  19. import org.apache.kafka.common.protocol.Errors;
  20. import org.apache.kafka.common.requests.FetchRequest;
  21. import org.apache.kafka.common.requests.FetchResponse;
  22. import org.apache.kafka.common.utils.LogContext;
  23. import org.junit.Rule;
  24. import org.junit.Test;
  25. import org.junit.rules.Timeout;
  26. import org.slf4j.Logger;
  27. import java.util.ArrayList;
  28. import java.util.Arrays;
  29. import java.util.Comparator;
  30. import java.util.Iterator;
  31. import java.util.LinkedHashMap;
  32. import java.util.List;
  33. import java.util.Map;
  34. import java.util.Set;
  35. import java.util.TreeSet;
  36. import static org.apache.kafka.common.requests.FetchMetadata.INITIAL_EPOCH;
  37. import static org.apache.kafka.common.requests.FetchMetadata.INVALID_SESSION_ID;
  38. import static org.junit.Assert.assertEquals;
  39. import static org.junit.Assert.assertFalse;
  40. import static org.junit.Assert.assertTrue;
  41. import static org.junit.Assert.fail;
  42. /**
  43. * A unit test for FetchSessionHandler.
  44. */
  45. public class FetchSessionHandlerTest {
  46. @Rule
  47. final public Timeout globalTimeout = Timeout.millis(120000);
  48. private static final LogContext LOG_CONTEXT = new LogContext("[FetchSessionHandler]=");
  49. private static final Logger log = LOG_CONTEXT.logger(FetchSessionHandler.class);
  50. /**
  51. * Create a set of TopicPartitions. We use a TreeSet, in order to get a deterministic
  52. * ordering for test purposes.
  53. */
  54. private final static Set<TopicPartition> toSet(TopicPartition... arr) {
  55. TreeSet<TopicPartition> set = new TreeSet<>(new Comparator<TopicPartition>() {
  56. @Override
  57. public int compare(TopicPartition o1, TopicPartition o2) {
  58. return o1.toString().compareTo(o2.toString());
  59. }
  60. });
  61. set.addAll(Arrays.asList(arr));
  62. return set;
  63. }
  64. @Test
  65. public void testFindMissing() throws Exception {
  66. TopicPartition foo0 = new TopicPartition("foo", 0);
  67. TopicPartition foo1 = new TopicPartition("foo", 1);
  68. TopicPartition bar0 = new TopicPartition("bar", 0);
  69. TopicPartition bar1 = new TopicPartition("bar", 1);
  70. TopicPartition baz0 = new TopicPartition("baz", 0);
  71. TopicPartition baz1 = new TopicPartition("baz", 1);
  72. assertEquals(toSet(), FetchSessionHandler.findMissing(toSet(foo0), toSet(foo0)));
  73. assertEquals(toSet(foo0), FetchSessionHandler.findMissing(toSet(foo0), toSet(foo1)));
  74. assertEquals(toSet(foo0, foo1),
  75. FetchSessionHandler.findMissing(toSet(foo0, foo1), toSet(baz0)));
  76. assertEquals(toSet(bar1, foo0, foo1),
  77. FetchSessionHandler.findMissing(toSet(foo0, foo1, bar0, bar1),
  78. toSet(bar0, baz0, baz1)));
  79. assertEquals(toSet(),
  80. FetchSessionHandler.findMissing(toSet(foo0, foo1, bar0, bar1, baz1),
  81. toSet(foo0, foo1, bar0, bar1, baz0, baz1)));
  82. }
  83. private static final class ReqEntry {
  84. final TopicPartition part;
  85. final FetchRequest.PartitionData data;
  86. ReqEntry(String topic, int partition, long fetchOffset, long logStartOffset, int maxBytes) {
  87. this.part = new TopicPartition(topic, partition);
  88. this.data = new FetchRequest.PartitionData(fetchOffset, logStartOffset, maxBytes);
  89. }
  90. }
  91. private static LinkedHashMap<TopicPartition, FetchRequest.PartitionData> reqMap(ReqEntry... entries) {
  92. LinkedHashMap<TopicPartition, FetchRequest.PartitionData> map = new LinkedHashMap<>();
  93. for (ReqEntry entry : entries) {
  94. map.put(entry.part, entry.data);
  95. }
  96. return map;
  97. }
  98. private static void assertMapEquals(Map<TopicPartition, FetchRequest.PartitionData> expected,
  99. Map<TopicPartition, FetchRequest.PartitionData> actual) {
  100. Iterator<Map.Entry<TopicPartition, FetchRequest.PartitionData>> expectedIter =
  101. expected.entrySet().iterator();
  102. Iterator<Map.Entry<TopicPartition, FetchRequest.PartitionData>> actualIter =
  103. actual.entrySet().iterator();
  104. int i = 1;
  105. while (expectedIter.hasNext()) {
  106. Map.Entry<TopicPartition, FetchRequest.PartitionData> expectedEntry = expectedIter.next();
  107. if (!actualIter.hasNext()) {
  108. fail("Element " + i + " not found.");
  109. }
  110. Map.Entry<TopicPartition, FetchRequest.PartitionData> actuaLEntry = actualIter.next();
  111. assertEquals("Element " + i + " had a different TopicPartition than expected.",
  112. expectedEntry.getKey(), actuaLEntry.getKey());
  113. assertEquals("Element " + i + " had different PartitionData than expected.",
  114. expectedEntry.getValue(), actuaLEntry.getValue());
  115. i++;
  116. }
  117. if (expectedIter.hasNext()) {
  118. fail("Unexpected element " + i + " found.");
  119. }
  120. }
  121. private static void assertMapsEqual(Map<TopicPartition, FetchRequest.PartitionData> expected,
  122. Map<TopicPartition, FetchRequest.PartitionData>... actuals) {
  123. for (Map<TopicPartition, FetchRequest.PartitionData> actual : actuals) {
  124. assertMapEquals(expected, actual);
  125. }
  126. }
  127. private static void assertListEquals(List<TopicPartition> expected, List<TopicPartition> actual) {
  128. for (TopicPartition expectedPart : expected) {
  129. if (!actual.contains(expectedPart)) {
  130. fail("Failed to find expected partition " + expectedPart);
  131. }
  132. }
  133. for (TopicPartition actualPart : actual) {
  134. if (!expected.contains(actualPart)) {
  135. fail("Found unexpected partition " + actualPart);
  136. }
  137. }
  138. }
  139. private static final class RespEntry {
  140. final TopicPartition part;
  141. final FetchResponse.PartitionData data;
  142. RespEntry(String topic, int partition, long highWatermark, long lastStableOffset) {
  143. this.part = new TopicPartition(topic, partition);
  144. this.data = new FetchResponse.PartitionData(
  145. Errors.NONE,
  146. highWatermark,
  147. lastStableOffset,
  148. 0,
  149. null,
  150. null);
  151. }
  152. }
  153. private static LinkedHashMap<TopicPartition, FetchResponse.PartitionData> respMap(RespEntry... entries) {
  154. LinkedHashMap<TopicPartition, FetchResponse.PartitionData> map = new LinkedHashMap<>();
  155. for (RespEntry entry : entries) {
  156. map.put(entry.part, entry.data);
  157. }
  158. return map;
  159. }
  160. /**
  161. * Test the handling of SESSIONLESS responses.
  162. * Pre-KIP-227 brokers always supply this kind of response.
  163. */
  164. @Test
  165. public void testSessionless() throws Exception {
  166. FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
  167. FetchSessionHandler.Builder builder = handler.newBuilder();
  168. builder.add(new TopicPartition("foo", 0),
  169. new FetchRequest.PartitionData(0, 100, 200));
  170. builder.add(new TopicPartition("foo", 1),
  171. new FetchRequest.PartitionData(10, 110, 210));
  172. FetchSessionHandler.FetchRequestData data = builder.build();
  173. assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200),
  174. new ReqEntry("foo", 1, 10, 110, 210)),
  175. data.toSend(), data.sessionPartitions());
  176. assertEquals(INVALID_SESSION_ID, data.metadata().sessionId());
  177. assertEquals(INITIAL_EPOCH, data.metadata().epoch());
  178. FetchResponse resp = new FetchResponse(Errors.NONE,
  179. respMap(new RespEntry("foo", 0, 0, 0),
  180. new RespEntry("foo", 1, 0, 0)),
  181. 0, INVALID_SESSION_ID);
  182. handler.handleResponse(resp);
  183. FetchSessionHandler.Builder builder2 = handler.newBuilder();
  184. builder2.add(new TopicPartition("foo", 0),
  185. new FetchRequest.PartitionData(0, 100, 200));
  186. FetchSessionHandler.FetchRequestData data2 = builder2.build();
  187. assertEquals(INVALID_SESSION_ID, data2.metadata().sessionId());
  188. assertEquals(INITIAL_EPOCH, data2.metadata().epoch());
  189. assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200)),
  190. data.toSend(), data.sessionPartitions());
  191. }
  192. /**
  193. * Test handling an incremental fetch session.
  194. */
  195. @Test
  196. public void testIncrementals() throws Exception {
  197. FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
  198. FetchSessionHandler.Builder builder = handler.newBuilder();
  199. builder.add(new TopicPartition("foo", 0),
  200. new FetchRequest.PartitionData(0, 100, 200));
  201. builder.add(new TopicPartition("foo", 1),
  202. new FetchRequest.PartitionData(10, 110, 210));
  203. FetchSessionHandler.FetchRequestData data = builder.build();
  204. assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200),
  205. new ReqEntry("foo", 1, 10, 110, 210)),
  206. data.toSend(), data.sessionPartitions());
  207. assertEquals(INVALID_SESSION_ID, data.metadata().sessionId());
  208. assertEquals(INITIAL_EPOCH, data.metadata().epoch());
  209. FetchResponse resp = new FetchResponse(Errors.NONE,
  210. respMap(new RespEntry("foo", 0, 10, 20),
  211. new RespEntry("foo", 1, 10, 20)),
  212. 0, 123);
  213. handler.handleResponse(resp);
  214. // Test an incremental fetch request which adds one partition and modifies another.
  215. FetchSessionHandler.Builder builder2 = handler.newBuilder();
  216. builder2.add(new TopicPartition("foo", 0),
  217. new FetchRequest.PartitionData(0, 100, 200));
  218. builder2.add(new TopicPartition("foo", 1),
  219. new FetchRequest.PartitionData(10, 120, 210));
  220. builder2.add(new TopicPartition("bar", 0),
  221. new FetchRequest.PartitionData(20, 200, 200));
  222. FetchSessionHandler.FetchRequestData data2 = builder2.build();
  223. assertFalse(data2.metadata().isFull());
  224. assertMapEquals(reqMap(new ReqEntry("foo", 0, 0, 100, 200),
  225. new ReqEntry("foo", 1, 10, 120, 210),
  226. new ReqEntry("bar", 0, 20, 200, 200)),
  227. data2.sessionPartitions());
  228. assertMapEquals(reqMap(new ReqEntry("bar", 0, 20, 200, 200),
  229. new ReqEntry("foo", 1, 10, 120, 210)),
  230. data2.toSend());
  231. FetchResponse resp2 = new FetchResponse(Errors.NONE,
  232. respMap(new RespEntry("foo", 1, 20, 20)),
  233. 0, 123);
  234. handler.handleResponse(resp2);
  235. // Skip building a new request. Test that handling an invalid fetch session epoch response results
  236. // in a request which closes the session.
  237. FetchResponse resp3 = new FetchResponse(Errors.INVALID_FETCH_SESSION_EPOCH, respMap(),
  238. 0, INVALID_SESSION_ID);
  239. handler.handleResponse(resp3);
  240. FetchSessionHandler.Builder builder4 = handler.newBuilder();
  241. builder4.add(new TopicPartition("foo", 0),
  242. new FetchRequest.PartitionData(0, 100, 200));
  243. builder4.add(new TopicPartition("foo", 1),
  244. new FetchRequest.PartitionData(10, 120, 210));
  245. builder4.add(new TopicPartition("bar", 0),
  246. new FetchRequest.PartitionData(20, 200, 200));
  247. FetchSessionHandler.FetchRequestData data4 = builder4.build();
  248. assertTrue(data4.metadata().isFull());
  249. assertEquals(data2.metadata().sessionId(), data4.metadata().sessionId());
  250. assertEquals(INITIAL_EPOCH, data4.metadata().epoch());
  251. assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200),
  252. new ReqEntry("foo", 1, 10, 120, 210),
  253. new ReqEntry("bar", 0, 20, 200, 200)),
  254. data4.sessionPartitions(), data4.toSend());
  255. }
  256. /**
  257. * Test that calling FetchSessionHandler#Builder#build twice fails.
  258. */
  259. @Test
  260. public void testDoubleBuild() throws Exception {
  261. FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
  262. FetchSessionHandler.Builder builder = handler.newBuilder();
  263. builder.add(new TopicPartition("foo", 0),
  264. new FetchRequest.PartitionData(0, 100, 200));
  265. builder.build();
  266. try {
  267. builder.build();
  268. fail("Expected calling build twice to fail.");
  269. } catch (Throwable t) {
  270. // expected
  271. }
  272. }
  273. @Test
  274. public void testIncrementalPartitionRemoval() throws Exception {
  275. FetchSessionHandler handler = new FetchSessionHandler(LOG_CONTEXT, 1);
  276. FetchSessionHandler.Builder builder = handler.newBuilder();
  277. builder.add(new TopicPartition("foo", 0),
  278. new FetchRequest.PartitionData(0, 100, 200));
  279. builder.add(new TopicPartition("foo", 1),
  280. new FetchRequest.PartitionData(10, 110, 210));
  281. builder.add(new TopicPartition("bar", 0),
  282. new FetchRequest.PartitionData(20, 120, 220));
  283. FetchSessionHandler.FetchRequestData data = builder.build();
  284. assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200),
  285. new ReqEntry("foo", 1, 10, 110, 210),
  286. new ReqEntry("bar", 0, 20, 120, 220)),
  287. data.toSend(), data.sessionPartitions());
  288. assertTrue(data.metadata().isFull());
  289. FetchResponse resp = new FetchResponse(Errors.NONE,
  290. respMap(new RespEntry("foo", 0, 10, 20),
  291. new RespEntry("foo", 1, 10, 20),
  292. new RespEntry("bar", 0, 10, 20)),
  293. 0, 123);
  294. handler.handleResponse(resp);
  295. // Test an incremental fetch request which removes two partitions.
  296. FetchSessionHandler.Builder builder2 = handler.newBuilder();
  297. builder2.add(new TopicPartition("foo", 1),
  298. new FetchRequest.PartitionData(10, 110, 210));
  299. FetchSessionHandler.FetchRequestData data2 = builder2.build();
  300. assertFalse(data2.metadata().isFull());
  301. assertEquals(123, data2.metadata().sessionId());
  302. assertEquals(1, data2.metadata().epoch());
  303. assertMapEquals(reqMap(new ReqEntry("foo", 1, 10, 110, 210)),
  304. data2.sessionPartitions());
  305. assertMapEquals(reqMap(), data2.toSend());
  306. ArrayList<TopicPartition> expectedToForget2 = new ArrayList<>();
  307. expectedToForget2.add(new TopicPartition("foo", 0));
  308. expectedToForget2.add(new TopicPartition("bar", 0));
  309. assertListEquals(expectedToForget2, data2.toForget());
  310. // A FETCH_SESSION_ID_NOT_FOUND response triggers us to close the session.
  311. // The next request is a session establishing FULL request.
  312. FetchResponse resp2 = new FetchResponse(Errors.FETCH_SESSION_ID_NOT_FOUND,
  313. respMap(), 0, INVALID_SESSION_ID);
  314. handler.handleResponse(resp2);
  315. FetchSessionHandler.Builder builder3 = handler.newBuilder();
  316. builder3.add(new TopicPartition("foo", 0),
  317. new FetchRequest.PartitionData(0, 100, 200));
  318. FetchSessionHandler.FetchRequestData data3 = builder3.build();
  319. assertTrue(data3.metadata().isFull());
  320. assertEquals(INVALID_SESSION_ID, data3.metadata().sessionId());
  321. assertEquals(INITIAL_EPOCH, data3.metadata().epoch());
  322. assertMapsEqual(reqMap(new ReqEntry("foo", 0, 0, 100, 200)),
  323. data3.sessionPartitions(), data3.toSend());
  324. }
  325. }