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
Fix Content negotiation incorrect when Accept header uses type parame… #50603
Conversation
@@ -282,10 +310,16 @@ func NegotiateMediaTypeOptions(header string, accepted []AcceptedMediaType, endp | |||
clause.Type == "*" && clause.SubType == "*": | |||
// TODO: should we prefer the first type with no unrecognized options? Do we need to ignore unrecognized |
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 comment can go , can't 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.
oh, now I got it, thank you, yes, will do that.
@@ -273,19 +300,29 @@ func NegotiateMediaTypeOptions(header string, accepted []AcceptedMediaType, endp | |||
} | |||
|
|||
clauses := goautoneg.ParseAccept(header) | |||
candidateMediaTypeList := []candidateMediaType{} |
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.
make this a var candidates []candidateMediaType
array so that an allocation is not required in all cases.
return retVal, true | ||
} | ||
//remove element | ||
candidateMediaTypeList = append(candidateMediaTypeList[:index], candidateMediaTypeList[index+1:]...) |
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.
It would be better to swap index with the last item, then truncate the array by one. This code will be called on every request and some efficiency matters.
} | ||
|
||
func mostSpecificMediaType(l []candidateMediaType) (candidateMediaType, int) { | ||
max := struct { |
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.
don't use a struct for this when two variables are sufficient
clauses goautoneg.Accept | ||
} | ||
|
||
func mostSpecificMediaType(l []candidateMediaType) (candidateMediaType, int) { |
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.
just return int, returning a struct is not necessary.
51b6e05
to
e245a78
Compare
/retest |
@smarterclayton updated, ptal
I'm not quit understand that delegation in the loop things, do you mean using delegation pattern will make code more elegant ? |
I don't remember, may have edited the comment after reading the code more. |
return retVal, true | ||
} | ||
// swap index with the last item, then truncate the array by one. | ||
candidates[index], candidates[len(candidates)-1] = candidates[len(candidates)-1], candidates[index] |
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.
oops, "swap" was incorrect. You can take the last array item and assign it to the current location. Then truncate.
If we don't accept index
, then we will never accept index and so it doesn't need to be preserved.
@smarterclayton updated, how about this ? |
@smarterclayton @sttts mind take a look ? |
@smarterclayton can you take another look? |
func (emptyEndpointRestrictions) AllowsConversion(gvk schema.GroupVersionKind) bool { | ||
if gvk.GroupVersion() == metav1alpha1.SchemeGroupVersion { | ||
switch gvk.Kind { | ||
case "Table", "PartialObjectMetadata", "PartialObjectMetadataList": |
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.
hmm, I didn't expect to see specific types 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.
actually, isn't the point of emptyEndpointRestrictions to avoid matching any specific type request?
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.
Yes
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 think the reason I add specific types here is same as here: (a temporary solution )
func (scope *RequestScope) AllowsConversion(gvk schema.GroupVersionKind) bool { |
kubernetes/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/negotiation/negotiate_test.go
Line 210 in 46648a5
"Accept": []string{"application/json, text/plain;as=PartialObjectMetadataList;v=v1alpha1;g=meta.k8s.io"}, |
application/json;as=PartialObjectMetadataList;v=v1alpha1;g=meta.k8s.io
the unit test will always pick the short one, since acceptMediaTypeOptions
always return false
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.
@liggitt do you have any suggestions ?
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 linked unit test should be selecting application/json. In the absence of qualifiers, the first matching, valid accept header should be used
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 function you linked should only return true if the endpoint supports those conversions (all objects support partial object meta, only endpoints that have a TableConvertor should support the table object).
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 emptyEndpointRestrictions is used in both NegotiateOutputSerializer
and NegotiateOutputStreamSerializer
.
NegotiateOutputSerializer
is called in the discovery endpoints. They don't support Table Convertor. The types served don't have ObjectMeta at all, so the original code is more appropriate.NegotiateOutputStreamSerializer
is called by the watch handler. Shouldn't the watch handler instead get the endpoint restrictions from the scope? @smarterclayton
@shiywang As for the unit test, I think you can add a test testing NegotiateOutputMediaType
, and add custom endpoint restrictions in your test matrix.
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.
Empty should not support anything. Both restrictions should be provided by the scope, and the underlying type has to support the rules for partial metadata. Only types that implement the meta.Object
interface are candidates for PartialObjectMetadata. Only storage implementations with ConvertToTable should support TableOutput.
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.
@shiywang As for the unit test, I think you can add a test testing NegotiateOutputMediaType, and add custom endpoint restrictions in your test matrix.
Thanks @caesarxuchao I will add a custom endpoint restriction and fall back to original code of emptyEndpointRestrictions
The linked unit test should be selecting application/json. In the absence of qualifiers, the first matching, valid accept header should be used
Why the first matching, shouldn't be most specific matching? left a question here.
Adding do-not-merge/release-note-label-needed because the release note process has not been followed. |
This PR hasn't been active in 60 days. It will be closed in 29 days (Nov 28, 2017). cc @caesarxuchao @deads2k @shiywang You can add 'keep-open' label to prevent this from happening, or add a comment to keep it open another 90 days |
/test pull-kubernetes-unit |
This PR has not been updated in several days, and does not appear to be a blocker issue. As such, it is being downgraded to pause it out of 1.9.0. If this is in error, please update the issue, approve and merge it in the next 16 hours. Thanks! /priority important-soon |
/remove-priority critical-urgent |
Updating per SIG: /remove-priority important-soon /priority critical-urgent |
[MILESTONENOTIFIER] Milestone Pull Request Current @caesarxuchao @deads2k @shiywang @smarterclayton Note: This pull request is marked as Example update:
Pull Request Labels
|
Looks good to me as well, thanks. |
[APPROVALNOTIFIER] This PR is APPROVED Approval requirements bypassed by manually added approval. This pull-request has been approved by: caesarxuchao, shiywang Associated issue: #50519 The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these OWNERS Files:
You can indicate your approval by writing |
/test all [submit-queue is verifying that this PR is safe to merge] |
Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions here. |
I added these tests and expected the detailed type to be selected: + {
+ req: &http.Request{
+ Header: http.Header{
+ "Accept": []string{"application/json;as=PartialObjectMetadataList;v=v1alpha1;g=meta.k8s.io, application/json"},
+ },
+ },
+ contentType: "application/json;as=PartialObjectMetadataList;v=v1alpha1;g=meta.k8s.io",
+ ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json;as=PartialObjectMetadataList;v=v1alpha1;g=meta.k8s.io", "application/json"}},
+ serializer: fakeCodec,
+ }, failed with + {
+ req: &http.Request{
+ Header: http.Header{
+ "Accept": []string{"application/json;as=PartialObjectMetadataList;v=v1alpha1;g=meta.k8s.io"},
+ },
+ },
+ contentType: "application/json;as=PartialObjectMetadataList;v=v1alpha1;g=meta.k8s.io",
+ ns: &fakeNegotiater{serializer: fakeCodec, types: []string{"application/json;as=PartialObjectMetadataList;v=v1alpha1;g=meta.k8s.io", "application/json"}},
+ serializer: fakeCodec,
+ }, failed with am I misunderstanding the tests? |
hi @liggitt, in your case the if you mean user specify |
Hmm... added #57081 and the fallback worked as I expected. I'm confused why the negotiate unit test fails in what appears to be an identical case |
you mean expectation of "right behavior" ? anyway I will take a look that test.
I also add a test enhance pr here: #57086 unit test should not only check |
Fixes #50519
@smarterclayton @liggitt still wip, I'll add some unit test soon, and simplify the logic