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  }