Skip to content

Commit e548601

Browse files
authored
Convert FileIOIntegrationTest to a unit test (#598)
1 parent af7cf91 commit e548601

File tree

3 files changed

+288
-263
lines changed

3 files changed

+288
-263
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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.dropwizard;
20+
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
23+
import com.azure.core.exception.AzureException;
24+
import com.google.cloud.storage.StorageException;
25+
import jakarta.ws.rs.core.Response;
26+
import java.util.stream.Stream;
27+
import org.apache.polaris.service.exception.IcebergExceptionMapper;
28+
import org.junit.jupiter.params.ParameterizedTest;
29+
import org.junit.jupiter.params.provider.Arguments;
30+
import org.junit.jupiter.params.provider.MethodSource;
31+
import software.amazon.awssdk.services.s3.model.S3Exception;
32+
33+
class IcebergExceptionMapperTest {
34+
35+
static Stream<Arguments> fileIOExceptionMapping() {
36+
return Stream.of(
37+
Arguments.of(new AzureException("Unknown"), 500),
38+
Arguments.of(new AzureException("Forbidden"), 403),
39+
Arguments.of(new AzureException("FORBIDDEN"), 403),
40+
Arguments.of(new AzureException("Not Authorized"), 403),
41+
Arguments.of(new AzureException("Access Denied"), 403),
42+
Arguments.of(S3Exception.builder().message("Access denied").build(), 403),
43+
Arguments.of(new StorageException(1, "access denied"), 403));
44+
}
45+
46+
@ParameterizedTest
47+
@MethodSource
48+
void fileIOExceptionMapping(RuntimeException ex, int statusCode) {
49+
IcebergExceptionMapper mapper = new IcebergExceptionMapper();
50+
try (Response response = mapper.toResponse(ex)) {
51+
assertThat(response.getStatus()).isEqualTo(statusCode);
52+
assertThat(response.getEntity()).extracting("message").isEqualTo(ex.getMessage());
53+
}
54+
}
55+
}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
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.dropwizard.catalog.io;
20+
21+
import static org.apache.iceberg.types.Types.NestedField.required;
22+
import static org.assertj.core.api.Assertions.assertThat;
23+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
24+
25+
import com.azure.core.exception.AzureException;
26+
import com.google.auth.oauth2.AccessToken;
27+
import com.google.auth.oauth2.GoogleCredentials;
28+
import com.google.cloud.storage.StorageException;
29+
import jakarta.ws.rs.core.Response;
30+
import jakarta.ws.rs.core.SecurityContext;
31+
import java.security.Principal;
32+
import java.time.Instant;
33+
import java.util.Date;
34+
import java.util.List;
35+
import java.util.Optional;
36+
import java.util.Set;
37+
import java.util.stream.Stream;
38+
import org.apache.iceberg.Schema;
39+
import org.apache.iceberg.catalog.Namespace;
40+
import org.apache.iceberg.rest.requests.CreateNamespaceRequest;
41+
import org.apache.iceberg.rest.requests.CreateTableRequest;
42+
import org.apache.iceberg.types.Types;
43+
import org.apache.polaris.core.PolarisCallContext;
44+
import org.apache.polaris.core.PolarisDiagnostics;
45+
import org.apache.polaris.core.admin.model.Catalog;
46+
import org.apache.polaris.core.admin.model.CreateCatalogRequest;
47+
import org.apache.polaris.core.admin.model.FileStorageConfigInfo;
48+
import org.apache.polaris.core.admin.model.PolarisCatalog;
49+
import org.apache.polaris.core.admin.model.StorageConfigInfo;
50+
import org.apache.polaris.core.auth.AuthenticatedPolarisPrincipal;
51+
import org.apache.polaris.core.auth.PolarisAuthorizer;
52+
import org.apache.polaris.core.context.RealmContext;
53+
import org.apache.polaris.core.entity.PolarisEntity;
54+
import org.apache.polaris.core.entity.PrincipalEntity;
55+
import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
56+
import org.apache.polaris.core.persistence.PolarisMetaStoreSession;
57+
import org.apache.polaris.core.persistence.cache.EntityCache;
58+
import org.apache.polaris.service.admin.PolarisServiceImpl;
59+
import org.apache.polaris.service.admin.api.PolarisCatalogsApi;
60+
import org.apache.polaris.service.catalog.IcebergCatalogAdapter;
61+
import org.apache.polaris.service.catalog.api.IcebergRestCatalogApi;
62+
import org.apache.polaris.service.catalog.api.IcebergRestCatalogApiService;
63+
import org.apache.polaris.service.config.RealmEntityManagerFactory;
64+
import org.apache.polaris.service.context.CallContextCatalogFactory;
65+
import org.apache.polaris.service.context.PolarisCallContextCatalogFactory;
66+
import org.apache.polaris.service.persistence.InMemoryPolarisMetaStoreManagerFactory;
67+
import org.apache.polaris.service.storage.PolarisStorageIntegrationProviderImpl;
68+
import org.apache.polaris.service.task.TaskExecutor;
69+
import org.junit.jupiter.api.BeforeAll;
70+
import org.junit.jupiter.api.BeforeEach;
71+
import org.junit.jupiter.params.ParameterizedTest;
72+
import org.junit.jupiter.params.provider.MethodSource;
73+
import org.mockito.Mockito;
74+
import software.amazon.awssdk.services.s3.model.S3Exception;
75+
76+
/** Validates the propagation of FileIO-level exceptions to the REST API layer. */
77+
public class FileIOExceptionsTest {
78+
private static final Schema SCHEMA =
79+
new Schema(required(3, "id", Types.IntegerType.get(), "unique ID"));
80+
81+
private static final String catalog = "test-catalog";
82+
private static final String catalogBaseLocation = "file:/tmp/buckets/my-bucket/path/to/data";
83+
private static final RealmContext realmContext = () -> "test-realm";
84+
85+
private static SecurityContext securityContext;
86+
private static TestFileIOFactory ioFactory;
87+
private static IcebergRestCatalogApi api;
88+
89+
@BeforeAll
90+
public static void beforeAll() {
91+
ioFactory = new TestFileIOFactory();
92+
93+
InMemoryPolarisMetaStoreManagerFactory metaStoreManagerFactory =
94+
new InMemoryPolarisMetaStoreManagerFactory();
95+
metaStoreManagerFactory.setStorageIntegrationProvider(
96+
new PolarisStorageIntegrationProviderImpl(
97+
Mockito::mock, () -> GoogleCredentials.create(new AccessToken("abc", new Date()))));
98+
99+
PolarisMetaStoreManager metaStoreManager =
100+
metaStoreManagerFactory.getOrCreateMetaStoreManager(realmContext);
101+
102+
EntityCache cache = new EntityCache(metaStoreManager);
103+
RealmEntityManagerFactory realmEntityManagerFactory =
104+
new RealmEntityManagerFactory(metaStoreManagerFactory, () -> cache) {};
105+
CallContextCatalogFactory callContextFactory =
106+
new PolarisCallContextCatalogFactory(
107+
realmEntityManagerFactory,
108+
metaStoreManagerFactory,
109+
Mockito.mock(TaskExecutor.class),
110+
ioFactory);
111+
PolarisAuthorizer authorizer = Mockito.mock(PolarisAuthorizer.class);
112+
IcebergRestCatalogApiService service =
113+
new IcebergCatalogAdapter(
114+
callContextFactory, realmEntityManagerFactory, metaStoreManagerFactory, authorizer);
115+
api = new IcebergRestCatalogApi(service);
116+
117+
PolarisMetaStoreSession session =
118+
metaStoreManagerFactory.getOrCreateSessionSupplier(realmContext).get();
119+
PolarisCallContext context =
120+
new PolarisCallContext(session, Mockito.mock(PolarisDiagnostics.class));
121+
PolarisMetaStoreManager.CreatePrincipalResult createdPrincipal =
122+
metaStoreManager.createPrincipal(
123+
context,
124+
new PrincipalEntity.Builder()
125+
.setName("test-principal")
126+
.setCreateTimestamp(Instant.now().toEpochMilli())
127+
.setCredentialRotationRequiredState()
128+
.build());
129+
130+
AuthenticatedPolarisPrincipal principal =
131+
new AuthenticatedPolarisPrincipal(
132+
PolarisEntity.of(createdPrincipal.getPrincipal()), Set.of());
133+
134+
securityContext =
135+
new SecurityContext() {
136+
@Override
137+
public Principal getUserPrincipal() {
138+
return principal;
139+
}
140+
141+
@Override
142+
public boolean isUserInRole(String s) {
143+
return false;
144+
}
145+
146+
@Override
147+
public boolean isSecure() {
148+
return true;
149+
}
150+
151+
@Override
152+
public String getAuthenticationScheme() {
153+
return "";
154+
}
155+
};
156+
157+
PolarisCatalogsApi catalogsApi =
158+
new PolarisCatalogsApi(
159+
new PolarisServiceImpl(realmEntityManagerFactory, metaStoreManagerFactory, authorizer));
160+
161+
FileStorageConfigInfo storageConfigInfo =
162+
FileStorageConfigInfo.builder()
163+
.setStorageType(StorageConfigInfo.StorageTypeEnum.FILE)
164+
.setAllowedLocations(List.of(catalogBaseLocation))
165+
.build();
166+
Catalog catalog =
167+
PolarisCatalog.builder()
168+
.setType(Catalog.TypeEnum.INTERNAL)
169+
.setName("test-catalog")
170+
.setProperties(
171+
org.apache.polaris.core.admin.model.CatalogProperties.builder(catalogBaseLocation)
172+
.build())
173+
.setStorageConfigInfo(storageConfigInfo)
174+
.build();
175+
176+
try (Response res =
177+
catalogsApi.createCatalog(
178+
new CreateCatalogRequest(catalog), realmContext, securityContext)) {
179+
assertThat(res.getStatus()).isEqualTo(201);
180+
}
181+
182+
try (Response res =
183+
api.createNamespace(
184+
FileIOExceptionsTest.catalog,
185+
CreateNamespaceRequest.builder().withNamespace(Namespace.of("ns1")).build(),
186+
realmContext,
187+
securityContext)) {
188+
assertThat(res.getStatus()).isEqualTo(200);
189+
}
190+
}
191+
192+
@BeforeEach
193+
void reset() {
194+
ioFactory.loadFileIOExceptionSupplier = Optional.empty();
195+
ioFactory.newInputFileExceptionSupplier = Optional.empty();
196+
ioFactory.newOutputFileExceptionSupplier = Optional.empty();
197+
}
198+
199+
private static void requestCreateTable() {
200+
CreateTableRequest request =
201+
CreateTableRequest.builder().withName("t1").withSchema(SCHEMA).build();
202+
Response res = api.createTable(catalog, "ns1", request, null, realmContext, securityContext);
203+
res.close();
204+
}
205+
206+
static Stream<RuntimeException> exceptions() {
207+
return Stream.of(
208+
new AzureException("Forbidden"),
209+
S3Exception.builder().statusCode(403).message("Forbidden").build(),
210+
new StorageException(403, "Forbidden"));
211+
}
212+
213+
@ParameterizedTest
214+
@MethodSource("exceptions")
215+
void testLoadFileIOExceptionPropagation(RuntimeException ex) {
216+
ioFactory.loadFileIOExceptionSupplier = Optional.of(() -> ex);
217+
assertThatThrownBy(FileIOExceptionsTest::requestCreateTable).isSameAs(ex);
218+
}
219+
220+
@ParameterizedTest
221+
@MethodSource("exceptions")
222+
void testNewInputFileExceptionPropagation(RuntimeException ex) {
223+
ioFactory.newInputFileExceptionSupplier = Optional.of(() -> ex);
224+
assertThatThrownBy(FileIOExceptionsTest::requestCreateTable).isSameAs(ex);
225+
}
226+
227+
@ParameterizedTest
228+
@MethodSource("exceptions")
229+
void testNewOutputFileExceptionPropagation(RuntimeException ex) {
230+
ioFactory.newOutputFileExceptionSupplier = Optional.of(() -> ex);
231+
assertThatThrownBy(FileIOExceptionsTest::requestCreateTable).isSameAs(ex);
232+
}
233+
}

0 commit comments

Comments
 (0)