add integration test (running against an actual dnsmasq)
This commit is contained in:
parent
d106190ef8
commit
b68bff846a
22
.travis.yml
Normal file
22
.travis.yml
Normal file
@ -0,0 +1,22 @@
|
||||
# Use the (faster) container-based infrastructure, see also
|
||||
# http://docs.travis-ci.com/user/workers/container-based-infrastructure/
|
||||
sudo: false
|
||||
dist: trusty
|
||||
services:
|
||||
- docker
|
||||
|
||||
language: go
|
||||
go:
|
||||
- "1.10"
|
||||
go_import_path: github.com/stapelberg/dnsmasq_exporter
|
||||
|
||||
|
||||
script:
|
||||
# Check whether files are syntactically correct.
|
||||
- "gofmt -l $(find . -name '*.go' | tr '\\n' ' ') >/dev/null"
|
||||
# Check whether files were not gofmt'ed.
|
||||
- "gosrc=$(find . -name '*.go' | tr '\\n' ' '); [ $(gofmt -l $gosrc 2>&- | wc -l) -eq 0 ] || (echo 'gofmt was not run on these files:'; gofmt -l $gosrc 2>&-; false)"
|
||||
- go tool vet .
|
||||
- go test -c
|
||||
- docker build --pull --no-cache --rm -t=dns -f travis/Dockerfile .
|
||||
- docker run -v $PWD:/usr/src:ro dns /bin/sh -c './dnsmasq_exporter.test -test.v'
|
35
dnsmasq.go
35
dnsmasq.go
@ -101,13 +101,14 @@ func init() {
|
||||
// be:
|
||||
// dig +short chaos txt cachesize.bind
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
promHandler := promhttp.Handler()
|
||||
dnsClient := &dns.Client{
|
||||
SingleInflight: true,
|
||||
}
|
||||
http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
|
||||
type server struct {
|
||||
promHandler http.Handler
|
||||
dnsClient *dns.Client
|
||||
dnsmasqAddr string
|
||||
leasesPath string
|
||||
}
|
||||
|
||||
func (s *server) metrics(w http.ResponseWriter, r *http.Request) {
|
||||
var eg errgroup.Group
|
||||
|
||||
eg.Go(func() error {
|
||||
@ -126,7 +127,7 @@ func main() {
|
||||
dns.Question{"servers.bind.", dns.TypeTXT, dns.ClassCHAOS},
|
||||
},
|
||||
}
|
||||
in, _, err := dnsClient.Exchange(msg, *dnsmasqAddr)
|
||||
in, _, err := s.dnsClient.Exchange(msg, s.dnsmasqAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -157,7 +158,7 @@ func main() {
|
||||
})
|
||||
|
||||
eg.Go(func() error {
|
||||
f, err := os.Open(*leasesPath)
|
||||
f, err := os.Open(s.leasesPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -179,7 +180,19 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
promHandler.ServeHTTP(w, r)
|
||||
})
|
||||
s.promHandler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
s := &server{
|
||||
promHandler: promhttp.Handler(),
|
||||
dnsClient: &dns.Client{
|
||||
SingleInflight: true,
|
||||
},
|
||||
dnsmasqAddr: *dnsmasqAddr,
|
||||
leasesPath: *leasesPath,
|
||||
}
|
||||
http.HandleFunc("/metrics", s.metrics)
|
||||
log.Fatal(http.ListenAndServe(*listen, nil))
|
||||
}
|
||||
|
152
dnsmasq_test.go
Normal file
152
dnsmasq_test.go
Normal file
@ -0,0 +1,152 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
func TestDnsmasqExporter(t *testing.T) {
|
||||
// NOTE(stapelberg): dnsmasq disables DNS operation upon --port=0 (as
|
||||
// opposed to picking a free port). Hence, we must pick one. This is
|
||||
// inherently prone to race conditions: another process could grab the port
|
||||
// between our ln.Close() and dnsmasq’s bind(). Ideally, dnsmasq would
|
||||
// support grabbing a free port and announcing it, or inheriting a listening
|
||||
// socket à la systemd socket activation.
|
||||
|
||||
ln, err := net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, port, err := net.SplitHostPort(ln.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ln.Close()
|
||||
|
||||
dnsmasq := exec.Command(
|
||||
"dnsmasq",
|
||||
"--port="+port,
|
||||
"--no-daemon",
|
||||
"--cache-size=666",
|
||||
"--bind-interfaces",
|
||||
"--interface=lo")
|
||||
dnsmasq.Stderr = os.Stderr
|
||||
fmt.Printf("starting %v\n", dnsmasq.Args)
|
||||
if err := dnsmasq.Start(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer dnsmasq.Process.Kill()
|
||||
|
||||
// Wait until dnsmasq started up
|
||||
resolver := &dns.Client{}
|
||||
for {
|
||||
// Cause a cache miss (dnsmasq must forward this query)
|
||||
var m dns.Msg
|
||||
m.SetQuestion("localhost.", dns.TypeA)
|
||||
if _, _, err := resolver.Exchange(&m, "localhost:"+port); err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(10 * time.Millisecond) // do not hog the CPU
|
||||
}
|
||||
|
||||
s := &server{
|
||||
promHandler: promhttp.Handler(),
|
||||
dnsClient: &dns.Client{
|
||||
SingleInflight: true,
|
||||
},
|
||||
dnsmasqAddr: "localhost:" + port,
|
||||
leasesPath: "testdata/dnsmasq.leases",
|
||||
}
|
||||
|
||||
t.Run("first", func(t *testing.T) {
|
||||
metrics := fetchMetrics(t, s)
|
||||
want := map[string]string{
|
||||
"dnsmasq_leases": "2",
|
||||
"dnsmasq_cachesize": "666",
|
||||
"dnsmasq_hits": "1",
|
||||
"dnsmasq_misses": "0",
|
||||
}
|
||||
for key, val := range want {
|
||||
if got, want := metrics[key], val; got != want {
|
||||
t.Errorf("metric %q: got %q, want %q", key, got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("second", func(t *testing.T) {
|
||||
metrics := fetchMetrics(t, s)
|
||||
want := map[string]string{
|
||||
"dnsmasq_leases": "2",
|
||||
"dnsmasq_cachesize": "666",
|
||||
"dnsmasq_hits": "2",
|
||||
"dnsmasq_misses": "0",
|
||||
}
|
||||
for key, val := range want {
|
||||
if got, want := metrics[key], val; got != want {
|
||||
t.Errorf("metric %q: got %q, want %q", key, got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Cause a cache miss (dnsmasq must forward this query)
|
||||
var m dns.Msg
|
||||
m.SetQuestion("no.such.domain.invalid.", dns.TypeA)
|
||||
if _, _, err := resolver.Exchange(&m, "localhost:"+port); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run("after query", func(t *testing.T) {
|
||||
metrics := fetchMetrics(t, s)
|
||||
want := map[string]string{
|
||||
"dnsmasq_leases": "2",
|
||||
"dnsmasq_cachesize": "666",
|
||||
"dnsmasq_hits": "3",
|
||||
"dnsmasq_misses": "1",
|
||||
}
|
||||
for key, val := range want {
|
||||
if got, want := metrics[key], val; got != want {
|
||||
t.Errorf("metric %q: got %q, want %q", key, got, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func fetchMetrics(t *testing.T, s *server) map[string]string {
|
||||
rec := httptest.NewRecorder()
|
||||
s.metrics(rec, httptest.NewRequest("GET", "/metrics", nil))
|
||||
resp := rec.Result()
|
||||
if got, want := resp.StatusCode, http.StatusOK; got != want {
|
||||
b, _ := ioutil.ReadAll(resp.Body)
|
||||
t.Fatalf("unexpected HTTP status: got %v (%v), want %v", resp.Status, string(b), want)
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
metrics := make(map[string]string)
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(body)), "\n") {
|
||||
if strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(line, " ")
|
||||
if len(parts) < 2 {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(parts[0], "dnsmasq_") {
|
||||
continue
|
||||
}
|
||||
metrics[parts[0]] = parts[1]
|
||||
}
|
||||
return metrics
|
||||
}
|
2
testdata/dnsmasq.leases
vendored
Normal file
2
testdata/dnsmasq.leases
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
lease1
|
||||
lease2
|
18
travis/Dockerfile
Normal file
18
travis/Dockerfile
Normal file
@ -0,0 +1,18 @@
|
||||
# vim:ft=Dockerfile
|
||||
FROM debian:sid
|
||||
|
||||
RUN echo force-unsafe-io > /etc/dpkg/dpkg.cfg.d/docker-apt-speedup
|
||||
# Paper over occasional network flakiness of some mirrors.
|
||||
RUN echo 'APT::Acquire::Retries "5";' > /etc/apt/apt.conf.d/80retry
|
||||
|
||||
# NOTE: I tried exclusively using gce_debian_mirror.storage.googleapis.com
|
||||
# instead of httpredir.debian.org, but the results (Fetched 123 MB in 36s (3357
|
||||
# kB/s)) are not any better than httpredir.debian.org (Fetched 123 MB in 34s
|
||||
# (3608 kB/s)). Hence, let’s stick with httpredir.debian.org (default) for now.
|
||||
|
||||
RUN apt-get update && \
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
dnsmasq && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /usr/src
|
Loading…
x
Reference in New Issue
Block a user