2020-11-30 20:05:37 +00:00
|
|
|
package metrics
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
conn "github.com/cloudflare/cloudflared/connection"
|
2020-11-25 06:55:13 +00:00
|
|
|
|
|
|
|
"github.com/rs/zerolog"
|
2020-11-30 20:05:37 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// ReadyServer serves HTTP 200 if the tunnel can serve traffic. Intended for k8s readiness checks.
|
|
|
|
type ReadyServer struct {
|
|
|
|
sync.RWMutex
|
|
|
|
isConnected map[int]bool
|
2020-11-25 06:55:13 +00:00
|
|
|
log *zerolog.Logger
|
2020-11-30 20:05:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewReadyServer initializes a ReadyServer and starts listening for dis/connection events.
|
2021-01-14 22:33:36 +00:00
|
|
|
func NewReadyServer(log *zerolog.Logger) *ReadyServer {
|
|
|
|
return &ReadyServer{
|
2020-11-30 20:05:37 +00:00
|
|
|
isConnected: make(map[int]bool, 0),
|
|
|
|
log: log,
|
|
|
|
}
|
2021-01-14 22:33:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (rs *ReadyServer) OnTunnelEvent(c conn.Event) {
|
|
|
|
switch c.EventType {
|
|
|
|
case conn.Connected:
|
|
|
|
rs.Lock()
|
|
|
|
rs.isConnected[int(c.Index)] = true
|
|
|
|
rs.Unlock()
|
|
|
|
case conn.Disconnected, conn.Reconnecting, conn.RegisteringTunnel:
|
|
|
|
rs.Lock()
|
|
|
|
rs.isConnected[int(c.Index)] = false
|
|
|
|
rs.Unlock()
|
|
|
|
case conn.SetURL:
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
rs.log.Error().Msgf("Unknown connection event case %v", c)
|
|
|
|
}
|
2020-11-30 20:05:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type body struct {
|
|
|
|
Status int `json:"status"`
|
|
|
|
ReadyConnections int `json:"readyConnections"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ServeHTTP responds with HTTP 200 if the tunnel is connected to the edge.
|
|
|
|
func (rs *ReadyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
statusCode, readyConnections := rs.makeResponse()
|
|
|
|
w.WriteHeader(statusCode)
|
|
|
|
body := body{
|
|
|
|
Status: statusCode,
|
|
|
|
ReadyConnections: readyConnections,
|
|
|
|
}
|
|
|
|
msg, err := json.Marshal(body)
|
|
|
|
if err != nil {
|
2020-11-25 06:55:13 +00:00
|
|
|
_, _ = fmt.Fprintf(w, `{"error": "%s"}`, err)
|
2020-11-30 20:05:37 +00:00
|
|
|
}
|
2020-11-25 06:55:13 +00:00
|
|
|
_, _ = w.Write(msg)
|
2020-11-30 20:05:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This is the bulk of the logic for ServeHTTP, broken into its own pure function
|
|
|
|
// to make unit testing easy.
|
|
|
|
func (rs *ReadyServer) makeResponse() (statusCode, readyConnections int) {
|
|
|
|
statusCode = http.StatusServiceUnavailable
|
|
|
|
rs.RLock()
|
|
|
|
defer rs.RUnlock()
|
|
|
|
for _, connected := range rs.isConnected {
|
|
|
|
if connected {
|
|
|
|
statusCode = http.StatusOK
|
|
|
|
readyConnections++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return statusCode, readyConnections
|
|
|
|
}
|