Skip to content

Commit

Permalink
HTTP probe: add support for HTTP methods, request payload and status …
Browse files Browse the repository at this point in the history
…validation

this extends the HTTP probe with additional properties related to the
outgoing HTTP request. additionally a response status validation can
be configured which is matched against the status line.

also the initialization has been refactored to error out on invalid
duration definitions instead of failing the probe execution.
  • Loading branch information
UiP9AV6Y committed Sep 6, 2024
1 parent bb72ca4 commit 9dedaac
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 55 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -342,13 +342,21 @@ probe "mongodb" {
probe "http" {
wait = true
http {
method = "post"
scheme = "http"
host = {
hostname = "localhost"
port = 8080
}
path = "/status"
timeout = "5s"
payload = "{\"body\": 123}"
# regex to match against the response status line
# (e.g. 403 Forbidden)
expectStatus = "(200|201)"
headers = {
Content-Type = "application/json"
}
}
}
Expand Down
12 changes: 8 additions & 4 deletions internal/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,15 @@ type SMTP struct {
Host
}

type HttpGet struct {
type HTTP struct {
Method string
Scheme string
Host
Path string
Timeout string
Path string
Timeout string
Payload string
ExpectStatus string
Headers map[string]string
}

type Probe struct {
Expand All @@ -58,7 +62,7 @@ type Probe struct {
Redis *Redis
MongoDB *MongoDB
Amqp *Amqp
HTTP *HttpGet
HTTP *HTTP
SMTP *SMTP
}

Expand Down
82 changes: 51 additions & 31 deletions pkg/probe/probe_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,78 +2,98 @@ package probe

import (
"fmt"
"net"
"net/http"
"net/url"
"regexp"
"strings"
"time"

"github.com/mittwald/mittnite/internal/config"
"github.com/mittwald/mittnite/internal/helper"
log "github.com/sirupsen/logrus"
)

type httpGetProbe struct {
type httpProbe struct {
method string
scheme string
host string
path string
timeout string
payload string
headers map[string]string
timeout time.Duration
status *regexp.Regexp
}

func NewHttpProbe(cfg *config.HttpGet) *httpGetProbe {
cfg.Scheme = helper.ResolveEnv(cfg.Scheme)
func NewHttpProbe(cfg *config.HTTP) (*httpProbe, error) {
cfg.Method = helper.SetDefaultStringIfEmpty(helper.ResolveEnv(cfg.Method), "GET", "method", "http")
cfg.Scheme = helper.SetDefaultStringIfEmpty(helper.ResolveEnv(cfg.Scheme), "http", "scheme", "http")
cfg.Hostname = helper.ResolveEnv(cfg.Hostname)
cfg.Port = helper.ResolveEnv(cfg.Port)
cfg.Path = helper.ResolveEnv(cfg.Path)
cfg.Timeout = helper.ResolveEnv(cfg.Timeout)

if cfg.Scheme == "" {
cfg.Scheme = "http"
}
cfg.Timeout = helper.SetDefaultStringIfEmpty(helper.ResolveEnv(cfg.Timeout), "5s", "timeout", "http")
cfg.ExpectStatus = helper.SetDefaultStringIfEmpty(helper.ResolveEnv(cfg.ExpectStatus), `(1|2|3)\d\d\s`, "expectStatus", "http")

method := strings.ToUpper(cfg.Method)
host := cfg.Hostname
if cfg.Port != "" {
host = fmt.Sprintf("%s:%s", cfg.Hostname, cfg.Port)
host = net.JoinHostPort(cfg.Hostname, cfg.Port)
}

status, err := regexp.Compile(cfg.ExpectStatus)
if err != nil {
return nil, fmt.Errorf("invalid HTTP status line regexp: %w", err)
}

connCfg := httpGetProbe{
timeout, err := time.ParseDuration(cfg.Timeout)
if err != nil {
return nil, fmt.Errorf("invalid timeout duration: %w", err)
}

connCfg := &httpProbe{
method: method,
scheme: cfg.Scheme,
host: host,
path: cfg.Path,
timeout: cfg.Timeout,
status: status,
timeout: timeout,
payload: cfg.Payload,
headers: cfg.Headers,
}

return &connCfg
return connCfg, nil
}

func (h *httpGetProbe) Exec() error {
timeout := time.Second * 5
if h.timeout != "" {
duration, err := time.ParseDuration(h.timeout)
if err == nil {
timeout = duration
} else {
return fmt.Errorf("invalid timeout duration: %s", err)
}
}

func (h *httpProbe) Exec() error {
u := url.URL{
Scheme: h.scheme,
Host: h.host,
Path: h.path,
}
urlStr := u.String()

client := &http.Client{
Timeout: timeout,
Timeout: h.timeout,
}
res, err := client.Get(urlStr)

data := strings.NewReader(h.payload)
req, err := http.NewRequest(h.method, u.String(), data)
if err != nil {
return err
}

for k, v := range h.headers {
req.Header.Set(k, v)
}

res, err := client.Do(req)
if err != nil {
return err
}

if res.StatusCode >= 200 && res.StatusCode < 400 {
log.WithFields(log.Fields{"kind": "probe", "name": "http", "status": "alive", "host": urlStr}).Debug()
return nil
if !h.status.MatchString(res.Status) {
return fmt.Errorf("http service %q returned status %q", urlStr, res.Status)
}

return fmt.Errorf("http service '%s' returned status code %d", urlStr, res.StatusCode)
log.WithFields(log.Fields{"kind": "probe", "name": "http", "status": "alive", "host": urlStr}).Debug()
return nil
}
46 changes: 26 additions & 20 deletions pkg/probe/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,29 +148,15 @@ func filterWaitProbes(cfg *config.Ignition, probes map[string]Probe) map[string]
}

func buildProbesFromConfig(cfg *config.Ignition) (map[string]Probe, error) {
result := make(map[string]Probe)

var errs []error

result := make(map[string]Probe)
for i := range cfg.Probes {
if cfg.Probes[i].Filesystem != "" {
result[cfg.Probes[i].Name] = &filesystemProbe{cfg.Probes[i].Filesystem}
} else if cfg.Probes[i].MySQL != nil {
result[cfg.Probes[i].Name] = NewMySQLProbe(cfg.Probes[i].MySQL)
} else if cfg.Probes[i].Redis != nil {
result[cfg.Probes[i].Name] = NewRedisProbe(cfg.Probes[i].Redis)
} else if cfg.Probes[i].MongoDB != nil {
var err error
result[cfg.Probes[i].Name], err = NewMongoDBProbe(cfg.Probes[i].MongoDB)
if err != nil {
errs = append(errs, err)
}
} else if cfg.Probes[i].Amqp != nil {
result[cfg.Probes[i].Name] = NewAmqpProbe(cfg.Probes[i].Amqp)
} else if cfg.Probes[i].HTTP != nil {
result[cfg.Probes[i].Name] = NewHttpProbe(cfg.Probes[i].HTTP)
} else if cfg.Probes[i].SMTP != nil {
result[cfg.Probes[i].Name] = NewSmtpProbe(cfg.Probes[i].SMTP)
p, err := newProbe(cfg.Probes[i])
if err != nil {
errs = append(errs, err)
} else if p != nil {
result[cfg.Probes[i].Name] = p
}
}

Expand All @@ -181,3 +167,23 @@ func buildProbesFromConfig(cfg *config.Ignition) (map[string]Probe, error) {

return result, err
}

func newProbe(p config.Probe) (Probe, error) {
if p.Filesystem != "" {
return &filesystemProbe{p.Filesystem}, nil
} else if p.MySQL != nil {
return NewMySQLProbe(p.MySQL), nil
} else if p.Redis != nil {
return NewRedisProbe(p.Redis), nil
} else if p.MongoDB != nil {
return NewMongoDBProbe(p.MongoDB)
} else if p.Amqp != nil {
return NewAmqpProbe(p.Amqp), nil
} else if p.HTTP != nil {
return NewHttpProbe(p.HTTP)
} else if p.SMTP != nil {
return NewSmtpProbe(p.SMTP), nil
}

return nil, nil
}

0 comments on commit 9dedaac

Please sign in to comment.