// watchHandler watches w and keeps *resourceVersion up to date. func (r *Reflector) watchHandler(w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error { start := time.Now() eventCount := 0 // Stopping the watcher should be idempotent and if we return from this function there's no way // we're coming back in with the same watch interface. defer w.Stop() loop: for { select { case <-stopCh: return errorStopRequested case err := <-errc: return err case event, ok := <-w.ResultChan(): if !ok { break loop } if event.Type == watch.Error { return apierrs.FromObject(event.Object) } if e, a := r.expectedType, reflect.TypeOf(event.Object); e != nil && e != a { utilruntime.HandleError(fmt.Errorf("%s: expected type %v, but watch event object had type %v", r.name, e, a)) continue } meta, err := meta.Accessor(event.Object) if err != nil { utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event)) continue } newResourceVersion := meta.GetResourceVersion() switch event.Type { case watch.Added: r.store.Add(event.Object) case watch.Modified: r.store.Update(event.Object) case watch.Deleted: // TODO: Will any consumers need access to the "last known // state", which is passed in event.Object? If so, may need // to change this. r.store.Delete(event.Object) default: utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event)) } *resourceVersion = newResourceVersion r.setLastSyncResourceVersion(newResourceVersion) eventCount++ } } watchDuration := time.Now().Sub(start) if watchDuration < 1*time.Second && eventCount == 0 { glog.V(4).Infof("%s: Unexpected watch close - watch lasted less than a second and no items received", r.name) return errors.New("very short watch") } glog.V(4).Infof("%s: Watch close - %v total %v items received", r.name, r.expectedType, eventCount) return nil }
// Error returns the error executing the request, nil if no error occurred. // If the returned object is of type Status and has Status != StatusSuccess, the // additional information in Status will be used to enrich the error. // 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 { return r.err } // attempt to convert the body into a Status object // to be backwards compatible with old servers that do not return a version, default to "v1" out, _, err := r.decoder.Decode(r.body, &schema.GroupVersionKind{Version: "v1"}, nil) if err != nil { glog.V(5).Infof("body was not decodable (unable to check for Status): %v", err) return r.err } switch t := out.(type) { case *metav1.Status: // because we default the kind, we *must* check for StatusFailure if t.Status == metav1.StatusFailure { return errors.FromObject(t) } } return r.err }
// Get returns the result as an object, which means it passes through the decoder. // If the returned object is of type Status and has .Status != StatusSuccess, the // additional information in Status will be used to enrich the error. func (r Result) Get() (runtime.Object, error) { if r.err != nil { // Check whether the result has a Status object in the body and prefer that. return nil, r.Error() } if r.decoder == nil { return nil, fmt.Errorf("serializer for %s doesn't exist", r.contentType) } // decode, but if the result is Status return that as an error instead. out, _, err := r.decoder.Decode(r.body, nil, nil) if err != nil { return nil, err } switch t := out.(type) { case *metav1.Status: // any status besides StatusSuccess is considered an error. if t.Status != metav1.StatusSuccess { return nil, errors.FromObject(t) } } return out, nil }
// Into stores the result into obj, if possible. If obj is nil it is ignored. // If the returned object is of type Status and has .Status != StatusSuccess, the // additional information in Status will be used to enrich the error. func (r Result) Into(obj runtime.Object) error { if r.err != nil { // Check whether the result has a Status object in the body and prefer that. return r.Error() } if r.decoder == nil { return fmt.Errorf("serializer for %s doesn't exist", r.contentType) } out, _, err := r.decoder.Decode(r.body, nil, obj) if err != nil || out == obj { return err } // if a different object is returned, see if it is Status and avoid double decoding // the object. switch t := out.(type) { case *metav1.Status: // any status besides StatusSuccess is considered an error. if t.Status != metav1.StatusSuccess { return errors.FromObject(t) } } return nil }
// transformResponse converts an API response into a structured API object func (r *Request) transformResponse(resp *http.Response, req *http.Request) Result { var body []byte if resp.Body != nil { if data, err := ioutil.ReadAll(resp.Body); err == nil { body = data } } if glog.V(8) { if bytes.IndexFunc(body, func(r rune) bool { return r < 0x0a }) != -1 { glog.Infof("Response Body:\n%s", hex.Dump(body)) } else { glog.Infof("Response Body: %s", string(body)) } } // verify the content type is accurate contentType := resp.Header.Get("Content-Type") decoder := r.serializers.Decoder if len(contentType) > 0 && (decoder == nil || (len(r.content.ContentType) > 0 && contentType != r.content.ContentType)) { mediaType, params, err := mime.ParseMediaType(contentType) if err != nil { return Result{err: errors.NewInternalError(err)} } decoder, err = r.serializers.RenegotiatedDecoder(mediaType, params) if err != nil { // if we fail to negotiate a decoder, treat this as an unstructured error switch { case resp.StatusCode == http.StatusSwitchingProtocols: // no-op, we've been upgraded case resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent: return Result{err: r.transformUnstructuredResponseError(resp, req, body)} } return Result{ body: body, contentType: contentType, statusCode: resp.StatusCode, } } } // Did the server give us a status response? isStatusResponse := false status := &unversioned.Status{} // Because release-1.1 server returns Status with empty APIVersion at paths // to the Extensions resources, we need to use DecodeInto here to provide // default groupVersion, otherwise a status response won't be correctly // decoded. err := runtime.DecodeInto(decoder, body, status) if err == nil && len(status.Status) > 0 { isStatusResponse = true } switch { case resp.StatusCode == http.StatusSwitchingProtocols: // no-op, we've been upgraded case resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent: if !isStatusResponse { return Result{err: r.transformUnstructuredResponseError(resp, req, body)} } return Result{err: errors.FromObject(status)} } // If the server gave us a status back, look at what it was. success := resp.StatusCode >= http.StatusOK && resp.StatusCode <= http.StatusPartialContent if isStatusResponse && (status.Status != unversioned.StatusSuccess && !success) { // "Failed" requests are clearly just an error and it makes sense to return them as such. return Result{err: errors.FromObject(status)} } return Result{ body: body, contentType: contentType, statusCode: resp.StatusCode, decoder: decoder, } }