| 
									
										
										
										
											2016-09-25 08:39:20 +01:00
										 |  |  | // Package etcd provides the etcd backend middleware.
 | 
					
						
							| 
									
										
										
										
											2016-03-20 17:44:58 +00:00
										 |  |  | package etcd
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import (
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 	"encoding/json"
 | 
					
						
							| 
									
										
										
										
											2016-06-07 20:57:45 +01:00
										 |  |  | 	"fmt"
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 	"strings"
 | 
					
						
							| 
									
										
										
										
											2016-03-24 17:46:14 +00:00
										 |  |  | 	"time"
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-02-21 22:51:47 -08:00
										 |  |  | 	"github.com/coredns/coredns/middleware"
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/middleware/etcd/msg"
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/middleware/pkg/singleflight"
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/middleware/proxy"
 | 
					
						
							|  |  |  | 	"github.com/coredns/coredns/request"
 | 
					
						
							| 
									
										
										
										
											2016-03-20 17:44:58 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-20 18:17:07 +00:00
										 |  |  | 	etcdc "github.com/coreos/etcd/client"
 | 
					
						
							| 
									
										
										
										
											2016-10-30 15:54:16 +00:00
										 |  |  | 	"github.com/miekg/dns"
 | 
					
						
							| 
									
										
										
										
											2016-03-20 17:44:58 +00:00
										 |  |  | 	"golang.org/x/net/context"
 | 
					
						
							|  |  |  | )
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-23 09:14:12 +01:00
										 |  |  | // Etcd is a middleware talks to an etcd cluster.
 | 
					
						
							| 
									
										
										
										
											2016-03-22 22:44:50 +00:00
										 |  |  | type Etcd struct {
 | 
					
						
							|  |  |  | 	Next       middleware.Handler
 | 
					
						
							|  |  |  | 	Zones      []string
 | 
					
						
							| 
									
										
										
										
											2016-03-25 20:26:42 +00:00
										 |  |  | 	PathPrefix string
 | 
					
						
							| 
									
										
										
										
											2016-03-24 08:22:24 +00:00
										 |  |  | 	Proxy      proxy.Proxy // Proxy for looking up names during the resolution process
 | 
					
						
							| 
									
										
										
										
											2016-03-22 22:44:50 +00:00
										 |  |  | 	Client     etcdc.KeysAPI
 | 
					
						
							|  |  |  | 	Ctx        context.Context
 | 
					
						
							|  |  |  | 	Inflight   *singleflight.Group
 | 
					
						
							| 
									
										
										
										
											2016-08-08 21:42:39 -07:00
										 |  |  | 	Stubmap    *map[string]proxy.Proxy // list of proxies for stub resolving.
 | 
					
						
							| 
									
										
										
										
											2016-10-30 15:54:16 +00:00
										 |  |  | 	Debugging  bool                    // Do we allow debug queries.
 | 
					
						
							| 
									
										
										
										
											2016-09-26 14:43:38 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	endpoints []string // Stored here as well, to aid in testing.
 | 
					
						
							| 
									
										
										
										
											2016-03-20 17:44:58 +00:00
										 |  |  | }
 | 
					
						
							| 
									
										
										
										
											2016-03-20 18:17:07 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-30 15:54:16 +00:00
										 |  |  | // Services implements the ServiceBackend interface.
 | 
					
						
							|  |  |  | func (e *Etcd) Services(state request.Request, exact bool, opt middleware.Options) (services, debug []msg.Service, err error) {
 | 
					
						
							|  |  |  | 	services, err = e.Records(state.Name(), exact)
 | 
					
						
							|  |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		return
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	if opt.Debug != "" {
 | 
					
						
							|  |  |  | 		debug = services
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	services = msg.Group(services)
 | 
					
						
							|  |  |  | 	return
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-05 15:43:27 +00:00
										 |  |  | // Reverse implements the ServiceBackend interface.
 | 
					
						
							|  |  |  | func (e *Etcd) Reverse(state request.Request, exact bool, opt middleware.Options) (services, debug []msg.Service, err error) {
 | 
					
						
							|  |  |  | 	return e.Services(state, exact, opt)
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-30 15:54:16 +00:00
										 |  |  | // Lookup implements the ServiceBackend interface.
 | 
					
						
							|  |  |  | func (e *Etcd) Lookup(state request.Request, name string, typ uint16) (*dns.Msg, error) {
 | 
					
						
							|  |  |  | 	return e.Proxy.Lookup(state, name, typ)
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // IsNameError implements the ServiceBackend interface.
 | 
					
						
							|  |  |  | func (e *Etcd) IsNameError(err error) bool {
 | 
					
						
							|  |  |  | 	if ee, ok := err.(etcdc.Error); ok && ee.Code == etcdc.ErrorCodeKeyNotFound {
 | 
					
						
							|  |  |  | 		return true
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return false
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Debug implements the ServiceBackend interface.
 | 
					
						
							|  |  |  | func (e *Etcd) Debug() string {
 | 
					
						
							|  |  |  | 	return e.PathPrefix
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Records looks up records in etcd. If exact is true, it will lookup just this
 | 
					
						
							|  |  |  | // name. This is used when find matches when completing SRV lookups for instance.
 | 
					
						
							| 
									
										
										
										
											2016-08-08 19:18:55 -07:00
										 |  |  | func (e *Etcd) Records(name string, exact bool) ([]msg.Service, error) {
 | 
					
						
							|  |  |  | 	path, star := msg.PathWithWildcard(name, e.PathPrefix)
 | 
					
						
							| 
									
										
										
										
											2016-10-30 15:54:16 +00:00
										 |  |  | 	r, err := e.get(path, true)
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		return nil, err
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2016-08-08 19:18:55 -07:00
										 |  |  | 	segments := strings.Split(msg.Path(name, e.PathPrefix), "/")
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 	switch {
 | 
					
						
							|  |  |  | 	case exact && r.Node.Dir:
 | 
					
						
							|  |  |  | 		return nil, nil
 | 
					
						
							|  |  |  | 	case r.Node.Dir:
 | 
					
						
							| 
									
										
										
										
											2016-08-08 19:18:55 -07:00
										 |  |  | 		return e.loopNodes(r.Node.Nodes, segments, star, nil)
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 	default:
 | 
					
						
							| 
									
										
										
										
											2016-08-08 19:18:55 -07:00
										 |  |  | 		return e.loopNodes([]*etcdc.Node{r.Node}, segments, false, nil)
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 	}
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-10-30 15:54:16 +00:00
										 |  |  | // get is a wrapper for client.Get that uses SingleInflight to suppress multiple outstanding queries.
 | 
					
						
							|  |  |  | func (e *Etcd) get(path string, recursive bool) (*etcdc.Response, error) {
 | 
					
						
							| 
									
										
										
										
											2016-08-08 19:18:55 -07:00
										 |  |  | 	resp, err := e.Inflight.Do(path, func() (interface{}, error) {
 | 
					
						
							|  |  |  | 		ctx, cancel := context.WithTimeout(e.Ctx, etcdTimeout)
 | 
					
						
							| 
									
										
										
										
											2016-03-24 17:46:14 +00:00
										 |  |  | 		defer cancel()
 | 
					
						
							| 
									
										
										
										
											2016-08-08 19:18:55 -07:00
										 |  |  | 		r, e := e.Client.Get(ctx, path, &etcdc.GetOptions{Sort: false, Recursive: recursive})
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 		if e != nil {
 | 
					
						
							|  |  |  | 			return nil, e
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		return r, e
 | 
					
						
							|  |  |  | 	})
 | 
					
						
							|  |  |  | 	if err != nil {
 | 
					
						
							|  |  |  | 		return nil, err
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return resp.(*etcdc.Response), err
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // skydns/local/skydns/east/staging/web
 | 
					
						
							|  |  |  | // skydns/local/skydns/west/production/web
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // skydns/local/skydns/*/*/web
 | 
					
						
							|  |  |  | // skydns/local/skydns/*/web
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // loopNodes recursively loops through the nodes and returns all the values. The nodes' keyname
 | 
					
						
							|  |  |  | // will be match against any wildcards when star is true.
 | 
					
						
							| 
									
										
										
										
											2016-10-30 15:54:16 +00:00
										 |  |  | func (e *Etcd) loopNodes(ns []*etcdc.Node, nameParts []string, star bool, bx map[msg.Service]bool) (sx []msg.Service, err error) {
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 	if bx == nil {
 | 
					
						
							|  |  |  | 		bx = make(map[msg.Service]bool)
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | Nodes:
 | 
					
						
							|  |  |  | 	for _, n := range ns {
 | 
					
						
							|  |  |  | 		if n.Dir {
 | 
					
						
							| 
									
										
										
										
											2016-08-08 19:18:55 -07:00
										 |  |  | 			nodes, err := e.loopNodes(n.Nodes, nameParts, star, bx)
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 			if err != nil {
 | 
					
						
							|  |  |  | 				return nil, err
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 			sx = append(sx, nodes...)
 | 
					
						
							|  |  |  | 			continue
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		if star {
 | 
					
						
							|  |  |  | 			keyParts := strings.Split(n.Key, "/")
 | 
					
						
							|  |  |  | 			for i, n := range nameParts {
 | 
					
						
							|  |  |  | 				if i > len(keyParts)-1 {
 | 
					
						
							|  |  |  | 					// name is longer than key
 | 
					
						
							|  |  |  | 					continue Nodes
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				if n == "*" || n == "any" {
 | 
					
						
							|  |  |  | 					continue
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 				if keyParts[i] != n {
 | 
					
						
							|  |  |  | 					continue Nodes
 | 
					
						
							|  |  |  | 				}
 | 
					
						
							|  |  |  | 			}
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		serv := new(msg.Service)
 | 
					
						
							|  |  |  | 		if err := json.Unmarshal([]byte(n.Value), serv); err != nil {
 | 
					
						
							| 
									
										
										
										
											2016-06-07 20:57:45 +01:00
										 |  |  | 			return nil, fmt.Errorf("%s: %s", n.Key, err.Error())
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 		}
 | 
					
						
							| 
									
										
										
										
											2016-03-25 15:30:44 +00:00
										 |  |  | 		b := msg.Service{Host: serv.Host, Port: serv.Port, Priority: serv.Priority, Weight: serv.Weight, Text: serv.Text, Key: n.Key}
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 		if _, ok := bx[b]; ok {
 | 
					
						
							|  |  |  | 			continue
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		bx[b] = true
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		serv.Key = n.Key
 | 
					
						
							| 
									
										
										
										
											2016-09-23 09:14:12 +01:00
										 |  |  | 		serv.TTL = e.TTL(n, serv)
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 		if serv.Priority == 0 {
 | 
					
						
							|  |  |  | 			serv.Priority = priority
 | 
					
						
							|  |  |  | 		}
 | 
					
						
							|  |  |  | 		sx = append(sx, *serv)
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							|  |  |  | 	return sx, nil
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-11 08:00:47 +10:00
										 |  |  | // TTL returns the smaller of the etcd TTL and the service's
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | // TTL. If neither of these are set (have a zero value), a default is used.
 | 
					
						
							| 
									
										
										
										
											2016-08-08 19:18:55 -07:00
										 |  |  | func (e *Etcd) TTL(node *etcdc.Node, serv *msg.Service) uint32 {
 | 
					
						
							| 
									
										
										
										
											2016-06-11 08:00:47 +10:00
										 |  |  | 	etcdTTL := uint32(node.TTL)
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-09-23 09:14:12 +01:00
										 |  |  | 	if etcdTTL == 0 && serv.TTL == 0 {
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 		return ttl
 | 
					
						
							|  |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2016-06-11 08:00:47 +10:00
										 |  |  | 	if etcdTTL == 0 {
 | 
					
						
							| 
									
										
										
										
											2016-09-23 09:14:12 +01:00
										 |  |  | 		return serv.TTL
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2016-09-23 09:14:12 +01:00
										 |  |  | 	if serv.TTL == 0 {
 | 
					
						
							| 
									
										
										
										
											2016-06-11 08:00:47 +10:00
										 |  |  | 		return etcdTTL
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2016-09-23 09:14:12 +01:00
										 |  |  | 	if etcdTTL < serv.TTL {
 | 
					
						
							| 
									
										
										
										
											2016-06-11 08:00:47 +10:00
										 |  |  | 		return etcdTTL
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 	}
 | 
					
						
							| 
									
										
										
										
											2016-09-23 09:14:12 +01:00
										 |  |  | 	return serv.TTL
 | 
					
						
							| 
									
										
										
										
											2016-03-20 18:17:07 +00:00
										 |  |  | }
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | const (
 | 
					
						
							| 
									
										
										
										
											2016-03-24 17:46:14 +00:00
										 |  |  | 	priority    = 10  // default priority when nothing is set
 | 
					
						
							|  |  |  | 	ttl         = 300 // default ttl when nothing is set
 | 
					
						
							|  |  |  | 	etcdTimeout = 5 * time.Second
 | 
					
						
							| 
									
										
										
										
											2016-03-20 21:36:55 +00:00
										 |  |  | )
 |