Skip to content

Commit 40283b2

Browse files
authored
Service: Add location tests for views (#2496)
1 parent 32b2c27 commit 40283b2

File tree

1 file changed

+171
-0
lines changed

1 file changed

+171
-0
lines changed

runtime/service/src/test/java/org/apache/polaris/service/catalog/iceberg/IcebergAllowedLocationTest.java

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,35 +20,57 @@
2020
package org.apache.polaris.service.catalog.iceberg;
2121

2222
import static org.apache.polaris.core.config.FeatureConfiguration.OPTIMIZED_SIBLING_CHECK;
23+
import static org.apache.polaris.core.entity.table.IcebergTableLikeEntity.USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY;
2324
import static org.apache.polaris.service.admin.PolarisAuthzTestBase.SCHEMA;
2425
import static org.assertj.core.api.Assertions.assertThat;
26+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2527
import static org.junit.jupiter.api.Assertions.assertEquals;
2628
import static org.junit.jupiter.api.Assertions.assertNotEquals;
2729
import static org.junit.jupiter.api.Assertions.assertThrows;
2830

2931
import jakarta.ws.rs.core.Response;
3032
import java.nio.file.Path;
33+
import java.nio.file.Paths;
3134
import java.util.HashMap;
3235
import java.util.List;
3336
import java.util.Map;
3437
import java.util.UUID;
38+
import org.apache.iceberg.MetadataUpdate;
3539
import org.apache.iceberg.catalog.Namespace;
40+
import org.apache.iceberg.catalog.TableIdentifier;
3641
import org.apache.iceberg.exceptions.ForbiddenException;
3742
import org.apache.iceberg.rest.requests.CreateNamespaceRequest;
3843
import org.apache.iceberg.rest.requests.CreateTableRequest;
44+
import org.apache.iceberg.rest.requests.CreateViewRequest;
45+
import org.apache.iceberg.rest.requests.ImmutableCreateViewRequest;
46+
import org.apache.iceberg.rest.requests.UpdateTableRequest;
47+
import org.apache.iceberg.view.ImmutableSQLViewRepresentation;
48+
import org.apache.iceberg.view.ImmutableViewVersion;
3949
import org.apache.polaris.core.admin.model.Catalog;
4050
import org.apache.polaris.core.admin.model.CatalogProperties;
4151
import org.apache.polaris.core.admin.model.CreateCatalogRequest;
4252
import org.apache.polaris.core.admin.model.FileStorageConfigInfo;
4353
import org.apache.polaris.core.admin.model.StorageConfigInfo;
4454
import org.apache.polaris.service.TestServices;
55+
import org.jetbrains.annotations.NotNull;
4556
import org.junit.jupiter.api.Test;
4657
import org.junit.jupiter.api.io.TempDir;
4758

4859
public class IcebergAllowedLocationTest {
4960
private static final String namespace = "ns";
5061
private static final String catalog = "test-catalog";
5162

63+
private static final String VIEW_QUERY = "select * from ns.tbl";
64+
public static final ImmutableViewVersion VIEW_VERSION =
65+
ImmutableViewVersion.builder()
66+
.versionId(1)
67+
.timestampMillis(System.currentTimeMillis())
68+
.schemaId(1)
69+
.defaultNamespace(Namespace.of(namespace))
70+
.addRepresentations(
71+
ImmutableSQLViewRepresentation.builder().sql(VIEW_QUERY).dialect("spark").build())
72+
.build();
73+
5274
private String getTableName() {
5375
return "table_" + UUID.randomUUID();
5476
}
@@ -131,6 +153,155 @@ private static TestServices getTestServices() {
131153
return services;
132154
}
133155

156+
@Test
157+
void testViewWithAllowedLocations(@TempDir Path tmpDir) {
158+
var viewId = TableIdentifier.of(namespace, "view");
159+
var services = getTestServices();
160+
var catalogLocation = tmpDir.resolve(catalog).toAbsolutePath().toUri().toString();
161+
createCatalog(services, Map.of(), catalogLocation, List.of(catalogLocation));
162+
var namespaceLocation = catalogLocation + "/" + namespace;
163+
createNamespace(services, namespaceLocation);
164+
165+
// create a view with allowed locations
166+
String customAllowedLocation1 = Paths.get(namespaceLocation, "custom-location1").toString();
167+
String customAllowedLocation2 = Paths.get(namespaceLocation, "custom-location2").toString();
168+
169+
CreateViewRequest createViewRequest =
170+
getCreateViewRequest(customAllowedLocation2, viewId.name(), customAllowedLocation1);
171+
var response =
172+
services
173+
.restApi()
174+
.createView(
175+
catalog,
176+
namespace,
177+
createViewRequest,
178+
services.realmContext(),
179+
services.securityContext());
180+
181+
assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
182+
183+
// update the view with allowed locations
184+
String customAllowedLocation3 = Paths.get(namespaceLocation, "custom-location3").toString();
185+
186+
Map<String, String> updatedProperties = new HashMap<>();
187+
updatedProperties.put(USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY, customAllowedLocation3);
188+
189+
UpdateTableRequest updateRequest =
190+
UpdateTableRequest.create(
191+
viewId, List.of(), List.of(new MetadataUpdate.SetProperties(updatedProperties)));
192+
193+
var updateResponse =
194+
services
195+
.catalogAdapter()
196+
.newHandlerWrapper(services.securityContext(), catalog)
197+
.replaceView(viewId, updateRequest);
198+
assertEquals(
199+
updateResponse.metadata().properties().get(USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY),
200+
customAllowedLocation3);
201+
}
202+
203+
@Test
204+
void testViewOutsideAllowedLocations(@TempDir Path tmpDir) {
205+
var viewId = TableIdentifier.of(namespace, "view");
206+
var services = getTestServices();
207+
208+
var catalogBaseLocation = tmpDir.resolve(catalog).toAbsolutePath().toUri().toString();
209+
var namespaceLocation = catalogBaseLocation + "/" + namespace;
210+
211+
createCatalog(services, Map.of(), catalogBaseLocation, List.of(catalogBaseLocation));
212+
createNamespace(services, namespaceLocation);
213+
214+
var locationNotAllowed =
215+
tmpDir.resolve("location-not-allowed").toAbsolutePath().toUri().toString();
216+
var locationAllowed = Paths.get(namespaceLocation, "custom-location").toString();
217+
218+
// Test 1: Create a view with allowed location, and update it with a location not allowed
219+
var properties = new HashMap<String, String>();
220+
221+
CreateViewRequest createViewRequest =
222+
ImmutableCreateViewRequest.builder()
223+
.name(viewId.name())
224+
.schema(SCHEMA)
225+
.viewVersion(VIEW_VERSION)
226+
.location(locationAllowed)
227+
.properties(properties)
228+
.build();
229+
230+
var response =
231+
services
232+
.restApi()
233+
.createView(
234+
catalog,
235+
namespace,
236+
createViewRequest,
237+
services.realmContext(),
238+
services.securityContext());
239+
assertEquals(response.getStatus(), Response.Status.OK.getStatusCode());
240+
241+
Map<String, String> updatedProperties = new HashMap<>();
242+
updatedProperties.put(USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY, locationNotAllowed);
243+
244+
var updateRequest =
245+
UpdateTableRequest.create(
246+
viewId,
247+
List.of(), // requirements
248+
List.of(new MetadataUpdate.SetProperties(updatedProperties)));
249+
250+
assertThatThrownBy(
251+
() ->
252+
services
253+
.catalogAdapter()
254+
.newHandlerWrapper(services.securityContext(), catalog)
255+
.replaceView(viewId, updateRequest));
256+
257+
// Test 2: Try to create a view with location not allowed
258+
var createViewRequestNotAllowed =
259+
getCreateViewRequest(locationNotAllowed, "view2", locationNotAllowed);
260+
261+
assertThatThrownBy(
262+
() ->
263+
services
264+
.restApi()
265+
.createView(
266+
catalog,
267+
namespace,
268+
createViewRequestNotAllowed,
269+
services.realmContext(),
270+
services.securityContext()))
271+
.isInstanceOf(ForbiddenException.class)
272+
.hasMessageContaining("Invalid locations");
273+
274+
// Test 3: Try to create a view with metadata location not allowed
275+
var createViewRequestMetadataNotAllowed =
276+
getCreateViewRequest(locationNotAllowed, "view3", locationAllowed);
277+
278+
assertThatThrownBy(
279+
() ->
280+
services
281+
.restApi()
282+
.createView(
283+
catalog,
284+
namespace,
285+
createViewRequestMetadataNotAllowed,
286+
services.realmContext(),
287+
services.securityContext()))
288+
.isInstanceOf(ForbiddenException.class)
289+
.hasMessageContaining("Invalid locations");
290+
}
291+
292+
private static @NotNull CreateViewRequest getCreateViewRequest(
293+
String writeMetadataPath, String viewName, String location) {
294+
var properties = new HashMap<String, String>();
295+
properties.put(USER_SPECIFIED_WRITE_METADATA_LOCATION_KEY, writeMetadataPath);
296+
return ImmutableCreateViewRequest.builder()
297+
.name(viewName)
298+
.schema(SCHEMA)
299+
.viewVersion(VIEW_VERSION)
300+
.location(location)
301+
.properties(properties)
302+
.build();
303+
}
304+
134305
private void createCatalog(
135306
TestServices services,
136307
Map<String, String> catalogConfig,

0 commit comments

Comments
 (0)