164 lines
3.8 KiB
Go
164 lines
3.8 KiB
Go
// Package etcd provides the etcd server Backend implementation,
|
|
package etcd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/miekg/coredns/middleware/etcd/msg"
|
|
|
|
etcdc "github.com/coreos/etcd/client"
|
|
)
|
|
|
|
const (
|
|
priority = 10 // default priority when nothing is set
|
|
ttl = 3600 // default ttl when nothing is set
|
|
)
|
|
|
|
func (g *Backend) Records(name string, exact bool) ([]msg.Service, error) {
|
|
path, star := msg.PathWithWildcard(name)
|
|
r, err := g.get(path, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
segments := strings.Split(msg.Path(name), "/")
|
|
switch {
|
|
case exact && r.Node.Dir:
|
|
return nil, nil
|
|
case r.Node.Dir:
|
|
return g.loopNodes(r.Node.Nodes, segments, star, nil)
|
|
default:
|
|
return g.loopNodes([]*etcdc.Node{r.Node}, segments, false, nil)
|
|
}
|
|
}
|
|
|
|
func (g *Backend) ReverseRecord(name string) (*msg.Service, error) {
|
|
path, star := msg.PathWithWildcard(name)
|
|
if star {
|
|
return nil, fmt.Errorf("reverse can not contain wildcards")
|
|
}
|
|
r, err := g.get(path, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if r.Node.Dir {
|
|
return nil, fmt.Errorf("reverse must not be a directory")
|
|
}
|
|
segments := strings.Split(msg.Path(name), "/")
|
|
records, err := g.loopNodes([]*etcdc.Node{r.Node}, segments, false, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(records) != 1 {
|
|
return nil, fmt.Errorf("must be only one service record")
|
|
}
|
|
return &records[0], nil
|
|
}
|
|
|
|
// get is a wrapper for client.Get that uses SingleInflight to suppress multiple
|
|
// outstanding queries.
|
|
func (g *Backend) get(path string, recursive bool) (*etcdc.Response, error) {
|
|
resp, err := g.inflight.Do(path, func() (interface{}, error) {
|
|
r, e := g.client.Get(g.ctx, path, &etcdc.GetOptions{Sort: false, Recursive: recursive})
|
|
if e != nil {
|
|
return nil, e
|
|
}
|
|
return r, e
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return resp.(*etcdc.Response), err
|
|
}
|
|
|
|
type bareService struct {
|
|
Host string
|
|
Port int
|
|
Priority int
|
|
Weight int
|
|
Text string
|
|
}
|
|
|
|
// 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.
|
|
func (g *Backend) loopNodes(ns []*etcdc.Node, nameParts []string, star bool, bx map[bareService]bool) (sx []msg.Service, err error) {
|
|
if bx == nil {
|
|
bx = make(map[bareService]bool)
|
|
}
|
|
Nodes:
|
|
for _, n := range ns {
|
|
if n.Dir {
|
|
nodes, err := g.loopNodes(n.Nodes, nameParts, star, bx)
|
|
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 {
|
|
return nil, err
|
|
}
|
|
b := bareService{serv.Host, serv.Port, serv.Priority, serv.Weight, serv.Text}
|
|
if _, ok := bx[b]; ok {
|
|
continue
|
|
}
|
|
bx[b] = true
|
|
|
|
serv.Key = n.Key
|
|
serv.Ttl = g.calculateTtl(n, serv)
|
|
if serv.Priority == 0 {
|
|
serv.Priority = priority
|
|
}
|
|
sx = append(sx, *serv)
|
|
}
|
|
return sx, nil
|
|
}
|
|
|
|
// calculateTtl returns the smaller of the etcd TTL and the service's
|
|
// TTL. If neither of these are set (have a zero value), the server
|
|
// default is used.
|
|
func (g *Backend) calculateTtl(node *etcdc.Node, serv *msg.Service) uint32 {
|
|
etcdTtl := uint32(node.TTL)
|
|
|
|
if etcdTtl == 0 && serv.Ttl == 0 {
|
|
return ttl
|
|
}
|
|
if etcdTtl == 0 {
|
|
return serv.Ttl
|
|
}
|
|
if serv.Ttl == 0 {
|
|
return etcdTtl
|
|
}
|
|
if etcdTtl < serv.Ttl {
|
|
return etcdTtl
|
|
}
|
|
return serv.Ttl
|
|
}
|
|
|
|
// Client exposes the underlying Etcd client (used in tests).
|
|
func (g *Backend) Client() etcdc.KeysAPI {
|
|
return g.client
|
|
}
|