plugin/etcdv3: Add etcd v3 plugin (#1702)

* Update dependencies and add etcdv3 client

* Update etcd plugin to support etcd v3 clients

Fixes #341
This commit is contained in:
Nitish Tiwari
2018-06-30 20:49:13 +05:30
committed by Miek Gieben
parent f3afd70021
commit 6fe27d99be
10327 changed files with 4196998 additions and 82 deletions

View File

@@ -2,11 +2,11 @@
## Name
*etcd* - enables reading zone data from an etcd instance.
*etcd* - enables reading zone data from an etcd version 3 instance.
## Description
The data in etcd has to be encoded as
The data in etcd instance has to be encoded as
a [message](https://github.com/skynetservices/skydns/blob/2fcff74cdc9f9a7dd64189a447ef27ac354b725f/msg/service.go#L26)
like [SkyDNS](https://github.com/skynetservices/skydns). It should also work just like SkyDNS.
@@ -21,7 +21,7 @@ etcd [ZONES...]
* **ZONES** zones etcd should be authoritative for.
The path will default to `/skydns` the local etcd proxy (http://localhost:2379). If no zones are
The path will default to `/skydns` the local etcd3 proxy (http://localhost:2379). If no zones are
specified the block's zone will be used as the zone.
If you want to `round robin` A and AAAA responses look at the `loadbalance` plugin.
@@ -169,7 +169,3 @@ dig +short skydns.local AAAA @localhost
2003::8:1
2003::8:2
~~~
## Bugs
Only the etcdv2 protocol is supported.

View File

@@ -1,9 +1,10 @@
// Package etcd provides the etcd backend plugin.
// Package etcd provides the etcd version 3 backend plugin.
package etcd
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
@@ -15,10 +16,19 @@ import (
"github.com/coredns/coredns/request"
"github.com/coredns/coredns/plugin/pkg/upstream"
etcdc "github.com/coreos/etcd/client"
etcdcv3 "github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/mvcc/mvccpb"
"github.com/miekg/dns"
)
const (
priority = 10 // default priority when nothing is set
ttl = 300 // default ttl when nothing is set
etcdTimeout = 5 * time.Second
)
var errKeyNotFound = errors.New("Key not found")
// Etcd is a plugin talks to an etcd cluster.
type Etcd struct {
Next plugin.Handler
@@ -26,7 +36,7 @@ type Etcd struct {
Zones []string
PathPrefix string
Upstream upstream.Upstream // Proxy for looking up names during the resolution process
Client etcdc.KeysAPI
Client *etcdcv3.Client
Ctx context.Context
Stubmap *map[string]proxy.Proxy // list of proxies for stub resolving.
@@ -56,10 +66,7 @@ func (e *Etcd) Lookup(state request.Request, name string, typ uint16) (*dns.Msg,
// 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
return err == errKeyNotFound
}
// Records looks up records in etcd. If exact is true, it will lookup just this
@@ -73,51 +80,50 @@ func (e *Etcd) Records(state request.Request, exact bool) ([]msg.Service, error)
return nil, err
}
segments := strings.Split(msg.Path(name, e.PathPrefix), "/")
switch {
case exact && r.Node.Dir:
return nil, nil
case r.Node.Dir:
return e.loopNodes(r.Node.Nodes, segments, star, nil)
default:
return e.loopNodes([]*etcdc.Node{r.Node}, segments, false, nil)
}
return e.loopNodes(r.Kvs, segments, star)
}
// get is a wrapper for client.Get
func (e *Etcd) get(path string, recursive bool) (*etcdc.Response, error) {
func (e *Etcd) get(path string, recursive bool) (*etcdcv3.GetResponse, error) {
ctx, cancel := context.WithTimeout(e.Ctx, etcdTimeout)
defer cancel()
r, err := e.Client.Get(ctx, path, &etcdc.GetOptions{Sort: false, Recursive: recursive})
if recursive == true {
if !strings.HasSuffix(path, "/") {
path = path + "/"
}
r, err := e.Client.Get(ctx, path, etcdcv3.WithPrefix())
if err != nil {
return nil, err
}
if r.Count == 0 {
path = strings.TrimSuffix(path, "/")
r, err = e.Client.Get(ctx, path)
if err != nil {
return nil, err
}
if r.Count == 0 {
return nil, errKeyNotFound
}
}
return r, nil
}
r, err := e.Client.Get(ctx, path)
if err != nil {
return nil, err
}
if r.Count == 0 {
return nil, errKeyNotFound
}
return r, nil
}
// 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 (e *Etcd) loopNodes(ns []*etcdc.Node, nameParts []string, star bool, bx map[msg.Service]bool) (sx []msg.Service, err error) {
if bx == nil {
bx = make(map[msg.Service]bool)
}
func (e *Etcd) loopNodes(kv []*mvccpb.KeyValue, nameParts []string, star bool) (sx []msg.Service, err error) {
bx := make(map[msg.Service]bool)
Nodes:
for _, n := range ns {
if n.Dir {
nodes, err := e.loopNodes(n.Nodes, nameParts, star, bx)
if err != nil {
return nil, err
}
sx = append(sx, nodes...)
continue
}
for _, n := range kv {
if star {
keyParts := strings.Split(n.Key, "/")
s := string(n.Key)
keyParts := strings.Split(s, "/")
for i, n := range nameParts {
if i > len(keyParts)-1 {
// name is longer than key
@@ -132,16 +138,16 @@ Nodes:
}
}
serv := new(msg.Service)
if err := json.Unmarshal([]byte(n.Value), serv); err != nil {
if err := json.Unmarshal(n.Value, serv); err != nil {
return nil, fmt.Errorf("%s: %s", n.Key, err.Error())
}
b := msg.Service{Host: serv.Host, Port: serv.Port, Priority: serv.Priority, Weight: serv.Weight, Text: serv.Text, Key: n.Key}
b := msg.Service{Host: serv.Host, Port: serv.Port, Priority: serv.Priority, Weight: serv.Weight, Text: serv.Text, Key: string(n.Key)}
if _, ok := bx[b]; ok {
continue
}
bx[b] = true
serv.Key = n.Key
serv.Key = string(n.Key)
serv.TTL = e.TTL(n, serv)
if serv.Priority == 0 {
serv.Priority = priority
@@ -153,8 +159,8 @@ Nodes:
// TTL returns the smaller of the etcd TTL and the service's
// TTL. If neither of these are set (have a zero value), a default is used.
func (e *Etcd) TTL(node *etcdc.Node, serv *msg.Service) uint32 {
etcdTTL := uint32(node.TTL)
func (e *Etcd) TTL(kv *mvccpb.KeyValue, serv *msg.Service) uint32 {
etcdTTL := uint32(kv.Lease)
if etcdTTL == 0 && serv.TTL == 0 {
return ttl
@@ -170,9 +176,3 @@ func (e *Etcd) TTL(node *etcdc.Node, serv *msg.Service) uint32 {
}
return serv.TTL
}
const (
priority = 10 // default priority when nothing is set
ttl = 300 // default ttl when nothing is set
etcdTimeout = 5 * time.Second
)

View File

@@ -65,8 +65,7 @@ func (e *Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (
// Do a fake A lookup, so we can distinguish between NODATA and NXDOMAIN
_, err = plugin.A(e, zone, state, nil, opt)
}
if e.IsNameError(err) {
if err != nil && e.IsNameError(err) {
if e.Fall.Through(state.Name()) {
return plugin.NextOrFailure(e.Name(), e.Next, ctx, w, r)
}

View File

@@ -15,7 +15,6 @@ import (
"github.com/coredns/coredns/plugin/proxy"
"github.com/coredns/coredns/plugin/test"
etcdc "github.com/coreos/etcd/client"
"github.com/miekg/dns"
)
@@ -300,12 +299,12 @@ func set(t *testing.T, e *Etcd, k string, ttl time.Duration, m *msg.Service) {
t.Fatal(err)
}
path, _ := msg.PathWithWildcard(k, e.PathPrefix)
e.Client.Set(ctxt, path, string(b), &etcdc.SetOptions{TTL: ttl})
e.Client.KV.Put(ctxt, path, string(b))
}
func delete(t *testing.T, e *Etcd, k string) {
path, _ := msg.PathWithWildcard(k, e.PathPrefix)
e.Client.Delete(ctxt, path, &etcdc.DeleteOptions{Recursive: false})
e.Client.Delete(ctxt, path)
}
func TestLookup(t *testing.T) {

View File

@@ -11,7 +11,7 @@ import (
"github.com/coredns/coredns/plugin/pkg/upstream"
"github.com/coredns/coredns/plugin/proxy"
etcdc "github.com/coreos/etcd/client"
etcdcv3 "github.com/coreos/etcd/clientv3"
"github.com/mholt/caddy"
)
@@ -124,22 +124,22 @@ func etcdParse(c *caddy.Controller) (*Etcd, bool, error) {
}
etc.Client = client
etc.endpoints = endpoints
return &etc, stubzones, nil
}
return &Etcd{}, false, nil
}
func newEtcdClient(endpoints []string, cc *tls.Config) (etcdc.KeysAPI, error) {
etcdCfg := etcdc.Config{
func newEtcdClient(endpoints []string, cc *tls.Config) (*etcdcv3.Client, error) {
etcdCfg := etcdcv3.Config{
Endpoints: endpoints,
Transport: mwtls.NewHTTPSTransport(cc),
TLS: cc,
}
cli, err := etcdc.New(etcdCfg)
cli, err := etcdcv3.New(etcdCfg)
if err != nil {
return nil, err
}
return etcdc.NewKeysAPI(cli), nil
return cli, nil
}
const defaultEndpoint = "http://localhost:2379"