Simplify proxy config for Endpoints by removing Mux.

This commit is contained in:
Wojciech Tyczynski 2017-03-17 13:53:46 +01:00
parent 8d7ba2bea2
commit 596527dafa
9 changed files with 172 additions and 240 deletions

View File

@ -308,14 +308,14 @@ func NewProxyServerDefault(config *options.ProxyServerConfig) (*ProxyServer, err
serviceConfig := proxyconfig.NewServiceConfig() serviceConfig := proxyconfig.NewServiceConfig()
serviceConfig.RegisterHandler(proxier) serviceConfig.RegisterHandler(proxier)
endpointsConfig := proxyconfig.NewEndpointsConfig() endpointsConfig := proxyconfig.NewEndpointsConfig(client.Core().RESTClient(), config.ConfigSyncPeriod)
endpointsConfig.RegisterHandler(endpointsHandler) endpointsConfig.RegisterHandler(endpointsHandler)
go endpointsConfig.Run(wait.NeverStop)
proxyconfig.NewSourceAPI( proxyconfig.NewSourceAPI(
client.Core().RESTClient(), client.Core().RESTClient(),
config.ConfigSyncPeriod, config.ConfigSyncPeriod,
serviceConfig.Channel("api"), serviceConfig.Channel("api"),
endpointsConfig.Channel("api"),
) )
config.NodeRef = &clientv1.ObjectReference{ config.NodeRef = &clientv1.ObjectReference{

View File

@ -18,6 +18,7 @@ package main
import ( import (
"fmt" "fmt"
"time"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/util/flag" "k8s.io/apiserver/pkg/util/flag"
@ -138,7 +139,7 @@ func main() {
serviceConfig := proxyconfig.NewServiceConfig() serviceConfig := proxyconfig.NewServiceConfig()
serviceConfig.RegisterHandler(&kubemark.FakeProxyHandler{}) serviceConfig.RegisterHandler(&kubemark.FakeProxyHandler{})
endpointsConfig := proxyconfig.NewEndpointsConfig() endpointsConfig := proxyconfig.NewEndpointsConfig(internalClientset.Core().RESTClient(), 15*time.Minute)
endpointsConfig.RegisterHandler(&kubemark.FakeProxyHandler{}) endpointsConfig.RegisterHandler(&kubemark.FakeProxyHandler{})
eventClient, err := clientgoclientset.NewForConfig(clientConfig) eventClient, err := clientgoclientset.NewForConfig(clientConfig)

View File

@ -41,6 +41,7 @@ go_library(
"//vendor:github.com/golang/glog", "//vendor:github.com/golang/glog",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/types", "//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/client-go/kubernetes/typed/core/v1", "//vendor:k8s.io/client-go/kubernetes/typed/core/v1",
"//vendor:k8s.io/client-go/pkg/api/v1", "//vendor:k8s.io/client-go/pkg/api/v1",
"//vendor:k8s.io/client-go/tools/record", "//vendor:k8s.io/client-go/tools/record",

View File

@ -20,6 +20,7 @@ import (
"time" "time"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
v1core "k8s.io/client-go/kubernetes/typed/core/v1" v1core "k8s.io/client-go/kubernetes/typed/core/v1"
clientv1 "k8s.io/client-go/pkg/api/v1" clientv1 "k8s.io/client-go/pkg/api/v1"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
@ -71,11 +72,12 @@ func NewHollowProxyOrDie(
UID: types.UID(nodeName), UID: types.UID(nodeName),
Namespace: "", Namespace: "",
} }
go endpointsConfig.Run(wait.NeverStop)
proxyconfig.NewSourceAPI( proxyconfig.NewSourceAPI(
client.Core().RESTClient(), client.Core().RESTClient(),
15*time.Minute, 15*time.Minute,
serviceConfig.Channel("api"), serviceConfig.Channel("api"),
endpointsConfig.Channel("api"),
) )
hollowProxy, err := proxyapp.NewProxyServer(client, eventClient, config, iptInterface, &FakeProxier{}, broadcaster, recorder, nil, "fake") hollowProxy, err := proxyapp.NewProxyServer(client, eventClient, config, iptInterface, &FakeProxier{}, broadcaster, recorder, nil, "fake")

View File

@ -18,11 +18,13 @@ go_library(
tags = ["automanaged"], tags = ["automanaged"],
deps = [ deps = [
"//pkg/api:go_default_library", "//pkg/api:go_default_library",
"//pkg/client/listers/core/internalversion:go_default_library",
"//pkg/util/config:go_default_library", "//pkg/util/config:go_default_library",
"//vendor:github.com/davecgh/go-spew/spew", "//vendor:github.com/davecgh/go-spew/spew",
"//vendor:github.com/golang/glog", "//vendor:github.com/golang/glog",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1", "//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/fields", "//vendor:k8s.io/apimachinery/pkg/fields",
"//vendor:k8s.io/apimachinery/pkg/labels",
"//vendor:k8s.io/apimachinery/pkg/types", "//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/runtime", "//vendor:k8s.io/apimachinery/pkg/util/runtime",
"//vendor:k8s.io/apimachinery/pkg/util/wait", "//vendor:k8s.io/apimachinery/pkg/util/wait",

View File

@ -29,31 +29,24 @@ import (
) )
// NewSourceAPI creates config source that watches for changes to the services and endpoints. // NewSourceAPI creates config source that watches for changes to the services and endpoints.
func NewSourceAPI(c cache.Getter, period time.Duration, servicesChan chan<- ServiceUpdate, endpointsChan chan<- EndpointsUpdate) { func NewSourceAPI(c cache.Getter, period time.Duration, servicesChan chan<- ServiceUpdate) {
servicesLW := cache.NewListWatchFromClient(c, "services", metav1.NamespaceAll, fields.Everything()) servicesLW := cache.NewListWatchFromClient(c, "services", metav1.NamespaceAll, fields.Everything())
endpointsLW := cache.NewListWatchFromClient(c, "endpoints", metav1.NamespaceAll, fields.Everything()) newSourceAPI(servicesLW, period, servicesChan, wait.NeverStop)
newSourceAPI(servicesLW, endpointsLW, period, servicesChan, endpointsChan, wait.NeverStop)
} }
func newSourceAPI( func newSourceAPI(
servicesLW cache.ListerWatcher, servicesLW cache.ListerWatcher,
endpointsLW cache.ListerWatcher,
period time.Duration, period time.Duration,
servicesChan chan<- ServiceUpdate, servicesChan chan<- ServiceUpdate,
endpointsChan chan<- EndpointsUpdate,
stopCh <-chan struct{}) { stopCh <-chan struct{}) {
serviceController := NewServiceController(servicesLW, period, servicesChan) serviceController := NewServiceController(servicesLW, period, servicesChan)
go serviceController.Run(stopCh) go serviceController.Run(stopCh)
endpointsController := NewEndpointsController(endpointsLW, period, endpointsChan) if !cache.WaitForCacheSync(stopCh, serviceController.HasSynced) {
go endpointsController.Run(stopCh)
if !cache.WaitForCacheSync(stopCh, serviceController.HasSynced, endpointsController.HasSynced) {
utilruntime.HandleError(fmt.Errorf("source controllers not synced")) utilruntime.HandleError(fmt.Errorf("source controllers not synced"))
return return
} }
servicesChan <- ServiceUpdate{Op: SYNCED} servicesChan <- ServiceUpdate{Op: SYNCED}
endpointsChan <- EndpointsUpdate{Op: SYNCED}
} }
func sendAddService(servicesChan chan<- ServiceUpdate) func(obj interface{}) { func sendAddService(servicesChan chan<- ServiceUpdate) func(obj interface{}) {
@ -99,49 +92,6 @@ func sendDeleteService(servicesChan chan<- ServiceUpdate) func(obj interface{})
} }
} }
func sendAddEndpoints(endpointsChan chan<- EndpointsUpdate) func(obj interface{}) {
return func(obj interface{}) {
endpoints, ok := obj.(*api.Endpoints)
if !ok {
utilruntime.HandleError(fmt.Errorf("cannot convert to *api.Endpoints: %v", obj))
return
}
endpointsChan <- EndpointsUpdate{Op: ADD, Endpoints: endpoints}
}
}
func sendUpdateEndpoints(endpointsChan chan<- EndpointsUpdate) func(oldObj, newObj interface{}) {
return func(_, newObj interface{}) {
endpoints, ok := newObj.(*api.Endpoints)
if !ok {
utilruntime.HandleError(fmt.Errorf("cannot convert to *api.Endpoints: %v", newObj))
return
}
endpointsChan <- EndpointsUpdate{Op: UPDATE, Endpoints: endpoints}
}
}
func sendDeleteEndpoints(endpointsChan chan<- EndpointsUpdate) func(obj interface{}) {
return func(obj interface{}) {
var endpoints *api.Endpoints
switch t := obj.(type) {
case *api.Endpoints:
endpoints = t
case cache.DeletedFinalStateUnknown:
var ok bool
endpoints, ok = t.Obj.(*api.Endpoints)
if !ok {
utilruntime.HandleError(fmt.Errorf("cannot convert to *api.Endpoints: %v", t.Obj))
return
}
default:
utilruntime.HandleError(fmt.Errorf("cannot convert to *api.Endpoints: %v", obj))
return
}
endpointsChan <- EndpointsUpdate{Op: REMOVE, Endpoints: endpoints}
}
}
// NewServiceController creates a controller that is watching services and sending // NewServiceController creates a controller that is watching services and sending
// updates into ServiceUpdate channel. // updates into ServiceUpdate channel.
func NewServiceController(lw cache.ListerWatcher, period time.Duration, ch chan<- ServiceUpdate) cache.Controller { func NewServiceController(lw cache.ListerWatcher, period time.Duration, ch chan<- ServiceUpdate) cache.Controller {
@ -157,19 +107,3 @@ func NewServiceController(lw cache.ListerWatcher, period time.Duration, ch chan<
) )
return serviceController return serviceController
} }
// NewEndpointsController creates a controller that is watching endpoints and sending
// updates into EndpointsUpdate channel.
func NewEndpointsController(lw cache.ListerWatcher, period time.Duration, ch chan<- EndpointsUpdate) cache.Controller {
_, endpointsController := cache.NewInformer(
lw,
&api.Endpoints{},
period,
cache.ResourceEventHandlerFuncs{
AddFunc: sendAddEndpoints(ch),
UpdateFunc: sendUpdateEndpoints(ch),
DeleteFunc: sendDeleteEndpoints(ch),
},
)
return endpointsController
}

View File

@ -166,68 +166,40 @@ func TestNewEndpointsSourceApi_UpdatesAndMultipleEndpoints(t *testing.T) {
watchResp: fakeWatch, watchResp: fakeWatch,
} }
ch := make(chan EndpointsUpdate) stopCh := make(chan struct{})
defer close(stopCh)
endpointsController := NewEndpointsController(lw, 30*time.Second, ch) ch := make(chan struct{})
go endpointsController.Run(wait.NeverStop) handler := newEpsHandler(t, nil, func() { ch <- struct{}{} })
endpointsConfig := newEndpointsConfig(lw, time.Minute)
endpointsConfig.RegisterHandler(handler)
go endpointsConfig.Run(stopCh)
// Add the first endpoints // Add the first endpoints
handler.expected = []*api.Endpoints{endpoints1v1}
fakeWatch.Add(endpoints1v1) fakeWatch.Add(endpoints1v1)
got, ok := <-ch <-ch
if !ok {
t.Errorf("Unable to read from channel when expected")
}
expected := EndpointsUpdate{Op: ADD, Endpoints: endpoints1v1}
if !apiequality.Semantic.DeepEqual(expected, got) {
t.Errorf("Expected %#v; Got %#v", expected, got)
}
// Add another endpoints // Add another endpoints
handler.expected = []*api.Endpoints{endpoints1v1, endpoints2}
fakeWatch.Add(endpoints2) fakeWatch.Add(endpoints2)
got, ok = <-ch <-ch
if !ok {
t.Errorf("Unable to read from channel when expected")
}
// Could be sorted either of these two ways:
expected = EndpointsUpdate{Op: ADD, Endpoints: endpoints2}
if !apiequality.Semantic.DeepEqual(expected, got) {
t.Errorf("Expected %#v, Got %#v", expected, got)
}
// Modify endpoints1 // Modify endpoints1
handler.expected = []*api.Endpoints{endpoints1v2, endpoints2}
fakeWatch.Modify(endpoints1v2) fakeWatch.Modify(endpoints1v2)
got, ok = <-ch <-ch
if !ok {
t.Errorf("Unable to read from channel when expected")
}
expected = EndpointsUpdate{Op: UPDATE, Endpoints: endpoints1v2}
if !apiequality.Semantic.DeepEqual(expected, got) {
t.Errorf("Expected %#v, Got %#v", expected, got)
}
// Delete endpoints1 // Delete endpoints1
handler.expected = []*api.Endpoints{endpoints2}
fakeWatch.Delete(endpoints1v2) fakeWatch.Delete(endpoints1v2)
got, ok = <-ch <-ch
if !ok {
t.Errorf("Unable to read from channel when expected")
}
expected = EndpointsUpdate{Op: REMOVE, Endpoints: endpoints1v2}
if !apiequality.Semantic.DeepEqual(expected, got) {
t.Errorf("Expected %#v, Got %#v", expected, got)
}
// Delete endpoints2 // Delete endpoints2
handler.expected = []*api.Endpoints{}
fakeWatch.Delete(endpoints2) fakeWatch.Delete(endpoints2)
got, ok = <-ch <-ch
if !ok {
t.Errorf("Unable to read from channel when expected")
}
expected = EndpointsUpdate{Op: REMOVE, Endpoints: endpoints2}
if !apiequality.Semantic.DeepEqual(expected, got) {
t.Errorf("Expected %#v, Got %#v", expected, got)
}
} }
type svcHandler struct { type svcHandler struct {
@ -286,13 +258,6 @@ func TestInitialSync(t *testing.T) {
// Wait for both services and endpoints handler. // Wait for both services and endpoints handler.
wg.Add(2) wg.Add(2)
svcConfig := NewServiceConfig()
epsConfig := NewEndpointsConfig()
svcHandler := newSvcHandler(t, []api.Service{*svc2, *svc1}, wg.Done)
svcConfig.RegisterHandler(svcHandler)
epsHandler := newEpsHandler(t, []*api.Endpoints{eps2, eps1}, wg.Done)
epsConfig.RegisterHandler(epsHandler)
// Setup fake api client. // Setup fake api client.
fakeSvcWatch := watch.NewFake() fakeSvcWatch := watch.NewFake()
svcLW := fakeLW{ svcLW := fakeLW{
@ -305,8 +270,16 @@ func TestInitialSync(t *testing.T) {
watchResp: fakeEpsWatch, watchResp: fakeEpsWatch,
} }
svcConfig := NewServiceConfig()
epsConfig := newEndpointsConfig(epsLW, time.Minute)
svcHandler := newSvcHandler(t, []api.Service{*svc2, *svc1}, wg.Done)
svcConfig.RegisterHandler(svcHandler)
epsHandler := newEpsHandler(t, []*api.Endpoints{eps2, eps1}, wg.Done)
epsConfig.RegisterHandler(epsHandler)
stopCh := make(chan struct{}) stopCh := make(chan struct{})
defer close(stopCh) defer close(stopCh)
newSourceAPI(svcLW, epsLW, time.Minute, svcConfig.Channel("one"), epsConfig.Channel("two"), stopCh) go epsConfig.Run(stopCh)
newSourceAPI(svcLW, time.Minute, svcConfig.Channel("one"), stopCh)
wg.Wait() wg.Wait()
} }

View File

@ -17,12 +17,20 @@ limitations under the License.
package config package config
import ( import (
"fmt"
"sync" "sync"
"time"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/golang/glog" "github.com/golang/glog"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/tools/cache"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
listers "k8s.io/kubernetes/pkg/client/listers/core/internalversion"
"k8s.io/kubernetes/pkg/util/config" "k8s.io/kubernetes/pkg/util/config"
) )
@ -76,102 +84,100 @@ type EndpointsConfigHandler interface {
// EndpointsConfig tracks a set of endpoints configurations. // EndpointsConfig tracks a set of endpoints configurations.
// It accepts "set", "add" and "remove" operations of endpoints via channels, and invokes registered handlers on change. // It accepts "set", "add" and "remove" operations of endpoints via channels, and invokes registered handlers on change.
type EndpointsConfig struct { type EndpointsConfig struct {
mux *config.Mux informer cache.Controller
bcaster *config.Broadcaster lister listers.EndpointsLister
store *endpointsStore handlers []EndpointsConfigHandler
// updates channel is used to trigger registered handlers.
updates chan struct{}
} }
// NewEndpointsConfig creates a new EndpointsConfig. // NewEndpointsConfig creates a new EndpointsConfig.
// It immediately runs the created EndpointsConfig. func NewEndpointsConfig(c cache.Getter, period time.Duration) *EndpointsConfig {
func NewEndpointsConfig() *EndpointsConfig { endpointsLW := cache.NewListWatchFromClient(c, "endpoints", metav1.NamespaceAll, fields.Everything())
// The updates channel is used to send interrupts to the Endpoints handler. return newEndpointsConfig(endpointsLW, period)
// It's buffered because we never want to block for as long as there is a }
// pending interrupt, but don't want to drop them if the handler is doing
// work. func newEndpointsConfig(lw cache.ListerWatcher, period time.Duration) *EndpointsConfig {
updates := make(chan struct{}, 1) result := &EndpointsConfig{}
store := &endpointsStore{updates: updates, endpoints: make(map[string]map[types.NamespacedName]*api.Endpoints)}
mux := config.NewMux(store) store, informer := cache.NewIndexerInformer(
bcaster := config.NewBroadcaster() lw,
go watchForUpdates(bcaster, store, updates) &api.Endpoints{},
return &EndpointsConfig{mux, bcaster, store} period,
cache.ResourceEventHandlerFuncs{
AddFunc: result.handleAddEndpoints,
UpdateFunc: result.handleUpdateEndpoints,
DeleteFunc: result.handleDeleteEndpoints,
},
cache.Indexers{},
)
result.informer = informer
result.lister = listers.NewEndpointsLister(store)
return result
} }
// RegisterHandler registers a handler which is called on every endpoints change. // RegisterHandler registers a handler which is called on every endpoints change.
func (c *EndpointsConfig) RegisterHandler(handler EndpointsConfigHandler) { func (c *EndpointsConfig) RegisterHandler(handler EndpointsConfigHandler) {
c.bcaster.Add(config.ListenerFunc(func(instance interface{}) { c.handlers = append(c.handlers, handler)
glog.V(3).Infof("Calling handler.OnEndpointsUpdate()")
handler.OnEndpointsUpdate(instance.([]*api.Endpoints))
}))
} }
// Channel returns a channel to which endpoints updates should be delivered. func (c *EndpointsConfig) Run(stopCh <-chan struct{}) {
func (c *EndpointsConfig) Channel(source string) chan EndpointsUpdate { // The updates channel is used to send interrupts to the Endpoints handler.
ch := c.mux.Channel(source) // It's buffered because we never want to block for as long as there is a
endpointsCh := make(chan EndpointsUpdate) // pending interrupt, but don't want to drop them if the handler is doing
// work.
c.updates = make(chan struct{}, 1)
go c.informer.Run(stopCh)
if !cache.WaitForCacheSync(stopCh, c.informer.HasSynced) {
utilruntime.HandleError(fmt.Errorf("endpoint controller not synced"))
return
}
// We have synced informers. Now we can start delivering updates
// to the registered handler.
go func() { go func() {
for update := range endpointsCh { for range c.updates {
ch <- update endpoints, err := c.lister.List(labels.Everything())
if err != nil {
glog.Errorf("Error while listing endpoints from cache: %v", err)
// This will cause a retry (if there isn't any other trigger in-flight).
c.dispatchUpdate()
continue
}
if endpoints == nil {
endpoints = []*api.Endpoints{}
}
for i := range c.handlers {
glog.V(3).Infof("Calling handler.OnEndpointsUpdate()")
c.handlers[i].OnEndpointsUpdate(endpoints)
}
} }
}() }()
return endpointsCh // Close updates channel when stopCh is closed.
go func() {
<-stopCh
close(c.updates)
}()
} }
// Config returns list of all endpoints from underlying store. func (c *EndpointsConfig) handleAddEndpoints(_ interface{}) {
func (c *EndpointsConfig) Config() []api.Endpoints { c.dispatchUpdate()
return c.store.MergedState().([]api.Endpoints)
} }
type endpointsStore struct { func (c *EndpointsConfig) handleUpdateEndpoints(_, _ interface{}) {
endpointLock sync.RWMutex c.dispatchUpdate()
endpoints map[string]map[types.NamespacedName]*api.Endpoints
synced bool
updates chan<- struct{}
} }
func (s *endpointsStore) Merge(source string, change interface{}) error { func (c *EndpointsConfig) handleDeleteEndpoints(_ interface{}) {
s.endpointLock.Lock() c.dispatchUpdate()
endpoints := s.endpoints[source] }
if endpoints == nil {
endpoints = make(map[types.NamespacedName]*api.Endpoints) func (c *EndpointsConfig) dispatchUpdate() {
}
update := change.(EndpointsUpdate)
switch update.Op {
case ADD, UPDATE:
glog.V(5).Infof("Adding new endpoint from source %s : %s", source, spew.Sdump(update.Endpoints))
name := types.NamespacedName{Namespace: update.Endpoints.Namespace, Name: update.Endpoints.Name}
endpoints[name] = update.Endpoints
case REMOVE:
glog.V(5).Infof("Removing an endpoint %s", spew.Sdump(update.Endpoints))
name := types.NamespacedName{Namespace: update.Endpoints.Namespace, Name: update.Endpoints.Name}
delete(endpoints, name)
case SYNCED:
s.synced = true
default:
glog.V(4).Infof("Received invalid update type: %s", spew.Sdump(update))
}
s.endpoints[source] = endpoints
synced := s.synced
s.endpointLock.Unlock()
if s.updates != nil && synced {
select { select {
case s.updates <- struct{}{}: case c.updates <- struct{}{}:
default: default:
glog.V(4).Infof("Endpoints handler already has a pending interrupt.") glog.V(4).Infof("Endpoints handler already has a pending interrupt.")
} }
}
return nil
}
func (s *endpointsStore) MergedState() interface{} {
s.endpointLock.RLock()
defer s.endpointLock.RUnlock()
endpoints := make([]*api.Endpoints, 0)
for _, sourceEndpoints := range s.endpoints {
for _, value := range sourceEndpoints {
endpoints = append(endpoints, value)
}
}
return endpoints
} }
// ServiceConfig tracks a set of service configurations. // ServiceConfig tracks a set of service configurations.

View File

@ -24,6 +24,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
) )
@ -228,98 +229,110 @@ func TestNewMultipleSourcesServicesMultipleHandlersAddedAndNotified(t *testing.T
handler2.ValidateServices(t, services) handler2.ValidateServices(t, services)
} }
func TestNewMultipleSourcesEndpointsMultipleHandlersAddedAndNotified(t *testing.T) { func TestNewEndpointsMultipleHandlersAddedAndNotified(t *testing.T) {
config := NewEndpointsConfig() fakeWatch := watch.NewFake()
config.store.synced = true lw := fakeLW{
channelOne := config.Channel("one") listResp: &api.EndpointsList{Items: []api.Endpoints{}},
channelTwo := config.Channel("two") watchResp: fakeWatch,
}
stopCh := make(chan struct{})
defer close(stopCh)
config := newEndpointsConfig(lw, time.Minute)
handler := NewEndpointsHandlerMock() handler := NewEndpointsHandlerMock()
handler2 := NewEndpointsHandlerMock() handler2 := NewEndpointsHandlerMock()
config.RegisterHandler(handler) config.RegisterHandler(handler)
config.RegisterHandler(handler2) config.RegisterHandler(handler2)
endpointsUpdate1 := CreateEndpointsUpdate(ADD, &api.Endpoints{ go config.Run(stopCh)
endpoints1 := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "foo"},
Subsets: []api.EndpointSubset{{ Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}, Addresses: []api.EndpointAddress{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}},
Ports: []api.EndpointPort{{Port: 80}}, Ports: []api.EndpointPort{{Port: 80}},
}}, }},
}) }
endpointsUpdate2 := CreateEndpointsUpdate(ADD, &api.Endpoints{ endpoints2 := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "bar"}, ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "bar"},
Subsets: []api.EndpointSubset{{ Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "3.3.3.3"}, {IP: "4.4.4.4"}}, Addresses: []api.EndpointAddress{{IP: "3.3.3.3"}, {IP: "4.4.4.4"}},
Ports: []api.EndpointPort{{Port: 80}}, Ports: []api.EndpointPort{{Port: 80}},
}}, }},
}) }
channelOne <- endpointsUpdate1 fakeWatch.Add(endpoints1)
channelTwo <- endpointsUpdate2 fakeWatch.Add(endpoints2)
endpoints := []*api.Endpoints{endpointsUpdate2.Endpoints, endpointsUpdate1.Endpoints} endpoints := []*api.Endpoints{endpoints2, endpoints1}
handler.ValidateEndpoints(t, endpoints) handler.ValidateEndpoints(t, endpoints)
handler2.ValidateEndpoints(t, endpoints) handler2.ValidateEndpoints(t, endpoints)
} }
func TestNewMultipleSourcesEndpointsMultipleHandlersAddRemoveSetAndNotified(t *testing.T) { func TestNewEndpointsMultipleHandlersAddRemoveSetAndNotified(t *testing.T) {
config := NewEndpointsConfig() fakeWatch := watch.NewFake()
config.store.synced = true lw := fakeLW{
channelOne := config.Channel("one") listResp: &api.EndpointsList{Items: []api.Endpoints{}},
channelTwo := config.Channel("two") watchResp: fakeWatch,
}
stopCh := make(chan struct{})
defer close(stopCh)
config := newEndpointsConfig(lw, time.Minute)
handler := NewEndpointsHandlerMock() handler := NewEndpointsHandlerMock()
handler2 := NewEndpointsHandlerMock() handler2 := NewEndpointsHandlerMock()
config.RegisterHandler(handler) config.RegisterHandler(handler)
config.RegisterHandler(handler2) config.RegisterHandler(handler2)
endpointsUpdate1 := CreateEndpointsUpdate(ADD, &api.Endpoints{ go config.Run(stopCh)
endpoints1 := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "foo"},
Subsets: []api.EndpointSubset{{ Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}}, Addresses: []api.EndpointAddress{{IP: "1.1.1.1"}, {IP: "2.2.2.2"}},
Ports: []api.EndpointPort{{Port: 80}}, Ports: []api.EndpointPort{{Port: 80}},
}}, }},
}) }
endpointsUpdate2 := CreateEndpointsUpdate(ADD, &api.Endpoints{ endpoints2 := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "bar"}, ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "bar"},
Subsets: []api.EndpointSubset{{ Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "3.3.3.3"}, {IP: "4.4.4.4"}}, Addresses: []api.EndpointAddress{{IP: "3.3.3.3"}, {IP: "4.4.4.4"}},
Ports: []api.EndpointPort{{Port: 80}}, Ports: []api.EndpointPort{{Port: 80}},
}}, }},
}) }
channelOne <- endpointsUpdate1 fakeWatch.Add(endpoints1)
channelTwo <- endpointsUpdate2 fakeWatch.Add(endpoints2)
endpoints := []*api.Endpoints{endpointsUpdate2.Endpoints, endpointsUpdate1.Endpoints} endpoints := []*api.Endpoints{endpoints2, endpoints1}
handler.ValidateEndpoints(t, endpoints) handler.ValidateEndpoints(t, endpoints)
handler2.ValidateEndpoints(t, endpoints) handler2.ValidateEndpoints(t, endpoints)
// Add one more // Add one more
endpointsUpdate3 := CreateEndpointsUpdate(ADD, &api.Endpoints{ endpoints3 := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "foobar"}, ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "foobar"},
Subsets: []api.EndpointSubset{{ Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "5.5.5.5"}, {IP: "6.6.6.6"}}, Addresses: []api.EndpointAddress{{IP: "5.5.5.5"}, {IP: "6.6.6.6"}},
Ports: []api.EndpointPort{{Port: 80}}, Ports: []api.EndpointPort{{Port: 80}},
}}, }},
}) }
channelTwo <- endpointsUpdate3 fakeWatch.Add(endpoints3)
endpoints = []*api.Endpoints{endpointsUpdate2.Endpoints, endpointsUpdate1.Endpoints, endpointsUpdate3.Endpoints} endpoints = []*api.Endpoints{endpoints2, endpoints1, endpoints3}
handler.ValidateEndpoints(t, endpoints) handler.ValidateEndpoints(t, endpoints)
handler2.ValidateEndpoints(t, endpoints) handler2.ValidateEndpoints(t, endpoints)
// Update the "foo" service with new endpoints // Update the "foo" service with new endpoints
endpointsUpdate1 = CreateEndpointsUpdate(ADD, &api.Endpoints{ endpoints1v2 := &api.Endpoints{
ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "foo"}, ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "foo"},
Subsets: []api.EndpointSubset{{ Subsets: []api.EndpointSubset{{
Addresses: []api.EndpointAddress{{IP: "7.7.7.7"}}, Addresses: []api.EndpointAddress{{IP: "7.7.7.7"}},
Ports: []api.EndpointPort{{Port: 80}}, Ports: []api.EndpointPort{{Port: 80}},
}}, }},
}) }
channelOne <- endpointsUpdate1 fakeWatch.Modify(endpoints1v2)
endpoints = []*api.Endpoints{endpointsUpdate2.Endpoints, endpointsUpdate1.Endpoints, endpointsUpdate3.Endpoints} endpoints = []*api.Endpoints{endpoints2, endpoints1v2, endpoints3}
handler.ValidateEndpoints(t, endpoints) handler.ValidateEndpoints(t, endpoints)
handler2.ValidateEndpoints(t, endpoints) handler2.ValidateEndpoints(t, endpoints)
// Remove "bar" service // Remove "bar" endpoints
endpointsUpdate2 = CreateEndpointsUpdate(REMOVE, &api.Endpoints{ObjectMeta: metav1.ObjectMeta{Namespace: "testnamespace", Name: "bar"}}) fakeWatch.Delete(endpoints2)
channelTwo <- endpointsUpdate2 endpoints = []*api.Endpoints{endpoints1v2, endpoints3}
endpoints = []*api.Endpoints{endpointsUpdate1.Endpoints, endpointsUpdate3.Endpoints}
handler.ValidateEndpoints(t, endpoints) handler.ValidateEndpoints(t, endpoints)
handler2.ValidateEndpoints(t, endpoints) handler2.ValidateEndpoints(t, endpoints)
} }