evilhornets 0.0.1
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/bin/hornet +5 -0
- data/lib/hornet.rb +126 -0
- data/lib/hornet/fleet.rb +74 -0
- data/lib/hornet/headquarter.rb +220 -0
- data/lib/hornet/hive.rb +110 -0
- metadata +77 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f2eb8b072d3546ff3b3252cec33ada70b49e7279
|
4
|
+
data.tar.gz: d8cad9948b5b5affda0e054f1ef594b17dc0770c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a8e5645179e857ab39824dcf742b81f75c9c4c67051a1ce6d027102ffaaab38ff983d9644ad9b73e990b8683715442a2426b1d99093e62b9e91a1d61c24b2973
|
7
|
+
data.tar.gz: 18d0774defbaed47e8b39a4e542cf1f628be92537747bd11d793188d3c9b4151f3290244ad3ed5e15ba82c63eaa8657e8ba8d8cb1ab6a9ca6b7713a33528657a
|
data/bin/hornet
ADDED
data/lib/hornet.rb
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'hornet/headquarter'
|
4
|
+
|
5
|
+
Commands = {
|
6
|
+
'up' => 'Spin up multiple EC2 micro instances to host bees.',
|
7
|
+
'attack' => 'Launch an attack against a target.',
|
8
|
+
'down' => 'Terminate load testing servers.',
|
9
|
+
'scale' => 'Adjust the number of load testing servers.',
|
10
|
+
'report' => 'Report the load testing result.',
|
11
|
+
'help' => 'Print the help document.',
|
12
|
+
}
|
13
|
+
|
14
|
+
class Hornet
|
15
|
+
|
16
|
+
def self.parse(args)
|
17
|
+
command = args.first
|
18
|
+
options = OpenStruct.new
|
19
|
+
parser = OptionParser.new do |opt|
|
20
|
+
|
21
|
+
if ['--help', '-h', 'help'].include? command
|
22
|
+
print_help = ['--help', '-h', 'help'].include? command
|
23
|
+
command = 'help'
|
24
|
+
end
|
25
|
+
if not Commands.keys.include? command
|
26
|
+
opt.banner = "Usage: hornet (%s) [options]" % Commands.keys.join('|')
|
27
|
+
else
|
28
|
+
opt.banner = "Usage: hornet %s [options]" % command
|
29
|
+
|
30
|
+
if ['up', 'attack', 'scale'].include? command
|
31
|
+
opt.separator Commands[command]
|
32
|
+
end
|
33
|
+
|
34
|
+
# build options depends on command
|
35
|
+
|
36
|
+
if command == 'attack' or print_help
|
37
|
+
opt.separator 'attack:'
|
38
|
+
opt.on('-n', '--number [NUMBER]', 'Number of total attacks to launch (default: 1000).') do |value|
|
39
|
+
options.number = value
|
40
|
+
end
|
41
|
+
opt.on('-c', '--concurrent [CONCURRENT]', 'The number of concurrent connections to make to the target (default: 100).') do |value|
|
42
|
+
options.concurrent = value
|
43
|
+
end
|
44
|
+
opt.on('-b', '--bees [BEES]', 'Number of containers to create (default: 1).') do |value|
|
45
|
+
options.bees = value
|
46
|
+
end
|
47
|
+
opt.on('-u', '--url [URL]', 'URL of the target to attack.') do |value|
|
48
|
+
options.url = value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if command == 'up' or print_help
|
53
|
+
opt.separator 'up:'
|
54
|
+
opt.on('-r', '--region [REGION]', 'Region the server will be built (default: us-east-1d).') do |value|
|
55
|
+
options.region = value
|
56
|
+
end
|
57
|
+
opt.on('-n', '--number [NUMBER]', 'Number of servers to start (default: 1).') do |value|
|
58
|
+
options.number = value
|
59
|
+
end
|
60
|
+
opt.on('-u', '--username [USERNAME]', 'The ssh username name to use to connect to the servers (default: ubuntu).') do |value|
|
61
|
+
options.username = value
|
62
|
+
end
|
63
|
+
opt.on('-k', '--key [KEY]', 'The ssh key pair name to use to connect to the servers.') do |value|
|
64
|
+
options.key_name = value
|
65
|
+
end
|
66
|
+
opt.on('-i', '--image_id [IMAGE_ID]', 'The ID of the AMI.') do |value|
|
67
|
+
options.image_id = value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
if command == 'scale' or print_help
|
72
|
+
opt.separator 'scale:'
|
73
|
+
opt.on('-n', '--number [NUMBER]', 'Number of servers to scale to (default: 1).') do |value|
|
74
|
+
options.number = value
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
opt.on('-h', '--help', 'Print this help document.') do |value|
|
79
|
+
abort parser.to_s
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
# don't parse anything if no command sepcified
|
86
|
+
if command.nil? or ['--help', '-h', 'help'].include? command
|
87
|
+
abort parser.to_s
|
88
|
+
end
|
89
|
+
parser.parse!
|
90
|
+
options
|
91
|
+
end
|
92
|
+
|
93
|
+
# validate options, abort if required options is missing
|
94
|
+
def self.validate_options(command, options)
|
95
|
+
ops = {}
|
96
|
+
begin
|
97
|
+
case command
|
98
|
+
when 'attack'
|
99
|
+
ops = {:number => 1000, :concurrent => 100, :bees => 1}.merge options.to_h
|
100
|
+
if not ops.has_key? :url
|
101
|
+
raise ArgumentError.new 'Missing argument: --url'
|
102
|
+
end
|
103
|
+
when 'up'
|
104
|
+
ops = {:region => 'us-east-1', :username => 'ubuntu', :number => 1, :image_id => 'ami-c3e997f9'}.merge options.to_h
|
105
|
+
if not ops.has_key? :key_name
|
106
|
+
raise ArgumentError.new 'Missing argument: --key'
|
107
|
+
end
|
108
|
+
when 'scale'
|
109
|
+
ops = {:number => 1}.merge options.to_h
|
110
|
+
end
|
111
|
+
rescue ArgumentError => msg
|
112
|
+
abort msg.to_s
|
113
|
+
end
|
114
|
+
OpenStruct.new ops
|
115
|
+
end
|
116
|
+
|
117
|
+
# move on to the next step.
|
118
|
+
def self.go(args, options)
|
119
|
+
command = args.first
|
120
|
+
if not command.nil?
|
121
|
+
options = Hornet.validate_options command, options
|
122
|
+
headquarter = Fleet::Headquarter.new(command, options.to_h)
|
123
|
+
headquarter.dispatch
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/lib/hornet/fleet.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
module Fleet
|
2
|
+
|
3
|
+
Headlines = [
|
4
|
+
'Time taken for tests',
|
5
|
+
'Complete requests',
|
6
|
+
'Failed requests',
|
7
|
+
'Non-2xx responses',
|
8
|
+
'Total transferred',
|
9
|
+
'HTML transferred',
|
10
|
+
'Requests per second',
|
11
|
+
'Time per request',
|
12
|
+
'Transfer rate'
|
13
|
+
]
|
14
|
+
|
15
|
+
def Fleet.print_report(result)
|
16
|
+
result.each do |key, value|
|
17
|
+
puts '%s: %s' % [key, value.join(' ')]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# merge all results together and with certain calculations
|
22
|
+
def Fleet.report(data)
|
23
|
+
result = {}
|
24
|
+
|
25
|
+
# hash
|
26
|
+
data.each_value do |entries|
|
27
|
+
entries.each do |key, value|
|
28
|
+
|
29
|
+
if result.has_key? key
|
30
|
+
result[key][0] << value.first.to_f
|
31
|
+
else
|
32
|
+
result[key] = value.size == 1 ? [[value.first.to_f]] : [[value.first.to_f], value.last]
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# process the result data, e.g, sum and avg
|
39
|
+
result.each do |key, value|
|
40
|
+
#puts value.to_s
|
41
|
+
if ['Time taken for tests'].include? key
|
42
|
+
value[0] = value[0].sort.last
|
43
|
+
elsif ['Complete requests', 'Failed requests', 'Total transferred', 'HTML transferred', 'Non-2xx responses'].include? key
|
44
|
+
value[0] = value[0].inject{|sum, x| sum + x}
|
45
|
+
else
|
46
|
+
count = value[0].count
|
47
|
+
value[0] = (value[0].inject{|sum, x| sum + x} / count).round(2)
|
48
|
+
end
|
49
|
+
result[key] = value
|
50
|
+
end
|
51
|
+
result
|
52
|
+
end
|
53
|
+
|
54
|
+
def Fleet.parse_ab_data(data)
|
55
|
+
result = {}
|
56
|
+
|
57
|
+
# parse each line with the matched heading
|
58
|
+
data.each_line do |line|
|
59
|
+
if line.start_with?(*Headlines)
|
60
|
+
parts = line.partition(':')
|
61
|
+
head = parts.first
|
62
|
+
body = parts.last.strip.split(' ', 2)
|
63
|
+
result[head] = body
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
if not result.has_key? 'Time per request'
|
68
|
+
return {}
|
69
|
+
end
|
70
|
+
|
71
|
+
result
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'hornet/fleet'
|
4
|
+
require 'hornet/hive'
|
5
|
+
|
6
|
+
module Fleet
|
7
|
+
class Headquarter
|
8
|
+
|
9
|
+
STATE_FILE = File.expand_path('~/.hives')
|
10
|
+
|
11
|
+
def initialize(command, options={})
|
12
|
+
@command = command
|
13
|
+
@options = options
|
14
|
+
|
15
|
+
@state = OpenStruct.new options
|
16
|
+
@state.loaded = false
|
17
|
+
|
18
|
+
readServerState
|
19
|
+
|
20
|
+
if @state.region
|
21
|
+
Aws.config.update({:region => @state.region})
|
22
|
+
@ec2_resource = Aws::EC2::Resource.new
|
23
|
+
@ec2_client = Aws::EC2::Client.new
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def dispatch
|
28
|
+
case @command
|
29
|
+
when 'up'
|
30
|
+
createHives @options
|
31
|
+
when 'attack'
|
32
|
+
hivesAttack @options
|
33
|
+
when 'scale'
|
34
|
+
scaleHives @options
|
35
|
+
when 'report'
|
36
|
+
hivesReport
|
37
|
+
when 'down'
|
38
|
+
destroyHives
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# create a number of hives using user options
|
43
|
+
def createHives(options)
|
44
|
+
number_of_hive = options.has_key?(:number) ? options[:number].to_i : 1
|
45
|
+
hive_options = {
|
46
|
+
:key_name => nil,
|
47
|
+
:image_id => nil,
|
48
|
+
:min_count => number_of_hive,
|
49
|
+
:max_count => number_of_hive,
|
50
|
+
:instance_type => 't2.micro'
|
51
|
+
}
|
52
|
+
hive_options.merge!(options.select {|k,v| hive_options.has_key?(k)})
|
53
|
+
hives = @ec2_resource.create_instances hive_options
|
54
|
+
puts "%i hives are being built" % number_of_hive
|
55
|
+
|
56
|
+
# write the current state to the file
|
57
|
+
@state.hives = hives.map(&:id) + @state.hives.to_a
|
58
|
+
writeServerList
|
59
|
+
|
60
|
+
checkHivesStatus hives
|
61
|
+
|
62
|
+
# tagging happens after the instance is ready
|
63
|
+
@ec2_resource.create_tags({:tags => [{:key => 'Name', :value => 'hive'}], :resources => hives.map(&:id)})
|
64
|
+
end
|
65
|
+
|
66
|
+
# start the attack simultaneously.
|
67
|
+
def hivesAttack(options)
|
68
|
+
hives = []
|
69
|
+
attack_threads = []
|
70
|
+
attack_options = []
|
71
|
+
|
72
|
+
puts "Preparing the attack:"
|
73
|
+
puts "%s bees will attack %s times, %s at a time" % [options[:bees], options[:bees].to_i * options[:number].to_i, options[:concurrent]]
|
74
|
+
remains = options[:bees].to_i % @state.hives.count
|
75
|
+
options[:bees] = options[:bees].to_i / @state.hives.count
|
76
|
+
|
77
|
+
puts "Hive Bees"
|
78
|
+
@state.hives.each_with_index do |instance_id, index|
|
79
|
+
if index == @state.hives.size - 1
|
80
|
+
options[:bees] += remains
|
81
|
+
end
|
82
|
+
attack_options << options.clone
|
83
|
+
puts '%s %s' % [instance_id, options[:bees]]
|
84
|
+
|
85
|
+
hive = Hive.new @state.username, @state.key_name, instance_id
|
86
|
+
hives << hive
|
87
|
+
attack_threads << Thread.new do
|
88
|
+
hive.attack attack_options[index]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
puts "\n"
|
93
|
+
attack_threads.each {|t| t.join}
|
94
|
+
puts "\n"
|
95
|
+
|
96
|
+
hivesReport hives
|
97
|
+
end
|
98
|
+
|
99
|
+
# collect report from every hive
|
100
|
+
def hivesReport(hives=[])
|
101
|
+
data = {}
|
102
|
+
report_threads = []
|
103
|
+
if not hives.any?
|
104
|
+
@state.hives.each_with_index do |instance_id, index|
|
105
|
+
hives << Hive.new(@state.username, @state.key_name, instance_id)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# create the report threads
|
110
|
+
hives.each do |hive|
|
111
|
+
report_threads << Thread.new do
|
112
|
+
data[hive.instance_id] = hive.report
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
report_threads.each {|t| t.join}
|
117
|
+
Fleet.print_report Fleet.report(data)
|
118
|
+
end
|
119
|
+
|
120
|
+
# scale hives up and down
|
121
|
+
def scaleHives(options)
|
122
|
+
if not @state.loaded
|
123
|
+
abort 'Perhaps build some hives first?'
|
124
|
+
end
|
125
|
+
number_of_hive = options.has_key?(:number) ? options[:number].to_i : 1
|
126
|
+
if @state.hives.count == number_of_hive
|
127
|
+
abort 'No hives scaled'
|
128
|
+
elsif @state.hives.count > number_of_hive
|
129
|
+
destroyHives number_of_hive > 0 ? @state.hives[number_of_hive..-1] : {}
|
130
|
+
else
|
131
|
+
options = {:number => number_of_hive - @state.hives.count, :image_id => @state.image_id}
|
132
|
+
createHives @state.to_h.merge options
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# tear down all running hives
|
137
|
+
def destroyHives instances = []
|
138
|
+
instances = instances.empty? ? @state.hives : instances
|
139
|
+
if not instances.empty?
|
140
|
+
|
141
|
+
# attemp the terminate ec2 instances
|
142
|
+
begin
|
143
|
+
@ec2_client.terminate_instances instance_ids: instances
|
144
|
+
rescue Aws::EC2::Errors::InvalidInstanceIDNotFound => e
|
145
|
+
# for mismatches, terminate what we can
|
146
|
+
instances_2b_removed = instances - e.to_s.match(/\'[^']*\'/)[0].split(',').map! {|x| x.strip.tr_s("'", "")}
|
147
|
+
@ec2_client.terminate_instances instance_ids: instances_2b_removed
|
148
|
+
rescue Aws::EC2::Errors::InvalidInstanceIDMalformed
|
149
|
+
end
|
150
|
+
|
151
|
+
if instances.count == @state.hives.count
|
152
|
+
removeServerList
|
153
|
+
else
|
154
|
+
@state.hives.reject! {|item| instances.include? item}
|
155
|
+
writeServerList
|
156
|
+
end
|
157
|
+
else
|
158
|
+
abord 'Perhaps build some hives first?'
|
159
|
+
end
|
160
|
+
puts '%i hives are teared down!' % instances.count
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
|
165
|
+
def readServerState
|
166
|
+
if not File.exist? STATE_FILE
|
167
|
+
return false
|
168
|
+
end
|
169
|
+
server_state = IO.readlines(STATE_FILE).map! {|l| l.strip}
|
170
|
+
begin
|
171
|
+
@state.username = server_state[0]
|
172
|
+
@state.key_name = server_state[1]
|
173
|
+
@state.region = server_state[2]
|
174
|
+
@state.image_id = server_state[3]
|
175
|
+
@state.hives = server_state[4..-1]
|
176
|
+
rescue
|
177
|
+
abort 'A problem occured when reading hives'
|
178
|
+
end
|
179
|
+
@state.loaded = true
|
180
|
+
end
|
181
|
+
|
182
|
+
def writeServerList
|
183
|
+
begin
|
184
|
+
File.open(STATE_FILE, 'w') do |f|
|
185
|
+
f.write("%s\n" % @state.username)
|
186
|
+
f.write("%s\n" % @state.key_name)
|
187
|
+
f.write("%s\n" % @state.region)
|
188
|
+
f.write("%s\n" % @state.image_id)
|
189
|
+
f.write(@state.hives.join("\n"))
|
190
|
+
end
|
191
|
+
rescue
|
192
|
+
abort 'Failed to written down hives details'
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def removeServerList
|
197
|
+
File.delete STATE_FILE
|
198
|
+
end
|
199
|
+
|
200
|
+
# check over status of hives
|
201
|
+
def checkHivesStatus(hives)
|
202
|
+
hives_built = []
|
203
|
+
filters = [{:name => 'instance-state-name', :values => ['pending', 'running']}]
|
204
|
+
while hives_built.count != hives.count do
|
205
|
+
statuses = @ec2_client.describe_instance_status instance_ids: hives.map(&:id), include_all_instances: true, filters: filters
|
206
|
+
statuses.each do |response|
|
207
|
+
response[:instance_statuses].each do |instance|
|
208
|
+
building = instance[:instance_state].name == 'running' ? false : true
|
209
|
+
instance_id = instance[:instance_id]
|
210
|
+
if not building and not hives_built.include? instance_id
|
211
|
+
puts 'Hive %s is ready!' % instance_id
|
212
|
+
hives_built << instance_id
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
sleep(1)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
data/lib/hornet/hive.rb
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require 'net/ssh'
|
3
|
+
require 'hornet/fleet'
|
4
|
+
|
5
|
+
module Fleet
|
6
|
+
class Hive
|
7
|
+
attr_accessor :instance_id
|
8
|
+
HOME_DIR = '/home/ubuntu'
|
9
|
+
|
10
|
+
def initialize(username, key, instance_id)
|
11
|
+
@username = username
|
12
|
+
@key = File.expand_path('~/.ssh/%s.pem' % key)
|
13
|
+
|
14
|
+
# grab the instance ip and id
|
15
|
+
instance = Aws::EC2::Instance.new instance_id
|
16
|
+
@instance_id = instance_id
|
17
|
+
@ip = instance.public_ip_address
|
18
|
+
end
|
19
|
+
|
20
|
+
# attack the target, clean the previous attack result, preapre the attack and then start the attack
|
21
|
+
def attack(option)
|
22
|
+
Net::SSH.start(@ip, @username, :keys => [@key]) do |ssh|
|
23
|
+
# remove all exited containers
|
24
|
+
clean_cmd = _clean ssh
|
25
|
+
clean_cmd.wait
|
26
|
+
|
27
|
+
# prepare the attack
|
28
|
+
create_cmd = _prepare ssh, option
|
29
|
+
create_cmd.wait
|
30
|
+
|
31
|
+
# build the command
|
32
|
+
#open a new channel and run the container
|
33
|
+
attack_cmd = _execute ssh, option
|
34
|
+
attack_cmd.wait
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# connect to the instance and collect the results
|
39
|
+
def report
|
40
|
+
result = {}
|
41
|
+
|
42
|
+
Net::SSH.start(@ip, @username, :keys => [@key]) do |ssh|
|
43
|
+
|
44
|
+
data = ""
|
45
|
+
collection_cmd = _collection_info ssh, data
|
46
|
+
collection_cmd.wait
|
47
|
+
|
48
|
+
# parse the result
|
49
|
+
index = 1
|
50
|
+
data.split('Connection Times (ms)').each do |d|
|
51
|
+
data = Fleet.parse_ab_data d
|
52
|
+
if data.any?
|
53
|
+
result[index] = data
|
54
|
+
index += 1
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
Fleet.report result
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
# add new ab command to ab.sh
|
65
|
+
def _prepare(ssh, option)
|
66
|
+
benchmark_command = 'ab -s 60 -r -n %{number} -c %{concurrent} "%{url}" >> /root/${HOSTNAME}.out' % option
|
67
|
+
ssh.open_channel do |cha|
|
68
|
+
cha.exec "touch %{path}/ab.sh && echo '%{cmd}' > %{path}/ab.sh" % {:cmd => benchmark_command, :path => HOME_DIR} do |ch, success|
|
69
|
+
raise "could not execute command" unless success
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# start the attack
|
75
|
+
def _execute(ssh, option)
|
76
|
+
puts "Hive %s is starting it's attack" % @instance_id
|
77
|
+
ssh.open_channel do |cha|
|
78
|
+
# 'for i in {1..%s}; do nohup docker run -v /home/ubuntu:/root andizzle/debian bash /root/ab.sh; done'
|
79
|
+
cmd = ['docker run -v /home/ubuntu:/root andizzle/debian bash /root/ab.sh'] * option[:bees]
|
80
|
+
cha.exec cmd.join ' & ' do |ch, success|
|
81
|
+
raise "could not execute command" unless success
|
82
|
+
ch.on_data do |c, data|
|
83
|
+
puts data
|
84
|
+
end
|
85
|
+
ch.on_close { puts "Hive %s has finished it's attack!" % @instance_id}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def _collection_info(ssh, data_pool)
|
91
|
+
ssh.open_channel do |cha|
|
92
|
+
cha.exec 'find %s -name "*.out" -exec cat {} \;' % HOME_DIR do |ch, success|
|
93
|
+
raise "could not execute command" unless success
|
94
|
+
ch.on_data do |c, data|
|
95
|
+
data_pool << data
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# clean up the battlefield
|
102
|
+
def _clean(ssh)
|
103
|
+
ssh.open_channel do |cha|
|
104
|
+
cha.exec 'docker ps -aq -f status=exited | xargs docker rm && find /home/ubuntu -name "*.out" -exec rm {} \;' % HOME_DIR do |ch, success|
|
105
|
+
raise "could not execute command" unless success
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
metadata
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: evilhornets
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andy Zhang
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-05-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: aws-sdk
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: net-ssh
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.9'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.9'
|
41
|
+
description: Stress test your web apps.
|
42
|
+
email: andizzle.zhang@gmail.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- bin/hornet
|
48
|
+
- lib/hornet.rb
|
49
|
+
- lib/hornet/fleet.rb
|
50
|
+
- lib/hornet/headquarter.rb
|
51
|
+
- lib/hornet/hive.rb
|
52
|
+
homepage: https://github.com/andizzle/evilhornets
|
53
|
+
licenses:
|
54
|
+
- MIT
|
55
|
+
metadata: {}
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
requirements: []
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 2.4.5
|
73
|
+
signing_key:
|
74
|
+
specification_version: 4
|
75
|
+
summary: Launch EC2 micro instances, each instance creates multiple docker containers
|
76
|
+
to stress test your web applications.
|
77
|
+
test_files: []
|