1/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package cache
18
19import (
20	"reflect"
21	"testing"
22	"time"
23
24	"k8s.io/apimachinery/pkg/util/clock"
25	"k8s.io/apimachinery/pkg/util/sets"
26	"k8s.io/apimachinery/pkg/util/wait"
27)
28
29func TestTTLExpirationBasic(t *testing.T) {
30	testObj := testStoreObject{id: "foo", val: "bar"}
31	deleteChan := make(chan string, 1)
32	ttlStore := NewFakeExpirationStore(
33		testStoreKeyFunc, deleteChan,
34		&FakeExpirationPolicy{
35			NeverExpire: sets.NewString(),
36			RetrieveKeyFunc: func(obj interface{}) (string, error) {
37				return obj.(*TimestampedEntry).Obj.(testStoreObject).id, nil
38			},
39		},
40		clock.RealClock{},
41	)
42	err := ttlStore.Add(testObj)
43	if err != nil {
44		t.Errorf("Unable to add obj %#v", testObj)
45	}
46	item, exists, err := ttlStore.Get(testObj)
47	if err != nil {
48		t.Errorf("Failed to get from store, %v", err)
49	}
50	if exists || item != nil {
51		t.Errorf("Got unexpected item %#v", item)
52	}
53	key, _ := testStoreKeyFunc(testObj)
54	select {
55	case delKey := <-deleteChan:
56		if delKey != key {
57			t.Errorf("Unexpected delete for key %s", key)
58		}
59	case <-time.After(wait.ForeverTestTimeout):
60		t.Errorf("Unexpected timeout waiting on delete")
61	}
62	close(deleteChan)
63}
64
65func TestReAddExpiredItem(t *testing.T) {
66	deleteChan := make(chan string, 1)
67	exp := &FakeExpirationPolicy{
68		NeverExpire: sets.NewString(),
69		RetrieveKeyFunc: func(obj interface{}) (string, error) {
70			return obj.(*TimestampedEntry).Obj.(testStoreObject).id, nil
71		},
72	}
73	ttlStore := NewFakeExpirationStore(
74		testStoreKeyFunc, deleteChan, exp, clock.RealClock{})
75	testKey := "foo"
76	testObj := testStoreObject{id: testKey, val: "bar"}
77	err := ttlStore.Add(testObj)
78	if err != nil {
79		t.Errorf("Unable to add obj %#v", testObj)
80	}
81
82	// This get will expire the item.
83	item, exists, err := ttlStore.Get(testObj)
84	if err != nil {
85		t.Errorf("Failed to get from store, %v", err)
86	}
87	if exists || item != nil {
88		t.Errorf("Got unexpected item %#v", item)
89	}
90
91	key, _ := testStoreKeyFunc(testObj)
92	differentValue := "different_bar"
93	err = ttlStore.Add(
94		testStoreObject{id: testKey, val: differentValue})
95	if err != nil {
96		t.Errorf("Failed to add second value")
97	}
98
99	select {
100	case delKey := <-deleteChan:
101		if delKey != key {
102			t.Errorf("Unexpected delete for key %s", key)
103		}
104	case <-time.After(wait.ForeverTestTimeout):
105		t.Errorf("Unexpected timeout waiting on delete")
106	}
107	exp.NeverExpire = sets.NewString(testKey)
108	item, exists, err = ttlStore.GetByKey(testKey)
109	if err != nil {
110		t.Errorf("Failed to get from store, %v", err)
111	}
112	if !exists || item == nil || item.(testStoreObject).val != differentValue {
113		t.Errorf("Got unexpected item %#v", item)
114	}
115	close(deleteChan)
116}
117
118func TestTTLList(t *testing.T) {
119	testObjs := []testStoreObject{
120		{id: "foo", val: "bar"},
121		{id: "foo1", val: "bar1"},
122		{id: "foo2", val: "bar2"},
123	}
124	expireKeys := sets.NewString(testObjs[0].id, testObjs[2].id)
125	deleteChan := make(chan string, len(testObjs))
126	defer close(deleteChan)
127
128	ttlStore := NewFakeExpirationStore(
129		testStoreKeyFunc, deleteChan,
130		&FakeExpirationPolicy{
131			NeverExpire: sets.NewString(testObjs[1].id),
132			RetrieveKeyFunc: func(obj interface{}) (string, error) {
133				return obj.(*TimestampedEntry).Obj.(testStoreObject).id, nil
134			},
135		},
136		clock.RealClock{},
137	)
138	for _, obj := range testObjs {
139		err := ttlStore.Add(obj)
140		if err != nil {
141			t.Errorf("Unable to add obj %#v", obj)
142		}
143	}
144	listObjs := ttlStore.List()
145	if len(listObjs) != 1 || !reflect.DeepEqual(listObjs[0], testObjs[1]) {
146		t.Errorf("List returned unexpected results %#v", listObjs)
147	}
148
149	// Make sure all our deletes come through in an acceptable rate (1/100ms)
150	for expireKeys.Len() != 0 {
151		select {
152		case delKey := <-deleteChan:
153			if !expireKeys.Has(delKey) {
154				t.Errorf("Unexpected delete for key %s", delKey)
155			}
156			expireKeys.Delete(delKey)
157		case <-time.After(wait.ForeverTestTimeout):
158			t.Errorf("Unexpected timeout waiting on delete")
159			return
160		}
161	}
162}
163
164func TestTTLPolicy(t *testing.T) {
165	fakeTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
166	ttl := 30 * time.Second
167	exactlyOnTTL := fakeTime.Add(-ttl)
168	expiredTime := fakeTime.Add(-(ttl + 1))
169
170	policy := TTLPolicy{ttl, clock.NewFakeClock(fakeTime)}
171	item := testStoreObject{id: "foo", val: "bar"}
172	itemkey, _ := testStoreKeyFunc(item)
173	fakeTimestampedEntry := &TimestampedEntry{Obj: item, Timestamp: exactlyOnTTL, key: itemkey}
174	if policy.IsExpired(fakeTimestampedEntry) {
175		t.Errorf("TTL cache should not expire entries exactly on ttl")
176	}
177	fakeTimestampedEntry.Timestamp = fakeTime
178	if policy.IsExpired(fakeTimestampedEntry) {
179		t.Errorf("TTL Cache should not expire entries before ttl")
180	}
181	fakeTimestampedEntry.Timestamp = expiredTime
182	if !policy.IsExpired(fakeTimestampedEntry) {
183		t.Errorf("TTL Cache should expire entries older than ttl")
184	}
185	for _, ttl = range []time.Duration{0, -1} {
186		policy.TTL = ttl
187		if policy.IsExpired(fakeTimestampedEntry) {
188			t.Errorf("TTL policy should only expire entries when initialized with a ttl > 0")
189		}
190	}
191}
192