|
19 | 19 | import org.apache.flink.api.java.tuple.Tuple2;
|
20 | 20 | import org.apache.flink.core.testutils.CheckedThread;
|
21 | 21 | import org.apache.flink.runtime.state.StateSnapshotContextSynchronousImpl;
|
| 22 | +import org.apache.flink.util.FlinkRuntimeException; |
22 | 23 |
|
23 | 24 | import com.fasterxml.jackson.core.JsonParseException;
|
24 | 25 | import com.jayway.jsonpath.JsonPath;
|
|
29 | 30 | import com.ververica.cdc.connectors.utils.TestSourceContext;
|
30 | 31 | import com.ververica.cdc.debezium.DebeziumSourceFunction;
|
31 | 32 | import com.ververica.cdc.debezium.history.FlinkJsonTableChangeSerializer;
|
| 33 | +import com.ververica.cdc.debezium.internal.Handover; |
| 34 | +import io.debezium.DebeziumException; |
32 | 35 | import io.debezium.document.Document;
|
33 | 36 | import io.debezium.document.DocumentWriter;
|
34 | 37 | import io.debezium.relational.Column;
|
@@ -915,6 +918,143 @@ public void go() throws Exception {
|
915 | 918 | }
|
916 | 919 | }
|
917 | 920 |
|
| 921 | + @Test |
| 922 | + public void testSnapshotOnClosedSource() throws Exception { |
| 923 | + final TestingListState<byte[]> offsetState = new TestingListState<>(); |
| 924 | + final TestingListState<String> historyState = new TestingListState<>(); |
| 925 | + |
| 926 | + { |
| 927 | + try (Connection connection = database.getJdbcConnection(); |
| 928 | + Statement statement = connection.createStatement()) { |
| 929 | + // Step-1: start the source from empty state |
| 930 | + final DebeziumSourceFunction<SourceRecord> source = createMySqlBinlogSource(); |
| 931 | + final TestSourceContext<SourceRecord> sourceContext = new TestSourceContext<>(); |
| 932 | + // setup source with empty state |
| 933 | + setupSource(source, false, offsetState, historyState, true, 0, 1); |
| 934 | + |
| 935 | + final CheckedThread runThread = |
| 936 | + new CheckedThread() { |
| 937 | + @Override |
| 938 | + public void go() throws Exception { |
| 939 | + source.run(sourceContext); |
| 940 | + } |
| 941 | + }; |
| 942 | + runThread.start(); |
| 943 | + |
| 944 | + // wait until the source finishes the database snapshot |
| 945 | + List<SourceRecord> records = drain(sourceContext, 9); |
| 946 | + assertEquals(9, records.size()); |
| 947 | + |
| 948 | + // state is still empty |
| 949 | + assertEquals(0, offsetState.list.size()); |
| 950 | + assertEquals(0, historyState.list.size()); |
| 951 | + |
| 952 | + statement.execute( |
| 953 | + "INSERT INTO `products` VALUES (110,'robot','Toy robot',1.304)"); // 110 |
| 954 | + |
| 955 | + int received = drain(sourceContext, 1).size(); |
| 956 | + assertEquals(1, received); |
| 957 | + |
| 958 | + // Step-2: trigger a checkpoint |
| 959 | + synchronized (sourceContext.getCheckpointLock()) { |
| 960 | + // trigger checkpoint-1 |
| 961 | + source.snapshotState(new StateSnapshotContextSynchronousImpl(101, 101)); |
| 962 | + } |
| 963 | + |
| 964 | + assertTrue(historyState.list.size() > 0); |
| 965 | + assertTrue(offsetState.list.size() > 0); |
| 966 | + |
| 967 | + // Step-3: mock the engine stop with savepoint, trigger a |
| 968 | + // checkpoint on closed source |
| 969 | + final Handover handover = source.getHandover(); |
| 970 | + handover.close(); |
| 971 | + synchronized (sourceContext.getCheckpointLock()) { |
| 972 | + // trigger checkpoint-2 |
| 973 | + source.snapshotState(new StateSnapshotContextSynchronousImpl(102, 102)); |
| 974 | + } |
| 975 | + |
| 976 | + assertTrue(historyState.list.size() > 0); |
| 977 | + assertTrue(offsetState.list.size() > 0); |
| 978 | + |
| 979 | + source.close(); |
| 980 | + runThread.sync(); |
| 981 | + } |
| 982 | + } |
| 983 | + } |
| 984 | + |
| 985 | + @Test |
| 986 | + public void testSnapshotOnFailedSource() throws Exception { |
| 987 | + final TestingListState<byte[]> offsetState = new TestingListState<>(); |
| 988 | + final TestingListState<String> historyState = new TestingListState<>(); |
| 989 | + |
| 990 | + { |
| 991 | + try (Connection connection = database.getJdbcConnection(); |
| 992 | + Statement statement = connection.createStatement()) { |
| 993 | + // Step-1: start the source from empty state |
| 994 | + final DebeziumSourceFunction<SourceRecord> source = createMySqlBinlogSource(); |
| 995 | + final TestSourceContext<SourceRecord> sourceContext = new TestSourceContext<>(); |
| 996 | + // setup source with empty state |
| 997 | + setupSource(source, false, offsetState, historyState, true, 0, 1); |
| 998 | + |
| 999 | + final CheckedThread runThread = |
| 1000 | + new CheckedThread() { |
| 1001 | + @Override |
| 1002 | + public void go() throws Exception { |
| 1003 | + source.run(sourceContext); |
| 1004 | + } |
| 1005 | + }; |
| 1006 | + runThread.start(); |
| 1007 | + |
| 1008 | + // wait until the source finishes the database snapshot |
| 1009 | + List<SourceRecord> records = drain(sourceContext, 9); |
| 1010 | + assertEquals(9, records.size()); |
| 1011 | + |
| 1012 | + // state is still empty |
| 1013 | + assertEquals(0, offsetState.list.size()); |
| 1014 | + assertEquals(0, historyState.list.size()); |
| 1015 | + |
| 1016 | + statement.execute( |
| 1017 | + "INSERT INTO `products` VALUES (110,'robot','Toy robot',1.304)"); // 110 |
| 1018 | + |
| 1019 | + int received = drain(sourceContext, 1).size(); |
| 1020 | + assertEquals(1, received); |
| 1021 | + |
| 1022 | + // Step-2: trigger a checkpoint |
| 1023 | + synchronized (sourceContext.getCheckpointLock()) { |
| 1024 | + // trigger checkpoint-1 |
| 1025 | + source.snapshotState(new StateSnapshotContextSynchronousImpl(101, 101)); |
| 1026 | + } |
| 1027 | + |
| 1028 | + assertTrue(historyState.list.size() > 0); |
| 1029 | + assertTrue(offsetState.list.size() > 0); |
| 1030 | + |
| 1031 | + // Step-3: mock the engine stop due to underlying debezium exception, trigger a |
| 1032 | + // checkpoint on failed source |
| 1033 | + final Handover handover = source.getHandover(); |
| 1034 | + handover.reportError(new DebeziumException("Mocked debezium exception")); |
| 1035 | + handover.close(); |
| 1036 | + try { |
| 1037 | + synchronized (sourceContext.getCheckpointLock()) { |
| 1038 | + // trigger checkpoint-2 |
| 1039 | + source.snapshotState(new StateSnapshotContextSynchronousImpl(102, 102)); |
| 1040 | + } |
| 1041 | + fail("Should fail."); |
| 1042 | + } catch (Exception e) { |
| 1043 | + assertTrue(e instanceof FlinkRuntimeException); |
| 1044 | + assertTrue( |
| 1045 | + e.getMessage() |
| 1046 | + .contains( |
| 1047 | + "Call snapshotState() on failed source, checkpoint failed.")); |
| 1048 | + assertTrue(e.getCause() instanceof Handover.ClosedException); |
| 1049 | + assertTrue(e.getCause().getMessage().contains("Close handover with error.")); |
| 1050 | + } finally { |
| 1051 | + source.close(); |
| 1052 | + runThread.sync(); |
| 1053 | + } |
| 1054 | + } |
| 1055 | + } |
| 1056 | + } |
| 1057 | + |
918 | 1058 | // ------------------------------------------------------------------------------------------
|
919 | 1059 | // Public Utilities
|
920 | 1060 | // ------------------------------------------------------------------------------------------
|
|
0 commit comments