|
10 | 10 | import static com.linkedin.venice.vpj.VenicePushJobConstants.DEFAULT_KEY_FIELD_PROP; |
11 | 11 | import static com.linkedin.venice.vpj.VenicePushJobConstants.DEFAULT_VALUE_FIELD_PROP; |
12 | 12 | import static com.linkedin.venice.vpj.VenicePushJobConstants.VENICE_DISCOVER_URL_PROP; |
| 13 | +import static com.linkedin.venice.vpj.VenicePushJobConstants.VENICE_STORE_NAME_PROP; |
| 14 | +import static com.linkedin.venice.vpj.VenicePushJobConstants.VT_CONSISTENCY_CHECK_ONLY; |
13 | 15 | import static org.testng.Assert.assertEquals; |
14 | 16 | import static org.testng.Assert.assertFalse; |
15 | 17 | import static org.testng.Assert.assertNotNull; |
| 18 | +import static org.testng.Assert.assertThrows; |
16 | 19 |
|
17 | 20 | import com.linkedin.venice.annotation.PubSubAgnosticTest; |
18 | 21 | import com.linkedin.venice.client.store.AvroGenericStoreClient; |
19 | 22 | import com.linkedin.venice.client.store.ClientConfig; |
20 | 23 | import com.linkedin.venice.client.store.ClientFactory; |
21 | 24 | import com.linkedin.venice.controllerapi.ControllerClient; |
22 | 25 | import com.linkedin.venice.controllerapi.UpdateStoreQueryParams; |
| 26 | +import com.linkedin.venice.exceptions.VeniceException; |
| 27 | +import com.linkedin.venice.hadoop.VenicePushJob; |
23 | 28 | import com.linkedin.venice.integration.utils.PubSubBrokerWrapper; |
24 | 29 | import com.linkedin.venice.integration.utils.VeniceClusterWrapper; |
25 | 30 | import com.linkedin.venice.meta.Version; |
@@ -106,6 +111,39 @@ protected Properties getExtraControllerProperties() { |
106 | 111 | */ |
107 | 112 | @Test(timeOut = TEST_TIMEOUT) |
108 | 113 | public void testFullPipelineWithBatchPushRTWritesAndInjectedInconsistency() throws Exception { |
| 114 | + String storeName = setupAACorruptedStore(); |
| 115 | + String versionTopic = Version.composeKafkaTopic(storeName, 1); |
| 116 | + File tempRoot = Files.createTempDirectory("vt-consistency-full-pipeline").toFile(); |
| 117 | + File outputDir = new File(tempRoot, "output"); |
| 118 | + try { |
| 119 | + Properties jobProps = buildCheckerJobProps(storeName, outputDir); |
| 120 | + assertThrows(VeniceException.class, () -> VTConsistencyCheckerJob.run(jobProps)); |
| 121 | + verifyMismatchInParquet(outputDir, versionTopic); |
| 122 | + } finally { |
| 123 | + org.apache.commons.io.FileUtils.deleteDirectory(tempRoot); |
| 124 | + } |
| 125 | + } |
| 126 | + |
| 127 | + @Test(timeOut = TEST_TIMEOUT) |
| 128 | + public void testFullPipelineWithVPJDrivenCheckerThrowsOnInconsistency() throws Exception { |
| 129 | + String storeName = setupAACorruptedStore(); |
| 130 | + String versionTopic = Version.composeKafkaTopic(storeName, 1); |
| 131 | + File tempRoot = Files.createTempDirectory("vt-consistency-vpj-driven").toFile(); |
| 132 | + File outputDir = new File(tempRoot, "output"); |
| 133 | + try { |
| 134 | + Properties jobProps = buildCheckerJobProps(storeName, outputDir); |
| 135 | + jobProps.setProperty(VT_CONSISTENCY_CHECK_ONLY, "true"); |
| 136 | + jobProps.setProperty(VENICE_STORE_NAME_PROP, storeName); |
| 137 | + try (VenicePushJob vpj = new VenicePushJob(Utils.getUniqueString("vpj-vt-check"), jobProps)) { |
| 138 | + assertThrows(VeniceException.class, vpj::run); |
| 139 | + } |
| 140 | + verifyMismatchInParquet(outputDir, versionTopic); |
| 141 | + } finally { |
| 142 | + org.apache.commons.io.FileUtils.deleteDirectory(tempRoot); |
| 143 | + } |
| 144 | + } |
| 145 | + |
| 146 | + private String setupAACorruptedStore() throws Exception { |
109 | 147 | // 1. Batch push with 5 records via VPJ |
110 | 148 | File inputDir = getTempDataDirectory(); |
111 | 149 | Schema recordSchema = TestWriteUtils.writeSimpleAvroFileWithStringToStringSchema(inputDir, 5); |
@@ -227,46 +265,43 @@ public void testFullPipelineWithBatchPushRTWritesAndInjectedInconsistency() thro |
227 | 265 | } |
228 | 266 | }); |
229 | 267 |
|
230 | | - // 6. Run VT consistency checker |
231 | | - File tempRoot = Files.createTempDirectory("vt-consistency-full-pipeline").toFile(); |
232 | | - File outputDir = new File(tempRoot, "output"); |
233 | | - try { |
234 | | - Properties jobProps = new Properties(); |
235 | | - jobProps.setProperty( |
236 | | - VTConsistencyCheckerJob.DC0_BROKER_URL, |
237 | | - childDatacenters.get(0).getPubSubBrokerWrapper().getAddress()); |
238 | | - jobProps.setProperty( |
239 | | - VTConsistencyCheckerJob.DC1_BROKER_URL, |
240 | | - childDatacenters.get(1).getPubSubBrokerWrapper().getAddress()); |
241 | | - jobProps.setProperty(VTConsistencyCheckerJob.STORE_NAME, storeName); |
242 | | - jobProps.setProperty(VENICE_DISCOVER_URL_PROP, childDatacenters.get(0).getControllerConnectString()); |
243 | | - jobProps.setProperty(VTConsistencyCheckerJob.OUTPUT_PATH, outputDir.getAbsolutePath()); |
244 | | - jobProps.setProperty(VTConsistencyCheckerJob.NUMBER_OF_REGIONS, "2"); |
| 268 | + return storeName; |
| 269 | + } |
| 270 | + } |
245 | 271 |
|
246 | | - VTConsistencyCheckerJob.run(jobProps); |
| 272 | + private Properties buildCheckerJobProps(String storeName, File outputDir) { |
| 273 | + Properties jobProps = new Properties(); |
| 274 | + jobProps.setProperty( |
| 275 | + VTConsistencyCheckerJob.DC0_BROKER_URL, |
| 276 | + childDatacenters.get(0).getPubSubBrokerWrapper().getAddress()); |
| 277 | + jobProps.setProperty( |
| 278 | + VTConsistencyCheckerJob.DC1_BROKER_URL, |
| 279 | + childDatacenters.get(1).getPubSubBrokerWrapper().getAddress()); |
| 280 | + jobProps.setProperty(VTConsistencyCheckerJob.STORE_NAME, storeName); |
| 281 | + jobProps.setProperty(VENICE_DISCOVER_URL_PROP, childDatacenters.get(0).getControllerConnectString()); |
| 282 | + jobProps.setProperty(VTConsistencyCheckerJob.OUTPUT_PATH, outputDir.getAbsolutePath()); |
| 283 | + jobProps.setProperty(VTConsistencyCheckerJob.NUMBER_OF_REGIONS, "2"); |
| 284 | + return jobProps; |
| 285 | + } |
247 | 286 |
|
248 | | - SparkSession reader = |
249 | | - SparkSession.builder().master("local[*]").appName("TestVTConsistencyCheckerJob-reader").getOrCreate(); |
250 | | - try { |
251 | | - Dataset<Row> result = reader.read().parquet(outputDir.getAbsolutePath()); |
252 | | - List<Row> rows = result.collectAsList(); |
| 287 | + private void verifyMismatchInParquet(File outputDir, String versionTopic) { |
| 288 | + SparkSession reader = |
| 289 | + SparkSession.builder().master("local[*]").appName("TestVTConsistencyCheckerJob-reader").getOrCreate(); |
| 290 | + try { |
| 291 | + Dataset<Row> result = reader.read().parquet(outputDir.getAbsolutePath()); |
| 292 | + List<Row> rows = result.collectAsList(); |
253 | 293 |
|
254 | | - // Expect at least one VALUE_MISMATCH for the overwritten key "buggy-key" |
255 | | - List<Row> mismatches = |
256 | | - rows.stream().filter(r -> "VALUE_MISMATCH".equals(r.getAs("type"))).collect(Collectors.toList()); |
257 | | - assertFalse(mismatches.isEmpty(), "Expected at least one VALUE_MISMATCH for the corrupted key"); |
| 294 | + List<Row> mismatches = |
| 295 | + rows.stream().filter(r -> "VALUE_MISMATCH".equals(r.getAs("type"))).collect(Collectors.toList()); |
| 296 | + assertFalse(mismatches.isEmpty(), "Expected at least one VALUE_MISMATCH for the corrupted key"); |
258 | 297 |
|
259 | | - Row corruptRow = mismatches.get(0); |
260 | | - assertEquals(corruptRow.getAs("version_topic"), versionTopic); |
261 | | - assertFalse( |
262 | | - corruptRow.getAs("dc0_value_hash").equals(corruptRow.getAs("dc1_value_hash")), |
263 | | - "DC value hashes must differ for corrupted key"); |
264 | | - } finally { |
265 | | - reader.stop(); |
266 | | - } |
267 | | - } finally { |
268 | | - org.apache.commons.io.FileUtils.deleteDirectory(tempRoot); |
269 | | - } |
| 298 | + Row corruptRow = mismatches.get(0); |
| 299 | + assertEquals(corruptRow.getAs("version_topic"), versionTopic); |
| 300 | + assertFalse( |
| 301 | + corruptRow.getAs("dc0_value_hash").equals(corruptRow.getAs("dc1_value_hash")), |
| 302 | + "DC value hashes must differ for corrupted key"); |
| 303 | + } finally { |
| 304 | + reader.stop(); |
270 | 305 | } |
271 | 306 | } |
272 | 307 |
|
|
0 commit comments