qswarm 0.0.2 → 0.0.4

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/bin/qswarm CHANGED
@@ -1,10 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ $stdout.sync = true
4
+
3
5
  require 'qswarm'
4
6
 
5
7
  usage = "#{$0} <configuration file>"
6
8
  abort usage unless config = ARGV.shift
7
9
 
10
+ $graylog2_facility = config.split('.')[0]
11
+ $graylog2_host = ARGV.shift || 'localhost'
12
+
13
+
8
14
  swarm = Qswarm::Swarm.load config
9
- swarm.log.level = Logger::INFO
15
+ swarm.log.level = Logger::DEBUG
10
16
  swarm.run
data/lib/qswarm/agent.rb CHANGED
@@ -7,11 +7,35 @@ module Qswarm
7
7
 
8
8
  attr_reader :swarm, :name
9
9
 
10
- def initialize(swarm, name, &block)
10
+ def initialize(swarm, name, args, &block)
11
11
  @swarm = swarm
12
12
  @name = name.to_s
13
13
  @brokers = {}
14
14
  @listeners = []
15
+ @args = args
16
+
17
+ unless args.nil?
18
+ case args.delete :type
19
+ when :esper
20
+ require 'java'
21
+
22
+ require 'esper-4.5.0/esper-4.5.0.jar'
23
+ require 'esper-4.5.0/esper/lib/commons-logging-1.1.1.jar'
24
+ require 'esper-4.5.0/esper/lib/antlr-runtime-3.2.jar'
25
+ require 'esper-4.5.0/esper/lib/cglib-nodep-2.2.jar'
26
+ require 'esper-4.5.0/esper/lib/log4j-1.2.16.jar'
27
+
28
+ include_class 'com.espertech.esper.client.EPRuntime'
29
+ include_class 'com.espertech.esper.client.EPServiceProviderManager'
30
+ include_class 'com.espertech.esper.client.EPServiceProvider'
31
+ include_class 'com.espertech.esper.client.EPStatement'
32
+
33
+ include_class 'com.espertech.esper.client.UpdateListener'
34
+ include_class 'com.espertech.esper.client.EventBean'
35
+ include_class 'org.apache.commons.logging.Log'
36
+ include_class 'org.apache.commons.logging.LogFactory'
37
+ end
38
+ end
15
39
 
16
40
  self.instance_eval(&block)
17
41
  end
data/lib/qswarm/broker.rb CHANGED
@@ -9,7 +9,7 @@ module Qswarm
9
9
  include Qswarm::Loggable
10
10
  extend Qswarm::DSL
11
11
 
12
- dsl_accessor :name, :host, :port, :user, :pass, :vhost, :exchange_type, :exchange_name, :durable, :prefetch
12
+ dsl_accessor :name, :host, :port, :user, :pass, :vhost, :exchange_type, :exchange_name, :durable
13
13
  @@connection = {}
14
14
 
15
15
  def initialize(name, &block)
@@ -26,6 +26,12 @@ module Qswarm
26
26
  @durable = true
27
27
  @prefetch = nil
28
28
 
29
+ # if block.arity == 1
30
+ # yield self
31
+ # else
32
+ # instance_eval &block
33
+ # end
34
+
29
35
  self.instance_eval(&block)
30
36
 
31
37
  @queues = {}
@@ -50,6 +56,9 @@ module Qswarm
50
56
  @exchange ||= begin
51
57
  @exchange = AMQP::Exchange.new(channel ||= AMQP::Channel.new(connection), @exchange_type, @exchange_name, :durable => @durable) do |exchange|
52
58
  logger.debug "Declared #{@exchange_type} exchange #{@vhost}/#{@exchange_name}"
59
+ @exchange.on_return do |basic_return, metadata, payload|
60
+ logger.error "#{payload} was returned! reply_code = #{basic_return.reply_code}, reply_text = #{basic_return.reply_text}"
61
+ end
53
62
  end
54
63
  end
55
64
  end
@@ -58,15 +67,51 @@ module Qswarm
58
67
  def channel name, routing_key = ''
59
68
  @channels["#{name}/#{routing_key}"] ||= begin
60
69
  logger.debug "Opening channel for #{name}/#{routing_key}"
61
- @channels["#{name}/#{routing_key}"] = AMQP::Channel.new(connection, :prefetch => @prefetch)
70
+ @channels["#{name}/#{routing_key}"] = AMQP::Channel.new(connection, AMQP::Channel.next_channel_id, :auto_recovery => true) do |c|
71
+ @channels["#{name}/#{routing_key}"].on_error do |channel, channel_close|
72
+ logger.error "[channel.close] Reply code = #{channel_close.reply_code}, reply text = #{channel_close.reply_text}"
73
+ end
74
+ end
62
75
  end
63
76
  end
64
77
 
65
78
  def connection
66
79
  # Pool connections at the class level
67
80
  @@connection["#{@host}:#{@port}#{@vhost}"] ||= begin
68
- logger.debug "Connecting to AMQP broker at #{@host}:#{@port}#{@vhost}"
69
- @@connection["#{@host}:#{@port}#{@vhost}"] = AMQP.connect("amqp://#{@user}:#{@pass}@#{@host}:#{@port}/#{CGI.escape(@vhost)}")
81
+ logger.debug "Connecting to AMQP broker at #{self.to_s}"
82
+ @@connection["#{@host}:#{@port}#{@vhost}"] = AMQP.connect(self.to_s, :heartbeat => 30, :on_tcp_connection_failure => Proc.new { |settings|
83
+ logger.error "AMQP initial connection failure to #{settings[:host]}:#{settings[:port]}#{settings[:vhost]}"
84
+ EM.stop
85
+ }, :on_possible_authentication_failure => Proc.new { |settings|
86
+ logger.error "AMQP initial authentication failed for #{settings[:host]}:#{settings[:port]}#{settings[:vhost]}"
87
+ EM.stop
88
+ }
89
+ ) do |c|
90
+ @@connection["#{@host}:#{@port}#{@vhost}"].on_recovery do |connection|
91
+ logger.debug "Recovered from AMQP network failure"
92
+ end
93
+ @@connection["#{@host}:#{@port}#{@vhost}"].on_tcp_connection_loss do |connection, settings|
94
+ # reconnect in 10 seconds, without enforcement
95
+ logger.error "Lost AMQP TCP connection to #{settings[:host]}:#{settings[:port]}#{settings[:vhost]}, reconnecting in 10s"
96
+ connection.reconnect(false, 10)
97
+ end
98
+ # Force reconnect on heartbeat loss to cope with our funny firewall issues
99
+ @@connection["#{@host}:#{@port}#{@vhost}"].on_skipped_heartbeats do |connection, settings|
100
+ logger.error "Skipped heartbeats detected, reconnecting in 10s"
101
+ connection.reconnect(false, 10)
102
+ end
103
+ # @@connection["#{@host}:#{@port}#{@vhost}"].on_connection_interruption do |connection|
104
+ # logger.error "Connection detected connection interruption"
105
+ # end
106
+ @@connection["#{@host}:#{@port}#{@vhost}"].on_error do |connection, connection_close|
107
+ logger.error "AMQP connection has been closed. Reply code = #{connection_close.reply_code}, reply text = #{connection_close.reply_text}"
108
+ if connection_close.reply_code == 320
109
+ logger.error "Set a 30s reconnection timer"
110
+ # every 30 seconds
111
+ connection.periodically_reconnect(30)
112
+ end
113
+ end
114
+ end
70
115
  end
71
116
  end
72
117
 
@@ -74,4 +119,4 @@ module Qswarm
74
119
  "amqp://#{@user}:#{@pass}@#{@host}:#{@port}/#{CGI.escape(@vhost)}"
75
120
  end
76
121
  end
77
- end
122
+ end
@@ -18,10 +18,13 @@ module Qswarm
18
18
  @speakers = []
19
19
  @sinks = []
20
20
  @format = :json
21
+ @instances = nil
21
22
 
22
23
  @queue_args = { :auto_delete => true, :durable => true, :exclusive => true }
23
24
  @subscribe_args = { :exclusive => false, :ack => false }
24
25
 
26
+ @speaker = args.delete :speaker unless args.nil?
27
+
25
28
  # @subscribe_args.merge! args.delete(:subscribe) unless args.nil?
26
29
  @queue_args.merge! args unless args.nil?
27
30
 
@@ -34,6 +37,7 @@ module Qswarm
34
37
  logger.info "Binding listener #{@name} < #{routing_key}"
35
38
  end
36
39
 
40
+ # Not sure about this
37
41
  def subscribe(*options)
38
42
  Array[*options].each { |o| @subscribe_args[o] = true }
39
43
  end
@@ -42,12 +46,22 @@ module Qswarm
42
46
  @subscribe_args[:ack]
43
47
  end
44
48
 
45
- def swarm(instances = 1)
49
+ def swarm(instances = nil)
50
+ @instances = instances
51
+ end
52
+
53
+ # Should this be subscribe options?
54
+ def uniq
46
55
  @uuid = '-' + UUID.generate
47
56
  end
48
57
 
49
- def speak(name = nil, &block)
50
- @speakers << Qswarm::Speaker.new(self, name, &block)
58
+ def speak(name, args = nil, &block)
59
+ if !args.nil?
60
+ require "qswarm/speakers/#{args[:type].downcase}"
61
+ @speakers << eval("Qswarm::Speakers::#{args[:type].capitalize}").new(self, name, &block)
62
+ else
63
+ @speakers << Qswarm::Speaker.new(self, name, &block)
64
+ end
51
65
  logger.info "Registering speaker: #{name} < #{@name}"
52
66
  end
53
67
 
@@ -57,9 +71,13 @@ module Qswarm
57
71
  end
58
72
 
59
73
  def run
74
+ # Any setup that needs to be done
75
+ @speakers.map { |s| s.run }
76
+
60
77
  @bind ||= @name
61
78
  logger.info "Listening on #{@name} < #{@bind}"
62
79
 
80
+ get_broker.channel(@name, @bind).prefetch(@instances) unless @instances.nil?
63
81
  get_broker.queue(@name + @uuid ||= '', @bind, @queue_args).subscribe(@subscribe_args) do |metadata, payload|
64
82
  logger.debug "[#{@agent.name}] Received '#{payload.inspect}' on listener #{@name}/#{metadata.routing_key}"
65
83
 
@@ -1,4 +1,5 @@
1
1
  require 'logger'
2
+ require 'gelf'
2
3
 
3
4
  module Qswarm
4
5
  module Loggable
@@ -7,7 +8,8 @@ module Qswarm
7
8
  end
8
9
 
9
10
  def self.logger
10
- @logger ||= Logger.new(STDOUT)
11
+ # @logger ||= Logger.new(STDOUT)
12
+ @logger ||= GELF::Logger.new($graylog2_host, 12201, 'WAN', { :facility => $graylog2_facility })
11
13
  end
12
14
  end
13
- end
15
+ end
@@ -27,7 +27,7 @@ module Qswarm
27
27
  @heard = payload
28
28
  end
29
29
 
30
- self.instance_exec(&@block)
30
+ self.instance_eval(&@block)
31
31
  rescue JSON::ParserError
32
32
  error = "JSON::ParserError on #{payload.inspect}"
33
33
  logger.error error
@@ -47,6 +47,9 @@ module Qswarm
47
47
  def get_broker(name)
48
48
  @listener.get_broker(name)
49
49
  end
50
+
51
+ def run
52
+ end
50
53
 
51
54
  private
52
55
 
@@ -0,0 +1,48 @@
1
+ require 'uri'
2
+ require 'em-http-request'
3
+
4
+ module Qswarm
5
+ module Speakers
6
+ class Http < Qswarm::Speaker
7
+ @@connections = {}
8
+
9
+ def initialize(listener, name, &block)
10
+ @uri = URI.parse(name)
11
+ @uri.host = 'localhost' if @uri.host.nil?
12
+ super
13
+ end
14
+
15
+ def inject(format = :text, msg)
16
+ publish format, msg
17
+ end
18
+
19
+ def run
20
+ connect
21
+ end
22
+
23
+ private
24
+
25
+ def connect
26
+ end
27
+
28
+ def publish(format, msg)
29
+ logger.debug "Sending '#{msg}' to #{@name}"
30
+
31
+ case format
32
+ when :get
33
+ if msg.is_a? Hash
34
+ http = EventMachine::HttpRequest.new(@uri).get :query => msg
35
+ else
36
+ http = EventMachine::HttpRequest.new(URI.join(@uri.to_s, msg)).get
37
+ end
38
+ http.errback do |err|
39
+ logger.error "Error sending #{msg} to #{@name}: #{err}"
40
+ end
41
+ http.callback do
42
+ logger.debug "#{http.response_header.status} #{http.response}"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,45 @@
1
+ require 'cinch'
2
+
3
+ module Qswarm
4
+ module Speakers
5
+ class Irc < Qswarm::Speaker
6
+ @@irc_servers = {}
7
+
8
+ def inject(format = :text, msg)
9
+ logger.debug "[#{@agent.name}] Sending '#{msg}' to channel #{@name}"
10
+ publish format, @name, msg
11
+ end
12
+
13
+ def run
14
+ @name.match(/([^#]*)(#.+)/) { irc_connect $1.empty? ? 'localhost' : $1, $2 }
15
+ end
16
+
17
+ private
18
+
19
+ def irc_connect(irc_server, channel)
20
+ if bot = @@irc_servers[irc_server]
21
+ logger.debug "Joining channel #{channel}"
22
+ @@irc_servers[irc_server].config.channels << channel
23
+ else
24
+ logger.debug "Connecting to IRC server #{irc_server} channel #{channel}"
25
+ @@irc_servers[irc_server] = Cinch::Bot.new
26
+ @@irc_servers[irc_server].config.server = irc_server
27
+ @@irc_servers[irc_server].config.nick = @agent.name
28
+ @@irc_servers[irc_server].config.channels << channel
29
+
30
+ EM.defer do
31
+ @@irc_servers[irc_server].start
32
+ end
33
+ end
34
+ end
35
+
36
+ def publish(format, name, msg)
37
+ @name.match(/([^#]*)(#.+)/) do
38
+ @@irc_servers[$1.empty? ? 'localhost' : $1].channel_manager.find_ensured($2).andand.send(
39
+ format == :json ? JSON.generate(msg) : msg
40
+ )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,39 @@
1
+ # require 'mysql2'
2
+
3
+ module Qswarm
4
+ module Speakers
5
+ class Mysql < Qswarm::Speaker
6
+ @@db_servers = {}
7
+
8
+ def inject(format = :text, msg)
9
+ logger.debug "[#{@agent.name}] Sending '#{msg}' to channel #{@name}"
10
+ publish format, @name, msg
11
+ end
12
+
13
+ def run
14
+ @name.match(/([^#]*)(#.+)/) { db_connect $1.empty? ? 'localhost' : $1, $2 }
15
+ end
16
+
17
+ private
18
+
19
+ def db_connect(db_server, database)
20
+ if connection = @@db_servers[db_server]
21
+ logger.debug "Connecting to database #{database}"
22
+ else
23
+ logger.debug "Connecting to DB server #{db_server} database #{database}"
24
+ @@db_servers[db_server] = 'foo'
25
+ end
26
+ end
27
+
28
+ def publish(format, name, msg)
29
+ @name.match(/([^#]*)(#.+)/) do
30
+ logger.debug "#{$1} #{$2} #{msg}"
31
+ # @@db_servers[$1.empty? ? 'localhost' : $1].channel_manager.find_ensured($2).andand.send(
32
+ # format == :json ? JSON.generate(msg) : msg
33
+ # )
34
+ # end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ module Qswarm
2
+ module Speakers
3
+ class Nc < Qswarm::Speaker
4
+ @@connections = {}
5
+
6
+ def inject(format = :text, msg)
7
+ publish format, msg
8
+ end
9
+
10
+ def run
11
+ @name.match(/([^#]*):(\d+)/) do
12
+ @host = $1.empty? ? 'localhost' : $1
13
+ @port = $2
14
+ @server = @host + ':' + @port
15
+ # @service = $3
16
+ end
17
+ connect
18
+ end
19
+
20
+ private
21
+
22
+ def connect
23
+ if connection = @@connections[@server]
24
+ logger.debug "Connecting to service #{@service}"
25
+ else
26
+ logger.debug "Connecting to host #{@server} service #{@service}"
27
+ @@connections[@server] = EventMachine::connect @host, @port do |connection|
28
+ def connection.receive_data(data)
29
+ puts "Received #{data} from #{@name}"
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ def publish(format, msg)
36
+ logger.debug "Sending '#{msg}' to #{@name}"
37
+ @@connections[@server].send_data( (format == :json ? JSON.generate(msg) : msg) + "\n")
38
+ end
39
+ end
40
+ end
41
+ end
data/lib/qswarm/swarm.rb CHANGED
@@ -24,9 +24,9 @@ module Qswarm
24
24
  logger
25
25
  end
26
26
 
27
- def agent(name, &block)
27
+ def agent(name, args = nil, &block)
28
28
  logger.info "Registering agent: #{name}"
29
- @agents << Qswarm::Agent.new(self, name, &block)
29
+ @agents << Qswarm::Agent.new(self, name, args, &block)
30
30
  end
31
31
 
32
32
  def broker(name, &block)
@@ -1,3 +1,3 @@
1
1
  module Qswarm
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.4"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qswarm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,12 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-01-29 00:00:00.000000000 +00:00
13
- default_executable:
12
+ date: 2012-03-14 00:00:00.000000000Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: eventmachine
17
- requirement: &2157460660 !ruby/object:Gem::Requirement
16
+ requirement: &70191652850140 !ruby/object:Gem::Requirement
18
17
  none: false
19
18
  requirements:
20
19
  - - ! '>='
@@ -22,10 +21,10 @@ dependencies:
22
21
  version: '0'
23
22
  type: :runtime
24
23
  prerelease: false
25
- version_requirements: *2157460660
24
+ version_requirements: *70191652850140
26
25
  - !ruby/object:Gem::Dependency
27
26
  name: amqp
28
- requirement: &2157460240 !ruby/object:Gem::Requirement
27
+ requirement: &70191652849720 !ruby/object:Gem::Requirement
29
28
  none: false
30
29
  requirements:
31
30
  - - ! '>='
@@ -33,10 +32,10 @@ dependencies:
33
32
  version: '0'
34
33
  type: :runtime
35
34
  prerelease: false
36
- version_requirements: *2157460240
35
+ version_requirements: *70191652849720
37
36
  - !ruby/object:Gem::Dependency
38
37
  name: uuid
39
- requirement: &2157459820 !ruby/object:Gem::Requirement
38
+ requirement: &70191652849300 !ruby/object:Gem::Requirement
40
39
  none: false
41
40
  requirements:
42
41
  - - ! '>='
@@ -44,10 +43,10 @@ dependencies:
44
43
  version: '0'
45
44
  type: :runtime
46
45
  prerelease: false
47
- version_requirements: *2157459820
46
+ version_requirements: *70191652849300
48
47
  - !ruby/object:Gem::Dependency
49
48
  name: json
50
- requirement: &2157459400 !ruby/object:Gem::Requirement
49
+ requirement: &70191652848880 !ruby/object:Gem::Requirement
51
50
  none: false
52
51
  requirements:
53
52
  - - ! '>='
@@ -55,10 +54,10 @@ dependencies:
55
54
  version: '0'
56
55
  type: :runtime
57
56
  prerelease: false
58
- version_requirements: *2157459400
57
+ version_requirements: *70191652848880
59
58
  - !ruby/object:Gem::Dependency
60
59
  name: andand
61
- requirement: &2157458980 !ruby/object:Gem::Requirement
60
+ requirement: &70191652848460 !ruby/object:Gem::Requirement
62
61
  none: false
63
62
  requirements:
64
63
  - - ! '>='
@@ -66,7 +65,7 @@ dependencies:
66
65
  version: '0'
67
66
  type: :runtime
68
67
  prerelease: false
69
- version_requirements: *2157458980
68
+ version_requirements: *70191652848460
70
69
  description: Framework for writing distributed agents hanging off an AMQP message
71
70
  bus
72
71
  email:
@@ -88,10 +87,13 @@ files:
88
87
  - lib/qswarm/listener.rb
89
88
  - lib/qswarm/loggable.rb
90
89
  - lib/qswarm/speaker.rb
90
+ - lib/qswarm/speakers/http.rb
91
+ - lib/qswarm/speakers/irc.rb
92
+ - lib/qswarm/speakers/mysql.rb
93
+ - lib/qswarm/speakers/nc.rb
91
94
  - lib/qswarm/swarm.rb
92
95
  - lib/qswarm/version.rb
93
96
  - qswarm.gemspec
94
- has_rdoc: true
95
97
  homepage: http://github.com/ennui2342/qswarm
96
98
  licenses: []
97
99
  post_install_message:
@@ -112,7 +114,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
112
114
  version: '0'
113
115
  requirements: []
114
116
  rubyforge_project: qswarm
115
- rubygems_version: 1.6.2
117
+ rubygems_version: 1.8.15
116
118
  signing_key:
117
119
  specification_version: 3
118
120
  summary: Distributed queue based agents in Ruby