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

Reuse generic TestGet in cache tests #113427

Merged

Conversation

wojtek-t
Copy link
Member

Ref #109831

This is the first step just to prove that reusing tests across implementations is feasible.

NONE

/kind cleanup
/priority important-longterm
/sig api-machinery

@k8s-ci-robot k8s-ci-robot added release-note-none Denotes a PR that doesn't merit a release note. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. priority/important-longterm Important over the long term, but may not be staffed and/or may need multiple releases to complete. sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. labels Oct 28, 2022
@k8s-ci-robot k8s-ci-robot added approved Indicates a PR has been approved by an approver from all required OWNERS files. area/apiserver labels Oct 28, 2022
@wojtek-t
Copy link
Member Author

/hold

I need to debug test failures.

@k8s-ci-robot k8s-ci-robot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Oct 28, 2022
@wojtek-t wojtek-t force-pushed the reuse_generic_tests_for_cacher branch from 8efd0af to 3528755 Compare October 28, 2022 13:46
@@ -129,33 +130,40 @@ func RunTestGet(ctx context.Context, t *testing.T, store storage.Interface) {
ignoreNotFound bool
expectNotFoundErr bool
expectRVTooLarge bool
expectedOut *example.Pod
Copy link
Contributor

Choose a reason for hiding this comment

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

What was forcing this change?

Copy link
Member Author

Choose a reason for hiding this comment

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

The change to a slice is described below - the "NotOlderThan" semantic is making some tests to have multiple potentially correct results.

The change to interface{} was to allow passing a generic thing to the newly created ExpectNoDiffWithOne function.
I reverted that and added a helper function for conversion instead.

@wojtek-t wojtek-t force-pushed the reuse_generic_tests_for_cacher branch from 3528755 to 1fa799e Compare October 31, 2022 16:42
Copy link
Member Author

@wojtek-t wojtek-t left a comment

Choose a reason for hiding this comment

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

@stevekuznetsov - thanks for the review, PTAL

@fedebongio
Copy link
Contributor

/assign @stevekuznetsov
/triage accepted

@k8s-ci-robot k8s-ci-robot added triage/accepted Indicates an issue or PR is ready to be actively worked on. and removed needs-triage Indicates an issue or PR lacks a `triage/foo` label and requires one. labels Nov 1, 2022
Copy link
Contributor

@stevekuznetsov stevekuznetsov left a comment

Choose a reason for hiding this comment

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

Couple small comments, but mostly LGTM. Love that by using the generic TestGet we gain so many more test cases for the cache layer.

@@ -1663,7 +1683,7 @@ func RunTestConsistentList(ctx context.Context, t *testing.T, store InterfaceWit
}

func RunTestGuaranteedUpdate(ctx context.Context, t *testing.T, store InterfaceWithPrefixTransformer, validation KeyValidation) {
key := "/testkey"
Copy link
Contributor

Choose a reason for hiding this comment

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

What did this change do, and why did we need it?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good question. So basically what happened is what:

So if we end-up using "key" (that is used for create/update/delete operations) that is different than what the KeyFunc in watchcache is computing for objects, it will result in get/list/watch operations to not work correctly.

So I basically switched the key (here and in some other places) to be consistent with what we're actually really computing in production code.

[I don't think it actually decreases the usefullness of the test, so it seems fine.]

@@ -1801,7 +1821,7 @@ func RunTestGuaranteedUpdate(ctx context.Context, t *testing.T, store InterfaceW
}

// verify that kv pair is not empty after set and that the underlying data matches expectations
validation(ctx, t, key)
Copy link
Contributor

Choose a reason for hiding this comment

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

What did this change do, and why did we need it?

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah...

So basically it's the same problem as described above.
For etcd3 implementation the implementation is doing it itself:
https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go#L127

However, after I changed the key generation in TestPropagateStore, it started returning "//foo".
And the difference is the path.Join("/", "/testkey") is idempotent, but path.Join("/", "//testkey") is not (it returns "/test/key")

But now looking into it, I think much cleaner way of doing that is to fix the etcd3-specific implementation to take that into account. Fixed there instead.

@@ -86,7 +86,7 @@ func DeepEqualSafePodSpec() example.PodSpec {
// keys and stored objects.
func TestPropagateStore(ctx context.Context, t *testing.T, store storage.Interface, obj *example.Pod) (string, *example.Pod) {
// Setup store with a key and grab the output for returning.
key := "/testkey"
Copy link
Contributor

Choose a reason for hiding this comment

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

What did this change do, and why did we need it?

Copy link
Contributor

Choose a reason for hiding this comment

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

(at this interface level the key just needs to be self-consistent, right? but I agree having it follow k8s semantics makes working with this code way nicer/more readable)

Copy link
Member Author

Choose a reason for hiding this comment

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

See my answer to your other comment above - it should explain it well.

Copy link
Member Author

Choose a reason for hiding this comment

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

FWIW - the failure of the integration test is expected now - it should self-resolve once: #113369 is merged

[I don't want to change it in this PR to potentially allow clean-cherrypick of the PR mentioned above.]

@@ -115,6 +115,16 @@ func ExpectNoDiff(t *testing.T, msg string, expected, got interface{}) {
}
}

func ExpectNoDiffWithOne(t *testing.T, msg string, expectedList []interface{}, got interface{}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: would prefer a name like ExpectContains or something more familiar to users of e.g. sets API

Copy link
Member Author

Choose a reason for hiding this comment

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

I like it - changed.

return
}
}
t.Errorf("%s: differs from all items, first: %s", msg, cmp.Diff(expectedList[0], got))
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: when I moved the rest of the tests to use cmp.Diff, it was asked to check that cmp.Diff() actually produced a diff when reflect.DeepEqual() == false, and if not, to fall back to the previous impl, see ExpectNoDiff - probably want the same here

Copy link
Member Author

Choose a reason for hiding this comment

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

changed - PTAL

if err := cacher.Get(context.TODO(), "pods/ns/bar", storage.GetOptions{ResourceVersion: fooCreated.ResourceVersion, IgnoreNotFound: true}, result); err != nil {
t.Errorf("Unexpected error: %v", err)
}
emptyPod := example.Pod{}
Copy link
Contributor

Choose a reason for hiding this comment

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

Having a hard time following this test. Is the use of resourceVersion in GET options simply a red herring? We're asking for a different key ...

Copy link
Member Author

Choose a reason for hiding this comment

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

no - we're passing IgnoreFound=true and checking if really the empty object is returned.

Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't that independent of passing RV here though? We are not asking for the wrong revision of an object, there is no bar pod at all ...

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah - it is independent from RV - the only case is that we're setting RV=0 to force getting from cache (as opposed to listing from RV="" which is passed down to etcd).

Copy link
Member Author

@wojtek-t wojtek-t left a comment

Choose a reason for hiding this comment

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

@stevekuznetsov - thanks for comments; PTAL

@@ -1663,7 +1683,7 @@ func RunTestConsistentList(ctx context.Context, t *testing.T, store InterfaceWit
}

func RunTestGuaranteedUpdate(ctx context.Context, t *testing.T, store InterfaceWithPrefixTransformer, validation KeyValidation) {
key := "/testkey"
Copy link
Member Author

Choose a reason for hiding this comment

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

Good question. So basically what happened is what:

So if we end-up using "key" (that is used for create/update/delete operations) that is different than what the KeyFunc in watchcache is computing for objects, it will result in get/list/watch operations to not work correctly.

So I basically switched the key (here and in some other places) to be consistent with what we're actually really computing in production code.

[I don't think it actually decreases the usefullness of the test, so it seems fine.]

@@ -86,7 +86,7 @@ func DeepEqualSafePodSpec() example.PodSpec {
// keys and stored objects.
func TestPropagateStore(ctx context.Context, t *testing.T, store storage.Interface, obj *example.Pod) (string, *example.Pod) {
// Setup store with a key and grab the output for returning.
key := "/testkey"
Copy link
Member Author

Choose a reason for hiding this comment

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

See my answer to your other comment above - it should explain it well.

@@ -115,6 +115,16 @@ func ExpectNoDiff(t *testing.T, msg string, expected, got interface{}) {
}
}

func ExpectNoDiffWithOne(t *testing.T, msg string, expectedList []interface{}, got interface{}) {
Copy link
Member Author

Choose a reason for hiding this comment

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

I like it - changed.

return
}
}
t.Errorf("%s: differs from all items, first: %s", msg, cmp.Diff(expectedList[0], got))
Copy link
Member Author

Choose a reason for hiding this comment

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

changed - PTAL

if err := cacher.Get(context.TODO(), "pods/ns/bar", storage.GetOptions{ResourceVersion: fooCreated.ResourceVersion, IgnoreNotFound: true}, result); err != nil {
t.Errorf("Unexpected error: %v", err)
}
emptyPod := example.Pod{}
Copy link
Member Author

Choose a reason for hiding this comment

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

no - we're passing IgnoreFound=true and checking if really the empty object is returned.

@@ -1801,7 +1821,7 @@ func RunTestGuaranteedUpdate(ctx context.Context, t *testing.T, store InterfaceW
}

// verify that kv pair is not empty after set and that the underlying data matches expectations
validation(ctx, t, key)
Copy link
Member Author

Choose a reason for hiding this comment

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

yeah...

So basically it's the same problem as described above.
For etcd3 implementation the implementation is doing it itself:
https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/storage/etcd3/store.go#L127

However, after I changed the key generation in TestPropagateStore, it started returning "//foo".
And the difference is the path.Join("/", "/testkey") is idempotent, but path.Join("/", "//testkey") is not (it returns "/test/key")

But now looking into it, I think much cleaner way of doing that is to fix the etcd3-specific implementation to take that into account. Fixed there instead.

@wojtek-t wojtek-t force-pushed the reuse_generic_tests_for_cacher branch 2 times, most recently from c3158e0 to d775bea Compare November 2, 2022 13:48
@wojtek-t wojtek-t force-pushed the reuse_generic_tests_for_cacher branch from d775bea to 75a1ef8 Compare November 2, 2022 15:37
Copy link
Contributor

@stevekuznetsov stevekuznetsov left a comment

Choose a reason for hiding this comment

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

Cool! This is a great proof for the concept here.

@stevekuznetsov
Copy link
Contributor

I assume we'll pass integration now that the other PR has merged.

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: stevekuznetsov, wojtek-t

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

@wojtek-t
Copy link
Member Author

wojtek-t commented Nov 2, 2022

/retest

@wojtek-t
Copy link
Member Author

wojtek-t commented Nov 2, 2022

/hold cancel

@k8s-ci-robot k8s-ci-robot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Nov 2, 2022
@wojtek-t
Copy link
Member Author

wojtek-t commented Nov 2, 2022

@stevekuznetsov - tests are passing now - can you tag it?

@stevekuznetsov
Copy link
Contributor

/lgtm
:shipit:

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Nov 2, 2022
@k8s-ci-robot k8s-ci-robot merged commit de95671 into kubernetes:master Nov 2, 2022
@k8s-ci-robot k8s-ci-robot added this to the v1.26 milestone Nov 2, 2022
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. area/apiserver cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. lgtm "Looks good to me", indicates that a PR is ready to be merged. priority/important-longterm Important over the long term, but may not be staffed and/or may need multiple releases to complete. release-note-none Denotes a PR that doesn't merit a release note. sig/api-machinery Categorizes an issue or PR as relevant to SIG API Machinery. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. triage/accepted Indicates an issue or PR is ready to be actively worked on.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants