package multus import ( "context" "fmt" "time" meta "k8s.io/apimachinery/pkg/apis/meta/v1" ) type validationState interface { Run(ctx context.Context, vsm *validationStateMachine) (suggestions []string, err error) } type validationStateMachine struct { vt *ValidationTest state validationState timer *time.Timer stateWasChanged bool resourceOwnerRefs []meta.OwnerReference testResults *ValidationTestResults lastSuggestions []string lastErr error done bool } func (vsm *validationStateMachine) SetNextState(nextState validationState) { vsm.stateWasChanged = true vsm.state = nextState } func (vsm *validationStateMachine) Exit() { vsm.done = true } func (vsm *validationStateMachine) Run(ctx context.Context) (*ValidationTestResults, error) { vsm.timer = time.NewTimer(vsm.vt.ResourceTimeout) defer func() { vsm.timer.Stop() }() for { select { case <-ctx.Done(): return vsm.exitContextCanceled(ctx) case <-vsm.timer.C: vsm.testResults.addSuggestions(vsm.lastSuggestions...) return vsm.testResults, fmt.Errorf("multus validation test timed out: %w", vsm.lastErr) default: // give each state the full resource timeout to run successfully if vsm.stateWasChanged { vsm.resetTimer() vsm.stateWasChanged = false } // run the state suggestions, err := vsm.state.Run(ctx, vsm) // if the context was canceled, the error message won't be as useful as gathered from // the last context, so exit before updating the latest suggestions and error if ctx.Err() != nil { return vsm.exitContextCanceled(ctx) } // record the latest suggestions and error vsm.lastErr = err vsm.lastSuggestions = suggestions // exit the state machine, error or not if vsm.done { vsm.testResults.addSuggestions(vsm.lastSuggestions...) if vsm.lastErr != nil { return vsm.testResults, fmt.Errorf("multus validation test failed: %w", vsm.lastErr) } return vsm.testResults, nil } if err != nil { vsm.vt.Logger.Infof("continuing: %s", err) } time.Sleep(2 * time.Second) } } } func (vsm *validationStateMachine) resetTimer() { if !vsm.timer.Stop() { <-vsm.timer.C } vsm.timer.Reset(vsm.vt.ResourceTimeout) } func (vsm *validationStateMachine) exitContextCanceled(ctx context.Context) (*ValidationTestResults, error) { vsm.testResults.addSuggestions(vsm.lastSuggestions...) return vsm.testResults, fmt.Errorf("context canceled before multus validation test could complete: %s: %w", ctx.Err().Error(), vsm.lastErr) }