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