Repository: kubernetes/kubernetes
File: pkg/controller/devicetainteviction/device_taint_eviction_test.go (Go)

Code before:

	opts := []cmp.Option{
		// Expected objects don't have managed fields, API objects do.
		cmpopts.IgnoreFields(metav1.ObjectMeta{}, "ManagedFields"),
		// metav1.Time gets rounded to seconds during serialization.
		cmpopts.AcyclicTransformer("RoundTime", func(t metav1.Time) metav1.Time {
			return metav1.Time{Time: t.Round(time.Second)}
		}),
		// Ignoring the condition.Message to avoid mismatch due to race and separately compare the Message.
		cmpopts.IgnoreFields(metav1.Condition{}, "Message"),
	}

	actualPods, err := tCtx.Client().CoreV1().Pods("").List(tCtx, metav1.ListOptions{})
	tCtx.ExpectNoError(err, prefix+"list pods")
	assertEqual(tCtx, expectPods, trimPods(actualPods.Items), prefix+"pods", opts...)
	rules, err := tCtx.Client().ResourceV1alpha3().DeviceTaintRules().List(tCtx, metav1.ListOptions{})
	tCtx.ExpectNoError(err, prefix+"list rules")
	assertEqual(tCtx, expectRules, trimRules(rules.Items), prefix+"rules", opts...)

	// Compare the Condition.Message
	// Due to race condition the Message may not only contain the expected message, it might contain Message from other stages.
	gomega.NewWithT(tCtx).Expect(rules.Items).To(gomega.HaveLen(len(expectRules)))
	for ruleIndex, rule := range rules.Items {
		gomega.NewWithT(tCtx).Expect(rule.Status.Conditions).To(gomega.HaveLen(len(expectRules[ruleIndex].Status.Conditions)))
		for conditionIndex, condition := range rule.Status.Conditions {
			gomega.NewWithT(tCtx).Expect(condition.Message).To(gomega.ContainSubstring(expectRules[ruleIndex].Status.Conditions[conditionIndex].Message))
		}
	}
}

// TestCancelEviction deletes the pod before the controller deletes it
// or removes the slice. Either way, eviction gets cancelled.
func TestCancelEviction(t *testing.T) {
	tCtx := ktesting.Init(t)
	tCtx.SyncTest("pod-deleted", func(tCtx ktesting.TContext) { testCancelEviction(tCtx, true) })
	tCtx.SyncTest("slice-deleted", func(tCtx ktesting.TContext) { testCancelEviction(tCtx, false) })
}

func testCancelEviction(tCtx ktesting.TContext, deletePod bool) {
	// The claim tolerates the taint long enough for us to
	// do something which cancels eviction.
	pod := podWithClaimName.DeepCopy()
	slice := sliceTainted.DeepCopy()
	slice.Spec.Devices[0].Taints[0].TimeAdded = metav1Time(time.Now())
	claim := inUseClaim.DeepCopy()
	tolerationSeconds := int64(60)
	claim.Status.Allocation.Devices.Results[0].Tolerations = []resourceapi.DeviceToleration{{
		Operator:          resourceapi.DeviceTolerationOpExists,
		Effect:            resourceapi.DeviceTaintEffectNoExecute,
		TolerationSeconds: &tolerationSeconds,
	}}

Review comment (suggestion):
If the controller adds random, unwanted garbage to the message, then the test won't complain when using `ContainSubstring`. Can we use regular expressions and change all expected strings to use anchors (`^1 pod needs to be evicted in 1 namespace.$`)? If we really want substring matching, then the anchors can be dropped where needed.

I don't care that `.` is strictly speaking a random single character. We would have to use `\.` if we wanted to be really precise.



Code after:
	rule, err := tCtx.Client().ResourceV1alpha3().DeviceTaintRules().Update(tCtx, rule, metav1.UpdateOptions{})
	tCtx.ExpectNoError(err, "update rule")

	// Wait for eviction.
	tCtx.Wait()
	evicted := metav1.Now()
	tCtx.Logf("TIME: eviction done at %s", evicted)
	// The rule status got updated once before evicting pods, but not yet after evicting it.
	check(tCtx, "evict: ", l(inProgress(rule, true, "PodsPendingEviction", "1 pod needs to be evicted in 1 namespace.", &evicted)), nil)

	// AddAfter does not move time forward. Do it ourselves...
	time.Sleep(ruleStatusPeriod)
	slept := metav1.Now()
	tCtx.Logf("TIME: slept till %s", slept)
	tCtx.Wait()
	done := metav1.Now()
	tCtx.Logf("TIME: done at %s", done)
	check(tCtx, "done: ", l(inProgress(rule, false, "Completed", "1 pod evicted since starting the controller.", &done)), nil)
	assertEqual(tCtx, map[types.UID]taintRuleStats{rule.UID: {numEvictedPods: 1}}, controller.taintRuleStats, "taint rule statistics should have counted the pod")

	// Delete the rule and verify that we don't leak memory by still tracking it.
	err = tCtx.Client().ResourceV1alpha3().DeviceTaintRules().Delete(tCtx, rule.Name, metav1.DeleteOptions{})
	tCtx.ExpectNoError(err, "delete rule")
	tCtx.Wait()
	deleted := metav1.Now()
	tCtx.Logf("TIME: deleted at %s", deleted)
	assert.Empty(tCtx, controller.taintRuleStats, "taint rule statistics should have dropped the deleted rule")
}

func check(tCtx ktesting.TContext, prefix string, expectRules []*resourcealpha.DeviceTaintRule, expectPods []*v1.Pod) {
	tCtx.Helper()

	opts := []cmp.Option{
		// Expected objects don't have managed fields, API objects do.
		cmpopts.IgnoreFields(metav1.ObjectMeta{}, "ManagedFields"),
		// metav1.Time gets rounded to seconds during serialization.
		cmpopts.AcyclicTransformer("RoundTime", func(t metav1.Time) metav1.Time {
			return metav1.Time{Time: t.Round(time.Second)}
		}),
	}

	actualPods, err := tCtx.Client().CoreV1().Pods("").List(tCtx, metav1.ListOptions{})
	tCtx.ExpectNoError(err, prefix+"list pods")
	assertEqual(tCtx, expectPods, trimPods(actualPods.Items), prefix+"pods", opts...)
	rules, err := tCtx.Client().ResourceV1alpha3().DeviceTaintRules().List(tCtx, metav1.ListOptions{})
	tCtx.ExpectNoError(err, prefix+"list rules")
	assertEqual(tCtx, expectRules, trimRules(rules.Items), prefix+"rules", opts...)
}

// TestCancelEviction deletes the pod before the controller deletes it
// or removes the slice. Either way, eviction gets cancelled.