haredo 0.0.5 → 2.0.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/src/bin/haredo ADDED
@@ -0,0 +1,115 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'optparse'
4
+ require 'haredo/version'
5
+ require 'haredo/admin/config'
6
+ require 'haredo/service/admin/daemon'
7
+ require 'haredo/service/daemon'
8
+
9
+ $config_dir = '/etc/haredo/config.yaml'
10
+
11
+ class CommandLine
12
+
13
+ def initialize()
14
+ @commands = {}
15
+
16
+ # Default is global service
17
+ @service = 'haredo'
18
+ end
19
+
20
+ def load(interface)
21
+ @commands[interface.name] = interface
22
+ end
23
+
24
+ def daemonize()
25
+
26
+ daemon = HareDo::Service::Daemon::new(@service)
27
+ daemon.run()
28
+
29
+ daemon.shutdown()
30
+
31
+ puts 'Daemon exiting'
32
+ end
33
+
34
+ def parse()
35
+ @daemonize = false
36
+
37
+ opts = OptionParser.new do |opts|
38
+ opts.banner = "HareDo #{HareDo::VERSION} (#{HareDo::RELEASE_DATE})\n\n"
39
+ opts.separator "Usage: haredo <global options> <command> <command_options>\n\n"
40
+
41
+ opts.separator 'Global options:'
42
+
43
+ opts.on('-c', '--config [val]', String, 'Use specific config file (default /etc/haredo/system.yml)') do |file|
44
+ @configfile = file
45
+ end
46
+
47
+ opts.on('-d', '--daemonize', String, 'Run in daemon mode') do
48
+ @daemonize = true
49
+ end
50
+
51
+ opts.on('-s', '--service [val]', String, 'Service to run') do |service|
52
+ @service = service
53
+ end
54
+
55
+ opts.on_tail("-h", "--help", "Display help") do
56
+ puts opts
57
+ puts
58
+ printHelp()
59
+
60
+ exit
61
+ end
62
+ end
63
+
64
+ opts.parse!(ARGV)
65
+
66
+ daemonize() if @daemonize == true
67
+ end
68
+
69
+ def printHelp()
70
+
71
+ puts "Available commands:\n"
72
+
73
+ @commands.keys.sort.each do |key|
74
+ desc = @commands[key].description
75
+ puts " %-10s %-25s " % [key, desc]
76
+ end
77
+
78
+ puts
79
+ puts "For a specific command's options, type: haredo <command> -h "
80
+ puts
81
+ end
82
+
83
+ def run()
84
+ command = ARGV[0]
85
+
86
+ if @commands.has_key?(command)
87
+ remaining = @commands[command].parse(ARGV[1..-1])
88
+ else
89
+
90
+ # HAAAAAAAAAAACK. These are shortcuts in case user forgets to type 'daemon
91
+ # start' or 'daemon {command}'
92
+
93
+ if command == 'start'
94
+ @commands['daemon'].parse(ARGV[0..-1])
95
+ end
96
+
97
+ if command == 'stop'
98
+ @commands['daemon'].parse(ARGV[0..-1])
99
+ end
100
+
101
+ if command == 'status'
102
+ @commands['daemon'].parse(ARGV[0..-1])
103
+ end
104
+
105
+ if parse() == false
106
+ printHelp()
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ program = CommandLine.new()
113
+ program.load(HareDo::Admin::Config::Interface.new())
114
+ program.load(HareDo::Admin::Daemon::Interface.new())
115
+ program.run()
@@ -0,0 +1,178 @@
1
+ require 'haredo/admin/interface'
2
+
3
+ module HareDo
4
+ module Admin
5
+ module Config
6
+
7
+ # The class implements to command line interface for configuration module
8
+ class Interface < Admin::Interface
9
+
10
+ def initialize()
11
+ super('config', 'Modify configuration')
12
+
13
+ @commands = {}
14
+
15
+ # Configure/test database connection settings
16
+ @commands['db'] = lambda do | name |
17
+
18
+ # Write changes to configuration file
19
+ if @write == true
20
+
21
+ loadConfig()
22
+
23
+ settings = {}
24
+ password = ENV['PGPASSWORD']
25
+
26
+ settings['host'] = @host if @host
27
+ settings['user'] = @username if @username
28
+ settings['password'] = password if password
29
+ settings['port'] = @port if @port
30
+ settings['name'] = @vhost if @vhost
31
+ settings['ssl'] = true if @ssl
32
+
33
+ @config['system']['db'] = settings
34
+
35
+ saveConfig()
36
+ end
37
+
38
+ end
39
+
40
+ # Configure/test RabbitMQ connection settings
41
+ @commands['broker'] = lambda do | name |
42
+
43
+ if @test == true
44
+ connected = false
45
+ params = {
46
+ :user => @username,
47
+ :password => ENV['RBQPASSWORD'],
48
+ :port => @port,
49
+ :vhost => @vhost,
50
+ :host => @host,
51
+ :ssl => @ssl
52
+ }
53
+
54
+ puts params
55
+
56
+ client = HareDo::Peer.new()
57
+
58
+ begin
59
+ connected = client.connect(params)
60
+ rescue
61
+ exit 1
62
+ end
63
+
64
+ if connected == true
65
+ puts 'succeeded'
66
+ client.disconnect()
67
+ exit 0
68
+ end
69
+
70
+ puts 'failed'
71
+ exit 1
72
+ end
73
+
74
+ if @write == true
75
+ # Write changes to configuration file
76
+
77
+ loadConfig()
78
+
79
+ settings = {}
80
+ password = ENV['RBQPASSWORD']
81
+
82
+ settings['host'] = @host if @host
83
+ settings['user'] = @username if @username
84
+ settings['password'] = password if password
85
+ settings['port'] = @port if @port
86
+ settings['vhost'] = @vhost if @vhost
87
+ settings['ssl'] = true if @ssl
88
+
89
+ @config['system']['broker'] = settings
90
+
91
+ saveConfig()
92
+ end
93
+ end
94
+ end
95
+
96
+ def help(opts)
97
+ puts opts
98
+ end
99
+
100
+ def parse(args)
101
+ $dir = nil
102
+ $count = 1
103
+
104
+ @ssl = false
105
+
106
+ opts = OptionParser.new do |opts|
107
+ opts.separator ''
108
+ opts.banner = "Test/update configuration and connection settings\n\n"
109
+ opts.banner += "Usage: config [options] {db | broker}"
110
+
111
+ opts.separator 'Available options:'
112
+
113
+ opts.on('-c', '--config [val]', String, 'Use specific config file (default /etc/haredo/system.yml)') do |file|
114
+ @configfile = file
115
+ $count += 1
116
+ end
117
+
118
+ opts.on_tail("-t", "--test", "Test connection settings") do
119
+ @test = true
120
+ end
121
+
122
+ opts.on_tail("-h", "--help", "Display help") do
123
+ puts opts
124
+ exit
125
+ end
126
+
127
+ opts.on_tail("-u", "--user [val]", String, "Username to use for connection") do |user|
128
+ @username = user
129
+ $count += 1
130
+ end
131
+
132
+ opts.on_tail("-s", "--server-host [val]", String, "Host") do |host|
133
+ @host = host
134
+ $count += 1
135
+ end
136
+
137
+ opts.on_tail("-e", "--encryption [val]", String, "Host to use for connection") do |ssl|
138
+ if ssl == 'true'
139
+ @ssl = true
140
+ end
141
+
142
+ $count += 1
143
+ end
144
+
145
+ opts.on_tail("-n", "--name [val]", String, "Name (Database name of RabbitMQ VHost)") do |name|
146
+ @vhost = name
147
+ $count += 1
148
+ end
149
+
150
+ opts.on_tail("-p", "--port [val]", String, "Port to use for connection)") do |port|
151
+ @port = port
152
+ $count += 1
153
+ end
154
+
155
+ opts.on_tail("-w", "--write-changed", "Write account setting to configuration file") do
156
+ @write = true
157
+ end
158
+
159
+ end
160
+
161
+ opts.parse!(args[0..-1])
162
+
163
+ command = args[0].chomp
164
+
165
+ if not @commands.has_key?(command)
166
+ help opts
167
+
168
+ return
169
+ end
170
+
171
+ @commands[command].call(command)
172
+ end
173
+
174
+ end # class
175
+
176
+ end # module Daemon
177
+ end # module Admin
178
+ end # module HareDo
@@ -0,0 +1,25 @@
1
+ require 'haredo/peer'
2
+ require 'haredo/config'
3
+
4
+ module HareDo
5
+ module Admin
6
+
7
+ class Interface
8
+
9
+ attr_reader :name, :description
10
+
11
+ include HareDo::Config
12
+
13
+ def initialize(name, desc)
14
+ @name = name
15
+ @description = desc
16
+ end
17
+
18
+ def parse(args)
19
+ # Derived class implements
20
+ end
21
+
22
+ end # class Interface
23
+
24
+ end # module Admin
25
+ end # module HareDo
@@ -0,0 +1,54 @@
1
+ require 'yaml'
2
+ require 'haredo/peer'
3
+
4
+ class Time
5
+ def isoDate()
6
+ return self.strftime("%Y-%m-%d %H:%M:%S")
7
+ end
8
+ end
9
+
10
+ module HareDo
11
+ module Config
12
+
13
+ def loadConfig()
14
+ @config_file = '/etc/haredo/haredo.conf'
15
+ @config = []
16
+ @config = YAML.load_file(@config_file)
17
+ @pid_file = '/var/run/haredo.pid'
18
+ end
19
+
20
+ def saveConfig()
21
+ file = File.open(@config_file, 'w')
22
+ file.write(YAML::dump(@config))
23
+ file.close()
24
+ end
25
+
26
+ # Connect to the RabbitMQ broker
27
+ #
28
+ # @return Return a client instance if connection was successful, nil otherwise
29
+ def connectBroker()
30
+
31
+ account = @config['system']['broker']
32
+
33
+ params = {
34
+ :user => account['user'],
35
+ :password => account['password'],
36
+ :host => account['host'],
37
+ :port => account['port'],
38
+ :vhost => account['vhost'],
39
+ :ssl => account['ssl']
40
+ }
41
+
42
+ peer = HareDo::Peer.new()
43
+
44
+ begin
45
+ connected = peer.connect(params)
46
+ rescue
47
+ return nil
48
+ end
49
+
50
+ return peer
51
+ end
52
+
53
+ end # module Config
54
+ end # module HareDo
@@ -1,7 +1,20 @@
1
- require "bunny"
1
+ require 'syslog'
2
+ require 'bunny'
2
3
  require 'haredo/version'
3
4
 
4
- module HareDo
5
+ def dump_message(msg)
6
+ if msg.properties != nil
7
+ puts ' Headers:'
8
+ msg.properties.each do |k,v|
9
+ puts " #{k}: #{v}"
10
+ end
11
+ end
12
+
13
+ puts " Data: #{msg.data}"
14
+ puts
15
+ end
16
+
17
+ module RabbitMQ
5
18
 
6
19
  # This is a simple class that represents a message delivered from a queue. The
7
20
  # attributes are as follows:
@@ -13,7 +26,7 @@ module HareDo
13
26
 
14
27
  class Message
15
28
 
16
- attr_reader :info, :properties, :headers, :data
29
+ attr_accessor :info, :properties, :headers, :data
17
30
 
18
31
  def initialize(info=nil, properties=nil, data=nil)
19
32
  @info = info
@@ -30,14 +43,137 @@ class Message
30
43
 
31
44
  end
32
45
 
33
- # This is an abstract base class used for both Client and Service.
46
+ end # module RabbitMQ
34
47
 
35
- class Node
48
+ module HareDo
49
+
50
+ #-------------------------------------------------------------------------------
51
+ # For managing plugins
52
+ #-------------------------------------------------------------------------------
53
+
54
+ module Plugins
36
55
 
37
- attr_reader :queue
56
+ class Instance
38
57
 
39
- def initialize()
58
+ def initialize(path)
59
+
60
+ # 1. Create an empty, anonymous module
61
+ @module = Module.new()
62
+
63
+ # 2. Get it's binding
64
+ @binding = @module.instance_eval 'binding'
40
65
 
66
+ # 3. Eval the desired file with the module's binding as
67
+ # context. Everything global in the module will be restricted to the
68
+ # module's namespace.
69
+ Kernel::eval(File.open(path).read(), @binding, path)
70
+ end
71
+
72
+ # Return a top-level object within the module
73
+ def get(object)
74
+ return @module.instance_eval object
75
+ end
76
+ end
77
+
78
+ # This class loads Ruby files/modules into private namespaces. That is, they are
79
+ # not globally visible.
80
+ #
81
+ # Example:
82
+ #
83
+ # instance = Plugins::Manager.load("#{@path}/plugin")
84
+ # mod = instance.get('Sonar')
85
+ # plugin = mod::Plugin.new(@config)
86
+ #
87
+ # plugins[plugin.uuid] = plugin
88
+
89
+ class Manager
90
+
91
+ attr_reader :loaded
92
+ attr_accessor :module_path_prefix
93
+
94
+ def initialize(peer)
95
+ @peer = peer
96
+ @loaded = {}
97
+ @config = {}
98
+
99
+ @module_path_prefix = 'haredo/plugins'
100
+ end
101
+
102
+ def [](name)
103
+ return @loaded[name]
104
+ end
105
+
106
+ def loadConfig(config)
107
+ @config.merge! config
108
+
109
+ config.each do |plugin_name, value|
110
+ load(plugin_name)
111
+ end
112
+ end
113
+
114
+ def load(plugin_name, base=@module_path_prefix)
115
+
116
+ # Iterate through the RUBY_PATH looking for a match
117
+ $:.each do |dir|
118
+ path = "#{dir}/#{base}/#{plugin_name}.rb"
119
+ if File.exists? path
120
+ instance = Instance.new(path)
121
+ cls = instance.get('Plugin')
122
+ plugin = cls.new(@peer, @config[cls::UUID])
123
+ @loaded[plugin.uuid] = plugin
124
+ $stderr.puts "Loaded plugin #{path}"
125
+
126
+ return
127
+ end
128
+ end
129
+ end
130
+
131
+ # Process a message. Looks up module by given by UUID in headers. If found,
132
+ # passes message off to it.
133
+ def process(msg)
134
+ uuid = msg.headers['uuid']
135
+
136
+ if @loaded.has_key?(uuid)
137
+ @loaded[uuid].process(msg)
138
+ end
139
+ end
140
+
141
+ def shutdown()
142
+ @loaded.each do|uuid, plugin|
143
+ plugin.finalize()
144
+ end
145
+ end
146
+
147
+ end # class Manager
148
+
149
+ end # module Plugins
150
+
151
+ # This is an abstract base class used for both Client and Service.
152
+
153
+ class Peer
154
+
155
+ attr_reader :queue, :plugins
156
+ attr_accessor :timeout, :sleep_interval
157
+
158
+ def initialize(name=nil)
159
+ # Message identifier
160
+ @mid = 0
161
+
162
+ # Client attributes
163
+ @timeout = 1.0
164
+ @sleep_interval = 0.001
165
+ @receive_queue = {}
166
+
167
+ # Server attributes
168
+ # The queue name used for listen()
169
+ @queue_name = name
170
+
171
+ @listen_queues = []
172
+
173
+ # The number of messages to prefecth from Rabbit
174
+ @prefetch = 10
175
+
176
+ @plugins = Plugins::Manager.new(self)
41
177
  end
42
178
 
43
179
  # Connect to RabbitMQ
@@ -72,82 +208,38 @@ class Node
72
208
 
73
209
  @channel = @cnx.create_channel()
74
210
 
211
+ @queue = @channel.queue( '', :auto_delete => true,
212
+ :arguments => { "x-message-ttl" => 1000 } )
213
+
214
+ @exchange = @channel.default_exchange()
215
+
75
216
  return true
76
217
  end
77
218
 
78
219
  # Disconnect from RabbitMQ
79
220
  def disconnect()
80
- @cnx.close()
81
- end
82
221
 
83
- # Sends a message.
84
- # @param to The to address
85
- # @param :data Message data
86
- # @param :headers Message headers
87
- # @param :headers Message from address
88
- # @param :properties Message properties
89
-
90
- def send(to, args)
91
-
92
- data = args[:data] || ''
93
- from = args[:from]
94
- headers = args[:headers] || {}
95
- properties = args[:properties] || {}
96
-
97
- properties[:routing_key] = to
98
- properties[:headers] = headers
99
-
100
- if not from.nil?
101
- properties[:reply_to] = from
102
- else
103
- properties[:reply_to] = @queue.name if @queue
222
+ if @queue != nil
223
+ @queue.delete()
224
+ @queue = nil
104
225
  end
105
226
 
106
- properties[:message_id] = @mid
107
-
108
- @exchange.publish(data, properties)
109
- end
110
-
111
- end # class Node
112
-
113
- # Implements a basic client designed for sending messages asynchronously and
114
- # blocking while receiving replies.
115
-
116
- class Client < Node
117
-
118
- attr_reader :queue
119
- attr_accessor :timeout, :sleep_interval
120
-
121
- def initialize()
122
- super
123
-
124
- @timeout = 1.0
125
- @sleep_interval = 0.001
126
- @receive_queue = {}
127
-
128
- # Message identifier
129
- @mid = 0
130
- end
131
-
132
- def connect(args)
133
- ret = super
134
-
135
- if ret == true
136
- @queue = @channel.queue( '', :auto_delete => true,
137
- :arguments => { "x-message-ttl" => 1000 } )
138
-
139
- @exchange = @channel.default_exchange()
227
+ @listen_queues.each do |listen_queue|
228
+ listen_queue.delete()
229
+ listen_queue = nil
140
230
  end
231
+
232
+ @listen_queues = {}
141
233
 
142
- return ret
143
- end
234
+ if @cnx != nil
235
+ @cnx.close()
236
+ @cnx = nil
237
+ end
144
238
 
145
- # Disconnect from RabbitMQ
146
- def disconnect()
147
- @queue.delete() if @queue
148
- super
239
+ @plugins.shutdown()
149
240
  end
150
241
 
242
+ # Sends a message.
151
243
  # Sends a message. Adds the @mid as message_id in message properties.
152
244
  # @param to The to address
153
245
  # @param :data Message data
@@ -164,13 +256,21 @@ class Client < Node
164
256
  headers = args[:headers] || {}
165
257
  properties = args[:properties] || {}
166
258
 
167
- properties[:message_id] = @mid
168
-
169
- super to, :data => data, :headers => headers, :properties => properties, :from => from
259
+ properties[:routing_key] = to
260
+ properties[:headers] = headers
261
+ properties[:message_id] = @mid
170
262
 
171
263
  rc = @mid
172
264
  @mid += 1
173
265
 
266
+ if not from.nil?
267
+ properties[:reply_to] = from
268
+ else
269
+ properties[:reply_to] = @queue.name if @queue
270
+ end
271
+
272
+ @exchange.publish(data, properties)
273
+
174
274
  return rc
175
275
  end
176
276
 
@@ -216,7 +316,7 @@ class Client < Node
216
316
  delivery_info, properties, payload = @queue.pop()
217
317
 
218
318
  if delivery_info != nil
219
- msg = Message.new(delivery_info, properties, payload)
319
+ msg = RabbitMQ::Message.new(delivery_info, properties, payload)
220
320
 
221
321
  if msg.headers.has_key?('id')
222
322
  if mid != nil
@@ -246,20 +346,24 @@ class Client < Node
246
346
  end
247
347
  end
248
348
 
249
- end # class Client
250
-
251
- # Represents a basic service class.
252
-
253
- class Service < Node
349
+ # You should use this method to reply back to a peer. It sets the reply header
350
+ # which tells the remote that this message is a response (as opposed to a
351
+ # message originating from another source which just happens to have the same
352
+ # message_id).
353
+ def reply(msg, args)
254
354
 
255
- # @param name The name of the queue the service will attempt to bind to.
256
- def initialize(name)
257
- super()
355
+ data = args[:data] || ''
356
+ headers = args[:headers] || {}
258
357
 
259
- @queue_name = name
358
+ id = msg.properties.message_id.to_i
359
+ to = msg.properties.reply_to
260
360
 
261
- # The number of messages to prefecth from Rabbit
262
- @prefetch = 10
361
+ # Set the reply flag to indicate that this is a response to a message
362
+ # sent. The message_id should already be set in the headers.
363
+ headers[:reply] = 1
364
+ headers[:id] = id.to_i
365
+
366
+ send(to, :headers => headers, :data => data)
263
367
  end
264
368
 
265
369
  # Defined the queue this service will listen on. Assumes a single-instance
@@ -275,40 +379,36 @@ class Service < Node
275
379
 
276
380
  # @returns Returns nil if non-blocking. Never returns if blocking.
277
381
 
278
- def run(args)
279
- @queue = createQueue()
382
+ def listen(args)
383
+ listen_queue = createQueue()
384
+
385
+ @listen_queues << listen_queue
386
+
280
387
  @exchange = @channel.default_exchange()
281
388
 
282
389
  block = args[:blocking] || false
283
390
 
284
391
  @channel.prefetch(@prefetch)
285
392
 
286
- queue.subscribe(:block => block, :ack => true) do |info, props, data|
287
- @channel.acknowledge(info.delivery_tag, false)
288
- serve Message.new(info, props, data)
393
+ if $syslog.nil?
394
+ Syslog.open( "haredo #{@name}", Syslog::LOG_PID,
395
+ Syslog::LOG_DAEMON | Syslog::LOG_LOCAL7 )
396
+
397
+ $syslog = true
289
398
  end
290
- end
291
399
 
292
- # You should use this method to reply back to a peer. It sets the reply header
293
- # which tells the remote that this message is a response (as opposed to a
294
- # message originating from another source which just happens to have the same
295
- # message_id).
296
- def reply(msg, args)
297
-
298
- data = args[:data] || ''
299
- headers = args[:headers] || {}
300
-
301
- id = msg.properties.message_id.to_i
302
- to = msg.properties.reply_to
303
-
304
- # Set the reply flag to indicate that this is a response to a message
305
- # sent. The message_id should already be set in the headers.
306
- headers[:reply] = 1
307
- headers[:id] = id.to_i
400
+ Syslog.notice('listen()')
308
401
 
309
- send(to, :headers => headers, :data => data)
402
+ listen_queue.subscribe(:block => block, :ack => true) do |info, props, data|
403
+ @channel.acknowledge(info.delivery_tag, false)
404
+ serve RabbitMQ::Message.new(info, props, data)
405
+ end
310
406
  end
311
-
312
- end # class Service
407
+
408
+ def serve(msg)
409
+ @plugins.process(msg)
410
+ end
411
+
412
+ end # class Peer
313
413
 
314
414
  end # module HareDo