@@ -37,8 +37,8 @@ import (
3737 maintenancev1alpha1 "github.com/Mellanox/maintenance-operator/api/v1alpha1"
3838 k8serrors "k8s.io/apimachinery/pkg/api/errors"
3939
40- v1alpha1 "github.com/NVIDIA/k8s-operator-libs/api/upgrade/v1alpha1"
41- upgrade "github.com/NVIDIA/k8s-operator-libs/pkg/upgrade"
40+ "github.com/NVIDIA/k8s-operator-libs/api/upgrade/v1alpha1"
41+ "github.com/NVIDIA/k8s-operator-libs/pkg/upgrade"
4242 "github.com/NVIDIA/k8s-operator-libs/pkg/upgrade/mocks"
4343)
4444
@@ -347,6 +347,179 @@ var _ = Describe("UpgradeStateManager tests", func() {
347347 Expect (stateCount [upgrade .UpgradeStateUpgradeRequired ]).To (Equal (2 ))
348348 Expect (stateCount [upgrade .UpgradeStateCordonRequired ]).To (Equal (maxParallelUpgrades ))
349349 })
350+ When ("a RestartOnlyPredicate is registered" , func () {
351+ var (
352+ daemonSet * appsv1.DaemonSet
353+ outdatedPod * corev1.Pod
354+ )
355+ BeforeEach (func () {
356+ daemonSet = & appsv1.DaemonSet {ObjectMeta : v1.ObjectMeta {}}
357+ // pod revision hash differs from the mocked DS hash ("test-hash-12345") -> out of sync
358+ outdatedPod = & corev1.Pod {ObjectMeta : v1.ObjectMeta {
359+ Labels : map [string ]string {upgrade .PodControllerRevisionHashLabelKey : "test-hash-outdated" }}}
360+ })
361+
362+ It ("cordons the node and routes it to PodRestartRequired when the predicate returns true" , func () {
363+ var gotPod * corev1.Pod
364+ var gotDS * appsv1.DaemonSet
365+ stateManagerInterface .WithRestartOnlyPredicate (
366+ func (_ context.Context , pod * corev1.Pod , ds * appsv1.DaemonSet ) (bool , error ) {
367+ gotPod , gotDS = pod , ds
368+ return true , nil
369+ })
370+ localCordonManager := mocks.CordonManager {}
371+ localCordonManager .On ("Cordon" , mock .Anything , mock .Anything ).Return (nil )
372+ stateManager .CordonManager = & localCordonManager
373+ node := nodeWithUpgradeState (upgrade .UpgradeStateUpgradeRequired )
374+ clusterState := upgrade .NewClusterUpgradeState ()
375+ clusterState .NodeStates [upgrade .UpgradeStateUpgradeRequired ] = []* upgrade.NodeUpgradeState {
376+ {Node : node , DriverPod : outdatedPod , DriverDaemonSet : daemonSet },
377+ }
378+
379+ Expect (stateManagerInterface .ApplyState (testCtx , & clusterState ,
380+ & v1alpha1.DriverUpgradePolicySpec {AutoUpgrade : true })).To (Succeed ())
381+ Expect (getNodeUpgradeState (node )).To (Equal (upgrade .UpgradeStatePodRestartRequired ))
382+ localCordonManager .AssertCalled (GinkgoT (), "Cordon" , mock .Anything , node )
383+ // predicate received this node's driver pod and owning DaemonSet
384+ Expect (gotPod ).To (Equal (outdatedPod ))
385+ Expect (gotDS ).To (Equal (daemonSet ))
386+ })
387+
388+ It ("leaves the node in UpgradeRequired for retry when the cordon fails" , func () {
389+ stateManagerInterface .WithRestartOnlyPredicate (
390+ func (_ context.Context , _ * corev1.Pod , _ * appsv1.DaemonSet ) (bool , error ) {
391+ return true , nil
392+ })
393+ localCordonManager := mocks.CordonManager {}
394+ localCordonManager .On ("Cordon" , mock .Anything , mock .Anything ).Return (fmt .Errorf ("cordon failure" ))
395+ stateManager .CordonManager = & localCordonManager
396+ node := nodeWithUpgradeState (upgrade .UpgradeStateUpgradeRequired )
397+ clusterState := upgrade .NewClusterUpgradeState ()
398+ clusterState .NodeStates [upgrade .UpgradeStateUpgradeRequired ] = []* upgrade.NodeUpgradeState {
399+ {Node : node , DriverPod : outdatedPod , DriverDaemonSet : daemonSet },
400+ }
401+
402+ Expect (stateManagerInterface .ApplyState (testCtx , & clusterState ,
403+ & v1alpha1.DriverUpgradePolicySpec {AutoUpgrade : true })).To (Succeed ())
404+ Expect (getNodeUpgradeState (node )).To (Equal (upgrade .UpgradeStateUpgradeRequired ))
405+ })
406+
407+ It ("routes the node to CordonRequired when the predicate returns false" , func () {
408+ stateManagerInterface .WithRestartOnlyPredicate (
409+ func (_ context.Context , _ * corev1.Pod , _ * appsv1.DaemonSet ) (bool , error ) {
410+ return false , nil
411+ })
412+ node := nodeWithUpgradeState (upgrade .UpgradeStateUpgradeRequired )
413+ clusterState := upgrade .NewClusterUpgradeState ()
414+ clusterState .NodeStates [upgrade .UpgradeStateUpgradeRequired ] = []* upgrade.NodeUpgradeState {
415+ {Node : node , DriverPod : outdatedPod , DriverDaemonSet : daemonSet },
416+ }
417+
418+ Expect (stateManagerInterface .ApplyState (testCtx , & clusterState ,
419+ & v1alpha1.DriverUpgradePolicySpec {AutoUpgrade : true })).To (Succeed ())
420+ Expect (getNodeUpgradeState (node )).To (Equal (upgrade .UpgradeStateCordonRequired ))
421+ })
422+
423+ It ("leaves the node in UpgradeRequired for retry when the predicate returns an error" , func () {
424+ stateManagerInterface .WithRestartOnlyPredicate (
425+ func (_ context.Context , _ * corev1.Pod , _ * appsv1.DaemonSet ) (bool , error ) {
426+ return true , fmt .Errorf ("predicate failure" )
427+ })
428+ node := nodeWithUpgradeState (upgrade .UpgradeStateUpgradeRequired )
429+ clusterState := upgrade .NewClusterUpgradeState ()
430+ clusterState .NodeStates [upgrade .UpgradeStateUpgradeRequired ] = []* upgrade.NodeUpgradeState {
431+ {Node : node , DriverPod : outdatedPod , DriverDaemonSet : daemonSet },
432+ }
433+
434+ Expect (stateManagerInterface .ApplyState (testCtx , & clusterState ,
435+ & v1alpha1.DriverUpgradePolicySpec {AutoUpgrade : true })).To (Succeed ())
436+ Expect (getNodeUpgradeState (node )).To (Equal (upgrade .UpgradeStateUpgradeRequired ))
437+ })
438+
439+ It ("does not invoke the predicate for an orphaned pod" , func () {
440+ called := false
441+ stateManagerInterface .WithRestartOnlyPredicate (
442+ func (_ context.Context , _ * corev1.Pod , _ * appsv1.DaemonSet ) (bool , error ) {
443+ called = true
444+ return true , nil
445+ })
446+ node := nodeWithUpgradeState (upgrade .UpgradeStateUpgradeRequired )
447+ clusterState := upgrade .NewClusterUpgradeState ()
448+ clusterState .NodeStates [upgrade .UpgradeStateUpgradeRequired ] = []* upgrade.NodeUpgradeState {
449+ {Node : node , DriverPod : & corev1.Pod {}, DriverDaemonSet : nil }, // orphaned
450+ }
451+
452+ Expect (stateManagerInterface .ApplyState (testCtx , & clusterState ,
453+ & v1alpha1.DriverUpgradePolicySpec {AutoUpgrade : true })).To (Succeed ())
454+ Expect (called ).To (BeFalse ())
455+ Expect (getNodeUpgradeState (node )).To (Equal (upgrade .UpgradeStateCordonRequired ))
456+ })
457+
458+ It ("does not invoke the predicate for an upgrade-requested node" , func () {
459+ called := false
460+ stateManagerInterface .WithRestartOnlyPredicate (
461+ func (_ context.Context , _ * corev1.Pod , _ * appsv1.DaemonSet ) (bool , error ) {
462+ called = true
463+ return true , nil
464+ })
465+ node := nodeWithUpgradeState (upgrade .UpgradeStateUpgradeRequired )
466+ node .Annotations [upgrade .GetUpgradeRequestedAnnotationKey ()] = "true"
467+ clusterState := upgrade .NewClusterUpgradeState ()
468+ clusterState .NodeStates [upgrade .UpgradeStateUpgradeRequired ] = []* upgrade.NodeUpgradeState {
469+ {Node : node , DriverPod : outdatedPod , DriverDaemonSet : daemonSet },
470+ }
471+
472+ Expect (stateManagerInterface .ApplyState (testCtx , & clusterState ,
473+ & v1alpha1.DriverUpgradePolicySpec {AutoUpgrade : true })).To (Succeed ())
474+ Expect (called ).To (BeFalse ())
475+ Expect (getNodeUpgradeState (node )).To (Equal (upgrade .UpgradeStateCordonRequired ))
476+ })
477+
478+ It ("does not invoke the predicate for a node waiting for safe driver load" , func () {
479+ called := false
480+ stateManagerInterface .WithRestartOnlyPredicate (
481+ func (_ context.Context , _ * corev1.Pod , _ * appsv1.DaemonSet ) (bool , error ) {
482+ called = true
483+ return true , nil
484+ })
485+ node := nodeWithUpgradeState (upgrade .UpgradeStateUpgradeRequired )
486+ node .Annotations [upgrade .GetUpgradeDriverWaitForSafeLoadAnnotationKey ()] = "true"
487+ clusterState := upgrade .NewClusterUpgradeState ()
488+ clusterState .NodeStates [upgrade .UpgradeStateUpgradeRequired ] = []* upgrade.NodeUpgradeState {
489+ {Node : node , DriverPod : outdatedPod , DriverDaemonSet : daemonSet },
490+ }
491+
492+ Expect (stateManagerInterface .ApplyState (testCtx , & clusterState ,
493+ & v1alpha1.DriverUpgradePolicySpec {AutoUpgrade : true })).To (Succeed ())
494+ Expect (called ).To (BeFalse ())
495+ Expect (getNodeUpgradeState (node )).To (Equal (upgrade .UpgradeStateCordonRequired ))
496+ })
497+
498+ It ("honors the maxParallelUpgrades throttle for restart-only nodes" , func () {
499+ const maxParallelUpgrades = 1
500+ stateManagerInterface .WithRestartOnlyPredicate (
501+ func (_ context.Context , _ * corev1.Pod , _ * appsv1.DaemonSet ) (bool , error ) {
502+ return true , nil
503+ })
504+ nodeStates := []* upgrade.NodeUpgradeState {
505+ {Node : nodeWithUpgradeState (upgrade .UpgradeStateUpgradeRequired ),
506+ DriverPod : outdatedPod , DriverDaemonSet : daemonSet },
507+ {Node : nodeWithUpgradeState (upgrade .UpgradeStateUpgradeRequired ),
508+ DriverPod : outdatedPod , DriverDaemonSet : daemonSet },
509+ }
510+ clusterState := upgrade .NewClusterUpgradeState ()
511+ clusterState .NodeStates [upgrade .UpgradeStateUpgradeRequired ] = nodeStates
512+ policy := & v1alpha1.DriverUpgradePolicySpec {AutoUpgrade : true , MaxParallelUpgrades : maxParallelUpgrades }
513+
514+ Expect (stateManagerInterface .ApplyState (testCtx , & clusterState , policy )).To (Succeed ())
515+ stateCount := make (map [string ]int )
516+ for i := range nodeStates {
517+ stateCount [getNodeUpgradeState (nodeStates [i ].Node )]++
518+ }
519+ Expect (stateCount [upgrade .UpgradeStatePodRestartRequired ]).To (Equal (maxParallelUpgrades ))
520+ Expect (stateCount [upgrade .UpgradeStateUpgradeRequired ]).To (Equal (1 ))
521+ })
522+ })
350523 It ("UpgradeStateManager should start additional upgrades if maxParallelUpgrades limit is not reached" , func () {
351524 const maxParallelUpgrades = 4
352525
0 commit comments