chaosclient.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. /*
  2. Copyright 2015 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. // Package chaosclient makes it easy to simulate network latency, misbehaving
  14. // servers, and random errors from servers. It is intended to stress test components
  15. // under failure conditions and expose weaknesses in the error handling logic
  16. // of the codebase.
  17. package chaosclient
  18. import (
  19. "errors"
  20. "fmt"
  21. "log"
  22. "math/rand"
  23. "net/http"
  24. "reflect"
  25. "runtime"
  26. "k8s.io/kubernetes/pkg/util/net"
  27. )
  28. // chaosrt provides the ability to perform simulations of HTTP client failures
  29. // under the Golang http.Transport interface.
  30. type chaosrt struct {
  31. rt http.RoundTripper
  32. notify ChaosNotifier
  33. c []Chaos
  34. }
  35. // Chaos intercepts requests to a remote HTTP endpoint and can inject arbitrary
  36. // failures.
  37. type Chaos interface {
  38. // Intercept should return true if the normal flow should be skipped, and the
  39. // return response and error used instead. Modifications to the request will
  40. // be ignored, but may be used to make decisions about types of failures.
  41. Intercept(req *http.Request) (bool, *http.Response, error)
  42. }
  43. // ChaosNotifier notifies another component that the ChaosRoundTripper has simulated
  44. // a failure.
  45. type ChaosNotifier interface {
  46. // OnChaos is invoked when a chaotic outcome was triggered. fn is the
  47. // source of Chaos and req was the outgoing request
  48. OnChaos(req *http.Request, c Chaos)
  49. }
  50. // ChaosFunc takes an http.Request and decides whether to alter the response. It
  51. // returns true if it wishes to mutate the response, with a http.Response or
  52. // error.
  53. type ChaosFunc func(req *http.Request) (bool, *http.Response, error)
  54. func (fn ChaosFunc) Intercept(req *http.Request) (bool, *http.Response, error) {
  55. return fn.Intercept(req)
  56. }
  57. func (fn ChaosFunc) String() string {
  58. return runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
  59. }
  60. // NewChaosRoundTripper creates an http.RoundTripper that will intercept requests
  61. // based on the provided Chaos functions. The notifier is invoked when a Chaos
  62. // Intercept is fired.
  63. func NewChaosRoundTripper(rt http.RoundTripper, notify ChaosNotifier, c ...Chaos) http.RoundTripper {
  64. return &chaosrt{rt, notify, c}
  65. }
  66. // RoundTrip gives each ChaosFunc an opportunity to intercept the request. The first
  67. // interceptor wins.
  68. func (rt *chaosrt) RoundTrip(req *http.Request) (*http.Response, error) {
  69. for _, c := range rt.c {
  70. if intercept, resp, err := c.Intercept(req); intercept {
  71. rt.notify.OnChaos(req, c)
  72. return resp, err
  73. }
  74. }
  75. return rt.rt.RoundTrip(req)
  76. }
  77. var _ = net.RoundTripperWrapper(&chaosrt{})
  78. func (rt *chaosrt) WrappedRoundTripper() http.RoundTripper {
  79. return rt.rt
  80. }
  81. // Seed represents a consistent stream of chaos.
  82. type Seed struct {
  83. *rand.Rand
  84. }
  85. // NewSeed creates an object that assists in generating random chaotic events
  86. // based on a deterministic seed.
  87. func NewSeed(seed int64) Seed {
  88. return Seed{rand.New(rand.NewSource(seed))}
  89. }
  90. type pIntercept struct {
  91. Chaos
  92. s Seed
  93. p float64
  94. }
  95. // P returns a ChaosFunc that fires with a probability of p (p between 0.0
  96. // and 1.0 with 0.0 meaning never and 1.0 meaning always).
  97. func (s Seed) P(p float64, c Chaos) Chaos {
  98. return pIntercept{c, s, p}
  99. }
  100. // Intercept intercepts requests with the provided probability p.
  101. func (c pIntercept) Intercept(req *http.Request) (bool, *http.Response, error) {
  102. if c.s.Float64() < c.p {
  103. return c.Chaos.Intercept(req)
  104. }
  105. return false, nil, nil
  106. }
  107. func (c pIntercept) String() string {
  108. return fmt.Sprintf("P{%f %s}", c.p, c.Chaos)
  109. }
  110. // ErrSimulatedConnectionResetByPeer emulates the golang net error when a connection
  111. // is reset by a peer.
  112. // TODO: make this more accurate
  113. // TODO: add other error types
  114. // TODO: add a helper for returning multiple errors randomly.
  115. var ErrSimulatedConnectionResetByPeer = Error{errors.New("connection reset by peer")}
  116. // Error returns the nested error when C() is invoked.
  117. type Error struct {
  118. error
  119. }
  120. // C returns the nested error
  121. func (e Error) Intercept(_ *http.Request) (bool, *http.Response, error) {
  122. return true, nil, e.error
  123. }
  124. // LogChaos is the default ChaosNotifier and writes a message to the Golang log.
  125. var LogChaos = ChaosNotifier(logChaos{})
  126. type logChaos struct{}
  127. func (logChaos) OnChaos(req *http.Request, c Chaos) {
  128. log.Printf("Triggered chaotic behavior for %s %s: %v", req.Method, req.URL.String(), c)
  129. }