[orchestrator/updater] Do not reschedule tasks if only placement constraints change and are satisfied by the assigned node#2496
Conversation
Codecov Report
@@ Coverage Diff @@
## master #2496 +/- ##
==========================================
- Coverage 61.46% 61.32% -0.15%
==========================================
Files 49 131 +82
Lines 6898 21449 +14551
==========================================
+ Hits 4240 13153 +8913
- Misses 2220 6889 +4669
- Partials 438 1407 +969 |
|
|
||
| // IsTaskPlacementConstraintsOnlyDirty checks if the Placement field alone | ||
| // in the spec has changed. | ||
| func IsTaskPlacementConstraintsOnlyDirty(s *api.Service, t *api.Task) bool { |
There was a problem hiding this comment.
Needs a unit test IMHOIRL.
There was a problem hiding this comment.
Also IMO IsTaskDirtyPlacementConstraintsOnly would be a better name.
There was a problem hiding this comment.
Agreed on both.
There was a problem hiding this comment.
areOnlyTaskPlacementConstraintsDirty?
| return true | ||
| } | ||
|
|
||
| constraints, _ := constraint.Parse(u.newService.Spec.Task.Placement.Constraints) |
There was a problem hiding this comment.
Why toss the error? Is it guaranteed that the constraint will parse correctly? If so, can you add a comment to that effect?
There was a problem hiding this comment.
FWIW I believe there are other places where we ignore the error. The idea is that it's validated elsewhere, and if it's somehow invalid, there's nothing that can be done here except spamming the logs, which we probably don't want to do. If parsing fails, constraints will be empty, which is the fallback behavior we would want anyway.
| constraints, _ := constraint.Parse(u.newService.Spec.Task.Placement.Constraints) | ||
|
|
||
| match := false | ||
| u.store.Update(func(tx store.Tx) error { |
There was a problem hiding this comment.
Seems like this should be a u.store.View call as not to block all other readers, no?
| } | ||
|
|
||
| // Make a deep copy of the service and task spec for the comparison. | ||
| serviceTaskSpec := *s.Spec.Task.Copy() |
There was a problem hiding this comment.
@anshulpundir does this copy over any map objects in the spec correctly?
| // Compare the task placement constraints. | ||
| var placementConstraintsChanged bool | ||
| if (s.Spec.Task.Placement != nil && t.Spec.Placement == nil) || | ||
| (s.Spec.Task.Placement == nil && t.Spec.Placement != nil) || |
There was a problem hiding this comment.
I think first two comparisons are not necessary because DeepEqual returns false if one of its arguments is nil.
| // can satisfy the changed constraints. | ||
| if u.isTaskPlacementConstraintsOnlyDirty(t) && u.nodeMatches(t) { | ||
| return false | ||
| } |
There was a problem hiding this comment.
This should go inside orchestrator.IsTaskDirty. Otherwise, when the orchestrators reconcile services, they will replace tasks that were intentionally not updated here.
There was a problem hiding this comment.
Thx for pointing this out. Done in next patch!
| return true | ||
| } | ||
|
|
||
| constraints, _ := constraint.Parse(u.newService.Spec.Task.Placement.Constraints) |
There was a problem hiding this comment.
FWIW I believe there are other places where we ignore the error. The idea is that it's validated elsewhere, and if it's somehow invalid, there's nothing that can be done here except spamming the logs, which we probably don't want to do. If parsing fails, constraints will be empty, which is the fallback behavior we would want anyway.
|
|
||
| // IsTaskPlacementConstraintsOnlyDirty checks if the Placement field alone | ||
| // in the spec has changed. | ||
| func IsTaskPlacementConstraintsOnlyDirty(s *api.Service, t *api.Task) bool { |
There was a problem hiding this comment.
areOnlyTaskPlacementConstraintsDirty?
|
|
||
| match := false | ||
| u.store.Update(func(tx store.Tx) error { | ||
| n := store.GetNode(tx, t.NodeID) |
There was a problem hiding this comment.
Check that the node was found (n != nil).
| } | ||
|
|
||
| // Make a deep copy of the service and task spec for the comparison. | ||
| serviceTaskSpec := *s.Spec.Task.Copy() |
| (s.Spec.Task.Placement == nil && t.Spec.Placement != nil) || | ||
| !reflect.DeepEqual(serviceTaskSpec.Placement, t.Spec.Placement) { | ||
| placementConstraintsChanged = true | ||
| } |
There was a problem hiding this comment.
I suggest moving this to the top of the function and returning early if placementConstraintsChanged is false, to avoid the overhead of doing deep copies and extra comparisons in the case where the placement constraints did not change.
There was a problem hiding this comment.
Makes sense. Will do.
| u.store.View(func(tx store.ReadTx) { | ||
| n = store.GetNode(tx, t.NodeID) | ||
| }) | ||
| return orchestrator.IsTaskDirty(u.newService, t, n) |
There was a problem hiding this comment.
What happens if the node spec changes between the read and the check? Would it be too heavy, or cause locking conflicts, if we checked the constraints inside of the read tx?
There was a problem hiding this comment.
This is a legit concern, but I think it's reasonable to expect users to not make frequent consecutive updates to the node and service specs.
There was a problem hiding this comment.
Oh just saw @aaronlehmann's comment, never mind.
|
What happens if the node spec changes between the read and the check? Would it be too heavy, or cause locking conflicts, if we checked the constraints inside of the read tx?
The orchestrator (or is it the task reaper?) will act on the node spec change and replace any tasks that need to be replaced.
|
|
Ah, alright. LGTM then. |
|
|
||
| // IsTaskDirty determines whether a task matches the given service's spec. | ||
| func IsTaskDirty(s *api.Service, t *api.Task) bool { | ||
| // IsTaskDirty determines whether a task matches the given service's spec and |
There was a problem hiding this comment.
@anshulpundir this is not a blocker, but I think this comment could be more clear. More specifically, describing the condition under which it returns true/false.
…traints change and are satisfied by the assigned node Signed-off-by: Anshul Pundir <anshul.pundir@docker.com>
No description provided.