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 }