toxiproxy 0.0.2
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 +7 -0
- data/.gitignore +3 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +50 -0
- data/Rakefile +12 -0
- data/bin/start-toxiproxy.sh +8 -0
- data/circle.yml +7 -0
- data/lib/toxiproxy.rb +179 -0
- data/lib/toxiproxy/collection.rb +58 -0
- data/lib/toxiproxy/toxic.rb +47 -0
- data/lib/toxiproxy/toxic_collection.rb +42 -0
- data/lib/toxiproxy/version.rb +3 -0
- data/shipit.rubygems.yml +2 -0
- data/test/test_helper.rb +3 -0
- data/test/toxiproxy_test.rb +209 -0
- data/toxiproxy.gemspec +20 -0
- metadata +104 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f2f83e60344122fe413e11816f5c976e46de7d6e
|
4
|
+
data.tar.gz: 52de814ecc4b5c472329179daf349599c9a5d15a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1ae9818932feabbb5982eb525d7dcfd1a03d20047d0e99d2cbd07e3b62d474285e118a29505802571c8132f595a5c014e0bf9327e9a0a6f8358db2836e37f8cb
|
7
|
+
data.tar.gz: a58e9df0b31c788e9b927fdf7c399621dce64e4f9dcb83e9670ea87ddcfe230a05eb2f3f2be29cf83e23d56ef2e5675a62f6210d66c1006030712cc34a654d36
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Shopify
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# toxiproxy-ruby
|
2
|
+
|
3
|
+
[Toxiproxy](https://github.com/shopify/toxiproxy) is a proxy to simulate network
|
4
|
+
and system conditions. The Ruby API aims to make it simple to write tests that
|
5
|
+
assure your application behaves appropriate under harsh conditions. Please see
|
6
|
+
the Toxiproxy project for more details.
|
7
|
+
|
8
|
+
```
|
9
|
+
gem install toxiproxy
|
10
|
+
```
|
11
|
+
|
12
|
+
Make sure the Toxiproxy server is already running.
|
13
|
+
|
14
|
+
## Usage
|
15
|
+
|
16
|
+
For example, to simulate 1000ms latency on a database server you can use the
|
17
|
+
`latency` toxic with the `latency` argument (see the Toxiproxy project for a
|
18
|
+
list of all toxics):
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
Toxiproxy[:mysql_master].downstream(:latency, latency: 1000) do
|
22
|
+
Shop.first # this took at least 1s
|
23
|
+
end
|
24
|
+
```
|
25
|
+
|
26
|
+
You can also take an endpoint down for the duration of a block at the TCP level:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
Toxiproxy[:mysql_master].down do
|
30
|
+
Shop.first # this'll raise
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
If you want to simulate all your Redis instances being down:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
Toxiproxy[/redis/].down do
|
38
|
+
# any redis call will fail
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
If you want to simulate that your cache server is slow at incoming network
|
43
|
+
(upstream), but fast at outgoing (downstream), you can apply a toxic to just the
|
44
|
+
upstream:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
Toxiproxy[:cache].upstream(:latency, latency: 1000) do
|
48
|
+
Cache.get(:omg) # will take at least a second
|
49
|
+
end
|
50
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
#!/bin/bash -e
|
2
|
+
|
3
|
+
VERSION='fad1365c087d6ad53944c8201a6131cb'
|
4
|
+
|
5
|
+
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 &"
|
data/circle.yml
ADDED
data/lib/toxiproxy.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
require "json"
|
2
|
+
require "uri"
|
3
|
+
require "net/http"
|
4
|
+
|
5
|
+
require "toxiproxy/collection"
|
6
|
+
require "toxiproxy/toxic"
|
7
|
+
require "toxiproxy/toxic_collection"
|
8
|
+
|
9
|
+
class Toxiproxy
|
10
|
+
URI = ::URI.parse("http://127.0.0.1:8474")
|
11
|
+
VALID_DIRECTIONS = [:upstream, :downstream]
|
12
|
+
|
13
|
+
class NotFound < StandardError; end
|
14
|
+
class ProxyExists < StandardError; end
|
15
|
+
class InvalidToxic < StandardError; end
|
16
|
+
|
17
|
+
attr_reader :listen, :name
|
18
|
+
|
19
|
+
def initialize(options)
|
20
|
+
@upstream = options[:upstream]
|
21
|
+
@listen = options[:listen] || "localhost:0"
|
22
|
+
@name = options[:name]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Forwardable doesn't support delegating class methods, so we resort to
|
26
|
+
# `define_method` to delegate from Toxiproxy to #all, and from there to the
|
27
|
+
# proxy collection.
|
28
|
+
class << self
|
29
|
+
Collection::METHODS.each do |method|
|
30
|
+
define_method(method) do |*args, &block|
|
31
|
+
self.all.send(method, *args, &block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns a collection of all currently active Toxiproxies.
|
37
|
+
def self.all
|
38
|
+
request = Net::HTTP::Get.new("/proxies")
|
39
|
+
response = http.request(request)
|
40
|
+
assert_response(response)
|
41
|
+
|
42
|
+
proxies = JSON.parse(response.body).map { |name, attrs|
|
43
|
+
self.new({
|
44
|
+
upstream: attrs["upstream"],
|
45
|
+
listen: attrs["listen"],
|
46
|
+
name: attrs["name"]
|
47
|
+
})
|
48
|
+
}
|
49
|
+
|
50
|
+
Collection.new(proxies)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Convenience method to create a proxy.
|
54
|
+
def self.create(options)
|
55
|
+
self.new(options).create
|
56
|
+
end
|
57
|
+
|
58
|
+
# Find a single proxy by name.
|
59
|
+
def self.find_by_name(name = nil, &block)
|
60
|
+
proxy = self.all.find { |p| p.name == name.to_s }
|
61
|
+
raise NotFound, "#{name} not found in #{self.all.map(&:name).join(', ')}" unless proxy
|
62
|
+
proxy
|
63
|
+
end
|
64
|
+
|
65
|
+
# If given a regex, it'll use `grep` to return a Toxiproxy::Collection.
|
66
|
+
# Otherwise, it'll convert the passed object to a string and find the proxy by
|
67
|
+
# name.
|
68
|
+
def self.[](query)
|
69
|
+
return grep(query) if query.is_a?(Regexp)
|
70
|
+
find_by_name(query)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Set an upstream toxic.
|
74
|
+
def upstream(toxic = nil, attrs = {})
|
75
|
+
return @upstream unless toxic
|
76
|
+
|
77
|
+
collection = ToxicCollection.new(self)
|
78
|
+
collection.upstream(toxic, attrs)
|
79
|
+
collection
|
80
|
+
end
|
81
|
+
|
82
|
+
# Set a downstream toxic.
|
83
|
+
def downstream(toxic, attrs = {})
|
84
|
+
collection = ToxicCollection.new(self)
|
85
|
+
collection.downstream(toxic, attrs)
|
86
|
+
collection
|
87
|
+
end
|
88
|
+
|
89
|
+
# Simulates the endpoint is down, by closing the connection and no
|
90
|
+
# longer accepting connections. This is useful to simulate critical system
|
91
|
+
# failure, such as a data store becoming completely unavailable.
|
92
|
+
def down(&block)
|
93
|
+
uptoxics = toxics(:upstream)
|
94
|
+
downtoxics = toxics(:downstream)
|
95
|
+
destroy
|
96
|
+
begin
|
97
|
+
yield
|
98
|
+
ensure
|
99
|
+
create
|
100
|
+
uptoxics.each(&:save)
|
101
|
+
downtoxics.each(&:save)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Create a Toxiproxy, proxying traffic from `@listen` (optional argument to
|
106
|
+
# the constructor) to `@upstream`. `#down` `#upstream` or `#downstream` can at any time alter the health
|
107
|
+
# of this connection.
|
108
|
+
def create
|
109
|
+
request = Net::HTTP::Post.new("/proxies")
|
110
|
+
|
111
|
+
hash = {upstream: upstream, name: name, listen: listen}
|
112
|
+
request.body = hash.to_json
|
113
|
+
|
114
|
+
response = http.request(request)
|
115
|
+
assert_response(response)
|
116
|
+
|
117
|
+
new = JSON.parse(response.body)
|
118
|
+
@listen = new["listen"]
|
119
|
+
|
120
|
+
self
|
121
|
+
end
|
122
|
+
|
123
|
+
# Destroys a Toxiproxy.
|
124
|
+
def destroy
|
125
|
+
request = Net::HTTP::Delete.new("/proxies/#{name}")
|
126
|
+
response = http.request(request)
|
127
|
+
assert_response(response)
|
128
|
+
self
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
# Returns a collection of the current toxics for a direction.
|
134
|
+
def toxics(direction)
|
135
|
+
unless VALID_DIRECTIONS.include?(direction.to_sym)
|
136
|
+
raise InvalidToxic, "Toxic direction must be one of: [#{VALID_DIRECTIONS.join(', ')}], got: #{direction}"
|
137
|
+
end
|
138
|
+
|
139
|
+
request = Net::HTTP::Get.new("/proxies/#{name}/#{direction}/toxics")
|
140
|
+
response = http.request(request)
|
141
|
+
assert_response(response)
|
142
|
+
|
143
|
+
toxics = JSON.parse(response.body).map { |name, attrs|
|
144
|
+
Toxic.new({
|
145
|
+
name: name,
|
146
|
+
proxy: self,
|
147
|
+
direction: direction,
|
148
|
+
attrs: attrs
|
149
|
+
})
|
150
|
+
}
|
151
|
+
|
152
|
+
toxics
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.http
|
156
|
+
@http ||= Net::HTTP.new(URI.host, URI.port)
|
157
|
+
end
|
158
|
+
|
159
|
+
def http
|
160
|
+
self.class.http
|
161
|
+
end
|
162
|
+
|
163
|
+
def self.assert_response(response)
|
164
|
+
case response
|
165
|
+
when Net::HTTPConflict
|
166
|
+
raise Toxiproxy::ProxyExists, response.body
|
167
|
+
when Net::HTTPNotFound
|
168
|
+
raise Toxiproxy::NotFound, response.body
|
169
|
+
when Net::HTTPBadRequest
|
170
|
+
raise Toxiproxy::InvalidToxic, response.body
|
171
|
+
else
|
172
|
+
response.value # raises if not OK
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def assert_response(*args)
|
177
|
+
self.class.assert_response(*args)
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
class Toxiproxy
|
4
|
+
# ProxyCollection represents a set of proxies. This allows to easily perform
|
5
|
+
# actions on every proxy in the collection.
|
6
|
+
#
|
7
|
+
# Unfortunately, it doesn't implement all of Enumerable because there's no way
|
8
|
+
# to subclass an Array or include Enumerable for the methods to return a
|
9
|
+
# Collection instead of an Array (see MRI). Instead, we delegate methods where
|
10
|
+
# it doesn't matter and only allow the filtering methods that really make
|
11
|
+
# sense on a proxy collection.
|
12
|
+
class Collection
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
DELEGATED_METHODS = [:length, :size, :count, :find, :each, :map]
|
16
|
+
DEFINED_METHODS = [:select, :reject, :grep, :down]
|
17
|
+
METHODS = DEFINED_METHODS + DELEGATED_METHODS
|
18
|
+
|
19
|
+
def_delegators :@collection, *DELEGATED_METHODS
|
20
|
+
|
21
|
+
def initialize(collection)
|
22
|
+
@collection = collection
|
23
|
+
end
|
24
|
+
|
25
|
+
# Sets every proxy in the collection as down. For example:
|
26
|
+
#
|
27
|
+
# Toxiproxy.grep(/redis/).down { .. }
|
28
|
+
#
|
29
|
+
# Would simulate every Redis server being down for the duration of the
|
30
|
+
# block.
|
31
|
+
def down(*args, &block)
|
32
|
+
@collection.inject(block) { |nested, proxy|
|
33
|
+
-> { proxy.down(*args, &nested) }
|
34
|
+
}.call
|
35
|
+
end
|
36
|
+
|
37
|
+
# Destroys all toxiproxy's in the collection
|
38
|
+
def destroy
|
39
|
+
@collection.each(&:destroy)
|
40
|
+
end
|
41
|
+
|
42
|
+
def select(&block)
|
43
|
+
self.class.new(@collection.select(&block))
|
44
|
+
end
|
45
|
+
|
46
|
+
def reject(&block)
|
47
|
+
self.class.new(@collection.reject(&block))
|
48
|
+
end
|
49
|
+
|
50
|
+
# Grep allows easily selecting a subset of proxies, by returning a
|
51
|
+
# ProxyCollection with every proxy name matching the regex passed.
|
52
|
+
def grep(regex)
|
53
|
+
self.class.new(@collection.select { |proxy|
|
54
|
+
proxy.name =~ regex
|
55
|
+
})
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Toxiproxy
|
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] || {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def enabled?
|
14
|
+
attrs[:enabled]
|
15
|
+
end
|
16
|
+
|
17
|
+
def enable
|
18
|
+
attrs[:enabled] = true
|
19
|
+
save
|
20
|
+
end
|
21
|
+
|
22
|
+
def disable
|
23
|
+
attrs[:enabled] = false
|
24
|
+
save
|
25
|
+
end
|
26
|
+
|
27
|
+
def []=(name, value)
|
28
|
+
attrs[name] = value
|
29
|
+
end
|
30
|
+
|
31
|
+
def save
|
32
|
+
unless VALID_DIRECTIONS.include?(direction.to_sym)
|
33
|
+
raise InvalidToxic, "Toxic direction must be one of: [#{VALID_DIRECTIONS.join(', ')}], got: #{direction}"
|
34
|
+
end
|
35
|
+
request = Net::HTTP::Post.new("/proxies/#{proxy.name}/#{direction}/toxics/#{name}")
|
36
|
+
|
37
|
+
request.body = attrs.to_json
|
38
|
+
|
39
|
+
response = Toxiproxy.http.request(request)
|
40
|
+
Toxiproxy.assert_response(response)
|
41
|
+
|
42
|
+
@attrs = JSON.parse(response.body)
|
43
|
+
|
44
|
+
self
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Toxiproxy
|
2
|
+
class ToxicCollection
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
attr_accessor :toxics
|
6
|
+
attr_reader :proxy
|
7
|
+
|
8
|
+
def_delegators :@toxics, :<<, :find
|
9
|
+
|
10
|
+
def initialize(proxy)
|
11
|
+
@proxy = proxy
|
12
|
+
@toxics = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def apply(&block)
|
16
|
+
@toxics.each(&:enable)
|
17
|
+
yield
|
18
|
+
ensure
|
19
|
+
@toxics.each(&:disable)
|
20
|
+
end
|
21
|
+
|
22
|
+
def upstream(toxic_name, attrs = {})
|
23
|
+
toxics << Toxic.new(
|
24
|
+
name: toxic_name,
|
25
|
+
proxy: proxy,
|
26
|
+
direction: :upstream,
|
27
|
+
attrs: attrs
|
28
|
+
)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def downstream(toxic_name, attrs = {})
|
33
|
+
toxics << Toxic.new(
|
34
|
+
name: toxic_name,
|
35
|
+
proxy: proxy,
|
36
|
+
direction: :downstream,
|
37
|
+
attrs: attrs
|
38
|
+
)
|
39
|
+
self
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/shipit.rubygems.yml
ADDED
data/test/test_helper.rb
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ToxiproxyTest < MiniTest::Unit::TestCase
|
4
|
+
def teardown
|
5
|
+
Toxiproxy.grep(/\Atest_/).each(&:destroy)
|
6
|
+
end
|
7
|
+
|
8
|
+
def test_create_proxy
|
9
|
+
proxy = Toxiproxy.create(upstream: "localhost:3306", name: "test_mysql_master")
|
10
|
+
|
11
|
+
assert_equal "localhost:3306", proxy.upstream
|
12
|
+
assert_equal "test_mysql_master", proxy.name
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_create_and_find_proxy
|
16
|
+
proxy = Toxiproxy.create(upstream: "localhost:3306", name: "test_mysql_master")
|
17
|
+
|
18
|
+
assert_equal "localhost:3306", proxy.upstream
|
19
|
+
assert_equal "test_mysql_master", proxy.name
|
20
|
+
|
21
|
+
proxy = Toxiproxy[:test_mysql_master]
|
22
|
+
|
23
|
+
assert_equal "localhost:3306", proxy.upstream
|
24
|
+
assert_equal "test_mysql_master", proxy.name
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_take_endpoint_down
|
28
|
+
with_tcpserver do |port|
|
29
|
+
proxy = Toxiproxy.create(upstream: "localhost:#{port}", name: "test_rubby_server")
|
30
|
+
listen_addr = proxy.listen
|
31
|
+
|
32
|
+
proxy.down do
|
33
|
+
assert_proxy_unavailable proxy
|
34
|
+
end
|
35
|
+
|
36
|
+
assert_proxy_available proxy
|
37
|
+
|
38
|
+
assert_equal listen_addr, proxy.listen
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_raises_when_proxy_doesnt_exist
|
43
|
+
assert_raises Toxiproxy::NotFound do
|
44
|
+
Toxiproxy[:does_not_exist]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_proxies_all_returns_proxy_collection
|
49
|
+
assert_instance_of Toxiproxy::Collection, Toxiproxy.all
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_down_on_proxy_collection_disables_entire_collection
|
53
|
+
with_tcpserver do |port1|
|
54
|
+
with_tcpserver do |port2|
|
55
|
+
proxy1 = Toxiproxy.create(upstream: "localhost:#{port1}", name: "test_proxy1")
|
56
|
+
proxy2 = Toxiproxy.create(upstream: "localhost:#{port2}", name: "test_proxy2")
|
57
|
+
|
58
|
+
assert_proxy_available proxy2
|
59
|
+
assert_proxy_available proxy1
|
60
|
+
|
61
|
+
Toxiproxy.all.down do
|
62
|
+
assert_proxy_unavailable proxy1
|
63
|
+
assert_proxy_unavailable proxy2
|
64
|
+
end
|
65
|
+
|
66
|
+
assert_proxy_available proxy2
|
67
|
+
assert_proxy_available proxy1
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_select_from_toxiproxy_collection
|
73
|
+
with_tcpserver do |port|
|
74
|
+
Toxiproxy.create(upstream: "localhost:#{port}", name: "test_proxy")
|
75
|
+
|
76
|
+
proxies = Toxiproxy.select { |p| p.upstream == "localhost:#{port}" }
|
77
|
+
|
78
|
+
assert_equal 1, proxies.size
|
79
|
+
assert_instance_of Toxiproxy::Collection, proxies
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_grep_returns_toxiproxy_collection
|
84
|
+
with_tcpserver do |port|
|
85
|
+
Toxiproxy.create(upstream: "localhost:#{port}", name: "test_proxy")
|
86
|
+
|
87
|
+
proxies = Toxiproxy.grep(/\Atest/)
|
88
|
+
|
89
|
+
assert_equal 1, proxies.size
|
90
|
+
assert_instance_of Toxiproxy::Collection, proxies
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_indexing_allows_regexp
|
95
|
+
with_tcpserver do |port|
|
96
|
+
Toxiproxy.create(upstream: "localhost:#{port}", name: "test_proxy")
|
97
|
+
|
98
|
+
proxies = Toxiproxy[/\Atest/]
|
99
|
+
|
100
|
+
assert_equal 1, proxies.size
|
101
|
+
assert_instance_of Toxiproxy::Collection, proxies
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_apply_upstream_toxic
|
106
|
+
$before = Time.now
|
107
|
+
|
108
|
+
with_tcpserver(receive: true) do |port|
|
109
|
+
proxy = Toxiproxy.create(upstream: "localhost:#{port}", name: "test_proxy")
|
110
|
+
|
111
|
+
proxy.upstream(:latency, latency: 100).apply do
|
112
|
+
before = Time.now
|
113
|
+
|
114
|
+
socket = connect_to_proxy(proxy)
|
115
|
+
socket.write("omg\n")
|
116
|
+
socket.flush
|
117
|
+
socket.gets
|
118
|
+
|
119
|
+
passed = Time.now - before
|
120
|
+
|
121
|
+
assert_in_delta passed, 0.100, 0.01
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_apply_downstream_toxic
|
127
|
+
with_tcpserver(receive: true) do |port|
|
128
|
+
proxy = Toxiproxy.create(upstream: "localhost:#{port}", name: "test_proxy")
|
129
|
+
|
130
|
+
proxy.downstream(:latency, latency: 100).apply do
|
131
|
+
before = Time.now
|
132
|
+
|
133
|
+
socket = connect_to_proxy(proxy)
|
134
|
+
socket.write("omg\n")
|
135
|
+
socket.flush
|
136
|
+
socket.gets
|
137
|
+
|
138
|
+
passed = Time.now - before
|
139
|
+
|
140
|
+
assert_in_delta passed, 0.100, 0.01
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def test_apply_prolong_toxics
|
146
|
+
with_tcpserver(receive: true) do |port|
|
147
|
+
proxy = Toxiproxy.create(upstream: "localhost:#{port}", name: "test_proxy")
|
148
|
+
|
149
|
+
proxy.upstream(:latency, latency: 100).downstream(:latency, latency: 100).apply do
|
150
|
+
before = Time.now
|
151
|
+
|
152
|
+
socket = connect_to_proxy(proxy)
|
153
|
+
socket.write("omg\n")
|
154
|
+
socket.flush
|
155
|
+
socket.gets
|
156
|
+
|
157
|
+
passed = Time.now - before
|
158
|
+
|
159
|
+
assert_in_delta passed, 0.200, 0.01
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def assert_proxy_available(proxy)
|
167
|
+
connect_to_proxy proxy
|
168
|
+
end
|
169
|
+
|
170
|
+
def assert_proxy_unavailable(proxy)
|
171
|
+
assert_raises Errno::ECONNREFUSED do
|
172
|
+
connect_to_proxy proxy
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def connect_to_proxy(proxy)
|
177
|
+
TCPSocket.new(*proxy.listen.split(":".freeze))
|
178
|
+
end
|
179
|
+
|
180
|
+
def with_tcpserver(receive = false, &block)
|
181
|
+
mon = Monitor.new
|
182
|
+
cond = mon.new_cond
|
183
|
+
port = nil
|
184
|
+
|
185
|
+
thread = Thread.new {
|
186
|
+
server = TCPServer.new("127.0.0.1", 0)
|
187
|
+
port = server.addr[1]
|
188
|
+
mon.synchronize { cond.signal }
|
189
|
+
loop do
|
190
|
+
client = server.accept
|
191
|
+
|
192
|
+
if receive
|
193
|
+
client.gets
|
194
|
+
client.write("omgs\n")
|
195
|
+
client.flush
|
196
|
+
end
|
197
|
+
|
198
|
+
client.close
|
199
|
+
end
|
200
|
+
server.close
|
201
|
+
}
|
202
|
+
|
203
|
+
mon.synchronize { cond.wait }
|
204
|
+
|
205
|
+
yield(port)
|
206
|
+
ensure
|
207
|
+
thread.kill
|
208
|
+
end
|
209
|
+
end
|
data/toxiproxy.gemspec
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.expand_path("../lib/toxiproxy/version", __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "toxiproxy"
|
5
|
+
spec.version = Toxiproxy::VERSION
|
6
|
+
spec.authors = ["Simon Eskildsen", "Jacob Wirth"]
|
7
|
+
spec.email = "simon.eskildsen@shopify.com"
|
8
|
+
spec.summary = "Ruby library for Toxiproxy"
|
9
|
+
spec.description = "A Ruby library for controlling Toxiproxy. Can be used in resiliency testing."
|
10
|
+
spec.homepage = "https://github.com/Shopify/toxiproxy"
|
11
|
+
spec.license = "MIT"
|
12
|
+
|
13
|
+
spec.files = `git ls-files`.split("\n")
|
14
|
+
spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
spec.require_paths = ["lib"]
|
16
|
+
|
17
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
18
|
+
spec.add_development_dependency "minitest"
|
19
|
+
spec.add_development_dependency "rake"
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: toxiproxy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Simon Eskildsen
|
8
|
+
- Jacob Wirth
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-11-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.3'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.3'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: minitest
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rake
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
description: A Ruby library for controlling Toxiproxy. Can be used in resiliency testing.
|
57
|
+
email: simon.eskildsen@shopify.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- bin/start-toxiproxy.sh
|
68
|
+
- circle.yml
|
69
|
+
- lib/toxiproxy.rb
|
70
|
+
- lib/toxiproxy/collection.rb
|
71
|
+
- lib/toxiproxy/toxic.rb
|
72
|
+
- lib/toxiproxy/toxic_collection.rb
|
73
|
+
- lib/toxiproxy/version.rb
|
74
|
+
- shipit.rubygems.yml
|
75
|
+
- test/test_helper.rb
|
76
|
+
- test/toxiproxy_test.rb
|
77
|
+
- toxiproxy.gemspec
|
78
|
+
homepage: https://github.com/Shopify/toxiproxy
|
79
|
+
licenses:
|
80
|
+
- MIT
|
81
|
+
metadata: {}
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 2.2.2
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: Ruby library for Toxiproxy
|
102
|
+
test_files:
|
103
|
+
- test/test_helper.rb
|
104
|
+
- test/toxiproxy_test.rb
|