resource/wait/port/port.go
// 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"
"golang.org/x/net/context"
)
// Port represents a port check
type Port struct {
*wait.Retrier
// the hostname
Host string `export:"host"`
// the TCP port
Port int `export:"port"`
ConnectionCheck
}
// Check if the port is open
func (p *Port) Check(context.Context, resource.Renderer) (resource.TaskStatus, error) {
status := resource.NewStatus()
err := p.CheckConnection()
if err == nil {
if p.RetryCount > 0 {
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.
status.RaiseLevel(resource.StatusWillChange)
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
status.AddMessage(fmt.Sprintf("Failed after %d retries (%v)", p.RetryCount, p.Duration))
}
}
return status, nil
}
// Apply retries the check until it passes or returns max failure threshold
func (p *Port) Apply(context.Context) (resource.TaskStatus, error) {
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 status, 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
}