-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wait.query and wait.port resources #334
Changes from all commits
b659e90
c3107c4
cf3961e
9b57623
87a17b0
fb2e2dd
a37c1e4
1906d1f
c1e34af
c7d5808
c051658
d918359
46b20ec
2b5332a
034414c
8487564
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
--- | ||
title: "wait.port" | ||
slug: "wait-port" | ||
date: "2016-10-03T10:23:34-04:00" | ||
menu: | ||
main: | ||
parent: resources | ||
--- | ||
|
||
|
||
|
||
|
||
## Example | ||
|
||
```hcl | ||
wait.port "8080" { | ||
host = "localhost" | ||
port = 8080 | ||
interval = "1s" | ||
max_retry = 10 | ||
grace_period = "2s" | ||
} | ||
|
||
``` | ||
|
||
|
||
## Parameters | ||
|
||
- `host` (string) | ||
|
||
a host name or ip address. A TCP connection will be attempted at this host | ||
and the specified Port. | ||
|
||
- `port` (required int) | ||
|
||
the TCP port to attempt to connect to. | ||
|
||
- `interval` (duration string) | ||
|
||
the amount of time to wait in between checks. The format is Go's duration | ||
string. A duration string is a possibly signed sequence of decimal numbers, | ||
each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or | ||
"2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". If | ||
the interval is not specified, it will default to 5 seconds. | ||
|
||
- `grace_period` (duration string) | ||
|
||
the amount of time to wait before running the first check and after a | ||
successful check. The format is Go's duration string. A duration string is | ||
a possibly signed sequence of decimal numbers, each with optional fraction | ||
and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units | ||
are "ns", "us" (or "µs"), "ms", "s", "m", "h". If no grace period is | ||
specified, no grace period will be taken into account. | ||
|
||
- `max_retry` (int) | ||
|
||
the maximum number of attempts before the wait fails. If the maximum number | ||
of retries is not set, it will default to 5. | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
--- | ||
title: "wait.query" | ||
slug: "wait-query" | ||
date: "2016-10-03T10:23:34-04:00" | ||
menu: | ||
main: | ||
parent: resources | ||
--- | ||
|
||
|
||
|
||
|
||
## Example | ||
|
||
```hcl | ||
wait.query "service-health" { | ||
check = "nc -z localhost 8080" | ||
interval = "1s" | ||
max_retry = 10 | ||
grace_period = "1s" | ||
} | ||
|
||
``` | ||
|
||
|
||
## Parameters | ||
|
||
- `interpreter` (string) | ||
|
||
the shell interpreter that will be used for your scripts. `/bin/sh` is | ||
used by default. | ||
|
||
- `check` (required string) | ||
|
||
the script to run to check if a resource is ready. exit with exit code 0 if | ||
the resource is healthy, and 1 (or above) otherwise. | ||
|
||
- `check_flags` (list of strings) | ||
|
||
flags to pass to the `interpreter` binary to check validity. For | ||
`/bin/sh` this is `-n`. | ||
|
||
- `exec_flags` (list of strings) | ||
|
||
flags to pass to the interpreter at execution time. | ||
|
||
- `timeout` (duration string) | ||
|
||
the amount of time the command will wait before halting forcefully. The | ||
format is Go's duration string. A duration string is a possibly signed | ||
sequence of decimal numbers, each with optional fraction and a unit | ||
suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units are "ns", | ||
"us" (or "µs"), "ms", "s", "m", "h". | ||
|
||
- `dir` (string) | ||
|
||
the working directory this command should be run in. | ||
|
||
- `env` (map of string to string) | ||
|
||
any environment variables that should be passed to the command. | ||
|
||
- `interval` (duration string) | ||
|
||
the amount of time to wait in between checks. The format is Go's duration | ||
string. A duration string is a possibly signed sequence of decimal numbers, | ||
each with optional fraction and a unit suffix, such as "300ms", "-1.5h" or | ||
"2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". If | ||
the interval is not specified, it will default to 5 seconds. | ||
|
||
- `grace_period` (duration string) | ||
|
||
the amount of time to wait before running the first check and after a | ||
successful check. The format is Go's duration string. A duration string is | ||
a possibly signed sequence of decimal numbers, each with optional fraction | ||
and a unit suffix, such as "300ms", "-1.5h" or "2h45m". Valid time units | ||
are "ns", "us" (or "µs"), "ms", "s", "m", "h". If no grace period is | ||
specified, no grace period will be taken into account. | ||
|
||
- `max_retry` (int) | ||
|
||
the maximum number of attempts before the wait fails. If the maximum number | ||
of retries is not set, it will default to 5. | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ module "docker.hcl" "docker" { | |
params = { | ||
user-name = "{{param `user-name`}}" | ||
} | ||
|
||
depends = ["module.packages"] | ||
} | ||
|
||
|
@@ -50,26 +51,22 @@ task "filebeat-enable" { | |
depends = ["file.content.filebeat-yml"] | ||
} | ||
|
||
task.query "elasticsearch-wait" { | ||
query = <<EOF | ||
MAX_SECONDS=60 | ||
while /bin/true | ||
do | ||
status=$(curl -s 'http://localhost:9200/_cluster/health' 2>/dev/null | jq -r .status) | ||
if [ "$status" == "yellow" ] || [ "$status" == "green" ] ; then | ||
exit 0 | ||
fi | ||
[[ "$SECONDS" -ge "$MAX_SECONDS" ]] && exit 1 | ||
done | ||
wait.query "elasticsearch-wait" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👏 👏 👏 👏 👏 👏 |
||
check = <<EOF | ||
status=$(curl -s 'http://localhost:9200/_cluster/health' 2>/dev/null | jq -r .status) | ||
[[ "$status" == "yellow" ]] || [[ "$status" == "green" ]] | ||
EOF | ||
|
||
interval = "10s" | ||
max_retry = 10 | ||
|
||
depends = ["docker.container.elasticsearch-container"] | ||
} | ||
|
||
task "filebeat-elasticsearch-template" { | ||
check = "[[ \"$(curl 'http://localhost:9200/_template/filebeat' 2>/dev/null)\" != \"{}\" ]] || exit 1" | ||
apply = "curl -XPUT 'http://localhost:9200/_template/filebeat' -d@/etc/filebeat/filebeat.template.json 2>/dev/null" | ||
depends = ["task.filebeat-enable", "docker.container.elasticsearch-container", "task.query.elasticsearch-wait"] | ||
depends = ["task.filebeat-enable", "docker.container.elasticsearch-container", "wait.query.elasticsearch-wait"] | ||
} | ||
|
||
task "filebeat-start" { | ||
|
@@ -80,7 +77,7 @@ task "filebeat-start" { | |
|
||
file.directory "elasticsearch-data-directory" { | ||
destination = "{{param `elasticsearch-data-directory`}}" | ||
create_all = true | ||
create_all = true | ||
} | ||
|
||
docker.image "elasticsearch-image" { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// Copyright © 2016 Asteris, LLC | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package port | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
|
||
log "github.com/Sirupsen/logrus" | ||
"github.com/asteris-llc/converge/resource" | ||
"github.com/asteris-llc/converge/resource/wait" | ||
) | ||
|
||
// Port represents a port check | ||
type Port struct { | ||
*resource.Status | ||
*wait.Retrier | ||
Host string | ||
Port int | ||
ConnectionCheck | ||
} | ||
|
||
// Check if the port is open | ||
func (p *Port) Check(resource.Renderer) (resource.TaskStatus, error) { | ||
p.Status = resource.NewStatus() | ||
|
||
err := p.CheckConnection() | ||
if err == nil { | ||
if p.RetryCount > 0 { | ||
p.Status.AddMessage(fmt.Sprintf("Passed after %d retries (%v)", p.RetryCount, p.Duration)) | ||
} | ||
} else { | ||
// The desired state is that the port will be available after the apply. We | ||
// also want to indicate that the port is not available in the plan output. | ||
// Therefore, we set the status to StatusWillChange. | ||
p.RaiseLevel(resource.StatusWillChange) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why does this mean the resource will change? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The desired state is that the port is listening. So when you run plan, StatusWillChange tells you that, optimistically, the port will be listening after the apply. Also, it gives you the color in the converge output that lets the user more easily see whether or not the port is available during the plan phase. That said, I don't think leaving the status as StatusNoChange will affect the functionality outside of the converge output. Do you think that is preferred? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's kind of an edge case of the change semantics, isn't it? I think either will work, but could you maybe add a comment to that effect above it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yep, comment added |
||
p.Status.AddMessage(fmt.Sprintf("Failed to connect to %s:%d: %s", p.Host, p.Port, err.Error())) | ||
if p.RetryCount > 0 { // only add retry messages after an apply attempt | ||
p.Status.AddMessage(fmt.Sprintf("Failed after %d retries (%v)", p.RetryCount, p.Duration)) | ||
} | ||
} | ||
|
||
return p, nil | ||
} | ||
|
||
// Apply retries the check until it passes or returns max failure threshold | ||
func (p *Port) Apply() (resource.TaskStatus, error) { | ||
p.Status = resource.NewStatus() | ||
|
||
_, err := p.RetryUntil(func() (bool, error) { | ||
checkErr := p.CheckConnection() | ||
return checkErr == nil, checkErr | ||
}) | ||
|
||
return p, err | ||
} | ||
|
||
// CheckConnection attempts to see if a tcp port is open | ||
func (p *Port) CheckConnection() error { | ||
if p.ConnectionCheck == nil { | ||
p.ConnectionCheck = &TCPConnectionCheck{} | ||
} | ||
return p.ConnectionCheck.CheckConnection(p.Host, p.Port) | ||
} | ||
|
||
// ConnectionCheck represents a connection checker | ||
type ConnectionCheck interface { | ||
CheckConnection(host string, port int) error | ||
} | ||
|
||
// TCPConnectionCheck impelements a ConnectionCheck over TCP | ||
type TCPConnectionCheck struct{} | ||
|
||
// CheckConnection attempts to see if a tcp port is open | ||
func (t *TCPConnectionCheck) CheckConnection(host string, port int) error { | ||
logger := log.WithField("module", "wait.port") | ||
|
||
addr := fmt.Sprintf("%s:%d", host, port) | ||
conn, err := net.Dial("tcp", addr) | ||
if err != nil { | ||
logger.WithError(err).WithField("addr", addr).Debug("connection failed") | ||
return err | ||
} | ||
defer conn.Close() | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's this for? We haven't released 0.3.0 yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I figured it was better to set that now since the examples are built to download the release binaries and will no longer work with the 0.2.0 binary. All in all, I think we need a better system for the examples. I will open an issue on that.