haredo 0.0.5 → 2.0.0

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