-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Filter owned-resource events in the workqueue #4647
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
Merged
+677
−13
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,222 @@ | ||
| package actionsgithubcom | ||
|
|
||
| import ( | ||
| "slices" | ||
| "time" | ||
|
|
||
| "github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1" | ||
| corev1 "k8s.io/api/core/v1" | ||
| "k8s.io/apimachinery/pkg/api/equality" | ||
| metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
| "sigs.k8s.io/controller-runtime/pkg/event" | ||
| "sigs.k8s.io/controller-runtime/pkg/predicate" | ||
| ) | ||
|
|
||
| // The predicates in this file exist purely to keep work out of the workqueue. | ||
| // They must never change what a reconciler does, so each of them is written as | ||
| // the projection of the fields its reconciler actually reads: an update is | ||
| // dropped only when every one of those fields is unchanged. Whenever a | ||
| // reconciler starts reading a new field, the matching projection below has to | ||
| // grow with it. | ||
| // | ||
| // Create, delete and generic events are always delivered. Only updates are | ||
| // considered here. | ||
|
|
||
| // autoscalingRunnerSetOwnedEphemeralRunnerSetPredicate filters updates of the | ||
| // EphemeralRunnerSets owned by an AutoscalingRunnerSet. | ||
| // | ||
| // The AutoscalingRunnerSet reconciler reads the runner set's object metadata | ||
| // (labels, annotations, finalizers and deletion timestamp), its whole spec, and, | ||
| // through ephemeralRunnerSetOutdatedForAppliedRevision, Status.Phase together | ||
| // with Status.AppliedActionableRevision. It never reads | ||
| // Status.FinishedRunnerCleanupPatchID, which the EphemeralRunnerSet rewrites for | ||
| // every listener patch id and which is the noisiest part of the object. | ||
| func autoscalingRunnerSetOwnedEphemeralRunnerSetPredicate() predicate.Predicate { | ||
| return predicate.Funcs{ | ||
| UpdateFunc: func(e event.UpdateEvent) bool { | ||
| oldRunnerSet, oldOk := e.ObjectOld.(*v1alpha1.EphemeralRunnerSet) | ||
| newRunnerSet, newOk := e.ObjectNew.(*v1alpha1.EphemeralRunnerSet) | ||
| if !oldOk || !newOk { | ||
| // Not the type we reasoned about, so we cannot claim the event | ||
| // is irrelevant. Let it through. | ||
| return true | ||
| } | ||
|
|
||
| if !equalReconciledObjectMeta(&oldRunnerSet.ObjectMeta, &newRunnerSet.ObjectMeta) || | ||
| !equality.Semantic.DeepEqual(&oldRunnerSet.Spec, &newRunnerSet.Spec) { | ||
|
Comment on lines
+45
to
+46
Collaborator
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. It is fine, this should not influence the controller in any meaningful way |
||
| return true | ||
| } | ||
|
|
||
| return oldRunnerSet.Status.Phase != newRunnerSet.Status.Phase || | ||
| oldRunnerSet.Status.AppliedActionableRevision != newRunnerSet.Status.AppliedActionableRevision | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // ephemeralRunnerSetOwnedEphemeralRunnerPredicate filters updates of the | ||
| // EphemeralRunners owned by an EphemeralRunnerSet. | ||
| // | ||
| // Besides object metadata and spec, the EphemeralRunnerSet reconciler reads | ||
| // Status.Phase, to group runners by state, Status.RunnerID, to decide whether a | ||
| // runner still has to be removed from the service, and Status.JobID, through | ||
| // HasJob, to skip runners that are busy serving a job. The rest of the runner | ||
| // status (readiness, failure bookkeeping, reason, message and the remaining job | ||
| // details written by the listener) is never read, and it is by far the noisiest | ||
| // part of the object. | ||
| func ephemeralRunnerSetOwnedEphemeralRunnerPredicate() predicate.Predicate { | ||
| return predicate.Funcs{ | ||
| UpdateFunc: func(e event.UpdateEvent) bool { | ||
| oldRunner, oldOk := e.ObjectOld.(*v1alpha1.EphemeralRunner) | ||
| newRunner, newOk := e.ObjectNew.(*v1alpha1.EphemeralRunner) | ||
| if !oldOk || !newOk { | ||
| return true | ||
| } | ||
|
|
||
| if !equalReconciledObjectMeta(&oldRunner.ObjectMeta, &newRunner.ObjectMeta) || | ||
| !equality.Semantic.DeepEqual(&oldRunner.Spec, &newRunner.Spec) { | ||
| return true | ||
| } | ||
|
|
||
| return oldRunner.Status.Phase != newRunner.Status.Phase || | ||
| oldRunner.Status.RunnerID != newRunner.Status.RunnerID || | ||
| oldRunner.Status.JobID != newRunner.Status.JobID | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // ephemeralRunnerOwnedPodPredicate filters updates of the pod owned by an | ||
| // EphemeralRunner. | ||
| // | ||
| // The EphemeralRunner reconciler reads the pod UID, its deletion timestamp, the | ||
| // pod phase, reason and message, the container and init container statuses, and | ||
| // the Ready condition. It never reads the pod spec or the remaining status | ||
| // fields, which is where most pod updates land: assigned IPs, the node the pod | ||
| // was scheduled on, start time and the conditions other than Ready. | ||
| func ephemeralRunnerOwnedPodPredicate() predicate.Predicate { | ||
| return predicate.Funcs{ | ||
| UpdateFunc: func(e event.UpdateEvent) bool { | ||
| oldPod, oldOk := e.ObjectOld.(*corev1.Pod) | ||
| newPod, newOk := e.ObjectNew.(*corev1.Pod) | ||
| if !oldOk || !newOk { | ||
| return true | ||
| } | ||
|
|
||
| if oldPod.UID != newPod.UID || | ||
| !equalTime(oldPod.DeletionTimestamp, newPod.DeletionTimestamp) { | ||
| return true | ||
| } | ||
|
|
||
| oldStatus, newStatus := &oldPod.Status, &newPod.Status | ||
| if oldStatus.Phase != newStatus.Phase || | ||
| oldStatus.Reason != newStatus.Reason || | ||
| oldStatus.Message != newStatus.Message { | ||
| return true | ||
| } | ||
|
|
||
| if !equality.Semantic.DeepEqual(oldStatus.ContainerStatuses, newStatus.ContainerStatuses) || | ||
| !equality.Semantic.DeepEqual(oldStatus.InitContainerStatuses, newStatus.InitContainerStatuses) { | ||
| return true | ||
| } | ||
|
|
||
| return podReady(oldPod) != podReady(newPod) | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // autoscalingListenerOwnedPodPredicate filters updates of the listener pod | ||
| // owned by an AutoscalingListener. | ||
| // | ||
| // The AutoscalingListener reconciler reads the pod's object metadata, because it | ||
| // merges labels and annotations back onto the pod and compares the listener | ||
| // config resource version annotation, and its whole spec, through | ||
| // listenerPodSpecRequiresRecreation. Off the status it reads only the phase, | ||
| // reason and message, to detect eviction, and the container statuses, to find | ||
| // the listener container and branch on whether it is running or terminated. The | ||
| // rest of the pod status is never read: assigned IPs, the node the pod landed | ||
| // on, start time, the conditions and the init container statuses. | ||
| func autoscalingListenerOwnedPodPredicate() predicate.Predicate { | ||
| return predicate.Funcs{ | ||
| UpdateFunc: func(e event.UpdateEvent) bool { | ||
| oldPod, oldOk := e.ObjectOld.(*corev1.Pod) | ||
| newPod, newOk := e.ObjectNew.(*corev1.Pod) | ||
| if !oldOk || !newOk { | ||
| return true | ||
| } | ||
|
|
||
| if !equalReconciledObjectMeta(&oldPod.ObjectMeta, &newPod.ObjectMeta) || | ||
| !equality.Semantic.DeepEqual(&oldPod.Spec, &newPod.Spec) { | ||
| return true | ||
| } | ||
|
|
||
| oldStatus, newStatus := &oldPod.Status, &newPod.Status | ||
| if oldStatus.Phase != newStatus.Phase || | ||
| oldStatus.Reason != newStatus.Reason || | ||
| oldStatus.Message != newStatus.Message { | ||
| return true | ||
| } | ||
|
|
||
| return !equality.Semantic.DeepEqual(oldStatus.ContainerStatuses, newStatus.ContainerStatuses) | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // autoscalingListenerOwnedServiceAccountPredicate filters updates of the service | ||
| // account owned by an AutoscalingListener. | ||
| // | ||
| // The AutoscalingListener reconciler only ever reads the service account's | ||
| // labels and annotations, which it merges back onto the object. Everything else | ||
| // the API server and the token controller write to it, the mounted secrets | ||
| // above all, is never read. | ||
| func autoscalingListenerOwnedServiceAccountPredicate() predicate.Predicate { | ||
| return predicate.Funcs{ | ||
| UpdateFunc: func(e event.UpdateEvent) bool { | ||
| oldServiceAccount, oldOk := e.ObjectOld.(*corev1.ServiceAccount) | ||
| newServiceAccount, newOk := e.ObjectNew.(*corev1.ServiceAccount) | ||
| if !oldOk || !newOk { | ||
| return true | ||
| } | ||
|
|
||
| return !equalReconciledObjectMeta(&oldServiceAccount.ObjectMeta, &newServiceAccount.ObjectMeta) | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // podReady reports whether the pod advertises the Ready condition. It is the | ||
| // single source of truth for both the reconciler, which mirrors the result into | ||
| // EphemeralRunner.Status.Ready, and the pod predicate, which has to wake the | ||
| // reconciler whenever the result changes. | ||
| func podReady(pod *corev1.Pod) bool { | ||
| var ready bool | ||
| var lastTransitionTime time.Time | ||
| for _, condition := range pod.Status.Conditions { | ||
| if condition.Type == corev1.PodReady && condition.LastTransitionTime.After(lastTransitionTime) { | ||
| ready = condition.Status == corev1.ConditionTrue | ||
| lastTransitionTime = condition.LastTransitionTime.Time | ||
| } | ||
| } | ||
| return ready | ||
| } | ||
|
|
||
| // equalReconciledObjectMeta compares the metadata fields the controllers in this | ||
| // package branch on. Bookkeeping the API server owns, such as the resource | ||
| // version, the managed fields and the timestamps outside of deletion, is | ||
| // deliberately left out. | ||
| func equalReconciledObjectMeta(old, new *metav1.ObjectMeta) bool { | ||
| return old.Generation == new.Generation && | ||
| equalTime(old.DeletionTimestamp, new.DeletionTimestamp) && | ||
| slices.Equal(old.Finalizers, new.Finalizers) && | ||
| equality.Semantic.DeepEqual(old.Labels, new.Labels) && | ||
| equality.Semantic.DeepEqual(old.Annotations, new.Annotations) && | ||
| equality.Semantic.DeepEqual(old.OwnerReferences, new.OwnerReferences) | ||
| } | ||
|
|
||
| func equalTime(old, new *metav1.Time) bool { | ||
| switch { | ||
| case old == nil && new == nil: | ||
| return true | ||
| case old == nil || new == nil: | ||
| return false | ||
| default: | ||
| return old.Equal(new) | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.