github.com/timstclair/heapster@v0.20.0-alpha1/Godeps/_workspace/src/k8s.io/kubernetes/pkg/client/unversioned/request.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors All rights reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package unversioned
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"io"
    23  	"io/ioutil"
    24  	"mime"
    25  	"net/http"
    26  	"net/url"
    27  	"path"
    28  	"strconv"
    29  	"strings"
    30  	"time"
    31  
    32  	"github.com/golang/glog"
    33  	"k8s.io/kubernetes/pkg/api/errors"
    34  	"k8s.io/kubernetes/pkg/api/unversioned"
    35  	"k8s.io/kubernetes/pkg/api/validation"
    36  	"k8s.io/kubernetes/pkg/client/metrics"
    37  	"k8s.io/kubernetes/pkg/conversion/queryparams"
    38  	"k8s.io/kubernetes/pkg/fields"
    39  	"k8s.io/kubernetes/pkg/labels"
    40  	"k8s.io/kubernetes/pkg/runtime"
    41  	"k8s.io/kubernetes/pkg/util"
    42  	"k8s.io/kubernetes/pkg/util/sets"
    43  	"k8s.io/kubernetes/pkg/watch"
    44  	watchjson "k8s.io/kubernetes/pkg/watch/json"
    45  )
    46  
    47  // specialParams lists parameters that are handled specially and which users of Request
    48  // are therefore not allowed to set manually.
    49  var specialParams = sets.NewString("timeout")
    50  
    51  // HTTPClient is an interface for testing a request object.
    52  type HTTPClient interface {
    53  	Do(req *http.Request) (*http.Response, error)
    54  }
    55  
    56  // ResponseWrapper is an interface for getting a response.
    57  // The response may be either accessed as a raw data (the whole output is put into memory) or as a stream.
    58  type ResponseWrapper interface {
    59  	DoRaw() ([]byte, error)
    60  	Stream() (io.ReadCloser, error)
    61  }
    62  
    63  // RequestConstructionError is returned when there's an error assembling a request.
    64  type RequestConstructionError struct {
    65  	Err error
    66  }
    67  
    68  // Error returns a textual description of 'r'.
    69  func (r *RequestConstructionError) Error() string {
    70  	return fmt.Sprintf("request construction error: '%v'", r.Err)
    71  }
    72  
    73  // Request allows for building up a request to a server in a chained fashion.
    74  // Any errors are stored until the end of your call, so you only have to
    75  // check once.
    76  type Request struct {
    77  	// required
    78  	client  HTTPClient
    79  	verb    string
    80  	baseURL *url.URL
    81  	codec   runtime.Codec
    82  
    83  	// generic components accessible via method setters
    84  	path    string
    85  	subpath string
    86  	params  url.Values
    87  	headers http.Header
    88  
    89  	// structural elements of the request that are part of the Kubernetes API conventions
    90  	namespace    string
    91  	namespaceSet bool
    92  	resource     string
    93  	resourceName string
    94  	subresource  string
    95  	selector     labels.Selector
    96  	timeout      time.Duration
    97  
    98  	apiVersion string
    99  
   100  	// output
   101  	err  error
   102  	body io.Reader
   103  
   104  	// The constructed request and the response
   105  	req  *http.Request
   106  	resp *http.Response
   107  }
   108  
   109  // NewRequest creates a new request helper object for accessing runtime.Objects on a server.
   110  func NewRequest(client HTTPClient, verb string, baseURL *url.URL, apiVersion string,
   111  	codec runtime.Codec) *Request {
   112  	metrics.Register()
   113  	return &Request{
   114  		client:     client,
   115  		verb:       verb,
   116  		baseURL:    baseURL,
   117  		path:       baseURL.Path,
   118  		apiVersion: apiVersion,
   119  		codec:      codec,
   120  	}
   121  }
   122  
   123  // Prefix adds segments to the relative beginning to the request path. These
   124  // items will be placed before the optional Namespace, Resource, or Name sections.
   125  // Setting AbsPath will clear any previously set Prefix segments
   126  func (r *Request) Prefix(segments ...string) *Request {
   127  	if r.err != nil {
   128  		return r
   129  	}
   130  	r.path = path.Join(r.path, path.Join(segments...))
   131  	return r
   132  }
   133  
   134  // Suffix appends segments to the end of the path. These items will be placed after the prefix and optional
   135  // Namespace, Resource, or Name sections.
   136  func (r *Request) Suffix(segments ...string) *Request {
   137  	if r.err != nil {
   138  		return r
   139  	}
   140  	r.subpath = path.Join(r.subpath, path.Join(segments...))
   141  	return r
   142  }
   143  
   144  // Resource sets the resource to access (<resource>/[ns/<namespace>/]<name>)
   145  func (r *Request) Resource(resource string) *Request {
   146  	if r.err != nil {
   147  		return r
   148  	}
   149  	if len(r.resource) != 0 {
   150  		r.err = fmt.Errorf("resource already set to %q, cannot change to %q", r.resource, resource)
   151  		return r
   152  	}
   153  	if ok, msg := validation.IsValidPathSegmentName(resource); !ok {
   154  		r.err = fmt.Errorf("invalid resource %q: %s", resource, msg)
   155  		return r
   156  	}
   157  	r.resource = resource
   158  	return r
   159  }
   160  
   161  // SubResource sets a sub-resource path which can be multiple segments segment after the resource
   162  // name but before the suffix.
   163  func (r *Request) SubResource(subresources ...string) *Request {
   164  	if r.err != nil {
   165  		return r
   166  	}
   167  	subresource := path.Join(subresources...)
   168  	if len(r.subresource) != 0 {
   169  		r.err = fmt.Errorf("subresource already set to %q, cannot change to %q", r.resource, subresource)
   170  		return r
   171  	}
   172  	for _, s := range subresources {
   173  		if ok, msg := validation.IsValidPathSegmentName(s); !ok {
   174  			r.err = fmt.Errorf("invalid subresource %q: %s", s, msg)
   175  			return r
   176  		}
   177  	}
   178  	r.subresource = subresource
   179  	return r
   180  }
   181  
   182  // Name sets the name of a resource to access (<resource>/[ns/<namespace>/]<name>)
   183  func (r *Request) Name(resourceName string) *Request {
   184  	if r.err != nil {
   185  		return r
   186  	}
   187  	if len(resourceName) == 0 {
   188  		r.err = fmt.Errorf("resource name may not be empty")
   189  		return r
   190  	}
   191  	if len(r.resourceName) != 0 {
   192  		r.err = fmt.Errorf("resource name already set to %q, cannot change to %q", r.resourceName, resourceName)
   193  		return r
   194  	}
   195  	if ok, msg := validation.IsValidPathSegmentName(resourceName); !ok {
   196  		r.err = fmt.Errorf("invalid resource name %q: %s", resourceName, msg)
   197  		return r
   198  	}
   199  	r.resourceName = resourceName
   200  	return r
   201  }
   202  
   203  // Namespace applies the namespace scope to a request (<resource>/[ns/<namespace>/]<name>)
   204  func (r *Request) Namespace(namespace string) *Request {
   205  	if r.err != nil {
   206  		return r
   207  	}
   208  	if r.namespaceSet {
   209  		r.err = fmt.Errorf("namespace already set to %q, cannot change to %q", r.namespace, namespace)
   210  		return r
   211  	}
   212  	if ok, msg := validation.IsValidPathSegmentName(namespace); !ok {
   213  		r.err = fmt.Errorf("invalid namespace %q: %s", namespace, msg)
   214  		return r
   215  	}
   216  	r.namespaceSet = true
   217  	r.namespace = namespace
   218  	return r
   219  }
   220  
   221  // NamespaceIfScoped is a convenience function to set a namespace if scoped is true
   222  func (r *Request) NamespaceIfScoped(namespace string, scoped bool) *Request {
   223  	if scoped {
   224  		return r.Namespace(namespace)
   225  	}
   226  	return r
   227  }
   228  
   229  // AbsPath overwrites an existing path with the segments provided. Trailing slashes are preserved
   230  // when a single segment is passed.
   231  func (r *Request) AbsPath(segments ...string) *Request {
   232  	if r.err != nil {
   233  		return r
   234  	}
   235  	if len(segments) == 1 {
   236  		// preserve any trailing slashes for legacy behavior
   237  		r.path = segments[0]
   238  	} else {
   239  		r.path = path.Join(segments...)
   240  	}
   241  	return r
   242  }
   243  
   244  // RequestURI overwrites existing path and parameters with the value of the provided server relative
   245  // URI. Some parameters (those in specialParameters) cannot be overwritten.
   246  func (r *Request) RequestURI(uri string) *Request {
   247  	if r.err != nil {
   248  		return r
   249  	}
   250  	locator, err := url.Parse(uri)
   251  	if err != nil {
   252  		r.err = err
   253  		return r
   254  	}
   255  	r.path = locator.Path
   256  	if len(locator.Query()) > 0 {
   257  		if r.params == nil {
   258  			r.params = make(url.Values)
   259  		}
   260  		for k, v := range locator.Query() {
   261  			r.params[k] = v
   262  		}
   263  	}
   264  	return r
   265  }
   266  
   267  const (
   268  	// A constant that clients can use to refer in a field selector to the object name field.
   269  	// Will be automatically emitted as the correct name for the API version.
   270  	NodeUnschedulable = "spec.unschedulable"
   271  	ObjectNameField   = "metadata.name"
   272  	PodHost           = "spec.nodeName"
   273  	PodStatus         = "status.phase"
   274  	SecretType        = "type"
   275  
   276  	EventReason                  = "reason"
   277  	EventSource                  = "source"
   278  	EventInvolvedKind            = "involvedObject.kind"
   279  	EventInvolvedNamespace       = "involvedObject.namespace"
   280  	EventInvolvedName            = "involvedObject.name"
   281  	EventInvolvedUID             = "involvedObject.uid"
   282  	EventInvolvedAPIVersion      = "involvedObject.apiVersion"
   283  	EventInvolvedResourceVersion = "involvedObject.resourceVersion"
   284  	EventInvolvedFieldPath       = "involvedObject.fieldPath"
   285  )
   286  
   287  type clientFieldNameToAPIVersionFieldName map[string]string
   288  
   289  func (c clientFieldNameToAPIVersionFieldName) filterField(field, value string) (newField, newValue string, err error) {
   290  	newFieldName, ok := c[field]
   291  	if !ok {
   292  		return "", "", fmt.Errorf("%v - %v - no field mapping defined", field, value)
   293  	}
   294  	return newFieldName, value, nil
   295  }
   296  
   297  type resourceTypeToFieldMapping map[string]clientFieldNameToAPIVersionFieldName
   298  
   299  func (r resourceTypeToFieldMapping) filterField(resourceType, field, value string) (newField, newValue string, err error) {
   300  	fMapping, ok := r[resourceType]
   301  	if !ok {
   302  		return "", "", fmt.Errorf("%v - %v - %v - no field mapping defined", resourceType, field, value)
   303  	}
   304  	return fMapping.filterField(field, value)
   305  }
   306  
   307  type versionToResourceToFieldMapping map[string]resourceTypeToFieldMapping
   308  
   309  func (v versionToResourceToFieldMapping) filterField(apiVersion, resourceType, field, value string) (newField, newValue string, err error) {
   310  	rMapping, ok := v[apiVersion]
   311  	if !ok {
   312  		glog.Warningf("Field selector: %v - %v - %v - %v: need to check if this is versioned correctly.", apiVersion, resourceType, field, value)
   313  		return field, value, nil
   314  	}
   315  	newField, newValue, err = rMapping.filterField(resourceType, field, value)
   316  	if err != nil {
   317  		// This is only a warning until we find and fix all of the client's usages.
   318  		glog.Warningf("Field selector: %v - %v - %v - %v: need to check if this is versioned correctly.", apiVersion, resourceType, field, value)
   319  		return field, value, nil
   320  	}
   321  	return newField, newValue, nil
   322  }
   323  
   324  var fieldMappings = versionToResourceToFieldMapping{
   325  	"v1": resourceTypeToFieldMapping{
   326  		"nodes": clientFieldNameToAPIVersionFieldName{
   327  			ObjectNameField:   ObjectNameField,
   328  			NodeUnschedulable: NodeUnschedulable,
   329  		},
   330  		"pods": clientFieldNameToAPIVersionFieldName{
   331  			PodHost:   PodHost,
   332  			PodStatus: PodStatus,
   333  		},
   334  		"secrets": clientFieldNameToAPIVersionFieldName{
   335  			SecretType: SecretType,
   336  		},
   337  		"serviceAccounts": clientFieldNameToAPIVersionFieldName{
   338  			ObjectNameField: ObjectNameField,
   339  		},
   340  		"endpoints": clientFieldNameToAPIVersionFieldName{
   341  			ObjectNameField: ObjectNameField,
   342  		},
   343  		"events": clientFieldNameToAPIVersionFieldName{
   344  			ObjectNameField:              ObjectNameField,
   345  			EventReason:                  EventReason,
   346  			EventSource:                  EventSource,
   347  			EventInvolvedKind:            EventInvolvedKind,
   348  			EventInvolvedNamespace:       EventInvolvedNamespace,
   349  			EventInvolvedName:            EventInvolvedName,
   350  			EventInvolvedUID:             EventInvolvedUID,
   351  			EventInvolvedAPIVersion:      EventInvolvedAPIVersion,
   352  			EventInvolvedResourceVersion: EventInvolvedResourceVersion,
   353  			EventInvolvedFieldPath:       EventInvolvedFieldPath,
   354  		},
   355  	},
   356  }
   357  
   358  // FieldsSelectorParam adds the given selector as a query parameter with the name paramName.
   359  func (r *Request) FieldsSelectorParam(s fields.Selector) *Request {
   360  	if r.err != nil {
   361  		return r
   362  	}
   363  	if s == nil {
   364  		return r
   365  	}
   366  	if s.Empty() {
   367  		return r
   368  	}
   369  	s2, err := s.Transform(func(field, value string) (newField, newValue string, err error) {
   370  		return fieldMappings.filterField(r.apiVersion, r.resource, field, value)
   371  	})
   372  	if err != nil {
   373  		r.err = err
   374  		return r
   375  	}
   376  	return r.setParam(unversioned.FieldSelectorQueryParam(r.apiVersion), s2.String())
   377  }
   378  
   379  // LabelsSelectorParam adds the given selector as a query parameter
   380  func (r *Request) LabelsSelectorParam(s labels.Selector) *Request {
   381  	if r.err != nil {
   382  		return r
   383  	}
   384  	if s == nil {
   385  		return r
   386  	}
   387  	if s.Empty() {
   388  		return r
   389  	}
   390  	return r.setParam(unversioned.LabelSelectorQueryParam(r.apiVersion), s.String())
   391  }
   392  
   393  // UintParam creates a query parameter with the given value.
   394  func (r *Request) UintParam(paramName string, u uint64) *Request {
   395  	if r.err != nil {
   396  		return r
   397  	}
   398  	return r.setParam(paramName, strconv.FormatUint(u, 10))
   399  }
   400  
   401  // Param creates a query parameter with the given string value.
   402  func (r *Request) Param(paramName, s string) *Request {
   403  	if r.err != nil {
   404  		return r
   405  	}
   406  	return r.setParam(paramName, s)
   407  }
   408  
   409  // VersionedParams will take the provided object, serialize it to a map[string][]string using the
   410  // implicit RESTClient API version and the provided object convertor, and then add those as parameters
   411  // to the request. Use this to provide versioned query parameters from client libraries.
   412  func (r *Request) VersionedParams(obj runtime.Object, convertor runtime.ObjectConvertor) *Request {
   413  	if r.err != nil {
   414  		return r
   415  	}
   416  	versioned, err := convertor.ConvertToVersion(obj, r.apiVersion)
   417  	if err != nil {
   418  		r.err = err
   419  		return r
   420  	}
   421  	params, err := queryparams.Convert(versioned)
   422  	if err != nil {
   423  		r.err = err
   424  		return r
   425  	}
   426  	for k, v := range params {
   427  		for _, vv := range v {
   428  			r.setParam(k, vv)
   429  		}
   430  	}
   431  	return r
   432  }
   433  
   434  func (r *Request) setParam(paramName, value string) *Request {
   435  	if specialParams.Has(paramName) {
   436  		r.err = fmt.Errorf("must set %v through the corresponding function, not directly.", paramName)
   437  		return r
   438  	}
   439  	if r.params == nil {
   440  		r.params = make(url.Values)
   441  	}
   442  	r.params[paramName] = append(r.params[paramName], value)
   443  	return r
   444  }
   445  
   446  func (r *Request) SetHeader(key, value string) *Request {
   447  	if r.headers == nil {
   448  		r.headers = http.Header{}
   449  	}
   450  	r.headers.Set(key, value)
   451  	return r
   452  }
   453  
   454  // Timeout makes the request use the given duration as a timeout. Sets the "timeout"
   455  // parameter.
   456  func (r *Request) Timeout(d time.Duration) *Request {
   457  	if r.err != nil {
   458  		return r
   459  	}
   460  	r.timeout = d
   461  	return r
   462  }
   463  
   464  // Timeout makes the request use the given duration as a timeout. Sets the "timeoutSeconds"
   465  // parameter.
   466  func (r *Request) TimeoutSeconds(d time.Duration) *Request {
   467  	if r.err != nil {
   468  		return r
   469  	}
   470  	if d != 0 {
   471  		timeout := int64(d.Seconds())
   472  		r.Param("timeoutSeconds", strconv.FormatInt(timeout, 10))
   473  	}
   474  	return r
   475  }
   476  
   477  // Body makes the request use obj as the body. Optional.
   478  // If obj is a string, try to read a file of that name.
   479  // If obj is a []byte, send it directly.
   480  // If obj is an io.Reader, use it directly.
   481  // If obj is a runtime.Object, marshal it correctly, and set Content-Type header.
   482  // Otherwise, set an error.
   483  func (r *Request) Body(obj interface{}) *Request {
   484  	if r.err != nil {
   485  		return r
   486  	}
   487  	switch t := obj.(type) {
   488  	case string:
   489  		data, err := ioutil.ReadFile(t)
   490  		if err != nil {
   491  			r.err = err
   492  			return r
   493  		}
   494  		glog.V(8).Infof("Request Body: %s", string(data))
   495  		r.body = bytes.NewBuffer(data)
   496  	case []byte:
   497  		glog.V(8).Infof("Request Body: %s", string(t))
   498  		r.body = bytes.NewBuffer(t)
   499  	case io.Reader:
   500  		r.body = t
   501  	case runtime.Object:
   502  		data, err := r.codec.Encode(t)
   503  		if err != nil {
   504  			r.err = err
   505  			return r
   506  		}
   507  		glog.V(8).Infof("Request Body: %s", string(data))
   508  		r.body = bytes.NewBuffer(data)
   509  		r.SetHeader("Content-Type", "application/json")
   510  	default:
   511  		r.err = fmt.Errorf("unknown type used for body: %+v", obj)
   512  	}
   513  	return r
   514  }
   515  
   516  // URL returns the current working URL.
   517  func (r *Request) URL() *url.URL {
   518  	p := r.path
   519  	if r.namespaceSet && len(r.namespace) > 0 {
   520  		p = path.Join(p, "namespaces", r.namespace)
   521  	}
   522  	if len(r.resource) != 0 {
   523  		p = path.Join(p, strings.ToLower(r.resource))
   524  	}
   525  	// Join trims trailing slashes, so preserve r.path's trailing slash for backwards compat if nothing was changed
   526  	if len(r.resourceName) != 0 || len(r.subpath) != 0 || len(r.subresource) != 0 {
   527  		p = path.Join(p, r.resourceName, r.subresource, r.subpath)
   528  	}
   529  
   530  	finalURL := &url.URL{}
   531  	if r.baseURL != nil {
   532  		*finalURL = *r.baseURL
   533  	}
   534  	finalURL.Path = p
   535  
   536  	query := url.Values{}
   537  	for key, values := range r.params {
   538  		for _, value := range values {
   539  			query.Add(key, value)
   540  		}
   541  	}
   542  
   543  	// timeout is handled specially here.
   544  	if r.timeout != 0 {
   545  		query.Set("timeout", r.timeout.String())
   546  	}
   547  	finalURL.RawQuery = query.Encode()
   548  	return finalURL
   549  }
   550  
   551  // finalURLTemplate is similar to URL(), but will make all specific parameter values equal
   552  // - instead of name or namespace, "{name}" and "{namespace}" will be used, and all query
   553  // parameters will be reset. This creates a copy of the request so as not to change the
   554  // underyling object.  This means some useful request info (like the types of field
   555  // selectors in use) will be lost.
   556  // TODO: preserve field selector keys
   557  func (r Request) finalURLTemplate() string {
   558  	if len(r.resourceName) != 0 {
   559  		r.resourceName = "{name}"
   560  	}
   561  	if r.namespaceSet && len(r.namespace) != 0 {
   562  		r.namespace = "{namespace}"
   563  	}
   564  	newParams := url.Values{}
   565  	v := []string{"{value}"}
   566  	for k := range r.params {
   567  		newParams[k] = v
   568  	}
   569  	r.params = newParams
   570  	return r.URL().String()
   571  }
   572  
   573  // Watch attempts to begin watching the requested location.
   574  // Returns a watch.Interface, or an error.
   575  func (r *Request) Watch() (watch.Interface, error) {
   576  	if r.err != nil {
   577  		return nil, r.err
   578  	}
   579  	url := r.URL().String()
   580  	req, err := http.NewRequest(r.verb, url, r.body)
   581  	if err != nil {
   582  		return nil, err
   583  	}
   584  	client := r.client
   585  	if client == nil {
   586  		client = http.DefaultClient
   587  	}
   588  	resp, err := client.Do(req)
   589  	updateURLMetrics(r, resp, err)
   590  	if err != nil {
   591  		// The watch stream mechanism handles many common partial data errors, so closed
   592  		// connections can be retried in many cases.
   593  		if util.IsProbableEOF(err) {
   594  			return watch.NewEmptyWatch(), nil
   595  		}
   596  		return nil, err
   597  	}
   598  	if resp.StatusCode != http.StatusOK {
   599  		if result := r.transformResponse(resp, req); result.err != nil {
   600  			return nil, result.err
   601  		}
   602  		return nil, fmt.Errorf("for request '%+v', got status: %v", url, resp.StatusCode)
   603  	}
   604  	return watch.NewStreamWatcher(watchjson.NewDecoder(resp.Body, r.codec)), nil
   605  }
   606  
   607  // updateURLMetrics is a convenience function for pushing metrics.
   608  // It also handles corner cases for incomplete/invalid request data.
   609  func updateURLMetrics(req *Request, resp *http.Response, err error) {
   610  	url := "none"
   611  	if req.baseURL != nil {
   612  		url = req.baseURL.Host
   613  	}
   614  
   615  	// If we have an error (i.e. apiserver down) we report that as a metric label.
   616  	if err != nil {
   617  		metrics.RequestResult.WithLabelValues(err.Error(), req.verb, url).Inc()
   618  	} else {
   619  		//Metrics for failure codes
   620  		metrics.RequestResult.WithLabelValues(strconv.Itoa(resp.StatusCode), req.verb, url).Inc()
   621  	}
   622  }
   623  
   624  // Stream formats and executes the request, and offers streaming of the response.
   625  // Returns io.ReadCloser which could be used for streaming of the response, or an error
   626  // 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.
   627  // 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.
   628  func (r *Request) Stream() (io.ReadCloser, error) {
   629  	if r.err != nil {
   630  		return nil, r.err
   631  	}
   632  	url := r.URL().String()
   633  	req, err := http.NewRequest(r.verb, url, nil)
   634  	if err != nil {
   635  		return nil, err
   636  	}
   637  	client := r.client
   638  	if client == nil {
   639  		client = http.DefaultClient
   640  	}
   641  	resp, err := client.Do(req)
   642  	updateURLMetrics(r, resp, err)
   643  	if err != nil {
   644  		return nil, err
   645  	}
   646  
   647  	switch {
   648  	case (resp.StatusCode >= 200) && (resp.StatusCode < 300):
   649  		return resp.Body, nil
   650  
   651  	default:
   652  		// ensure we close the body before returning the error
   653  		defer resp.Body.Close()
   654  
   655  		// we have a decent shot at taking the object returned, parsing it as a status object and returning a more normal error
   656  		bodyBytes, err := ioutil.ReadAll(resp.Body)
   657  		if err != nil {
   658  			return nil, fmt.Errorf("%v while accessing %v", resp.Status, url)
   659  		}
   660  
   661  		if runtimeObject, err := r.codec.Decode(bodyBytes); err == nil {
   662  			statusError := errors.FromObject(runtimeObject)
   663  
   664  			if _, ok := statusError.(APIStatus); ok {
   665  				return nil, statusError
   666  			}
   667  		}
   668  
   669  		bodyText := string(bodyBytes)
   670  		return nil, fmt.Errorf("%s while accessing %v: %s", resp.Status, url, bodyText)
   671  	}
   672  }
   673  
   674  // request connects to the server and invokes the provided function when a server response is
   675  // received. It handles retry behavior and up front validation of requests. It will invoke
   676  // fn at most once. It will return an error if a problem occurred prior to connecting to the
   677  // server - the provided function is responsible for handling server errors.
   678  func (r *Request) request(fn func(*http.Request, *http.Response)) error {
   679  	//Metrics for total request latency
   680  	start := time.Now()
   681  	defer func() {
   682  		metrics.RequestLatency.WithLabelValues(r.verb, r.finalURLTemplate()).Observe(metrics.SinceInMicroseconds(start))
   683  	}()
   684  
   685  	if r.err != nil {
   686  		return r.err
   687  	}
   688  
   689  	// TODO: added to catch programmer errors (invoking operations with an object with an empty namespace)
   690  	if (r.verb == "GET" || r.verb == "PUT" || r.verb == "DELETE") && r.namespaceSet && len(r.resourceName) > 0 && len(r.namespace) == 0 {
   691  		return fmt.Errorf("an empty namespace may not be set when a resource name is provided")
   692  	}
   693  	if (r.verb == "POST") && r.namespaceSet && len(r.namespace) == 0 {
   694  		return fmt.Errorf("an empty namespace may not be set during creation")
   695  	}
   696  
   697  	client := r.client
   698  	if client == nil {
   699  		client = http.DefaultClient
   700  	}
   701  
   702  	// Right now we make about ten retry attempts if we get a Retry-After response.
   703  	// TODO: Change to a timeout based approach.
   704  	maxRetries := 10
   705  	retries := 0
   706  	for {
   707  		url := r.URL().String()
   708  		req, err := http.NewRequest(r.verb, url, r.body)
   709  		if err != nil {
   710  			return err
   711  		}
   712  		req.Header = r.headers
   713  
   714  		resp, err := client.Do(req)
   715  		updateURLMetrics(r, resp, err)
   716  		if err != nil {
   717  			return err
   718  		}
   719  
   720  		done := func() bool {
   721  			// ensure the response body is closed before we reconnect, so that we reuse the same
   722  			// TCP connection
   723  			defer resp.Body.Close()
   724  
   725  			retries++
   726  			if seconds, wait := checkWait(resp); wait && retries < maxRetries {
   727  				glog.V(4).Infof("Got a Retry-After %s response for attempt %d to %v", seconds, retries, url)
   728  				time.Sleep(time.Duration(seconds) * time.Second)
   729  				return false
   730  			}
   731  			fn(req, resp)
   732  			return true
   733  		}()
   734  		if done {
   735  			return nil
   736  		}
   737  	}
   738  }
   739  
   740  // Do formats and executes the request. Returns a Result object for easy response
   741  // processing.
   742  //
   743  // Error type:
   744  //  * If the request can't be constructed, or an error happened earlier while building its
   745  //    arguments: *RequestConstructionError
   746  //  * If the server responds with a status: *errors.StatusError or *errors.UnexpectedObjectError
   747  //  * http.Client.Do errors are returned directly.
   748  func (r *Request) Do() Result {
   749  	var result Result
   750  	err := r.request(func(req *http.Request, resp *http.Response) {
   751  		result = r.transformResponse(resp, req)
   752  	})
   753  	if err != nil {
   754  		return Result{err: err}
   755  	}
   756  	return result
   757  }
   758  
   759  // DoRaw executes the request but does not process the response body.
   760  func (r *Request) DoRaw() ([]byte, error) {
   761  	var result Result
   762  	err := r.request(func(req *http.Request, resp *http.Response) {
   763  		result.body, result.err = ioutil.ReadAll(resp.Body)
   764  	})
   765  	if err != nil {
   766  		return nil, err
   767  	}
   768  	return result.body, result.err
   769  }
   770  
   771  // transformResponse converts an API response into a structured API object
   772  func (r *Request) transformResponse(resp *http.Response, req *http.Request) Result {
   773  	var body []byte
   774  	if resp.Body != nil {
   775  		if data, err := ioutil.ReadAll(resp.Body); err == nil {
   776  			body = data
   777  		}
   778  	}
   779  	glog.V(8).Infof("Response Body: %s", string(body))
   780  
   781  	// Did the server give us a status response?
   782  	isStatusResponse := false
   783  	var status unversioned.Status
   784  	if err := r.codec.DecodeInto(body, &status); err == nil && status.Status != "" {
   785  		isStatusResponse = true
   786  	}
   787  
   788  	switch {
   789  	case resp.StatusCode == http.StatusSwitchingProtocols:
   790  		// no-op, we've been upgraded
   791  	case resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent:
   792  		if !isStatusResponse {
   793  			return Result{err: r.transformUnstructuredResponseError(resp, req, body)}
   794  		}
   795  		return Result{err: errors.FromObject(&status)}
   796  	}
   797  
   798  	// If the server gave us a status back, look at what it was.
   799  	success := resp.StatusCode >= http.StatusOK && resp.StatusCode <= http.StatusPartialContent
   800  	if isStatusResponse && (status.Status != unversioned.StatusSuccess && !success) {
   801  		// "Failed" requests are clearly just an error and it makes sense to return them as such.
   802  		return Result{err: errors.FromObject(&status)}
   803  	}
   804  
   805  	return Result{
   806  		body:       body,
   807  		statusCode: resp.StatusCode,
   808  		codec:      r.codec,
   809  	}
   810  }
   811  
   812  // transformUnstructuredResponseError handles an error from the server that is not in a structured form.
   813  // It is expected to transform any response that is not recognizable as a clear server sent error from the
   814  // K8S API using the information provided with the request. In practice, HTTP proxies and client libraries
   815  // introduce a level of uncertainty to the responses returned by servers that in common use result in
   816  // unexpected responses. The rough structure is:
   817  //
   818  // 1. Assume the server sends you something sane - JSON + well defined error objects + proper codes
   819  //    - this is the happy path
   820  //    - when you get this output, trust what the server sends
   821  // 2. Guard against empty fields / bodies in received JSON and attempt to cull sufficient info from them to
   822  //    generate a reasonable facsimile of the original failure.
   823  //    - Be sure to use a distinct error type or flag that allows a client to distinguish between this and error 1 above
   824  // 3. Handle true disconnect failures / completely malformed data by moving up to a more generic client error
   825  // 4. Distinguish between various connection failures like SSL certificates, timeouts, proxy errors, unexpected
   826  //    initial contact, the presence of mismatched body contents from posted content types
   827  //    - Give these a separate distinct error type and capture as much as possible of the original message
   828  //
   829  // TODO: introduce transformation of generic http.Client.Do() errors that separates 4.
   830  func (r *Request) transformUnstructuredResponseError(resp *http.Response, req *http.Request, body []byte) error {
   831  	if body == nil && resp.Body != nil {
   832  		if data, err := ioutil.ReadAll(resp.Body); err == nil {
   833  			body = data
   834  		}
   835  	}
   836  	glog.V(8).Infof("Response Body: %s", string(body))
   837  
   838  	message := "unknown"
   839  	if isTextResponse(resp) {
   840  		message = strings.TrimSpace(string(body))
   841  	}
   842  	retryAfter, _ := retryAfterSeconds(resp)
   843  	return errors.NewGenericServerResponse(resp.StatusCode, req.Method, r.resource, r.resourceName, message, retryAfter, true)
   844  }
   845  
   846  // isTextResponse returns true if the response appears to be a textual media type.
   847  func isTextResponse(resp *http.Response) bool {
   848  	contentType := resp.Header.Get("Content-Type")
   849  	if len(contentType) == 0 {
   850  		return true
   851  	}
   852  	media, _, err := mime.ParseMediaType(contentType)
   853  	if err != nil {
   854  		return false
   855  	}
   856  	return strings.HasPrefix(media, "text/")
   857  }
   858  
   859  // checkWait returns true along with a number of seconds if the server instructed us to wait
   860  // before retrying.
   861  func checkWait(resp *http.Response) (int, bool) {
   862  	switch r := resp.StatusCode; {
   863  	// any 500 error code and 429 can trigger a wait
   864  	case r == errors.StatusTooManyRequests, r >= 500:
   865  	default:
   866  		return 0, false
   867  	}
   868  	i, ok := retryAfterSeconds(resp)
   869  	return i, ok
   870  }
   871  
   872  // retryAfterSeconds returns the value of the Retry-After header and true, or 0 and false if
   873  // the header was missing or not a valid number.
   874  func retryAfterSeconds(resp *http.Response) (int, bool) {
   875  	if h := resp.Header.Get("Retry-After"); len(h) > 0 {
   876  		if i, err := strconv.Atoi(h); err == nil {
   877  			return i, true
   878  		}
   879  	}
   880  	return 0, false
   881  }
   882  
   883  // Result contains the result of calling Request.Do().
   884  type Result struct {
   885  	body       []byte
   886  	err        error
   887  	statusCode int
   888  
   889  	codec runtime.Codec
   890  }
   891  
   892  // Raw returns the raw result.
   893  func (r Result) Raw() ([]byte, error) {
   894  	return r.body, r.err
   895  }
   896  
   897  // Get returns the result as an object.
   898  func (r Result) Get() (runtime.Object, error) {
   899  	if r.err != nil {
   900  		return nil, r.err
   901  	}
   902  	return r.codec.Decode(r.body)
   903  }
   904  
   905  // StatusCode returns the HTTP status code of the request. (Only valid if no
   906  // error was returned.)
   907  func (r Result) StatusCode(statusCode *int) Result {
   908  	*statusCode = r.statusCode
   909  	return r
   910  }
   911  
   912  // Into stores the result into obj, if possible.
   913  func (r Result) Into(obj runtime.Object) error {
   914  	if r.err != nil {
   915  		return r.err
   916  	}
   917  	return r.codec.DecodeInto(r.body, obj)
   918  }
   919  
   920  // WasCreated updates the provided bool pointer to whether the server returned
   921  // 201 created or a different response.
   922  func (r Result) WasCreated(wasCreated *bool) Result {
   923  	*wasCreated = r.statusCode == http.StatusCreated
   924  	return r
   925  }
   926  
   927  // Error returns the error executing the request, nil if no error occurred.
   928  // See the Request.Do() comment for what errors you might get.
   929  func (r Result) Error() error {
   930  	return r.err
   931  }