diff --git a/pkg/master/ip_cache.go b/pkg/master/ip_cache.go index 05a59c647c2..efbc06b2ae5 100644 --- a/pkg/master/ip_cache.go +++ b/pkg/master/ip_cache.go @@ -17,7 +17,6 @@ limitations under the License. package master import ( - "sync" "time" "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider" @@ -26,19 +25,6 @@ import ( "github.com/golang/glog" ) -type ipCacheEntry struct { - ip string - lastUpdate time.Time -} - -type ipCache struct { - clock util.Clock - cloudProvider cloudprovider.Interface - cache map[string]ipCacheEntry - lock sync.Mutex - ttl time.Duration -} - // NewIPCache makes a new ip caching layer, which will get IP addresses from cp, // and use clock for deciding when to re-get an IP address. // Thread-safe. @@ -47,30 +33,24 @@ type ipCache struct { // that could be produced from a template and a type via `go generate`. func NewIPCache(cp cloudprovider.Interface, clock util.Clock, ttl time.Duration) *ipCache { return &ipCache{ - clock: clock, - cloudProvider: cp, - cache: map[string]ipCacheEntry{}, - ttl: ttl, + cache: util.NewTimeCache( + clock, + ttl, + func(host string) util.T { + return getInstanceIPFromCloud(cp, host) + }, + ), } } +type ipCache struct { + cache util.TimeCache +} + // GetInstanceIP returns the IP address of host, from the cache // if possible, otherwise it asks the cloud provider. func (c *ipCache) GetInstanceIP(host string) string { - c.lock.Lock() - defer c.lock.Unlock() - data, ok := c.cache[host] - now := c.clock.Now() - - if !ok || now.Sub(data.lastUpdate) > c.ttl { - ip := getInstanceIPFromCloud(c.cloudProvider, host) - data = ipCacheEntry{ - ip: ip, - lastUpdate: now, - } - c.cache[host] = data - } - return data.ip + return c.cache.Get(host).(string) } func getInstanceIPFromCloud(cloud cloudprovider.Interface, host string) string { diff --git a/pkg/util/time_cache.go b/pkg/util/time_cache.go new file mode 100644 index 00000000000..7a9bf4a9d0b --- /dev/null +++ b/pkg/util/time_cache.go @@ -0,0 +1,75 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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 util + +import ( + "sync" + "time" +) + +// T stands in for any type in TimeCache +// Should make it easy to use this as a template for an autogenerator +// if we ever start doing that. +type T interface{} + +type TimeCache interface { + // Get will fetch an item from the cache if + // it is present and recent enough. + Get(key string) T +} + +type timeCacheEntry struct { + item T + lastUpdate time.Time +} + +type timeCache struct { + clock Clock + fillFunc func(string) T + cache map[string]timeCacheEntry + lock sync.Mutex + ttl time.Duration +} + +// NewTimeCache returns a cache which calls fill to fill its entries, and +// forgets entries after ttl has passed. +func NewTimeCache(clock Clock, ttl time.Duration, fill func(key string) T) TimeCache { + return &timeCache{ + clock: clock, + fillFunc: fill, + cache: map[string]timeCacheEntry{}, + ttl: ttl, + } +} + +// Get returns the value of key from the cache, if it is present +// and recent enough; otherwise, it blocks while it gets the value. +func (c *timeCache) Get(key string) T { + c.lock.Lock() + defer c.lock.Unlock() + data, ok := c.cache[key] + now := c.clock.Now() + + if !ok || now.Sub(data.lastUpdate) > c.ttl { + data = timeCacheEntry{ + item: c.fillFunc(key), + lastUpdate: now, + } + c.cache[key] = data + } + return data.item +} diff --git a/pkg/util/time_cache_test.go b/pkg/util/time_cache_test.go new file mode 100644 index 00000000000..1f42b08dcdd --- /dev/null +++ b/pkg/util/time_cache_test.go @@ -0,0 +1,63 @@ +/* +Copyright 2015 Google Inc. All rights reserved. + +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 util + +import ( + "testing" + "time" +) + +func TestCacheExpire(t *testing.T) { + calls := map[string]int{} + ff := func(key string) T { calls[key]++; return key } + clock := &FakeClock{time.Now()} + + c := NewTimeCache(clock, 60*time.Second, ff) + + c.Get("foo") + c.Get("bar") + // This call should hit the cache, so we expect no additional calls + c.Get("foo") + // Advance the clock, this call should miss the cache, so expect one more call. + clock.Time = clock.Time.Add(61 * time.Second) + c.Get("foo") + c.Get("bar") + + if e, a := 2, calls["foo"]; e != a { + t.Errorf("Wrong number of calls for foo: wanted %v, got %v", e, a) + } + if e, a := 2, calls["bar"]; e != a { + t.Errorf("Wrong number of calls for bar: wanted %v, got %v", e, a) + } +} + +func TestCacheNotExpire(t *testing.T) { + calls := map[string]int{} + ff := func(key string) T { calls[key]++; return key } + clock := &FakeClock{time.Now()} + + c := NewTimeCache(clock, 60*time.Second, ff) + + c.Get("foo") + // This call should hit the cache, so we expect no additional calls to the cloud + clock.Time = clock.Time.Add(60 * time.Second) + c.Get("foo") + + if e, a := 1, calls["foo"]; e != a { + t.Errorf("Wrong number of calls for foo: wanted %v, got %v", e, a) + } +}