qswarm 0.0.2 → 0.0.4

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