toxiproxy 0.1.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.github/probots.yml +2 -0
- data/.github/workflows/ci.yml +19 -0
- data/README.md +20 -6
- data/bin/start-toxiproxy.sh +13 -4
- data/lib/toxiproxy.rb +95 -56
- data/lib/toxiproxy/{collection.rb → proxy_collection.rb} +3 -5
- data/lib/toxiproxy/toxic.rb +34 -36
- data/lib/toxiproxy/toxic_collection.rb +26 -12
- data/lib/toxiproxy/version.rb +1 -1
- data/test/test_helper.rb +2 -0
- data/test/toxiproxy_test.rb +140 -32
- data/toxiproxy.gemspec +4 -1
- metadata +26 -11
- data/circle.yml +0 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 87ada93994d565f7179ecf9b542e20d98c0e8d0a147ebb96ad9a6db91a1af343
|
4
|
+
data.tar.gz: 2f4a626ed0b4db30f596963485749b22a754695bea9d94de3ee872f233eaae1d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e95bfdfa0351fad12ea9b5baf9020ec34bb1948e3d1d97afa9dcc1fc231d15520b1b937a493a7e3a87bdb4ec8e7f42234e22f5318c8fc564ceb7dd8bc7609348
|
7
|
+
data.tar.gz: 944defb2bfee5378844b38a51400164be70f0ee39806d6f5cded3585ddd61ac6f4bb75229786f948bd5b3afc35a437d09c326575f0ac4e89f24a7f880f2badc9
|
data/.github/probots.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
name: Run tests
|
2
|
+
on: [push]
|
3
|
+
jobs:
|
4
|
+
test:
|
5
|
+
runs-on: ubuntu-latest
|
6
|
+
strategy:
|
7
|
+
matrix:
|
8
|
+
ruby: [ '2.5', '2.6', '2.7' ]
|
9
|
+
name: Ruby ${{ matrix.ruby }}
|
10
|
+
steps:
|
11
|
+
- uses: actions/checkout@v2
|
12
|
+
- uses: ruby/setup-ruby@v1
|
13
|
+
with:
|
14
|
+
ruby-version: ${{ matrix.ruby }}
|
15
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
16
|
+
- name: Install and start toxiproxy
|
17
|
+
run: ./bin/start-toxiproxy.sh
|
18
|
+
- name: Run tests
|
19
|
+
run: bundle exec rake test
|
data/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# toxiproxy-ruby
|
2
2
|
|
3
|
+
`toxiproxy-ruby` `1.x` (latest) is compatible with the Toxiproxy `2.x` series.
|
4
|
+
`toxiproxy-ruby` `0.x` is compatible with the Toxiproxy `1.x` series.
|
5
|
+
|
3
6
|
[Toxiproxy](https://github.com/shopify/toxiproxy) is a proxy to simulate network
|
4
7
|
and system conditions. The Ruby API aims to make it simple to write tests that
|
5
8
|
ensure your application behaves appropriately under harsh conditions. Before you
|
@@ -17,14 +20,19 @@ documentation](https://github.com/shopify/toxiproxy)
|
|
17
20
|
|
18
21
|
## Usage
|
19
22
|
|
20
|
-
The Ruby client communicates with the Toxiproxy daemon via HTTP.
|
23
|
+
The Ruby client communicates with the Toxiproxy daemon via HTTP. By default it
|
24
|
+
connects to `http://127.0.0.1:8474`, but you can point to any host:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
Toxiproxy.host = 'http://toxiproxy.local:5665'
|
28
|
+
```
|
21
29
|
|
22
30
|
For example, to simulate 1000ms latency on a database server you can use the
|
23
31
|
`latency` toxic with the `latency` argument (see the Toxiproxy project for a
|
24
32
|
list of all toxics):
|
25
33
|
|
26
34
|
```ruby
|
27
|
-
Toxiproxy[:mysql_master].
|
35
|
+
Toxiproxy[:mysql_master].toxic(:latency, latency: 1000).apply do
|
28
36
|
Shop.first # this took at least 1s
|
29
37
|
end
|
30
38
|
```
|
@@ -55,7 +63,8 @@ Toxiproxy[:cache].upstream(:latency, latency: 1000).apply do
|
|
55
63
|
end
|
56
64
|
```
|
57
65
|
|
58
|
-
|
66
|
+
By default the toxic is applied to the downstream connection, you can be
|
67
|
+
explicit and chain them:
|
59
68
|
|
60
69
|
```ruby
|
61
70
|
Toxiproxy[/redis/].upstream(:slow_close, delay: 100).downstream(:latency, jitter: 300).apply do
|
@@ -79,10 +88,15 @@ Toxiproxy.populate([{
|
|
79
88
|
name: "mysql_read_only",
|
80
89
|
listen: "localhost:21213",
|
81
90
|
upstream: "localhost:3306",
|
82
|
-
})
|
91
|
+
}])
|
83
92
|
```
|
84
93
|
|
85
94
|
This will create the proxies passed, or replace the proxies if they already exist in Toxiproxy.
|
86
95
|
It's recommended to do this early as early in boot as possible, see the
|
87
|
-
[Toxiproxy README](https://github.com/shopify/toxiproxy#
|
88
|
-
proxies, we recommend storing the Toxiproxy configs in a configuration file
|
96
|
+
[Toxiproxy README](https://github.com/shopify/toxiproxy#usage). If you have many
|
97
|
+
proxies, we recommend storing the Toxiproxy configs in a configuration file and
|
98
|
+
deserializing it into `Toxiproxy.populate`.
|
99
|
+
|
100
|
+
If you're doing this in Rails, you may have to do this in `config/boot.rb` (as
|
101
|
+
early in boot as possible) as older versions of `ActiveRecord` establish a
|
102
|
+
database connection as soon as it's loaded.
|
data/bin/start-toxiproxy.sh
CHANGED
@@ -1,8 +1,17 @@
|
|
1
1
|
#!/bin/bash -e
|
2
2
|
|
3
|
-
VERSION='
|
3
|
+
VERSION='v2.1.0'
|
4
|
+
TOXIPROXY_LOG_DIR=${CIRCLE_ARTIFACTS:-'/tmp'}
|
5
|
+
|
6
|
+
if [[ "$OSTYPE" == "linux"* ]]; then
|
7
|
+
DOWNLOAD_TYPE="linux-amd64"
|
8
|
+
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
9
|
+
DOWNLOAD_TYPE="darwin-amd64"
|
10
|
+
fi
|
11
|
+
|
12
|
+
echo "[dowload toxiproxy for $DOWNLOAD_TYPE]"
|
13
|
+
curl --silent -L https://github.com/Shopify/toxiproxy/releases/download/$VERSION/toxiproxy-server-$DOWNLOAD_TYPE -o ./bin/toxiproxy-server
|
4
14
|
|
5
15
|
echo "[start toxiproxy]"
|
6
|
-
|
7
|
-
|
8
|
-
nohup bash -c "./bin/toxiproxy > ${CIRCLE_ARTIFACTS}/toxiproxy.log 2>&1 &"
|
16
|
+
chmod +x ./bin/toxiproxy-server
|
17
|
+
nohup bash -c "./bin/toxiproxy-server > ${TOXIPROXY_LOG_DIR}/toxiproxy.log 2>&1 &"
|
data/lib/toxiproxy.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
1
|
require "json"
|
2
2
|
require "uri"
|
3
3
|
require "net/http"
|
4
|
+
require "forwardable"
|
4
5
|
|
5
|
-
require "toxiproxy/collection"
|
6
6
|
require "toxiproxy/toxic"
|
7
7
|
require "toxiproxy/toxic_collection"
|
8
|
+
require "toxiproxy/proxy_collection"
|
8
9
|
|
9
10
|
class Toxiproxy
|
10
|
-
|
11
|
+
extend SingleForwardable
|
12
|
+
|
13
|
+
DEFAULT_URI = 'http://127.0.0.1:8474'
|
11
14
|
VALID_DIRECTIONS = [:upstream, :downstream]
|
12
15
|
|
13
16
|
class NotFound < StandardError; end
|
@@ -23,29 +26,31 @@ class Toxiproxy
|
|
23
26
|
@enabled = options[:enabled]
|
24
27
|
end
|
25
28
|
|
26
|
-
|
27
|
-
# `define_method` to delegate from Toxiproxy to #all, and from there to the
|
28
|
-
# proxy collection.
|
29
|
-
class << self
|
30
|
-
Collection::METHODS.each do |method|
|
31
|
-
define_method(method) do |*args, &block|
|
32
|
-
self.all.send(method, *args, &block)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
29
|
+
def_delegators :all, *ProxyCollection::METHODS
|
36
30
|
|
37
31
|
# Re-enables all proxies and disables all toxics.
|
38
32
|
def self.reset
|
39
|
-
request = Net::HTTP::
|
40
|
-
|
33
|
+
request = Net::HTTP::Post.new("/reset")
|
34
|
+
request["Content-Type"] = "application/json"
|
35
|
+
|
36
|
+
response = http_request(request)
|
41
37
|
assert_response(response)
|
42
38
|
self
|
43
39
|
end
|
44
40
|
|
41
|
+
def self.version
|
42
|
+
return false unless running?
|
43
|
+
|
44
|
+
request = Net::HTTP::Get.new("/version")
|
45
|
+
response = http_request(request)
|
46
|
+
assert_response(response)
|
47
|
+
response.body
|
48
|
+
end
|
49
|
+
|
45
50
|
# Returns a collection of all currently active Toxiproxies.
|
46
51
|
def self.all
|
47
52
|
request = Net::HTTP::Get.new("/proxies")
|
48
|
-
response =
|
53
|
+
response = http_request(request)
|
49
54
|
assert_response(response)
|
50
55
|
|
51
56
|
proxies = JSON.parse(response.body).map { |name, attrs|
|
@@ -57,7 +62,12 @@ class Toxiproxy
|
|
57
62
|
})
|
58
63
|
}
|
59
64
|
|
60
|
-
|
65
|
+
ProxyCollection.new(proxies)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Sets the toxiproxy host to use.
|
69
|
+
def self.host=(host)
|
70
|
+
@uri = host.is_a?(::URI) ? host : ::URI.parse(host)
|
61
71
|
end
|
62
72
|
|
63
73
|
# Convenience method to create a proxy.
|
@@ -88,59 +98,69 @@ class Toxiproxy
|
|
88
98
|
def self.populate(*proxies)
|
89
99
|
proxies = proxies.first if proxies.first.is_a?(Array)
|
90
100
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
101
|
+
request = Net::HTTP::Post.new("/populate")
|
102
|
+
request.body = proxies.to_json
|
103
|
+
request["Content-Type"] = "application/json"
|
104
|
+
|
105
|
+
response = http_request(request)
|
106
|
+
assert_response(response)
|
107
|
+
|
108
|
+
proxies = JSON.parse(response.body).fetch('proxies', []).map do |attrs|
|
109
|
+
self.new({
|
110
|
+
upstream: attrs["upstream"],
|
111
|
+
listen: attrs["listen"],
|
112
|
+
name: attrs["name"],
|
113
|
+
enabled: attrs["enabled"]
|
114
|
+
})
|
115
|
+
end
|
116
|
+
|
117
|
+
ProxyCollection.new(proxies)
|
99
118
|
end
|
100
119
|
|
101
120
|
def self.running?
|
102
|
-
TCPSocket.new(
|
121
|
+
TCPSocket.new(uri.host, uri.port).close
|
103
122
|
true
|
104
123
|
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
105
124
|
false
|
106
125
|
end
|
107
126
|
|
108
127
|
# Set an upstream toxic.
|
109
|
-
def upstream(
|
110
|
-
return @upstream unless
|
128
|
+
def upstream(type = nil, attrs = {})
|
129
|
+
return @upstream unless type # also alias for the upstream endpoint
|
111
130
|
|
112
131
|
collection = ToxicCollection.new([self])
|
113
|
-
collection.upstream(
|
132
|
+
collection.upstream(type, attrs)
|
114
133
|
collection
|
115
134
|
end
|
116
135
|
|
117
136
|
# Set a downstream toxic.
|
118
|
-
def downstream(
|
137
|
+
def downstream(type, attrs = {})
|
119
138
|
collection = ToxicCollection.new([self])
|
120
|
-
collection.downstream(
|
139
|
+
collection.downstream(type, attrs)
|
121
140
|
collection
|
122
141
|
end
|
142
|
+
alias_method :toxic, :downstream
|
143
|
+
alias_method :toxicate, :downstream
|
123
144
|
|
124
145
|
# Simulates the endpoint is down, by closing the connection and no
|
125
146
|
# longer accepting connections. This is useful to simulate critical system
|
126
147
|
# failure, such as a data store becoming completely unavailable.
|
127
148
|
def down(&block)
|
128
149
|
disable
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
enable
|
133
|
-
end
|
150
|
+
yield
|
151
|
+
ensure
|
152
|
+
enable
|
134
153
|
end
|
135
154
|
|
136
155
|
# Disables a Toxiproxy. This will drop all active connections and stop the proxy from listening.
|
137
156
|
def disable
|
138
157
|
request = Net::HTTP::Post.new("/proxies/#{name}")
|
158
|
+
request["Content-Type"] = "application/json"
|
139
159
|
|
140
160
|
hash = {enabled: false}
|
141
161
|
request.body = hash.to_json
|
142
162
|
|
143
|
-
response =
|
163
|
+
response = http_request(request)
|
144
164
|
assert_response(response)
|
145
165
|
self
|
146
166
|
end
|
@@ -148,11 +168,12 @@ class Toxiproxy
|
|
148
168
|
# Enables a Toxiproxy. This will cause the proxy to start listening again.
|
149
169
|
def enable
|
150
170
|
request = Net::HTTP::Post.new("/proxies/#{name}")
|
171
|
+
request["Content-Type"] = "application/json"
|
151
172
|
|
152
173
|
hash = {enabled: true}
|
153
174
|
request.body = hash.to_json
|
154
175
|
|
155
|
-
response =
|
176
|
+
response = http_request(request)
|
156
177
|
assert_response(response)
|
157
178
|
self
|
158
179
|
end
|
@@ -162,11 +183,12 @@ class Toxiproxy
|
|
162
183
|
# of this connection.
|
163
184
|
def create
|
164
185
|
request = Net::HTTP::Post.new("/proxies")
|
186
|
+
request["Content-Type"] = "application/json"
|
165
187
|
|
166
188
|
hash = {upstream: upstream, name: name, listen: listen, enabled: enabled}
|
167
189
|
request.body = hash.to_json
|
168
190
|
|
169
|
-
response =
|
191
|
+
response = http_request(request)
|
170
192
|
assert_response(response)
|
171
193
|
|
172
194
|
new = JSON.parse(response.body)
|
@@ -178,37 +200,54 @@ class Toxiproxy
|
|
178
200
|
# Destroys a Toxiproxy.
|
179
201
|
def destroy
|
180
202
|
request = Net::HTTP::Delete.new("/proxies/#{name}")
|
181
|
-
response =
|
203
|
+
response = http_request(request)
|
182
204
|
assert_response(response)
|
183
205
|
self
|
184
206
|
end
|
185
207
|
|
186
|
-
# Returns
|
187
|
-
def toxics
|
188
|
-
|
189
|
-
|
190
|
-
end
|
191
|
-
|
192
|
-
request = Net::HTTP::Get.new("/proxies/#{name}/#{direction}/toxics")
|
193
|
-
response = http.request(request)
|
208
|
+
# Returns an array of the current toxics for a direction.
|
209
|
+
def toxics
|
210
|
+
request = Net::HTTP::Get.new("/proxies/#{name}/toxics")
|
211
|
+
response = http_request(request)
|
194
212
|
assert_response(response)
|
195
213
|
|
196
|
-
|
197
|
-
Toxic.new(
|
198
|
-
|
214
|
+
JSON.parse(response.body).map { |attrs|
|
215
|
+
Toxic.new(
|
216
|
+
type: attrs['type'],
|
217
|
+
name: attrs['name'],
|
199
218
|
proxy: self,
|
200
|
-
|
201
|
-
|
202
|
-
|
219
|
+
stream: attrs['stream'],
|
220
|
+
toxicity: attrs['toxicity'],
|
221
|
+
attributes: attrs['attributes'],
|
222
|
+
)
|
203
223
|
}
|
204
|
-
|
205
|
-
toxics
|
206
224
|
end
|
207
225
|
|
208
226
|
private
|
209
227
|
|
228
|
+
def self.http_request(request)
|
229
|
+
ensure_webmock_whitelists_toxiproxy if defined? WebMock
|
230
|
+
http.request(request)
|
231
|
+
end
|
232
|
+
|
233
|
+
def http_request(request)
|
234
|
+
self.class.http_request(request)
|
235
|
+
end
|
236
|
+
|
237
|
+
def self.ensure_webmock_whitelists_toxiproxy
|
238
|
+
endpoint = "#{uri.host}:#{uri.port}"
|
239
|
+
WebMock::Config.instance.allow ||= []
|
240
|
+
unless WebMock::Config.instance.allow.include?(endpoint)
|
241
|
+
WebMock::Config.instance.allow << endpoint
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def self.uri
|
246
|
+
@uri ||= ::URI.parse(DEFAULT_URI)
|
247
|
+
end
|
248
|
+
|
210
249
|
def self.http
|
211
|
-
@http ||= Net::HTTP.new(
|
250
|
+
@http ||= Net::HTTP.new(uri.host, uri.port)
|
212
251
|
end
|
213
252
|
|
214
253
|
def http
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require "forwardable"
|
2
|
-
|
3
1
|
class Toxiproxy
|
4
2
|
# ProxyCollection represents a set of proxies. This allows to easily perform
|
5
3
|
# actions on every proxy in the collection.
|
@@ -9,7 +7,7 @@ class Toxiproxy
|
|
9
7
|
# Collection instead of an Array (see MRI). Instead, we delegate methods where
|
10
8
|
# it doesn't matter and only allow the filtering methods that really make
|
11
9
|
# sense on a proxy collection.
|
12
|
-
class
|
10
|
+
class ProxyCollection
|
13
11
|
extend Forwardable
|
14
12
|
|
15
13
|
DELEGATED_METHODS = [:length, :size, :count, :find, :each, :map]
|
@@ -47,13 +45,13 @@ class Toxiproxy
|
|
47
45
|
toxics.downstream(toxic, attrs)
|
48
46
|
toxics
|
49
47
|
end
|
48
|
+
alias_method :toxicate, :downstream
|
49
|
+
alias_method :toxic, :downstream
|
50
50
|
|
51
|
-
# Disables all proxies in the collection.
|
52
51
|
def disable
|
53
52
|
@collection.each(&:disable)
|
54
53
|
end
|
55
54
|
|
56
|
-
# Enables all proxies in the collection.
|
57
55
|
def enable
|
58
56
|
@collection.each(&:enable)
|
59
57
|
end
|
data/lib/toxiproxy/toxic.rb
CHANGED
@@ -1,51 +1,49 @@
|
|
1
1
|
class Toxiproxy
|
2
2
|
class Toxic
|
3
|
-
attr_reader :name, :
|
4
|
-
|
5
|
-
|
6
|
-
def initialize(
|
7
|
-
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@
|
3
|
+
attr_reader :name, :type, :stream, :proxy
|
4
|
+
attr_accessor :attributes, :toxicity
|
5
|
+
|
6
|
+
def initialize(attrs)
|
7
|
+
raise "Toxic type is required" unless attrs[:type]
|
8
|
+
@type = attrs[:type]
|
9
|
+
@stream = attrs[:stream] || 'downstream'
|
10
|
+
@name = attrs[:name] || "#{@type}_#{@stream}"
|
11
|
+
@proxy = attrs[:proxy]
|
12
|
+
@toxicity = attrs[:toxicity] || 1.0
|
13
|
+
@attributes = attrs[:attributes] || {}
|
11
14
|
end
|
12
15
|
|
13
|
-
def
|
14
|
-
|
15
|
-
|
16
|
+
def save
|
17
|
+
request = Net::HTTP::Post.new("/proxies/#{proxy.name}/toxics")
|
18
|
+
request["Content-Type"] = "application/json"
|
16
19
|
|
17
|
-
|
18
|
-
attrs['enabled'] = true
|
19
|
-
save
|
20
|
-
end
|
20
|
+
request.body = as_json
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
save
|
25
|
-
end
|
22
|
+
response = Toxiproxy.http_request(request)
|
23
|
+
Toxiproxy.assert_response(response)
|
26
24
|
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
json = JSON.parse(response.body)
|
26
|
+
@attributes = json['attributes']
|
27
|
+
@toxicity = json['toxicity']
|
30
28
|
|
31
|
-
|
32
|
-
attrs[name] = value
|
29
|
+
self
|
33
30
|
end
|
34
31
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
request = Net::HTTP::Post.new("/proxies/#{proxy.name}/#{direction}/toxics/#{name}")
|
40
|
-
|
41
|
-
request.body = attrs.to_json
|
42
|
-
|
43
|
-
response = Toxiproxy.http.request(request)
|
32
|
+
def destroy
|
33
|
+
request = Net::HTTP::Delete.new("/proxies/#{proxy.name}/toxics/#{name}")
|
34
|
+
response = Toxiproxy.http_request(request)
|
44
35
|
Toxiproxy.assert_response(response)
|
45
|
-
|
46
|
-
@attrs = JSON.parse(response.body)
|
47
|
-
|
48
36
|
self
|
49
37
|
end
|
38
|
+
|
39
|
+
def as_json
|
40
|
+
{
|
41
|
+
name: name,
|
42
|
+
type: type,
|
43
|
+
stream: stream,
|
44
|
+
toxicity: toxicity,
|
45
|
+
attributes: attributes,
|
46
|
+
}.to_json
|
47
|
+
end
|
50
48
|
end
|
51
49
|
end
|
@@ -13,34 +13,48 @@ class Toxiproxy
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def apply(&block)
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
names = toxics.group_by { |t| [t.name, t.proxy.name] }
|
17
|
+
dups = names.values.select { |toxics| toxics.length > 1 }
|
18
|
+
if !dups.empty?
|
19
|
+
raise ArgumentError, "There are two toxics with the name #{dups.first[0]} for proxy #{dups.first[1]}, please override the default name (<type>_<direction>)"
|
20
|
+
end
|
21
|
+
|
22
|
+
begin
|
23
|
+
@toxics.each(&:save)
|
24
|
+
yield
|
25
|
+
ensure
|
26
|
+
@toxics.each(&:destroy)
|
27
|
+
end
|
20
28
|
end
|
21
29
|
|
22
|
-
def upstream(
|
30
|
+
def upstream(type, attrs = {})
|
23
31
|
proxies.each do |proxy|
|
24
32
|
toxics << Toxic.new(
|
25
|
-
name:
|
33
|
+
name: attrs.delete('name') || attrs.delete(:name),
|
34
|
+
type: type,
|
26
35
|
proxy: proxy,
|
27
|
-
|
28
|
-
|
36
|
+
stream: :upstream,
|
37
|
+
toxicity: attrs.delete('toxicitiy') || attrs.delete(:toxicity),
|
38
|
+
attributes: attrs
|
29
39
|
)
|
30
40
|
end
|
31
41
|
self
|
32
42
|
end
|
33
43
|
|
34
|
-
def downstream(
|
44
|
+
def downstream(type, attrs = {})
|
35
45
|
proxies.each do |proxy|
|
36
46
|
toxics << Toxic.new(
|
37
|
-
name:
|
47
|
+
name: attrs.delete('name') || attrs.delete(:name),
|
48
|
+
type: type,
|
38
49
|
proxy: proxy,
|
39
|
-
|
40
|
-
|
50
|
+
stream: :downstream,
|
51
|
+
toxicity: attrs.delete('toxicitiy') || attrs.delete(:toxicity),
|
52
|
+
attributes: attrs
|
41
53
|
)
|
42
54
|
end
|
43
55
|
self
|
44
56
|
end
|
57
|
+
alias_method :toxic, :downstream
|
58
|
+
alias_method :toxicate, :downstream
|
45
59
|
end
|
46
60
|
end
|
data/lib/toxiproxy/version.rb
CHANGED
data/test/test_helper.rb
CHANGED
data/test/toxiproxy_test.rb
CHANGED
@@ -24,27 +24,47 @@ class ToxiproxyTest < MiniTest::Unit::TestCase
|
|
24
24
|
assert_equal "test_mysql_master", proxy.name
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
27
|
+
def test_proxy_not_running_with_bad_host
|
28
|
+
Toxiproxy.host = 'http://0.0.0.0:12345'
|
29
|
+
assert !Toxiproxy.running?, 'toxiproxy should not be running'
|
30
|
+
ensure
|
31
|
+
Toxiproxy.host = Toxiproxy::DEFAULT_URI
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_enable_and_disable_proxy_with_toxic
|
28
35
|
with_tcpserver do |port|
|
29
36
|
proxy = Toxiproxy.create(upstream: "localhost:#{port}", name: "test_rubby_server")
|
30
37
|
listen_addr = proxy.listen
|
31
38
|
|
32
|
-
Toxiproxy::Toxic.new(
|
33
|
-
name: 'latency',
|
34
|
-
proxy: proxy,
|
35
|
-
direction: :upstream,
|
36
|
-
attrs: {'latency' => 123}
|
37
|
-
).enable
|
39
|
+
Toxiproxy::Toxic.new(type: 'latency', attributes: { latency: 123 }, proxy: proxy).save
|
38
40
|
|
39
41
|
proxy.disable
|
40
42
|
assert_proxy_unavailable proxy
|
41
43
|
proxy.enable
|
42
44
|
assert_proxy_available proxy
|
43
45
|
|
44
|
-
latency = proxy.toxics
|
45
|
-
assert_equal 123, latency['latency']
|
46
|
-
assert latency.enabled?
|
46
|
+
latency = proxy.toxics.find { |toxic| toxic.name == 'latency_downstream' }
|
47
47
|
|
48
|
+
assert_equal 123, latency.attributes['latency']
|
49
|
+
assert_equal listen_addr, proxy.listen
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_delete_toxic
|
54
|
+
with_tcpserver do |port|
|
55
|
+
proxy = Toxiproxy.create(upstream: "localhost:#{port}", name: "test_rubby_server")
|
56
|
+
listen_addr = proxy.listen
|
57
|
+
|
58
|
+
latency = Toxiproxy::Toxic.new(type: 'latency', attributes: { latency: 123 }, proxy: proxy).save
|
59
|
+
|
60
|
+
assert_proxy_available proxy
|
61
|
+
|
62
|
+
latency = proxy.toxics.find { |toxic| toxic.name == 'latency_downstream' }
|
63
|
+
assert_equal 123, latency.attributes['latency']
|
64
|
+
|
65
|
+
latency.destroy
|
66
|
+
|
67
|
+
assert proxy.toxics.empty?
|
48
68
|
assert_equal listen_addr, proxy.listen
|
49
69
|
end
|
50
70
|
end
|
@@ -57,20 +77,12 @@ class ToxiproxyTest < MiniTest::Unit::TestCase
|
|
57
77
|
proxy.disable
|
58
78
|
assert_proxy_unavailable proxy
|
59
79
|
|
60
|
-
Toxiproxy::Toxic.new(
|
61
|
-
name: 'latency',
|
62
|
-
proxy: proxy,
|
63
|
-
direction: :upstream,
|
64
|
-
attrs: {'latency' => 125}
|
65
|
-
).enable
|
80
|
+
Toxiproxy::Toxic.new(type: 'latency', attributes: { latency: 123 }, proxy: proxy).save
|
66
81
|
|
67
82
|
Toxiproxy.reset
|
68
83
|
assert_proxy_available proxy
|
69
84
|
|
70
|
-
|
71
|
-
assert_equal 125, latency['latency']
|
72
|
-
assert !latency.enabled?
|
73
|
-
|
85
|
+
assert proxy.toxics.empty?
|
74
86
|
assert_equal listen_addr, proxy.listen
|
75
87
|
end
|
76
88
|
end
|
@@ -97,7 +109,7 @@ class ToxiproxyTest < MiniTest::Unit::TestCase
|
|
97
109
|
end
|
98
110
|
|
99
111
|
def test_proxies_all_returns_proxy_collection
|
100
|
-
assert_instance_of Toxiproxy::
|
112
|
+
assert_instance_of Toxiproxy::ProxyCollection, Toxiproxy.all
|
101
113
|
end
|
102
114
|
|
103
115
|
def test_down_on_proxy_collection_disables_entire_collection
|
@@ -147,7 +159,7 @@ class ToxiproxyTest < MiniTest::Unit::TestCase
|
|
147
159
|
proxies = Toxiproxy.select { |p| p.upstream == "localhost:#{port}" }
|
148
160
|
|
149
161
|
assert_equal 1, proxies.size
|
150
|
-
assert_instance_of Toxiproxy::
|
162
|
+
assert_instance_of Toxiproxy::ProxyCollection, proxies
|
151
163
|
end
|
152
164
|
end
|
153
165
|
|
@@ -158,7 +170,7 @@ class ToxiproxyTest < MiniTest::Unit::TestCase
|
|
158
170
|
proxies = Toxiproxy.grep(/\Atest/)
|
159
171
|
|
160
172
|
assert_equal 1, proxies.size
|
161
|
-
assert_instance_of Toxiproxy::
|
173
|
+
assert_instance_of Toxiproxy::ProxyCollection, proxies
|
162
174
|
end
|
163
175
|
end
|
164
176
|
|
@@ -169,7 +181,7 @@ class ToxiproxyTest < MiniTest::Unit::TestCase
|
|
169
181
|
proxies = Toxiproxy[/\Atest/]
|
170
182
|
|
171
183
|
assert_equal 1, proxies.size
|
172
|
-
assert_instance_of Toxiproxy::
|
184
|
+
assert_instance_of Toxiproxy::ProxyCollection, proxies
|
173
185
|
end
|
174
186
|
end
|
175
187
|
|
@@ -211,6 +223,24 @@ class ToxiproxyTest < MiniTest::Unit::TestCase
|
|
211
223
|
end
|
212
224
|
end
|
213
225
|
|
226
|
+
def test_toxic_applies_a_downstream_toxic
|
227
|
+
with_tcpserver(receive: true) do |port|
|
228
|
+
proxy = Toxiproxy.create(upstream: "localhost:#{port}", name: "test_proxy")
|
229
|
+
|
230
|
+
proxy.toxic(:latency, latency: 100).apply do
|
231
|
+
latency = proxy.toxics.find { |toxic| toxic.name == 'latency_downstream' }
|
232
|
+
|
233
|
+
assert_equal 100, latency.attributes['latency']
|
234
|
+
assert_equal 'downstream', latency.stream
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def test_toxic_default_name_is_type_and_stream
|
240
|
+
toxic = Toxiproxy::Toxic.new(type: "latency", stream: "downstream")
|
241
|
+
assert_equal "latency_downstream", toxic.name
|
242
|
+
end
|
243
|
+
|
214
244
|
def test_apply_prolong_toxics
|
215
245
|
with_tcpserver(receive: true) do |port|
|
216
246
|
proxy = Toxiproxy.create(upstream: "localhost:#{port}", name: "test_proxy")
|
@@ -328,34 +358,112 @@ class ToxiproxyTest < MiniTest::Unit::TestCase
|
|
328
358
|
end
|
329
359
|
|
330
360
|
def test_populate_creates_proxies_update_upstream
|
331
|
-
|
332
|
-
|
361
|
+
proxy_name = "test_toxiproxy_populate1"
|
362
|
+
proxies_config = [{
|
363
|
+
name: proxy_name,
|
333
364
|
upstream: "localhost:3306",
|
334
365
|
listen: "localhost:22222",
|
335
366
|
},
|
336
367
|
]
|
337
368
|
|
338
|
-
proxies = Toxiproxy.populate(
|
369
|
+
proxies = Toxiproxy.populate(proxies_config)
|
339
370
|
|
340
|
-
|
341
|
-
name:
|
371
|
+
proxies_config = [{
|
372
|
+
name: proxy_name,
|
342
373
|
upstream: "localhost:3307",
|
343
374
|
listen: "localhost:22222",
|
344
375
|
},
|
345
376
|
]
|
346
377
|
|
347
|
-
proxies2 = Toxiproxy.populate(
|
378
|
+
proxies2 = Toxiproxy.populate(proxies_config)
|
348
379
|
|
349
|
-
|
350
|
-
|
380
|
+
refute_equal proxies.find(name: proxy_name).first.upstream,
|
381
|
+
proxies2.find(name: proxy_name).first.upstream
|
382
|
+
|
383
|
+
proxies2.each do |proxy|
|
384
|
+
assert_proxy_available(proxy)
|
385
|
+
end
|
351
386
|
end
|
352
387
|
|
353
388
|
def test_running_helper
|
354
389
|
assert_equal true, Toxiproxy.running?
|
355
390
|
end
|
356
391
|
|
392
|
+
def test_version
|
393
|
+
assert_instance_of String, Toxiproxy.version
|
394
|
+
end
|
395
|
+
|
396
|
+
def test_multiple_of_same_toxic_type
|
397
|
+
with_tcpserver(receive: true) do |port|
|
398
|
+
proxy = Toxiproxy.create(upstream: "localhost:#{port}", name: "test_proxy")
|
399
|
+
proxy.toxic(:latency, latency: 100).toxic(:latency, latency: 100, name: "second_latency_downstream").apply do
|
400
|
+
before = Time.now
|
401
|
+
|
402
|
+
socket = connect_to_proxy(proxy)
|
403
|
+
socket.write("omg\n")
|
404
|
+
socket.flush
|
405
|
+
socket.gets
|
406
|
+
|
407
|
+
passed = Time.now - before
|
408
|
+
|
409
|
+
assert_in_delta passed, 0.200, 0.01
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
def test_multiple_of_same_toxic_type_with_same_name
|
415
|
+
with_tcpserver(receive: true) do |port|
|
416
|
+
proxy = Toxiproxy.create(upstream: "localhost:#{port}", name: "test_proxy")
|
417
|
+
|
418
|
+
assert_raises ArgumentError do
|
419
|
+
proxy.toxic(:latency, latency: 100).toxic(:latency, latency: 100).apply { }
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
def test_invalid_direction
|
425
|
+
with_tcpserver(receive: true) do |port|
|
426
|
+
proxy = Toxiproxy.create(upstream: "localhost:#{port}", name: "test_rubby_server")
|
427
|
+
|
428
|
+
assert_raises Toxiproxy::InvalidToxic do
|
429
|
+
Toxiproxy::Toxic.new(type: 'latency', attributes: { latency: 123 }, proxy: proxy, stream: 'lolstream').save
|
430
|
+
end
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
def test_whitelists_webmock_when_allow_is_nil
|
435
|
+
with_webmock_enabled do
|
436
|
+
WebMock::Config.instance.allow = nil
|
437
|
+
Toxiproxy.version # This should initialize the list.
|
438
|
+
assert WebMock::Config.instance.allow.include?(@endpoint)
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
def test_whitelisting_webmock_does_not_override_other_configuration
|
443
|
+
with_webmock_enabled do
|
444
|
+
WebMock::Config.instance.allow = ['some-other-host']
|
445
|
+
Toxiproxy.version
|
446
|
+
# 'some-other-host' should not be overriden.
|
447
|
+
assert WebMock::Config.instance.allow.include?('some-other-host')
|
448
|
+
assert WebMock::Config.instance.allow.include?(@endpoint)
|
449
|
+
|
450
|
+
Toxiproxy.version
|
451
|
+
# Endpoint should not be duplicated.
|
452
|
+
assert_equal 1, WebMock::Config.instance.allow.count(@endpoint)
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
357
456
|
private
|
358
457
|
|
458
|
+
def with_webmock_enabled
|
459
|
+
WebMock.enable!
|
460
|
+
WebMock.disable_net_connect!
|
461
|
+
@endpoint = "#{Toxiproxy.uri.host}:#{Toxiproxy.uri.port}"
|
462
|
+
yield
|
463
|
+
ensure
|
464
|
+
WebMock.disable!
|
465
|
+
end
|
466
|
+
|
359
467
|
def assert_proxy_available(proxy)
|
360
468
|
connect_to_proxy proxy
|
361
469
|
end
|
data/toxiproxy.gemspec
CHANGED
@@ -10,11 +10,14 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.homepage = "https://github.com/Shopify/toxiproxy"
|
11
11
|
spec.license = "MIT"
|
12
12
|
|
13
|
+
spec.metadata['allowed_push_host'] = 'https://rubygems.org'
|
14
|
+
|
13
15
|
spec.files = `git ls-files`.split("\n")
|
14
16
|
spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
17
|
spec.require_paths = ["lib"]
|
16
18
|
|
17
|
-
spec.add_development_dependency "bundler"
|
19
|
+
spec.add_development_dependency "bundler"
|
18
20
|
spec.add_development_dependency "minitest"
|
19
21
|
spec.add_development_dependency "rake"
|
22
|
+
spec.add_development_dependency "webmock"
|
20
23
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: toxiproxy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simon Eskildsen
|
@@ -9,22 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2020-10-27 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
-
- - "
|
18
|
+
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '
|
20
|
+
version: '0'
|
21
21
|
type: :development
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
|
-
- - "
|
25
|
+
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '
|
27
|
+
version: '0'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: minitest
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,21 +53,36 @@ dependencies:
|
|
53
53
|
- - ">="
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: webmock
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
56
70
|
description: A Ruby library for controlling Toxiproxy. Can be used in resiliency testing.
|
57
71
|
email: simon.eskildsen@shopify.com
|
58
72
|
executables: []
|
59
73
|
extensions: []
|
60
74
|
extra_rdoc_files: []
|
61
75
|
files:
|
76
|
+
- ".github/probots.yml"
|
77
|
+
- ".github/workflows/ci.yml"
|
62
78
|
- ".gitignore"
|
63
79
|
- Gemfile
|
64
80
|
- LICENSE
|
65
81
|
- README.md
|
66
82
|
- Rakefile
|
67
83
|
- bin/start-toxiproxy.sh
|
68
|
-
- circle.yml
|
69
84
|
- lib/toxiproxy.rb
|
70
|
-
- lib/toxiproxy/
|
85
|
+
- lib/toxiproxy/proxy_collection.rb
|
71
86
|
- lib/toxiproxy/toxic.rb
|
72
87
|
- lib/toxiproxy/toxic_collection.rb
|
73
88
|
- lib/toxiproxy/version.rb
|
@@ -78,7 +93,8 @@ files:
|
|
78
93
|
homepage: https://github.com/Shopify/toxiproxy
|
79
94
|
licenses:
|
80
95
|
- MIT
|
81
|
-
metadata:
|
96
|
+
metadata:
|
97
|
+
allowed_push_host: https://rubygems.org
|
82
98
|
post_install_message:
|
83
99
|
rdoc_options: []
|
84
100
|
require_paths:
|
@@ -94,8 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
110
|
- !ruby/object:Gem::Version
|
95
111
|
version: '0'
|
96
112
|
requirements: []
|
97
|
-
|
98
|
-
rubygems_version: 2.2.3
|
113
|
+
rubygems_version: 3.0.3
|
99
114
|
signing_key:
|
100
115
|
specification_version: 4
|
101
116
|
summary: Ruby library for Toxiproxy
|