func TestExpirationBasic(t *testing.T) { unexpectedVal := "bar" expectedVal := "bar2" testObj := testObject{ key: "foo", val: unexpectedVal, } fakeClock := clock.NewFakeClock(time.Now()) objectCache := NewFakeObjectCache(func() (interface{}, error) { return expectedVal, nil }, 1*time.Second, fakeClock) err := objectCache.Add(testObj.key, testObj.val) if err != nil { t.Errorf("Unable to add obj %#v by key: %s", testObj, testObj.key) } // sleep 2s so cache should be expired. fakeClock.Sleep(2 * time.Second) value, err := objectCache.Get(testObj.key) if err != nil { t.Errorf("Unable to get obj %#v by key: %s", testObj, testObj.key) } if value.(string) != expectedVal { t.Errorf("Expected to get cached value: %#v, but got: %s", expectedVal, value.(string)) } }
func TestSecretStoreGetAlwaysRefresh(t *testing.T) { fakeClient := &fake.Clientset{} fakeClock := clock.NewFakeClock(time.Now()) store := newSecretStore(fakeClient, fakeClock, 0) for i := 0; i < 10; i++ { store.Add(fmt.Sprintf("ns-%d", i), fmt.Sprintf("name-%d", i)) } fakeClient.ClearActions() wg := sync.WaitGroup{} wg.Add(100) for i := 0; i < 100; i++ { go func(i int) { store.Get(fmt.Sprintf("ns-%d", i%10), fmt.Sprintf("name-%d", i%10)) wg.Done() }(i) } wg.Wait() actions := fakeClient.Actions() assert.Equal(t, 100, len(actions), "unexpected actions: %#v", actions) for _, a := range actions { assert.True(t, a.Matches("get", "secrets"), "unexpected actions: %#v", a) } }
func TestTTLPolicy(t *testing.T) { fakeTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) ttl := 30 * time.Second exactlyOnTTL := fakeTime.Add(-ttl) expiredTime := fakeTime.Add(-(ttl + 1)) policy := TTLPolicy{ttl, clock.NewFakeClock(fakeTime)} fakeTimestampedEntry := ×tampedEntry{obj: struct{}{}, timestamp: exactlyOnTTL} if policy.IsExpired(fakeTimestampedEntry) { t.Errorf("TTL cache should not expire entries exactly on ttl") } fakeTimestampedEntry.timestamp = fakeTime if policy.IsExpired(fakeTimestampedEntry) { t.Errorf("TTL Cache should not expire entries before ttl") } fakeTimestampedEntry.timestamp = expiredTime if !policy.IsExpired(fakeTimestampedEntry) { t.Errorf("TTL Cache should expire entries older than ttl") } for _, ttl = range []time.Duration{0, -1} { policy.Ttl = ttl if policy.IsExpired(fakeTimestampedEntry) { t.Errorf("TTL policy should only expire entries when initialized with a ttl > 0") } } }
// NewFakeRecorder returns a pointer to a newly constructed FakeRecorder. func NewFakeRecorder() *FakeRecorder { return &FakeRecorder{ source: v1.EventSource{Component: "nodeControllerTest"}, Events: []*v1.Event{}, clock: clock.NewFakeClock(time.Now()), } }
func TestCacheRefcounts(t *testing.T) { fakeClient := &fake.Clientset{} fakeClock := clock.NewFakeClock(time.Now()) store := newSecretStore(fakeClient, fakeClock, time.Minute) manager := &cachingSecretManager{ secretStore: store, registeredPods: make(map[objectKey]*v1.Pod), } s1 := secretsToAttach{ imagePullSecretNames: []string{"s1"}, containerEnvSecretNames: [][]string{{"s1"}, {"s2"}, {"s3"}}, } manager.RegisterPod(podWithSecrets("ns1", "name1", s1)) manager.RegisterPod(podWithSecrets("ns1", "name2", s1)) s2 := secretsToAttach{ imagePullSecretNames: []string{"s2"}, containerEnvSecretNames: [][]string{{"s4"}, {"s5"}}, } manager.RegisterPod(podWithSecrets("ns1", "name2", s2)) manager.RegisterPod(podWithSecrets("ns1", "name3", s2)) manager.RegisterPod(podWithSecrets("ns1", "name4", s2)) manager.UnregisterPod(podWithSecrets("ns1", "name3", s2)) s3 := secretsToAttach{ imagePullSecretNames: []string{"s1"}, containerEnvSecretNames: [][]string{{"s3"}, {"s5"}}, } manager.RegisterPod(podWithSecrets("ns1", "name5", s3)) manager.RegisterPod(podWithSecrets("ns1", "name6", s3)) s4 := secretsToAttach{ imagePullSecretNames: []string{"s3"}, containerEnvSecretNames: [][]string{{"s6"}}, } manager.RegisterPod(podWithSecrets("ns1", "name7", s4)) manager.UnregisterPod(podWithSecrets("ns1", "name7", s4)) // Also check the Add + Update + Remove scenario. manager.RegisterPod(podWithSecrets("ns1", "other-name", s1)) manager.RegisterPod(podWithSecrets("ns1", "other-name", s2)) manager.UnregisterPod(podWithSecrets("ns1", "other-name", s2)) // Now we have: 1 pod with s1, 2 pods with s2 and 2 pods with s3, 0 pods with s4. verify := func(ns, name string, count int) bool { store.lock.Lock() defer store.lock.Unlock() item, ok := store.items[objectKey{ns, name}] if !ok { return count == 0 } return item.refCount == count } assert.True(t, verify("ns1", "s1", 3)) assert.True(t, verify("ns1", "s2", 3)) assert.True(t, verify("ns1", "s3", 3)) assert.True(t, verify("ns1", "s4", 2)) assert.True(t, verify("ns1", "s5", 4)) assert.True(t, verify("ns1", "s6", 0)) assert.True(t, verify("ns1", "s7", 0)) }
// NewFakeControllerExpectationsLookup creates a fake store for PodExpectations. func NewFakeControllerExpectationsLookup(ttl time.Duration) (*ControllerExpectations, *clock.FakeClock) { fakeTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) fakeClock := clock.NewFakeClock(fakeTime) ttlPolicy := &cache.TTLPolicy{Ttl: ttl, Clock: fakeClock} ttlStore := cache.NewFakeExpirationStore( ExpKeyFunc, nil, ttlPolicy, fakeClock) return &ControllerExpectations{ttlStore}, fakeClock }
func TestExpiredGet(t *testing.T) { fakeClock := clock.NewFakeClock(time.Now()) c := NewLRUExpireCacheWithClock(10, fakeClock) c.Add("short-lived", "12345", 1*time.Millisecond) // ensure the entry expired fakeClock.Step(2 * time.Millisecond) expectNotEntry(t, c, "short-lived") }
func newTestBasicWorkQueue() (*basicWorkQueue, *clock.FakeClock) { fakeClock := clock.NewFakeClock(time.Now()) wq := &basicWorkQueue{ clock: fakeClock, queue: make(map[types.UID]time.Time), } return wq, fakeClock }
func TestDeduping(t *testing.T) { fakeClock := clock.NewFakeClock(time.Now()) q := newDelayingQueue(fakeClock, "") first := "foo" q.AddAfter(first, 50*time.Millisecond) if err := waitForWaitingQueueToFill(q); err != nil { t.Fatalf("unexpected err: %v", err) } q.AddAfter(first, 70*time.Millisecond) if err := waitForWaitingQueueToFill(q); err != nil { t.Fatalf("unexpected err: %v", err) } if q.Len() != 0 { t.Errorf("should not have added") } // step past the first block, we should receive now fakeClock.Step(60 * time.Millisecond) if err := waitForAdded(q, 1); err != nil { t.Errorf("should have added") } item, _ := q.Get() q.Done(item) // step past the second add fakeClock.Step(20 * time.Millisecond) if q.Len() != 0 { t.Errorf("should not have added") } // test again, but this time the earlier should override q.AddAfter(first, 50*time.Millisecond) q.AddAfter(first, 30*time.Millisecond) if err := waitForWaitingQueueToFill(q); err != nil { t.Fatalf("unexpected err: %v", err) } if q.Len() != 0 { t.Errorf("should not have added") } fakeClock.Step(40 * time.Millisecond) if err := waitForAdded(q, 1); err != nil { t.Errorf("should have added") } item, _ = q.Get() q.Done(item) // step past the second add fakeClock.Step(20 * time.Millisecond) if q.Len() != 0 { t.Errorf("should not have added") } if q.Len() != 0 { t.Errorf("should not have added") } }
// TestSecondsSinceSync verifies that proper results are returned // when checking the time between syncs func TestSecondsSinceSync(t *testing.T) { tunneler := &SSHTunneler{} assert := assert.New(t) tunneler.lastSync = time.Date(2015, time.January, 1, 1, 1, 1, 1, time.UTC).Unix() // Nano Second. No difference. tunneler.clock = clock.NewFakeClock(time.Date(2015, time.January, 1, 1, 1, 1, 2, time.UTC)) assert.Equal(int64(0), tunneler.SecondsSinceSync()) // Second tunneler.clock = clock.NewFakeClock(time.Date(2015, time.January, 1, 1, 1, 2, 1, time.UTC)) assert.Equal(int64(1), tunneler.SecondsSinceSync()) // Minute tunneler.clock = clock.NewFakeClock(time.Date(2015, time.January, 1, 1, 2, 1, 1, time.UTC)) assert.Equal(int64(60), tunneler.SecondsSinceSync()) // Hour tunneler.clock = clock.NewFakeClock(time.Date(2015, time.January, 1, 2, 1, 1, 1, time.UTC)) assert.Equal(int64(3600), tunneler.SecondsSinceSync()) // Day tunneler.clock = clock.NewFakeClock(time.Date(2015, time.January, 2, 1, 1, 1, 1, time.UTC)) assert.Equal(int64(86400), tunneler.SecondsSinceSync()) // Month tunneler.clock = clock.NewFakeClock(time.Date(2015, time.February, 1, 1, 1, 1, 1, time.UTC)) assert.Equal(int64(2678400), tunneler.SecondsSinceSync()) // Future Month. Should be -Month. tunneler.lastSync = time.Date(2015, time.February, 1, 1, 1, 1, 1, time.UTC).Unix() tunneler.clock = clock.NewFakeClock(time.Date(2015, time.January, 1, 1, 1, 1, 1, time.UTC)) assert.Equal(int64(-2678400), tunneler.SecondsSinceSync()) }
// newTestWatchCache just adds a fake clock. func newTestWatchCache(capacity int) *watchCache { keyFunc := func(obj runtime.Object) (string, error) { return NamespaceKeyFunc("prefix", obj) } getAttrsFunc := func(obj runtime.Object) (labels.Set, fields.Set, error) { return nil, nil, nil } wc := newWatchCache(capacity, keyFunc, getAttrsFunc) wc.clock = clock.NewFakeClock(time.Now()) return wc }
// TestActiveDeadlineHandler verifies the active deadline handler functions as expected. func TestActiveDeadlineHandler(t *testing.T) { pods := newTestPods(4) fakeClock := clock.NewFakeClock(time.Now()) podStatusProvider := &mockPodStatusProvider{pods: pods} fakeRecorder := &record.FakeRecorder{} handler, err := newActiveDeadlineHandler(podStatusProvider, fakeRecorder, fakeClock) if err != nil { t.Fatalf("unexpected error: %v", err) } now := metav1.Now() startTime := metav1.NewTime(now.Time.Add(-1 * time.Minute)) // this pod has exceeded its active deadline exceededActiveDeadlineSeconds := int64(30) pods[0].Status.StartTime = &startTime pods[0].Spec.ActiveDeadlineSeconds = &exceededActiveDeadlineSeconds // this pod has not exceeded its active deadline notYetActiveDeadlineSeconds := int64(120) pods[1].Status.StartTime = &startTime pods[1].Spec.ActiveDeadlineSeconds = ¬YetActiveDeadlineSeconds // this pod has no deadline pods[2].Status.StartTime = &startTime pods[2].Spec.ActiveDeadlineSeconds = nil testCases := []struct { pod *v1.Pod expected bool }{{pods[0], true}, {pods[1], false}, {pods[2], false}, {pods[3], false}} for i, testCase := range testCases { if actual := handler.ShouldSync(testCase.pod); actual != testCase.expected { t.Errorf("[%d] ShouldSync expected %#v, got %#v", i, testCase.expected, actual) } actual := handler.ShouldEvict(testCase.pod) if actual.Evict != testCase.expected { t.Errorf("[%d] ShouldEvict.Evict expected %#v, got %#v", i, testCase.expected, actual.Evict) } if testCase.expected { if actual.Reason != reason { t.Errorf("[%d] ShouldEvict.Reason expected %#v, got %#v", i, message, actual.Reason) } if actual.Message != message { t.Errorf("[%d] ShouldEvict.Message expected %#v, got %#v", i, message, actual.Message) } } } }
func TestRateLimitingQueue(t *testing.T) { limiter := NewItemExponentialFailureRateLimiter(1*time.Millisecond, 1*time.Second) queue := NewRateLimitingQueue(limiter).(*rateLimitingType) fakeClock := clock.NewFakeClock(time.Now()) delayingQueue := &delayingType{ Interface: New(), clock: fakeClock, heartbeat: fakeClock.Tick(maxWait), stopCh: make(chan struct{}), waitingForAddCh: make(chan waitFor, 1000), metrics: newRetryMetrics(""), } queue.DelayingInterface = delayingQueue queue.AddRateLimited("one") waitEntry := <-delayingQueue.waitingForAddCh if e, a := 1*time.Millisecond, waitEntry.readyAt.Sub(fakeClock.Now()); e != a { t.Errorf("expected %v, got %v", e, a) } queue.AddRateLimited("one") waitEntry = <-delayingQueue.waitingForAddCh if e, a := 2*time.Millisecond, waitEntry.readyAt.Sub(fakeClock.Now()); e != a { t.Errorf("expected %v, got %v", e, a) } if e, a := 2, queue.NumRequeues("one"); e != a { t.Errorf("expected %v, got %v", e, a) } queue.AddRateLimited("two") waitEntry = <-delayingQueue.waitingForAddCh if e, a := 1*time.Millisecond, waitEntry.readyAt.Sub(fakeClock.Now()); e != a { t.Errorf("expected %v, got %v", e, a) } queue.AddRateLimited("two") waitEntry = <-delayingQueue.waitingForAddCh if e, a := 2*time.Millisecond, waitEntry.readyAt.Sub(fakeClock.Now()); e != a { t.Errorf("expected %v, got %v", e, a) } queue.Forget("one") if e, a := 0, queue.NumRequeues("one"); e != a { t.Errorf("expected %v, got %v", e, a) } queue.AddRateLimited("one") waitEntry = <-delayingQueue.waitingForAddCh if e, a := 1*time.Millisecond, waitEntry.readyAt.Sub(fakeClock.Now()); e != a { t.Errorf("expected %v, got %v", e, a) } }
func newTestGenericPLEG() *TestGenericPLEG { fakeRuntime := &containertest.FakeRuntime{} clock := clock.NewFakeClock(time.Time{}) // The channel capacity should be large enough to hold all events in a // single test. pleg := &GenericPLEG{ relistPeriod: time.Hour, runtime: fakeRuntime, eventChannel: make(chan *PodLifecycleEvent, 100), podRecords: make(podRecords), clock: clock, } return &TestGenericPLEG{pleg: pleg, runtime: fakeRuntime, clock: clock} }
func TestAddTwoFireEarly(t *testing.T) { fakeClock := clock.NewFakeClock(time.Now()) q := newDelayingQueue(fakeClock, "") first := "foo" second := "bar" third := "baz" q.AddAfter(first, 1*time.Second) q.AddAfter(second, 50*time.Millisecond) if err := waitForWaitingQueueToFill(q); err != nil { t.Fatalf("unexpected err: %v", err) } if q.Len() != 0 { t.Errorf("should not have added") } fakeClock.Step(60 * time.Millisecond) if err := waitForAdded(q, 1); err != nil { t.Fatalf("unexpected err: %v", err) } item, _ := q.Get() if !reflect.DeepEqual(item, second) { t.Errorf("expected %v, got %v", second, item) } q.AddAfter(third, 2*time.Second) fakeClock.Step(1 * time.Second) if err := waitForAdded(q, 1); err != nil { t.Fatalf("unexpected err: %v", err) } item, _ = q.Get() if !reflect.DeepEqual(item, first) { t.Errorf("expected %v, got %v", first, item) } fakeClock.Step(2 * time.Second) if err := waitForAdded(q, 1); err != nil { t.Fatalf("unexpected err: %v", err) } item, _ = q.Get() if !reflect.DeepEqual(item, third) { t.Errorf("expected %v, got %v", third, item) } }
func TestGarbageCollectImageNotOldEnough(t *testing.T) { policy := ImageGCPolicy{ HighThresholdPercent: 90, LowThresholdPercent: 80, MinAge: time.Minute * 1, } fakeRuntime := &containertest.FakeRuntime{} mockCadvisor := new(cadvisortest.Mock) manager := &realImageGCManager{ runtime: fakeRuntime, policy: policy, imageRecords: make(map[string]*imageRecord), cadvisor: mockCadvisor, recorder: &record.FakeRecorder{}, } fakeRuntime.ImageList = []container.Image{ makeImage(0, 1024), makeImage(1, 2048), } // 1 image is in use, and another one is not old enough fakeRuntime.AllPodList = []*containertest.FakePod{ {Pod: &container.Pod{ Containers: []*container.Container{ makeContainer(1), }, }}, } fakeClock := clock.NewFakeClock(time.Now()) t.Log(fakeClock.Now()) require.NoError(t, manager.detectImages(fakeClock.Now())) require.Equal(t, manager.imageRecordsLen(), 2) // no space freed since one image is in used, and another one is not old enough spaceFreed, err := manager.freeSpace(1024, fakeClock.Now()) assert := assert.New(t) require.NoError(t, err) assert.EqualValues(0, spaceFreed) assert.Len(fakeRuntime.ImageList, 2) // move clock by minAge duration, then 1 image will be garbage collected fakeClock.Step(policy.MinAge) spaceFreed, err = manager.freeSpace(1024, fakeClock.Now()) require.NoError(t, err) assert.EqualValues(1024, spaceFreed) assert.Len(fakeRuntime.ImageList, 1) }
func TestCacheInvalidation(t *testing.T) { fakeClient := &fake.Clientset{} fakeClock := clock.NewFakeClock(time.Now()) store := newSecretStore(fakeClient, fakeClock, time.Minute) manager := &cachingSecretManager{ secretStore: store, registeredPods: make(map[objectKey]*v1.Pod), } // Create a pod with some secrets. s1 := secretsToAttach{ imagePullSecretNames: []string{"s1"}, containerEnvSecretNames: [][]string{{"s1"}, {"s2"}}, } manager.RegisterPod(podWithSecrets("ns1", "name1", s1)) // Fetch both secrets - this should triggger get operations. store.Get("ns1", "s1") store.Get("ns1", "s2") actions := fakeClient.Actions() assert.Equal(t, 2, len(actions), "unexpected actions: %#v", actions) fakeClient.ClearActions() // Update a pod with a new secret. s2 := secretsToAttach{ imagePullSecretNames: []string{"s1"}, containerEnvSecretNames: [][]string{{"s1"}, {"s2"}, {"s3"}}, } manager.RegisterPod(podWithSecrets("ns1", "name1", s2)) // All secrets should be invalidated - this should trigger get operations. store.Get("ns1", "s1") store.Get("ns1", "s2") store.Get("ns1", "s3") actions = fakeClient.Actions() assert.Equal(t, 3, len(actions), "unexpected actions: %#v", actions) fakeClient.ClearActions() // Create a new pod that is refencing the first two secrets - those should // be invalidated. manager.RegisterPod(podWithSecrets("ns1", "name2", s1)) store.Get("ns1", "s1") store.Get("ns1", "s2") store.Get("ns1", "s3") actions = fakeClient.Actions() assert.Equal(t, 2, len(actions), "unexpected actions: %#v", actions) fakeClient.ClearActions() }
func TestSimpleQueue(t *testing.T) { fakeClock := clock.NewFakeClock(time.Now()) q := newDelayingQueue(fakeClock, "") first := "foo" q.AddAfter(first, 50*time.Millisecond) if err := waitForWaitingQueueToFill(q); err != nil { t.Fatalf("unexpected err: %v", err) } if q.Len() != 0 { t.Errorf("should not have added") } fakeClock.Step(60 * time.Millisecond) if err := waitForAdded(q, 1); err != nil { t.Errorf("should have added") } item, _ := q.Get() q.Done(item) // step past the next heartbeat fakeClock.Step(10 * time.Second) err := wait.Poll(1*time.Millisecond, 30*time.Millisecond, func() (done bool, err error) { if q.Len() > 0 { return false, fmt.Errorf("added to queue") } return false, nil }) if err != wait.ErrWaitTimeout { t.Errorf("expected timeout, got: %v", err) } if q.Len() != 0 { t.Errorf("should not have added") } }
func pullerTestEnv(c pullerTestCase, serialized bool) (puller ImageManager, fakeClock *clock.FakeClock, fakeRuntime *ctest.FakeRuntime, container *v1.Container) { container = &v1.Container{ Name: "container_name", Image: c.containerImage, ImagePullPolicy: c.policy, } backOff := flowcontrol.NewBackOff(time.Second, time.Minute) fakeClock = clock.NewFakeClock(time.Now()) backOff.Clock = fakeClock fakeRuntime = &ctest.FakeRuntime{} fakeRecorder := &record.FakeRecorder{} fakeRuntime.ImageList = []Image{{ID: "present_image"}} fakeRuntime.Err = c.pullerErr fakeRuntime.InspectErr = c.inspectErr puller = NewImageManager(fakeRecorder, fakeRuntime, backOff, serialized, 0, 0) return }
func TestAddAndGet(t *testing.T) { testObj := testObject{ key: "foo", val: "bar", } objectCache := NewFakeObjectCache(func() (interface{}, error) { return nil, fmt.Errorf("Unexpected Error: updater should never be called in this test!") }, 1*time.Hour, clock.NewFakeClock(time.Now())) err := objectCache.Add(testObj.key, testObj.val) if err != nil { t.Errorf("Unable to add obj %#v by key: %s", testObj, testObj.key) } value, err := objectCache.Get(testObj.key) if err != nil { t.Errorf("Unable to get obj %#v by key: %s", testObj, testObj.key) } if value.(string) != testObj.val { t.Errorf("Expected to get cached value: %#v, but got: %s", testObj.val, value.(string)) } }
func TestSecretStoreGetNeverRefresh(t *testing.T) { fakeClient := &fake.Clientset{} fakeClock := clock.NewFakeClock(time.Now()) store := newSecretStore(fakeClient, fakeClock, time.Minute) for i := 0; i < 10; i++ { store.Add(fmt.Sprintf("ns-%d", i), fmt.Sprintf("name-%d", i)) } fakeClient.ClearActions() wg := sync.WaitGroup{} wg.Add(100) for i := 0; i < 100; i++ { go func(i int) { store.Get(fmt.Sprintf("ns-%d", i%10), fmt.Sprintf("name-%d", i%10)) wg.Done() }(i) } wg.Wait() actions := fakeClient.Actions() // Only first Get, should forward the Get request. assert.Equal(t, 10, len(actions), "unexpected actions: %#v", actions) }
func TestCopyShifting(t *testing.T) { fakeClock := clock.NewFakeClock(time.Now()) q := newDelayingQueue(fakeClock, "") first := "foo" second := "bar" third := "baz" q.AddAfter(first, 1*time.Second) q.AddAfter(second, 500*time.Millisecond) q.AddAfter(third, 250*time.Millisecond) if err := waitForWaitingQueueToFill(q); err != nil { t.Fatalf("unexpected err: %v", err) } if q.Len() != 0 { t.Errorf("should not have added") } fakeClock.Step(2 * time.Second) if err := waitForAdded(q, 3); err != nil { t.Fatalf("unexpected err: %v", err) } actualFirst, _ := q.Get() if !reflect.DeepEqual(actualFirst, third) { t.Errorf("expected %v, got %v", third, actualFirst) } actualSecond, _ := q.Get() if !reflect.DeepEqual(actualSecond, second) { t.Errorf("expected %v, got %v", second, actualSecond) } actualThird, _ := q.Get() if !reflect.DeepEqual(actualThird, first) { t.Errorf("expected %v, got %v", first, actualThird) } }
// TestMinReclaim verifies that min-reclaim works as desired. func TestMinReclaim(t *testing.T) { podMaker := makePodWithMemoryStats summaryStatsMaker := makeMemoryStats podsToMake := []podToMake{ {name: "guaranteed-low", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), memoryWorkingSet: "200Mi"}, {name: "guaranteed-high", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), memoryWorkingSet: "800Mi"}, {name: "burstable-low", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), memoryWorkingSet: "300Mi"}, {name: "burstable-high", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), memoryWorkingSet: "800Mi"}, {name: "best-effort-low", requests: newResourceList("", ""), limits: newResourceList("", ""), memoryWorkingSet: "300Mi"}, {name: "best-effort-high", requests: newResourceList("", ""), limits: newResourceList("", ""), memoryWorkingSet: "500Mi"}, } pods := []*v1.Pod{} podStats := map[*v1.Pod]statsapi.PodStats{} for _, podToMake := range podsToMake { pod, podStat := podMaker(podToMake.name, podToMake.requests, podToMake.limits, podToMake.memoryWorkingSet) pods = append(pods, pod) podStats[pod] = podStat } podToEvict := pods[5] activePodsFunc := func() []*v1.Pod { return pods } fakeClock := clock.NewFakeClock(time.Now()) podKiller := &mockPodKiller{} diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false} imageGC := &mockImageGC{freed: int64(0), err: nil} nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} config := Config{ MaxPodGracePeriodSeconds: 5, PressureTransitionPeriod: time.Minute * 5, Thresholds: []Threshold{ { Signal: SignalMemoryAvailable, Operator: OpLessThan, Value: ThresholdValue{ Quantity: quantityMustParse("1Gi"), }, MinReclaim: &ThresholdValue{ Quantity: quantityMustParse("500Mi"), }, }, }, } summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("2Gi", podStats)} manager := &managerImpl{ clock: fakeClock, killPodFunc: podKiller.killPodNow, imageGC: imageGC, config: config, recorder: &record.FakeRecorder{}, summaryProvider: summaryProvider, nodeRef: nodeRef, nodeConditionsLastObservedAt: nodeConditionsObservedAt{}, thresholdsFirstObservedAt: thresholdsObservedAt{}, } // synchronize manager.synchronize(diskInfoProvider, activePodsFunc) // we should not have memory pressure if manager.IsUnderMemoryPressure() { t.Errorf("Manager should not report memory pressure") } // induce memory pressure! fakeClock.Step(1 * time.Minute) summaryProvider.result = summaryStatsMaker("500Mi", podStats) manager.synchronize(diskInfoProvider, activePodsFunc) // we should have memory pressure if !manager.IsUnderMemoryPressure() { t.Errorf("Manager should report memory pressure") } // check the right pod was killed if podKiller.pod != podToEvict { t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name) } observedGracePeriod := *podKiller.gracePeriodOverride if observedGracePeriod != int64(0) { t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod) } // reduce memory pressure, but not below the min-reclaim amount fakeClock.Step(1 * time.Minute) summaryProvider.result = summaryStatsMaker("1.2Gi", podStats) podKiller.pod = nil // reset state manager.synchronize(diskInfoProvider, activePodsFunc) // we should have memory pressure (because transition period not yet met) if !manager.IsUnderMemoryPressure() { t.Errorf("Manager should report memory pressure") } // check the right pod was killed if podKiller.pod != podToEvict { t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name) } observedGracePeriod = *podKiller.gracePeriodOverride if observedGracePeriod != int64(0) { t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod) } // reduce memory pressure and ensure the min-reclaim amount fakeClock.Step(1 * time.Minute) summaryProvider.result = summaryStatsMaker("2Gi", podStats) podKiller.pod = nil // reset state manager.synchronize(diskInfoProvider, activePodsFunc) // we should have memory pressure (because transition period not yet met) if !manager.IsUnderMemoryPressure() { t.Errorf("Manager should report memory pressure") } // no pod should have been killed if podKiller.pod != nil { t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name) } // move the clock past transition period to ensure that we stop reporting pressure fakeClock.Step(5 * time.Minute) summaryProvider.result = summaryStatsMaker("2Gi", podStats) podKiller.pod = nil // reset state manager.synchronize(diskInfoProvider, activePodsFunc) // we should not have memory pressure (because transition period met) if manager.IsUnderMemoryPressure() { t.Errorf("Manager should not report memory pressure") } // no pod should have been killed if podKiller.pod != nil { t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name) } }
// TestAdmissionNamespaceForceLiveLookup verifies live lookups are done after deleting a namespace func TestAdmissionNamespaceForceLiveLookup(t *testing.T) { namespace := "test" getCalls := int64(0) phases := map[string]api.NamespacePhase{namespace: api.NamespaceActive} mockClient := newMockClientForTest(phases) mockClient.AddReactor("get", "namespaces", func(action core.Action) (bool, runtime.Object, error) { getCalls++ return true, &api.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}, Status: api.NamespaceStatus{Phase: phases[namespace]}}, nil }) fakeClock := clock.NewFakeClock(time.Now()) handler, informerFactory, err := newHandlerForTestWithClock(mockClient, fakeClock) if err != nil { t.Errorf("unexpected error initializing handler: %v", err) } informerFactory.Start(wait.NeverStop) pod := newPod(namespace) // verify create operations in the namespace is allowed err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err != nil { t.Errorf("Unexpected error rejecting creates in an active namespace") } if getCalls != 0 { t.Errorf("Expected no live lookups of the namespace, got %d", getCalls) } getCalls = 0 // verify delete of namespace can proceed err = handler.Admit(admission.NewAttributesRecord(nil, nil, api.Kind("Namespace").WithVersion("version"), "", namespace, api.Resource("namespaces").WithVersion("version"), "", admission.Delete, nil)) if err != nil { t.Errorf("Expected namespace deletion to be allowed") } if getCalls != 0 { t.Errorf("Expected no live lookups of the namespace, got %d", getCalls) } getCalls = 0 // simulate the phase changing phases[namespace] = api.NamespaceTerminating // verify create operations in the namespace cause an error err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err == nil { t.Errorf("Expected error rejecting creates in a namespace right after deleting it") } if getCalls != 1 { t.Errorf("Expected a live lookup of the namespace at t=0, got %d", getCalls) } getCalls = 0 // Ensure the live lookup is still forced up to forceLiveLookupTTL fakeClock.Step(forceLiveLookupTTL) // verify create operations in the namespace cause an error err = handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if err == nil { t.Errorf("Expected error rejecting creates in a namespace right after deleting it") } if getCalls != 1 { t.Errorf("Expected a live lookup of the namespace at t=forceLiveLookupTTL, got %d", getCalls) } getCalls = 0 // Ensure the live lookup expires fakeClock.Step(time.Millisecond) // verify create operations in the namespace don't force a live lookup after the timeout handler.Admit(admission.NewAttributesRecord(&pod, nil, api.Kind("Pod").WithVersion("version"), pod.Namespace, pod.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)) if getCalls != 0 { t.Errorf("Expected no live lookup of the namespace at t=forceLiveLookupTTL+1ms, got %d", getCalls) } getCalls = 0 }
func TestNodeReclaimFuncs(t *testing.T) { podMaker := makePodWithDiskStats summaryStatsMaker := makeDiskStats podsToMake := []podToMake{ {name: "guaranteed-low", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), rootFsUsed: "200Mi"}, {name: "guaranteed-high", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), rootFsUsed: "800Mi"}, {name: "burstable-low", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), rootFsUsed: "300Mi"}, {name: "burstable-high", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), rootFsUsed: "800Mi"}, {name: "best-effort-low", requests: newResourceList("", ""), limits: newResourceList("", ""), rootFsUsed: "300Mi"}, {name: "best-effort-high", requests: newResourceList("", ""), limits: newResourceList("", ""), rootFsUsed: "500Mi"}, } pods := []*v1.Pod{} podStats := map[*v1.Pod]statsapi.PodStats{} for _, podToMake := range podsToMake { pod, podStat := podMaker(podToMake.name, podToMake.requests, podToMake.limits, podToMake.rootFsUsed, podToMake.logsFsUsed, podToMake.perLocalVolumeUsed) pods = append(pods, pod) podStats[pod] = podStat } podToEvict := pods[5] activePodsFunc := func() []*v1.Pod { return pods } fakeClock := clock.NewFakeClock(time.Now()) podKiller := &mockPodKiller{} diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false} imageGcFree := resource.MustParse("700Mi") imageGC := &mockImageGC{freed: imageGcFree.Value(), err: nil} nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} config := Config{ MaxPodGracePeriodSeconds: 5, PressureTransitionPeriod: time.Minute * 5, Thresholds: []Threshold{ { Signal: SignalNodeFsAvailable, Operator: OpLessThan, Value: ThresholdValue{ Quantity: quantityMustParse("1Gi"), }, MinReclaim: &ThresholdValue{ Quantity: quantityMustParse("500Mi"), }, }, }, } summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("16Gi", "200Gi", podStats)} manager := &managerImpl{ clock: fakeClock, killPodFunc: podKiller.killPodNow, imageGC: imageGC, config: config, recorder: &record.FakeRecorder{}, summaryProvider: summaryProvider, nodeRef: nodeRef, nodeConditionsLastObservedAt: nodeConditionsObservedAt{}, thresholdsFirstObservedAt: thresholdsObservedAt{}, } // synchronize manager.synchronize(diskInfoProvider, activePodsFunc) // we should not have disk pressure if manager.IsUnderDiskPressure() { t.Errorf("Manager should not report disk pressure") } // induce hard threshold fakeClock.Step(1 * time.Minute) summaryProvider.result = summaryStatsMaker(".9Gi", "200Gi", podStats) manager.synchronize(diskInfoProvider, activePodsFunc) // we should have disk pressure if !manager.IsUnderDiskPressure() { t.Errorf("Manager should report disk pressure since soft threshold was met") } // verify image gc was invoked if !imageGC.invoked { t.Errorf("Manager should have invoked image gc") } // verify no pod was killed because image gc was sufficient if podKiller.pod != nil { t.Errorf("Manager should not have killed a pod, but killed: %v", podKiller.pod.Name) } // reset state imageGC.invoked = false // remove disk pressure fakeClock.Step(20 * time.Minute) summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats) manager.synchronize(diskInfoProvider, activePodsFunc) // we should not have disk pressure if manager.IsUnderDiskPressure() { t.Errorf("Manager should not report disk pressure") } // induce disk pressure! fakeClock.Step(1 * time.Minute) summaryProvider.result = summaryStatsMaker("400Mi", "200Gi", podStats) manager.synchronize(diskInfoProvider, activePodsFunc) // we should have disk pressure if !manager.IsUnderDiskPressure() { t.Errorf("Manager should report disk pressure") } // ensure image gc was invoked if !imageGC.invoked { t.Errorf("Manager should have invoked image gc") } // check the right pod was killed if podKiller.pod != podToEvict { t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name) } observedGracePeriod := *podKiller.gracePeriodOverride if observedGracePeriod != int64(0) { t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod) } // reduce disk pressure fakeClock.Step(1 * time.Minute) summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats) imageGC.invoked = false // reset state podKiller.pod = nil // reset state manager.synchronize(diskInfoProvider, activePodsFunc) // we should have disk pressure (because transition period not yet met) if !manager.IsUnderDiskPressure() { t.Errorf("Manager should report disk pressure") } // no image gc should have occurred if imageGC.invoked { t.Errorf("Manager chose to perform image gc when it was not neeed") } // no pod should have been killed if podKiller.pod != nil { t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name) } // move the clock past transition period to ensure that we stop reporting pressure fakeClock.Step(5 * time.Minute) summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats) imageGC.invoked = false // reset state podKiller.pod = nil // reset state manager.synchronize(diskInfoProvider, activePodsFunc) // we should not have disk pressure (because transition period met) if manager.IsUnderDiskPressure() { t.Errorf("Manager should not report disk pressure") } // no image gc should have occurred if imageGC.invoked { t.Errorf("Manager chose to perform image gc when it was not neeed") } // no pod should have been killed if podKiller.pod != nil { t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name) } }
func newTestDockerService() (*dockerService, *dockertools.FakeDockerClient, *clock.FakeClock) { fakeClock := clock.NewFakeClock(time.Time{}) c := dockertools.NewFakeDockerClient().WithClock(fakeClock) return &dockerService{client: c, os: &containertest.FakeOS{}, networkPlugin: &network.NoopNetworkPlugin{}}, c, fakeClock }
func TestEventfNoNamespace(t *testing.T) { testPod := &v1.Pod{ ObjectMeta: v1.ObjectMeta{ SelfLink: "/api/version/pods/foo", Name: "foo", UID: "bar", }, } testRef, err := v1.GetPartialReference(testPod, "spec.containers[2]") if err != nil { t.Fatal(err) } table := []struct { obj k8sruntime.Object eventtype string reason string messageFmt string elements []interface{} expect *v1.Event expectLog string expectUpdate bool }{ { obj: testRef, eventtype: v1.EventTypeNormal, reason: "Started", messageFmt: "some verbose message: %v", elements: []interface{}{1}, expect: &v1.Event{ ObjectMeta: v1.ObjectMeta{ Name: "foo", Namespace: "default", }, InvolvedObject: v1.ObjectReference{ Kind: "Pod", Name: "foo", Namespace: "", UID: "bar", APIVersion: "version", FieldPath: "spec.containers[2]", }, Reason: "Started", Message: "some verbose message: 1", Source: v1.EventSource{Component: "eventTest"}, Count: 1, Type: v1.EventTypeNormal, }, expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"", Name:"foo", UID:"bar", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`, expectUpdate: false, }, } testCache := map[string]*v1.Event{} logCalled := make(chan struct{}) createEvent := make(chan *v1.Event) updateEvent := make(chan *v1.Event) patchEvent := make(chan *v1.Event) testEvents := testEventSink{ OnCreate: OnCreateFactory(testCache, createEvent), OnUpdate: func(event *v1.Event) (*v1.Event, error) { updateEvent <- event return event, nil }, OnPatch: OnPatchFactory(testCache, patchEvent), } eventBroadcaster := NewBroadcasterForTests(0) sinkWatcher := eventBroadcaster.StartRecordingToSink(&testEvents) clock := clock.NewFakeClock(time.Now()) recorder := recorderWithFakeClock(v1.EventSource{Component: "eventTest"}, eventBroadcaster, clock) for index, item := range table { clock.Step(1 * time.Second) logWatcher := eventBroadcaster.StartLogging(func(formatter string, args ...interface{}) { if e, a := item.expectLog, fmt.Sprintf(formatter, args...); e != a { t.Errorf("Expected '%v', got '%v'", e, a) } logCalled <- struct{}{} }) recorder.Eventf(item.obj, item.eventtype, item.reason, item.messageFmt, item.elements...) <-logCalled // validate event if item.expectUpdate { actualEvent := <-patchEvent validateEvent(strconv.Itoa(index), actualEvent, item.expect, t) } else { actualEvent := <-createEvent validateEvent(strconv.Itoa(index), actualEvent, item.expect, t) } logWatcher.Stop() } sinkWatcher.Stop() }
func TestInodePressureNodeFsInodes(t *testing.T) { podMaker := func(name string, requests v1.ResourceList, limits v1.ResourceList, rootInodes, logInodes, volumeInodes string) (*v1.Pod, statsapi.PodStats) { pod := newPod(name, []v1.Container{ newContainer(name, requests, limits), }, nil) podStats := newPodInodeStats(pod, parseQuantity(rootInodes), parseQuantity(logInodes), parseQuantity(volumeInodes)) return pod, podStats } summaryStatsMaker := func(rootFsInodesFree, rootFsInodes string, podStats map[*v1.Pod]statsapi.PodStats) *statsapi.Summary { rootFsInodesFreeVal := resource.MustParse(rootFsInodesFree) internalRootFsInodesFree := uint64(rootFsInodesFreeVal.Value()) rootFsInodesVal := resource.MustParse(rootFsInodes) internalRootFsInodes := uint64(rootFsInodesVal.Value()) result := &statsapi.Summary{ Node: statsapi.NodeStats{ Fs: &statsapi.FsStats{ InodesFree: &internalRootFsInodesFree, Inodes: &internalRootFsInodes, }, }, Pods: []statsapi.PodStats{}, } for _, podStat := range podStats { result.Pods = append(result.Pods, podStat) } return result } podsToMake := []podToMake{ {name: "guaranteed-low", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), rootFsInodesUsed: "200Mi"}, {name: "guaranteed-high", requests: newResourceList("100m", "1Gi"), limits: newResourceList("100m", "1Gi"), rootFsInodesUsed: "800Mi"}, {name: "burstable-low", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), rootFsInodesUsed: "300Mi"}, {name: "burstable-high", requests: newResourceList("100m", "100Mi"), limits: newResourceList("200m", "1Gi"), rootFsInodesUsed: "800Mi"}, {name: "best-effort-low", requests: newResourceList("", ""), limits: newResourceList("", ""), rootFsInodesUsed: "300Mi"}, {name: "best-effort-high", requests: newResourceList("", ""), limits: newResourceList("", ""), rootFsInodesUsed: "800Mi"}, } pods := []*v1.Pod{} podStats := map[*v1.Pod]statsapi.PodStats{} for _, podToMake := range podsToMake { pod, podStat := podMaker(podToMake.name, podToMake.requests, podToMake.limits, podToMake.rootFsInodesUsed, podToMake.logsFsInodesUsed, podToMake.perLocalVolumeInodesUsed) pods = append(pods, pod) podStats[pod] = podStat } podToEvict := pods[5] activePodsFunc := func() []*v1.Pod { return pods } fakeClock := clock.NewFakeClock(time.Now()) podKiller := &mockPodKiller{} diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false} imageGC := &mockImageGC{freed: int64(0), err: nil} nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} config := Config{ MaxPodGracePeriodSeconds: 5, PressureTransitionPeriod: time.Minute * 5, Thresholds: []Threshold{ { Signal: SignalNodeFsInodesFree, Operator: OpLessThan, Value: ThresholdValue{ Quantity: quantityMustParse("1Mi"), }, }, { Signal: SignalNodeFsInodesFree, Operator: OpLessThan, Value: ThresholdValue{ Quantity: quantityMustParse("2Mi"), }, GracePeriod: time.Minute * 2, }, }, } summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker("3Mi", "4Mi", podStats)} manager := &managerImpl{ clock: fakeClock, killPodFunc: podKiller.killPodNow, imageGC: imageGC, config: config, recorder: &record.FakeRecorder{}, summaryProvider: summaryProvider, nodeRef: nodeRef, nodeConditionsLastObservedAt: nodeConditionsObservedAt{}, thresholdsFirstObservedAt: thresholdsObservedAt{}, } // create a best effort pod to test admission podToAdmit, _ := podMaker("pod-to-admit", newResourceList("", ""), newResourceList("", ""), "0", "0", "0") // synchronize manager.synchronize(diskInfoProvider, activePodsFunc) // we should not have disk pressure if manager.IsUnderDiskPressure() { t.Errorf("Manager should not report inode pressure") } // try to admit our pod (should succeed) if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); !result.Admit { t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, true, result.Admit) } // induce soft threshold fakeClock.Step(1 * time.Minute) summaryProvider.result = summaryStatsMaker("1.5Mi", "4Mi", podStats) manager.synchronize(diskInfoProvider, activePodsFunc) // we should have disk pressure if !manager.IsUnderDiskPressure() { t.Errorf("Manager should report inode pressure since soft threshold was met") } // verify no pod was yet killed because there has not yet been enough time passed. if podKiller.pod != nil { t.Errorf("Manager should not have killed a pod yet, but killed: %v", podKiller.pod.Name) } // step forward in time pass the grace period fakeClock.Step(3 * time.Minute) summaryProvider.result = summaryStatsMaker("1.5Mi", "4Mi", podStats) manager.synchronize(diskInfoProvider, activePodsFunc) // we should have disk pressure if !manager.IsUnderDiskPressure() { t.Errorf("Manager should report inode pressure since soft threshold was met") } // verify the right pod was killed with the right grace period. if podKiller.pod != podToEvict { t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name) } if podKiller.gracePeriodOverride == nil { t.Errorf("Manager chose to kill pod but should have had a grace period override.") } observedGracePeriod := *podKiller.gracePeriodOverride if observedGracePeriod != manager.config.MaxPodGracePeriodSeconds { t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", manager.config.MaxPodGracePeriodSeconds, observedGracePeriod) } // reset state podKiller.pod = nil podKiller.gracePeriodOverride = nil // remove inode pressure fakeClock.Step(20 * time.Minute) summaryProvider.result = summaryStatsMaker("3Mi", "4Mi", podStats) manager.synchronize(diskInfoProvider, activePodsFunc) // we should not have disk pressure if manager.IsUnderDiskPressure() { t.Errorf("Manager should not report inode pressure") } // induce inode pressure! fakeClock.Step(1 * time.Minute) summaryProvider.result = summaryStatsMaker("0.5Mi", "4Mi", podStats) manager.synchronize(diskInfoProvider, activePodsFunc) // we should have disk pressure if !manager.IsUnderDiskPressure() { t.Errorf("Manager should report inode pressure") } // check the right pod was killed if podKiller.pod != podToEvict { t.Errorf("Manager chose to kill pod: %v, but should have chosen %v", podKiller.pod.Name, podToEvict.Name) } observedGracePeriod = *podKiller.gracePeriodOverride if observedGracePeriod != int64(0) { t.Errorf("Manager chose to kill pod with incorrect grace period. Expected: %d, actual: %d", 0, observedGracePeriod) } // try to admit our pod (should fail) if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); result.Admit { t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, false, result.Admit) } // reduce inode pressure fakeClock.Step(1 * time.Minute) summaryProvider.result = summaryStatsMaker("3Mi", "4Mi", podStats) podKiller.pod = nil // reset state manager.synchronize(diskInfoProvider, activePodsFunc) // we should have disk pressure (because transition period not yet met) if !manager.IsUnderDiskPressure() { t.Errorf("Manager should report inode pressure") } // no pod should have been killed if podKiller.pod != nil { t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name) } // try to admit our pod (should fail) if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); result.Admit { t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, false, result.Admit) } // move the clock past transition period to ensure that we stop reporting pressure fakeClock.Step(5 * time.Minute) summaryProvider.result = summaryStatsMaker("3Mi", "4Mi", podStats) podKiller.pod = nil // reset state manager.synchronize(diskInfoProvider, activePodsFunc) // we should not have disk pressure (because transition period met) if manager.IsUnderDiskPressure() { t.Errorf("Manager should not report inode pressure") } // no pod should have been killed if podKiller.pod != nil { t.Errorf("Manager chose to kill pod: %v when no pod should have been killed", podKiller.pod.Name) } // try to admit our pod (should succeed) if result := manager.Admit(&lifecycle.PodAdmitAttributes{Pod: podToAdmit}); !result.Admit { t.Errorf("Admit pod: %v, expected: %v, actual: %v", podToAdmit, true, result.Admit) } }
func TestMultiSinkCache(t *testing.T) { testPod := &v1.Pod{ ObjectMeta: v1.ObjectMeta{ SelfLink: "/api/version/pods/foo", Name: "foo", Namespace: "baz", UID: "bar", }, } testPod2 := &v1.Pod{ ObjectMeta: v1.ObjectMeta{ SelfLink: "/api/version/pods/foo", Name: "foo", Namespace: "baz", UID: "differentUid", }, } testRef, err := v1.GetPartialReference(testPod, "spec.containers[2]") testRef2, err := v1.GetPartialReference(testPod2, "spec.containers[3]") if err != nil { t.Fatal(err) } table := []struct { obj k8sruntime.Object eventtype string reason string messageFmt string elements []interface{} expect *v1.Event expectLog string expectUpdate bool }{ { obj: testRef, eventtype: v1.EventTypeNormal, reason: "Started", messageFmt: "some verbose message: %v", elements: []interface{}{1}, expect: &v1.Event{ ObjectMeta: v1.ObjectMeta{ Name: "foo", Namespace: "baz", }, InvolvedObject: v1.ObjectReference{ Kind: "Pod", Name: "foo", Namespace: "baz", UID: "bar", APIVersion: "version", FieldPath: "spec.containers[2]", }, Reason: "Started", Message: "some verbose message: 1", Source: v1.EventSource{Component: "eventTest"}, Count: 1, Type: v1.EventTypeNormal, }, expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`, expectUpdate: false, }, { obj: testPod, eventtype: v1.EventTypeNormal, reason: "Killed", messageFmt: "some other verbose message: %v", elements: []interface{}{1}, expect: &v1.Event{ ObjectMeta: v1.ObjectMeta{ Name: "foo", Namespace: "baz", }, InvolvedObject: v1.ObjectReference{ Kind: "Pod", Name: "foo", Namespace: "baz", UID: "bar", APIVersion: "version", }, Reason: "Killed", Message: "some other verbose message: 1", Source: v1.EventSource{Component: "eventTest"}, Count: 1, Type: v1.EventTypeNormal, }, expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"version", ResourceVersion:"", FieldPath:""}): type: 'Normal' reason: 'Killed' some other verbose message: 1`, expectUpdate: false, }, { obj: testRef, eventtype: v1.EventTypeNormal, reason: "Started", messageFmt: "some verbose message: %v", elements: []interface{}{1}, expect: &v1.Event{ ObjectMeta: v1.ObjectMeta{ Name: "foo", Namespace: "baz", }, InvolvedObject: v1.ObjectReference{ Kind: "Pod", Name: "foo", Namespace: "baz", UID: "bar", APIVersion: "version", FieldPath: "spec.containers[2]", }, Reason: "Started", Message: "some verbose message: 1", Source: v1.EventSource{Component: "eventTest"}, Count: 2, Type: v1.EventTypeNormal, }, expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`, expectUpdate: true, }, { obj: testRef2, eventtype: v1.EventTypeNormal, reason: "Started", messageFmt: "some verbose message: %v", elements: []interface{}{1}, expect: &v1.Event{ ObjectMeta: v1.ObjectMeta{ Name: "foo", Namespace: "baz", }, InvolvedObject: v1.ObjectReference{ Kind: "Pod", Name: "foo", Namespace: "baz", UID: "differentUid", APIVersion: "version", FieldPath: "spec.containers[3]", }, Reason: "Started", Message: "some verbose message: 1", Source: v1.EventSource{Component: "eventTest"}, Count: 1, Type: v1.EventTypeNormal, }, expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Started' some verbose message: 1`, expectUpdate: false, }, { obj: testRef, eventtype: v1.EventTypeNormal, reason: "Started", messageFmt: "some verbose message: %v", elements: []interface{}{1}, expect: &v1.Event{ ObjectMeta: v1.ObjectMeta{ Name: "foo", Namespace: "baz", }, InvolvedObject: v1.ObjectReference{ Kind: "Pod", Name: "foo", Namespace: "baz", UID: "bar", APIVersion: "version", FieldPath: "spec.containers[2]", }, Reason: "Started", Message: "some verbose message: 1", Source: v1.EventSource{Component: "eventTest"}, Count: 3, Type: v1.EventTypeNormal, }, expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"bar", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[2]"}): type: 'Normal' reason: 'Started' some verbose message: 1`, expectUpdate: true, }, { obj: testRef2, eventtype: v1.EventTypeNormal, reason: "Stopped", messageFmt: "some verbose message: %v", elements: []interface{}{1}, expect: &v1.Event{ ObjectMeta: v1.ObjectMeta{ Name: "foo", Namespace: "baz", }, InvolvedObject: v1.ObjectReference{ Kind: "Pod", Name: "foo", Namespace: "baz", UID: "differentUid", APIVersion: "version", FieldPath: "spec.containers[3]", }, Reason: "Stopped", Message: "some verbose message: 1", Source: v1.EventSource{Component: "eventTest"}, Count: 1, Type: v1.EventTypeNormal, }, expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1`, expectUpdate: false, }, { obj: testRef2, eventtype: v1.EventTypeNormal, reason: "Stopped", messageFmt: "some verbose message: %v", elements: []interface{}{1}, expect: &v1.Event{ ObjectMeta: v1.ObjectMeta{ Name: "foo", Namespace: "baz", }, InvolvedObject: v1.ObjectReference{ Kind: "Pod", Name: "foo", Namespace: "baz", UID: "differentUid", APIVersion: "version", FieldPath: "spec.containers[3]", }, Reason: "Stopped", Message: "some verbose message: 1", Source: v1.EventSource{Component: "eventTest"}, Count: 2, Type: v1.EventTypeNormal, }, expectLog: `Event(v1.ObjectReference{Kind:"Pod", Namespace:"baz", Name:"foo", UID:"differentUid", APIVersion:"version", ResourceVersion:"", FieldPath:"spec.containers[3]"}): type: 'Normal' reason: 'Stopped' some verbose message: 1`, expectUpdate: true, }, } testCache := map[string]*v1.Event{} createEvent := make(chan *v1.Event) updateEvent := make(chan *v1.Event) patchEvent := make(chan *v1.Event) testEvents := testEventSink{ OnCreate: OnCreateFactory(testCache, createEvent), OnUpdate: func(event *v1.Event) (*v1.Event, error) { updateEvent <- event return event, nil }, OnPatch: OnPatchFactory(testCache, patchEvent), } testCache2 := map[string]*v1.Event{} createEvent2 := make(chan *v1.Event) updateEvent2 := make(chan *v1.Event) patchEvent2 := make(chan *v1.Event) testEvents2 := testEventSink{ OnCreate: OnCreateFactory(testCache2, createEvent2), OnUpdate: func(event *v1.Event) (*v1.Event, error) { updateEvent2 <- event return event, nil }, OnPatch: OnPatchFactory(testCache2, patchEvent2), } eventBroadcaster := NewBroadcasterForTests(0) clock := clock.NewFakeClock(time.Now()) recorder := recorderWithFakeClock(v1.EventSource{Component: "eventTest"}, eventBroadcaster, clock) sinkWatcher := eventBroadcaster.StartRecordingToSink(&testEvents) for index, item := range table { clock.Step(1 * time.Second) recorder.Eventf(item.obj, item.eventtype, item.reason, item.messageFmt, item.elements...) // validate event if item.expectUpdate { actualEvent := <-patchEvent validateEvent(strconv.Itoa(index), actualEvent, item.expect, t) } else { actualEvent := <-createEvent validateEvent(strconv.Itoa(index), actualEvent, item.expect, t) } } // Another StartRecordingToSink call should start to record events with new clean cache. sinkWatcher2 := eventBroadcaster.StartRecordingToSink(&testEvents2) for index, item := range table { clock.Step(1 * time.Second) recorder.Eventf(item.obj, item.eventtype, item.reason, item.messageFmt, item.elements...) // validate event if item.expectUpdate { actualEvent := <-patchEvent2 validateEvent(strconv.Itoa(index), actualEvent, item.expect, t) } else { actualEvent := <-createEvent2 validateEvent(strconv.Itoa(index), actualEvent, item.expect, t) } } sinkWatcher.Stop() sinkWatcher2.Stop() }
func newTestCache() (*requestCache, *clock.FakeClock) { c := newRequestCache() fakeClock := clock.NewFakeClock(time.Now()) c.clock = fakeClock return c, fakeClock }