2014-08-03 07:00:42 +00:00
|
|
|
/*
|
2015-05-01 16:19:44 +00:00
|
|
|
Copyright 2014 The Kubernetes Authors All rights reserved.
|
2014-08-03 07:00:42 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package cache
|
|
|
|
|
|
|
|
import (
|
2014-09-20 01:09:40 +00:00
|
|
|
"errors"
|
2014-12-05 20:03:07 +00:00
|
|
|
"io"
|
2014-08-03 07:00:42 +00:00
|
|
|
"reflect"
|
|
|
|
"time"
|
|
|
|
|
2014-09-23 00:37:12 +00:00
|
|
|
apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
2014-10-23 02:28:06 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
2014-09-02 17:55:27 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
2014-08-03 07:00:42 +00:00
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
|
|
|
"github.com/golang/glog"
|
|
|
|
)
|
|
|
|
|
2014-09-16 01:10:10 +00:00
|
|
|
// ListerWatcher is any object that knows how to perform an initial list and start a watch on a resource.
|
|
|
|
type ListerWatcher interface {
|
|
|
|
// List should return a list type object; the Items field will be extracted, and the
|
|
|
|
// ResourceVersion field will be used to start the watch in the right place.
|
|
|
|
List() (runtime.Object, error)
|
|
|
|
// Watch should begin a watch at the specified version.
|
2014-10-07 20:51:28 +00:00
|
|
|
Watch(resourceVersion string) (watch.Interface, error)
|
2014-09-16 01:10:10 +00:00
|
|
|
}
|
|
|
|
|
2014-08-03 22:36:36 +00:00
|
|
|
// Reflector watches a specified resource and causes all changes to be reflected in the given store.
|
|
|
|
type Reflector struct {
|
2014-08-14 22:42:05 +00:00
|
|
|
// The type of object we expect to place in the store.
|
2014-08-03 07:00:42 +00:00
|
|
|
expectedType reflect.Type
|
2014-08-14 22:42:05 +00:00
|
|
|
// The destination to sync up with the watch source
|
|
|
|
store Store
|
2014-09-16 01:10:10 +00:00
|
|
|
// listerWatcher is used to perform lists and watches.
|
|
|
|
listerWatcher ListerWatcher
|
2014-08-18 21:47:20 +00:00
|
|
|
// period controls timing between one watch ending and
|
2014-08-14 22:42:05 +00:00
|
|
|
// the beginning of the next one.
|
2015-02-27 00:58:00 +00:00
|
|
|
period time.Duration
|
|
|
|
resyncPeriod time.Duration
|
2015-03-27 18:17:54 +00:00
|
|
|
// lastSyncResourceVersion is the resource version token last
|
|
|
|
// observed when doing a sync with the underlying store
|
|
|
|
// it is not thread safe as it is not synchronized with access to the store
|
|
|
|
lastSyncResourceVersion string
|
2014-08-03 07:00:42 +00:00
|
|
|
}
|
|
|
|
|
2015-02-16 15:54:29 +00:00
|
|
|
// NewNamespaceKeyedIndexerAndReflector creates an Indexer and a Reflector
|
|
|
|
// The indexer is configured to key on namespace
|
|
|
|
func NewNamespaceKeyedIndexerAndReflector(lw ListerWatcher, expectedType interface{}, resyncPeriod time.Duration) (indexer Indexer, reflector *Reflector) {
|
|
|
|
indexer = NewIndexer(MetaNamespaceKeyFunc, Indexers{"namespace": MetaNamespaceIndexFunc})
|
|
|
|
reflector = NewReflector(lw, expectedType, indexer, resyncPeriod)
|
|
|
|
return indexer, reflector
|
|
|
|
}
|
|
|
|
|
2014-09-02 10:00:28 +00:00
|
|
|
// NewReflector creates a new Reflector object which will keep the given store up to
|
2014-08-03 22:36:36 +00:00
|
|
|
// date with the server's contents for the given resource. Reflector promises to
|
2014-08-03 07:00:42 +00:00
|
|
|
// only put things in the store that have the type of expectedType.
|
2015-02-27 00:58:00 +00:00
|
|
|
// If resyncPeriod is non-zero, then lists will be executed after every resyncPeriod,
|
|
|
|
// so that you can use reflectors to periodically process everything as well as
|
|
|
|
// incrementally processing the things that change.
|
|
|
|
func NewReflector(lw ListerWatcher, expectedType interface{}, store Store, resyncPeriod time.Duration) *Reflector {
|
2014-09-16 01:10:10 +00:00
|
|
|
r := &Reflector{
|
|
|
|
listerWatcher: lw,
|
|
|
|
store: store,
|
|
|
|
expectedType: reflect.TypeOf(expectedType),
|
|
|
|
period: time.Second,
|
2015-02-27 00:58:00 +00:00
|
|
|
resyncPeriod: resyncPeriod,
|
2014-08-03 07:00:42 +00:00
|
|
|
}
|
2014-09-16 01:10:10 +00:00
|
|
|
return r
|
2014-08-03 07:00:42 +00:00
|
|
|
}
|
|
|
|
|
2014-08-14 22:42:05 +00:00
|
|
|
// Run starts a watch and handles watch events. Will restart the watch if it is closed.
|
|
|
|
// Run starts a goroutine and returns immediately.
|
2014-09-16 01:10:10 +00:00
|
|
|
func (r *Reflector) Run() {
|
2015-04-09 04:13:15 +00:00
|
|
|
go util.Forever(func() { r.listAndWatch(util.NeverStop) }, r.period)
|
2014-09-16 01:10:10 +00:00
|
|
|
}
|
|
|
|
|
2015-01-21 23:25:54 +00:00
|
|
|
// RunUntil starts a watch and handles watch events. Will restart the watch if it is closed.
|
|
|
|
// RunUntil starts a goroutine and returns immediately. It will exit when stopCh is closed.
|
|
|
|
func (r *Reflector) RunUntil(stopCh <-chan struct{}) {
|
2015-04-09 04:13:15 +00:00
|
|
|
go util.Until(func() { r.listAndWatch(stopCh) }, r.period, stopCh)
|
2015-01-21 23:25:54 +00:00
|
|
|
}
|
|
|
|
|
2015-02-27 00:58:00 +00:00
|
|
|
var (
|
|
|
|
// nothing will ever be sent down this channel
|
|
|
|
neverExitWatch <-chan time.Time = make(chan time.Time)
|
|
|
|
|
|
|
|
// Used to indicate that watching stopped so that a resync could happen.
|
|
|
|
errorResyncRequested = errors.New("resync channel fired")
|
2015-04-09 04:13:15 +00:00
|
|
|
|
|
|
|
// Used to indicate that watching stopped because of a signal from the stop
|
|
|
|
// channel passed in from a client of the reflector.
|
|
|
|
errorStopRequested = errors.New("Stop requested")
|
2015-02-27 00:58:00 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// resyncChan returns a channel which will receive something when a resync is required.
|
|
|
|
func (r *Reflector) resyncChan() <-chan time.Time {
|
|
|
|
if r.resyncPeriod == 0 {
|
|
|
|
return neverExitWatch
|
|
|
|
}
|
|
|
|
return time.After(r.resyncPeriod)
|
|
|
|
}
|
|
|
|
|
2015-04-09 04:13:15 +00:00
|
|
|
func (r *Reflector) listAndWatch(stopCh <-chan struct{}) {
|
2014-10-07 20:51:28 +00:00
|
|
|
var resourceVersion string
|
2015-04-09 04:13:15 +00:00
|
|
|
resyncCh := r.resyncChan()
|
2014-09-16 01:10:10 +00:00
|
|
|
|
|
|
|
list, err := r.listerWatcher.List()
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Failed to list %v: %v", r.expectedType, err)
|
|
|
|
return
|
|
|
|
}
|
2014-10-23 22:01:25 +00:00
|
|
|
meta, err := meta.Accessor(list)
|
2014-09-16 01:10:10 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Unable to understand list result %#v", list)
|
|
|
|
return
|
|
|
|
}
|
2014-10-23 22:01:25 +00:00
|
|
|
resourceVersion = meta.ResourceVersion()
|
2014-09-16 01:10:10 +00:00
|
|
|
items, err := runtime.ExtractList(list)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Unable to understand list result %#v (%v)", list, err)
|
|
|
|
return
|
|
|
|
}
|
2015-01-26 21:44:53 +00:00
|
|
|
if err := r.syncWith(items); err != nil {
|
2014-09-16 01:10:10 +00:00
|
|
|
glog.Errorf("Unable to sync list result: %v", err)
|
|
|
|
return
|
|
|
|
}
|
2015-03-27 18:17:54 +00:00
|
|
|
r.lastSyncResourceVersion = resourceVersion
|
2014-09-16 01:10:10 +00:00
|
|
|
|
|
|
|
for {
|
|
|
|
w, err := r.listerWatcher.Watch(resourceVersion)
|
2014-08-03 22:36:36 +00:00
|
|
|
if err != nil {
|
2014-12-05 20:03:07 +00:00
|
|
|
switch err {
|
|
|
|
case io.EOF:
|
|
|
|
// watch closed normally
|
|
|
|
case io.ErrUnexpectedEOF:
|
|
|
|
glog.V(1).Infof("Watch for %v closed with unexpected EOF: %v", r.expectedType, err)
|
|
|
|
default:
|
|
|
|
glog.Errorf("Failed to watch %v: %v", r.expectedType, err)
|
|
|
|
}
|
2014-08-03 22:36:36 +00:00
|
|
|
return
|
|
|
|
}
|
2015-04-09 04:13:15 +00:00
|
|
|
if err := r.watchHandler(w, &resourceVersion, resyncCh, stopCh); err != nil {
|
2015-02-27 00:58:00 +00:00
|
|
|
if err != errorResyncRequested {
|
2015-04-09 04:13:15 +00:00
|
|
|
glog.Errorf("watch of %v ended with: %v", r.expectedType, err)
|
2015-02-27 00:58:00 +00:00
|
|
|
}
|
2014-09-20 01:09:40 +00:00
|
|
|
return
|
|
|
|
}
|
2014-09-16 01:10:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// syncWith replaces the store's items with the given list.
|
|
|
|
func (r *Reflector) syncWith(items []runtime.Object) error {
|
2015-01-26 21:44:53 +00:00
|
|
|
found := make([]interface{}, 0, len(items))
|
2014-09-16 01:10:10 +00:00
|
|
|
for _, item := range items {
|
2015-01-26 21:44:53 +00:00
|
|
|
found = append(found, item)
|
2014-09-16 01:10:10 +00:00
|
|
|
}
|
|
|
|
|
2015-01-26 21:44:53 +00:00
|
|
|
return r.store.Replace(found)
|
2014-08-03 07:00:42 +00:00
|
|
|
}
|
|
|
|
|
2014-08-14 22:42:05 +00:00
|
|
|
// watchHandler watches w and keeps *resourceVersion up to date.
|
2015-04-09 04:13:15 +00:00
|
|
|
func (r *Reflector) watchHandler(w watch.Interface, resourceVersion *string, resyncCh <-chan time.Time, stopCh <-chan struct{}) error {
|
2014-09-20 01:09:40 +00:00
|
|
|
start := time.Now()
|
|
|
|
eventCount := 0
|
2015-04-09 04:13:15 +00:00
|
|
|
|
|
|
|
// Stopping the watcher should be idempotent and if we return from this function there's no way
|
|
|
|
// we're coming back in with the same watch interface.
|
|
|
|
defer w.Stop()
|
|
|
|
|
2015-02-27 00:58:00 +00:00
|
|
|
loop:
|
2014-08-03 07:00:42 +00:00
|
|
|
for {
|
2015-02-27 00:58:00 +00:00
|
|
|
select {
|
2015-04-09 04:13:15 +00:00
|
|
|
case <-stopCh:
|
|
|
|
return errorStopRequested
|
|
|
|
case <-resyncCh:
|
2015-02-27 00:58:00 +00:00
|
|
|
return errorResyncRequested
|
|
|
|
case event, ok := <-w.ResultChan():
|
|
|
|
if !ok {
|
|
|
|
break loop
|
|
|
|
}
|
|
|
|
if event.Type == watch.Error {
|
|
|
|
return apierrs.FromObject(event.Object)
|
|
|
|
}
|
|
|
|
if e, a := r.expectedType, reflect.TypeOf(event.Object); e != a {
|
|
|
|
glog.Errorf("expected type %v, but watch event object had type %v", e, a)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
meta, err := meta.Accessor(event.Object)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("unable to understand watch event %#v", event)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
switch event.Type {
|
|
|
|
case watch.Added:
|
|
|
|
r.store.Add(event.Object)
|
|
|
|
case watch.Modified:
|
|
|
|
r.store.Update(event.Object)
|
|
|
|
case watch.Deleted:
|
|
|
|
// TODO: Will any consumers need access to the "last known
|
|
|
|
// state", which is passed in event.Object? If so, may need
|
|
|
|
// to change this.
|
|
|
|
r.store.Delete(event.Object)
|
|
|
|
default:
|
|
|
|
glog.Errorf("unable to understand watch event %#v", event)
|
|
|
|
}
|
|
|
|
*resourceVersion = meta.ResourceVersion()
|
2015-03-27 18:17:54 +00:00
|
|
|
r.lastSyncResourceVersion = *resourceVersion
|
2015-02-27 00:58:00 +00:00
|
|
|
eventCount++
|
2014-08-03 07:00:42 +00:00
|
|
|
}
|
|
|
|
}
|
2014-09-20 01:09:40 +00:00
|
|
|
|
|
|
|
watchDuration := time.Now().Sub(start)
|
|
|
|
if watchDuration < 1*time.Second && eventCount == 0 {
|
2014-12-18 20:38:24 +00:00
|
|
|
glog.V(4).Infof("Unexpected watch close - watch lasted less than a second and no items received")
|
2014-09-20 01:09:40 +00:00
|
|
|
return errors.New("very short watch")
|
|
|
|
}
|
2014-12-05 20:03:07 +00:00
|
|
|
glog.V(4).Infof("Watch close - %v total %v items received", r.expectedType, eventCount)
|
2014-09-20 01:09:40 +00:00
|
|
|
return nil
|
2014-08-03 07:00:42 +00:00
|
|
|
}
|
2015-03-27 18:17:54 +00:00
|
|
|
|
|
|
|
// LastSyncResourceVersion is the resource version observed when last sync with the underlying store
|
|
|
|
// The value returned is not synchronized with access to the underlying store and is not thread-safe
|
|
|
|
func (r *Reflector) LastSyncResourceVersion() string {
|
|
|
|
return r.lastSyncResourceVersion
|
|
|
|
}
|