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

Avoid double decoding all client responses #36001

Merged

Conversation

smarterclayton
Copy link
Contributor

@smarterclayton smarterclayton commented Nov 1, 2016

Fixes #35982

The linked issue uncovered that we were always double decoding the response in restclient for get, list, update, create, and patch. That's fairly expensive, most especially for list. This PR refines the behavior of the rest client to avoid double decoding, and does so while minimizing the changes to rest client consumers.

restclient must be able to deal with multiple types of servers. Alter the behavior of restclient.Result#Raw() to not process the body on error, but instead to return the generic error (which still matches the error checking cases in api/error like IsBadRequest). If the caller uses
.Error(), .Into(), or .Get(), try decoding the body as a Status.

For older servers, continue to default apiVersion "v1" when calling restclient.Result#Error(). This was only for 1.1 servers and the extensions group, which we have since fixed.

This removes a double decode of very large objects (like LIST) - we were trying to DecodeInto status, but that ends up decoding the entire result and then throwing it away. This makes the decode behavior specific to the type of action the user wants.

The error handling behavior of `pkg/client/restclient.Result` has changed.  Calls to `Result.Raw()` will no longer parse the body, although they will still return errors that react to `pkg/api/errors.Is*()` as in previous releases.  Callers of `Get()` and `Into()` will continue to receive errors that are parsed from the body if the kind and apiVersion of the body match the `Status` object.

This more closely aligns rest client as a generic RESTful client, while preserving the special Kube API extended error handling for the `Get` and `Into` methods (which most Kube clients use).

This change is Reviewable

@smarterclayton smarterclayton added priority/important-soon Must be staffed and worked on either currently, or very soon, ideally in time for the next release. sig/scalability Categorizes an issue or PR as relevant to SIG Scalability. labels Nov 1, 2016
@smarterclayton
Copy link
Contributor Author

@kubernetes/sig-api-machinery @wojtek-t this doubles client read performance (even versioned clients).

@smarterclayton smarterclayton added the release-note Denotes a PR that will be considered when it comes time to generate release notes. label Nov 1, 2016
@k8s-github-robot k8s-github-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Nov 1, 2016
@smarterclayton
Copy link
Contributor Author

I added a release note that describes how error handling has changed - all calls to errors.IsNotFound/* should be the same for Raw, but the body will no longer be parsed unless clients explicitly ask for it. I did a quick review of all the places where we call Raw and didn't find any places that could be broken by this (/healthz, /version, /metrics, /swagger, and test metrics loads)

@k8s-ci-robot
Copy link
Contributor

Jenkins GCE e2e failed for commit 88ef1a8b5bd2fbe229aee818cc2addcbab084b8e. Full PR test history.

The magic incantation to run this job again is @k8s-bot cvm gce e2e test this. Please help us cut down flakes by linking to an open flake issue when you hit one in your PR.

@k8s-ci-robot
Copy link
Contributor

Jenkins GCI GCE e2e failed for commit 88ef1a8b5bd2fbe229aee818cc2addcbab084b8e. Full PR test history.

The magic incantation to run this job again is @k8s-bot gci gce e2e test this. Please help us cut down flakes by linking to an open flake issue when you hit one in your PR.

@k8s-ci-robot
Copy link
Contributor

Jenkins unit/integration failed for commit 4836d5ed3baa2edf979b113a7803444f929944b5. Full PR test history.

The magic incantation to run this job again is @k8s-bot unit test this. Please help us cut down flakes by linking to an open flake issue when you hit one in your PR.

// See the Request.Do() comment for what errors you might get.
func (r Result) Error() error {
// if we have received an unexpected server error, and we have a body and decoder, we can try to extract
// a Status object.
if r.err == nil || !errors.IsUnexpectedServerError(r.err) || len(r.body) == 0 || r.decoder == nil {
Copy link
Member

Choose a reason for hiding this comment

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

Why IsUnexpectedServerError? I think we should just check if r.err implements APIStatus interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, the error in this case is set by the thing that creates Result (which calls newGeneric...Error, which is UnexpectedServerError).

UnexpectedServerError literally means - we got an error from the server but we couldn't / didn't decode the body. So this code is looking for errors where before we didn't try to decode it, and try to decode it.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, the logic is correct. The name UnexpectedServerError is misleading IMO, the name makes sense for the "couldn't decode the body" case, but doesn't make sense for the "didn't decode the body" case.

},
},
{
// successful status is ignored
Copy link
Member

Choose a reason for hiding this comment

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

When will this happen in real life?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Delete returns a status object, as do a few others.

@caesarxuchao
Copy link
Member

LGTM. @deads2k if you want to take another look.

@smarterclayton
Copy link
Contributor Author

Found a bug - authorization didn't register Status. Is authorization new for 1.5 or did we also fail to register it in 1.4?

@liggitt
Copy link
Member

liggitt commented Nov 2, 2016

Was in 1.4

@smarterclayton
Copy link
Contributor Author

I'm going to fix it in 1.5 but that means 1.4 clients can't get Status messages from subject access review. Was it alpha?

@smarterclayton
Copy link
Contributor Author

Ok, that was fun.

  1. ExportOptions wasn't registered in all groups - fixed that.
  2. Added a test to verify ExportOptions was in all groups and could decode / encode correctly
  3. Authorization wasn't registered in testapi
  4. Found a bug with how we treat Status - it's projected into all groups as v1, when it really should be the group version. If you called runtime.ConvertToVersion with "your group version" it would fail to convert a v1.Status response into unversioned.Status. Added a case to convertToVersion to bandaid over this until I add the server api group version and rip out unversioned.

Ready for re-review. No backwards compatibility impact now.

@k8s-ci-robot
Copy link
Contributor

Jenkins GCE Node e2e failed for commit 40220c917efec57c4384f4c4850d6f76b666bd5c. Full PR test history.

The magic incantation to run this job again is @k8s-bot node e2e test this. Please help us cut down flakes by linking to an open flake issue when you hit one in your PR.

@k8s-ci-robot
Copy link
Contributor

Jenkins verification failed for commit 40220c917efec57c4384f4c4850d6f76b666bd5c. Full PR test history.

The magic incantation to run this job again is @k8s-bot verify test this. Please help us cut down flakes by linking to an open flake issue when you hit one in your PR.

restclient must be able to deal with multiple types of servers. Alter
the behavior of restclient.Result#Raw() to not process the body on
error, but instead to return the generic error (which still matches the
error checking cases in api/error like IsBadRequest). If the caller uses
.Error(), .Into(), or .Get(), try decoding the body as a Status.

For older servers, continue to default apiVersion "v1" when calling
restclient.Result#Error(). This was only for 1.1 servers and the
extensions group, which we have since fixed.

This removes a double decode of very large objects (like LIST).
Status is exposed as v1 in the current schema (so all groups are
returning v1.Status).  However, if you give a codec only "mygroup"
"myversion", it will fail to convert Status to v1. For now, unversioned
types should be allowed to be projected into all group versions, and
when we add the server group we'll rip out the unversioned concept
entirely.
@@ -279,6 +290,7 @@ func init() {
Rbac = Groups[rbac.GroupName]
Storage = Groups[storage.GroupName]
ImagePolicy = Groups[imagepolicy.GroupName]
Authorization = Groups[authorization.GroupName]
Copy link
Contributor

Choose a reason for hiding this comment

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

If nobody uses this, don't add it. We want less code to touch for each group you add, not more.

@@ -258,6 +260,15 @@ func init() {
externalTypes: api.Scheme.KnownTypes(externalGroupVersion),
}
}
if _, ok := Groups[authorization.GroupName]; !ok {
Copy link
Contributor

Choose a reason for hiding this comment

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

It'd be nice to have a unit test somewhere that makes sure this parallel registration stays in sync.

@deads2k
Copy link
Contributor

deads2k commented Nov 2, 2016

nits, lgtm.

@smarterclayton
Copy link
Contributor Author

I need it to properly test authorization implements ExportOptions, which is
what this issue uncovered.

On Wed, Nov 2, 2016 at 7:53 AM, David Eads notifications@github.com wrote:

@deads2k commented on this pull request.

In pkg/api/testapi/testapi.go
#36001 (review)
:

@@ -279,6 +290,7 @@ func init() {
Rbac = Groups[rbac.GroupName]
Storage = Groups[storage.GroupName]
ImagePolicy = Groups[imagepolicy.GroupName]

  • Authorization = Groups[authorization.GroupName]

If nobody uses this, don't add it. We want less code to touch for each
group you add, not more.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#36001 (review),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABG_p89wRSx18Qsc9yIqaCAFfpyZ5kZBks5q6Hm2gaJpZM4Kmjnm
.

@smarterclayton
Copy link
Contributor Author

I agree. Do you have an issue that covers your concerns with testapi that
I can add to?

On Wed, Nov 2, 2016 at 7:54 AM, David Eads notifications@github.com wrote:

@deads2k commented on this pull request.

In pkg/api/testapi/testapi.go
#36001 (review)
:

@@ -258,6 +260,15 @@ func init() {
externalTypes: api.Scheme.KnownTypes(externalGroupVersion),
}
}

  • if _, ok := Groups[authorization.GroupName]; !ok {

It'd be nice to have a unit test somewhere that makes sure this parallel
registration stays in sync.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#36001 (review),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABG_p3Ovqif8TFW6SW325KaNQWv3qq1wks5q6HncgaJpZM4Kmjnm
.

@smarterclayton
Copy link
Contributor Author

As per comments

/lgtm

On Wed, Nov 2, 2016 at 11:48 AM, Clayton Coleman ccoleman@redhat.com
wrote:

I agree. Do you have an issue that covers your concerns with testapi that
I can add to?

On Wed, Nov 2, 2016 at 7:54 AM, David Eads notifications@github.com
wrote:

@deads2k commented on this pull request.

In pkg/api/testapi/testapi.go
#36001 (review)
:

@@ -258,6 +260,15 @@ func init() {
externalTypes: api.Scheme.KnownTypes(externalGroupVersion),
}
}

  • if _, ok := Groups[authorization.GroupName]; !ok {

It'd be nice to have a unit test somewhere that makes sure this parallel
registration stays in sync.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#36001 (review),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABG_p3Ovqif8TFW6SW325KaNQWv3qq1wks5q6HncgaJpZM4Kmjnm
.

@deads2k
Copy link
Contributor

deads2k commented Nov 2, 2016

I agree. Do you have an issue that covers your concerns with testapi that
I can add to?

Old PR trying to remove the package entirely: #34644 . I'm still not sure that I'm convinced of the value it adds. I thought new groups would be able to ignore the package, so I didn't press on it.

@smarterclayton smarterclayton added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Nov 2, 2016
@caesarxuchao
Copy link
Member

ExportOptions wasn't registered in all groups - fixed that.

Can you add ExportOptions to this line to test it's registered in all groups?

@caesarxuchao
Copy link
Member

Sorry, I saw you did that :)

@caesarxuchao
Copy link
Member

Fix #27886.

@k8s-github-robot
Copy link

Automatic merge from submit-queue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lgtm "Looks good to me", indicates that a PR is ready to be merged. priority/important-soon Must be staffed and worked on either currently, or very soon, ideally in time for the next release. release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/scalability Categorizes an issue or PR as relevant to SIG Scalability. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

pkg/client/restclient/request.go should not double decode into fake Status
7 participants