Browse Source
Add RisingWave catalog tests (#8308)
Add RisingWave catalog tests (#8308)
* Add RisingWave catalog tests for S3 tables * Add RisingWave catalog integration tests to CI workflow * Refactor RisingWave catalog tests based on PR feedback * Address PR feedback: optimize checks, cleanup logs * fix tests * consistentpull/8323/head
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 628 additions and 0 deletions
-
68.github/workflows/s3-tables-tests.yml
-
79test/s3tables/catalog_risingwave/risingwave_catalog_test.go
-
481test/s3tables/catalog_risingwave/setup_test.go
@ -0,0 +1,79 @@ |
|||||
|
package catalog_risingwave |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"strings" |
||||
|
"testing" |
||||
|
) |
||||
|
|
||||
|
func TestRisingWaveIcebergCatalog(t *testing.T) { |
||||
|
if testing.Short() { |
||||
|
t.Skip("Skipping integration test in short mode") |
||||
|
} |
||||
|
|
||||
|
env := NewTestEnvironment(t) |
||||
|
defer env.Cleanup(t) |
||||
|
|
||||
|
if !env.dockerAvailable { |
||||
|
t.Skip("Docker not available, skipping RisingWave integration test") |
||||
|
} |
||||
|
|
||||
|
t.Log(">>> Starting SeaweedFS...") |
||||
|
env.StartSeaweedFS(t) |
||||
|
t.Log(">>> SeaweedFS started.") |
||||
|
|
||||
|
tableBucket := "iceberg-tables" |
||||
|
t.Logf(">>> Creating table bucket: %s", tableBucket) |
||||
|
createTableBucket(t, env, tableBucket) |
||||
|
|
||||
|
t.Log(">>> Starting RisingWave...") |
||||
|
env.StartRisingWave(t) |
||||
|
t.Log(">>> RisingWave started.") |
||||
|
|
||||
|
// Create Iceberg namespace
|
||||
|
createIcebergNamespace(t, env, "default") |
||||
|
|
||||
|
// Create a catalog in RisingWave that points to SeaweedFS Iceberg REST API
|
||||
|
icebergUri := env.dockerIcebergEndpoint() |
||||
|
s3Endpoint := env.dockerS3Endpoint() |
||||
|
|
||||
|
tableName := "test_table_" + randomString(6) |
||||
|
createIcebergTable(t, env, tableBucket, "default", tableName) |
||||
|
|
||||
|
sourceName := "test_source_" + randomString(6) |
||||
|
createSourceSql := fmt.Sprintf(` |
||||
|
CREATE SOURCE %s WITH ( |
||||
|
connector = 'iceberg', |
||||
|
catalog.type = 'rest', |
||||
|
catalog.uri = '%s', |
||||
|
catalog.name = 'default', |
||||
|
database.name = 'default', |
||||
|
table.name = '%s', |
||||
|
warehouse.path = 's3://%s',
|
||||
|
s3.endpoint = '%s', |
||||
|
s3.region = 'us-east-1', |
||||
|
s3.access.key = '%s', |
||||
|
s3.secret.key = '%s', |
||||
|
s3.path.style.access = 'true', |
||||
|
catalog.rest.sigv4_enabled = 'true', |
||||
|
catalog.rest.signing_region = 'us-east-1', |
||||
|
catalog.rest.signing_name = 's3' |
||||
|
);`, sourceName, icebergUri, tableName, tableBucket, s3Endpoint, env.accessKey, env.secretKey) |
||||
|
|
||||
|
t.Logf(">>> Creating source %s...", sourceName) |
||||
|
runRisingWaveSQL(t, env.postgresSidecar, createSourceSql) |
||||
|
|
||||
|
showSourcesOutput := runRisingWaveSQL(t, env.postgresSidecar, "SHOW SOURCES;") |
||||
|
if !strings.Contains(showSourcesOutput, sourceName) { |
||||
|
t.Fatalf("Expected source %s in SHOW SOURCES output:\n%s", sourceName, showSourcesOutput) |
||||
|
} |
||||
|
|
||||
|
describeOutput := runRisingWaveSQL(t, env.postgresSidecar, fmt.Sprintf("DESCRIBE %s;", sourceName)) |
||||
|
if !strings.Contains(describeOutput, "id") || !strings.Contains(describeOutput, "name") { |
||||
|
t.Fatalf("Expected id/name columns in DESCRIBE output:\n%s", describeOutput) |
||||
|
} |
||||
|
|
||||
|
runRisingWaveSQL(t, env.postgresSidecar, fmt.Sprintf("SELECT * FROM %s LIMIT 0;", sourceName)) |
||||
|
|
||||
|
t.Log(">>> RisingWave Iceberg Catalog test passed!") |
||||
|
} |
||||
@ -0,0 +1,481 @@ |
|||||
|
package catalog_risingwave |
||||
|
|
||||
|
import ( |
||||
|
"bytes" |
||||
|
"context" |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
"io" |
||||
|
"math/rand" |
||||
|
"net" |
||||
|
"net/http" |
||||
|
"os" |
||||
|
"os/exec" |
||||
|
"path/filepath" |
||||
|
"strings" |
||||
|
"sync" |
||||
|
"testing" |
||||
|
"time" |
||||
|
|
||||
|
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" |
||||
|
"github.com/aws/aws-sdk-go-v2/credentials" |
||||
|
"github.com/seaweedfs/seaweedfs/test/s3tables/testutil" |
||||
|
) |
||||
|
|
||||
|
var ( |
||||
|
miniProcessMu sync.Mutex |
||||
|
lastMiniProcess *exec.Cmd |
||||
|
) |
||||
|
|
||||
|
func stopPreviousMini() { |
||||
|
miniProcessMu.Lock() |
||||
|
defer miniProcessMu.Unlock() |
||||
|
|
||||
|
if lastMiniProcess != nil && lastMiniProcess.Process != nil { |
||||
|
_ = lastMiniProcess.Process.Kill() |
||||
|
_ = lastMiniProcess.Wait() |
||||
|
} |
||||
|
lastMiniProcess = nil |
||||
|
} |
||||
|
|
||||
|
func registerMiniProcess(cmd *exec.Cmd) { |
||||
|
miniProcessMu.Lock() |
||||
|
lastMiniProcess = cmd |
||||
|
miniProcessMu.Unlock() |
||||
|
} |
||||
|
|
||||
|
func clearMiniProcess(cmd *exec.Cmd) { |
||||
|
miniProcessMu.Lock() |
||||
|
if lastMiniProcess == cmd { |
||||
|
lastMiniProcess = nil |
||||
|
} |
||||
|
miniProcessMu.Unlock() |
||||
|
} |
||||
|
|
||||
|
type TestEnvironment struct { |
||||
|
t *testing.T |
||||
|
dockerAvailable bool |
||||
|
seaweedfsDataDir string |
||||
|
masterPort int |
||||
|
filerPort int |
||||
|
s3Port int |
||||
|
icebergRestPort int |
||||
|
risingwavePort int |
||||
|
bindIP string |
||||
|
accessKey string |
||||
|
secretKey string |
||||
|
risingwaveContainer string |
||||
|
postgresSidecar string |
||||
|
masterProcess *exec.Cmd |
||||
|
logFile *os.File |
||||
|
} |
||||
|
|
||||
|
func NewTestEnvironment(t *testing.T) *TestEnvironment { |
||||
|
env := &TestEnvironment{ |
||||
|
t: t, |
||||
|
accessKey: "AKIAIOSFODNN7EXAMPLE", |
||||
|
secretKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", |
||||
|
} |
||||
|
|
||||
|
// Check if Docker is available
|
||||
|
cmd := exec.Command("docker", "version") |
||||
|
env.dockerAvailable = cmd.Run() == nil |
||||
|
|
||||
|
return env |
||||
|
} |
||||
|
|
||||
|
func (env *TestEnvironment) hostMasterAddress() string { |
||||
|
return fmt.Sprintf("127.0.0.1:%d", env.masterPort) |
||||
|
} |
||||
|
|
||||
|
func (env *TestEnvironment) hostS3Endpoint() string { |
||||
|
return fmt.Sprintf("http://127.0.0.1:%d", env.s3Port) |
||||
|
} |
||||
|
|
||||
|
func (env *TestEnvironment) hostIcebergEndpoint() string { |
||||
|
return fmt.Sprintf("http://127.0.0.1:%d", env.icebergRestPort) |
||||
|
} |
||||
|
|
||||
|
func (env *TestEnvironment) dockerS3Endpoint() string { |
||||
|
return fmt.Sprintf("http://host.docker.internal:%d", env.s3Port) |
||||
|
} |
||||
|
|
||||
|
func (env *TestEnvironment) dockerIcebergEndpoint() string { |
||||
|
return fmt.Sprintf("http://host.docker.internal:%d", env.icebergRestPort) |
||||
|
} |
||||
|
|
||||
|
func (env *TestEnvironment) StartSeaweedFS(t *testing.T) { |
||||
|
t.Helper() |
||||
|
|
||||
|
stopPreviousMini() |
||||
|
|
||||
|
var err error |
||||
|
env.seaweedfsDataDir, err = os.MkdirTemp("", "seaweed-risingwave-test-") |
||||
|
if err != nil { |
||||
|
t.Fatalf("failed to create temp directory: %v", err) |
||||
|
} |
||||
|
|
||||
|
env.masterPort = mustFreePort(t, "Master") |
||||
|
env.filerPort = mustFreePort(t, "Filer") |
||||
|
env.s3Port = mustFreePort(t, "S3") |
||||
|
env.icebergRestPort = mustFreePort(t, "Iceberg") |
||||
|
env.risingwavePort = mustFreePort(t, "RisingWave") |
||||
|
|
||||
|
env.bindIP = testutil.FindBindIP() |
||||
|
|
||||
|
iamConfigPath, err := testutil.WriteIAMConfig(env.seaweedfsDataDir, env.accessKey, env.secretKey) |
||||
|
if err != nil { |
||||
|
t.Fatalf("failed to create IAM config: %v", err) |
||||
|
} |
||||
|
|
||||
|
// Create log file for SeaweedFS
|
||||
|
logFile, err := os.Create(filepath.Join(env.seaweedfsDataDir, "seaweedfs.log")) |
||||
|
if err != nil { |
||||
|
t.Fatalf("failed to create log file: %v", err) |
||||
|
} |
||||
|
env.logFile = logFile |
||||
|
|
||||
|
// Start SeaweedFS using weed mini (all-in-one including Iceberg REST)
|
||||
|
env.masterProcess = exec.Command( |
||||
|
"weed", "mini", |
||||
|
"-ip", env.bindIP, |
||||
|
"-ip.bind", "0.0.0.0", |
||||
|
"-master.port", fmt.Sprintf("%d", env.masterPort), |
||||
|
"-filer.port", fmt.Sprintf("%d", env.filerPort), |
||||
|
"-s3.port", fmt.Sprintf("%d", env.s3Port), |
||||
|
"-s3.port.iceberg", fmt.Sprintf("%d", env.icebergRestPort), |
||||
|
"-s3.config", iamConfigPath, |
||||
|
"-dir", env.seaweedfsDataDir, |
||||
|
) |
||||
|
env.masterProcess.Stdout = logFile |
||||
|
env.masterProcess.Stderr = logFile |
||||
|
env.masterProcess.Env = append(os.Environ(), |
||||
|
"AWS_ACCESS_KEY_ID="+env.accessKey, |
||||
|
"AWS_SECRET_ACCESS_KEY="+env.secretKey, |
||||
|
"ICEBERG_WAREHOUSE=s3://iceberg-tables", |
||||
|
"S3TABLES_DEFAULT_BUCKET=iceberg-tables", |
||||
|
) |
||||
|
if err := env.masterProcess.Start(); err != nil { |
||||
|
t.Fatalf("failed to start weed mini: %v", err) |
||||
|
} |
||||
|
registerMiniProcess(env.masterProcess) |
||||
|
|
||||
|
// Wait for all services to be ready
|
||||
|
if !waitForPort(env.masterPort, 15*time.Second) { |
||||
|
t.Fatalf("weed mini failed to start - master port %d not listening", env.masterPort) |
||||
|
} |
||||
|
if !waitForPort(env.filerPort, 15*time.Second) { |
||||
|
t.Fatalf("weed mini failed to start - filer port %d not listening", env.filerPort) |
||||
|
} |
||||
|
if !waitForPort(env.s3Port, 15*time.Second) { |
||||
|
t.Fatalf("weed mini failed to start - s3 port %d not listening", env.s3Port) |
||||
|
} |
||||
|
if !waitForPort(env.icebergRestPort, 15*time.Second) { |
||||
|
t.Fatalf("weed mini failed to start - iceberg rest port %d not listening", env.icebergRestPort) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func mustFreePort(t *testing.T, name string) int { |
||||
|
t.Helper() |
||||
|
minPort := 10000 |
||||
|
maxPort := 55000 // Ensure port+10000 < 65535
|
||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano())) |
||||
|
|
||||
|
for i := 0; i < 1000; i++ { |
||||
|
port := minPort + r.Intn(maxPort-minPort) |
||||
|
|
||||
|
// Check http port
|
||||
|
ln, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port)) |
||||
|
if err != nil { |
||||
|
continue |
||||
|
} |
||||
|
ln.Close() |
||||
|
|
||||
|
// Check grpc port (weed mini uses port+10000)
|
||||
|
ln2, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", port+10000)) |
||||
|
if err != nil { |
||||
|
continue |
||||
|
} |
||||
|
ln2.Close() |
||||
|
|
||||
|
return port |
||||
|
} |
||||
|
t.Fatalf("failed to find a free port < %d for %s after 1000 attempts", maxPort, name) |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
func waitForPort(port int, timeout time.Duration) bool { |
||||
|
deadline := time.Now().Add(timeout) |
||||
|
for time.Now().Before(deadline) { |
||||
|
conn, err := net.DialTimeout("tcp", fmt.Sprintf("localhost:%d", port), 500*time.Millisecond) |
||||
|
if err == nil { |
||||
|
conn.Close() |
||||
|
return true |
||||
|
} |
||||
|
time.Sleep(100 * time.Millisecond) |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
func (env *TestEnvironment) StartRisingWave(t *testing.T) { |
||||
|
t.Helper() |
||||
|
|
||||
|
containerName := "seaweed-risingwave-" + randomString(8) |
||||
|
env.risingwaveContainer = containerName |
||||
|
|
||||
|
cmd := exec.Command("docker", "run", "-d", |
||||
|
"--name", containerName, |
||||
|
"-p", fmt.Sprintf("%d:4566", env.risingwavePort), |
||||
|
"--add-host", "host.docker.internal:host-gateway", |
||||
|
"-e", "AWS_ACCESS_KEY_ID="+env.accessKey, |
||||
|
"-e", "AWS_SECRET_ACCESS_KEY="+env.secretKey, |
||||
|
"-e", "AWS_REGION=us-east-1", |
||||
|
"-e", "AWS_S3_PATH_STYLE_ACCESS=true", |
||||
|
"-e", "AWS_S3_FORCE_PATH_STYLE=true", |
||||
|
"risingwavelabs/risingwave:v2.5.0", |
||||
|
"playground", |
||||
|
) |
||||
|
if output, err := cmd.CombinedOutput(); err != nil { |
||||
|
t.Fatalf("failed to start RisingWave container: %v\n%s", err, string(output)) |
||||
|
} |
||||
|
|
||||
|
// Start a sidecar postgres container for running psql commands
|
||||
|
sidecarName := "seaweed-risingwave-sidecar-" + randomString(8) |
||||
|
env.postgresSidecar = sidecarName |
||||
|
sidecarCmd := exec.Command("docker", "run", "-d", "--rm", |
||||
|
"--name", sidecarName, |
||||
|
"--network", fmt.Sprintf("container:%s", containerName), |
||||
|
"postgres:16-alpine", |
||||
|
"sleep", "infinity", |
||||
|
) |
||||
|
if output, err := sidecarCmd.CombinedOutput(); err != nil { |
||||
|
t.Fatalf("failed to start postgres sidecar: %v\n%s", err, string(output)) |
||||
|
} |
||||
|
|
||||
|
// Wait for RisingWave port to be open on host
|
||||
|
if !waitForPort(env.risingwavePort, 120*time.Second) { |
||||
|
t.Fatalf("timed out waiting for RisingWave port %d to be open", env.risingwavePort) |
||||
|
} |
||||
|
|
||||
|
// Wait for RisingWave to be truly ready via psql in the sidecar.
|
||||
|
if !env.waitForRisingWave(120 * time.Second) { |
||||
|
t.Fatalf("timed out waiting for RisingWave to be ready via psql") |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func (env *TestEnvironment) waitForRisingWave(timeout time.Duration) bool { |
||||
|
deadline := time.Now().Add(timeout) |
||||
|
env.t.Logf(">>> Waiting for RisingWave to be ready (timeout %v)...\n", timeout) |
||||
|
for time.Now().Before(deadline) { |
||||
|
if output, err := runPostgresClientSQL(env.postgresSidecar, "SELECT 1;"); err == nil { |
||||
|
env.t.Logf(">>> RisingWave is ready.\n") |
||||
|
return true |
||||
|
} else { |
||||
|
env.t.Logf(">>> RisingWave not ready yet: %v (Output: %s)\n", err, string(output)) |
||||
|
} |
||||
|
time.Sleep(5 * time.Second) |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
func runPostgresClientSQL(containerName, sql string) ([]byte, error) { |
||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) |
||||
|
defer cancel() |
||||
|
|
||||
|
cmd := exec.CommandContext(ctx, "docker", "exec", |
||||
|
containerName, |
||||
|
"psql", |
||||
|
"-h", "127.0.0.1", |
||||
|
"-p", "4566", |
||||
|
"-U", "root", |
||||
|
"-d", "dev", |
||||
|
"-v", "ON_ERROR_STOP=1", |
||||
|
"-c", sql, |
||||
|
) |
||||
|
return cmd.CombinedOutput() |
||||
|
} |
||||
|
|
||||
|
func (env *TestEnvironment) Cleanup(t *testing.T) { |
||||
|
t.Helper() |
||||
|
|
||||
|
if env.risingwaveContainer != "" { |
||||
|
if t.Failed() { |
||||
|
logs, err := exec.Command("docker", "logs", env.risingwaveContainer).CombinedOutput() |
||||
|
if err == nil { |
||||
|
env.t.Logf(">>> RisingWave Logs:\n%s\n", string(logs)) |
||||
|
} else { |
||||
|
env.t.Logf(">>> Failed to get RisingWave logs: %v\n", err) |
||||
|
} |
||||
|
} |
||||
|
_ = exec.Command("docker", "rm", "-f", env.risingwaveContainer).Run() |
||||
|
} |
||||
|
|
||||
|
if env.postgresSidecar != "" { |
||||
|
_ = exec.Command("docker", "rm", "-f", env.postgresSidecar).Run() |
||||
|
} |
||||
|
|
||||
|
if env.seaweedfsDataDir != "" && t.Failed() { |
||||
|
logPath := filepath.Join(env.seaweedfsDataDir, "seaweedfs.log") |
||||
|
if content, err := os.ReadFile(logPath); err == nil { |
||||
|
env.t.Logf(">>> SeaweedFS Logs:\n%s\n", string(content)) |
||||
|
} |
||||
|
env.t.Logf(">>> Filer Contents:\n") |
||||
|
listFilerContents(t, env, "/") |
||||
|
} |
||||
|
|
||||
|
if env.masterProcess != nil && env.masterProcess.Process != nil { |
||||
|
_ = env.masterProcess.Process.Kill() |
||||
|
_ = env.masterProcess.Wait() |
||||
|
} |
||||
|
clearMiniProcess(env.masterProcess) |
||||
|
|
||||
|
if env.seaweedfsDataDir != "" { |
||||
|
if env.logFile != nil { |
||||
|
env.logFile.Close() |
||||
|
} |
||||
|
_ = os.RemoveAll(env.seaweedfsDataDir) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func runRisingWaveSQL(t *testing.T, containerName, sql string) string { |
||||
|
t.Helper() |
||||
|
|
||||
|
output, err := runPostgresClientSQL(containerName, sql) |
||||
|
if err != nil { |
||||
|
t.Fatalf("RisingWave command failed: %v\nSQL: %s\nOutput:\n%s", err, sql, string(output)) |
||||
|
} |
||||
|
return string(output) |
||||
|
} |
||||
|
|
||||
|
func createTableBucket(t *testing.T, env *TestEnvironment, bucketName string) { |
||||
|
t.Helper() |
||||
|
|
||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) |
||||
|
defer cancel() |
||||
|
|
||||
|
cmd := exec.CommandContext(ctx, "weed", "shell", |
||||
|
fmt.Sprintf("-master=%s", env.hostMasterAddress()), |
||||
|
) |
||||
|
cmd.Stdin = strings.NewReader(fmt.Sprintf("s3tables.bucket -create -name %s -account 000000000000\nexit\n", bucketName)) |
||||
|
output, err := cmd.CombinedOutput() |
||||
|
if err != nil { |
||||
|
t.Fatalf("failed to create table bucket %s via weed shell: %v\nOutput: %s", bucketName, err, string(output)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func doIcebergSignedJSONRequest(env *TestEnvironment, method, path string, payload any) (int, string, error) { |
||||
|
url := env.hostIcebergEndpoint() + path |
||||
|
|
||||
|
var body io.Reader |
||||
|
var payloadHash string |
||||
|
|
||||
|
if payload != nil { |
||||
|
data, err := json.Marshal(payload) |
||||
|
if err != nil { |
||||
|
return 0, "", err |
||||
|
} |
||||
|
body = bytes.NewReader(data) |
||||
|
// hash := sha256.Sum256(data)
|
||||
|
// payloadHash = hex.EncodeToString(hash[:])
|
||||
|
payloadHash = "UNSIGNED-PAYLOAD" |
||||
|
} else { |
||||
|
payloadHash = "UNSIGNED-PAYLOAD" |
||||
|
} |
||||
|
|
||||
|
req, err := http.NewRequest(method, url, body) |
||||
|
if err != nil { |
||||
|
return 0, "", fmt.Errorf("failed to create request: %w", err) |
||||
|
} |
||||
|
|
||||
|
if payload != nil { |
||||
|
req.Header.Set("Content-Type", "application/json") |
||||
|
} |
||||
|
req.Header.Set("X-Amz-Content-Sha256", payloadHash) |
||||
|
|
||||
|
// Sign the request
|
||||
|
credsProvider := credentials.NewStaticCredentialsProvider(env.accessKey, env.secretKey, "") |
||||
|
creds, err := credsProvider.Retrieve(context.Background()) |
||||
|
if err != nil { |
||||
|
return 0, "", fmt.Errorf("failed to retrieve credentials: %w", err) |
||||
|
} |
||||
|
signer := v4.NewSigner() |
||||
|
|
||||
|
if err := signer.SignHTTP(context.Background(), creds, req, payloadHash, "s3", "us-east-1", time.Now()); err != nil { |
||||
|
return 0, "", fmt.Errorf("failed to sign request: %w", err) |
||||
|
} |
||||
|
|
||||
|
client := &http.Client{} |
||||
|
resp, err := client.Do(req) |
||||
|
if err != nil { |
||||
|
return 0, "", fmt.Errorf("request failed: %w", err) |
||||
|
} |
||||
|
defer resp.Body.Close() |
||||
|
|
||||
|
respBody, err := io.ReadAll(resp.Body) |
||||
|
if err != nil { |
||||
|
return 0, "", fmt.Errorf("failed to read response body: %w", err) |
||||
|
} |
||||
|
|
||||
|
return resp.StatusCode, string(respBody), nil |
||||
|
} |
||||
|
|
||||
|
func createIcebergNamespace(t *testing.T, env *TestEnvironment, namespace string) { |
||||
|
t.Helper() |
||||
|
|
||||
|
status, raw, err := doIcebergSignedJSONRequest(env, "POST", "/v1/namespaces", map[string]any{ |
||||
|
"namespace": []string{namespace}, |
||||
|
}) |
||||
|
if err != nil { |
||||
|
t.Fatalf("failed to create Iceberg namespace %s: %v", namespace, err) |
||||
|
} |
||||
|
if status != 200 && status != 409 { |
||||
|
t.Fatalf("failed to create Iceberg namespace %s: status %d body: %s", namespace, status, raw) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func createIcebergTable(t *testing.T, env *TestEnvironment, bucketName, namespace, tableName string) { |
||||
|
t.Helper() |
||||
|
|
||||
|
createPath := fmt.Sprintf("/v1/namespaces/%s/tables", namespace) |
||||
|
status, raw, err := doIcebergSignedJSONRequest(env, "POST", createPath, map[string]any{ |
||||
|
"name": tableName, |
||||
|
"location": fmt.Sprintf("s3://%s/%s/%s", bucketName, namespace, tableName), |
||||
|
"schema": map[string]any{ |
||||
|
"type": "struct", |
||||
|
"fields": []map[string]any{ |
||||
|
{"id": 1, "name": "id", "required": false, "type": "int"}, |
||||
|
{"id": 2, "name": "name", "required": false, "type": "string"}, |
||||
|
}, |
||||
|
}, |
||||
|
}) |
||||
|
if err != nil { |
||||
|
t.Fatalf("failed to create Iceberg table %s.%s in bucket %s: %v", namespace, tableName, bucketName, err) |
||||
|
} |
||||
|
if status != 200 && status != 409 { |
||||
|
t.Fatalf("failed to create Iceberg table %s.%s in bucket %s: status %d body: %s", namespace, tableName, bucketName, status, raw) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func listFilerContents(t *testing.T, env *TestEnvironment, path string) { |
||||
|
t.Helper() |
||||
|
|
||||
|
cmd := exec.Command("weed", "shell", |
||||
|
fmt.Sprintf("-master=%s", env.hostMasterAddress()), |
||||
|
) |
||||
|
cmd.Stdin = strings.NewReader(fmt.Sprintf("fs.ls -R %s\nexit\n", path)) |
||||
|
output, err := cmd.CombinedOutput() |
||||
|
if err != nil { |
||||
|
env.t.Logf(">>> Warning: failed to list filer contents: %v\nOutput: %s\n", err, string(output)) |
||||
|
} else { |
||||
|
env.t.Logf("%s\n", string(output)) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
func randomString(n int) string { |
||||
|
const letters = "abcdefghijklmnopqrstuvwxyz0123456789" |
||||
|
b := make([]byte, n) |
||||
|
for i := range b { |
||||
|
b[i] = letters[rand.Intn(len(letters))] |
||||
|
} |
||||
|
return string(b) |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue