genericize ip cache

This commit is contained in:
Daniel Smith 2015-01-09 14:06:30 -08:00
parent f2b82c6c2b
commit 6e70853e83
3 changed files with 150 additions and 32 deletions

View File

@ -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 {

75
pkg/util/time_cache.go Normal file
View File

@ -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
}

View File

@ -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)
}
}