zombees 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.
@@ -0,0 +1,43 @@
1
+ require 'integration_spec_helper'
2
+ require 'zombees'
3
+ require 'zombees/honey_comb'
4
+
5
+ module Zombees
6
+ describe Worker, integration: true do
7
+ before(:each) { Fog.mock! }
8
+ let(:config) {{
9
+ provider: 'AWS',
10
+ aws_access_key_id: 'asdf',
11
+ aws_secret_access_key: 'ghi'
12
+ }}
13
+ let(:worker) { HoneyComb.new(config).worker }
14
+
15
+ it 'creates a worker instance' do
16
+ expect(worker).to be_a(described_class)
17
+ end
18
+
19
+ it 'prepares a worker on bootstrap' do
20
+ worker.bootstrap
21
+ expect(worker).to be_ready
22
+ end
23
+
24
+ it 'should destroy the server if its ready', focus: true do
25
+ worker.bootstrap
26
+ worker.server.should_receive(:destroy)
27
+ worker.shutdown
28
+ end
29
+
30
+ it 'should not destroy the server if ts not ready to shutdown', focus: true do
31
+ worker.bootstrap
32
+ worker.server.stub(:ready?, false)
33
+ worker.server.should_not_receive(:destroy)
34
+ worker.shutdown
35
+ end
36
+
37
+ it 'runs the command request on a worker' do
38
+ worker.bootstrap
39
+ stdout = worker.run_command('ls')
40
+ stdout.should_not be_nil
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ require 'spec_helper'
2
+ require 'fog'
3
+ Fog.mock!
@@ -0,0 +1,30 @@
1
+ require 'zombees'
2
+ require 'pry'
3
+ RSpec.configure do |config|
4
+ config.treat_symbols_as_metadata_keys_with_true_values = true
5
+ config.run_all_when_everything_filtered = true
6
+ config.filter_run :focus
7
+
8
+ config.order = 'random'
9
+
10
+ config.filter_run_excluding :integration
11
+ if ENV['CI']
12
+ config.filter_run_excluding :local
13
+ require 'coveralls'
14
+ Coveralls.wear!('test_frameworks')
15
+ elsif ENV['COVERAGE']
16
+ require 'simplecov'
17
+ SimpleCov.start('test_frameworks')
18
+ end
19
+
20
+ config.before(:each) do
21
+ #TODO Pull request to yell to expose level
22
+ require 'yell'
23
+ module Yell
24
+ class Logger
25
+ attr_accessor :level
26
+ end
27
+ end
28
+ Yell[Object].level = Yell.level.gt(:error)
29
+ end
30
+ end
@@ -0,0 +1,139 @@
1
+ require 'spec_helper'
2
+ require 'fog'
3
+ require 'zombees/ab_adapter'
4
+
5
+ module Zombees
6
+ describe AbAdapter do
7
+
8
+
9
+ it 'prepares the worker supplied' do
10
+ worker = mock('worker')
11
+ worker.should_receive(:run_command).with(/apt-get -y update.*apt-get -y install/)
12
+ subject.prepare(worker)
13
+ end
14
+
15
+ it 'runs a command on a command object' do
16
+ worker = stub('worker')
17
+ mock_command = mock('command')
18
+ subject.command_source = ->(config) { mock_command }
19
+
20
+ mock_command.should_receive(:run).with(worker)
21
+
22
+ subject.run(worker)
23
+ end
24
+
25
+ it 'runs a command with specified config' do
26
+ expected_config = { hello: 'world' }
27
+ subject = described_class.new(expected_config)
28
+ worker = stub('worker')
29
+
30
+ subject.command_source = ->(config) {
31
+ config.should == expected_config
32
+ stub('command').as_null_object
33
+ }
34
+
35
+ subject.run(worker)
36
+ end
37
+
38
+ it 'parsers the command result' do
39
+ mock_parser = mock('parser')
40
+ AbAdapter::Parser.stub(:new).with(hello: 'world').and_return(mock_parser)
41
+ mock_parser.should_receive(:parse)
42
+
43
+ described_class.new.parse(hello: 'world')
44
+ end
45
+
46
+ it 'parses and aggregates the result' do
47
+ mock_parser = mock('parser')
48
+ mock_aggregator = mock('aggregator')
49
+ AbAdapter::Parser.stub(:new).with(hello: 'world').and_return(mock_parser)
50
+ AbAdapter::Aggregator.stub(:new).with(command: 'results').and_return(mock_aggregator)
51
+ mock_parser.should_receive(:parse).and_return(command: 'results')
52
+ mock_aggregator.should_receive(:aggregate)
53
+
54
+ described_class.new.aggregate(hello: 'world')
55
+ end
56
+ end
57
+
58
+ describe AbAdapter::Command do
59
+ context 'command generation' do
60
+ subject { described_class.new(requests: 1000, concurrency: 5, url: 'http://a.co', ab_options: '--foo').command }
61
+ it { should match '^ab' }
62
+ it("doesn't exit on socket receive") { should match '-r' }
63
+ it('should specify number of requests') { should match '-n 1000' }
64
+ it('should specify concurrency') { should match '-c 5' }
65
+ it('should set a dummy session id') { should match '-C "sessionid=fake"' }
66
+ it('should send extra options to ab') { should match '--foo' }
67
+ it('should pass the url') { should match '"http://a.co"$' }
68
+ end
69
+
70
+ it 'generate a valid ab command', integration: true, local: true do
71
+ command = described_class.new(requests: 1, concurrency: 1, url: 'http://www.google.com/').command + "> /dev/null"
72
+ result = system(command)
73
+ result.should be_true
74
+ end
75
+
76
+ it 'executes a command on a worker' do
77
+ subject.stub(:command).and_return('ab')
78
+ worker = mock('Worker')
79
+ worker.should_receive(:run_command).with('ab')
80
+ subject.run(worker)
81
+ end
82
+ end
83
+
84
+ describe AbAdapter::Parser do
85
+ let(:output) { IO.read File.expand_path("../../fixtures/ab.txt", __FILE__) }
86
+
87
+ it 'transforms results of the ab command into a hash of data' do
88
+ data = described_class.parse(output)
89
+ data.should have_key(:requests_per_second)
90
+ data[:requests_per_second].should eq 120.06
91
+ data[:time_per_request].should eq 83.295
92
+ data[:time_per_request_concurrent].should eq 8.329
93
+ end
94
+
95
+ it 'transforms results of the run command' do
96
+ command_results = 3.downto(1).map do |i|
97
+ [ stub(:command, stdout: output) ]
98
+ end
99
+ parser = described_class.new(command_results)
100
+ data = parser.parse
101
+ data.should have(3).results
102
+ end
103
+ end
104
+ describe AbAdapter::Aggregator do
105
+ it 'doesnt have any keys if input doesnt have any' do
106
+ input = [{foo: :bar},{baz: :bat}]
107
+ subject = described_class.new(input)
108
+ result = subject.aggregate
109
+ result.should_not have_key(:time_per_request)
110
+ result.should_not have_key(:complete_requests)
111
+ end
112
+ it 'totalize number of completed requests' do
113
+ input = [{complete_requests: 10}, {complete_requests: 15}]
114
+ subject = described_class.new(input)
115
+ result = subject.aggregate
116
+ result.should have_key(:complete_requests)
117
+ result[:complete_requests].should == 25
118
+ end
119
+ it 'totalize number of completed requests' do
120
+ input = [{failed_requests: 2}, {failed_requests: 1}]
121
+ subject = described_class.new(input)
122
+ result = subject.aggregate
123
+ result.should have_key(:failed_requests)
124
+ result[:failed_requests].should == 3
125
+ end
126
+ it 'averages time per request' do
127
+ input = [{time_per_request: 1.2}, {time_per_request: 1.6}]
128
+ subject = described_class.new(input)
129
+ result = subject.aggregate
130
+ result.should have_key(:time_per_request)
131
+ result[:time_per_request].should == 1.4
132
+ end
133
+ end
134
+ end
135
+
136
+
137
+ describe Fog::SSH::Result do
138
+ it { described_class.instance_methods.should include :stdout }
139
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ module CommandRoleSpecs
4
+ describe 'CommandRole' do
5
+ it 'should contain a command class' do
6
+
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,47 @@
1
+ require 'zombees/connection'
2
+ require 'celluloid/pmap'
3
+ require 'spec_helper'
4
+
5
+ module Zombees
6
+ describe Connection do
7
+ describe '#instance' do
8
+ before do
9
+ Fog::Compute.stub(:new).and_return(stub(key_pairs: stub(get: true)))
10
+ end
11
+
12
+ # it 'creates a single instance for the same config' do
13
+ # config = {foo: 'bar'}
14
+ # first_connection = Connection.instance(config)
15
+ # second_connection = Connection.instance(config)
16
+ #
17
+ # first_connection.should_not be_nil
18
+ # first_connection.object_id.should == second_connection.object_id
19
+ # end
20
+ #
21
+ # it 'creates a multiple instances for different config' do
22
+ # first_connection = Connection.instance(foo: :bar)
23
+ # second_connection = Connection.instance(baz: :bat)
24
+ #
25
+ # first_connection.should_not be_nil
26
+ # first_connection.object_id.should_not == second_connection.object_id
27
+ # end
28
+ #
29
+ # it 'creates a single instance for the same config when instantiated from multiple threads', focus: true do
30
+ # # Faking wait on fog instantiation
31
+ # Fog::Compute.stub(:new) do
32
+ # sleep(rand(0.1..0.3))
33
+ # stub(key_pairs: stub(get: true))
34
+ # end
35
+ # config = {some_key: rand(1..10)}
36
+ # connections = 10.downto(1).pmap do
37
+ # Connection.instance(config)
38
+ # end
39
+ #
40
+ # connections.each_cons(2) do |first, second|
41
+ # first.should_not be_nil
42
+ # first.object_id.should == second.object_id
43
+ # end
44
+ # end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ module Zombees
4
+ describe HoneyComb do
5
+ before do
6
+ Connection.stub(:import_key_pair)
7
+ end
8
+ it 'stores a configuration' do
9
+ honeycomb = described_class.new(foo: :bar)
10
+ expect(honeycomb.config[:foo]).to eq :bar
11
+ end
12
+ it 'imports key pair' do
13
+ Connection.should_receive(:import_key_pair).with(foo: :bar)
14
+ honeycomb = described_class.new(foo: :bar)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'zombees/queen'
3
+
4
+ module Zombees
5
+ describe Queen do
6
+ let(:swarm) { mock('Swarm') }
7
+ let(:queen) {described_class.new(config: {}, worker_count: 3, command: "ls", swarm: swarm)}
8
+
9
+ before do
10
+ Connection.stub(:import_key_pair)
11
+ end
12
+ describe '#run' do
13
+ it 'runs the swarm' do
14
+ swarm.should_receive(:run)
15
+ queen.swarm_source = ->(options) { swarm }
16
+ queen.run
17
+ end
18
+ end
19
+
20
+ it 'creates a swarm with default options' do
21
+ queen = described_class.new(config: {foo: :bar}, worker_count: 3, command: 'ls')
22
+ queen.swarm_source = ->(options) do
23
+ options.honey_comb.config[:foo].should eq :bar
24
+ stub(run: true)
25
+ end
26
+ queen.run
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+ require 'zombees/swarm'
3
+ require 'zombees/ab_adapter'
4
+
5
+ module Zombees
6
+ module NoopAdapter
7
+ def self.prepare(worker)
8
+
9
+ end
10
+ def self.run(worker)
11
+
12
+ end
13
+ def self.aggregate(whatever)
14
+
15
+ end
16
+ class Command
17
+
18
+ end
19
+ end
20
+ class NoopWorker
21
+ def bootstrap; self end
22
+ end
23
+ describe Swarm do
24
+ def generate_swarm(population)
25
+ described_class.new(options, population)
26
+ end
27
+ let(:swarm) { generate_swarm(nil) }
28
+ let(:options) { SwarmOptions.new(worker_count: 3, command: noop_adapter, honey_comb: honey_comb) }
29
+ let(:honey_comb) { stub(worker: worker) }
30
+ let(:worker) { NoopWorker.new }
31
+ let(:noop_adapter) { NoopAdapter }
32
+
33
+ it 'bootstraps requested number of servers' do
34
+ worker.should_receive(:bootstrap).exactly(3).times
35
+
36
+ swarm.breed
37
+ end
38
+
39
+ it 'attempts shutdown if bootstrap fails' do
40
+ e = RuntimeError.new
41
+ worker.stub(:bootstrap).and_raise(e)
42
+ worker.should_receive(:shutdown).exactly(3).times.with(e)
43
+
44
+ swarm.breed
45
+ end
46
+
47
+ it 'adds command-specific configuration to workers' do
48
+ noop_adapter.should_receive(:prepare).with(worker).exactly(3).times
49
+
50
+ swarm.breed
51
+ end
52
+
53
+ it 'does not breed when already populated' do
54
+ worker.should_receive(:bootstrap).exactly(3).times.and_return(stub(run_command: true))
55
+
56
+ swarm.breed
57
+ swarm.breed
58
+ end
59
+
60
+
61
+ it 'distributes a command to the population of workers' do
62
+ workers = (1..3).map { |i| mock("Worker#{i}", shutdown: true) }
63
+
64
+ NoopAdapter.should_receive(:run).exactly(workers.size).times.and_return(true)
65
+ NoopAdapter.should_receive(:aggregate).with([true, true, true]).once.and_return(true)
66
+ swarm = generate_swarm(workers)
67
+ swarm.run
68
+ end
69
+
70
+ it 'attempts to shutdown worker if run fails' do
71
+ worker = mock('Worker')
72
+
73
+ e = RuntimeError.new
74
+ NoopAdapter.stub(:run).and_raise(e)
75
+ swarm = generate_swarm([worker])
76
+ worker.should_receive(:shutdown)
77
+ swarm.run
78
+ end
79
+
80
+ it 'attempts to shutdown worker if run fails' do
81
+ worker = mock('Worker')
82
+
83
+ e = RuntimeError.new
84
+ NoopAdapter.stub(:aggregate).and_return(true)
85
+ swarm = generate_swarm([worker])
86
+ worker.should_receive(:shutdown)
87
+ swarm.run
88
+ end
89
+ end
90
+ end
data/tourfleet ADDED
@@ -0,0 +1,27 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIIEowIBAAKCAQEA2BRbCBTnbp++xa/VfI3cygPgBStFvqtKYatrq0BOjOwO/xMt
3
+ de1xf9z693ZZkpSb3arCeh/6eBU6dLDPMXLkYvRaOhbgFbwR78jUR5mZQYwOB6WY
4
+ SFc81YgKyrVGHHZQN+48xnaNHuYCR3RBdMnBSY/tRsKtMWSTgaCwSpVH5rTtEwYe
5
+ k0jy7cmIw6i/RecnmCkmhoEupW3780aj0uLEWML7w6/ID0JALVUyObemhjN1H/6q
6
+ 3QoncfH1UvRqfEthMVSiQD8sp9ECK9JaMtCXEgPjXdlVatENsDHYCrHuHsACiGQm
7
+ W+j2JOdWrCXTE3FH7FHwNmraHH0EI4zj6kdK7QIDAQABAoIBACO9ejEkBAOwOIXK
8
+ R62KaXKgoXU5axfWjT8Kc4yn3lZA1VoreeF/nL5hdsFnufkcy4smq3Q3xd4G5NxY
9
+ Qm0Ta+NSN3pUPkxaBz16ImKVbVwCJm/tW/rbMxy+m8pboXUjvLEDntnU0hLCSK44
10
+ 9Hq0xFu+iqihmrQIxr0yLvanKnVmOeEJLOeU8L/O/opxxG0uaaQSQzjJRw5WjhIc
11
+ ZMCJ+VYNqe3tZMHNHtXRaNqp2hlSgeoNEq0XubxRCO4MuW8iFi8SywevbWYdOCj4
12
+ yhgp6Q4wr92qgtonn2j2UBJTYFgn9i9cr1ryk2msnA2vkmrAeOOKyMO4jKTm7RY4
13
+ tLLAlgECgYEA7e9boFVEL+qwIAb1lAMezbjzZ5ElUBWK9sSlFz63PsSlUWUUexlW
14
+ NWuqvGTM+sA1E/Ei9FV5yahM8fGTmmP/94SJXGinbhg98i517qbi2O56SuPS23db
15
+ 5dmJnyCZxu8MVoNqhGtJ+dM3aw+5ghj1QP5yoLJ4gjDFdt+sYX2gIPECgYEA6Hwz
16
+ tHJ/XpQDuJ7XeF58/vAfF049E9lv25oS7xVtmlw3XEG3Y0X75agnkC+KA+2/xHPX
17
+ ceWYI+EyBRo0/kqn3OUScyB2Lwg9I967YSIIir9dDddM71ruyh2PQliJcqzEJeUq
18
+ yN4/zb3xiF5eVUTayK0r6hhqHcphiq//PHBOib0CgYBd4dsSeXJtQbn6+SP/IfgO
19
+ jlKkY9YIMmfhlJfbgyiEwMzGQA8Dv2yPqYr1yQREUIDg/H2hUPS8CAdMU9i8y4Zd
20
+ INOePcEKpNAUdhaccwcBN5/TWu+BHyiImfw+aHukzf/dRv9Jfpfy1k+Ak/TLi5OB
21
+ 8KREGgeIvzu6+uimkw4S0QKBgDSDEV652gEv56NE5aB/nzYqYKtv9aXNIFH2/D3a
22
+ ljYejDafdV/MB/i4wa70vfTjN8SU8d39IR9Bl33FjKC/lijz6PXuKCO5da3remGX
23
+ QVytmsQslGkgHf2zLY+r1ef8FBYHLGHQqZK8S5kkz6Ps+IoJa3jl6Novw1aDKGCr
24
+ cWH1AoGBAJz8/QJ+Dnmav70cX7faOlzTvzaNjqgWnZNAHXo/xxEG3MeIq5Oo6ae6
25
+ ADXnkU42kxs5FjnlTzpBZ/cYueHylDZydatUKsVngnnnm9drdnHinvatBctpQAbE
26
+ t2NOTCaWbsHRhRvZhUPCxYcrpEBAeOIrctf1hqnNFEWRN1vzpE58
27
+ -----END RSA PRIVATE KEY-----
data/tourfleet.pub ADDED
@@ -0,0 +1 @@
1
+ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYFFsIFOdun77Fr9V8jdzKA+AFK0W+q0phq2urQE6M7A7/Ey117XF/3Pr3dlmSlJvdqsJ6H/p4FTp0sM8xcuRi9Fo6FuAVvBHvyNRHmZlBjA4HpZhIVzzViArKtUYcdlA37jzGdo0e5gJHdEF0ycFJj+1Gwq0xZJOBoLBKlUfmtO0TBh6TSPLtyYjDqL9F5yeYKSaGgS6lbfvzRqPS4sRYwvvDr8gPQkAtVTI5t6aGM3Uf/qrdCidx8fVS9Gp8S2ExVKJAPyyn0QIr0loy0JcSA+Nd2VVq0Q2wMdgKse4ewAKIZCZb6PYk51asJdMTcUfsUfA2atocfQQjjOPqR0rt solomon@G10205.local
data/zombee.png ADDED
Binary file
data/zombees.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'zombees/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "zombees"
8
+ spec.version = Zombees::VERSION
9
+ spec.authors = ["Maxim Filimonov","Solomon White"]
10
+ spec.email = ["tpaktopsp@gmail.com", "rubysolo@gmail.com"]
11
+ spec.description = %q{Zombees}
12
+ spec.summary = %q{Distributed load testing in Ruby}
13
+ spec.homepage = "http://github.com/zombees/zombees"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'celluloid-pmap'
22
+ spec.add_dependency 'celluloid', '~>0.13.0'
23
+ spec.add_dependency 'fog', '~>1.8.0'
24
+ spec.add_dependency 'net-ssh','~>2.5.0'
25
+ spec.add_dependency 'yell'
26
+ spec.add_dependency 'colorize'
27
+
28
+ spec.add_development_dependency 'rspec'
29
+ spec.add_development_dependency "bundler", "~> 1.3"
30
+ spec.add_development_dependency "rake"
31
+ spec.add_development_dependency "guard-rspec"
32
+ spec.add_development_dependency 'emoji-rspec'
33
+ spec.add_development_dependency 'coveralls'
34
+ end