Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
711a5ea
Add shared rich integration test suite
xiangfu0 Apr 24, 2026
2a7abcc
Expand shared rich integration suite batch
xiangfu0 Apr 24, 2026
162ba07
Add segment generation minion to shared suite
xiangfu0 Apr 24, 2026
a8e72ef
Add dimension table to shared suite
xiangfu0 Apr 24, 2026
690e1ff
Add more tests to shared rich suite
xiangfu0 Apr 24, 2026
1d8463e
Add error code tests to shared suite
xiangfu0 Apr 24, 2026
a17343e
Expand shared suite with quota and minion tests
xiangfu0 Apr 24, 2026
5d9e416
Add logical table variants to shared suite
xiangfu0 Apr 24, 2026
2aeaf75
Add remaining logical table variants to shared suite
xiangfu0 Apr 25, 2026
46e26a4
Add simple minion test to shared suite
xiangfu0 Apr 25, 2026
630c398
Add admin and realtime minion tests to shared suite
xiangfu0 Apr 25, 2026
8435421
Add query context and znode tests to shared suite
xiangfu0 Apr 25, 2026
0db777c
Add commit time and explain tests to shared suites
xiangfu0 Apr 25, 2026
399d447
Add cursor and empty response shared suites
xiangfu0 Apr 25, 2026
8120e6a
Add query limit and null handling shared suites
xiangfu0 Apr 25, 2026
a6aac6a
Add grpc and metrics shared suites
xiangfu0 Apr 25, 2026
f7db241
Add grpc and query killing shared suites
xiangfu0 Apr 25, 2026
bdaa357
Add workload shared suite profiles
xiangfu0 Apr 25, 2026
5ee91af
Add Kafka shared suite profiles
xiangfu0 Apr 25, 2026
b8dfb08
Add realtime manager shared suite profile
xiangfu0 Apr 25, 2026
02c3fb2
Add service discovery and timeseries shared profiles
xiangfu0 Apr 25, 2026
6e7ad25
Add timeseries auth shared suite profile
xiangfu0 Apr 25, 2026
9ff026d
Add auth shared suite profiles
xiangfu0 Apr 25, 2026
5253bd5
Add TLS URL auth and gRPC shared profiles
xiangfu0 Apr 25, 2026
b67ecbd
Add offline shared suite profile
xiangfu0 Apr 25, 2026
43a26c7
Add custom tenant MSQ shared profile
xiangfu0 Apr 25, 2026
cc65d56
Add LLC realtime shared profile
xiangfu0 Apr 25, 2026
47b4cd0
Add shared hybrid integration suite
xiangfu0 Apr 25, 2026
8ccf09e
Add controller periodic shared profile
xiangfu0 Apr 25, 2026
bd50fbd
Add no-override and peer-download shared profiles
xiangfu0 Apr 25, 2026
da3e590
Expand no-override offline shared suite
xiangfu0 Apr 25, 2026
7aa4c39
Expand shared realtime integration suites
xiangfu0 Apr 25, 2026
b5445a2
Add controller-only and multi-node shared suites
xiangfu0 Apr 25, 2026
4da68c1
Add preload and disabled shared suites
xiangfu0 Apr 25, 2026
9c1425a
Add shared rebalance integration suites
xiangfu0 Apr 25, 2026
9b4a811
Run shared integration profiles in CI
xiangfu0 Apr 26, 2026
3c286c4
Run shared rich integration suite in CI
xiangfu0 Apr 26, 2026
cb93569
Balance shared rich integration suite lane
xiangfu0 Apr 26, 2026
3afd40a
Revert "Balance shared rich integration suite lane"
xiangfu0 Apr 26, 2026
353b628
Revert "Run shared rich integration suite in CI"
xiangfu0 Apr 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 31 additions & 11 deletions .github/workflows/scripts/pr-tests/.pinot_tests_integration.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,43 @@ print_surefire_dumps() {
done <<< "${dump_files}"
}

# Integration Tests
cd pinot-integration-tests || exit 1
if [ "$RUN_TEST_SET" == "1" ]; then
mvn test \
-P github-actions,codecoverage,integration-tests-set-1 || {
run_integration_profile() {
local profile="$1"
shift

mvn test "$@" \
-P "github-actions,codecoverage,${profile}" || {
print_surefire_dumps
exit 1
}
}

# Integration Tests
cd pinot-integration-tests || exit 1
if [ "$RUN_TEST_SET" == "1" ]; then
run_integration_profile integration-tests-set-1
shared_profiles=(
shared-hybrid-cluster-integration-test-suite
shared-llc-realtime-cluster-integration-test-suite
)
for shared_profile in "${shared_profiles[@]}"; do
run_integration_profile "${shared_profile}"
done
exit 0
fi
if [ "$RUN_TEST_SET" == "2" ]; then
mvn test \
-DargLine="-Xms1g -Xmx2g -Dlog4j2.configurationFile=log4j2.xml" \
-P github-actions,codecoverage,integration-tests-set-2 || {
print_surefire_dumps
exit 1
}
run_integration_profile integration-tests-set-2 \
-DargLine="-Xms1g -Xmx2g -Dlog4j2.configurationFile=log4j2.xml"
# Keep this lane to the shared profiles that have shown a local wall-clock win.
shared_profiles=(
shared-no-override-offline-cluster-integration-test-suite
shared-realtime-manager-cluster-integration-test-suite
shared-controller-only-cluster-integration-test-suite
shared-offline-cluster-integration-test-suite
)
for shared_profile in "${shared_profiles[@]}"; do
run_integration_profile "${shared_profile}"
done
exit 0
fi

Expand Down
754 changes: 754 additions & 0 deletions pinot-integration-tests/INTEGRATION_TEST_SETUP_GROUPS.md

Large diffs are not rendered by default.

1,253 changes: 1,253 additions & 0 deletions pinot-integration-tests/pom.xml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
/**
* Tests that the controller, broker and server admin consoles return the expected pages.
*/
public class AdminConsoleIntegrationTest extends BaseClusterIntegrationTest {
public class AdminConsoleIntegrationTest extends SharedRichClusterIntegrationTest {

@BeforeClass
public void setUp()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
* Shared set of common tests for cluster integration tests.
* <p>To enable the test, override it and add @Test annotation.
*/
public abstract class BaseClusterIntegrationTestSet extends BaseClusterIntegrationTest {
public abstract class BaseClusterIntegrationTestSet extends SharedRichClusterIntegrationTest {
private static final Logger LOGGER = LoggerFactory.getLogger(BaseClusterIntegrationTestSet.class);

// Default settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ protected TableConfig createDedupTableWithReplicas(File sampleAvroFile, String p
public void tearDown()
throws IOException {
dropRealtimeTable(getTableName());
dropRealtimeTable(DEDUP_TABLE_WITH_REPLICAS);
deleteSchema(getTableName());
deleteSchema(DEDUP_TABLE_WITH_REPLICAS);
stopServer();
stopBroker();
stopController();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
Expand All @@ -33,6 +34,7 @@
import org.apache.pinot.minion.executor.PinotTaskExecutor;
import org.apache.pinot.spi.env.PinotConfiguration;
import org.apache.pinot.spi.utils.JsonUtils;
import org.apache.pinot.spi.utils.builder.TableNameBuilder;
import org.apache.pinot.tools.BootstrapTableTool;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
Expand All @@ -48,13 +50,16 @@
* Integration test that provides example of {@link PinotTaskGenerator} and {@link PinotTaskExecutor} and tests simple
* minion functionality.
*/
public class BasicAuthBatchIntegrationTest extends ClusterTest {
public class BasicAuthBatchIntegrationTest extends SharedRichClusterIntegrationTest {
private static final String BOOTSTRAP_DATA_DIR = "/examples/batch/baseballStats";
private static final String TABLE_NAME = "baseballStats";
private static final String SCHEMA_FILE = "baseballStats_schema.json";
private static final String CONFIG_FILE = "baseballStats_offline_table_config.json";
private static final String DATA_FILE = "baseballStats_data.csv";
private static final String JOB_FILE = "ingestionJobSpec.yaml";

private File _quickstartTmpDir;

@BeforeClass
public void setUp()
throws Exception {
Expand All @@ -65,16 +70,24 @@ public void setUp()
startBroker();
startServer();
startMinion();

cleanUpTableAndSchema();
}

@AfterClass(alwaysRun = true)
public void tearDown()
throws Exception {
stopMinion();
stopServer();
stopBroker();
stopController();
stopZk();
Exception exception = null;
exception = runCleanup(exception, this::cleanUpTableAndSchema);
exception = runCleanup(exception, this::cleanUpQuickstartTmpDir);
exception = runCleanup(exception, this::stopMinionIfStarted);
exception = runCleanup(exception, this::stopServerIfStarted);
exception = runCleanup(exception, this::stopBrokerIfStarted);
exception = runCleanup(exception, this::stopControllerIfStarted);
exception = runCleanup(exception, this::stopZk);
if (exception != null) {
throw exception;
}
}

@Override
Expand All @@ -97,6 +110,31 @@ protected void overrideMinionConf(PinotConfiguration minionConf) {
BasicAuthTestUtils.addMinionConfiguration(minionConf);
}

@Override
protected Map<String, String> getControllerRequestClientHeaders() {
return AUTH_HEADER;
}

@Override
protected boolean shouldStartSharedKafka() {
return false;
}

@Override
protected int getSharedNumBrokers() {
return 1;
}

@Override
protected int getSharedNumServers() {
return 1;
}

@Override
protected boolean shouldStartSharedMinion() {
return true;
}

@Test
public void testBrokerNoAuth() {
try {
Expand Down Expand Up @@ -139,51 +177,129 @@ public void testControllerGetTablesNoAuth() {
@Test
public void testIngestionBatch()
throws Exception {
File quickstartTmpDir = new File(FileUtils.getTempDirectory(), String.valueOf(System.currentTimeMillis()));
FileUtils.forceDeleteOnExit(quickstartTmpDir);
cleanUpQuickstartTmpDir();
_quickstartTmpDir =
new File(FileUtils.getTempDirectory(), getClass().getSimpleName() + "-" + System.currentTimeMillis());
FileUtils.forceDeleteOnExit(_quickstartTmpDir);

File baseDir = new File(quickstartTmpDir, "baseballStats");
File baseDir = new File(_quickstartTmpDir, TABLE_NAME);
File dataDir = new File(baseDir, "rawdata");
File schemaFile = new File(baseDir, SCHEMA_FILE);
File configFile = new File(baseDir, CONFIG_FILE);
File dataFile = new File(dataDir, DATA_FILE);
File jobFile = new File(baseDir, JOB_FILE);
Preconditions.checkState(dataDir.mkdirs());
try {
Preconditions.checkState(dataDir.mkdirs());

FileUtils.copyURLToFile(getClass().getResource(BOOTSTRAP_DATA_DIR + "/" + SCHEMA_FILE), schemaFile);
FileUtils.copyURLToFile(getClass().getResource(BOOTSTRAP_DATA_DIR + "/" + CONFIG_FILE), configFile);
FileUtils.copyURLToFile(getClass().getResource(BOOTSTRAP_DATA_DIR + "/rawdata/" + DATA_FILE), dataFile);
FileUtils.copyURLToFile(getClass().getResource(BOOTSTRAP_DATA_DIR + "/" + JOB_FILE), jobFile);
FileUtils.copyURLToFile(getClass().getResource(BOOTSTRAP_DATA_DIR + "/" + SCHEMA_FILE), schemaFile);
FileUtils.copyURLToFile(getClass().getResource(BOOTSTRAP_DATA_DIR + "/" + CONFIG_FILE), configFile);
FileUtils.copyURLToFile(getClass().getResource(BOOTSTRAP_DATA_DIR + "/rawdata/" + DATA_FILE), dataFile);
FileUtils.copyURLToFile(getClass().getResource(BOOTSTRAP_DATA_DIR + "/" + JOB_FILE), jobFile);

// patch ingestion job file
String jobFileContents = IOUtils.toString(new FileInputStream(jobFile));
IOUtils.write(jobFileContents.replaceAll("9000", String.valueOf(getControllerPort())),
new FileOutputStream(jobFile));
// patch ingestion job file
String jobFileContents;
try (FileInputStream inputStream = new FileInputStream(jobFile)) {
jobFileContents = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
}
try (FileOutputStream outputStream = new FileOutputStream(jobFile)) {
IOUtils.write(jobFileContents.replaceAll("9000", String.valueOf(getControllerPort())), outputStream,
StandardCharsets.UTF_8);
}

new BootstrapTableTool("http", "localhost", getControllerPort(), baseDir.getAbsolutePath(),
AuthProviderUtils.makeAuthProvider(AUTH_TOKEN)).execute();
new BootstrapTableTool("http", "localhost", getControllerPort(), baseDir.getAbsolutePath(),
AuthProviderUtils.makeAuthProvider(AUTH_TOKEN)).execute();

Thread.sleep(5000);
Thread.sleep(5000);

// admin with full access
JsonNode response = JsonUtils.stringToJsonNode(
// admin with full access
JsonNode response = JsonUtils.stringToJsonNode(
sendPostRequest("http://localhost:" + getRandomBrokerPort() + "/query/sql",
"{\"sql\":\"SELECT count(*) FROM baseballStats\"}", AUTH_HEADER));
Assert.assertEquals(response.get("resultTable").get("dataSchema").get("columnDataTypes").get(0).asText(), "LONG",
"must return result with LONG value");
Assert.assertEquals(response.get("resultTable").get("dataSchema").get("columnNames").get(0).asText(), "count(*)",
"must return column name 'count(*)");
Assert.assertEquals(response.get("resultTable").get("rows").get(0).get(0).asInt(), 97889,
"must return row count 97889");
Assert.assertTrue(response.get("exceptions").isEmpty(), "must not return exception");

// user with valid auth but no table access - must return 403
try {
sendPostRequest("http://localhost:" + getRandomBrokerPort() + "/query/sql",
"{\"sql\":\"SELECT count(*) FROM baseballStats\"}", AUTH_HEADER));
Assert.assertEquals(response.get("resultTable").get("dataSchema").get("columnDataTypes").get(0).asText(), "LONG",
"must return result with LONG value");
Assert.assertEquals(response.get("resultTable").get("dataSchema").get("columnNames").get(0).asText(), "count(*)",
"must return column name 'count(*)");
Assert.assertEquals(response.get("resultTable").get("rows").get(0).get(0).asInt(), 97889,
"must return row count 97889");
Assert.assertTrue(response.get("exceptions").isEmpty(), "must not return exception");
"{\"sql\":\"SELECT count(*) FROM baseballStats\"}", AUTH_HEADER_USER);
} catch (IOException e) {
HttpErrorStatusException httpErrorStatusException = (HttpErrorStatusException) e.getCause();
Assert.assertEquals(httpErrorStatusException.getStatusCode(), 403, "must return 403");
}
} finally {
cleanUpQuickstartTmpDir();
}
}

// user with valid auth but no table access - must return 403
private void cleanUpTableAndSchema()
throws Exception {
if (_helixResourceManager == null) {
return;
}

String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(TABLE_NAME);
if (_helixResourceManager.getAllTables().contains(offlineTableName) || _helixResourceManager.hasOfflineTable(
TABLE_NAME)) {
dropOfflineTable(TABLE_NAME);
waitForTableDataManagerRemoved(offlineTableName);
waitForEVToDisappear(offlineTableName);
}
if (_helixResourceManager.getSchema(TABLE_NAME) != null) {
deleteSchema(TABLE_NAME);
}
}

private void cleanUpQuickstartTmpDir()
throws IOException {
if (_quickstartTmpDir != null) {
FileUtils.deleteDirectory(_quickstartTmpDir);
_quickstartTmpDir = null;
}
}

private void stopMinionIfStarted() {
if (_minionStarter != null) {
stopMinion();
}
}

private void stopServerIfStarted() {
if (!_serverStarters.isEmpty()) {
stopServer();
}
}

private void stopBrokerIfStarted() {
if (!_brokerStarters.isEmpty()) {
stopBroker();
}
}

private void stopControllerIfStarted() {
if (_controllerStarter != null) {
stopController();
}
}

private Exception runCleanup(Exception firstException, Cleanup cleanup) {
try {
sendPostRequest("http://localhost:" + getRandomBrokerPort() + "/query/sql",
"{\"sql\":\"SELECT count(*) FROM baseballStats\"}", AUTH_HEADER_USER);
} catch (IOException e) {
HttpErrorStatusException httpErrorStatusException = (HttpErrorStatusException) e.getCause();
Assert.assertEquals(httpErrorStatusException.getStatusCode(), 403, "must return 403");
cleanup.run();
} catch (Exception e) {
if (firstException == null) {
return e;
}
firstException.addSuppressed(e);
}
return firstException;
}

private interface Cleanup {
void run()
throws Exception;
}
}
Loading
Loading