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