Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
/kind bug |
thiyyakat
left a comment
There was a problem hiding this comment.
Thanks for the changes and the refactoring! I haven't looked at the full PR yet. I had one case in mind for which I thought the new code may not behave as expected. Once you address that, I will look at the rest as well.
| defer trackers.Stop() | ||
| waitForCacheSync(stop, c) | ||
| err := c.updateMachineAndMachineDeploymentDeletionAnnotations(context.TODO(), testMachineDeployment) | ||
| err := func() error { |
There was a problem hiding this comment.
This may be a stupid question, but what is the point of the closure here? Can you not simply have just L2361?
| node.Annotations[PreserveMachineAnnotationKey]; ok && | ||
| AllowedPreserveAnnotationValues.Has(val) { | ||
| return val, true |
There was a problem hiding this comment.
Please print a warning here if the value is not valid.
| if val, ok := | ||
| machine.Annotations[PreserveMachineAnnotationKey]; ok && | ||
| AllowedPreserveAnnotationValues.Has(val) { | ||
| return val, true |
There was a problem hiding this comment.
Please log a warning if the value is not valid.
| preserveAnnotationValue, exists := machineutils.GetPreserveAnnotationValue(node, machine) | ||
| if !exists { | ||
| return | ||
| } else if getErr != nil { | ||
| if !apierrors.IsNotFound(getErr) { | ||
| err = getErr | ||
| return | ||
| } | ||
| klog.Warningf("Couldn't find node %q for machine %q", nodeName, machine.Name) | ||
| err = nil | ||
| } |
There was a problem hiding this comment.
Please correct me if I'm wrong, but what about the case where preservation was being manipulated by only the machine object's annotations? If a machine was preserved, and the annotation was deleted to indicate that preservation should be stopped, you will end up returning early here instead of stopping preservation.
There was a problem hiding this comment.
Good catch! Addressed in the latest commit ab6bf44
gagan16k
left a comment
There was a problem hiding this comment.
Thanks for the changes! Have some comments, PTAL
| }) | ||
| updatedMachine, err := machineutils.PatchMachine(ctx, c.Machines(machine.Namespace), &machine, func(m *v1alpha1.Machine) error { | ||
| m.Labels = labelsutil.AddLabel(m.Labels, v1alpha1.DefaultMachineDeploymentUniqueLabelKey, hash) | ||
| return nil |
There was a problem hiding this comment.
This will force a reconcile instead of retry on returning 409 errors, so the unit test for LDRCBST could flake even more now. Might need to find another way to correct that.
There was a problem hiding this comment.
After our offline discussions, the error is very low and honestly I am not really sure why the LDRCBST test is failed. More investigation would be required for fixing it and not sure whether we want to tackle it now.
| // Deep-copy to avoid mutating cache objects downstream. | ||
| for i, m := range filteredMachines { | ||
| filteredMachines[i] = m.DeepCopy() | ||
| } | ||
|
|
There was a problem hiding this comment.
Great change, but I was thinking if we could apply this while creating the filteredMachines slices, in claimMachines().
Argument for: Saves extra work on the slice, and does it in the same loop while the slice is getting constructed.
Argument against: The other callers for the function, do not seem to write to the slice like this reconciler does, and thus do not really need a deep copy. WDYT?
There was a problem hiding this comment.
I did not implement it in claimMachines() for the reason mentioned in Argument against. But if the team sees differently, I can implement it.
aaronfern
left a comment
There was a problem hiding this comment.
General feedback
- Newly introduced flag should be mentioned in description and release notes
- Error strings should not be capitalised
- In general it's not nice that this huge bunch of miscellaneous code fixes are part of this bug fix. They are good pieces, but not required to fix the bug this PR talks about. It's fine for now as the work is done already, but keep this in mind for future reference
| fs.Int32Var(&s.SafetyOptions.SafetyDown, "safety-down", s.SafetyOptions.SafetyDown, "Upper-limit minus safety-down value gives the lower-limit. This is the limits below which any temporarily frozen machineSet/machineDeployment object is unfrozen. lower-limit = desired + maxSurge (if applicable) + safetyUp - safetyDown.") | ||
|
|
||
| fs.DurationVar(&s.SafetyOptions.MachineSafetyOvershootingPeriod.Duration, "machine-safety-overshooting-period", s.SafetyOptions.MachineSafetyOvershootingPeriod.Duration, "Time period (in duration) used to poll for overshooting of machine objects backing a machineSet by safety controller.") | ||
| fs.DurationVar(&s.SafetyOptions.MachinePreserveTimeout.Duration, "machine-preserve-timeout", s.SafetyOptions.MachinePreserveTimeout.Duration, "Duration for which a failed machine should be preserved if it has the appropriate preserve annotation set.") |
There was a problem hiding this comment.
The issue description does not mention any new flag. A new flag like this is important enough to be mentioned as a release note
| klog.Errorf("Error annotating and setting PreserveExpiryTime on machine %q for auto-preservation: %v", machine.Name, err) | ||
| return nil, err |
There was a problem hiding this comment.
Returning here stops patching of further machines. Consider pooling all error messages and returning an aggregate error at the end of the loop
| // nodeTemplateChanged returns true if machine's nodeTemplate is changed. | ||
| func nodeTemplateChanged(machineset *v1alpha1.MachineSet, machine *v1alpha1.Machine) bool { |
There was a problem hiding this comment.
This func name is semantically incorrect. Generally a machine's nodeTemplate won't change but would rather get outdated and needs to be re-synced.
Consider names like nodeTemplateOutOfSync or machineNodeTemplateMismatch or feel free to choose another name that you feel is more accutare
| // configChanged returns true if machine's config is changed. | ||
| func configChanged(machineset *v1alpha1.MachineSet, machine *v1alpha1.Machine) bool { |
There was a problem hiding this comment.
Same as above, name is not accurate
| // classKindChanged returns true if machine's class.Kind is changed. | ||
| func classKindChanged(machineset *v1alpha1.MachineSet, machine *v1alpha1.Machine) bool { |
There was a problem hiding this comment.
This naming is not accurate too
| machineCurrentStatus := v1alpha1.CurrentStatus{ | ||
| Phase: v1alpha1.MachineFailed, | ||
| LastUpdateTime: metav1.Now(), | ||
| } |
There was a problem hiding this comment.
Does having this out and assigned to another var help much?
There was a problem hiding this comment.
Yes. I update the PreserveExpiryTime below, based on certain criteria. If there is a better way, I can change it.
| } else { | ||
| delete(clone.Annotations, machineutils.PreserveMachineAnnotationKey) | ||
| klog.V(3).Infof( | ||
| "Syncing machine %q 's annotation:%q=%q to its node %q 's annotation:%q=%q", |
There was a problem hiding this comment.
This is worded incorrectly. We sync the node's annotation to the machine
| PreserveExpiryTime: machine.Status.CurrentStatus.PreserveExpiryTime, | ||
| } | ||
| // check if preservation is needed for the failed machine | ||
| if val, shouldHandlePreservation := machineutils.GetPreserveAnnotationValue(node, machine); shouldHandlePreservation && val == machineutils.PreserveMachineAnnotationValueWhenFailed { | ||
| clone.Status.CurrentStatus.PreserveExpiryTime = &metav1.Time{Time: metav1.Now().Add(c.getEffectiveMachinePreserveTimeout(machine).Duration)} | ||
| } |
There was a problem hiding this comment.
This snippet could potentially overwrite existing PreserveExpiryTime on the machine that is set a line above
Although, @thiyyakat do you know why we might already have a PreserveExpiryTime on the machine? We enter this block only when the machine is in the Pending phase so I wonder if line 1142 is ever relevant
There was a problem hiding this comment.
Correct me if I am wrong @thiyyakat
Only for preserve=now PreserveExpiryTime is set else it is nil. I am setting PreserveExpiryTime only for preserve=when-failed, so they should not conflict.
There was a problem hiding this comment.
Yes. As long as we're checking for the annotation value == when-failed, it should be fine.
| func (c *controller) updateMachineToFailedState(ctx context.Context, description string, machine, clone *v1alpha1.Machine) (bool, error) { | ||
| // Log the error message for machine failure | ||
| klog.Error(description) | ||
| node, _ := c.nodeLister.Get(machine.Annotations[v1alpha1.NodeLabelKey]) |
There was a problem hiding this comment.
Please don't ignore a potential error
There was a problem hiding this comment.
After offline discussions, we decided to log the error.
1fa4282 to
f28bbed
Compare
Signed-off-by: r4mek <vivek.ram688@gmail.com>
Signed-off-by: r4mek <vivek.ram688@gmail.com>
Signed-off-by: r4mek <vivek.ram688@gmail.com>
Signed-off-by: r4mek <vivek.ram688@gmail.com>
Signed-off-by: r4mek <vivek.ram688@gmail.com>
cd95cde to
f46b8e9
Compare
thiyyakat
left a comment
There was a problem hiding this comment.
Hi Vivek. Few more comments from me. PTAL. Thanks
| var ( | ||
| // DefaultMachinePreserveTimeout is the default time for which the machine is preserved | ||
| // when [MachineConfiguration.MachinePreserveTimeout] is not specified. | ||
| DefaultMachinePreserveTimeout = metav1.Duration{Duration: 96 * time.Hour} |
There was a problem hiding this comment.
Why not change this to a constant?
DefaultMachinePreserveTimeout = 96 * time.Hour
And, in manageAutoPreservationOfFailedMachines
m.Status.CurrentStatus.PreserveExpiryTime = &metav1.Time{Time: metav1.Now().Add(v1alpha1.DefaultMachinePreserveTimeout)}
|
|
||
| filteredMachines = c.manageAutoPreservationOfFailedMachines(ctx, filteredMachines, machineSet) | ||
| filteredMachines, err = c.manageAutoPreservationOfFailedMachines(ctx, filteredMachines, machineSet) | ||
| if err != nil { |
There was a problem hiding this comment.
Since we don't update AutoPreserveFailedMachineCount until calculateMachineSetStatus is called in reconcileClusterMachineSet, there is a chance that a few machines would be successfully annotated with the auto-preserve annotation, but in the next reconciliation, since AutoPreserveFailedMachineCount is stale, manageAutoPreservationOfFailedMachines will end up preserving additional machines above AutoPreserveFailedMachineMax.
However, this will self-heal because the annotation update on the machine object will requeue the machineset object as well.
@gagan16k , I think your fix will reduce the likelihood of this happening, right? If we do not rely on AutoPreserveFailedMachineCount set on the status? We need to make sure both fixes go in together.
| result: false, | ||
| }, | ||
| }), | ||
| Entry("should return true if machine is annotated with preserve=false", testCase{ |
There was a problem hiding this comment.
Could you please add a test case for when preserveExpiryTime is not set on a Failed machine?
|
|
||
| preservedMachine, err := machineutils.PatchMachine(ctx, c.controlMachineClient.Machines(annotatedMachine.Namespace), annotatedMachine, func(m *v1alpha1.Machine) error { | ||
| if annotatedMachine.Spec.MachineConfiguration != nil && annotatedMachine.Spec.MachineConfiguration.MachinePreserveTimeout != nil { | ||
| m.Status.CurrentStatus.PreserveExpiryTime = &metav1.Time{Time: metav1.Now().Add(annotatedMachine.Spec.MachineConfiguration.MachinePreserveTimeout.Duration)} |
There was a problem hiding this comment.
In the current state of the code in your PR, is there a possibility that a machine is annotated with auto-preserved, causing the machine and its owner object (the machineset) to get reconciled again, but the PreserveExpiryTime is not set, so in the latest reconciliation, the machine is found to be without PreserveExpiryTime and is thus deleted?
I was wondering if we could instead set the PreserveExpiryTime first and then add the auto-preserved annotation(just for tracking). But if there is an error during the status update, it would be difficult to add this annotation correctly to the same machine. Plus semantically it seems odd because we are using the annotation as an intent here.
Instead, in the shouldFailedMachineBeTerminated, what if we check for either preserve expiry time or auto-preserved annotation, and not just preserve expiry time?
| return | ||
| node, err := c.nodeLister.Get(nodeName) | ||
| if err != nil { | ||
| klog.V(3).Infof("Error fetching node %q . Will check the machine %q for annotation:%q", nodeName, machine.Name, machineutils.PreserveMachineAnnotationKey) |
There was a problem hiding this comment.
This log will get printed every reconciliation until a node object registers and the lister cache gets updated. Even for non-preservation-related machines. I would suggesting doing away with the log or increasing the level.
| effectivePreserveValue := getEffectivePreservationAnnotations(&preserveInfo, getErr) | ||
|
|
||
| var removeAnnotations bool | ||
| clone := machine.DeepCopy() |
There was a problem hiding this comment.
Why not use updatedMachine here? Is it because you don't want to return nil on error? Can that not be taken care of in the defer ?
| _, err := c.controlMachineClient.Machines(clone.Namespace).Update(ctx, clone, metav1.UpdateOptions{}) | ||
| return err | ||
| klog.V(3).Infof( | ||
| "Removing machine %q 's annotation:%q=%q as node %q 's has annotation:%q=%q", |
There was a problem hiding this comment.
| "Removing machine %q 's annotation:%q=%q as node %q 's has annotation:%q=%q", | |
| "Removing machine %q 's annotation:%q=%q as node %q has annotation:%q=%q", |
Nit.
| PreserveExpiryTime: machine.Status.CurrentStatus.PreserveExpiryTime, | ||
| } | ||
| // check if preservation is needed for the failed machine | ||
| if val, shouldHandlePreservation := machineutils.GetPreserveAnnotationValue(node, machine); shouldHandlePreservation && val == machineutils.PreserveMachineAnnotationValueWhenFailed { | ||
| clone.Status.CurrentStatus.PreserveExpiryTime = &metav1.Time{Time: metav1.Now().Add(c.getEffectiveMachinePreserveTimeout(machine).Duration)} | ||
| } |
There was a problem hiding this comment.
Yes. As long as we're checking for the annotation value == when-failed, it should be fine.
| AllowedPreserveAnnotationValues.Has(val) { | ||
| return val, true | ||
| } | ||
| klog.Warningf( |
There was a problem hiding this comment.
This would get printed for every un-annotated node, every reconciliation, right? 😅 I think you should only print if it is an invalid value. No point printing otherwise.
| AllowedPreserveAnnotationValues.Has(val) { | ||
| return val, true | ||
| } | ||
| klog.Warningf( |
There was a problem hiding this comment.
This too would get printed for every un-annotated machine, every reconciliation, until a node joins?
What this PR does / why we need it:
PreserveExpiryTimeon the machine if the machine has annotation:node.machine.sapcloud.io/preserve: "when-failed"and it transitions toFailedphase.PreserveExpiryTimeon the machine if the failed machine gets the annotation:node.machine.sapcloud.io/preserve: "auto-preserved"UpdateMachineWithRetries()from the lister cache, which may return a stale object.Which issue(s) this PR fixes:
Fixes #
Special notes for your reviewer:
All the miscellaneous code fix are much needed to fix the aforementioned bug without surfacing/introducing new bugs.
Release note: