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

mockkubeapiserver: Refactor storage to be pluggable #355

Merged
merged 1 commit into from
Oct 18, 2023

Conversation

justinsb
Copy link
Contributor

This helps if we want e.g. to capture all the objects easily.

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Sep 24, 2023
@k8s-ci-robot k8s-ci-robot added approved Indicates a PR has been approved by an approver from all required OWNERS files. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Sep 24, 2023
@justinsb justinsb force-pushed the refactor_storage branch 5 times, most recently from c317b37 to b6aa657 Compare October 18, 2023 01:51
This helps if we want e.g. to capture all the objects easily.
Copy link
Contributor

@yuwenma yuwenma left a comment

Choose a reason for hiding this comment

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

Since we found a data race in the previous memorystorage, can we try go test -race and validate the new change passes the data race? (I think so because we keep the storage mutex in each CRUD method)

return fmt.Errorf("status was of unexpected type %T", statusObj)
}

generation := u.GetGeneration()
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious, why do we want to setup the generation here?
I saw it is optional and in k-d-p there seems to be also no specific logic around generation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good question :-) I think I added it because otherwise kstatus will not consider the deployment ready (which makes our golden tests unhappy / means that they don't converge)

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, makes sense.

// These changes seem to be done synchronously (similar to a mutating webhook)
labels := u.GetLabels()
name := u.GetName()
if labels["kubernetes.io/metadata.name"] != name {
Copy link
Contributor

Choose a reason for hiding this comment

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

good to learn this label!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I think it is legacy, but ... it's there. I think we prefer field selectors now: https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors/

Only a handful of fieldSelectors are implemented for some objects (though I think it would be cool if we supported arbitrary CEL expressions). fieldSelector for metadata.name is implemented everywhere I believe, because that is how we can watch one object (you can only pass the watch parameter to LIST requests, IIRC)

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: justinsb, yuwenma

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

The pull request process is described 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

@@ -0,0 +1,33 @@
package storage
Copy link
Contributor

Choose a reason for hiding this comment

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

I like this style in storage, and the hook (I misread the rationale of Hook until seeing the "CRDHook")

// UpdateCRD is called whenever a CRD is updated.
// We register the types from the CRD.
func (s *MemoryStorage) UpdateCRD(ev *storage.WatchEvent) error {
// TODO: Deleted / changed CRDs
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the expected behavior of "delete CRD". Is this for the case when CRD belongs to a versioned manifest?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If a CRD is deleted, we should unregister it from the apiserver, so it isn't discoverable, you can't query it etc. That's ... not implemented yet!

}

type resourceVersionClock struct {
now atomic.Int64
Copy link
Contributor

Choose a reason for hiding this comment

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

I missed the atomic in the previous review. This is cool.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, in general it's premature optimization vs sync.Mutex, but here I find it more ergonomic (as well as being possibly more efficient)

}
}

ev := storage.BuildWatchEvent(gvk, "ADDED", obj)
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to make sure I understand the code correctly: this "ADDED" is a no-op and here we mainly use the watchCallback (next line), right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the "real" watch code, the hooks are a sort of "fast-path built-in watch". When you start a watch with kube-apiserver with resourceVersion unspecified, you get a synthetic ADDED event for every object that exists at that time.

It's actually a streaming LIST (which is otherwise missing from kube), and a little more efficient on the client (at least) in terms of memory, because you only have to process one object at a time, instead of a whole page of objects. The downside is that it's hard to tell when the initial LIST is over, and you have all the objects. In the past few versions of kube-apiserver, they added a bookmark notification when the initial list was completed, which is how you can tell. Maybe I should add bookmark notifications :-) More info here: https://github.com/kubernetes/enhancements/blob/master/keps/sig-api-machinery/3157-watch-list/README.md

@yuwenma
Copy link
Contributor

yuwenma commented Oct 18, 2023

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Oct 18, 2023
@k8s-ci-robot k8s-ci-robot merged commit 96dd7e1 into kubernetes-sigs:master Oct 18, 2023
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants