recommender_mock removed
This commit is contained in:
parent
c9f5b0a41c
commit
7271220b10
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
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 recommender
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"k8s.io/autoscaler/vertical-pod-autoscaler/apimock"
|
||||
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
hashutil "k8s.io/kubernetes/pkg/util/hash"
|
||||
)
|
||||
|
||||
// CachingRecommender provides VPA recommendations for pods.
|
||||
// VPA responses are cached.
|
||||
type CachingRecommender interface {
|
||||
// Get returns VPA recommendation for given pod
|
||||
Get(spec *apiv1.PodSpec) (*apimock.Recommendation, error)
|
||||
}
|
||||
|
||||
type cachingRecommenderImpl struct {
|
||||
api apimock.RecommenderAPI
|
||||
cache *TTLCache
|
||||
}
|
||||
|
||||
// NewCachingRecommender creates CachingRecommender with given cache TTL
|
||||
func NewCachingRecommender(ttl time.Duration, api apimock.RecommenderAPI) CachingRecommender {
|
||||
ca := NewTTLCache(ttl)
|
||||
ca.StartCacheGC(ttl)
|
||||
|
||||
result := &cachingRecommenderImpl{api: api, cache: ca}
|
||||
// We need to stop background cacheGC worker if cachingRecommenderImpl gets destryed.
|
||||
// If we forget this, background go routine will forever run and hold a reference to TTLCache object.
|
||||
runtime.SetFinalizer(result, stopChacheGC)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Get returns VPA recommendation for the given pod. If recommendation is not in cache, sends request to RecommenderAPI
|
||||
func (c *cachingRecommenderImpl) Get(spec *apiv1.PodSpec) (*apimock.Recommendation, error) {
|
||||
cacheKey := getCacheKey(spec)
|
||||
if cacheKey != nil {
|
||||
if cached := c.cache.Get(cacheKey); cached != nil {
|
||||
return cached.(*apimock.Recommendation), nil
|
||||
}
|
||||
}
|
||||
|
||||
response, err := c.api.GetRecommendation(spec)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching recommendation %v", err)
|
||||
}
|
||||
if response != nil && cacheKey != nil {
|
||||
c.cache.Set(cacheKey, response)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func getCacheKey(spec *apiv1.PodSpec) *string {
|
||||
podTemplateSpecHasher := sha1.New()
|
||||
hashutil.DeepHashObject(podTemplateSpecHasher, *spec)
|
||||
result := string(podTemplateSpecHasher.Sum(make([]byte, 0)))
|
||||
return &result
|
||||
}
|
||||
|
||||
func stopChacheGC(c *cachingRecommenderImpl) {
|
||||
c.cache.StopCacheGC()
|
||||
}
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
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 recommender
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/autoscaler/vertical-pod-autoscaler/pkg/utils/test"
|
||||
)
|
||||
|
||||
func TestGetWithCache(t *testing.T) {
|
||||
apiMock := &test.RecommenderAPIMock{}
|
||||
rec := test.Recommendation("test", "", "")
|
||||
pod := test.BuildTestPod("test", "", "", "", nil, nil)
|
||||
apiMock.On("GetRecommendation", &pod.Spec).Return(rec, nil)
|
||||
recommender := NewCachingRecommender(10*time.Second, apiMock)
|
||||
|
||||
result, err := recommender.Get(&pod.Spec)
|
||||
|
||||
assert.Equal(t, rec, result)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
// test get from cache
|
||||
for i := 0; i < 5; i++ {
|
||||
result, err = recommender.Get(&pod.Spec)
|
||||
}
|
||||
apiMock.AssertNumberOfCalls(t, "GetRecommendation", 1)
|
||||
}
|
||||
|
||||
func TestGetCacheExpired(t *testing.T) {
|
||||
apiMock := &test.RecommenderAPIMock{}
|
||||
rec := test.Recommendation("test", "", "")
|
||||
pod := test.BuildTestPod("test", "", "", "", nil, nil)
|
||||
apiMock.On("GetRecommendation", &pod.Spec).Return(rec, nil)
|
||||
recommender := NewCachingRecommender(time.Second, apiMock)
|
||||
|
||||
result, err := recommender.Get(&pod.Spec)
|
||||
assert.Equal(t, rec, result)
|
||||
assert.Equal(t, nil, err)
|
||||
|
||||
<-time.After(2 * time.Second)
|
||||
|
||||
result, err = recommender.Get(&pod.Spec)
|
||||
apiMock.AssertNumberOfCalls(t, "GetRecommendation", 2)
|
||||
|
||||
}
|
||||
|
||||
func TestNoRec(t *testing.T) {
|
||||
apiMock := &test.RecommenderAPIMock{}
|
||||
pod := test.BuildTestPod("test", "", "", "", nil, nil)
|
||||
apiMock.On("GetRecommendation", &pod.Spec).Return(nil, nil)
|
||||
recommender := NewCachingRecommender(time.Second, apiMock)
|
||||
|
||||
result, err := recommender.Get(&pod.Spec)
|
||||
assert.Nil(t, result)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// check nil response not chached
|
||||
result, err = recommender.Get(&pod.Spec)
|
||||
apiMock.AssertNumberOfCalls(t, "GetRecommendation", 2)
|
||||
}
|
||||
|
||||
func TestError(t *testing.T) {
|
||||
apiMock := &test.RecommenderAPIMock{}
|
||||
pod := test.BuildTestPod("test", "", "", "", nil, nil)
|
||||
err := fmt.Errorf("Expected Fail")
|
||||
apiMock.On("GetRecommendation", &pod.Spec).Return(nil, err)
|
||||
recommender := NewCachingRecommender(time.Second, apiMock)
|
||||
|
||||
result, err := recommender.Get(&pod.Spec)
|
||||
assert.Nil(t, result)
|
||||
assert.Error(t, err)
|
||||
|
||||
// check error response not chached
|
||||
result, err = recommender.Get(&pod.Spec)
|
||||
apiMock.AssertNumberOfCalls(t, "GetRecommendation", 2)
|
||||
}
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
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 recommender
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TTLCache represents cache with ttl
|
||||
type TTLCache struct {
|
||||
ttl time.Duration
|
||||
mutex sync.RWMutex
|
||||
items map[string]*cachedValue
|
||||
stopGC chan struct{}
|
||||
}
|
||||
|
||||
type cachedValue struct {
|
||||
value interface{}
|
||||
expirationTime time.Time
|
||||
}
|
||||
|
||||
// NewTTLCache reates TTLCache for given TTL
|
||||
func NewTTLCache(ttl time.Duration) *TTLCache {
|
||||
return &TTLCache{
|
||||
ttl: ttl,
|
||||
items: make(map[string]*cachedValue),
|
||||
stopGC: make(chan struct{})}
|
||||
}
|
||||
|
||||
func (r *cachedValue) isFresh() bool {
|
||||
return r.expirationTime.After(time.Now())
|
||||
}
|
||||
|
||||
// Get Returns value present in cache for given cache key, or nil if key is not found or value TTL has expired.
|
||||
func (c *TTLCache) Get(cacheKey *string) interface{} {
|
||||
c.mutex.RLock()
|
||||
defer c.mutex.RUnlock()
|
||||
value, found := c.items[*cacheKey]
|
||||
if found && value.isFresh() {
|
||||
return value.value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set adds given value for given key
|
||||
func (c *TTLCache) Set(cacheKey *string, value interface{}) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
c.items[*cacheKey] = &cachedValue{value: value, expirationTime: time.Now().Add(c.ttl)}
|
||||
}
|
||||
|
||||
func (c *TTLCache) cleanup() {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
for key, item := range c.items {
|
||||
if !item.isFresh() {
|
||||
delete(c.items, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StartCacheGC starts background garbage collector worker which on every time interval removes expired cache entries
|
||||
// If StartCacheGC was called, in order to properly remove cache object, call StopCacheGC.
|
||||
// Otherwise TTLCache will never be garbage collected since background worker holds reference to it.
|
||||
func (c *TTLCache) StartCacheGC(interval time.Duration) {
|
||||
ticker := time.Tick(interval)
|
||||
go (func() {
|
||||
for {
|
||||
select {
|
||||
case <-ticker:
|
||||
c.cleanup()
|
||||
case <-c.stopGC:
|
||||
return
|
||||
}
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
// StopCacheGC stops background cache garbage collector
|
||||
func (c *TTLCache) StopCacheGC() {
|
||||
c.stopGC <- struct{}{}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
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 recommender
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
cache := NewTTLCache(5 * time.Second)
|
||||
cacheKey := "hello"
|
||||
result := cache.Get(&cacheKey)
|
||||
assert.Nil(t, result, "Result should be nil")
|
||||
|
||||
cache.Set(&cacheKey, "world")
|
||||
result = cache.Get(&cacheKey)
|
||||
assert.Equal(t, "world", result)
|
||||
}
|
||||
|
||||
func TestExpiration(t *testing.T) {
|
||||
cache := NewTTLCache(5 * time.Second)
|
||||
key1 := "A"
|
||||
key2 := "B"
|
||||
key3 := "C"
|
||||
|
||||
cache.Set(&key1, "1")
|
||||
cache.Set(&key2, "2")
|
||||
cache.Set(&key3, "3")
|
||||
cache.StartCacheGC(time.Second)
|
||||
|
||||
<-time.After(2 * time.Second)
|
||||
assert.Equal(t, "1", cache.Get(&key1))
|
||||
cache.Set(&key1, "1")
|
||||
|
||||
<-time.After(3 * time.Second)
|
||||
assert.Equal(t, "1", cache.Get(&key1))
|
||||
assert.Nil(t, cache.Get(&key2))
|
||||
assert.Nil(t, cache.Get(&key3))
|
||||
|
||||
<-time.After(5 * time.Second)
|
||||
assert.Nil(t, cache.Get(&key1))
|
||||
|
||||
cache.StopCacheGC()
|
||||
cache.Set(&key1, "1")
|
||||
<-time.After(6 * time.Second)
|
||||
assert.Nil(t, cache.Get(&key1))
|
||||
}
|
||||
Loading…
Reference in New Issue