From 5d70567f1cd50f5191bb050c277684028fd85e72 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Thu, 24 Mar 2016 17:58:31 +0000 Subject: [PATCH] Implement chaos middleware This allows for CH TXT queries that return some information about the server and/or the authors (or whatever you put in there). --- core/directives.go | 4 +-- core/setup/chaos.go | 45 +++++++++++++++++++++++ core/setup/chaos_test.go | 61 ++++++++++++++++++++++++++++++++ middleware/chaos/chaos.go | 50 ++++++++++++++++++++++++++ middleware/chaos/chaos.md | 26 ++++++++++++++ middleware/errors/errors_test.go | 4 +-- middleware/recorder.go | 4 +-- 7 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 core/setup/chaos.go create mode 100644 core/setup/chaos_test.go create mode 100644 middleware/chaos/chaos.go create mode 100644 middleware/chaos/chaos.md diff --git a/core/directives.go b/core/directives.go index 27d379a0d..14f52ced8 100644 --- a/core/directives.go +++ b/core/directives.go @@ -52,14 +52,14 @@ var directiveOrder = []directive{ // Directives that inject handlers (middleware) {"prometheus", setup.Prometheus}, + {"chaos", setup.Chaos}, {"rewrite", setup.Rewrite}, - {"file", setup.File}, {"loadbalance", setup.Loadbalance}, {"log", setup.Log}, {"errors", setup.Errors}, - {"etcd", setup.Etcd}, {"file", setup.File}, + {"etcd", setup.Etcd}, {"proxy", setup.Proxy}, } diff --git a/core/setup/chaos.go b/core/setup/chaos.go new file mode 100644 index 000000000..13103adf4 --- /dev/null +++ b/core/setup/chaos.go @@ -0,0 +1,45 @@ +package setup + +import ( + "github.com/miekg/coredns/middleware" + "github.com/miekg/coredns/middleware/chaos" +) + +// Chaos configures a new Chaos middleware instance. +func Chaos(c *Controller) (middleware.Middleware, error) { + version, authors, err := chaosParse(c) + if err != nil { + return nil, err + } + + return func(next middleware.Handler) middleware.Handler { + return chaos.Chaos{ + Next: next, + Version: version, + Authors: authors, + } + }, nil +} + +func chaosParse(c *Controller) (string, map[string]bool, error) { + version := "" + authors := make(map[string]bool) + + for c.Next() { + args := c.RemainingArgs() + if len(args) == 0 { + return defaultVersion, nil, nil + } + if len(args) == 1 { + return args[0], nil, nil + } + version = args[0] + for _, a := range args[1:] { + authors[a] = true + } + return version, authors, nil + } + return version, authors, nil +} + +const defaultVersion = "CoreDNS" diff --git a/core/setup/chaos_test.go b/core/setup/chaos_test.go new file mode 100644 index 000000000..a745545fc --- /dev/null +++ b/core/setup/chaos_test.go @@ -0,0 +1,61 @@ +package setup + +import ( + "fmt" + "strings" + "testing" +) + +func TestChaos(t *testing.T) { + tests := []struct { + input string + shouldErr bool + expectedVersion string // expected veresion. + expectedAuthor string // expected author (string, although we get a map). + expectedErrContent string // substring from the expected error. Empty for positive cases. + }{ + // positive + { + `chaos`, false, defaultVersion, "", "", + }, + { + `chaos v2`, false, "v2", "", "", + }, + { + `chaos v3 "Miek Gieben"`, false, "v3", "Miek Gieben", "", + }, + { + fmt.Sprintf(`chaos { + %s + }`, defaultVersion), false, defaultVersion, "", "", + }, + } + + for i, test := range tests { + c := NewTestController(test.input) + version, authors, err := chaosParse(c) + + if test.shouldErr && err == nil { + t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input) + } + + if err != nil { + if !test.shouldErr { + t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err) + } + + if !strings.Contains(err.Error(), test.expectedErrContent) { + t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input) + } + } + + if !test.shouldErr && version != test.expectedVersion { + t.Errorf("Chaos not correctly set for input %s. Expected: %s, actual: %s", test.input, test.expectedVersion, version) + } + if !test.shouldErr && authors != nil { + if _, ok := authors[test.expectedAuthor]; !ok { + t.Errorf("Chaos not correctly set for input %s. Expected: '%s', actual: '%s'", test.input, test.expectedAuthor, "Miek Gieben") + } + } + } +} diff --git a/middleware/chaos/chaos.go b/middleware/chaos/chaos.go new file mode 100644 index 000000000..a7a63c1a1 --- /dev/null +++ b/middleware/chaos/chaos.go @@ -0,0 +1,50 @@ +package chaos + +import ( + "os" + + "github.com/miekg/coredns/middleware" + + "github.com/miekg/dns" + "golang.org/x/net/context" +) + +type Chaos struct { + Next middleware.Handler + Version string + Authors map[string]bool // get randomization for free \o/ +} + +func (c Chaos) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { + state := middleware.State{W: w, Req: r} + if state.QClass() != dns.ClassINET || state.QType() != dns.TypeTXT { + return c.Next.ServeDNS(ctx, w, r) + } + m := new(dns.Msg) + hdr := dns.RR_Header{Name: state.QName(), Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} + switch state.Name() { + default: + return c.Next.ServeDNS(ctx, w, r) + case "authors.bind.": + for a, _ := range c.Authors { + m.Answer = append(m.Answer, &dns.TXT{Hdr: hdr, Txt: []string{trim(a)}}) + } + case "version.bind.", "version.server.": + m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{trim(c.Version)}}} + case "hostname.bind.", "id.server.": + hostname, err := os.Hostname() + if err != nil { + hostname = "localhost" + } + m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{trim(hostname)}}} + } + w.WriteMsg(m) + return 0, nil +} + +func trim(s string) string { + if len(s) < 256 { + return s + } + return s[:255] +} diff --git a/middleware/chaos/chaos.md b/middleware/chaos/chaos.md new file mode 100644 index 000000000..45d022fce --- /dev/null +++ b/middleware/chaos/chaos.md @@ -0,0 +1,26 @@ +# chaos + +`chaos` + +## Syntax + +~~~ +chaos [version] [authors...] +~~~ + +* `version` the version to return, defaults to CoreDNS. +* `authors` what authors to return. No default. + +## Examples + +~~~ +etcd { + path /skydns + endpoint endpoint... + stubzones +} +~~~ + +* `path` /skydns +* `endpoint` endpoints... +* `stubzones` diff --git a/middleware/errors/errors_test.go b/middleware/errors/errors_test.go index 77783bf6d..c0a1dfb7b 100644 --- a/middleware/errors/errors_test.go +++ b/middleware/errors/errors_test.go @@ -8,10 +8,10 @@ import ( "strings" "testing" - "golang.org/x/net/context" - "github.com/miekg/coredns/middleware" + "github.com/miekg/dns" + "golang.org/x/net/context" ) func TestErrors(t *testing.T) { diff --git a/middleware/recorder.go b/middleware/recorder.go index 94fe20c8e..19a15463e 100644 --- a/middleware/recorder.go +++ b/middleware/recorder.go @@ -66,8 +66,8 @@ func (r *ResponseRecorder) Start() time.Time { return r.start } -// Reply returns the written message from the ResponseRecorder. -func (r *ResponseRecorder) Reply() *dns.Msg { +// Msg returns the written message from the ResponseRecorder. +func (r *ResponseRecorder) Msg() *dns.Msg { return r.msg }