mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-07 12:37:41 +01:00
* iceberg: withhold the S3 endpoint from credential-vending clients A client that sends X-Iceberg-Access-Delegation: vended-credentials builds its storage credential out of the LoadTable config and drops the one it was configured with. We vend no credentials, so the endpoint we advertised left DuckDB signing nothing: every metadata and data file came back 403, and its attempt to refresh the empty credential 404ed on stage-created tables. Answer those clients with no config at all so they keep their own credentials. Clients that do not ask for delegation still get the endpoint. * iceberg: mark load responses as varying on the delegation header The FileIO config in a table or view load response now depends on whether the client asked for vended credentials, so a cache between us and the client must key on that header rather than on the URL alone. * test: cover the DuckDB vended-credentials access pattern Runs weed mini with -s3.externalUrl, which is what makes the catalog advertise an endpoint at all, and checks both halves: a plain LoadTable still gets the endpoint, while one asking for vended credentials never gets an endpoint without the credentials to sign with. The DuckDB round trip creates a table from a query and reads it back, which is the flow that failed with 403 on every data file.
84 lines
2.3 KiB
Go
84 lines
2.3 KiB
Go
package iceberg
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
)
|
|
|
|
func TestGetBucketFromPrefix_WarehouseQueryFallback(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
url string
|
|
want string
|
|
}{
|
|
{
|
|
name: "warehouse query routes to its bucket when no prefix in path",
|
|
url: "/v1/namespaces?warehouse=s3%3A%2F%2Fmyblkt%2F",
|
|
want: "myblkt",
|
|
},
|
|
{
|
|
name: "warehouse query with sub-path still picks the bucket",
|
|
url: "/v1/namespaces?warehouse=s3%3A%2F%2Fanother%2Fextra",
|
|
want: "another",
|
|
},
|
|
{
|
|
name: "bare bucket name is taken as the table bucket",
|
|
url: "/v1/namespaces?warehouse=not-a-url",
|
|
want: "not-a-url",
|
|
},
|
|
{
|
|
name: "table bucket ARN routes to its bucket",
|
|
url: "/v1/namespaces?warehouse=arn%3Aaws%3As3tables%3Aus-east-1%3Aadmin%3Abucket%2Fseaweed-iceberg",
|
|
want: "seaweed-iceberg",
|
|
},
|
|
{
|
|
name: "unusable warehouse value falls through to default",
|
|
url: "/v1/namespaces?warehouse=file%3A%2F%2F%2Ftmp%2Fwh",
|
|
want: "warehouse",
|
|
},
|
|
{
|
|
name: "no warehouse and no prefix returns default",
|
|
url: "/v1/namespaces",
|
|
want: "warehouse",
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
r := httptest.NewRequest("GET", tc.url, nil)
|
|
got := getBucketFromPrefix(r)
|
|
if got != tc.want {
|
|
t.Fatalf("getBucketFromPrefix(%q) = %q, want %q", tc.url, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildFileIOConfig(t *testing.T) {
|
|
loadTable := func() *http.Request {
|
|
return httptest.NewRequest(http.MethodGet, "/v1/namespaces/ns/tables/t", nil)
|
|
}
|
|
|
|
t.Run("no endpoint configured yields empty config", func(t *testing.T) {
|
|
s := &Server{}
|
|
got := s.buildFileIOConfig(loadTable())
|
|
if len(got) != 0 {
|
|
t.Fatalf("buildFileIOConfig() = %v, want empty", got)
|
|
}
|
|
})
|
|
|
|
t.Run("endpoint is advertised with path-style-access and region", func(t *testing.T) {
|
|
s := &Server{s3Endpoint: "http://seaweed.example:8333"}
|
|
got := s.buildFileIOConfig(loadTable())
|
|
if got["s3.endpoint"] != "http://seaweed.example:8333" {
|
|
t.Fatalf("s3.endpoint = %q, want %q", got["s3.endpoint"], "http://seaweed.example:8333")
|
|
}
|
|
if got["s3.path-style-access"] != "true" {
|
|
t.Fatalf("s3.path-style-access = %q, want %q", got["s3.path-style-access"], "true")
|
|
}
|
|
if got["s3.region"] == "" {
|
|
t.Fatalf("s3.region was empty, want a non-empty default so clients like DuckDB do not require AWS_REGION")
|
|
}
|
|
})
|
|
}
|