zombees 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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