...

Source file src/k8s.io/kubernetes/pkg/controller/job/tracking_utils_test.go

Documentation: k8s.io/kubernetes/pkg/controller/job

     1  /*
     2  Copyright 2020 The Kubernetes 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 job
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	batch "k8s.io/api/batch/v1"
    26  	v1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/util/sets"
    29  	"k8s.io/component-base/metrics/testutil"
    30  	"k8s.io/klog/v2/ktesting"
    31  	"k8s.io/kubernetes/pkg/controller/job/metrics"
    32  )
    33  
    34  func TestUIDTrackingExpectations(t *testing.T) {
    35  	logger, _ := ktesting.NewTestContext(t)
    36  	tracks := []struct {
    37  		job         string
    38  		firstRound  []string
    39  		secondRound []string
    40  	}{
    41  		{
    42  			job:         "foo",
    43  			firstRound:  []string{"a", "b", "c", "d"},
    44  			secondRound: []string{"e", "f"},
    45  		},
    46  		{
    47  			job:         "bar",
    48  			firstRound:  []string{"x", "y", "z"},
    49  			secondRound: []string{"u", "v", "w"},
    50  		},
    51  		{
    52  			job:         "baz",
    53  			firstRound:  []string{"w"},
    54  			secondRound: []string{"a"},
    55  		},
    56  	}
    57  	expectations := newUIDTrackingExpectations()
    58  
    59  	// Insert first round of keys in parallel.
    60  
    61  	var wg sync.WaitGroup
    62  	wg.Add(len(tracks))
    63  	errs := make([]error, len(tracks))
    64  	for i := range tracks {
    65  		track := tracks[i]
    66  		go func(errID int) {
    67  			errs[errID] = expectations.expectFinalizersRemoved(logger, track.job, track.firstRound)
    68  			wg.Done()
    69  		}(i)
    70  	}
    71  	wg.Wait()
    72  	for i, err := range errs {
    73  		if err != nil {
    74  			t.Errorf("Failed adding first round of UIDs for job %s: %v", tracks[i].job, err)
    75  		}
    76  	}
    77  
    78  	for _, track := range tracks {
    79  		uids := expectations.getSet(track.job)
    80  		if uids == nil {
    81  			t.Errorf("Set of UIDs is empty for job %s", track.job)
    82  		} else if diff := cmp.Diff(track.firstRound, sets.List(uids.set)); diff != "" {
    83  			t.Errorf("Unexpected keys for job %s (-want,+got):\n%s", track.job, diff)
    84  		}
    85  	}
    86  
    87  	// Delete the first round of keys and add the second round in parallel.
    88  
    89  	for i, track := range tracks {
    90  		wg.Add(len(track.firstRound) + 1)
    91  		track := track
    92  		for _, uid := range track.firstRound {
    93  			uid := uid
    94  			go func() {
    95  				expectations.finalizerRemovalObserved(logger, track.job, uid)
    96  				wg.Done()
    97  			}()
    98  		}
    99  		go func(errID int) {
   100  			errs[errID] = expectations.expectFinalizersRemoved(logger, track.job, track.secondRound)
   101  			wg.Done()
   102  		}(i)
   103  	}
   104  	wg.Wait()
   105  
   106  	for i, err := range errs {
   107  		if err != nil {
   108  			t.Errorf("Failed adding second round of UIDs for job %s: %v", tracks[i].job, err)
   109  		}
   110  	}
   111  
   112  	for _, track := range tracks {
   113  		uids := expectations.getSet(track.job)
   114  		if uids == nil {
   115  			t.Errorf("Set of UIDs is empty for job %s", track.job)
   116  		} else if diff := cmp.Diff(track.secondRound, sets.List(uids.set)); diff != "" {
   117  			t.Errorf("Unexpected keys for job %s (-want,+got):\n%s", track.job, diff)
   118  		}
   119  	}
   120  	for _, track := range tracks {
   121  		expectations.deleteExpectations(logger, track.job)
   122  		uids := expectations.getSet(track.job)
   123  		if uids != nil {
   124  			t.Errorf("Wanted expectations for job %s to be cleared, but they were not", track.job)
   125  		}
   126  	}
   127  }
   128  
   129  func TestRecordFinishedPodWithTrackingFinalizer(t *testing.T) {
   130  	metrics.Register()
   131  	cases := map[string]struct {
   132  		oldPod     *v1.Pod
   133  		newPod     *v1.Pod
   134  		wantAdd    int
   135  		wantDelete int
   136  	}{
   137  		"new non-finished Pod with finalizer": {
   138  			newPod: &v1.Pod{
   139  				ObjectMeta: metav1.ObjectMeta{
   140  					Finalizers: []string{batch.JobTrackingFinalizer},
   141  				},
   142  				Status: v1.PodStatus{
   143  					Phase: v1.PodPending,
   144  				},
   145  			},
   146  		},
   147  		"pod with finalizer fails": {
   148  			oldPod: &v1.Pod{
   149  				ObjectMeta: metav1.ObjectMeta{
   150  					Finalizers: []string{batch.JobTrackingFinalizer},
   151  				},
   152  				Status: v1.PodStatus{
   153  					Phase: v1.PodRunning,
   154  				},
   155  			},
   156  			newPod: &v1.Pod{
   157  				ObjectMeta: metav1.ObjectMeta{
   158  					Finalizers: []string{batch.JobTrackingFinalizer},
   159  				},
   160  				Status: v1.PodStatus{
   161  					Phase: v1.PodFailed,
   162  				},
   163  			},
   164  			wantAdd: 1,
   165  		},
   166  		"pod with finalizer succeeds": {
   167  			oldPod: &v1.Pod{
   168  				ObjectMeta: metav1.ObjectMeta{
   169  					Finalizers: []string{batch.JobTrackingFinalizer},
   170  				},
   171  				Status: v1.PodStatus{
   172  					Phase: v1.PodRunning,
   173  				},
   174  			},
   175  			newPod: &v1.Pod{
   176  				ObjectMeta: metav1.ObjectMeta{
   177  					Finalizers: []string{batch.JobTrackingFinalizer},
   178  				},
   179  				Status: v1.PodStatus{
   180  					Phase: v1.PodSucceeded,
   181  				},
   182  			},
   183  			wantAdd: 1,
   184  		},
   185  		"succeeded pod loses finalizer": {
   186  			oldPod: &v1.Pod{
   187  				ObjectMeta: metav1.ObjectMeta{
   188  					Finalizers: []string{batch.JobTrackingFinalizer},
   189  				},
   190  				Status: v1.PodStatus{
   191  					Phase: v1.PodSucceeded,
   192  				},
   193  			},
   194  			newPod: &v1.Pod{
   195  				Status: v1.PodStatus{
   196  					Phase: v1.PodSucceeded,
   197  				},
   198  			},
   199  			wantDelete: 1,
   200  		},
   201  		"pod without finalizer removed": {
   202  			oldPod: &v1.Pod{
   203  				Status: v1.PodStatus{
   204  					Phase: v1.PodSucceeded,
   205  				},
   206  			},
   207  		},
   208  	}
   209  	for name, tc := range cases {
   210  		t.Run(name, func(t *testing.T) {
   211  			metrics.TerminatedPodsTrackingFinalizerTotal.Reset()
   212  			recordFinishedPodWithTrackingFinalizer(tc.oldPod, tc.newPod)
   213  			if err := validateTerminatedPodsTrackingFinalizerTotal(metrics.Add, tc.wantAdd); err != nil {
   214  				t.Errorf("Failed validating terminated_pods_tracking_finalizer_total(add): %v", err)
   215  			}
   216  			if err := validateTerminatedPodsTrackingFinalizerTotal(metrics.Delete, tc.wantDelete); err != nil {
   217  				t.Errorf("Failed validating terminated_pods_tracking_finalizer_total(delete): %v", err)
   218  			}
   219  		})
   220  	}
   221  }
   222  
   223  func validateTerminatedPodsTrackingFinalizerTotal(event string, want int) error {
   224  	got, err := testutil.GetCounterMetricValue(metrics.TerminatedPodsTrackingFinalizerTotal.WithLabelValues(event))
   225  	if err != nil {
   226  		return err
   227  	}
   228  	if int(got) != want {
   229  		return fmt.Errorf("got value %d, want %d", int(got), want)
   230  	}
   231  	return nil
   232  }
   233  

View as plain text