hello_goodbye 0.1.0

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,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