github.com/kubeflow/training-operator@v1.7.0/pkg/controller.v1/expectation/expectation_test.go (about) 1 /* 2 Copyright 2023 The Kubeflow Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package expectation 18 19 import ( 20 "sync" 21 "testing" 22 "time" 23 24 "github.com/stretchr/testify/assert" 25 v1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/util/uuid" 28 "k8s.io/client-go/tools/cache" 29 clock "k8s.io/utils/clock/testing" 30 ) 31 32 var ( 33 // KeyFunc is the short name to DeletionHandlingMetaNamespaceKeyFunc. 34 // IndexerInformer uses a delta queue, therefore for deletes we have to use this 35 // key function but it should be just fine for non delete events. 36 KeyFunc = cache.DeletionHandlingMetaNamespaceKeyFunc 37 ) 38 39 // NewFakeControllerExpectationsLookup creates a fake store for PodExpectations. 40 func NewFakeControllerExpectationsLookup(ttl time.Duration) (*ControllerExpectations, *clock.FakeClock) { 41 fakeTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) 42 fakeClock := clock.NewFakeClock(fakeTime) 43 ttlPolicy := &cache.TTLPolicy{TTL: ttl, Clock: fakeClock} 44 ttlStore := cache.NewFakeExpirationStore( 45 ExpKeyFunc, nil, ttlPolicy, fakeClock) 46 return &ControllerExpectations{ttlStore}, fakeClock 47 } 48 49 func newReplicationController(replicas int) *v1.ReplicationController { 50 rc := &v1.ReplicationController{ 51 TypeMeta: metav1.TypeMeta{APIVersion: "v1"}, 52 ObjectMeta: metav1.ObjectMeta{ 53 UID: uuid.NewUUID(), 54 Name: "foobar", 55 Namespace: metav1.NamespaceDefault, 56 ResourceVersion: "18", 57 }, 58 Spec: v1.ReplicationControllerSpec{ 59 Replicas: func() *int32 { i := int32(replicas); return &i }(), 60 Selector: map[string]string{"foo": "bar"}, 61 Template: &v1.PodTemplateSpec{ 62 ObjectMeta: metav1.ObjectMeta{ 63 Labels: map[string]string{ 64 "name": "foo", 65 "type": "production", 66 }, 67 }, 68 Spec: v1.PodSpec{ 69 Containers: []v1.Container{ 70 { 71 Image: "foo/bar", 72 TerminationMessagePath: v1.TerminationMessagePathDefault, 73 ImagePullPolicy: v1.PullIfNotPresent, 74 }, 75 }, 76 RestartPolicy: v1.RestartPolicyAlways, 77 DNSPolicy: v1.DNSDefault, 78 NodeSelector: map[string]string{ 79 "baz": "blah", 80 }, 81 }, 82 }, 83 }, 84 } 85 return rc 86 } 87 88 func TestControllerExpectations(t *testing.T) { 89 ttl := 30 * time.Second 90 e, fakeClock := NewFakeControllerExpectationsLookup(ttl) 91 // In practice we can't really have add and delete expectations since we only either create or 92 // delete replicas in one rc pass, and the rc goes to sleep soon after until the expectations are 93 // either fulfilled or timeout. 94 adds, dels := 10, 30 95 rc := newReplicationController(1) 96 97 // RC fires off adds and deletes at apiserver, then sets expectations 98 rcKey, err := KeyFunc(rc) 99 assert.NoError(t, err, "Couldn't get key for object %#v: %v", rc, err) 100 101 err = e.SetExpectations(rcKey, adds, dels) 102 assert.NoError(t, err, "Could not register expectations for rc, err: %v", err) 103 var wg sync.WaitGroup 104 for i := 0; i < adds+1; i++ { 105 wg.Add(1) 106 go func() { 107 // In prod this can happen either because of a failed create by the rc 108 // or after having observed a create via informer 109 e.CreationObserved(rcKey) 110 wg.Done() 111 }() 112 } 113 wg.Wait() 114 115 // There are still delete expectations 116 assert.False(t, e.SatisfiedExpectations(rcKey), "Rc will sync before expectations are met") 117 118 for i := 0; i < dels+1; i++ { 119 wg.Add(1) 120 go func() { 121 e.DeletionObserved(rcKey) 122 wg.Done() 123 }() 124 } 125 wg.Wait() 126 127 // Expectations have been surpassed 128 podExp, exists, err := e.GetExpectations(rcKey) 129 assert.NoError(t, err, "Could not get expectations for rc, exists %v and err %v", exists, err) 130 assert.True(t, exists, "Could not get expectations for rc, exists %v and err %v", exists, err) 131 132 add, del := podExp.GetExpectations() 133 assert.Equal(t, int64(-1), add, "Unexpected pod expectations %#v", podExp) 134 assert.Equal(t, int64(-1), del, "Unexpected pod expectations %#v", podExp) 135 assert.True(t, e.SatisfiedExpectations(rcKey), "Expectations are met but the rc will not sync") 136 137 // Next round of rc sync, old expectations are cleared 138 err = e.SetExpectations(rcKey, 1, 2) 139 assert.NoError(t, err, "Could not register expectations for rc, err %v", err) 140 podExp, exists, err = e.GetExpectations(rcKey) 141 assert.NoError(t, err, "Could not get expectations for rc, exists %v and err %v", exists, err) 142 assert.True(t, exists, "Could not get expectations for rc, exists %v and err %v", exists, err) 143 add, del = podExp.GetExpectations() 144 145 assert.Equal(t, int64(1), add, "Unexpected pod expectations %#v", podExp) 146 assert.Equal(t, int64(2), del, "Unexpected pod expectations %#v", podExp) 147 148 // Expectations have expired because of ttl 149 fakeClock.Step(ttl + 1) 150 assert.True(t, e.SatisfiedExpectations(rcKey), 151 "Expectations should have expired but didn't") 152 }