-
Notifications
You must be signed in to change notification settings - Fork 138
Fix handling of NotFound errors when deciding if a Failed machine should be terminated #1135
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,6 +31,7 @@ import ( | |
| "sync" | ||
| "time" | ||
|
|
||
| corev1 "k8s.io/api/core/v1" | ||
| apiequality "k8s.io/apimachinery/pkg/api/equality" | ||
| apierrors "k8s.io/apimachinery/pkg/api/errors" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
|
|
@@ -904,29 +905,37 @@ func isMachineStatusEqual(s1, s2 v1alpha1.MachineStatus) bool { | |
| // or if it is a candidate for auto-preservation. If none of these conditions are met, it returns true indicating | ||
| // that the failed machine should be terminated. | ||
| func (c *controller) shouldFailedMachineBeTerminated(machine *v1alpha1.Machine) bool { | ||
| // if preserve expiry time is set and is in the future, machine is already preserved | ||
| if machine.Status.CurrentStatus.PreserveExpiryTime != nil { | ||
| if machine.Status.CurrentStatus.PreserveExpiryTime.After(time.Now()) { | ||
| klog.V(3).Infof("Failed machine %q is preserved until %v", machine.Name, machine.Status.CurrentStatus.PreserveExpiryTime) | ||
| return false | ||
| var ( | ||
| nodeFound bool | ||
| node *corev1.Node | ||
| err error | ||
| ) | ||
| nodeName := machine.Labels[v1alpha1.NodeLabelKey] | ||
| // We don't return on error until it is determined whether the machine has valid preservation state | ||
| if nodeName != "" { | ||
| node, err = c.nodeLister.Get(nodeName) | ||
| if err != nil { | ||
| if !apierrors.IsNotFound(err) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if there was any other error for listing the node? Then we unconditionally don't honor preservation? Why is that, preservation state can still be inferred from the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The behavior isn't consistent across different usages. As part of But here we don't do the same.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nevermind, brain fog!
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just thinking aloud here: If node is not found in the lister, it is usually not a transient error AFAIK. In that case, it makes sense to defer to the machine's value to manipulate preservation. Trouble is, if it is a transient error (there's no way of knowing, I understand that), isn't it better to retry rather than change the current state? Or do we defer to the machine in that case too? Also, if you see the rest of the preservation code, unless the nodename is "" or the node is not found, we do a lot of node updates. All of those would fail and cause returns. However, if the machine was marked with when-failed or now, the machine object at least would get preserved, even if the latter steps result in errors. So there is some value in deferring to the machine. If we are confident that operators wouldn't leave stale annotations on the machine object causing an early, unintentional end to preservation, then we could defer to the machine object on all errors. Also, what do you think the behaviour should be if the machine object is found to be un-annotated on a lister error? To me it seems like we should return the error, rather than assume preservation is not desired. LMK what you think.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
But we aren't retrying here.
Sure, that is being done in
I think you misunderstood my question, I want preservation to be honored as well. #1135 (comment) #1135 (comment)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After offline discussion with @takoverflow and @r4mek, it was decided to defer to the machine's annotation value on all errors to allow operators to preserve machines whose backing nodes cannot be fetched (in this case it is currently only NotFound error). If the machine object is not annotated, we return
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed in 111e654 |
||
| klog.Errorf("error finding preservation state for machine %q: %v. Proceeding with termination of the machine.", machine.Name, err) | ||
| return true | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we want to terminate the machine if we we get an error in
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can't ignore it unconditionally because the error may not be transient and preservation may not be desired for the machine+node. So, we can instead defer to the machine object like @takoverflow suggested in his comment. If the machine object carries no request for preservation then we terminate the machine. |
||
| } | ||
| klog.Warningf("node %q not found for machine %q.", nodeName, machine.Name) | ||
| } else { | ||
| nodeFound = true | ||
| } | ||
| klog.V(3).Infof("Preservation of failed machine %q has timed out at %v", machine.Name, machine.Status.CurrentStatus.PreserveExpiryTime) | ||
| return true | ||
| } | ||
| preserveValue, err := c.findEffectivePreserveValue(machine) | ||
| if err != nil { | ||
| // in case of error fetching node or annotations, we don't want to block deletion of failed machines, so we return true | ||
| klog.Errorf("error finding effective preserve value for machine %q: %v. Proceeding with termination of the machine.", machine.Name, err) | ||
|
|
||
| if machineutils.IsMachinePreservationExpired(machine) { | ||
|
takoverflow marked this conversation as resolved.
Outdated
gagan16k marked this conversation as resolved.
Outdated
|
||
| klog.V(3).Infof("Preservation of failed machine %q has timed out at %v", machine.Name, machine.Status.CurrentStatus.PreserveExpiryTime) | ||
| return true | ||
| } | ||
| switch preserveValue { | ||
| case machineutils.PreserveMachineAnnotationValueWhenFailed, machineutils.PreserveMachineAnnotationValueNow, machineutils.PreserveMachineAnnotationValueAutoPreserved: // this is in case preservation process is not complete yet | ||
|
|
||
| preserveInfo := machineutils.GetPreserveStateInfo(node, machine) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In cases when lister returned |
||
| if machineutils.IsPositivePreserveValue(machineutils.GetEffectivePreservationAnnotations(&preserveInfo, nodeFound)) { | ||
| klog.V(3).Infof("Failed machine %q is either preserved or in the process of being preserved.", machine.Name) | ||
| return false | ||
| case machineutils.PreserveMachineAnnotationValueFalse: | ||
| return true | ||
| default: | ||
| return true | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| // manageAutoPreservationOfFailedMachines annotates failed machines with preserve=auto-preserved annotation | ||
|
|
@@ -1019,23 +1028,3 @@ func removeAutoPreserveAnnotationFromMachine(machineToUpdate *v1alpha1.Machine) | |
| delete(machineToUpdate.Annotations, machineutils.PreserveMachineAnnotationKey) | ||
| return nil | ||
| } | ||
|
|
||
| func (c *controller) findEffectivePreserveValue(machine *v1alpha1.Machine) (string, error) { | ||
| var nodeAnnotationValue, machineAnnotationValue, lANodeAnnotationValue string | ||
| machineAnnotationValue = machine.Annotations[machineutils.PreserveMachineAnnotationKey] | ||
| lANodeAnnotationValue = machine.Annotations[machineutils.LastAppliedNodePreserveValueAnnotationKey] | ||
| nodeName := machine.Labels[v1alpha1.NodeLabelKey] | ||
| if nodeName != "" { | ||
| node, err := c.nodeLister.Get(nodeName) | ||
| if err != nil { | ||
| klog.Errorf("error fetching node %q for machine %q: %v", nodeName, machine.Name, err) | ||
| return "", err | ||
| } | ||
| nodeAnnotationValue = node.Annotations[machineutils.PreserveMachineAnnotationKey] | ||
| } | ||
| if nodeAnnotationValue == "" && lANodeAnnotationValue == "" { | ||
| return machineAnnotationValue, nil | ||
| } else { | ||
| return nodeAnnotationValue, nil | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -756,18 +756,6 @@ func (c *controller) isCreationProcessing(machine *v1alpha1.Machine) bool { | |
| Machine Preservation operations | ||
| */ | ||
|
|
||
| // preserveStateInfo encapsulates the preservation annotation values found | ||
| // on the machine and node objects, along with the effective preservation value for the machine | ||
| // and the last applied node preserve value by MCM. | ||
| type preserveStateInfo struct { | ||
| nodeAnnotated bool | ||
| machineAnnotated bool | ||
| nodeValue string | ||
| machineValue string | ||
| lastAppliedNodeValue string | ||
| preserveExpiryTimeSet bool | ||
| } | ||
|
|
||
| // manageMachinePreservation manages machine preservation based on the preserve annotation values on the node and machine objects. | ||
| func (c *controller) manageMachinePreservation(ctx context.Context, machine *v1alpha1.Machine) (retry machineutils.RetryPeriod, err error) { | ||
| defer func() { | ||
|
|
@@ -785,35 +773,45 @@ func (c *controller) manageMachinePreservation(ctx context.Context, machine *v1a | |
| retry = machineutils.LongRetry | ||
| } | ||
| }() | ||
| var ( | ||
| nodeFound bool | ||
| node *corev1.Node | ||
| ) | ||
| nodeName := machine.Labels[v1alpha1.NodeLabelKey] | ||
| // We buffer the error returned here until we can tell if the machine is preservation-bound. | ||
| preserveInfo, getErr := c.getPreserveStateInfo(machine) | ||
| if preserveInfo.machineAnnotated && !machineutils.AllowedPreserveAnnotationValues.Has(preserveInfo.machineValue) { | ||
| if nodeName != "" { | ||
| node, err = c.nodeLister.Get(nodeName) // We don't return on error immediately because we need to determine whether the machine has valid preservation state | ||
| } | ||
| preserveInfo := machineutils.GetPreserveStateInfo(node, machine) | ||
| if preserveInfo.MachineAnnotated && !machineutils.AllowedPreserveAnnotationValues.Has(preserveInfo.MachineValue) { | ||
| // If machine is annotated incorrectly, log and proceed as though machine is not annotated. | ||
| klog.Warningf("Preserve annotation %q=%q on machine %q is invalid", machineutils.PreserveMachineAnnotationKey, preserveInfo.machineValue, machine.Name) | ||
| preserveInfo.machineAnnotated = false | ||
| preserveInfo.machineValue = "" | ||
| klog.Warningf("Preserve annotation %q=%q on machine %q is invalid", machineutils.PreserveMachineAnnotationKey, preserveInfo.MachineValue, machine.Name) | ||
| preserveInfo.MachineAnnotated = false | ||
| preserveInfo.MachineValue = "" | ||
| } | ||
| if preserveInfo.nodeAnnotated && !machineutils.AllowedPreserveAnnotationValues.Has(preserveInfo.nodeValue) { | ||
| klog.Warningf("Preserve annotation %q=%q on node %q backing machine %q is invalid", machineutils.PreserveMachineAnnotationKey, preserveInfo.nodeValue, nodeName, machine.Name) | ||
| if preserveInfo.NodeAnnotated && !machineutils.AllowedPreserveAnnotationValues.Has(preserveInfo.NodeValue) { | ||
| klog.Warningf("Preserve annotation %q=%q on node %q backing machine %q is invalid", machineutils.PreserveMachineAnnotationKey, preserveInfo.NodeValue, nodeName, machine.Name) | ||
| return | ||
| } | ||
| preservationBound := isMachinePreservationBound(&preserveInfo) | ||
| if !preservationBound { | ||
| // We clear the error here to prevent preservation logic from interfering with non-preservation-bound machines. | ||
| err = nil | ||
| return | ||
| } else if getErr != nil { | ||
| if !apierrors.IsNotFound(getErr) { | ||
| err = getErr | ||
| } | ||
|
|
||
| if err != nil { | ||
| if !apierrors.IsNotFound(err) { | ||
| return | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we return here or ignore this?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ignoring error, as discussed offline. Addressed in 2f7ed54. PTAL |
||
| } | ||
| klog.Warningf("Couldn't find node %q for machine %q", nodeName, machine.Name) | ||
| err = nil | ||
| klog.Warningf("Couldn't find node %q for machine %q", nodeName, machine.Name) | ||
| } else { | ||
| nodeFound = true | ||
|
gagan16k marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| // Note: when the backing node cannot be found, we assume the machine's annotation value needs to be enforced to enable | ||
| // preservation of the machine object. | ||
| effectivePreserveValue := getEffectivePreservationAnnotations(&preserveInfo, getErr) | ||
| effectivePreserveValue := machineutils.GetEffectivePreservationAnnotations(&preserveInfo, nodeFound) | ||
|
|
||
| var removeAnnotations bool | ||
| clone := machine.DeepCopy() | ||
|
|
@@ -872,81 +870,33 @@ func (c *controller) manageMachinePreservation(ctx context.Context, machine *v1a | |
| } | ||
|
|
||
| if shouldAnnotationsBeUpdatedOnMachine(removeAnnotations, &preserveInfo) { | ||
| err = c.updatePreserveAnnotationOnMachine(ctx, preserveInfo.nodeValue, clone) | ||
| err = c.updatePreserveAnnotationOnMachine(ctx, preserveInfo.NodeValue, clone) | ||
| } | ||
| return | ||
| } | ||
|
|
||
| // getEffectivePreservationAnnotations returns the effective preservation value. | ||
| // | ||
| // If there is no active node annotation AND no previously-applied node annotation, | ||
| // enforce machine's preserve annotation. | ||
| // Otherwise, the node annotation takes precedence (even if now empty/removed). | ||
| // | ||
| // lastAppliedNodeValue is required to handle the following scenario: | ||
| // | ||
| // T1: Node and Machine both have the same annotation with the same value. (MCM is up and running). | ||
| // T2 (T2 > T1): MCM went down. | ||
| // T3 (T3 > T2): Node annotation was removed. | ||
| // T4 (T4 > T3): MCM came back up. | ||
| // At T4 it sees a Node with no preserve annotation but a Machine with a preserve annotation. | ||
| // It continues to preserve the machine. | ||
| func getEffectivePreservationAnnotations(info *preserveStateInfo, getPreserveStateErr error) string { | ||
| // If the node cannot be found, nodeValue is "". | ||
| // In this case, we want the machine's annotation value to be enforced. | ||
| if apierrors.IsNotFound(getPreserveStateErr) { | ||
| return info.machineValue | ||
| } | ||
| // If there is no active node annotation AND no previously-applied node annotation, | ||
| // enforce machine's preserve annotation. | ||
| // Otherwise, the node annotation takes precedence (even if now empty/removed). | ||
| if info.nodeValue == "" && info.lastAppliedNodeValue == "" { | ||
| return info.machineValue | ||
| } | ||
| return info.nodeValue | ||
| } | ||
|
|
||
| func isMachinePreservationBound(info *preserveStateInfo) bool { | ||
| // isMachinePreservationBound returns whether the machine carries any preservation state. | ||
| func isMachinePreservationBound(info *machineutils.PreserveStateInfo) bool { | ||
| // if machine has no preservation state, the machine is not preservation-bound | ||
| if !info.preserveExpiryTimeSet && !info.nodeAnnotated && !info.machineAnnotated && info.lastAppliedNodeValue == "" { | ||
| if !info.PreserveExpiryTimeSet && !info.NodeAnnotated && !info.MachineAnnotated && info.LastAppliedNodeValue == "" { | ||
| return false | ||
| } | ||
| return true | ||
| } | ||
|
|
||
| func (c *controller) getPreserveStateInfo(machine *v1alpha1.Machine) (preserveStateInfo, error) { | ||
| var info preserveStateInfo | ||
| if machine.Annotations != nil { | ||
| info.machineValue, info.machineAnnotated = machine.Annotations[machineutils.PreserveMachineAnnotationKey] | ||
| info.lastAppliedNodeValue = machine.Annotations[machineutils.LastAppliedNodePreserveValueAnnotationKey] | ||
| } | ||
| if machine.Status.CurrentStatus.PreserveExpiryTime != nil { | ||
| info.preserveExpiryTimeSet = true | ||
| } | ||
| nodeName := machine.Labels[v1alpha1.NodeLabelKey] | ||
| if nodeName != "" { | ||
| node, err := c.nodeLister.Get(nodeName) | ||
| if err != nil { | ||
| return info, err | ||
| } | ||
| info.nodeValue, info.nodeAnnotated = node.Annotations[machineutils.PreserveMachineAnnotationKey] | ||
| } | ||
| return info, nil | ||
| } | ||
|
|
||
| // shouldAnnotationsBeUpdatedOnMachine returns true when the machine's annotation tracking needs | ||
| // to be synced after a preservation action. | ||
| func shouldAnnotationsBeUpdatedOnMachine(removeAnnotations bool, preserveInfo *preserveStateInfo) bool { | ||
| func shouldAnnotationsBeUpdatedOnMachine(removeAnnotations bool, preserveInfo *machineutils.PreserveStateInfo) bool { | ||
| // annotations were already removed by stopPreservationIfActive — nothing left to sync | ||
| if removeAnnotations { | ||
| return false | ||
| } | ||
| // node annotation is not in control — machine annotation prevails, no sync needed | ||
| if !preserveInfo.nodeAnnotated && preserveInfo.lastAppliedNodeValue == "" { | ||
| if !preserveInfo.NodeAnnotated && preserveInfo.LastAppliedNodeValue == "" { | ||
| return false | ||
| } | ||
| // node value is unchanged and machine has no annotation to clear — nothing has changed | ||
| if preserveInfo.nodeValue == preserveInfo.lastAppliedNodeValue && !preserveInfo.machineAnnotated { | ||
| if preserveInfo.NodeValue == preserveInfo.LastAppliedNodeValue && !preserveInfo.MachineAnnotated { | ||
| return false | ||
| } | ||
| return true | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is not correct, when there's an error, we return early and never bother checking machine's preservation state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed in commit 111e654.