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.
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ # rcov generated
2
+ coverage
3
+
4
+ # rdoc generated
5
+ rdoc
6
+
7
+ # yard generated
8
+ doc
9
+ .yardoc
10
+
11
+ # bundler
12
+ .bundle
13
+
14
+ # jeweler generated
15
+ pkg
16
+
17
+ # For vim:
18
+ *.swp
19
+
20
+ # built gems
21
+ *.gem
22
+
23
+ vendor/bundle
24
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in hello_goodbye.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Joshua Murray
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,308 @@
1
+ HelloGoodbye
2
+ ===============
3
+ A daemon manager with a TCP interface built on top of EventMachine.
4
+
5
+ Installation
6
+ ------------------
7
+ gem build hello_goodbye.gemspec
8
+ gem install hello_goodbye-0.1.0.gem
9
+
10
+ The Foremen Manager
11
+ -------------------
12
+ The foremen manager is the mother-ship for all your custom foremen that will spawn workers (or do whatever you wish).
13
+ A foreman itself, the manager creates a TCP console of its own so foremen can be reviewed and managed from the manager
14
+ itself.
15
+
16
+ manager = HelloGoodbye::ForemenManager.new(:port => 8080, :server => "127.0.0.1")
17
+ # or...
18
+ manager = HelloGoodbye.manager(8080, "127.0.0.1")
19
+
20
+ To register foremen (before "start!"ing the manager), execute code like the following:
21
+
22
+ manager.register_foreman( :port => 8081, :class => HelloGoodbye::TestForeman )
23
+
24
+ If you want to capture errors (and you should -- if the manager goes down, everything is liable to go down with it),
25
+ pass a block to the on_error method as follows:
26
+
27
+ manager.on_error do |e|
28
+ # Do stuff.
29
+ end
30
+
31
+ When you're all set up, fire away with the start! method:
32
+
33
+ manager.start!
34
+
35
+ This will block. After this is executed, all your manager and all your foremen should be listening in on their respective ports
36
+ for your command.
37
+
38
+ Consoles
39
+ -------------
40
+ After making a TCP connection to one of the consoles, communication should occur to and from the console using one of the following standard JSON packages.
41
+
42
+ Commands issued to a service should be formatted as follows:
43
+
44
+ {
45
+ "command": "YOUR COMMAND"
46
+ }
47
+
48
+ Responses from the service will be formatted as follows:
49
+
50
+ {
51
+ "success": true,
52
+ "message": "MESSAGE FROM SERVICE",
53
+ "results": []
54
+ }
55
+
56
+ Note the following:
57
+
58
+ * **success** can be **true** for **false**
59
+ * **results** will either be an array of data relavent to the command, or will be absent.
60
+
61
+ The Manager Console
62
+ --------------------
63
+ Once started, your manager will be available for TCP connections, and will respond to the following commands:
64
+ <table>
65
+ <tr>
66
+ <th>Command</th><th>Response Message</th><th>Results</th><th>Action Performed</th>
67
+ </tr>
68
+ <tr>
69
+ <td>
70
+ hello
71
+ </td>
72
+ <td>
73
+ hello
74
+ </td>
75
+ <td>
76
+ None.
77
+ </td>
78
+ <td>
79
+ Nothing. Just a convenient way to "ping" the service.
80
+ </td>
81
+ </tr>
82
+ <tr>
83
+ <td>
84
+ goodbye
85
+ </td>
86
+ <td>
87
+ goodbye
88
+ </td>
89
+ <td>
90
+ None.
91
+ </td>
92
+ <td>
93
+ Closes the connection.
94
+ </td>
95
+ </tr>
96
+ <tr>
97
+ <td>
98
+ foremen
99
+ </td>
100
+ <td>
101
+ ok
102
+ </td>
103
+ <td>
104
+ An array of hashes with details about each forman.
105
+ </td>
106
+ <td>
107
+ Nothing.
108
+ </td>
109
+ </tr>
110
+ <tr>
111
+ <td>
112
+ start XX
113
+ </td>
114
+ <td>
115
+ "ok" if successful.
116
+ </td>
117
+ <td>
118
+ An array of started foreman ids.
119
+ </td>
120
+ <td>
121
+ The foreman with the name XX is started. If XX is "all", all stopped foremen
122
+ will be started.
123
+
124
+ XX will be "test" if the foreman name is TestForman.
125
+ Otherwise, if XX is an integer, the ID of the active foreman will be consulted
126
+ instead of the name.
127
+ </td>
128
+ </tr>
129
+ <tr>
130
+ <td>
131
+ stop XX
132
+ </td>
133
+ <td>
134
+ "ok" if successful.
135
+ </td>
136
+ <td>
137
+ An array of stopped foreman ids.
138
+ </td>
139
+ <td>
140
+ The foreman with the name XX is stopped. If XX is "all", all active foremen
141
+ will be stopped.
142
+
143
+ XX will be "test" if the foreman name is TestForman.
144
+ Otherwise, if XX is an integer, the ID of the active foreman will be consulted
145
+ instead of the name.
146
+ </td>
147
+ </tr>
148
+ <tr>
149
+ <td>
150
+ (Anything else)
151
+ </td>
152
+ <td>
153
+ unknown command
154
+ </td>
155
+ <td>
156
+ None.
157
+ </td>
158
+ <td>Nothing.</td>
159
+ </tr>
160
+ </table>
161
+
162
+
163
+
164
+ Custom Foremen
165
+ -------------------
166
+ Foremen that you build must inherit from HelloGoodbye::Foreman. Beyond that, you should only have to implement a few instance methods that will be executed when the corresponding console commands are executed during a TCP connection:
167
+
168
+ def start
169
+ # Start listening for events to respond to.
170
+ end
171
+
172
+ def stop
173
+ # Stop listening for events.
174
+ end
175
+
176
+
177
+ Foremen Console
178
+ -------------------
179
+ Once started, your foreman will be available for TCP connections, and will respond to the following commands:
180
+ <table>
181
+ <tr>
182
+ <th>Command</th><th>Response Message</th><th>Results</th><th>Action Performed</th>
183
+ </tr>
184
+ <tr>
185
+ <td>
186
+ hello
187
+ </td>
188
+ <td>
189
+ hello
190
+ </td>
191
+ <td>
192
+ None.
193
+ </td>
194
+ <td>
195
+ Nothing. Just a convenient way to "ping" the service.
196
+ </td>
197
+ </tr>
198
+ <tr>
199
+ <td>
200
+ goodbye
201
+ </td>
202
+ <td>
203
+ goodbye
204
+ </td>
205
+ <td>
206
+ None.
207
+ </td>
208
+ <td>
209
+ Closes the connection.
210
+ </td>
211
+ </tr>
212
+ <tr>
213
+ <td>
214
+ start
215
+ </td>
216
+ <td>
217
+ ok
218
+ </td>
219
+ <td>
220
+ Nothing.
221
+ </td>
222
+ <td>
223
+ Executes the foreman's static "start" method. Typically, this would execute whatever "daemon" will listen for events and spawn workers.
224
+ </td>
225
+ </tr>
226
+ <tr>
227
+ <td>
228
+ stop
229
+ </td>
230
+ <td>
231
+ ok
232
+ </td>
233
+ <td>
234
+ Nada
235
+ </td>
236
+ <td>
237
+ Executes the forman's "stop" method, stopping the foreman's daemon.
238
+ </td>
239
+ </tr>
240
+ <tr>
241
+ <td>
242
+ status
243
+ </td>
244
+ <td>
245
+ "running" or "stopped"
246
+ </td>
247
+ <td>
248
+ Nada
249
+ </td>
250
+ <td>
251
+ Nothing.
252
+ </td>
253
+ </tr>
254
+ <tr>
255
+ <td>
256
+ (Anything else not implemented by your custom foreman)
257
+ </td>
258
+ <td>
259
+ unknown command
260
+ </td>
261
+ <td>
262
+ None.
263
+ </td>
264
+ <td>
265
+ Nothing.
266
+ </td>
267
+ </tr>
268
+ </table>
269
+
270
+ Custom Consoles
271
+ ------------------
272
+ Although there is a generic
273
+ ```
274
+ HelloGoodbye::ForemanConsole
275
+ ```
276
+ that will hopefully suit the needs for most usecases, custom consoles can easily be created and attached to custom foremen as needed.
277
+
278
+ First, to implement your custom console, inherit from
279
+ ```
280
+ HelloGoodbye::ForemanConsole
281
+ ```
282
+ and override
283
+ ```
284
+ receive_command
285
+ ```
286
+ .
287
+
288
+ To make your own class extensible, and to make use of the built in console commands implemented in the
289
+ ```
290
+ HelloGoodbye::ForemanConsole
291
+ ```
292
+ class, you'll want to start with the template below when overriding this method:
293
+
294
+ def receive_command(command)
295
+ # Process additional commands here.
296
+ # Return if processes successfully
297
+ super # Process the default commands. If no match, a failure response will be returned.
298
+ end
299
+
300
+ The last little catch is this: you must let your custom forman class know which console to use. To do this, in your Foreman class, assuming your console class is HelloGoodbye::TestConsole (yes, your console **must** be in the HelloGoodbye module ):
301
+
302
+ set_console_type :test # i.e. ":test" for TestConsole
303
+
304
+ Thus, the rules are as follow:
305
+
306
+ * Your console's class name must be prefixed with "Console".
307
+ * When setting this type with the class method, you must pass in a symbol matching the de-classified class name, minus the "Console" prefix.
308
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc "Run specs"
7
+ RSpec::Core::RakeTask.new(:spec) do |t|
8
+ t.rspec_opts = ["-c", "-f progress","-r ./spec/spec_helper.rb"]
9
+ t.pattern = './spec/**/*_spec.rb'
10
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/hello_goodbye/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Joshua Murray"]
6
+ gem.email = ["joshua.murray@gmail.com"]
7
+ gem.description = %q{A daemon manager with a TCP interface built on top of EventMachine.}
8
+ gem.summary = %q{A daemon manager with a TCP interface built on top of EventMachine.}
9
+ gem.homepage = "https://github.com/joshcom/hello_goodbye"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "hello_goodbye"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = HelloGoodbye::VERSION
17
+
18
+ gem.add_dependency "eventmachine", "0.12.10"
19
+ gem.add_dependency "json", "1.5.3"
20
+ gem.add_development_dependency "bundler"
21
+ gem.add_development_dependency "rake"
22
+ gem.add_development_dependency "shoulda", ">= 0"
23
+ gem.add_development_dependency "rspec", "2.6.0"
24
+ end
data/hello_goodbye.rb ADDED
@@ -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,88 @@
1
+ module HelloGoodbye
2
+
3
+ # Commands shared by all consoles:
4
+ # * 'hello' => Used to ping the server.
5
+ # Responds with 'hello'.
6
+ # * 'goodbye' => Used to close a connection.
7
+ # Responds with 'goodbye'.
8
+ class Console < EventMachine::Connection
9
+
10
+ require File.expand_path('../../hello_goodbye/consoles/foreman_console',__FILE__)
11
+ require File.expand_path('../../hello_goodbye/consoles/manager_console',__FILE__)
12
+ require File.expand_path('../../hello_goodbye/json/request',__FILE__)
13
+ require File.expand_path('../../hello_goodbye/json/response',__FILE__)
14
+
15
+ # :foreman
16
+ # A reference to the Foreman object that instantiated the console, so that
17
+ # the console can serve as an interface to this object.
18
+ attr_accessor :foreman
19
+
20
+ # Returns a standard console of a given type. I'm aware this logic is dumb.
21
+ # Parameters:
22
+ # * type
23
+ # ** :manager => ManagerConsole
24
+ def self.get(type)
25
+ case type
26
+ when :manager
27
+ ManagerConsole
28
+ when :foreman
29
+ ForemanConsole
30
+ else
31
+ if (obj = HelloGoodbye.const_get("#{type}_console".split('_').collect(&:capitalize).join))
32
+ obj
33
+ else
34
+ raise ArgumentError, "What type of console is #{type}?"
35
+ end
36
+ end
37
+ end
38
+
39
+ def foreman=(f)
40
+ f.console = self
41
+ @foreman = f
42
+ end
43
+
44
+ # Returns:
45
+ # true if data was handled, false if not.
46
+ def receive_command(command)
47
+ case command
48
+ when "hello"
49
+ send_response :success => true,
50
+ :message => "hello"
51
+ return true
52
+ when "goodbye"
53
+ send_response :success => true,
54
+ :message => "goodbye"
55
+ close_connection
56
+ return true
57
+ else
58
+ send_response :success => false,
59
+ :message => "unknown command"
60
+ end
61
+ false
62
+ end
63
+
64
+ # Parameters:
65
+ # hash
66
+ # * success => true or false
67
+ # * message => "Your message"
68
+ # * results => []
69
+ def send_response(hash)
70
+ send_data Response.new(hash).to_json
71
+ end
72
+
73
+ def receive_data(data)
74
+ data = data.strip
75
+ return false if data == ""
76
+
77
+ begin
78
+ json = Request.new(data)
79
+ rescue JSON::ParserError => e
80
+ send_response :success => false,
81
+ :message => "invalid json"
82
+ return false
83
+ end
84
+
85
+ receive_command(json.command)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,23 @@
1
+ module HelloGoodbye
2
+ class ForemanConsole < Console
3
+ def receive_command(command)
4
+ case command
5
+ when "start"
6
+ self.foreman.employ
7
+ send_response :success => true,
8
+ :message => "ok"
9
+ return true
10
+ when "stop"
11
+ self.foreman.unemploy
12
+ send_response :success => true,
13
+ :message => "ok"
14
+ return true
15
+ when "status"
16
+ send_response :success => true,
17
+ :message => self.foreman.status.to_s
18
+ return true
19
+ end
20
+ super
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ module HelloGoodbye
2
+ class ManagerConsole < Console
3
+ def receive_command(command)
4
+ case command
5
+ when /^start /
6
+ id = command.gsub(/^start /, "")
7
+ if (started_foremen = self.foreman.trigger_foreman(:start, id))
8
+ send_response :success => true,
9
+ :message => "ok",
10
+ :results => started_foremen
11
+ else
12
+ send_response :success => false,
13
+ :message => "no match for foreman '#{id}'"
14
+ end
15
+ return true
16
+ when /^stop /
17
+ id = command.gsub(/^stop /, "")
18
+ if (stopped_foremen = self.foreman.trigger_foreman(:stop,id))
19
+ send_response :success => true,
20
+ :message => "ok",
21
+ :results => stopped_foremen
22
+ else
23
+ send_response :success => false,
24
+ :message => "no match for foreman '#{id}'"
25
+ end
26
+ return true
27
+ when "foremen"
28
+ send_response :success => true, :message => "ok",
29
+ :results => self.foreman.report
30
+ return true
31
+ end
32
+ super
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,117 @@
1
+ module HelloGoodbye
2
+ class Foreman
3
+
4
+ attr_accessor :server, :port, :console, :my_id, :server_id
5
+ attr_reader :foreman_started
6
+
7
+ DEFAULT_SERVER = "127.0.0.1"
8
+
9
+ # Overrides the default ForemanConsole console type
10
+ # to fire up when #start! is called.
11
+ def self.set_console_type(console_sym)
12
+ @console_type = console_sym
13
+ end
14
+
15
+ # Returns the current console type for this class.
16
+ def self.console_type
17
+ @console_type ||= :foreman
18
+ end
19
+
20
+ def self.default_server
21
+ DEFAULT_SERVER
22
+ end
23
+
24
+ # Parameters:
25
+ # options:
26
+ # * server =>
27
+ # * port => The port to run the server on
28
+ def initialize(options={})
29
+ self.server = options[:server]
30
+ self.port = options[:port]
31
+ @foreman_started = false
32
+ end
33
+
34
+ # Starts the foreman's worker spawning action.
35
+ def start
36
+ raise ArgumentError, "Foreman.start must be implemented by child class."
37
+ end
38
+
39
+ # Stops the foreman's worker spawning action.
40
+ def stop
41
+ raise ArgumentError, "Foreman.start must be implemented by child class."
42
+ end
43
+
44
+ # Starts the console for the foreman. Subclasses should implement this method,
45
+ # passing a block to super to start up any tasks (AMQB subscriber, etc)
46
+ # that may need to be done.
47
+ def start!
48
+ raise RuntimeError, "Foreman already started!" if @foreman_started == true
49
+ start_with_reactor do
50
+ self.start_console
51
+ yield if block_given?
52
+ end
53
+ @foreman_started = true
54
+ end
55
+
56
+ # Detects the name to report this foreman class as.
57
+ # For example, will be "test" for "HelloGoodbye::TestForeman".
58
+ def my_name
59
+ begin
60
+ self.class.name.match(/^HelloGoodbye::(.*)Foreman$/)[1].downcase
61
+ rescue
62
+ self.class.name
63
+ end
64
+ end
65
+
66
+ # Reports the current status of the foreman.
67
+ # Returns:
68
+ # * :stopped if the foreman is not currently employing workers.
69
+ # * :running if the foreman is active
70
+ def status
71
+ @status || :stopped
72
+ end
73
+
74
+ # true if the foreman is currently employing workers
75
+ def running?
76
+ self.status == :running
77
+ end
78
+
79
+ # Sets the foreman status to either :running or :stopped
80
+ def status=(status)
81
+ @status = status.to_sym
82
+ end
83
+
84
+ # Sets the foreman status to :running and calls self.start
85
+ def employ
86
+ self.start
87
+ self.status = :running
88
+ end
89
+
90
+ # Sets the foreman status to :stopped and calls self.stop
91
+ def unemploy
92
+ self.stop
93
+ self.status = :stopped
94
+ end
95
+
96
+ def server
97
+ @server || DEFAULT_SERVER
98
+ end
99
+
100
+ def start_console
101
+ me = self
102
+ self.server_id = EM::start_server(self.server, self.port, Console.get(self.class.console_type)) do |c|
103
+ c.foreman = me
104
+ end
105
+ end
106
+
107
+ def start_with_reactor(&block)
108
+ if EM.reactor_running?
109
+ block.call
110
+ else
111
+ EM.run {
112
+ block.call
113
+ }
114
+ end
115
+ end
116
+ end
117
+ end