2016-03-20 17:44:58 +00:00
|
|
|
// Package etcd provides the etcd backend.
|
|
|
|
|
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
|
|
|
|
2016-03-20 17:54:21 +00:00
|
|
|
"github.com/miekg/coredns/middleware"
|
2016-03-20 21:36:55 +00:00
|
|
|
"github.com/miekg/coredns/middleware/etcd/msg"
|
2016-03-21 21:21:29 +00:00
|
|
|
"github.com/miekg/coredns/middleware/proxy"
|
2016-04-26 17:57:11 +01:00
|
|
|
"github.com/miekg/coredns/middleware/singleflight"
|
2016-03-20 17:44:58 +00:00
|
|
|
|
2016-03-20 18:17:07 +00:00
|
|
|
etcdc "github.com/coreos/etcd/client"
|
2016-03-20 17:44:58 +00:00
|
|
|
"golang.org/x/net/context"
|
|
|
|
|
)
|
|
|
|
|
|
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-03-25 20:26:42 +00:00
|
|
|
Stubmap *map[string]proxy.Proxy // List of proxies for stub resolving.
|
Allow debug queries to etcd middleware (#150)
With this you can retreive the raw data that the etcd middleware
used to create the reply. The debug data is put in TXT records
that are stuffed in the CH classs. This is only enabled if you
specify `debug` in the etcd stanza.
You can retrieve it by prefixing your query with 'o-o.debug.'
For instance:
; <<>> DiG 9.10.3-P4-Ubuntu <<>> @localhost -p 1053 SRV o-o.debug.production.*.skydns.local
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 47798
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 3
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;o-o.debug.production.*.skydns.local. IN SRV
;; ANSWER SECTION:
production.*.skydns.local. 154 IN SRV 10 50 8080 service1.example.com.
production.*.skydns.local. 154 IN SRV 10 50 8080 service2.example.com.
;; ADDITIONAL SECTION:
skydns.local.skydns.east.production.rails.1. 154 CH TXT "service1.example.com:8080(10,0,,false)[0,]"
skydns.local.skydns.west.production.rails.2. 154 CH TXT "service2.example.com:8080(10,0,,false)[0,]"
2016-05-22 21:16:26 +01:00
|
|
|
Debug bool // Do we allow debug queries.
|
2016-03-20 17:44:58 +00:00
|
|
|
}
|
2016-03-20 18:17:07 +00:00
|
|
|
|
2016-03-24 17:31:01 +00:00
|
|
|
// 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)
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get is a wrapper for client.Get that uses SingleInflight to suppress multiple outstanding queries.
|
2016-08-08 19:18:55 -07:00
|
|
|
func (e *Etcd) Get(path string, recursive bool) (*etcdc.Response, error) {
|
|
|
|
|
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-08-08 19:18:55 -07: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-08-08 19:18:55 -07: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-06-11 08:00:47 +10: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-03-20 21:36:55 +00:00
|
|
|
return serv.Ttl
|
|
|
|
|
}
|
|
|
|
|
if serv.Ttl == 0 {
|
2016-06-11 08:00:47 +10:00
|
|
|
return etcdTTL
|
2016-03-20 21:36:55 +00:00
|
|
|
}
|
2016-06-11 08:00:47 +10:00
|
|
|
if etcdTTL < serv.Ttl {
|
|
|
|
|
return etcdTTL
|
2016-03-20 21:36:55 +00:00
|
|
|
}
|
|
|
|
|
return serv.Ttl
|
2016-03-20 18:17:07 +00:00
|
|
|
}
|
2016-03-20 21:36:55 +00:00
|
|
|
|
2016-03-22 10:29:48 +00:00
|
|
|
// etcNameError checks if the error is ErrorCodeKeyNotFound from etcd.
|
|
|
|
|
func isEtcdNameError(err error) bool {
|
|
|
|
|
if e, ok := err.(etcdc.Error); ok && e.Code == etcdc.ErrorCodeKeyNotFound {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
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
|
2016-06-11 08:00:47 +10:00
|
|
|
minTTL = 60
|
2016-03-24 17:46:14 +00:00
|
|
|
hostmaster = "hostmaster"
|
|
|
|
|
etcdTimeout = 5 * time.Second
|
2016-03-20 21:36:55 +00:00
|
|
|
)
|