InVisionApp/go-health

View on GitHub
checkers/reachable.go

Summary

Maintainability
A
0 mins
Test Coverage
package checkers

import (
    "net"
    "net/url"
    "time"
)

const (
    // ReachableDDHealthErrors is the datadog name used when there is a failure in the reachable checker
    ReachableDDHealthErrors = "health.errors"
    // ReachableDefaultPort is the default port used if no port is defined in a reachable checker
    ReachableDefaultPort = "80"
    // ReachableDefaultNetwork is the default network used in the reachable checker
    ReachableDefaultNetwork = "tcp"
)

var (
    // ReachableDefaultTimeout is the default timeout used when reachable is checking the URL
    ReachableDefaultTimeout = time.Duration(3) * time.Second
)

// ReachableDialer is the signature for a function that checks if an address is reachable
type ReachableDialer func(network, address string, timeout time.Duration) (net.Conn, error)

// ReachableDatadogIncrementer is any datadog client that has the Incr method for tracking metrics
type ReachableDatadogIncrementer interface {
    Incr(name string, tags []string, rate float64) error
}

// ReachableConfig is used for configuring an HTTP check. The only required field is `URL`.
//
// "Dialer" is optional and defaults to using net.DialTimeout.
//
// "Timeout" is optional and defaults to "3s".
//
// "Network" is optional and defaults to "tcp"; it should be one of "tcp",
// "tcp4", "tcp6", "unix", "unixpacket", "udp", "udp4", "udp6", "unixgram" or an
// IP transport. The IP transports are "ip", "ip4", or "ip6" followed by a colon
// and a literal protocol number or a protocol name, as in "ip:1" or "ip:icmp".
//
// "DatadogClient" is optional; if defined metrics will be sent via statsd.
//
// "DatadogTags" is optional; defines the tags that are passed to datadog when there is a failure
type ReachableConfig struct {
    URL           *url.URL                    // Required
    Dialer        ReachableDialer             // Optional (default net.DialTimeout)
    Timeout       time.Duration               // Optional (default 3s)
    Network       string                      // Optional (default tcp)
    DatadogClient ReachableDatadogIncrementer // Optional
    DatadogTags   []string                    // Optional
}

// ReachableChecker checks that URL responds to a TCP request
type ReachableChecker struct {
    dialer  ReachableDialer
    timeout time.Duration
    network string
    url     *url.URL
    datadog ReachableDatadogIncrementer
    tags    []string
}

// NewReachableChecker creates a new reachable health checker
func NewReachableChecker(cfg *ReachableConfig) (*ReachableChecker, error) {
    t := ReachableDefaultTimeout
    if cfg.Timeout != 0 {
        t = cfg.Timeout
    }
    d := net.DialTimeout
    if cfg.Dialer != nil {
        d = cfg.Dialer
    }
    n := ReachableDefaultNetwork
    if cfg.Network != "" {
        n = cfg.Network
    }
    r := &ReachableChecker{
        dialer:  d,
        timeout: t,
        network: n,
        url:     cfg.URL,
        datadog: cfg.DatadogClient,
        tags:    cfg.DatadogTags,
    }
    return r, nil
}

// Status checks if the endpoint is reachable
func (r *ReachableChecker) Status() (interface{}, error) {
    // We must provide a port so when a port is not set in the URL provided use
    // the default port (80)
    port := r.url.Port()
    if len(port) == 0 {
        port = ReachableDefaultPort
    }

    conn, err := r.dialer(r.network, r.url.Hostname()+":"+port, r.timeout)
    if err != nil {
        return r.fail(err)
    }
    if conn != nil {
        if errClose := conn.Close(); errClose != nil {
            return r.fail(errClose)
        }
    }
    return nil, nil
}

func (r *ReachableChecker) fail(err error) (interface{}, error) {
    if r.datadog != nil {
        r.datadog.Incr(ReachableDDHealthErrors, r.tags, 1.0)
    }
    return nil, err
}