Remove grpc watch functionality (#2549)

This was added, but didn't see any use. For a large, complex chunk of
code we should have some users of it.

Remove all watch functionally from plugins, servers and packages.

Fixes: #2548

Signed-off-by: Miek Gieben <miek@miek.nl>
This commit is contained in:
Miek Gieben
2019-02-11 14:46:53 +00:00
committed by GitHub
parent f69819387d
commit 29cb00aada
19 changed files with 92 additions and 1018 deletions

View File

@@ -3,7 +3,6 @@ package federation
import (
"github.com/coredns/coredns/plugin/kubernetes"
"github.com/coredns/coredns/plugin/kubernetes/object"
"github.com/coredns/coredns/plugin/pkg/watch"
api "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -19,9 +18,6 @@ func (APIConnFederationTest) Stop() error { return
func (APIConnFederationTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnFederationTest) EpIndexReverse(string) []*object.Endpoints { return nil }
func (APIConnFederationTest) Modified() int64 { return 0 }
func (APIConnFederationTest) SetWatchChan(watch.Chan) {}
func (APIConnFederationTest) Watch(string) error { return nil }
func (APIConnFederationTest) StopWatching(string) {}
func (APIConnFederationTest) PodIndex(string) []*object.Pod {
return []*object.Pod{

View File

@@ -7,7 +7,6 @@ import (
"github.com/coredns/coredns/plugin/kubernetes"
"github.com/coredns/coredns/plugin/kubernetes/object"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/pkg/watch"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
@@ -158,9 +157,6 @@ func (external) Stop() error { return nil }
func (external) EpIndexReverse(string) []*object.Endpoints { return nil }
func (external) SvcIndexReverse(string) []*object.Service { return nil }
func (external) Modified() int64 { return 0 }
func (external) SetWatchChan(watch.Chan) {}
func (external) Watch(string) error { return nil }
func (external) StopWatching(string) {}
func (external) EpIndex(s string) []*object.Endpoints { return nil }
func (external) EndpointsList() []*object.Endpoints { return nil }
func (external) GetNodeByName(name string) (*api.Node, error) { return nil, nil }

View File

@@ -112,11 +112,6 @@ kubernetes [ZONES...] {
This plugin implements dynamic health checking. Currently this is limited to reporting healthy when
the API has synced.
## Watch
This plugin implements watch. A client that connects to CoreDNS using `coredns/client` can be notified
of changes to A, AAAA, and SRV records for Kubernetes services and endpoints.
## Examples
Handle all queries in the `cluster.local` zone. Connect to Kubernetes in-cluster. Also handle all

View File

@@ -8,13 +8,11 @@ import (
"time"
"github.com/coredns/coredns/plugin/kubernetes/object"
dnswatch "github.com/coredns/coredns/plugin/pkg/watch"
api "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
)
@@ -45,11 +43,6 @@ type dnsController interface {
// Modified returns the timestamp of the most recent changes
Modified() int64
// Watch-related items
SetWatchChan(dnswatch.Chan)
Watch(string) error
StopWatching(string)
}
type dnsControl struct {
@@ -79,9 +72,6 @@ type dnsControl struct {
shutdown bool
stopCh chan struct{}
// watch-related items channel
watchChan dnswatch.Chan
watched map[string]struct{}
zones []string
endpointNameMode bool
}
@@ -105,7 +95,6 @@ func newdnsController(kubeClient kubernetes.Interface, opts dnsControlOpts) *dns
client: kubeClient,
selector: opts.selector,
stopCh: make(chan struct{}),
watched: make(map[string]struct{}),
zones: opts.zones,
endpointNameMode: opts.endpointNameMode,
}
@@ -117,7 +106,7 @@ func newdnsController(kubeClient kubernetes.Interface, opts dnsControlOpts) *dns
},
&api.Service{},
opts.resyncPeriod,
cache.ResourceEventHandlerFuncs{AddFunc: dns.Add, UpdateFunc: dns.Update, DeleteFunc: dns.Delete},
cache.ResourceEventHandlerFuncs{},
cache.Indexers{svcNameNamespaceIndex: svcNameNamespaceIndexFunc, svcIPIndex: svcIPIndexFunc},
object.ToService,
)
@@ -130,7 +119,7 @@ func newdnsController(kubeClient kubernetes.Interface, opts dnsControlOpts) *dns
},
&api.Pod{},
opts.resyncPeriod,
cache.ResourceEventHandlerFuncs{AddFunc: dns.Add, UpdateFunc: dns.Update, DeleteFunc: dns.Delete},
cache.ResourceEventHandlerFuncs{},
cache.Indexers{podIPIndex: podIPIndexFunc},
object.ToPod,
)
@@ -144,7 +133,7 @@ func newdnsController(kubeClient kubernetes.Interface, opts dnsControlOpts) *dns
},
&api.Endpoints{},
opts.resyncPeriod,
cache.ResourceEventHandlerFuncs{AddFunc: dns.Add, UpdateFunc: dns.Update, DeleteFunc: dns.Delete},
cache.ResourceEventHandlerFuncs{},
cache.Indexers{epNameNamespaceIndex: epNameNamespaceIndexFunc, epIPIndex: epIPIndexFunc},
object.ToEndpoints)
}
@@ -223,26 +212,6 @@ func podListFunc(c kubernetes.Interface, ns string, s labels.Selector) func(meta
}
}
func serviceWatchFunc(c kubernetes.Interface, ns string, s labels.Selector) func(options meta.ListOptions) (watch.Interface, error) {
return func(options meta.ListOptions) (watch.Interface, error) {
if s != nil {
options.LabelSelector = s.String()
}
w, err := c.CoreV1().Services(ns).Watch(options)
return w, err
}
}
func podWatchFunc(c kubernetes.Interface, ns string, s labels.Selector) func(options meta.ListOptions) (watch.Interface, error) {
return func(options meta.ListOptions) (watch.Interface, error) {
if s != nil {
options.LabelSelector = s.String()
}
w, err := c.CoreV1().Pods(ns).Watch(options)
return w, err
}
}
func endpointsListFunc(c kubernetes.Interface, ns string, s labels.Selector) func(meta.ListOptions) (runtime.Object, error) {
return func(opts meta.ListOptions) (runtime.Object, error) {
if s != nil {
@@ -253,16 +222,6 @@ func endpointsListFunc(c kubernetes.Interface, ns string, s labels.Selector) fun
}
}
func endpointsWatchFunc(c kubernetes.Interface, ns string, s labels.Selector) func(options meta.ListOptions) (watch.Interface, error) {
return func(options meta.ListOptions) (watch.Interface, error) {
if s != nil {
options.LabelSelector = s.String()
}
w, err := c.CoreV1().Endpoints(ns).Watch(options)
return w, err
}
}
func namespaceListFunc(c kubernetes.Interface, s labels.Selector) func(meta.ListOptions) (runtime.Object, error) {
return func(opts meta.ListOptions) (runtime.Object, error) {
if s != nil {
@@ -273,27 +232,6 @@ func namespaceListFunc(c kubernetes.Interface, s labels.Selector) func(meta.List
}
}
func namespaceWatchFunc(c kubernetes.Interface, s labels.Selector) func(options meta.ListOptions) (watch.Interface, error) {
return func(options meta.ListOptions) (watch.Interface, error) {
if s != nil {
options.LabelSelector = s.String()
}
w, err := c.CoreV1().Namespaces().Watch(options)
return w, err
}
}
func (dns *dnsControl) SetWatchChan(c dnswatch.Chan) { dns.watchChan = c }
func (dns *dnsControl) StopWatching(qname string) { delete(dns.watched, qname) }
func (dns *dnsControl) Watch(qname string) error {
if dns.watchChan == nil {
return fmt.Errorf("cannot start watch because the channel has not been set")
}
dns.watched[qname] = struct{}{}
return nil
}
// Stop stops the controller.
func (dns *dnsControl) Stop() error {
dns.stopLock.Lock()

View File

@@ -5,7 +5,6 @@ import (
"github.com/coredns/coredns/plugin/etcd/msg"
"github.com/coredns/coredns/plugin/kubernetes/object"
"github.com/coredns/coredns/plugin/pkg/watch"
"github.com/coredns/coredns/plugin/test"
"github.com/coredns/coredns/request"
@@ -85,9 +84,6 @@ func (external) Stop() error { return nil }
func (external) EpIndexReverse(string) []*object.Endpoints { return nil }
func (external) SvcIndexReverse(string) []*object.Service { return nil }
func (external) Modified() int64 { return 0 }
func (external) SetWatchChan(watch.Chan) {}
func (external) Watch(string) error { return nil }
func (external) StopWatching(string) {}
func (external) EpIndex(s string) []*object.Endpoints { return nil }
func (external) EndpointsList() []*object.Endpoints { return nil }
func (external) GetNodeByName(name string) (*api.Node, error) { return nil, nil }

View File

@@ -7,7 +7,6 @@ import (
"github.com/coredns/coredns/plugin/kubernetes/object"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/pkg/watch"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
@@ -441,9 +440,6 @@ func (APIConnServeTest) Stop() error { return nil
func (APIConnServeTest) EpIndexReverse(string) []*object.Endpoints { return nil }
func (APIConnServeTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnServeTest) Modified() int64 { return time.Now().Unix() }
func (APIConnServeTest) SetWatchChan(watch.Chan) {}
func (APIConnServeTest) Watch(string) error { return nil }
func (APIConnServeTest) StopWatching(string) {}
func (APIConnServeTest) PodIndex(string) []*object.Pod {
a := []*object.Pod{

View File

@@ -5,7 +5,6 @@ import (
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/kubernetes/object"
"github.com/coredns/coredns/plugin/pkg/watch"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
@@ -66,9 +65,6 @@ func (APIConnServiceTest) PodIndex(string) []*object.Pod { return ni
func (APIConnServiceTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnServiceTest) EpIndexReverse(string) []*object.Endpoints { return nil }
func (APIConnServiceTest) Modified() int64 { return 0 }
func (APIConnServiceTest) SetWatchChan(watch.Chan) {}
func (APIConnServiceTest) Watch(string) error { return nil }
func (APIConnServiceTest) StopWatching(string) {}
func (APIConnServiceTest) SvcIndex(string) []*object.Service {
svcs := []*object.Service{

View File

@@ -4,7 +4,6 @@ import (
"testing"
"github.com/coredns/coredns/plugin/kubernetes/object"
"github.com/coredns/coredns/plugin/pkg/watch"
api "k8s.io/api/core/v1"
)
@@ -20,9 +19,6 @@ func (APIConnTest) SvcIndexReverse(string) []*object.Service { return nil }
func (APIConnTest) EpIndex(string) []*object.Endpoints { return nil }
func (APIConnTest) EndpointsList() []*object.Endpoints { return nil }
func (APIConnTest) Modified() int64 { return 0 }
func (APIConnTest) SetWatchChan(watch.Chan) {}
func (APIConnTest) Watch(string) error { return nil }
func (APIConnTest) StopWatching(string) {}
func (APIConnTest) ServiceList() []*object.Service {
svcs := []*object.Service{

View File

@@ -6,7 +6,6 @@ import (
"github.com/coredns/coredns/plugin/kubernetes/object"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/pkg/watch"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
@@ -24,9 +23,6 @@ func (APIConnReverseTest) EpIndex(string) []*object.Endpoints { return nil }
func (APIConnReverseTest) EndpointsList() []*object.Endpoints { return nil }
func (APIConnReverseTest) ServiceList() []*object.Service { return nil }
func (APIConnReverseTest) Modified() int64 { return 0 }
func (APIConnReverseTest) SetWatchChan(watch.Chan) {}
func (APIConnReverseTest) Watch(string) error { return nil }
func (APIConnReverseTest) StopWatching(string) {}
func (APIConnReverseTest) SvcIndex(svc string) []*object.Service {
if svc != "svc1.testns" {

View File

@@ -1,194 +1,48 @@
package kubernetes
import (
"github.com/coredns/coredns/plugin/kubernetes/object"
"github.com/coredns/coredns/plugin/pkg/watch"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
)
// SetWatchChan implements watch.Watchable
func (k *Kubernetes) SetWatchChan(c watch.Chan) {
k.APIConn.SetWatchChan(c)
}
// Watch is called when a watch is started for a name.
func (k *Kubernetes) Watch(qname string) error {
return k.APIConn.Watch(qname)
}
// StopWatching is called when no more watches remain for a name
func (k *Kubernetes) StopWatching(qname string) {
k.APIConn.StopWatching(qname)
}
var _ watch.Watchable = &Kubernetes{}
func (dns *dnsControl) sendServiceUpdates(s *object.Service) {
for i := range dns.zones {
name := serviceFQDN(s, dns.zones[i])
if _, ok := dns.watched[name]; ok {
dns.watchChan <- name
func serviceWatchFunc(c kubernetes.Interface, ns string, s labels.Selector) func(options meta.ListOptions) (watch.Interface, error) {
return func(options meta.ListOptions) (watch.Interface, error) {
if s != nil {
options.LabelSelector = s.String()
}
w, err := c.CoreV1().Services(ns).Watch(options)
return w, err
}
}
func (dns *dnsControl) sendPodUpdates(p *object.Pod) {
for i := range dns.zones {
name := podFQDN(p, dns.zones[i])
if _, ok := dns.watched[name]; ok {
dns.watchChan <- name
func podWatchFunc(c kubernetes.Interface, ns string, s labels.Selector) func(options meta.ListOptions) (watch.Interface, error) {
return func(options meta.ListOptions) (watch.Interface, error) {
if s != nil {
options.LabelSelector = s.String()
}
w, err := c.CoreV1().Pods(ns).Watch(options)
return w, err
}
}
func (dns *dnsControl) sendEndpointsUpdates(ep *object.Endpoints) {
for _, zone := range dns.zones {
for _, name := range endpointFQDN(ep, zone, dns.endpointNameMode) {
if _, ok := dns.watched[name]; ok {
dns.watchChan <- name
}
}
name := serviceFQDN(ep, zone)
if _, ok := dns.watched[name]; ok {
dns.watchChan <- name
func endpointsWatchFunc(c kubernetes.Interface, ns string, s labels.Selector) func(options meta.ListOptions) (watch.Interface, error) {
return func(options meta.ListOptions) (watch.Interface, error) {
if s != nil {
options.LabelSelector = s.String()
}
w, err := c.CoreV1().Endpoints(ns).Watch(options)
return w, err
}
}
// endpointsSubsetDiffs returns an Endpoints struct containing the Subsets that have changed between a and b.
// When we notify clients of changed endpoints we only want to notify them of endpoints that have changed.
// The Endpoints API object holds more than one endpoint, held in a list of Subsets. Each Subset refers to
// an endpoint. So, here we create a new Endpoints struct, and populate it with only the endpoints that have changed.
// This new Endpoints object is later used to generate the list of endpoint FQDNs to send to the client.
// This function computes this literally by combining the sets (in a and not in b) union (in b and not in a).
func endpointsSubsetDiffs(a, b *object.Endpoints) *object.Endpoints {
c := b.CopyWithoutSubsets()
// In the following loop, the first iteration computes (in a but not in b).
// The second iteration then adds (in b but not in a)
// The end result is an Endpoints that only contains the subsets (endpoints) that are different between a and b.
for _, abba := range [][]*object.Endpoints{{a, b}, {b, a}} {
a := abba[0]
b := abba[1]
left:
for _, as := range a.Subsets {
for _, bs := range b.Subsets {
if subsetsEquivalent(as, bs) {
continue left
}
}
c.Subsets = append(c.Subsets, as)
func namespaceWatchFunc(c kubernetes.Interface, s labels.Selector) func(options meta.ListOptions) (watch.Interface, error) {
return func(options meta.ListOptions) (watch.Interface, error) {
if s != nil {
options.LabelSelector = s.String()
}
}
return c
}
// sendUpdates sends a notification to the server if a watch is enabled for the qname.
func (dns *dnsControl) sendUpdates(oldObj, newObj interface{}) {
// If both objects have the same resource version, they are identical.
if newObj != nil && oldObj != nil && (oldObj.(meta.Object).GetResourceVersion() == newObj.(meta.Object).GetResourceVersion()) {
return
}
obj := newObj
if obj == nil {
obj = oldObj
}
switch ob := obj.(type) {
case *object.Service:
dns.updateModifed()
if len(dns.watched) == 0 {
return
}
dns.sendServiceUpdates(ob)
case *object.Endpoints:
if newObj == nil || oldObj == nil {
dns.updateModifed()
if len(dns.watched) == 0 {
return
}
dns.sendEndpointsUpdates(ob)
return
}
p := oldObj.(*object.Endpoints)
// endpoint updates can come frequently, make sure it's a change we care about
if endpointsEquivalent(p, ob) {
return
}
dns.updateModifed()
if len(dns.watched) == 0 {
return
}
dns.sendEndpointsUpdates(endpointsSubsetDiffs(p, ob))
case *object.Pod:
dns.updateModifed()
if len(dns.watched) == 0 {
return
}
dns.sendPodUpdates(ob)
default:
log.Warningf("Updates for %T not supported.", ob)
w, err := c.CoreV1().Namespaces().Watch(options)
return w, err
}
}
func (dns *dnsControl) Add(obj interface{}) { dns.sendUpdates(nil, obj) }
func (dns *dnsControl) Delete(obj interface{}) { dns.sendUpdates(obj, nil) }
func (dns *dnsControl) Update(oldObj, newObj interface{}) { dns.sendUpdates(oldObj, newObj) }
// subsetsEquivalent checks if two endpoint subsets are significantly equivalent
// I.e. that they have the same ready addresses, host names, ports (including protocol
// and service names for SRV)
func subsetsEquivalent(sa, sb object.EndpointSubset) bool {
if len(sa.Addresses) != len(sb.Addresses) {
return false
}
if len(sa.Ports) != len(sb.Ports) {
return false
}
// in Addresses and Ports, we should be able to rely on
// these being sorted and able to be compared
// they are supposed to be in a canonical format
for addr, aaddr := range sa.Addresses {
baddr := sb.Addresses[addr]
if aaddr.IP != baddr.IP {
return false
}
if aaddr.Hostname != baddr.Hostname {
return false
}
}
for port, aport := range sa.Ports {
bport := sb.Ports[port]
if aport.Name != bport.Name {
return false
}
if aport.Port != bport.Port {
return false
}
if aport.Protocol != bport.Protocol {
return false
}
}
return true
}
// endpointsEquivalent checks if the update to an endpoint is something
// that matters to us or if they are effectively equivalent.
func endpointsEquivalent(a, b *object.Endpoints) bool {
if len(a.Subsets) != len(b.Subsets) {
return false
}
// we should be able to rely on
// these being sorted and able to be compared
// they are supposed to be in a canonical format
for i, sa := range a.Subsets {
sb := b.Subsets[i]
if !subsetsEquivalent(sa, sb) {
return false
}
}
return true
}

View File

@@ -1,53 +0,0 @@
package kubernetes
import (
"strconv"
"strings"
"testing"
"github.com/coredns/coredns/plugin/kubernetes/object"
)
func endpointSubsets(addrs ...string) (eps []object.EndpointSubset) {
for _, ap := range addrs {
apa := strings.Split(ap, ":")
address := apa[0]
port, _ := strconv.Atoi(apa[1])
eps = append(eps, object.EndpointSubset{Addresses: []object.EndpointAddress{{IP: address}}, Ports: []object.EndpointPort{{Port: int32(port)}}})
}
return eps
}
func TestEndpointsSubsetDiffs(t *testing.T) {
var tests = []struct {
a, b, expected object.Endpoints
}{
{ // From a->b: Nothing changes
object.Endpoints{Subsets: endpointSubsets("10.0.0.1:80", "10.0.0.2:8080")},
object.Endpoints{Subsets: endpointSubsets("10.0.0.1:80", "10.0.0.2:8080")},
object.Endpoints{},
},
{ // From a->b: Everything goes away
object.Endpoints{Subsets: endpointSubsets("10.0.0.1:80", "10.0.0.2:8080")},
object.Endpoints{},
object.Endpoints{Subsets: endpointSubsets("10.0.0.1:80", "10.0.0.2:8080")},
},
{ // From a->b: Everything is new
object.Endpoints{},
object.Endpoints{Subsets: endpointSubsets("10.0.0.1:80", "10.0.0.2:8080")},
object.Endpoints{Subsets: endpointSubsets("10.0.0.1:80", "10.0.0.2:8080")},
},
{ // From a->b: One goes away, one is new
object.Endpoints{Subsets: endpointSubsets("10.0.0.2:8080")},
object.Endpoints{Subsets: endpointSubsets("10.0.0.1:80")},
object.Endpoints{Subsets: endpointSubsets("10.0.0.2:8080", "10.0.0.1:80")},
},
}
for i, te := range tests {
got := endpointsSubsetDiffs(&te.a, &te.b)
if !endpointsEquivalent(got, &te.expected) {
t.Errorf("Expected '%v' for test %v, got '%v'.", te.expected, i, got)
}
}
}

View File

@@ -5,7 +5,6 @@ import (
"strings"
"testing"
"github.com/coredns/coredns/plugin/kubernetes/object"
"github.com/coredns/coredns/plugin/pkg/dnstest"
"github.com/coredns/coredns/plugin/test"
@@ -142,88 +141,3 @@ func difference(testRRs []dns.RR, gotRRs []dns.RR) []dns.RR {
}
return foundRRs
}
func TestEndpointsEquivalent(t *testing.T) {
epA := object.Endpoints{
Subsets: []object.EndpointSubset{{
Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
}},
}
epB := object.Endpoints{
Subsets: []object.EndpointSubset{{
Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
}},
}
epC := object.Endpoints{
Subsets: []object.EndpointSubset{{
Addresses: []object.EndpointAddress{{IP: "1.2.3.5", Hostname: "foo"}},
}},
}
epD := object.Endpoints{
Subsets: []object.EndpointSubset{{
Addresses: []object.EndpointAddress{{IP: "1.2.3.5", Hostname: "foo"}},
},
{
Addresses: []object.EndpointAddress{{IP: "1.2.2.2", Hostname: "foofoo"}},
}},
}
epE := object.Endpoints{
Subsets: []object.EndpointSubset{{
Addresses: []object.EndpointAddress{{IP: "1.2.3.5", Hostname: "foo"}, {IP: "1.1.1.1"}},
}},
}
epF := object.Endpoints{
Subsets: []object.EndpointSubset{{
Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foofoo"}},
}},
}
epG := object.Endpoints{
Subsets: []object.EndpointSubset{{
Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
Ports: []object.EndpointPort{{Name: "http", Port: 80, Protocol: "TCP"}},
}},
}
epH := object.Endpoints{
Subsets: []object.EndpointSubset{{
Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
Ports: []object.EndpointPort{{Name: "newportname", Port: 80, Protocol: "TCP"}},
}},
}
epI := object.Endpoints{
Subsets: []object.EndpointSubset{{
Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
Ports: []object.EndpointPort{{Name: "http", Port: 8080, Protocol: "TCP"}},
}},
}
epJ := object.Endpoints{
Subsets: []object.EndpointSubset{{
Addresses: []object.EndpointAddress{{IP: "1.2.3.4", Hostname: "foo"}},
Ports: []object.EndpointPort{{Name: "http", Port: 80, Protocol: "UDP"}},
}},
}
tests := []struct {
equiv bool
a *object.Endpoints
b *object.Endpoints
}{
{true, &epA, &epB},
{false, &epA, &epC},
{false, &epA, &epD},
{false, &epA, &epE},
{false, &epA, &epF},
{false, &epF, &epG},
{false, &epG, &epH},
{false, &epG, &epI},
{false, &epG, &epJ},
}
for i, tc := range tests {
if tc.equiv && !endpointsEquivalent(tc.a, tc.b) {
t.Errorf("Test %d: expected endpoints to be equivalent and they are not.", i)
}
if !tc.equiv && endpointsEquivalent(tc.a, tc.b) {
t.Errorf("Test %d: expected endpoints to be seen as different but they were not.", i)
}
}
}

View File

@@ -1,23 +0,0 @@
package watch
// Chan is used to inform the server of a change. Whenever
// a watched FQDN has a change in data, that FQDN should be
// sent down this channel.
type Chan chan string
// Watchable is the interface watchable plugins should implement
type Watchable interface {
// Name returns the plugin name.
Name() string
// SetWatchChan is called when the watch channel is created.
SetWatchChan(Chan)
// Watch is called whenever a watch is created for a FQDN. Plugins
// should send the FQDN down the watch channel when its data may have
// changed. This is an exact match only.
Watch(qname string) error
// StopWatching is called whenever all watches are canceled for a FQDN.
StopWatching(qname string)
}

View File

@@ -1,178 +0,0 @@
package watch
import (
"fmt"
"io"
"sync"
"github.com/miekg/dns"
"github.com/coredns/coredns/pb"
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/log"
"github.com/coredns/coredns/request"
)
// Watcher handles watch creation, cancellation, and processing.
type Watcher interface {
// Watch monitors a client stream and creates and cancels watches.
Watch(pb.DnsService_WatchServer) error
// Stop cancels open watches and stops the watch processing go routine.
Stop()
}
// Manager contains all the data needed to manage watches
type Manager struct {
changes Chan
stopper chan bool
counter int64
watches map[string]watchlist
plugins []Watchable
mutex sync.Mutex
}
type watchlist map[int64]pb.DnsService_WatchServer
// NewWatcher creates a Watcher, which is used to manage watched names.
func NewWatcher(plugins []Watchable) *Manager {
w := &Manager{changes: make(Chan), stopper: make(chan bool), watches: make(map[string]watchlist), plugins: plugins}
for _, p := range plugins {
p.SetWatchChan(w.changes)
}
go w.process()
return w
}
func (w *Manager) nextID() int64 {
w.mutex.Lock()
w.counter++
id := w.counter
w.mutex.Unlock()
return id
}
// Watch monitors a client stream and creates and cancels watches.
func (w *Manager) Watch(stream pb.DnsService_WatchServer) error {
for {
in, err := stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
create := in.GetCreateRequest()
if create != nil {
msg := new(dns.Msg)
err := msg.Unpack(create.Query.Msg)
if err != nil {
log.Warningf("Could not decode watch request: %s\n", err)
stream.Send(&pb.WatchResponse{Err: "could not decode request"})
continue
}
id := w.nextID()
if err := stream.Send(&pb.WatchResponse{WatchId: id, Created: true}); err != nil {
// if we fail to notify client of watch creation, don't create the watch
continue
}
// Normalize qname
qname := (&request.Request{Req: msg}).Name()
w.mutex.Lock()
if _, ok := w.watches[qname]; !ok {
w.watches[qname] = make(watchlist)
}
w.watches[qname][id] = stream
w.mutex.Unlock()
for _, p := range w.plugins {
err := p.Watch(qname)
if err != nil {
log.Warningf("Failed to start watch for %s in plugin %s: %s\n", qname, p.Name(), err)
stream.Send(&pb.WatchResponse{Err: fmt.Sprintf("failed to start watch for %s in plugin %s", qname, p.Name())})
}
}
continue
}
cancel := in.GetCancelRequest()
if cancel != nil {
w.mutex.Lock()
for qname, wl := range w.watches {
ws, ok := wl[cancel.WatchId]
if !ok {
continue
}
// only allow cancels from the client that started it
// TODO: test what happens if a stream tries to cancel a watchID that it doesn't own
if ws != stream {
continue
}
delete(wl, cancel.WatchId)
// if there are no more watches for this qname, we should tell the plugins
if len(wl) == 0 {
for _, p := range w.plugins {
p.StopWatching(qname)
}
delete(w.watches, qname)
}
// let the client know we canceled the watch
stream.Send(&pb.WatchResponse{WatchId: cancel.WatchId, Canceled: true})
}
w.mutex.Unlock()
continue
}
}
}
func (w *Manager) process() {
for {
select {
case <-w.stopper:
return
case changed := <-w.changes:
w.mutex.Lock()
for qname, wl := range w.watches {
if plugin.Zones([]string{changed}).Matches(qname) == "" {
continue
}
for id, stream := range wl {
wr := pb.WatchResponse{WatchId: id, Qname: qname}
err := stream.Send(&wr)
if err != nil {
log.Warningf("Error sending change for %s to watch %d: %s. Removing watch.\n", qname, id, err)
delete(w.watches[qname], id)
}
}
}
w.mutex.Unlock()
}
}
}
// Stop cancels open watches and stops the watch processing go routine.
func (w *Manager) Stop() {
w.stopper <- true
w.mutex.Lock()
for wn, wl := range w.watches {
for id, stream := range wl {
wr := pb.WatchResponse{WatchId: id, Canceled: true}
err := stream.Send(&wr)
if err != nil {
log.Warningf("Error notifying client of cancellation: %s\n", err)
}
}
delete(w.watches, wn)
}
w.mutex.Unlock()
}