mirror of
https://github.com/coredns/coredns.git
synced 2026-03-29 00:05:36 -04:00
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:
committed by
Miek Gieben
parent
f3afd70021
commit
6fe27d99be
@@ -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.
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user