// finishRequest makes a given resultFunc asynchronous and handles errors returned by the response. // Any api.Status object returned is considered an "error", which interrupts the normal response flow. func finishRequest(timeout time.Duration, fn resultFunc) (result runtime.Object, err error) { // these channels need to be buffered to prevent the goroutine below from hanging indefinitely // when the select statement reads something other than the one the goroutine sends on. ch := make(chan runtime.Object, 1) errCh := make(chan error, 1) panicCh := make(chan interface{}, 1) go func() { // panics don't cross goroutine boundaries, so we have to handle ourselves defer util.HandleCrash(func(panicReason interface{}) { // Propagate to parent goroutine panicCh <- panicReason }) if result, err := fn(); err != nil { errCh <- err } else { ch <- result } }() select { case result = <-ch: if status, ok := result.(*unversioned.Status); ok { return nil, errors.FromObject(status) } return result, nil case err = <-errCh: return nil, err case p := <-panicCh: panic(p) case <-time.After(timeout): return nil, errors.NewTimeoutError("request did not complete within allowed duration", 0) } }
// watchHandler watches w and keeps *resourceVersion up to date. func (r *Reflector) watchHandler(w watch.Interface, resourceVersion *string, resyncCh <-chan time.Time, 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 <-resyncCh: return errorResyncRequested 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 { util.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 { util.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event)) continue } newResourceVersion := meta.ResourceVersion() 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: util.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 }
// 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 } } glog.V(8).Infof("Response Body: %s", string(body)) // Did the server give us a status response? isStatusResponse := false var status unversioned.Status if err := r.codec.DecodeInto(body, &status); err == nil && status.Status != "" { 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, statusCode: resp.StatusCode, codec: r.codec, } }
// Stream formats and executes the request, and offers streaming of the response. // Returns io.ReadCloser which could be used for streaming of the response, or an error // Any non-2xx http status code causes an error. If we get a non-2xx code, we try to convert the body into an APIStatus object. // If we can, we return that as an error. Otherwise, we create an error that lists the http status and the content of the response. func (r *Request) Stream() (io.ReadCloser, error) { if r.err != nil { return nil, r.err } url := r.URL().String() req, err := http.NewRequest(r.verb, url, nil) if err != nil { return nil, err } client := r.client if client == nil { client = http.DefaultClient } resp, err := client.Do(req) if err != nil { return nil, err } switch { case (resp.StatusCode >= 200) && (resp.StatusCode < 300): return resp.Body, nil default: // ensure we close the body before returning the error defer resp.Body.Close() // we have a decent shot at taking the object returned, parsing it as a status object and returning a more normal error bodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("%v while accessing %v", resp.Status, url) } if runtimeObject, err := r.codec.Decode(bodyBytes); err == nil { statusError := errors.FromObject(runtimeObject) if _, ok := statusError.(APIStatus); ok { return nil, statusError } } bodyText := string(bodyBytes) return nil, fmt.Errorf("%s while accessing %v: %s", resp.Status, url, bodyText) } }