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
Reuse generic TestGet in cache tests #113427
Conversation
/hold I need to debug test failures. |
8efd0af
to
3528755
Compare
@@ -129,33 +130,40 @@ func RunTestGet(ctx context.Context, t *testing.T, store storage.Interface) { | |||
ignoreNotFound bool | |||
expectNotFoundErr bool | |||
expectRVTooLarge bool | |||
expectedOut *example.Pod |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
3528755
to
1fa799e
Compare
There was a problem hiding this 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
/assign @stevekuznetsov |
There was a problem hiding this 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" |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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:
-
the default watchcache behavior assumes a specific naming function for object (it's possible to configure it via the CacherConfig, but I think the closer to the actual code we're in tests the better)
-
for these tests it's set to:
-
https://github.com/kubernetes/kubernetes/pull/113427/files#diff-0ef268eda54e793fece7779731777cfc7cb0fb1f0d8b3954e18e8ba74f94cc07R956
[which is the same thing we're using for namespaced resources in production code] -
the function is basically used to cache objects that are read via watch from etcd (so how the cache is propagated)
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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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{}) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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{} |
There was a problem hiding this comment.
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 ...
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 ...
There was a problem hiding this comment.
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).
There was a problem hiding this 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" |
There was a problem hiding this comment.
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:
-
the default watchcache behavior assumes a specific naming function for object (it's possible to configure it via the CacherConfig, but I think the closer to the actual code we're in tests the better)
-
for these tests it's set to:
-
https://github.com/kubernetes/kubernetes/pull/113427/files#diff-0ef268eda54e793fece7779731777cfc7cb0fb1f0d8b3954e18e8ba74f94cc07R956
[which is the same thing we're using for namespaced resources in production code] -
the function is basically used to cache objects that are read via watch from etcd (so how the cache is propagated)
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" |
There was a problem hiding this comment.
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{}) { |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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{} |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
c3158e0
to
d775bea
Compare
d775bea
to
75a1ef8
Compare
There was a problem hiding this 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.
I assume we'll pass integration now that the other PR has merged. |
[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 |
/retest |
/hold cancel |
@stevekuznetsov - tests are passing now - can you tag it? |
/lgtm |
Ref #109831
This is the first step just to prove that reusing tests across implementations is feasible.
/kind cleanup
/priority important-longterm
/sig api-machinery