hello_goodbye 0.1.0

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