Skip to content

Commit ae183dc

Browse files
authored
fix(setting): add retries on creation (#130)
* fix(setting): add retries on creation * fix: support configurable timeout
1 parent d559efa commit ae183dc

File tree

7 files changed

+82
-12
lines changed

7 files changed

+82
-12
lines changed

docs/resources/setting.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,13 @@ This field must be left null when using a ClickHouse Cloud cluster.
3838
When using a self hosted ClickHouse instance, this field should only be set when there is more than one replica and you are not using 'replicated' storage for user_directory.
3939
- `max` (String) Max Value for the setting
4040
- `min` (String) Min Value for the setting
41+
- `timeouts` (Attributes) (see [below for nested schema](#nestedatt--timeouts))
4142
- `value` (String) Value for the setting
4243
- `writability` (String) Writability attribute for the setting
44+
45+
<a id="nestedatt--timeouts"></a>
46+
### Nested Schema for `timeouts`
47+
48+
Optional:
49+
50+
- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours).

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
github.com/hashicorp/hcl/v2 v2.24.0
99
github.com/hashicorp/terraform-json v0.27.2
1010
github.com/hashicorp/terraform-plugin-framework v1.16.1
11+
github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0
1112
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0
1213
github.com/hashicorp/terraform-plugin-go v0.29.0
1314
github.com/hashicorp/terraform-plugin-log v0.10.0

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoK
9797
github.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE=
9898
github.com/hashicorp/terraform-plugin-framework v1.16.1 h1:1+zwFm3MEqd/0K3YBB2v9u9DtyYHyEuhVOfeIXbteWA=
9999
github.com/hashicorp/terraform-plugin-framework v1.16.1/go.mod h1:0xFOxLy5lRzDTayc4dzK/FakIgBhNf/lC4499R9cV4Y=
100+
github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0 h1:jblRy1PkLfPm5hb5XeMa3tezusnMRziUGqtT5epSYoI=
101+
github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0/go.mod h1:5jm2XK8uqrdiSRfD5O47OoxyGMCnwTcl8eoiDgSa+tc=
100102
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0 h1:Zz3iGgzxe/1XBkooZCewS0nJAaCFPFPHdNJd8FgE4Ow=
101103
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0/go.mod h1:GBKTNGbGVJohU03dZ7U8wHqc2zYnMUawgCN+gC0itLc=
102104
github.com/hashicorp/terraform-plugin-go v0.29.0 h1:1nXKl/nSpaYIUBU1IG/EsDOX0vv+9JxAltQyDMpq5mU=

internal/dbops/interface.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dbops
22

33
import (
44
"context"
5+
"time"
56
)
67

78
type Client interface {
@@ -39,7 +40,7 @@ type Client interface {
3940
AssociateSettingsProfile(ctx context.Context, id string, roleId *string, userId *string, clusterName *string) error
4041
DisassociateSettingsProfile(ctx context.Context, id string, roleId *string, userId *string, clusterName *string) error
4142

42-
CreateSetting(ctx context.Context, settingsProfileID string, setting Setting, clusterName *string) (*Setting, error)
43+
CreateSetting(ctx context.Context, settingsProfileID string, setting Setting, clusterName *string, timeout time.Duration) (*Setting, error)
4344
GetSetting(ctx context.Context, settingsProfileID string, name string, clusterName *string) (*Setting, error)
4445
DeleteSetting(ctx context.Context, settingsProfileID string, name string, clusterName *string) error
4546

internal/dbops/settings.go

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package dbops
33
import (
44
"context"
55
"fmt"
6+
"time"
67

8+
"github.com/hashicorp/terraform-plugin-log/tflog"
79
"github.com/pingcap/errors"
810

911
"github.com/ClickHouse/terraform-provider-clickhousedbops/internal/clickhouseclient"
@@ -18,7 +20,7 @@ type Setting struct {
1820
Writability *string
1921
}
2022

21-
func (i *impl) CreateSetting(ctx context.Context, settingsProfileID string, setting Setting, clusterName *string) (*Setting, error) {
23+
func (i *impl) CreateSetting(ctx context.Context, settingsProfileID string, setting Setting, clusterName *string, timeout time.Duration) (*Setting, error) {
2224
settingsProfile, err := i.GetSettingsProfile(ctx, settingsProfileID, clusterName)
2325
if err != nil {
2426
return nil, errors.WithMessage(err, "error getting settings profile")
@@ -41,7 +43,40 @@ func (i *impl) CreateSetting(ctx context.Context, settingsProfileID string, sett
4143
return nil, errors.WithMessage(err, "error running query")
4244
}
4345

44-
return i.GetSetting(ctx, settingsProfileID, setting.Name, clusterName)
46+
// Retry to handle potential replication lag
47+
retryCtx, cancel := context.WithTimeout(ctx, timeout)
48+
defer cancel()
49+
50+
backoff := 50 * time.Millisecond
51+
for {
52+
createdSetting, err := i.GetSetting(retryCtx, settingsProfileID, setting.Name, clusterName)
53+
if err != nil {
54+
return nil, errors.WithMessage(err, "error retrieving created setting")
55+
}
56+
57+
if createdSetting != nil {
58+
return createdSetting, nil
59+
}
60+
61+
tflog.Debug(ctx, "Setting not found, retrying with exponential backoff", map[string]any{
62+
"setting_name": setting.Name,
63+
"backoff": backoff.String(),
64+
})
65+
66+
// Context-aware sleep with exponential backoff
67+
timer := time.NewTimer(backoff)
68+
defer timer.Stop()
69+
70+
select {
71+
case <-retryCtx.Done():
72+
return nil, fmt.Errorf(
73+
"setting %q was created but could not be retrieved within timeout (%v): %w",
74+
setting.Name, timeout, retryCtx.Err(),
75+
)
76+
case <-timer.C:
77+
backoff *= 2
78+
}
79+
}
4580
}
4681

4782
func (i *impl) GetSetting(ctx context.Context, settingsProfileID string, name string, clusterName *string) (*Setting, error) {

pkg/resource/setting/model.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
package setting
22

33
import (
4+
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
45
"github.com/hashicorp/terraform-plugin-framework/types"
56
)
67

78
type Setting struct {
8-
ClusterName types.String `tfsdk:"cluster_name"`
9-
SettingsProfileID types.String `tfsdk:"settings_profile_id"`
10-
Name types.String `tfsdk:"name"`
11-
Value types.String `tfsdk:"value"`
12-
Min types.String `tfsdk:"min"`
13-
Max types.String `tfsdk:"max"`
14-
Writability types.String `tfsdk:"writability"`
9+
ClusterName types.String `tfsdk:"cluster_name"`
10+
SettingsProfileID types.String `tfsdk:"settings_profile_id"`
11+
Name types.String `tfsdk:"name"`
12+
Value types.String `tfsdk:"value"`
13+
Min types.String `tfsdk:"min"`
14+
Max types.String `tfsdk:"max"`
15+
Writability types.String `tfsdk:"writability"`
16+
Timeouts timeouts.Value `tfsdk:"timeouts"`
1517
}

pkg/resource/setting/setting.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import (
44
"context"
55
_ "embed"
66
"fmt"
7+
"time"
78

9+
"github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts"
810
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
911
"github.com/hashicorp/terraform-plugin-framework/path"
1012
"github.com/hashicorp/terraform-plugin-framework/resource"
@@ -38,7 +40,7 @@ func (r *Resource) Metadata(_ context.Context, req resource.MetadataRequest, res
3840
resp.TypeName = req.ProviderTypeName + "_setting"
3941
}
4042

41-
func (r *Resource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
43+
func (r *Resource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
4244
resp.Schema = schema.Schema{
4345
Attributes: map[string]schema.Attribute{
4446
"cluster_name": schema.StringAttribute{
@@ -116,6 +118,9 @@ func (r *Resource) Schema(_ context.Context, _ resource.SchemaRequest, resp *res
116118
),
117119
},
118120
},
121+
"timeouts": timeouts.Attributes(ctx, timeouts.Opts{
122+
Create: true,
123+
}),
119124
},
120125
MarkdownDescription: settingResourceDescription,
121126
}
@@ -180,7 +185,14 @@ func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp
180185
Writability: plan.Writability.ValueStringPointer(),
181186
}
182187

183-
createdSetting, err := r.client.CreateSetting(ctx, plan.SettingsProfileID.ValueString(), setting, plan.ClusterName.ValueStringPointer())
188+
createTimeout, diags := plan.Timeouts.Create(ctx, 1*time.Minute)
189+
resp.Diagnostics.Append(diags...)
190+
191+
if resp.Diagnostics.HasError() {
192+
return
193+
}
194+
195+
createdSetting, err := r.client.CreateSetting(ctx, plan.SettingsProfileID.ValueString(), setting, plan.ClusterName.ValueStringPointer(), createTimeout)
184196
if err != nil {
185197
resp.Diagnostics.AddError(
186198
"Error Creating ClickHouse Setting",
@@ -189,9 +201,18 @@ func (r *Resource) Create(ctx context.Context, req resource.CreateRequest, resp
189201
return
190202
}
191203

204+
if createdSetting == nil {
205+
resp.Diagnostics.AddError(
206+
"Error Creating ClickHouse Setting",
207+
"Created setting returned nil without error",
208+
)
209+
return
210+
}
211+
192212
state := Setting{
193213
ClusterName: plan.ClusterName,
194214
SettingsProfileID: plan.SettingsProfileID,
215+
Timeouts: plan.Timeouts,
195216
}
196217

197218
modelFromApiResponse(&state, *createdSetting)

0 commit comments

Comments
 (0)