Skip to content

Commit 174807b

Browse files
authored
Update S3-related tests to run in Testcontainers (#117)
Fix S3-related tests from #86: - Add `MinIOContainer` and `S3Application` to manage MinIO - Generate fixtures via `SmallDataFactory` (prefer composition over inheritance) - Migrate `CloudAccessTest` and `S3SelectTest` to TestContainers suite. - Pre-bake test `s3`/`s3-invalid` PXF server configs into image. - Move S3 tests from the singlecluster to the testcontainers pipeline (`pxf-s3`) and delete the unused legacy MinIO scripting. - Replace s3a client with aws S3 client in tests data management.
1 parent f3b92e6 commit 174807b

21 files changed

Lines changed: 761 additions & 725 deletions

File tree

.github/workflows/pxf-ci.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,6 @@ jobs:
330330
- jdbc
331331
- proxy
332332
- unused
333-
- s3
334333
- features
335334
- gpdb
336335
- gpdb_fdw
@@ -501,7 +500,6 @@ jobs:
501500
- jdbc
502501
- proxy
503502
- unused
504-
- s3
505503
- features
506504
- gpdb
507505
- gpdb_fdw
@@ -659,6 +657,7 @@ jobs:
659657
matrix:
660658
tc_group:
661659
- 'pxf-jdbc'
660+
- 'pxf-s3'
662661
use_fdw:
663662
- 'false'
664663
- 'true'

automation/Makefile

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,6 @@ endif
3535
# lowercase the protocol
3636
PROTOCOL := $(shell echo $(PROTOCOL) | tr A-Z a-z)
3737

38-
ifeq "$(PROTOCOL)" "minio"
39-
MINIO=true
40-
PROTOCOL=s3
41-
endif
42-
4338
PXF_BASE ?= $(PXF_HOME)
4439
TEMPLATES_DIR=$(PXF_HOME)/templates
4540

@@ -193,21 +188,6 @@ ifneq "$(PROTOCOL)" ""
193188
cp $(TEMPLATES_DIR)/pxf-site.xml $(PROTOCOL_HOME)/; \
194189
sed $(SED_OPTS) 's|</configuration>|<property><name>pxf.fs.basePath</name><value>$(BASE_PATH)</value></property></configuration>|g' $(PROTOCOL_HOME)/pxf-site.xml; \
195190
fi; \
196-
if [ $(PROTOCOL) = s3 ]; then \
197-
if [ "$(MINIO)" = "true" ]; then \
198-
cp $(TEMPLATES_DIR)/minio-site.xml $(PROTOCOL_HOME)/$(PROTOCOL)-site.xml; \
199-
sed $(SED_OPTS) "s|YOUR_MINIO_URL|http://localhost:9000|" $(PROTOCOL_HOME)/$(PROTOCOL)-site.xml; \
200-
fi; \
201-
mkdir -p $(PROTOCOL_HOME)-invalid; \
202-
cp $(TEMPLATES_DIR)/$(PROTOCOL)-site.xml $(PROTOCOL_HOME)-invalid/; \
203-
if [ -z "$(ACCESS_KEY_ID)" ] || [ -z "$(SECRET_ACCESS_KEY)" ]; then \
204-
echo "AWS Keys (ACCESS_KEY_ID, SECRET_ACCESS_KEY) not set"; \
205-
rm -rf $(PROTOCOL_HOME); \
206-
exit 1; \
207-
fi; \
208-
sed $(SED_OPTS) "s|YOUR_AWS_ACCESS_KEY_ID|$(ACCESS_KEY_ID)|" $(PROTOCOL_HOME)/$(PROTOCOL)-site.xml; \
209-
sed $(SED_OPTS) "s|YOUR_AWS_SECRET_ACCESS_KEY|$(SECRET_ACCESS_KEY)|" $(PROTOCOL_HOME)/$(PROTOCOL)-site.xml; \
210-
fi; \
211191
if [ $(PROTOCOL) = abfss ]; then \
212192
if [ -z "$(ABFSS_ACCOUNT)" ] || [ -z "$(ABFSS_CLIENT_ENDPOINT)" ] || [ -z "$(ABFSS_CLIENT_ID)" ] || [ -z "$(ABFSS_CLIENT_SECRET)" ]; then \
213193
echo "ADL Keys (ABFSS_ACCOUNT, ABFSS_CLIENT_ID, ABFSS_CLIENT_SECRET, ABFSS_CLIENT_ENDPOINT) not set"; \
@@ -255,6 +235,7 @@ endif
255235
# Usage:
256236
# make test-tc => run all testcontainers tests (Ubuntu)
257237
# make test-tc TC_GROUP=pxf-jdbc => run only pxf-jdbc group
238+
# make test-tc TC_GROUP=pxf-s3 => run S3 Select + CloudAccess tests (MinIO sidecar)
258239
# make test-tc DISTRO=rocky9 => run with Rocky Linux 9 base image
259240
.PHONY: test-tc
260241
test-tc: check-env symlink_pxf_jars pxf_regress

automation/pom.xml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<orc.version>1.6.13</orc.version>
2424
<snappy.java.version>1.1.10.4</snappy.java.version>
2525
<powermock.version>1.6.4</powermock.version>
26+
<argLine.extra></argLine.extra>
2627
</properties>
2728

2829
<repositories>
@@ -59,7 +60,7 @@
5960
<version>2.15</version>
6061
<configuration>
6162
<testFailureIgnore>true</testFailureIgnore>
62-
<argLine>-Xmx4096m</argLine>
63+
<argLine>-Xmx4096m ${argLine.extra}</argLine>
6364
<forkCount>1</forkCount>
6465
<reuseForks>false</reuseForks>
6566
</configuration>
@@ -535,4 +536,16 @@
535536
<version>1.9.5</version>
536537
</dependency>
537538
</dependencies>
539+
540+
<profiles>
541+
<profile>
542+
<id>java9-plus</id>
543+
<activation>
544+
<jdk>[9,)</jdk>
545+
</activation>
546+
<properties>
547+
<argLine.extra>--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED</argLine.extra>
548+
</properties>
549+
</profile>
550+
</profiles>
538551
</project>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package org.apache.cloudberry.pxf.automation.applications;
2+
3+
/*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
import com.amazonaws.auth.AWSStaticCredentialsProvider;
23+
import com.amazonaws.auth.BasicAWSCredentials;
24+
import com.amazonaws.client.builder.AwsClientBuilder;
25+
import com.amazonaws.services.s3.AmazonS3;
26+
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
27+
import com.amazonaws.services.s3.model.DeleteObjectsRequest;
28+
import com.amazonaws.services.s3.model.ListObjectsV2Request;
29+
import com.amazonaws.services.s3.model.ListObjectsV2Result;
30+
import com.amazonaws.services.s3.model.PutObjectRequest;
31+
import com.amazonaws.services.s3.model.S3ObjectSummary;
32+
import org.apache.cloudberry.pxf.automation.testcontainers.MinIOContainer;
33+
34+
import java.io.IOException;
35+
import java.nio.file.Path;
36+
import java.util.ArrayList;
37+
import java.util.List;
38+
39+
/**
40+
* S3 API access wrapper used by automation tests to seed and clean fixtures
41+
* in a MinIO bucket. Owns the AmazonS3 client; callers should call
42+
* `shutdown()` when done (typically in afterClass before stopping the
43+
* MinIO container).
44+
*/
45+
public class S3Application implements AutoCloseable {
46+
47+
private final AmazonS3 s3Client;
48+
49+
public S3Application(MinIOContainer minio) {
50+
this.s3Client = buildS3Client(minio.getHostEndpoint(), minio.getAccessKey(), minio.getSecretKey());
51+
}
52+
53+
public void createBucket(String bucket) {
54+
if (!s3Client.doesBucketExistV2(bucket)) {
55+
s3Client.createBucket(bucket);
56+
}
57+
}
58+
59+
public void putObject(String bucket, String key, Path localFile) throws IOException {
60+
s3Client.putObject(new PutObjectRequest(bucket, key, localFile.toFile()));
61+
}
62+
63+
public void deletePrefix(String bucket, String prefix) {
64+
ListObjectsV2Request request = new ListObjectsV2Request()
65+
.withBucketName(bucket)
66+
.withPrefix(prefix);
67+
ListObjectsV2Result listing;
68+
do {
69+
listing = s3Client.listObjectsV2(request);
70+
List<String> keys = new ArrayList<>();
71+
for (S3ObjectSummary summary : listing.getObjectSummaries()) {
72+
keys.add(summary.getKey());
73+
}
74+
if (!keys.isEmpty()) {
75+
s3Client.deleteObjects(new DeleteObjectsRequest(bucket).withKeys(keys.toArray(new String[0])));
76+
}
77+
request.setContinuationToken(listing.getNextContinuationToken());
78+
} while (listing.isTruncated());
79+
}
80+
81+
public void shutdown() {
82+
s3Client.shutdown();
83+
}
84+
85+
@Override
86+
public void close() {
87+
shutdown();
88+
}
89+
90+
private static AmazonS3 buildS3Client(String endpoint, String accessKey, String secretKey) {
91+
return AmazonS3ClientBuilder.standard()
92+
.withEndpointConfiguration(new AwsClientBuilder.EndpointConfiguration(endpoint, "us-east-1"))
93+
.withPathStyleAccessEnabled(true)
94+
.withCredentials(new AWSStaticCredentialsProvider(
95+
new BasicAWSCredentials(accessKey, secretKey)))
96+
.build();
97+
}
98+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package org.apache.cloudberry.pxf.automation.testcontainers;
2+
3+
/*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
import org.testcontainers.containers.GenericContainer;
23+
import org.testcontainers.containers.Network;
24+
import org.testcontainers.containers.wait.strategy.Wait;
25+
import org.testcontainers.utility.DockerImageName;
26+
27+
/**
28+
* TestContainers wrapper around MinIO for S3 / S3 Select automation tests.
29+
* The container joins a shared Docker network with alias minio, so PXF inside the
30+
* Cloudberry container can reach it at http://minio:9000.
31+
*
32+
* This class only manages the container lifecycle and exposes endpoint /
33+
* credential accessors. S3 API access (buckets, objects) lives in
34+
* {@link org.apache.cloudberry.pxf.automation.applications.S3Application}.
35+
*/
36+
public class MinIOContainer extends GenericContainer<MinIOContainer> {
37+
38+
private static final String DEFAULT_IMAGE = "minio/minio:RELEASE.2024-11-07T00-52-20Z";
39+
private static final String NETWORK_ALIAS = "minio";
40+
41+
public static final int API_PORT = 9000;
42+
public static final int CONSOLE_PORT = 9001;
43+
44+
public static final String ACCESS_KEY = "admin";
45+
public static final String SECRET_KEY = "password";
46+
public static final String DEFAULT_BUCKET = "gpdb-ud-scratch";
47+
48+
public MinIOContainer(Network network) {
49+
super(DockerImageName.parse(DEFAULT_IMAGE));
50+
51+
withNetwork(network)
52+
.withNetworkAliases(NETWORK_ALIAS)
53+
.withExposedPorts(API_PORT, CONSOLE_PORT)
54+
.withEnv("MINIO_ROOT_USER", ACCESS_KEY)
55+
.withEnv("MINIO_ROOT_PASSWORD", SECRET_KEY)
56+
.withEnv("MINIO_API_SELECT_PARQUET", "on")
57+
.withCommand("server", "/data", "--console-address", ":" + CONSOLE_PORT)
58+
.waitingFor(Wait.forHttp("/minio/health/live").forPort(API_PORT));
59+
}
60+
61+
/** S3 API endpoint reachable from the test JVM (mapped port). */
62+
public String getHostEndpoint() {
63+
return "http://localhost:" + getMappedPort(API_PORT);
64+
}
65+
66+
/** S3 API endpoint for PXF and other containers on the same Docker network. */
67+
public String getInternalEndpoint() {
68+
return "http://" + NETWORK_ALIAS + ":" + API_PORT;
69+
}
70+
71+
public String getAccessKey() {
72+
return ACCESS_KEY;
73+
}
74+
75+
public String getSecretKey() {
76+
return SECRET_KEY;
77+
}
78+
}

automation/src/main/java/org/apache/cloudberry/pxf/automation/testcontainers/PXFCloudberryContainer.java

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
import org.testcontainers.containers.wait.strategy.Wait;
3232
import org.testcontainers.utility.DockerImageName;
3333
import org.testcontainers.utility.MountableFile;
34+
import org.apache.cloudberry.pxf.automation.utils.AutomationUtils;
3435

35-
import java.io.File;
3636
import java.io.IOException;
3737
import java.nio.charset.StandardCharsets;
3838
import java.nio.file.Files;
@@ -137,7 +137,7 @@ private static String resolveDistro() {
137137
*/
138138
public static synchronized PXFCloudberryContainer getInstance() {
139139
if (instance == null) {
140-
String repo = resolveProperty("pxf.test.repo.path", findRepoPath());
140+
String repo = resolveProperty("pxf.test.repo.path", AutomationUtils.findRepoRoot().toString());
141141
String distro = resolveDistro();
142142
String imageName = "pxf/cbdb-testcontainer-" + distro + ":1";
143143
String baseImage = BASE_IMAGES.getOrDefault(distro, BASE_IMAGES.get("ubuntu"));
@@ -204,21 +204,6 @@ private static String resolveProperty(String key, String fallback) {
204204
return (value != null && !value.isEmpty()) ? value : fallback;
205205
}
206206

207-
private static String findRepoPath() {
208-
File dir = new File(System.getProperty("user.dir"));
209-
for (int i = 0; i < 5; i++) {
210-
if (new File(dir, "automation/pom.xml").exists()) {
211-
return dir.getAbsolutePath();
212-
}
213-
dir = dir.getParentFile();
214-
if (dir == null)
215-
break;
216-
}
217-
throw new IllegalStateException(
218-
"Cannot auto-detect cloudberry-pxf repo root. Set -Dpxf.test.repo.path=...");
219-
}
220-
221-
222207
public Network getSharedNetwork() {
223208
return network;
224209
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package org.apache.cloudberry.pxf.automation.utils;
2+
3+
/*
4+
* Licensed to the Apache Software Foundation (ASF) under one
5+
* or more contributor license agreements. See the NOTICE file
6+
* distributed with this work for additional information
7+
* regarding copyright ownership. The ASF licenses this file
8+
* to you under the Apache License, Version 2.0 (the
9+
* "License"); you may not use this file except in compliance
10+
* with the License. You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing,
15+
* software distributed under the License is distributed on an
16+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
* KIND, either express or implied. See the License for the
18+
* specific language governing permissions and limitations
19+
* under the License.
20+
*/
21+
22+
import java.io.File;
23+
import java.nio.file.Path;
24+
25+
/**
26+
* Shared helpers for the cloudberry-pxf automation suite.
27+
*/
28+
public final class AutomationUtils {
29+
30+
private static final String REPO_MARKER = "automation/pom.xml";
31+
private static final int MAX_PARENT_HOPS = 6;
32+
33+
private AutomationUtils() {
34+
}
35+
36+
/**
37+
* Walks upward from {@code user.dir} looking for the cloudberry-pxf repo
38+
* root (identified by the {@code automation/pom.xml} marker). Used by code
39+
* that needs to read resources from the working tree at runtime.
40+
*
41+
* @throws IllegalStateException if the marker is not found within a handful
42+
* of parent directories. Set {@code -Dpxf.test.repo.path=...} to bypass.
43+
*/
44+
public static Path findRepoRoot() {
45+
File dir = new File(System.getProperty("user.dir"));
46+
for (int i = 0; i < MAX_PARENT_HOPS; i++) {
47+
if (new File(dir, REPO_MARKER).exists()) {
48+
return dir.toPath().toAbsolutePath().normalize();
49+
}
50+
dir = dir.getParentFile();
51+
if (dir == null) {
52+
break;
53+
}
54+
}
55+
throw new IllegalStateException(
56+
"Cannot auto-detect cloudberry-pxf repo root from user.dir="
57+
+ System.getProperty("user.dir")
58+
+ ". Set -Dpxf.test.repo.path=... to override.");
59+
}
60+
}

0 commit comments

Comments
 (0)