Skip to content

Commit 839bc25

Browse files
authored
Integration tests: improve support for temporary folders (#987)
3 integration tests currently require a temporary folder for various purposes. The folders are generally hard-coded to some local filesystem location, which makes those tests impossible to execute against a remote Polaris instance. This PR aims at fixing this situation by leveraging a new optional environment variable that points to a location that is accessible by both the server and the client running the tests. This would typically be an S3 bucket, for example, but could also be a shared volume mounted on both machines at the same mount point. If this environment variable is not present, then local execution is assumed and a temporary directory managed by JUnit is used instead.
1 parent 11cf175 commit 839bc25

File tree

6 files changed

+170
-43
lines changed

6 files changed

+170
-43
lines changed

integration-tests/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ dependencies {
6262

6363
implementation(platform(libs.junit.bom))
6464
implementation("org.junit.jupiter:junit-jupiter")
65+
implementation("org.junit.jupiter:junit-jupiter-api")
6566
compileOnly("org.junit.jupiter:junit-jupiter-engine")
6667
implementation(libs.assertj.core)
6768
implementation(libs.mockito.core)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.polaris.service.it.env;
20+
21+
import com.google.common.annotations.VisibleForTesting;
22+
import java.net.URI;
23+
import java.nio.file.Path;
24+
import java.util.function.Function;
25+
26+
public final class IntegrationTestsHelper {
27+
28+
/**
29+
* The environment variable that can be used to override the temporary directory used by the
30+
* integration tests.
31+
*/
32+
public static final String INTEGRATION_TEST_TEMP_DIR_ENV_VAR = "INTEGRATION_TEST_TEMP_DIR";
33+
34+
private IntegrationTestsHelper() {}
35+
36+
/**
37+
* Get the temporary directory to use for integration tests.
38+
*
39+
* <p>If the environment variable {@link #INTEGRATION_TEST_TEMP_DIR_ENV_VAR} is set, it will be
40+
* used as the temporary directory. Otherwise, the default local temporary directory will be used.
41+
*
42+
* <p>The environment variable should be a URI, e.g. {@code "file:///tmp/polaris"} or {@code
43+
* "s3://bucket/polaris"}. If the URI does not have a scheme, it will be assumed to be a local
44+
* file URI.
45+
*/
46+
public static URI getTemporaryDirectory(Path defaultLocalDirectory) {
47+
return getTemporaryDirectory(System::getenv, defaultLocalDirectory);
48+
}
49+
50+
@VisibleForTesting
51+
static URI getTemporaryDirectory(Function<String, String> getenv, Path defaultLocalDirectory) {
52+
String envVar = getenv.apply(INTEGRATION_TEST_TEMP_DIR_ENV_VAR);
53+
envVar = envVar != null ? envVar : defaultLocalDirectory.toString();
54+
envVar = envVar.startsWith("/") ? "file://" + envVar : envVar;
55+
return URI.create(envVar + "/").normalize();
56+
}
57+
}

integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisApplicationIntegrationTest.java

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,12 @@
2929
import jakarta.ws.rs.core.Response;
3030
import jakarta.ws.rs.core.Response.Status;
3131
import java.io.IOException;
32-
import java.nio.file.Files;
32+
import java.net.URI;
3333
import java.nio.file.Path;
3434
import java.time.Duration;
3535
import java.time.temporal.ChronoUnit;
3636
import java.util.List;
3737
import java.util.Map;
38-
import org.apache.commons.io.FileUtils;
3938
import org.apache.hadoop.conf.Configuration;
4039
import org.apache.iceberg.BaseTable;
4140
import org.apache.iceberg.PartitionData;
@@ -75,6 +74,7 @@
7574
import org.apache.polaris.core.entity.PolarisEntityConstants;
7675
import org.apache.polaris.service.it.env.ClientCredentials;
7776
import org.apache.polaris.service.it.env.ClientPrincipal;
77+
import org.apache.polaris.service.it.env.IntegrationTestsHelper;
7878
import org.apache.polaris.service.it.env.PolarisApiEndpoints;
7979
import org.apache.polaris.service.it.env.PolarisClient;
8080
import org.apache.polaris.service.it.env.RestApi;
@@ -87,6 +87,7 @@
8787
import org.junit.jupiter.api.Test;
8888
import org.junit.jupiter.api.TestInfo;
8989
import org.junit.jupiter.api.extension.ExtendWith;
90+
import org.junit.jupiter.api.io.TempDir;
9091
import org.junit.jupiter.params.ParameterizedTest;
9192
import org.junit.jupiter.params.provider.ValueSource;
9293

@@ -109,7 +110,6 @@ public class PolarisApplicationIntegrationTest {
109110

110111
public static final String PRINCIPAL_ROLE_ALL = "PRINCIPAL_ROLE:ALL";
111112

112-
private static Path testDir;
113113
private static String realm;
114114

115115
private static RestApi managementApi;
@@ -118,25 +118,22 @@ public class PolarisApplicationIntegrationTest {
118118
private static ClientCredentials clientCredentials;
119119
private static ClientPrincipal admin;
120120
private static String authToken;
121+
private static URI baseLocation;
121122

122123
private String principalRoleName;
123124
private String internalCatalogName;
124125

125126
@BeforeAll
126-
public static void setup(PolarisApiEndpoints apiEndpoints, ClientPrincipal adminCredentials)
127-
throws IOException {
127+
public static void setup(
128+
PolarisApiEndpoints apiEndpoints, ClientPrincipal adminCredentials, @TempDir Path tempDir) {
128129
endpoints = apiEndpoints;
129130
client = polarisClient(endpoints);
130131
realm = endpoints.realmId();
131132
admin = adminCredentials;
132133
clientCredentials = adminCredentials.credentials();
133134
authToken = client.obtainToken(clientCredentials);
134-
135-
testDir = Path.of("build/test_data/iceberg/" + realm);
136-
FileUtils.deleteQuietly(testDir.toFile());
137-
Files.createDirectories(testDir);
138-
139135
managementApi = client.managementApi(clientCredentials);
136+
baseLocation = IntegrationTestsHelper.getTemporaryDirectory(tempDir).resolve(realm + "/");
140137
}
141138

142139
@AfterAll
@@ -443,19 +440,17 @@ public void testIcebergRegisterTableInExternalCatalog() throws IOException {
443440
Catalog.TypeEnum.EXTERNAL,
444441
principalRoleName,
445442
FileStorageConfigInfo.builder(StorageConfigInfo.StorageTypeEnum.FILE)
446-
.setAllowedLocations(List.of("file://" + testDir.toFile().getAbsolutePath()))
443+
.setAllowedLocations(List.of(baseLocation.toString()))
447444
.build(),
448-
"file://" + testDir.toFile().getAbsolutePath());
445+
baseLocation.toString());
449446
try (RESTSessionCatalog sessionCatalog = newSessionCatalog(catalogName);
450447
HadoopFileIO fileIo = new HadoopFileIO(new Configuration())) {
451448
SessionCatalog.SessionContext sessionContext = SessionCatalog.SessionContext.createEmpty();
452449
Namespace ns = Namespace.of("db1");
453450
sessionCatalog.createNamespace(sessionContext, ns);
454451
TableIdentifier tableIdentifier = TableIdentifier.of(ns, "the_table");
455452
String location =
456-
"file://"
457-
+ testDir.toFile().getAbsolutePath()
458-
+ "/testIcebergRegisterTableInExternalCatalog";
453+
baseLocation.resolve("testIcebergRegisterTableInExternalCatalog").toString();
459454
String metadataLocation = location + "/metadata/000001-494949494949494949.metadata.json";
460455

461456
TableMetadata tableMetadata =
@@ -489,19 +484,16 @@ public void testIcebergUpdateTableInExternalCatalog() throws IOException {
489484
Catalog.TypeEnum.EXTERNAL,
490485
principalRoleName,
491486
FileStorageConfigInfo.builder(StorageConfigInfo.StorageTypeEnum.FILE)
492-
.setAllowedLocations(List.of("file://" + testDir.toFile().getAbsolutePath()))
487+
.setAllowedLocations(List.of(baseLocation.toString()))
493488
.build(),
494-
"file://" + testDir.toFile().getAbsolutePath());
489+
baseLocation.toString());
495490
try (RESTSessionCatalog sessionCatalog = newSessionCatalog(catalogName);
496491
HadoopFileIO fileIo = new HadoopFileIO(new Configuration())) {
497492
SessionCatalog.SessionContext sessionContext = SessionCatalog.SessionContext.createEmpty();
498493
Namespace ns = Namespace.of("db1");
499494
sessionCatalog.createNamespace(sessionContext, ns);
500495
TableIdentifier tableIdentifier = TableIdentifier.of(ns, "the_table");
501-
String location =
502-
"file://"
503-
+ testDir.toFile().getAbsolutePath()
504-
+ "/testIcebergUpdateTableInExternalCatalog";
496+
String location = baseLocation.resolve("testIcebergUpdateTableInExternalCatalog").toString();
505497
String metadataLocation = location + "/metadata/000001-494949494949494949.metadata.json";
506498

507499
Types.NestedField col1 = Types.NestedField.of(1, false, "col1", Types.StringType.get());
@@ -541,20 +533,16 @@ public void testIcebergDropTableInExternalCatalog() throws IOException {
541533
Catalog.TypeEnum.EXTERNAL,
542534
principalRoleName,
543535
FileStorageConfigInfo.builder(StorageConfigInfo.StorageTypeEnum.FILE)
544-
.setAllowedLocations(List.of("file://" + testDir.toFile().getAbsolutePath()))
536+
.setAllowedLocations(List.of(baseLocation.toString()))
545537
.build(),
546-
"file://" + testDir.toFile().getAbsolutePath());
538+
baseLocation.toString());
547539
try (RESTSessionCatalog sessionCatalog = newSessionCatalog(catalogName);
548540
HadoopFileIO fileIo = new HadoopFileIO(new Configuration())) {
549541
SessionCatalog.SessionContext sessionContext = SessionCatalog.SessionContext.createEmpty();
550542
Namespace ns = Namespace.of("db1");
551543
sessionCatalog.createNamespace(sessionContext, ns);
552544
TableIdentifier tableIdentifier = TableIdentifier.of(ns, "the_table");
553-
String location =
554-
"file://"
555-
+ testDir.toFile().getAbsolutePath()
556-
+ "/"
557-
+ "testIcebergDropTableInExternalCatalog";
545+
String location = baseLocation.resolve("testIcebergDropTableInExternalCatalog").toString();
558546
String metadataLocation = location + "/metadata/000001-494949494949494949.metadata.json";
559547

560548
TableMetadata tableMetadata =

integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisRestCatalogIntegrationTest.java

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import java.lang.annotation.Retention;
3030
import java.lang.annotation.RetentionPolicy;
3131
import java.lang.reflect.Method;
32+
import java.net.URI;
33+
import java.nio.file.Path;
3234
import java.util.HashMap;
3335
import java.util.List;
3436
import java.util.Map;
@@ -79,6 +81,7 @@
7981
import org.apache.polaris.service.it.env.CatalogApi;
8082
import org.apache.polaris.service.it.env.ClientCredentials;
8183
import org.apache.polaris.service.it.env.IcebergHelper;
84+
import org.apache.polaris.service.it.env.IntegrationTestsHelper;
8285
import org.apache.polaris.service.it.env.ManagementApi;
8386
import org.apache.polaris.service.it.env.PolarisApiEndpoints;
8487
import org.apache.polaris.service.it.env.PolarisClient;
@@ -92,6 +95,7 @@
9295
import org.junit.jupiter.api.Test;
9396
import org.junit.jupiter.api.TestInfo;
9497
import org.junit.jupiter.api.extension.ExtendWith;
98+
import org.junit.jupiter.api.io.TempDir;
9599

96100
/**
97101
* Import the full core Iceberg catalog tests by hitting the REST service via the RESTCatalog
@@ -108,9 +112,9 @@ public class PolarisRestCatalogIntegrationTest extends CatalogTests<RESTCatalog>
108112
private static final String TEST_ROLE_ARN =
109113
Optional.ofNullable(System.getenv("INTEGRATION_TEST_ROLE_ARN"))
110114
.orElse("arn:aws:iam::123456789012:role/my-role");
111-
private static final String S3_BUCKET_BASE =
112-
Optional.ofNullable(System.getenv("INTEGRATION_TEST_S3_PATH"))
113-
.orElse("file:///tmp/buckets/my-bucket");
115+
116+
private static URI s3BucketBase;
117+
private static URI externalCatalogBase;
114118

115119
protected static final String VIEW_QUERY = "select * from ns1.layer1_table";
116120
private static String principalRoleName;
@@ -125,7 +129,7 @@ public class PolarisRestCatalogIntegrationTest extends CatalogTests<RESTCatalog>
125129
private String currentCatalogName;
126130

127131
private final String catalogBaseLocation =
128-
S3_BUCKET_BASE + "/" + System.getenv("USER") + "/path/to/data";
132+
s3BucketBase + "/" + System.getenv("USER") + "/path/to/data";
129133

130134
private static final String[] DEFAULT_CATALOG_PROPERTIES = {
131135
"allow.unstructured.table.location", "true",
@@ -148,7 +152,8 @@ String[] properties() default {
148152
}
149153

150154
@BeforeAll
151-
static void setup(PolarisApiEndpoints apiEndpoints, ClientCredentials credentials) {
155+
static void setup(
156+
PolarisApiEndpoints apiEndpoints, ClientCredentials credentials, @TempDir Path tempDir) {
152157
adminCredentials = credentials;
153158
endpoints = apiEndpoints;
154159
client = polarisClient(endpoints);
@@ -157,6 +162,9 @@ static void setup(PolarisApiEndpoints apiEndpoints, ClientCredentials credential
157162
principalRoleName = client.newEntityName("rest-admin");
158163
principalCredentials = managementApi.createPrincipalWithRole(principalName, principalRoleName);
159164
catalogApi = client.catalogApi(principalCredentials);
165+
URI testRootUri = IntegrationTestsHelper.getTemporaryDirectory(tempDir);
166+
s3BucketBase = testRootUri.resolve("my-bucket");
167+
externalCatalogBase = testRootUri.resolve("external-catalog");
160168
}
161169

162170
@AfterAll
@@ -192,7 +200,7 @@ public void before(TestInfo testInfo) {
192200
for (int i = 0; i < properties.length; i += 2) {
193201
catalogPropsBuilder.addProperty(properties[i], properties[i + 1]);
194202
}
195-
if (!S3_BUCKET_BASE.startsWith("file:/")) {
203+
if (!s3BucketBase.getScheme().equals("file")) {
196204
catalogPropsBuilder.addProperty(
197205
CatalogEntity.REPLACE_NEW_LOCATION_PREFIX_WITH_CATALOG_DEFAULT_KEY, "file:");
198206
}
@@ -202,7 +210,7 @@ public void before(TestInfo testInfo) {
202210
.setName(currentCatalogName)
203211
.setProperties(catalogPropsBuilder.build())
204212
.setStorageConfigInfo(
205-
S3_BUCKET_BASE.startsWith("file:/")
213+
s3BucketBase.getScheme().equals("file")
206214
? new FileStorageConfigInfo(
207215
StorageConfigInfo.StorageTypeEnum.FILE, List.of("file://"))
208216
: awsConfigModel)
@@ -541,12 +549,12 @@ public void testLoadTableWithAccessDelegationForExternalCatalogWithConfigDisable
541549
TableMetadata.newTableMetadata(
542550
new Schema(List.of(Types.NestedField.of(1, false, "col1", new Types.StringType()))),
543551
PartitionSpec.unpartitioned(),
544-
"file:///tmp/ns1/my_table",
552+
externalCatalogBase + "/ns1/my_table",
545553
Map.of());
546554
try (ResolvingFileIO resolvingFileIO = new ResolvingFileIO()) {
547555
resolvingFileIO.initialize(Map.of());
548556
resolvingFileIO.setConf(new Configuration());
549-
String fileLocation = "file:///tmp/ns1/my_table/metadata/v1.metadata.json";
557+
String fileLocation = externalCatalogBase + "/ns1/my_table/metadata/v1.metadata.json";
550558
TableMetadataParser.write(tableMetadata, resolvingFileIO.newOutputFile(fileLocation));
551559
restCatalog.registerTable(TableIdentifier.of(ns1, "my_table"), fileLocation);
552560
try {
@@ -576,12 +584,12 @@ public void testLoadTableWithoutAccessDelegationForExternalCatalogWithConfigDisa
576584
TableMetadata.newTableMetadata(
577585
new Schema(List.of(Types.NestedField.of(1, false, "col1", new Types.StringType()))),
578586
PartitionSpec.unpartitioned(),
579-
"file:///tmp/ns1/my_table",
587+
externalCatalogBase + "/ns1/my_table",
580588
Map.of());
581589
try (ResolvingFileIO resolvingFileIO = new ResolvingFileIO()) {
582590
resolvingFileIO.initialize(Map.of());
583591
resolvingFileIO.setConf(new Configuration());
584-
String fileLocation = "file:///tmp/ns1/my_table/metadata/v1.metadata.json";
592+
String fileLocation = externalCatalogBase + "/ns1/my_table/metadata/v1.metadata.json";
585593
TableMetadataParser.write(tableMetadata, resolvingFileIO.newOutputFile(fileLocation));
586594
restCatalog.registerTable(TableIdentifier.of(ns1, "my_table"), fileLocation);
587595
try {
@@ -610,12 +618,12 @@ public void testLoadTableWithAccessDelegationForExternalCatalogWithConfigEnabled
610618
TableMetadata.newTableMetadata(
611619
new Schema(List.of(Types.NestedField.of(1, false, "col1", new Types.StringType()))),
612620
PartitionSpec.unpartitioned(),
613-
"file:///tmp/ns1/my_table",
621+
externalCatalogBase + "/ns1/my_table",
614622
Map.of());
615623
try (ResolvingFileIO resolvingFileIO = new ResolvingFileIO()) {
616624
resolvingFileIO.initialize(Map.of());
617625
resolvingFileIO.setConf(new Configuration());
618-
String fileLocation = "file:///tmp/ns1/my_table/metadata/v1.metadata.json";
626+
String fileLocation = externalCatalogBase + "/ns1/my_table/metadata/v1.metadata.json";
619627
TableMetadataParser.write(tableMetadata, resolvingFileIO.newOutputFile(fileLocation));
620628
restCatalog.registerTable(TableIdentifier.of(ns1, "my_table"), fileLocation);
621629
try {

integration-tests/src/main/java/org/apache/polaris/service/it/test/PolarisSparkIntegrationTest.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import jakarta.ws.rs.client.Entity;
2828
import jakarta.ws.rs.core.Response;
2929
import java.io.IOException;
30+
import java.net.URI;
3031
import java.nio.file.Path;
3132
import java.time.Instant;
3233
import java.util.List;
@@ -41,6 +42,7 @@
4142
import org.apache.polaris.core.admin.model.StorageConfigInfo;
4243
import org.apache.polaris.service.it.env.CatalogApi;
4344
import org.apache.polaris.service.it.env.ClientCredentials;
45+
import org.apache.polaris.service.it.env.IntegrationTestsHelper;
4446
import org.apache.polaris.service.it.env.ManagementApi;
4547
import org.apache.polaris.service.it.env.PolarisApiEndpoints;
4648
import org.apache.polaris.service.it.env.PolarisClient;
@@ -82,7 +84,7 @@ public class PolarisSparkIntegrationTest {
8284
private String catalogName;
8385
private String externalCatalogName;
8486

85-
@TempDir public Path warehouseDir;
87+
private URI warehouseDir;
8688

8789
@BeforeAll
8890
public static void setup() throws IOException {
@@ -95,13 +97,16 @@ public static void cleanup() {
9597
}
9698

9799
@BeforeEach
98-
public void before(PolarisApiEndpoints apiEndpoints, ClientCredentials credentials) {
100+
public void before(
101+
PolarisApiEndpoints apiEndpoints, ClientCredentials credentials, @TempDir Path tempDir) {
99102
endpoints = apiEndpoints;
100103
client = polarisClient(endpoints);
101104
sparkToken = client.obtainToken(credentials);
102105
managementApi = client.managementApi(credentials);
103106
catalogApi = client.catalogApi(credentials);
104107

108+
warehouseDir = IntegrationTestsHelper.getTemporaryDirectory(tempDir).resolve("spark-warehouse");
109+
105110
catalogName = client.newEntityName("spark_catalog");
106111
externalCatalogName = client.newEntityName("spark_ext_catalog");
107112

0 commit comments

Comments
 (0)