Change:
/kind bug What happened: Sent a request for an aggregated resource (sample-api wardle/flunders). Response was a 200 status code and an error message saying the response could not be given. What you expected to happen: We should have an error status code. How to reproduce it (as minimally and precisely as possible): 1) Set up sample-apiserver. 2) curl -k -v -XGET -H "User-Agent: kubectl/v1.8.0 (linux/amd64) kubernetes/" -H "Accept: application/vnd.kubernetes.protobuf" -H "Authorization: Bearer <token" https://<aggregator 3) Response < HTTP/1.1 200 OK < Content-Length: 173 < Content-Type: application/vnd.kubernetes.protobuf < Date: Tue, 08 Aug 2017 22:57:37 GMT < k8s v1Status  Connection to host 35.193.112.40 left intact Failure~object v1alpha1.FlunderList does not implement the protobuf marshalling interface and cannot be encoded to a protobuf message"0" Anything else we need to know?: Environment: Kubernetes version (use ): 1.8 Cloud provider or hardware configuration**: GCE OS (e.g. from /etc/os-release): Debian GNU/Linux 7 (wheezy) Kernel (e.g. ): 3.16.0-4-amd64 Install tools: Others:
/sig api-machinery
I expect that's a shortcoming of the example server, not the aggregator itself
Issues go stale after 90d of inactivity. Mark the issue as fresh with . Stale issues rot after an additional 30d of inactivity and eventually close. Prevent issues from auto-closing with an comment. If this issue is safe to close now please do so with . Send feedback to sig-testing, kubernetes/test-infra and/or . /lifecycle stale
/remove-lifecycle stale
Issues go stale after 90d of inactivity. Mark the issue as fresh with . Stale issues rot after an additional 30d of inactivity and eventually close. If this issue is safe to close now please do so with . Send feedback to sig-testing, kubernetes/test-infra and/or . /lifecycle stale
/remove-lifecycle stale /lifecycle frozen
Hello, I'd like to take a look into this as my first contribution.
/assign
GitHub didn't allow me to assign the following users: tristanburgess. Note that only and repo collaborators can be assigned. For more information please see <detailsIn response to : /assign Instructions for interacting with me using PR comments are available . If you have questions or suggestions related to my behavior, please file an issue against the repository. </details
Looking into this so far, I'm able to reproduce the issue, and also that using does serialize correctly: There are potentially two separate considerations out of this as far as I can tell (in addition to the original issue): 1) 's example resource types do serialize JSON correctly, but does not appear implement protobuf serialization. I still need to confirm that it definitely isn't implemented (the error conditions at play may be too general to pick up a more specific error in play). There's a example resource type in addition to . Would be worth confirming the behavior of that type as well. If desired, I can also file a feature request to implement protobuf serialization for the example resource types. 2) I will investigate separately if disabling the JSON serial/deserial impl has a similar effect as this bug, or a more desirable effect; either way, would be good to unify the two behaviors. Lumped in with this part of the investigation will be to figure out what's handling the error from , and ultimately giving the 200. In terms of the actual status code we want to return, would make sense for this? Looking at the doc above it seems to fit the situation that the requestor requests a response format that isn't servable. It looks like completely invalid formats to the API do serve a 406. Any thoughts?
Pertaining to 2), JSON serialization appears to be baked in to the API as the default, so there really isn't a similar error for it. A JSON marshalization fault at the apimachinery level currently would prevent the apiserver from starting up successfully. Therefore, only have to worry about protobuf for this.
For 1), .proto definitions definitely weren't created for the extension API resource types. I generated them and confirmed that API response was successful after that. I'll file a separate ticket/PR for the addition to the sample APIs after this issue is fixed.
I had a starting implementation that successfully returned a 406 on protobuf marshalling definition not implemented (), but on running unit tests I stumbled upon a larger problem. There's an order of operations issue which is preventing http status code from being handled properly with both serialization encode success and serialization encode failure between apiserver and apimachinery. can only be called once. will call if is not called explicitly beforehand. In , is called with the statusCode passed in before calling ,which generates a serialized response body with machinery in . The problem in and child methods is if encoding fails, regardless of the failure reason. We have already called before , so the error handler in , , has no net effect on the status code in the response, even though it generates an appropriate error and tries to call with the new status code. The specific case of encode failing can be resolved by calling only after . If there's an error during encoding, no full will occur such that the default 200 status code will be set in place. Thus, would successfully write the new error code resultant from encode failure, which includes handling of the protobuf error enhancements I have . The catch is, this change would now cause successful encode to return the wrong status code, because our , and even Golangs built in package (which is used for our JSON encoding), will both translate and the data, which in turn will call , because we haven't called ourselves yet. The solution here is likely to move header modifications closer to time, to allow for this differentiation between original and a potential resultant from an encode failure. Two questions I'm currently weighing are: 1) If the original status code is failure (4xx, 5xx), and encoding also fails, what's the right status code and response? 2) If we move closer to , such that pre- errors can be handled with the correct status, but itself fails, we still have a problem where we've already called , so we can't further change the status code. What's the correct behavior here? Possibly to return the original status code but generate a raw text response body instead.

Discussion:
importmap = "k8s.io/kubernetes/vendor/k8s.io/apimachinery/pkg/runtime/serializer/protobuf", importpath = "k8s.io/apimachinery/pkg/runtime/serializer/protobuf", deps = [ <ins> "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", </ins> "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/recognizer:go_default_library",

"bytes" "fmt" "io" <ins> "net/http" </ins> "reflect" "github.com/gogo/protobuf/proto" <ins> metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" </ins> "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/recognizer"

return fmt.Sprintf("object %v does not implement the protobuf marshalling interface and cannot be encoded to a protobuf message", e.t) } <ins> func (e errNotMarshalable) Status() metav1.Status { return metav1.Status{ Status:  metav1.StatusFailure, Code:    http.StatusNotAcceptable, Reason:  metav1.StatusReason("NotAcceptable"), Message: e.Error(), } } </ins> func IsNotMarshalable(err error) bool { _, ok := err.(errNotMarshalable) return err != nil && ok

responsewriters.WriteObjectNegotiated(codecs, newGroupVersion, w, req, http.StatusOK, &UnregisteredAPIObject{"Undecodable"}) })) defer server.Close() <del> // We send a 200 status code before we encode the object, so we expect OK, but there will // still be an error object.  This seems ok, the alternative is to validate the object before // encoding, but this really should never happen, so it's wasted compute for every API request. status := expectApiStatus(t, "GET", server.URL, nil, http.StatusOK) </del> <ins> // Decode error response behavior is dictated by // apiserver/pkg/endpoints/handlers/responsewriters/status.go::ErrorToAPIStatus(). // Unless specific metav1.Status() parameters are implemented for the particular error in question, such that // the status code is defined, metav1 errors where error.status == metav1.StatusFailure // will throw a '500 Internal Server Error'. Non-metav1 type errors will always throw a '500 Internal Server Error'. status := expectApiStatus(t, "GET", server.URL, nil, http.StatusInternalServerError) </ins> if status.Reason != metav1.StatusReasonUnknown { t.Errorf("unexpected reason %#v", status) }

"k8s.io/apiserver/pkg/util/wsstream" ) <ins> // httpResponseWriterWithInit wraps http.ResponseWriter, and implements the io.Writer interface to be used // with encoding. The purpose is to allow for encoding to a stream, while accommodating a custom HTTP status code // if encoding fails, and meeting the encoder's io.Writer interface requirement. type httpResponseWriterWithInit struct { hasWritten bool mediaType  string statusCode int innerW     http.ResponseWriter } func (w httpResponseWriterWithInit) Write(b []byte) (n int, err error) { if !w.hasWritten { w.innerW.Header().Set("Content-Type", w.mediaType) w.innerW.WriteHeader(w.statusCode) w.hasWritten = true } return w.innerW.Write(b) } </ins> // WriteObject renders a returned runtime.Object to the response as a stream or an encoded object. If the object // returned by the response implements rest.ResourceStreamer that interface will be used to render the // response. The Accept header and current API version will be passed in, and the output will be copied

// SerializeObject renders an object in the content type negotiated by the client using the provided encoder. // The context is optional and can be nil. <del> func SerializeObject(mediaType string, encoder runtime.Encoder, w http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) { w.Header().Set("Content-Type", mediaType) w.WriteHeader(statusCode) </del> <ins> func SerializeObject(mediaType string, encoder runtime.Encoder, innerW http.ResponseWriter, req *http.Request, statusCode int, object runtime.Object) { w := httpResponseWriterWithInit{mediaType: mediaType, innerW: innerW, statusCode: statusCode} </ins> if err := encoder.Encode(object, w); err != nil { <del> errorJSONFatal(err, encoder, w) </del> <ins> errSerializationFatal(err, encoder, w) </ins> } }

return code } <del> // errorJSONFatal renders an error to the response, and if codec fails will render plaintext. </del> <ins> // errSerializationFatal renders an error to the response, and if codec fails will render plaintext. </ins> // Returns the HTTP status code of the error. <del> func errorJSONFatal(err error, codec runtime.Encoder, w http.ResponseWriter) int { </del> <ins> func errSerializationFatal(err error, codec runtime.Encoder, w httpResponseWriterWithInit) { </ins> utilruntime.HandleError(fmt.Errorf("apiserver was unable to write a JSON response: %v", err)) status := ErrorToAPIStatus(err) <del> code := int(status.Code) </del> <ins> candidateStatusCode := int(status.Code) // If original statusCode was not successful, we need to return the original error. // We cannot hide it behind serialization problems if w.statusCode >= http.StatusOK && w.statusCode < http.StatusBadRequest { w.statusCode = candidateStatusCode } </ins> output, err := runtime.Encode(codec, status) if err != nil { <del> w.WriteHeader(code) fmt.Fprintf(w, "%s: %s", status.Reason, status.Message) return code </del> <ins> w.mediaType = "text/plain" output = []byte(fmt.Sprintf("%s: %s", status.Reason, status.Message)) </ins> } <del> w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) </del> w.Write(output) <del> return code </del> } // WriteRawJSON writes a non-API object in JSON.