sigs.k8s.io/kueue@v0.6.2/pkg/workload/admissionchecks_test.go (about)

     1  /*
     2  Copyright 2023 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 workload
    18  
    19  import (
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	"github.com/google/go-cmp/cmp/cmpopts"
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/utils/ptr"
    28  
    29  	kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
    30  )
    31  
    32  func TestSyncAdmittedCondition(t *testing.T) {
    33  	cases := map[string]struct {
    34  		checkStates    []kueue.AdmissionCheckState
    35  		conditions     []metav1.Condition
    36  		wantConditions []metav1.Condition
    37  		wantChange     bool
    38  	}{
    39  		"empty": {},
    40  		"reservation no checks": {
    41  			conditions: []metav1.Condition{
    42  				{
    43  					Type:   kueue.WorkloadQuotaReserved,
    44  					Status: metav1.ConditionTrue,
    45  				},
    46  			},
    47  			wantConditions: []metav1.Condition{
    48  				{
    49  					Type:   kueue.WorkloadQuotaReserved,
    50  					Status: metav1.ConditionTrue,
    51  				},
    52  				{
    53  					Type:   kueue.WorkloadAdmitted,
    54  					Status: metav1.ConditionTrue,
    55  					Reason: "Admitted",
    56  				},
    57  			},
    58  			wantChange: true,
    59  		},
    60  		"reservation, checks not ready": {
    61  			checkStates: []kueue.AdmissionCheckState{
    62  				{
    63  					Name:  "check1",
    64  					State: kueue.CheckStatePending,
    65  				},
    66  				{
    67  					Name:  "check2",
    68  					State: kueue.CheckStateReady,
    69  				},
    70  			},
    71  			conditions: []metav1.Condition{
    72  				{
    73  					Type:   kueue.WorkloadQuotaReserved,
    74  					Status: metav1.ConditionTrue,
    75  				},
    76  			},
    77  			wantConditions: []metav1.Condition{
    78  				{
    79  					Type:   kueue.WorkloadQuotaReserved,
    80  					Status: metav1.ConditionTrue,
    81  				},
    82  			},
    83  		},
    84  		"reservation, checks ready": {
    85  			checkStates: []kueue.AdmissionCheckState{
    86  				{
    87  					Name:  "check1",
    88  					State: kueue.CheckStateReady,
    89  				},
    90  				{
    91  					Name:  "check2",
    92  					State: kueue.CheckStateReady,
    93  				},
    94  			},
    95  			conditions: []metav1.Condition{
    96  				{
    97  					Type:   kueue.WorkloadQuotaReserved,
    98  					Status: metav1.ConditionTrue,
    99  				},
   100  			},
   101  			wantConditions: []metav1.Condition{
   102  				{
   103  					Type:   kueue.WorkloadQuotaReserved,
   104  					Status: metav1.ConditionTrue,
   105  				},
   106  				{
   107  					Type:   kueue.WorkloadAdmitted,
   108  					Status: metav1.ConditionTrue,
   109  					Reason: "Admitted",
   110  				},
   111  			},
   112  			wantChange: true,
   113  		},
   114  		"reservation lost": {
   115  			checkStates: []kueue.AdmissionCheckState{
   116  				{
   117  					Name:  "check1",
   118  					State: kueue.CheckStateReady,
   119  				},
   120  				{
   121  					Name:  "check2",
   122  					State: kueue.CheckStateReady,
   123  				},
   124  			},
   125  			conditions: []metav1.Condition{
   126  				{
   127  					Type:   kueue.WorkloadAdmitted,
   128  					Status: metav1.ConditionTrue,
   129  				},
   130  			},
   131  			wantConditions: []metav1.Condition{
   132  				{
   133  					Type:   kueue.WorkloadAdmitted,
   134  					Status: metav1.ConditionFalse,
   135  					Reason: "NoReservation",
   136  				},
   137  			},
   138  			wantChange: true,
   139  		},
   140  		"check lost": {
   141  			checkStates: []kueue.AdmissionCheckState{
   142  				{
   143  					Name:  "check1",
   144  					State: kueue.CheckStateReady,
   145  				},
   146  				{
   147  					Name:  "check2",
   148  					State: kueue.CheckStatePending,
   149  				},
   150  			},
   151  			conditions: []metav1.Condition{
   152  				{
   153  					Type:   kueue.WorkloadQuotaReserved,
   154  					Status: metav1.ConditionTrue,
   155  				},
   156  				{
   157  					Type:   kueue.WorkloadAdmitted,
   158  					Status: metav1.ConditionTrue,
   159  				},
   160  			},
   161  			wantConditions: []metav1.Condition{
   162  				{
   163  					Type:   kueue.WorkloadQuotaReserved,
   164  					Status: metav1.ConditionTrue,
   165  				},
   166  				{
   167  					Type:   kueue.WorkloadAdmitted,
   168  					Status: metav1.ConditionFalse,
   169  					Reason: "NoChecks",
   170  				},
   171  			},
   172  			wantChange: true,
   173  		},
   174  		"reservation and check lost": {
   175  			checkStates: []kueue.AdmissionCheckState{
   176  				{
   177  					Name:  "check1",
   178  					State: kueue.CheckStateReady,
   179  				},
   180  				{
   181  					Name:  "check2",
   182  					State: kueue.CheckStatePending,
   183  				},
   184  			},
   185  			conditions: []metav1.Condition{
   186  				{
   187  					Type:   kueue.WorkloadAdmitted,
   188  					Status: metav1.ConditionTrue,
   189  				},
   190  			},
   191  			wantConditions: []metav1.Condition{
   192  				{
   193  					Type:   kueue.WorkloadAdmitted,
   194  					Status: metav1.ConditionFalse,
   195  					Reason: "NoReservationNoChecks",
   196  				},
   197  			},
   198  			wantChange: true,
   199  		},
   200  	}
   201  
   202  	for name, tc := range cases {
   203  		t.Run(name, func(t *testing.T) {
   204  			wl := &kueue.Workload{
   205  				Status: kueue.WorkloadStatus{
   206  					AdmissionChecks: tc.checkStates,
   207  					Conditions:      tc.conditions,
   208  				},
   209  			}
   210  
   211  			gotChange := SyncAdmittedCondition(wl)
   212  
   213  			if gotChange != tc.wantChange {
   214  				t.Errorf("Unexpected change status, expecting %v", tc.wantChange)
   215  			}
   216  
   217  			if diff := cmp.Diff(tc.wantConditions, wl.Status.Conditions, cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime", "Message")); diff != "" {
   218  				t.Errorf("Unexpected conditions after sync (- want/+ got):\n%s", diff)
   219  			}
   220  		})
   221  	}
   222  }
   223  
   224  func TestSetCheckState(t *testing.T) {
   225  	t0 := metav1.NewTime(time.Now().Add(-5 * time.Second))
   226  	t1 := metav1.NewTime(time.Now())
   227  	ps1Updates := kueue.PodSetUpdate{
   228  		Name: "ps1",
   229  		Labels: map[string]string{
   230  			"l1": "l1v",
   231  		},
   232  		Annotations: map[string]string{
   233  			"a1": "a1v",
   234  		},
   235  		NodeSelector: map[string]string{
   236  			"ns1": "ms1v",
   237  		},
   238  		Tolerations: []corev1.Toleration{
   239  			{
   240  				Key:               "t1",
   241  				Operator:          corev1.TolerationOpEqual,
   242  				Value:             "t1v",
   243  				Effect:            corev1.TaintEffectNoSchedule,
   244  				TolerationSeconds: ptr.To[int64](5),
   245  			},
   246  		},
   247  	}
   248  
   249  	cases := map[string]struct {
   250  		origStates []kueue.AdmissionCheckState
   251  		state      kueue.AdmissionCheckState
   252  		wantStates []kueue.AdmissionCheckState
   253  	}{
   254  		"add new check": {
   255  			origStates: []kueue.AdmissionCheckState{},
   256  			state: kueue.AdmissionCheckState{
   257  				Name:               "check1",
   258  				State:              kueue.CheckStatePending,
   259  				LastTransitionTime: *t0.DeepCopy(),
   260  				Message:            "msg1",
   261  				PodSetUpdates:      []kueue.PodSetUpdate{*ps1Updates.DeepCopy()},
   262  			},
   263  			wantStates: []kueue.AdmissionCheckState{
   264  				{
   265  					Name:               "check1",
   266  					State:              kueue.CheckStatePending,
   267  					LastTransitionTime: *t0.DeepCopy(),
   268  					Message:            "msg1",
   269  					PodSetUpdates:      []kueue.PodSetUpdate{*ps1Updates.DeepCopy()},
   270  				},
   271  			},
   272  		},
   273  		"update check": {
   274  			origStates: []kueue.AdmissionCheckState{
   275  				{
   276  					Name:               "check1",
   277  					State:              kueue.CheckStatePending,
   278  					LastTransitionTime: *t0.DeepCopy(),
   279  					Message:            "msg1",
   280  					PodSetUpdates:      nil,
   281  				},
   282  				{
   283  					Name:               "check2",
   284  					State:              kueue.CheckStatePending,
   285  					LastTransitionTime: *t0.DeepCopy(),
   286  					Message:            "msg1",
   287  					PodSetUpdates:      nil,
   288  				},
   289  			},
   290  			state: kueue.AdmissionCheckState{
   291  				Name:               "check1",
   292  				State:              kueue.CheckStateReady,
   293  				LastTransitionTime: *t1.DeepCopy(),
   294  				Message:            "msg2",
   295  				PodSetUpdates:      []kueue.PodSetUpdate{*ps1Updates.DeepCopy()},
   296  			},
   297  			wantStates: []kueue.AdmissionCheckState{
   298  				{
   299  					Name:               "check1",
   300  					State:              kueue.CheckStateReady,
   301  					LastTransitionTime: *t1.DeepCopy(),
   302  					Message:            "msg2",
   303  					PodSetUpdates:      []kueue.PodSetUpdate{*ps1Updates.DeepCopy()},
   304  				},
   305  				{
   306  					Name:               "check2",
   307  					State:              kueue.CheckStatePending,
   308  					LastTransitionTime: *t0.DeepCopy(),
   309  					Message:            "msg1",
   310  					PodSetUpdates:      nil,
   311  				},
   312  			},
   313  		},
   314  		"add new check, no transition tim": {
   315  			origStates: []kueue.AdmissionCheckState{},
   316  			state: kueue.AdmissionCheckState{
   317  				Name:          "check1",
   318  				State:         kueue.CheckStatePending,
   319  				Message:       "msg1",
   320  				PodSetUpdates: []kueue.PodSetUpdate{*ps1Updates.DeepCopy()},
   321  			},
   322  			wantStates: []kueue.AdmissionCheckState{
   323  				{
   324  					Name:          "check1",
   325  					State:         kueue.CheckStatePending,
   326  					Message:       "msg1",
   327  					PodSetUpdates: []kueue.PodSetUpdate{*ps1Updates.DeepCopy()},
   328  				},
   329  			},
   330  		},
   331  		"update check, no transition time": {
   332  			origStates: []kueue.AdmissionCheckState{
   333  				{
   334  					Name:               "check1",
   335  					State:              kueue.CheckStatePending,
   336  					LastTransitionTime: *t0.DeepCopy(),
   337  					Message:            "msg1",
   338  					PodSetUpdates:      nil,
   339  				},
   340  				{
   341  					Name:               "check2",
   342  					State:              kueue.CheckStatePending,
   343  					LastTransitionTime: *t0.DeepCopy(),
   344  					Message:            "msg1",
   345  					PodSetUpdates:      nil,
   346  				},
   347  			},
   348  			state: kueue.AdmissionCheckState{
   349  				Name:          "check1",
   350  				State:         kueue.CheckStateReady,
   351  				Message:       "msg2",
   352  				PodSetUpdates: []kueue.PodSetUpdate{*ps1Updates.DeepCopy()},
   353  			},
   354  			wantStates: []kueue.AdmissionCheckState{
   355  				{
   356  					Name:          "check1",
   357  					State:         kueue.CheckStateReady,
   358  					Message:       "msg2",
   359  					PodSetUpdates: []kueue.PodSetUpdate{*ps1Updates.DeepCopy()},
   360  				},
   361  				{
   362  					Name:               "check2",
   363  					State:              kueue.CheckStatePending,
   364  					LastTransitionTime: *t0.DeepCopy(),
   365  					Message:            "msg1",
   366  					PodSetUpdates:      nil,
   367  				},
   368  			},
   369  		},
   370  	}
   371  
   372  	for name, tc := range cases {
   373  		t.Run(name, func(t *testing.T) {
   374  			gotStates := tc.origStates
   375  
   376  			SetAdmissionCheckState(&gotStates, tc.state)
   377  
   378  			opts := []cmp.Option{}
   379  			if tc.state.LastTransitionTime.IsZero() {
   380  				opts = append(opts, cmpopts.IgnoreFields(kueue.AdmissionCheckState{}, "LastTransitionTime"), cmpopts.EquateApproxTime(time.Second))
   381  
   382  				if updatedCheck := FindAdmissionCheck(gotStates, tc.state.Name); updatedCheck == nil {
   383  					t.Error("Cannot find the updated check state")
   384  				} else {
   385  					if diff := cmp.Diff(metav1.NewTime(time.Now()), updatedCheck.LastTransitionTime, opts...); diff != "" {
   386  						t.Errorf("Unexpected LastTransitionTime (- want/+ got):\n%s", diff)
   387  					}
   388  				}
   389  			}
   390  
   391  			if diff := cmp.Diff(tc.wantStates, gotStates, opts...); diff != "" {
   392  				t.Errorf("Unexpected conditions after sync (- want/+ got):\n%s", diff)
   393  			}
   394  		})
   395  	}
   396  }