From 0ad7fbebfc2f8077f3da764e7b4c5b934a123e23 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Sun, 28 Jun 2026 19:02:31 +0700 Subject: [PATCH] fix(couchbase): improve readiness timeout --- README.md | 2 ++ adapter/couchbase.go | 2 +- adapter/couchbase_initialization.go | 11 ++++++++- adapter/couchbase_test.go | 36 +++++++++++++++++++++++++++++ config.example.yml | 2 +- 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 22ef53f..efab7d5 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,8 @@ On startup each adapter initializes the minimum resource it owns, then every tic Each configured service starts independently. If one service cannot connect, it logs the error and retries without stopping other services in the same deployment. +For hosted Couchbase/Capella clusters, `ready_timeout` defaults to `2m`. If Couchbase still reports `CONNECTION_ERROR`, check the connection string, bucket name, database user permissions, and Capella allowed IP/network access. + ## Adding a new adapter 1. Create `adapter/.go`. diff --git a/adapter/couchbase.go b/adapter/couchbase.go index 601af66..d8dbbd5 100644 --- a/adapter/couchbase.go +++ b/adapter/couchbase.go @@ -93,7 +93,7 @@ func (a *couchbaseAdapter) Connect(ctx context.Context) error { } b := cluster.Bucket(a.bucket) if err := b.WaitUntilReady(a.readyTimeout, &gocb.WaitUntilReadyOptions{Context: ctx}); err != nil { - return err + return a.bucketReadyError(err) } if err := a.ensureScopeAndCollection(ctx, b); err != nil { return err diff --git a/adapter/couchbase_initialization.go b/adapter/couchbase_initialization.go index 66557c1..30b3630 100644 --- a/adapter/couchbase_initialization.go +++ b/adapter/couchbase_initialization.go @@ -9,7 +9,16 @@ import ( "github.com/couchbase/gocb/v2" ) -const defaultCouchbaseReadyTimeout = 30 * time.Second +const defaultCouchbaseReadyTimeout = 2 * time.Minute + +func (a *couchbaseAdapter) bucketReadyError(err error) error { + return fmt.Errorf( + "bucket %q was not ready after %s: %w; check bucket_name, connection_string, database user permissions, and Capella allowed IP/network access; increase ready_timeout if the cluster or bucket was just created", + a.bucket, + a.readyTimeout, + err, + ) +} func (a *couchbaseAdapter) ensureBucket(ctx context.Context, cluster *gocb.Cluster) error { if a.bucketRAMQuotaMB == 0 { diff --git a/adapter/couchbase_test.go b/adapter/couchbase_test.go index cc8dc91..decf1e1 100644 --- a/adapter/couchbase_test.go +++ b/adapter/couchbase_test.go @@ -1,6 +1,8 @@ package adapter import ( + "errors" + "strings" "testing" "time" ) @@ -29,6 +31,25 @@ func TestCouchbaseFactoryParsesInitializationOptions(t *testing.T) { } } +func TestCouchbaseFactoryUsesHostedReadyTimeoutDefault(t *testing.T) { + a, err := New("couchbase", Config{ + "connection_string": "couchbases://cb.example.com", + "username": "user", + "password": "pass", + "bucket_name": "keepalive", + "scope_name": "scope", + "collection_name": "collection", + }) + if err != nil { + t.Fatalf("New returned error: %v", err) + } + + got := a.(*couchbaseAdapter) + if got.readyTimeout != 2*time.Minute { + t.Fatalf("readyTimeout = %s, want 2m", got.readyTimeout) + } +} + func TestCouchbaseFactoryRejectsInvalidReadyTimeout(t *testing.T) { _, err := New("couchbase", Config{ "connection_string": "couchbases://cb.example.com", @@ -43,3 +64,18 @@ func TestCouchbaseFactoryRejectsInvalidReadyTimeout(t *testing.T) { t.Fatal("New returned nil error") } } + +func TestCouchbaseBucketReadyErrorAddsActionableContext(t *testing.T) { + cause := errors.New("unambiguous timeout") + a := couchbaseAdapter{bucket: "keepalive", readyTimeout: 2 * time.Minute} + + err := a.bucketReadyError(cause) + if !errors.Is(err, cause) { + t.Fatalf("bucketReadyError does not wrap cause: %v", err) + } + for _, want := range []string{"keepalive", "2m0s", "Capella allowed IP", "ready_timeout"} { + if !strings.Contains(err.Error(), want) { + t.Fatalf("bucketReadyError() = %q, want it to contain %q", err, want) + } + } +} diff --git a/config.example.yml b/config.example.yml index b42c381..a5d5e84 100644 --- a/config.example.yml +++ b/config.example.yml @@ -25,6 +25,6 @@ services: bucket_name: keepalive scope_name: _default collection_name: _default - ready_timeout: 30s + ready_timeout: 2m # Optional. Set only when this user can create a missing bucket. # bucket_ram_quota_mb: 128