mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-31 10:13:14 -04:00 
			
		
		
		
	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).
This commit is contained in:
		| @@ -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}, | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										45
									
								
								core/setup/chaos.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								core/setup/chaos.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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" | ||||
							
								
								
									
										61
									
								
								core/setup/chaos_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								core/setup/chaos_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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") | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										50
									
								
								middleware/chaos/chaos.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								middleware/chaos/chaos.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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] | ||||
| } | ||||
							
								
								
									
										26
									
								
								middleware/chaos/chaos.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								middleware/chaos/chaos.md
									
									
									
									
									
										Normal file
									
								
							| @@ -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` | ||||
| @@ -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) { | ||||
|   | ||||
| @@ -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 | ||||
| } | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user