Skip to content

Commit 13f5d9c

Browse files
committed
test: Introduce integration test of rest catalog client against docker container
1 parent d206c1d commit 13f5d9c

File tree

13 files changed

+788
-9
lines changed

13 files changed

+788
-9
lines changed

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
[workspace]
1919
resolver = "2"
20-
members = ["crates/catalog/*", "crates/iceberg"]
20+
members = ["crates/catalog/*", "crates/iceberg", "crates/test_utils"]
2121

2222
[workspace.dependencies]
2323
anyhow = "1.0.72"
@@ -31,6 +31,7 @@ bitvec = "1.0.1"
3131
chrono = "0.4"
3232
derive_builder = "0.12.0"
3333
either = "1"
34+
env_logger = "0.10.0"
3435
futures = "0.3"
3536
iceberg = { path = "./crates/iceberg" }
3637
iceberg-catalog-rest = { path = "./crates/catalog/rest" }
@@ -43,6 +44,7 @@ once_cell = "1"
4344
opendal = "0.42"
4445
ordered-float = "4.0.0"
4546
pretty_assertions = "1.4.0"
47+
port_scanner = "0.1.5"
4648
reqwest = { version = "^0.11", features = ["json"] }
4749
rust_decimal = "1.31.0"
4850
serde = { version = "^1.0", features = ["rc"] }

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@
1515
# specific language governing permissions and limitations
1616
# under the License.
1717

18+
.EXPORT_ALL_VARIABLES:
19+
20+
RUST_LOG = debug
21+
1822
build:
1923
cargo build
20-
24+
2125
check-fmt:
2226
cargo fmt --all -- --check
2327

crates/catalog/rest/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ keywords = ["iceberg", "rest", "catalog"]
3131
async-trait = { workspace = true }
3232
chrono = { workspace = true }
3333
iceberg = { workspace = true }
34+
log = "0.4.20"
3435
reqwest = { workspace = true }
3536
serde = { workspace = true }
3637
serde_derive = { workspace = true }
@@ -40,5 +41,7 @@ urlencoding = { workspace = true }
4041
uuid = { workspace = true, features = ["v4"] }
4142

4243
[dev-dependencies]
44+
iceberg_test_utils = { path = "../../test_utils", features = ["tests"] }
4345
mockito = { workspace = true }
46+
port_scanner = { workspace = true }
4447
tokio = { workspace = true }

crates/catalog/rest/src/catalog.rs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use std::collections::HashMap;
2121

2222
use async_trait::async_trait;
2323
use reqwest::header::{self, HeaderMap, HeaderName, HeaderValue};
24-
use reqwest::{Client, Request};
24+
use reqwest::{Client, Request, Response, StatusCode};
2525
use serde::de::DeserializeOwned;
2626
use typed_builder::TypedBuilder;
2727
use urlencoding::encode;
@@ -163,15 +163,45 @@ impl HttpClient {
163163
) -> Result<()> {
164164
let resp = self.0.execute(request).await?;
165165

166+
println!("Status code: {}", resp.status());
167+
166168
if resp.status().as_u16() == SUCCESS_CODE {
167169
Ok(())
168170
} else {
171+
let code = resp.status();
172+
let text = resp.bytes().await?;
173+
let e = serde_json::from_slice::<E>(&text).map_err(|e| {
174+
Error::new(
175+
ErrorKind::Unexpected,
176+
"Failed to parse response from rest catalog server!",
177+
)
178+
.with_context("json", String::from_utf8_lossy(&text))
179+
.with_context("code", code.to_string())
180+
.with_source(e)
181+
})?;
182+
Err(e.into())
183+
}
184+
}
185+
186+
/// More generic logic handling for special cases like head.
187+
async fn do_execute<R, E: DeserializeOwned + Into<Error>>(
188+
&self,
189+
request: Request,
190+
handler: impl FnOnce(&Response) -> Option<R>,
191+
) -> Result<R> {
192+
let resp = self.0.execute(request).await?;
193+
194+
if let Some(ret) = handler(&resp) {
195+
Ok(ret)
196+
} else {
197+
let code = resp.status();
169198
let text = resp.bytes().await?;
170199
let e = serde_json::from_slice::<E>(&text).map_err(|e| {
171200
Error::new(
172201
ErrorKind::Unexpected,
173202
"Failed to parse response from rest catalog server!",
174203
)
204+
.with_context("code", code.to_string())
175205
.with_context("json", String::from_utf8_lossy(&text))
176206
.with_source(e)
177207
})?;
@@ -273,9 +303,12 @@ impl Catalog for RestCatalog {
273303
.build()?;
274304

275305
self.client
276-
.execute::<ErrorResponse, NO_CONTENT>(request)
306+
.do_execute::<bool, ErrorResponse>(request, |resp| match resp.status() {
307+
StatusCode::NO_CONTENT => Some(true),
308+
StatusCode::NOT_FOUND => Some(false),
309+
_ => None,
310+
})
277311
.await
278-
.map(|_| true)
279312
}
280313

281314
/// Drop a namespace from the catalog.
@@ -326,7 +359,7 @@ impl Catalog for RestCatalog {
326359
partition_spec: creation.partition_spec,
327360
write_order: creation.sort_order,
328361
// We don't support stage create yet.
329-
stage_create: None,
362+
stage_create: Some(false),
330363
properties: if creation.properties.is_empty() {
331364
None
332365
} else {
@@ -406,9 +439,12 @@ impl Catalog for RestCatalog {
406439
.build()?;
407440

408441
self.client
409-
.execute::<ErrorResponse, NO_CONTENT>(request)
442+
.do_execute::<bool, ErrorResponse>(request, |resp| match resp.status() {
443+
StatusCode::NO_CONTENT => Some(true),
444+
StatusCode::NOT_FOUND => Some(false),
445+
_ => None,
446+
})
410447
.await
411-
.map(|_| true)
412448
}
413449

414450
/// Rename a table in the catalog.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
version: '3.8'
19+
20+
services:
21+
rest:
22+
image: tabulario/iceberg-rest:0.10.0
23+
environment:
24+
- AWS_ACCESS_KEY_ID=admin
25+
- AWS_SECRET_ACCESS_KEY=password
26+
- AWS_REGION=us-east-1
27+
- CATALOG_CATOLOG__IMPL=org.apache.iceberg.jdbc.JdbcCatalog
28+
- CATALOG_URI=jdbc:sqlite:file:/tmp/iceberg_rest_mode=memory
29+
- CATALOG_WAREHOUSE=s3://icebergdata/demo
30+
- CATALOG_IO__IMPL=org.apache.iceberg.aws.s3.S3FileIO
31+
- CATALOG_S3_ENDPOINT=http://minio:9000
32+
depends_on:
33+
- minio
34+
links:
35+
- minio:icebergdata.minio
36+
expose:
37+
- 8181
38+
39+
minio:
40+
image: minio/minio
41+
environment:
42+
- MINIO_ROOT_USER=admin
43+
- MINIO_ROOT_PASSWORD=password
44+
- MINIO_DOMAIN=minio
45+
expose:
46+
- 9001
47+
- 9000
48+
command: [ "server", "/data", "--console-address", ":9001" ]
49+
50+
mc:
51+
depends_on:
52+
- minio
53+
image: minio/mc
54+
environment:
55+
- AWS_ACCESS_KEY_ID=admin
56+
- AWS_SECRET_ACCESS_KEY=password
57+
- AWS_REGION=us-east-1
58+
entrypoint: >
59+
/bin/sh -c "
60+
until (/usr/bin/mc config host add minio http://minio:9000 admin password) do echo '...waiting...' && sleep 1; done;
61+
/usr/bin/mc rm -r --force minio/icebergdata;
62+
/usr/bin/mc mb minio/icebergdata;
63+
/usr/bin/mc policy set public minio/icebergdata;
64+
tail -f /dev/null
65+
"

0 commit comments

Comments
 (0)