toxiproxy 0.1.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: c4abc83dd0b261de59236fbfe5a2e3a0660ca2b0
4
- data.tar.gz: 67d9083dd5672fd70cf53365b5d550d86edfca82
2
+ SHA256:
3
+ metadata.gz: 87ada93994d565f7179ecf9b542e20d98c0e8d0a147ebb96ad9a6db91a1af343
4
+ data.tar.gz: 2f4a626ed0b4db30f596963485749b22a754695bea9d94de3ee872f233eaae1d
5
5
  SHA512:
6
- metadata.gz: f0cd49f6f4b93c88a532ca43087a8c54a46907210ccf5693d970b3f8edbdf73a6a5efc550623a8e9ac9fe02be8a59c5790403fc59a52f662907c1a11aa8aa752
7
- data.tar.gz: d6dd0670c4505efdf609e41e8938053a6318595ea8176a89724d5e768156ec9f03d5e8043442dd952af83f3f42a54ec110dd6cd3754a7035b9c161719578a1f2
6
+ metadata.gz: e95bfdfa0351fad12ea9b5baf9020ec34bb1948e3d1d97afa9dcc1fc231d15520b1b937a493a7e3a87bdb4ec8e7f42234e22f5318c8fc564ceb7dd8bc7609348
7
+ data.tar.gz: 944defb2bfee5378844b38a51400164be70f0ee39806d6f5cded3585ddd61ac6f4bb75229786f948bd5b3afc35a437d09c326575f0ac4e89f24a7f880f2badc9
@@ -0,0 +1,2 @@
1
+ enabled:
2
+ - cla
@@ -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].downstream(:latency, latency: 1000).apply do
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
- You can apply many toxics to many connections:
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#Usage). If you have many
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.
@@ -1,8 +1,17 @@
1
1
  #!/bin/bash -e
2
2
 
3
- VERSION='21a264cc75549c3ae837b606eb6e17ea'
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
- curl --silent http://shopify-vagrant.s3.amazonaws.com/toxiproxy/toxiproxy-$VERSION -o ./bin/toxiproxy
7
- chmod +x ./bin/toxiproxy
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 &"
@@ -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
- URI = ::URI.parse("http://127.0.0.1:8474")
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
- # Forwardable doesn't support delegating class methods, so we resort to
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::Get.new("/reset")
40
- response = http.request(request)
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 = http.request(request)
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
- Collection.new(proxies)
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
- proxies.map { |proxy|
92
- existing = find_by_name(proxy[:name])
93
- if existing && (existing.upstream != proxy[:upstream] || existing.listen != proxy[:listen])
94
- existing.destroy
95
- existing = false
96
- end
97
- self.create(proxy) unless existing
98
- }.compact
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(URI.host, URI.port).close
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(toxic = nil, attrs = {})
110
- return @upstream unless toxic
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(toxic, attrs)
132
+ collection.upstream(type, attrs)
114
133
  collection
115
134
  end
116
135
 
117
136
  # Set a downstream toxic.
118
- def downstream(toxic, attrs = {})
137
+ def downstream(type, attrs = {})
119
138
  collection = ToxicCollection.new([self])
120
- collection.downstream(toxic, attrs)
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
- begin
130
- yield
131
- ensure
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 = http.request(request)
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 = http.request(request)
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 = http.request(request)
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 = http.request(request)
203
+ response = http_request(request)
182
204
  assert_response(response)
183
205
  self
184
206
  end
185
207
 
186
- # Returns a collection of the current toxics for a direction.
187
- def toxics(direction)
188
- unless VALID_DIRECTIONS.include?(direction.to_sym)
189
- raise InvalidToxic, "Toxic direction must be one of: [#{VALID_DIRECTIONS.join(', ')}], got: #{direction}"
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
- toxics = JSON.parse(response.body).map { |name, attrs|
197
- Toxic.new({
198
- name: name,
214
+ JSON.parse(response.body).map { |attrs|
215
+ Toxic.new(
216
+ type: attrs['type'],
217
+ name: attrs['name'],
199
218
  proxy: self,
200
- direction: direction,
201
- attrs: attrs
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(URI.host, URI.port)
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 Collection
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
@@ -1,51 +1,49 @@
1
1
  class Toxiproxy
2
2
  class Toxic
3
- attr_reader :name, :proxy, :direction
4
- attr_reader :attrs
5
-
6
- def initialize(options)
7
- @proxy = options[:proxy]
8
- @name = options[:name]
9
- @direction = options[:direction]
10
- @attrs = options[:attrs] || {}
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 enabled?
14
- attrs['enabled']
15
- end
16
+ def save
17
+ request = Net::HTTP::Post.new("/proxies/#{proxy.name}/toxics")
18
+ request["Content-Type"] = "application/json"
16
19
 
17
- def enable
18
- attrs['enabled'] = true
19
- save
20
- end
20
+ request.body = as_json
21
21
 
22
- def disable
23
- attrs['enabled'] = false
24
- save
25
- end
22
+ response = Toxiproxy.http_request(request)
23
+ Toxiproxy.assert_response(response)
26
24
 
27
- def [](name)
28
- attrs[name]
29
- end
25
+ json = JSON.parse(response.body)
26
+ @attributes = json['attributes']
27
+ @toxicity = json['toxicity']
30
28
 
31
- def []=(name, value)
32
- attrs[name] = value
29
+ self
33
30
  end
34
31
 
35
- def save
36
- unless VALID_DIRECTIONS.include?(direction.to_sym)
37
- raise InvalidToxic, "Toxic direction must be one of: [#{VALID_DIRECTIONS.join(', ')}], got: #{direction}"
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
- @toxics.each(&:enable)
17
- yield
18
- ensure
19
- @toxics.each(&:disable)
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(toxic_name, attrs = {})
30
+ def upstream(type, attrs = {})
23
31
  proxies.each do |proxy|
24
32
  toxics << Toxic.new(
25
- name: toxic_name,
33
+ name: attrs.delete('name') || attrs.delete(:name),
34
+ type: type,
26
35
  proxy: proxy,
27
- direction: :upstream,
28
- attrs: attrs
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(toxic_name, attrs = {})
44
+ def downstream(type, attrs = {})
35
45
  proxies.each do |proxy|
36
46
  toxics << Toxic.new(
37
- name: toxic_name,
47
+ name: attrs.delete('name') || attrs.delete(:name),
48
+ type: type,
38
49
  proxy: proxy,
39
- direction: :downstream,
40
- attrs: attrs
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
@@ -1,3 +1,3 @@
1
1
  class Toxiproxy
2
- VERSION = "0.1.3"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -1,2 +1,4 @@
1
1
  require 'minitest/autorun'
2
2
  require_relative "../lib/toxiproxy"
3
+ require 'webmock/minitest'
4
+ WebMock.disable!
@@ -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 test_enable_and_disable_proxy
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(:upstream).find { |toxic| toxic.name == 'latency' }
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
- latency = proxy.toxics(:upstream).find { |toxic| toxic.name == 'latency' }
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::Collection, Toxiproxy.all
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::Collection, proxies
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::Collection, proxies
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::Collection, proxies
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
- proxies = [{
332
- name: "test_toxiproxy_populate1",
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(proxies)
369
+ proxies = Toxiproxy.populate(proxies_config)
339
370
 
340
- proxies = [{
341
- name: "test_toxiproxy_populate1",
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(proxies)
378
+ proxies2 = Toxiproxy.populate(proxies_config)
348
379
 
349
- assert_equal proxies.first[:upstream], proxies2.first.upstream
350
- assert_proxy_available(proxies2.first)
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
@@ -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", "~> 1.3"
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.1.3
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: 2015-07-02 00:00:00.000000000 Z
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: '1.3'
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: '1.3'
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/collection.rb
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
- rubyforge_project:
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
data/circle.yml DELETED
@@ -1,7 +0,0 @@
1
- dependencies:
2
- override:
3
- - bundle install
4
- - ./bin/start-toxiproxy.sh
5
- test:
6
- override:
7
- - bundle exec rake test