@@ -45,6 +45,7 @@ type MockK8sClient struct {
4545 runLogCollectorJobFn func (ctx context.Context , nodeName string ) (ctrl.Result , error )
4646 annotationManagerOverride annotation.NodeAnnotationManagerInterface
4747 mockStatusChecker * mockStatusChecker
48+ configOverride * config.TomlConfig
4849}
4950
5051func (m * MockK8sClient ) CreateMaintenanceResource (ctx context.Context , healthEventData * events.HealthEventData , groupConfig * common.EquivalenceGroupConfig ) (string , error ) {
@@ -64,11 +65,12 @@ func (m *MockK8sClient) GetStatusChecker() crstatus.CRStatusCheckerInterface {
6465}
6566
6667type mockStatusChecker struct {
67- getCRStateFn func (context.Context , string , string ) crstatus.CRState
68- shouldSkip []bool
69- states []crstatus.CRState
70- stateByCR map [string ]crstatus.CRState
71- callCount int
68+ getCRStateFn func (context.Context , string , string ) crstatus.CRState
69+ getCRStateForReferenceFn func (context.Context , string , annotation.MaintenanceResourceReference , string ) crstatus.CRState
70+ shouldSkip []bool
71+ states []crstatus.CRState
72+ stateByCR map [string ]crstatus.CRState
73+ callCount int
7274}
7375
7476func (statusChecker * mockStatusChecker ) ShouldSkipCRCreation (context.Context , string , string ) bool {
@@ -80,6 +82,23 @@ func (statusChecker *mockStatusChecker) GetCRState(ctx context.Context, actionNa
8082 return statusChecker .getCRStateFn (ctx , actionName , crName )
8183 }
8284
85+ return statusChecker .nextState (crName )
86+ }
87+
88+ func (statusChecker * mockStatusChecker ) GetCRStateForReference (
89+ ctx context.Context ,
90+ crName string ,
91+ resourceRef annotation.MaintenanceResourceReference ,
92+ completeConditionType string ,
93+ ) crstatus.CRState {
94+ if statusChecker .getCRStateForReferenceFn != nil {
95+ return statusChecker .getCRStateForReferenceFn (ctx , crName , resourceRef , completeConditionType )
96+ }
97+
98+ return statusChecker .nextState (crName )
99+ }
100+
101+ func (statusChecker * mockStatusChecker ) nextState (crName string ) crstatus.CRState {
83102 if statusChecker .stateByCR != nil {
84103 return statusChecker .stateByCR [crName ]
85104 }
@@ -105,19 +124,25 @@ func (statusChecker *mockStatusChecker) GetCRState(ctx context.Context, actionNa
105124}
106125
107126func (m * MockK8sClient ) GetConfig () * config.TomlConfig {
127+ if m .configOverride != nil {
128+ return m .configOverride
129+ }
130+
108131 return & config.TomlConfig {
109132 RemediationActions : map [string ]config.MaintenanceResource {
110133 protos .RecommendedAction_RESTART_BM .String (): {
111- EquivalenceGroup : "restart" ,
112- ApiGroup : "janitor.dgxc.nvidia.com" ,
113- Version : "v1alpha1" ,
114- Kind : "RebootNode" ,
134+ EquivalenceGroup : "restart" ,
135+ ApiGroup : "janitor.dgxc.nvidia.com" ,
136+ Version : "v1alpha1" ,
137+ Kind : "RebootNode" ,
138+ CompleteConditionType : "NodeReady" ,
115139 },
116140 protos .RecommendedAction_COMPONENT_RESET .String (): {
117- EquivalenceGroup : "restart" ,
118- ApiGroup : "janitor.dgxc.nvidia.com" ,
119- Version : "v1alpha1" ,
120- Kind : "RebootNode" ,
141+ EquivalenceGroup : "restart" ,
142+ ApiGroup : "janitor.dgxc.nvidia.com" ,
143+ Version : "v1alpha1" ,
144+ Kind : "RebootNode" ,
145+ CompleteConditionType : "NodeReady" ,
121146 },
122147 },
123148 }
@@ -184,10 +209,17 @@ func (m *MockNodeAnnotationManager) GetRemediationState(ctx context.Context, nod
184209 if createdAtForGroup , ok := m .createdByGroup [groupName ]; ok {
185210 groupCreatedAt = createdAtForGroup
186211 }
212+ actionName := m .actionByGroup [groupName ]
213+ if actionName == "" {
214+ actionName = protos .RecommendedAction_RESTART_BM .String ()
215+ }
187216 annotationState .EquivalenceGroups [groupName ] = annotation.EquivalenceGroupState {
188217 MaintenanceCR : crName ,
189218 CreatedAt : groupCreatedAt ,
190- ActionName : m .actionByGroup [groupName ],
219+ ActionName : actionName ,
220+ ApiGroup : "janitor.dgxc.nvidia.com" ,
221+ Version : "v1alpha1" ,
222+ Kind : "RebootNode" ,
191223 }
192224 }
193225 return annotationState , nil , nil
@@ -1016,11 +1048,15 @@ func TestHandleRemediationEventEnsuresGVKBeforeStatusCheck(t *testing.T) {
10161048 mockK8sClient := & MockK8sClient {
10171049 annotationManagerOverride : mockAnnotationManager ,
10181050 mockStatusChecker : & mockStatusChecker {
1019- getCRStateFn : func (_ context.Context , actionName string , crName string ) crstatus.CRState {
1051+ getCRStateForReferenceFn : func (_ context.Context , crName string ,
1052+ resourceRef annotation.MaintenanceResourceReference , completeConditionType string ) crstatus.CRState {
10201053 statusChecked = true
10211054 assert .True (t , ensureCalled , "GVK annotation should be ensured before CR status checks" )
1022- assert .Equal (t , protos .RecommendedAction_RESTART_BM .String (), actionName )
10231055 assert .Equal (t , "existing-cr" , crName )
1056+ assert .Equal (t , "janitor.dgxc.nvidia.com" , resourceRef .ApiGroup )
1057+ assert .Equal (t , "v1alpha1" , resourceRef .Version )
1058+ assert .Equal (t , "RebootNode" , resourceRef .Kind )
1059+ assert .Equal (t , "NodeReady" , completeConditionType )
10241060
10251061 return crstatus .CRStateInProgress
10261062 },
@@ -1049,6 +1085,53 @@ func TestHandleRemediationEventEnsuresGVKBeforeStatusCheck(t *testing.T) {
10491085 assert .True (t , statusChecked )
10501086}
10511087
1088+ func TestEvaluateExistingCRUsesStoredReference (t * testing.T ) {
1089+ ctx := context .Background ()
1090+ statusChecker := & mockStatusChecker {
1091+ getCRStateForReferenceFn : func (_ context.Context , crName string ,
1092+ resourceRef annotation.MaintenanceResourceReference , completeConditionType string ) crstatus.CRState {
1093+ assert .Equal (t , "stored-cr" , crName )
1094+ assert .Equal (t , "stored.example.com" , resourceRef .ApiGroup )
1095+ assert .Equal (t , "v9" , resourceRef .Version )
1096+ assert .Equal (t , "StoredMaintenance" , resourceRef .Kind )
1097+ assert .Equal (t , "stored-namespace" , resourceRef .Namespace )
1098+ assert .Equal (t , "NodeReady" , completeConditionType )
1099+
1100+ return crstatus .CRStateInProgress
1101+ },
1102+ }
1103+ mockK8sClient := & MockK8sClient {
1104+ mockStatusChecker : statusChecker ,
1105+ configOverride : & config.TomlConfig {
1106+ RemediationActions : map [string ]config.MaintenanceResource {
1107+ protos .RecommendedAction_RESTART_BM .String (): {
1108+ ApiGroup : "config.example.com" ,
1109+ Version : "v1" ,
1110+ Kind : "ConfigMaintenance" ,
1111+ Namespace : "config-namespace" ,
1112+ CompleteConditionType : "NodeReady" ,
1113+ },
1114+ },
1115+ },
1116+ }
1117+ r := NewFaultRemediationReconciler (nil , nil , nil , ReconcilerConfig {RemediationClient : mockK8sClient }, false )
1118+
1119+ decision := r .evaluateExistingCR (ctx , statusChecker , namedEquivalenceGroupState {
1120+ name : "restart" ,
1121+ state : annotation.EquivalenceGroupState {
1122+ MaintenanceCR : "stored-cr" ,
1123+ ActionName : protos .RecommendedAction_RESTART_BM .String (),
1124+ ApiGroup : "stored.example.com" ,
1125+ Version : "v9" ,
1126+ Kind : "StoredMaintenance" ,
1127+ Namespace : "stored-namespace" ,
1128+ },
1129+ }, time .Now (), "test-node" )
1130+
1131+ assert .False (t , decision .shouldCreate )
1132+ assert .Equal (t , "stored-cr" , decision .crName )
1133+ }
1134+
10521135func TestCRBasedDeduplication (t * testing.T ) {
10531136 ctx := context .Background ()
10541137
0 commit comments