evilhornets 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|