hello_goodbye 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,103 @@
1
+ module HelloGoodbye
2
+ require File.expand_path('../../hello_goodbye/console',__FILE__)
3
+ require File.expand_path('../../hello_goodbye/foreman',__FILE__)
4
+ class ForemenManager < Foreman
5
+
6
+ DEFAULT_MANAGER_PORT = 8080
7
+
8
+ set_console_type :manager
9
+
10
+ def self.default_manager_port
11
+ DEFAULT_MANAGER_PORT
12
+ end
13
+
14
+ def initialize(options={})
15
+ @next_foreman_id = -1
16
+ options.map do |key,value|
17
+ self.send("#{key}=",value) if self.respond_to?("#{key}=".to_sym)
18
+ end
19
+ end
20
+
21
+ def port
22
+ @port || DEFAULT_MANAGER_PORT
23
+ end
24
+
25
+ def foremen
26
+ @foremen ||= []
27
+ end
28
+
29
+ def next_foreman_id
30
+ @next_foreman_id += 1
31
+ end
32
+
33
+ # Registers a new forman
34
+ # Parameters:
35
+ # * foreman_hash
36
+ # :port => (int) Port number to listen on.
37
+ # :class => A reference to the foreman class
38
+ # that will handle the connection and spawn
39
+ # workers.
40
+ def register_foreman(foreman_hash)
41
+ self.foremen << foreman_hash.merge(:reference => nil, :id => next_foreman_id)
42
+ end
43
+
44
+ # Starts the manager console and all the
45
+ # registered foremen.
46
+ def start!
47
+ super do
48
+ self.start_foremen
49
+ end
50
+ end
51
+
52
+ def on_error(&block)
53
+ EM::error_handler do |e|
54
+ block.call
55
+ end
56
+ end
57
+
58
+ # Start the consoles for all foremen. This does not start
59
+ # employing workers.
60
+ def start_foremen
61
+ self.foremen.each do |foreman|
62
+ foreman[:reference] = foreman[:class].new(:server => self.server,
63
+ :port => foreman[:port])
64
+ foreman[:reference].start!
65
+ end
66
+ end
67
+
68
+ # Trigger all formen to either start or stop employing workers.
69
+ # Parameters:
70
+ # * mode => :start or :stop
71
+ # * id => The foreman's integer ID or name
72
+ def trigger_foreman(mode,id)
73
+ [].tap do |toggled_foremen|
74
+ self.foremen.each do |foreman|
75
+ if foreman[:reference].nil? || (id != "all" && foreman[:reference].my_name != id && foreman[:id] != id.to_i)
76
+ next
77
+ end
78
+ next if mode == :start && foreman[:reference].running?
79
+ next if mode == :stop && !foreman[:reference].running?
80
+
81
+ if mode == :start
82
+ foreman[:reference].employ
83
+ else
84
+ foreman[:reference].unemploy
85
+ end
86
+ toggled_foremen << foreman[:id]
87
+ end
88
+ end
89
+ end
90
+
91
+ def report
92
+ [].tap do |r|
93
+ self.foremen.each do |foreman|
94
+ r << {
95
+ :id => foreman[:id],
96
+ :name => foreman[:reference].my_name,
97
+ :status => foreman[:reference].status.to_s
98
+ }
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,20 @@
1
+ module HelloGoodbye
2
+ class Request
3
+ attr_accessor :request_hash
4
+ attr_reader :command
5
+
6
+ def initialize(str)
7
+ self.request_data=str
8
+ end
9
+
10
+ def request_data=(str)
11
+ @request_data = JSON.parse(str)
12
+ unless @request_data.include?("command")
13
+ raise ArgumentError,
14
+ "The JSON string you supplied is invalid. All requests must include a 'command' attribute."
15
+ end
16
+ @command = @request_data["command"]
17
+ @request_data
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ module HelloGoodbye
2
+ class Response
3
+ attr_accessor :success, :results, :message
4
+
5
+ def initialize(options={})
6
+ options.map do |key,value|
7
+ self.send("#{key}=".to_sym,value) if self.respond_to?("#{key}=".to_sym)
8
+ end
9
+ end
10
+
11
+ def success
12
+ (@success == true ? true : false )
13
+ end
14
+
15
+ def results=(r)
16
+ if r.nil?
17
+ @results = nil
18
+ else
19
+ @results = Array(r)
20
+ end
21
+ end
22
+
23
+ def to_hash
24
+ h = {
25
+ "success" => self.success,
26
+ "message" => self.message.to_s
27
+ }
28
+ h["results"] = self.results if !self.results.nil?
29
+ h
30
+ end
31
+
32
+ def to_json
33
+ self.to_hash.to_json
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,3 @@
1
+ module HelloGoodbye
2
+ VERSION = File.read(File.dirname(__FILE__) + "/../../VERSION").chomp
3
+ end
@@ -0,0 +1,25 @@
1
+ require 'rubygems'
2
+ require 'eventmachine'
3
+ require 'json'
4
+
5
+ require File.expand_path('../hello_goodbye/foremen_manager',__FILE__)
6
+
7
+ module HelloGoodbye
8
+
9
+ # Resets the current foremen manager, so that the next time
10
+ # self.manager is called, a new ForemenManager instance will be
11
+ # created.
12
+ def self.reset!
13
+ @manager = nil
14
+ end
15
+
16
+ # Create a new manager or use the existing manager. If an existing manager exists,
17
+ # port and server will be ignored.
18
+ # Parameters:
19
+ # * port: The port the manager should connect to.
20
+ # * server: The server the service will run from.
21
+ def self.manager(port=ForemenManager.default_manager_port,server=Foreman.default_server)
22
+ @manager ||= ForemenManager.new(:port => port, :server => server)
23
+ end
24
+
25
+ end
@@ -0,0 +1,67 @@
1
+ describe HelloGoodbye::Console do
2
+
3
+ let(:console) do
4
+ h = HelloGoodbye::Console.new 1
5
+
6
+ # Override EM method for this object
7
+ def h.send_data(data)
8
+ # Do nothing
9
+ end
10
+
11
+ def h.close_connection
12
+ # Do nothing
13
+ end
14
+
15
+ h
16
+ end
17
+
18
+ let(:test_console) do
19
+ HelloGoodbye::TestConsole.new 1
20
+ end
21
+
22
+ describe ".get" do
23
+ it "should raise error when console not recognized" do
24
+ expect{
25
+ HelloGoodbye::Console.get(:nothing)
26
+ }.to raise_error
27
+ end
28
+ it "should return a manager console" do
29
+ HelloGoodbye::Console.get(:manager).should be(HelloGoodbye::ManagerConsole)
30
+ end
31
+ end
32
+
33
+ describe "#receive_command" do
34
+ it "should return false on unknown actions" do
35
+ console.receive_command("custom_action").should be(false)
36
+ end
37
+
38
+ it "should return true on known actions" do
39
+ console.receive_command("hello").should be(true)
40
+ end
41
+ end
42
+
43
+ context "basic commands" do
44
+ it "should reply to hello" do
45
+ command_to_console(console,"hello")["message"].should eq("hello")
46
+ end
47
+ it "should reply to goodbye and close connection" do
48
+ command_to_console(console,"goodbye")["message"].should eq("goodbye")
49
+ console.connection_closed?().should be_true
50
+ end
51
+ it "should report unknown commands" do
52
+ c = command_to_console(console,"unknown_action")
53
+ c["message"].should eq("unknown command")
54
+ c["success"].should be_false
55
+ end
56
+ end
57
+
58
+ context "custom consoles" do
59
+ it "should reply to default commands" do
60
+ command_to_console(test_console,"hello")["message"].should eq("hello")
61
+ end
62
+ it "should reply to custom commands" do
63
+ command_to_console(test_console,"custom")["message"].should eq("test command")
64
+ end
65
+ end
66
+
67
+ end
@@ -0,0 +1,35 @@
1
+ describe HelloGoodbye::Console do
2
+
3
+ before(:all) do
4
+ @foreman = HelloGoodbye::TestForeman.new
5
+ end
6
+
7
+ let(:foreman_console) do
8
+ f = HelloGoodbye::TestConsole.new(1)
9
+ f.foreman = @foreman
10
+ f
11
+ end
12
+
13
+ let(:foreman) do
14
+ @foreman
15
+ end
16
+
17
+ context "starting and stopping the foreman" do
18
+ it "should start the foreman class from the console" do
19
+ r = command_to_console(foreman_console,"start")
20
+ r["message"].should eq("ok")
21
+ foreman.status.should be(:running)
22
+ end
23
+
24
+ it "should stop the foreman class from the console" do
25
+ r = command_to_console(foreman_console,"stop")
26
+ r["message"].should eq("ok")
27
+ foreman.status.should be(:stopped)
28
+ end
29
+
30
+ it "should stop report the status of the forman" do
31
+ r = command_to_console(foreman_console,"status")
32
+ r["message"].should eq("stopped")
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,47 @@
1
+ describe HelloGoodbye::ManagerConsole do
2
+ before(:all) do
3
+ @manager = HelloGoodbye::ForemenManager.new
4
+ @manager.register_foreman({
5
+ :port => 8081,
6
+ :class => HelloGoodbye::TestForeman
7
+ })
8
+ end
9
+
10
+ let(:manager) do
11
+ @manager
12
+ end
13
+
14
+ let(:manager_console) do
15
+ m = HelloGoodbye::ManagerConsole.new(1)
16
+ m.foreman = @manager
17
+ m
18
+ end
19
+
20
+ context "when running with a manager" do
21
+ it "should report on active foreman" do
22
+ start_foremen_manager_and(manager) do |manager|
23
+ h = command_to_console(manager_console, "foremen")
24
+ h["results"].size.should be(1)
25
+ end
26
+ end
27
+
28
+ it "should start foremen by name" do
29
+ start_foremen_manager_and(manager) do |manager|
30
+ h = command_to_console(manager_console, "start test")
31
+ h["results"].first.should be(0)
32
+ manager_console.foreman.foremen.first[:reference].running?().should be_true
33
+
34
+ h = command_to_console(manager_console, "stop 0")
35
+ h["results"].first.should be(0)
36
+ manager_console.foreman.foremen.first[:reference].running?().should be_false
37
+ end
38
+ end
39
+
40
+ it "should start all foremen with 'all'" do
41
+ h = command_to_console(manager_console, "start all")
42
+ h["results"].first.should be(0)
43
+ manager_console.foreman.foremen.first[:reference].running?().should be_true
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,84 @@
1
+ describe HelloGoodbye::ForemenManager do
2
+
3
+ before(:all) do
4
+ @manager = HelloGoodbye::ForemenManager.new
5
+ end
6
+
7
+ let(:manager) do
8
+ @manager
9
+ end
10
+
11
+ describe ".console_type" do
12
+ it "should be :manager" do
13
+ HelloGoodbye::ForemenManager.console_type.should be(:manager)
14
+ end
15
+ end
16
+
17
+ context "when registering foremen" do
18
+ it "should allow foremen to be registered" do
19
+ manager.register_foreman({
20
+ :port => 8081,
21
+ :class => HelloGoodbye::TestForeman
22
+ })
23
+ manager.register_foreman({
24
+ :port => 8082,
25
+ :class => HelloGoodbye::TestForeman
26
+ })
27
+ manager.foremen.size.should be(2)
28
+ end
29
+
30
+ it "should give each forman unique ids" do
31
+ manager.foremen.first[:id].should_not eq(manager.foremen[1][:id])
32
+ end
33
+ it "should not have references to objects" do
34
+ manager.foremen.each do |f|
35
+ f[:reference].should be_nil
36
+ end
37
+ end
38
+ end
39
+
40
+ context "when starting the manager" do
41
+ it "should spawn a responsive console" do
42
+ start_foremen_manager_and do |manager|
43
+ EM.run_block do
44
+ expect {
45
+ c = TCPSocket.open(default_server,default_port)
46
+ c.close
47
+ }.to_not raise_error
48
+ end
49
+ end
50
+ end
51
+
52
+ it "should start foremen" do
53
+ start_foremen_manager_and(manager) do |manager|
54
+ manager.foremen.each do |f|
55
+ f[:reference].should be_a(HelloGoodbye::TestForeman)
56
+ end
57
+ end
58
+ end
59
+
60
+ it "should spawn a responsive console for foremen" do
61
+ start_foremen_manager_and(manager) do |manager|
62
+ EM.run_block do
63
+ expect {
64
+ c = TCPSocket.open(default_server,manager.foremen.first[:reference].port)
65
+ c.close
66
+ }.to_not raise_error
67
+ end
68
+ end
69
+ end
70
+
71
+ it "should report active foreman" do
72
+ start_foremen_manager_and(manager) do |manager|
73
+ EM.run_block do
74
+ manager.report.first.should eq({:id => manager.foremen.first[:id],
75
+ :name => "test", :status => "stopped"})
76
+ manager.foremen.first[:reference].employ
77
+ manager.report.first.should eq({:id => manager.foremen.first[:id],
78
+ :name => "test", :status => "running"})
79
+ end
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,64 @@
1
+ describe HelloGoodbye::Foreman do
2
+ before(:all) do
3
+ @manager = HelloGoodbye::ForemenManager.new
4
+ @manager.register_foreman({
5
+ :port => 8081,
6
+ :class => HelloGoodbye::TestForeman
7
+ })
8
+ end
9
+ let(:manager) do
10
+ @manager
11
+ end
12
+
13
+ let(:foreman) do
14
+ HelloGoodbye::Foreman
15
+ end
16
+
17
+ let(:test_foreman) do
18
+ HelloGoodbye::TestForeman
19
+ end
20
+
21
+ describe ".console_type" do
22
+ it "should default to type :foreman" do
23
+ foreman.console_type.should be(:foreman)
24
+ end
25
+
26
+ it "should be overridable by custom consoles :test_foreman" do
27
+ test_foreman.console_type.should be(:test)
28
+ end
29
+ end
30
+
31
+ describe "starting the foreman" do
32
+ it "should only be startable once" do
33
+ f = foreman.new
34
+ EM.run_block do
35
+ f.start!
36
+ expect {
37
+ f.start!
38
+ }.to raise_error
39
+ end
40
+ end
41
+ end
42
+
43
+ context "under the control of a manager" do
44
+ it "should be stopped initially" do
45
+ start_foremen_manager_and(manager) do |manager|
46
+ manager.foremen.first[:reference].status.should be(:stopped)
47
+ end
48
+ end
49
+ it "should be employable" do
50
+ start_foremen_manager_and(manager) do |manager|
51
+ f = manager.foremen.first[:reference]
52
+ f.employ
53
+ f.status.should be(:running)
54
+ end
55
+ end
56
+ it "should be unemployable" do
57
+ start_foremen_manager_and(manager) do |manager|
58
+ f = manager.foremen.first[:reference]
59
+ f.unemploy
60
+ f.status.should be(:stopped)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,28 @@
1
+ describe HelloGoodbye::Request do
2
+ let(:request) do
3
+ HelloGoodbye::Request
4
+ end
5
+ describe "#request_data" do
6
+ it "should require valid json" do
7
+ expect {
8
+ request.new("So not JSON")
9
+ }.to raise_error
10
+ end
11
+
12
+ it "should require a command attribute" do
13
+ expect {
14
+ request.new '{"success": true}'
15
+ }.to raise_error
16
+ end
17
+
18
+ it "should be successful with a command" do
19
+ expect {
20
+ request.new '{"command": "hello"}'
21
+ }.to_not raise_error
22
+ end
23
+
24
+ it "should set command attribute" do
25
+ request.new('{"command": "hello"}').command.should eq("hello")
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ describe HelloGoodbye::Response do
2
+ let(:results_response) do
3
+ HelloGoodbye::Response.new :success => true,
4
+ :message => "Hi Mom!", :results => [{"color" => "blue"}]
5
+ end
6
+ let(:response) do
7
+ HelloGoodbye::Response.new
8
+ end
9
+
10
+ it "should set fields from initialization" do
11
+ results_response.success.should be_true
12
+ results_response.message.should eq("Hi Mom!")
13
+ results_response.results.first.should eq({"color" => "blue"})
14
+ end
15
+
16
+ it "should default to unsuccessful" do
17
+ response.success.should be_false
18
+ end
19
+
20
+ it "should exclude results from hash when there are none" do
21
+ response.to_hash["results"].should be_nil
22
+ end
23
+
24
+ it "should build a hash based on values" do
25
+ results_response.to_hash["message"].should eq("Hi Mom!")
26
+ end
27
+
28
+ it "should build a hash based on values" do
29
+ results_response.to_hash["results"].size.should eq(1)
30
+ end
31
+
32
+ it "should return a json string" do
33
+ response.to_json.should be_a(String)
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ describe HelloGoodbye do
2
+ describe "Mother Module" do
3
+ it "should create a new manager" do
4
+ m = HelloGoodbye.manager
5
+ m.should be_a(HelloGoodbye::ForemenManager)
6
+ end
7
+
8
+ it "should return the same manager instance" do
9
+ m = HelloGoodbye.manager(2000,"joshcom.net")
10
+ m2 = HelloGoodbye.manager
11
+ m.should be(m2)
12
+ end
13
+
14
+ it "should create a new manager instance after reset!" do
15
+ m = HelloGoodbye.manager(2000,"joshcom.net")
16
+ HelloGoodbye.reset!
17
+ m2 = HelloGoodbye.manager
18
+ m.should_not be(m2)
19
+ end
20
+ end
21
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --require spec_helper
@@ -0,0 +1,66 @@
1
+ require File.expand_path('../../lib/hello_goodbye',__FILE__)
2
+ require File.expand_path('../../spec/test_foreman',__FILE__)
3
+ require File.expand_path('../../spec/test_console',__FILE__)
4
+ require File.expand_path('../../lib/hello_goodbye/json/request',__FILE__)
5
+ require File.expand_path('../../lib/hello_goodbye/json/response',__FILE__)
6
+
7
+ require 'rspec'
8
+
9
+ # Possibly the most important helper method.
10
+ # This method, in the following order:
11
+ # 1. Starts an event loop
12
+ # 2. Executes #start! on either the parameter ForemenManager or
13
+ # creates a new ForemenManager and executes start! on that.
14
+ # 3. Yields to a given block, passing the ForemenManager to that block.
15
+ # 4. Stops the event loop.
16
+ def start_foremen_manager_and(f=spec_manager)
17
+ f.instance_variable_set("@foreman_started",false)
18
+ EM.run_block do
19
+ f.start!
20
+ yield(f)
21
+ end
22
+ end
23
+
24
+ def command_to_json_str(command)
25
+ {
26
+ :command => command
27
+ }.to_json
28
+ end
29
+
30
+ def command_to_console(console,command)
31
+ sent_data = nil
32
+ override_method(console,'send_data') do |data|
33
+ sent_data = data
34
+ end
35
+ override_method(console,'close_connection') do
36
+ @test_connection_closed = true
37
+ end
38
+ override_method(console,'connection_closed?') do
39
+ @test_connection_closed || false
40
+ end
41
+ console.receive_command(command)
42
+ build_response_hash(sent_data)
43
+ end
44
+
45
+ def override_method(console,method,&block)
46
+ (class << console; self; end).send(:define_method, method) do |*args|
47
+ block.call(*args)
48
+ end
49
+ end
50
+
51
+ def build_response_hash(data)
52
+ JSON.parse(data)
53
+ end
54
+
55
+ def default_server
56
+ "127.0.0.1"
57
+ end
58
+
59
+ def default_port
60
+ 8080
61
+ end
62
+
63
+ def spec_manager
64
+ HelloGoodbye::ForemenManager.new(:port => default_port,
65
+ :server => default_server)
66
+ end
@@ -0,0 +1,23 @@
1
+ require File.expand_path('../../lib/hello_goodbye',__FILE__)
2
+
3
+ module HelloGoodbye
4
+ class TestConsole < ForemanConsole
5
+ def receive_command(command)
6
+ case command
7
+ when "custom"
8
+ send_response :success => true,
9
+ :message => "test command"
10
+ return true
11
+ when "error"
12
+ send_response :success => true,
13
+ :message => "oops"
14
+ raise RuntimeError, "Oh noooooes!"
15
+ when "kill"
16
+ send_response :success => true,
17
+ :message => "Why me?"
18
+ EM.stop
19
+ end
20
+ super
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ require File.expand_path('../../lib/hello_goodbye',__FILE__)
2
+
3
+ module HelloGoodbye
4
+ class TestForeman < Foreman
5
+ set_console_type :test
6
+
7
+ def start
8
+ # Do nothing
9
+ end
10
+
11
+ def stop
12
+ # Do nothing
13
+ end
14
+ end
15
+ end