Skip to content
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

🌱 inmemory: fix watch to continue serving based on resourceVersion parameter #11695

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

chrischdi
Copy link
Member

What this PR does / why we need it:

  • Watches on remote cluster expires every 10s #8893 leads to watches getting recreated every 10s
  • When using the inmemory provider, changes during recreation of a watch got lost
    • e.g. Node got created in the simulated cluster
    • e.g. Pd got created in the simulated cluster
  • This caused the controllers to require reaching the sync period (or e.g. in the Node missing case, MHC kicked in at 10 mins and the machine got recreated)
  • This lead to e.g. longer taking scale tests due to e.g. machines not getting ready because an event got missed

This PR has the following changes:

  • Change the way in-memory provider sets resourceVersions, to have one increasing resourceVersion per simulated cluster (similar to what k8s is doing)
  • use the resourceVersion parameter on watches (if given) to enqueue events for all objects which have a higher resourceVersion

Which issue(s) this PR fixes (optional, in fixes #<issue number>(, fixes #<issue_number>, ...) format, will close the issue(s) when PR gets merged):
Fixes #

/area provider/infrastructure-in-memory

@chrischdi chrischdi added the tide/merge-method-squash Denotes a PR that should be squashed by tide when it merges. label Jan 17, 2025
@k8s-ci-robot k8s-ci-robot added area/provider/infrastructure-in-memory Issues or PRs related to the in-memory infrastructure provider cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Jan 17, 2025
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please ask for approval from chrischdi. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@chrischdi chrischdi force-pushed the pr-inmemory-fix-watch-resourceversion branch from 54e7e43 to 1b644a2 Compare January 17, 2025 08:56
@chrischdi
Copy link
Member Author

Kudos to @sbueringer for helping analysing and brainstorming to find the root cause.

@chrischdi
Copy link
Member Author

/test help

@k8s-ci-robot
Copy link
Contributor

@chrischdi: The specified target(s) for /test were not found.
The following commands are available to trigger required jobs:

/test pull-cluster-api-build-main
/test pull-cluster-api-e2e-blocking-main
/test pull-cluster-api-e2e-conformance-ci-latest-main
/test pull-cluster-api-e2e-conformance-main
/test pull-cluster-api-e2e-latestk8s-main
/test pull-cluster-api-e2e-main
/test pull-cluster-api-e2e-mink8s-main
/test pull-cluster-api-e2e-upgrade-1-32-1-33-main
/test pull-cluster-api-test-main
/test pull-cluster-api-test-mink8s-main
/test pull-cluster-api-verify-main

The following commands are available to trigger optional jobs:

/test pull-cluster-api-apidiff-main

Use /test all to run the following jobs that were automatically triggered:

pull-cluster-api-apidiff-main
pull-cluster-api-build-main
pull-cluster-api-e2e-blocking-main
pull-cluster-api-test-main
pull-cluster-api-verify-main

In response to this:

/test help

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@chrischdi
Copy link
Member Author

/test pull-cluster-api-e2e-main

@chrischdi chrischdi force-pushed the pr-inmemory-fix-watch-resourceversion branch from 1b644a2 to 3bac618 Compare January 17, 2025 13:18
@chrischdi
Copy link
Member Author

/test pull-cluster-api-e2e-main

@@ -139,6 +198,16 @@ func (m *WatchEventDispatcher) Run(ctx context.Context, timeout string, w http.R
}
w.Header().Set("Transfer-Encoding", "chunked")
w.WriteHeader(http.StatusOK)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it okay that we don't flush after this like before?

// Write all initial events.
for _, event := range initialEvents {
if err := resp.WriteEntity(event); err != nil {
log.Error(err, "Writing old event", "eventType", event.Type, "objectName", event.Object.GetName(), "resourceVersion", event.Object.GetResourceVersion())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
log.Error(err, "Writing old event", "eventType", event.Type, "objectName", event.Object.GetName(), "resourceVersion", event.Object.GetResourceVersion())
log.Error(err, "Error writing old event", "eventType", event.Type, "objectName", event.Object.GetName(), "resourceVersion", event.Object.GetResourceVersion())

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about using "initial event" instead of "old event"?

if err := resp.WriteEntity(event); err != nil {
log.Error(err, "Writing event", "eventType", event.Type, "objectName", event.Object.GetName(), "resourceVersion", event.Object.GetResourceVersion())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
log.Error(err, "Writing event", "eventType", event.Type, "objectName", event.Object.GetName(), "resourceVersion", event.Object.GetResourceVersion())
log.Error(err, "Error writing event", "eventType", event.Type, "objectName", event.Object.GetName(), "resourceVersion", event.Object.GetResourceVersion())

Copy link
Member

@fabriziopandini fabriziopandini left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job in investigating and fixing this issue, kudos!

@@ -145,6 +145,9 @@ func (c *cache) List(resourceGroup string, list client.ObjectList, opts ...clien
if err := meta.SetList(list, items); err != nil {
return apierrors.NewInternalError(err)
}

list.SetResourceVersion(fmt.Sprintf("%d", tracker.lastResourceVersion))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: is it correct that list ResourceVersion in the system and not the last resource version in the list of items?
Q: is is correct set set this value non matter of it is a "plain" list or a lis watch?
(from a quick check with kubectl yes to both, but I like a confirmation)

}
}

func (h *apiServerHandler) apiV1list(ctx context.Context, req *restful.Request, gvk schema.GroupVersionKind, inmemoryClient inmemoryclient.Client) (*unstructured.UnstructuredList, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be

Suggested change
func (h *apiServerHandler) apiV1list(ctx context.Context, req *restful.Request, gvk schema.GroupVersionKind, inmemoryClient inmemoryclient.Client) (*unstructured.UnstructuredList, error) {
func (h *apiServerHandler) v1List(ctx context.Context, req *restful.Request, gvk schema.GroupVersionKind, inmemoryClient inmemoryclient.Client) (*unstructured.UnstructuredList, error) {

(easier to distinguish from apiV1List with upper L)

// Write all initial events.
for _, event := range initialEvents {
if err := resp.WriteEntity(event); err != nil {
log.Error(err, "Writing old event", "eventType", event.Type, "objectName", event.Object.GetName(), "resourceVersion", event.Object.GetResourceVersion())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about using "initial event" instead of "old event"?

// Determine the highest written resourceVersion so we can filter out duplicated events from the channel.
minResourceVersion := uint64(0)
if len(initialEvents) > 0 {
minResourceVersion, err = strconv.ParseUint(initialEvents[len(initialEvents)-1].Object.GetResourceVersion(), 10, 64)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of assuming an ordering of events by resource version (which I'm not sure we are enforcing somewhere), what about computing minResourceVersion when we go through initialEvents in the for loop above

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/provider/infrastructure-in-memory Issues or PRs related to the in-memory infrastructure provider cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. tide/merge-method-squash Denotes a PR that should be squashed by tide when it merges.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants