Skip to content

Commit

Permalink
Merge pull request #23 from tetratelabs/allow-tcp-failure
Browse files Browse the repository at this point in the history
Allow TCP echo to fail
  • Loading branch information
chirauki authored May 24, 2024
2 parents 486f26e + 5c8b83d commit 773c935
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.vscode/
dist/
terraform-provider-checkmate
terraform.tfstate*
29 changes: 28 additions & 1 deletion docs/resources/tcp_echo.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,39 @@ resource "checkmate_tcp_echo" "example" {
# Set a number of consecutive sucesses to make the check pass
consecutive_successes = 5
}
# In case you expect to be some kind of problem, and not getting
# a response back, you can set `expect_failure` to true. In that case
# you can skip `expected_message`.
resource "checkmate_tcp_echo" "example" {
# The hostname where the echo request will be sent
host = "foo.bar"
# The TCP port at which the request will be sent
port = 3002
# Message that will be sent to the TCP echo server
message = "PROXY nonexistent.local:4242 foobartest"
# Expect this to fail
expect_write_failure = true
# Set the connection timeout for the destination host, in milliseconds
connection_timeout = 3000
# Set the per try timeout for the destination host, in milliseconds
single_attempt_timeout = 2000
# Set a number of consecutive sucesses to make the check pass
consecutive_successes = 5
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `expected_message` (String) The message expected to be included in the echo response
- `host` (String) The hostname where to send the TCP echo request to
- `message` (String) The message to send in the echo request
- `port` (Number) The port of the hostname where to send the TCP echo request
Expand All @@ -52,6 +77,8 @@ resource "checkmate_tcp_echo" "example" {
- `connection_timeout` (Number) The timeout for stablishing a new TCP connection in milliseconds
- `consecutive_successes` (Number) Number of consecutive successes required before the check is considered successful overall. Defaults to 1.
- `create_anyway_on_check_failure` (Boolean) If false, the resource will fail to create if the check does not pass. If true, the resource will be created anyway. Defaults to false.
- `expect_write_failure` (Boolean) Wether or not the check is expected to fail after successfully connecting to the target. If true, the check will be considered successful if it fails. Defaults to false.
- `expected_message` (String) The message expected to be included in the echo response
- `interval` (Number) Interval in milliseconds between attemps. Default 200
- `keepers` (Map of String) Arbitrary map of string values that when changed will cause the check to run again.
- `single_attempt_timeout` (Number) Timeout for an individual attempt. If exceeded, the attempt will be considered failure and potentially retried. Default 5000ms
Expand Down
26 changes: 26 additions & 0 deletions examples/resources/checkmate_tcp_echo/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,29 @@ resource "checkmate_tcp_echo" "example" {
# Set a number of consecutive sucesses to make the check pass
consecutive_successes = 5
}

# In case you expect to be some kind of problem, and not getting
# a response back, you can set `expect_failure` to true. In that case
# you can skip `expected_message`.
resource "checkmate_tcp_echo" "example" {
# The hostname where the echo request will be sent
host = "foo.bar"

# The TCP port at which the request will be sent
port = 3002

# Message that will be sent to the TCP echo server
message = "PROXY nonexistent.local:4242 foobartest"

# Expect this to fail
expect_write_failure = true

# Set the connection timeout for the destination host, in milliseconds
connection_timeout = 3000

# Set the per try timeout for the destination host, in milliseconds
single_attempt_timeout = 2000

# Set a number of consecutive sucesses to make the check pass
consecutive_successes = 5
}
35 changes: 33 additions & 2 deletions pkg/provider/resource_tcp_echo.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ import (
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
Expand Down Expand Up @@ -66,7 +68,17 @@ func (*TCPEchoResource) Schema(ctx context.Context, req resource.SchemaRequest,
},
"expected_message": schema.StringAttribute{
MarkdownDescription: "The message expected to be included in the echo response",
Required: true,
Required: false,
Optional: true,
Computed: true,
Default: stringdefault.StaticString(""),
},
"expect_write_failure": schema.BoolAttribute{
MarkdownDescription: "Wether or not the check is expected to fail after successfully connecting to the target. If true, the check will be considered successful if it fails. Defaults to false.",
Required: false,
Optional: true,
Computed: true,
Default: booldefault.StaticBool(false),
},
"timeout": schema.Int64Attribute{
MarkdownDescription: "Overall timeout in milliseconds for the check before giving up, default 10000",
Expand Down Expand Up @@ -125,6 +137,7 @@ type TCPEchoResourceModel struct {
Port types.Int64 `tfsdk:"port"`
Message types.String `tfsdk:"message"`
ExpectedMessage types.String `tfsdk:"expected_message"`
ExpectWriteFailure types.Bool `tfsdk:"expect_write_failure"`
ConnectionTimeout types.Int64 `tfsdk:"connection_timeout"`
SingleAttemptTimeout types.Int64 `tfsdk:"single_attempt_timeout"`
Timeout types.Int64 `tfsdk:"timeout"`
Expand Down Expand Up @@ -160,6 +173,14 @@ func (r *TCPEchoResource) Create(ctx context.Context, req resource.CreateRequest
}

func (r *TCPEchoResource) TCPEcho(ctx context.Context, data *TCPEchoResourceModel, diag *diag.Diagnostics) {
if !data.ExpectWriteFailure.ValueBool() && data.ExpectedMessage.ValueString() == "" {
tflog.Error(ctx, "expected_message is required when expect_failure is false")
return
}
if data.ExpectedMessage.ValueString() != "" && data.ExpectWriteFailure.ValueBool() {
tflog.Warn(ctx, "expected_message is ignored when expect_failure is true")
}

data.Passed = types.BoolValue(false)

window := helpers.RetryWindow{
Expand All @@ -169,6 +190,7 @@ func (r *TCPEchoResource) TCPEcho(ctx context.Context, data *TCPEchoResourceMode
}

result := window.Do(func(attempt int, success int) bool {
exepctFailure := data.ExpectWriteFailure.ValueBool()
destStr := data.Host.ValueString() + ":" + strconv.Itoa(int(data.Port.ValueInt64()))

d := net.Dialer{Timeout: time.Duration(data.ConnectionTimeout.ValueInt64()) * time.Millisecond}
Expand All @@ -187,17 +209,26 @@ func (r *TCPEchoResource) TCPEcho(ctx context.Context, data *TCPEchoResourceMode

deadlineDuration := time.Millisecond * time.Duration(data.SingleAttemptTimeout.ValueInt64())
err = conn.SetDeadline(time.Now().Add(deadlineDuration))
if err != nil {
if err != nil && !exepctFailure {
tflog.Warn(ctx, fmt.Sprintf("could not set connection deadline: %v", err.Error()))
return false
}

reply := make([]byte, 1024)
_, err = conn.Read(reply)
if err != nil {
if exepctFailure {
// We expected this
return true
}
tflog.Warn(ctx, fmt.Sprintf("read from server failed: %v", err.Error()))
return false
}
// At this point, if we expect failure, we can just return the check failed,
// as we were expecting it to fail
if exepctFailure {
return false
}

if !strings.Contains(string(reply), data.ExpectedMessage.ValueString()) {
tflog.Warn(ctx, fmt.Sprintf("Got response %q, which does not include expected message %q", string(reply), data.ExpectedMessage.ValueString()))
Expand Down

0 comments on commit 773c935

Please sign in to comment.