forked from asteris-llc/converge
/
port.go
102 lines (86 loc) · 3.02 KB
/
port.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// 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)
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()
// CheckConnection returns an err if the connection fails but RetryUntil
// breaks on errors. So if we get an error, we just return false and
// otherwise ignore the error
return checkErr == nil, nil
})
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
}