qswarm 0.0.21 → 1.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.
@@ -1,17 +1,61 @@
1
1
  module Qswarm
2
2
  module DSL
3
- def dsl_accessor(*symbols)
4
- symbols.each { |sym|
5
- class_eval %{
6
- def #{sym}(*val)
7
- if val.empty?
8
- @#{sym}
9
- else
10
- @#{sym} = val.size == 1 ? val[0] : val
3
+ module Config
4
+ @@caller = nil
5
+
6
+ def self.caller
7
+ @@caller
8
+ end
9
+
10
+ def self.caller=(caller)
11
+ @@caller = caller
12
+ end
13
+
14
+ class Swarm; extend Qswarm::DSL::Config; end
15
+ end
16
+
17
+ def self.included(base)
18
+ base.extend(ClassMethods)
19
+ end
20
+
21
+ def dsl_load(config)
22
+ dsl_call(File.read(config))
23
+ end
24
+
25
+ def dsl_call(string = nil, &block)
26
+ parent = Config.caller
27
+ Config.caller = self
28
+ if string.nil?
29
+ res = Config::Swarm.module_eval(&block)
30
+ else
31
+ res = Config::Swarm.module_eval(string)
32
+ end
33
+ Config.caller = parent
34
+ res
35
+ end
36
+
37
+ module ClassMethods
38
+ def dsl_accessor(*symbols)
39
+ symbols.each do |sym|
40
+ Qswarm::DSL::Config.module_eval %{
41
+ def #{sym}
42
+ @@#{sym}
43
+ end
44
+
45
+ def #{sym}=(*val)
46
+ @@#{sym} = val.size == 1 ? val[0] : val
11
47
  end
12
- end
13
- }
14
- }
48
+ }
49
+ end
50
+ end
51
+
52
+ def dsl(*symbols)
53
+ symbols.each do |sym|
54
+ Qswarm::DSL::Config.module_eval "def #{sym}(*args, &block) @@caller.send(#{sym.inspect}, *args, &block); end"
55
+ #Qswarm::DSL::Config.module_eval "def #{sym}(name, args = nil, &block) @@caller.send(#{sym.inspect}, name, args, &block); end"
56
+ #Qswarm::DSL::Config.module_eval { define_method(sym, -> (name, args = nil, &block) { @@caller.send(sym.inspect, name, args, &block) } ) }
57
+ end
58
+ end
15
59
  end
16
60
  end
17
- end
61
+ end
@@ -1,43 +1,24 @@
1
1
  require 'andand'
2
2
  require 'eventmachine'
3
3
 
4
- require 'qswarm/agent'
5
- require 'qswarm/broker'
6
-
7
4
  module Qswarm
8
5
  class Swarm
9
- include Qswarm::Loggable
6
+ include Qswarm::DSL
10
7
 
11
- def self.load(config)
12
- dsl = new
13
- dsl.instance_eval(File.read(config), config)
14
- dsl
15
- end
8
+ dsl :agent
16
9
 
17
- def initialize
10
+ def initialize(config)
18
11
  @agents = []
19
12
  $fqdn = Socket.gethostbyname(Socket.gethostname).first
20
- @brokers = {}
21
- end
22
13
 
23
- def log
24
- logger
14
+ dsl_load(config)
25
15
  end
26
16
 
27
17
  def agent(name, args = nil, &block)
28
- logger.info "Registering agent: #{name}"
18
+ Qswarm.logger.info "Registering agent: #{name.inspect}"
29
19
  @agents << Qswarm::Agent.new(self, name, args, &block)
30
20
  end
31
21
 
32
- def broker(name, &block)
33
- logger.info "Registering broker: #{name}"
34
- @brokers[name] = Qswarm::Broker.new(name, &block)
35
- end
36
-
37
- def get_broker(name)
38
- @brokers[name]
39
- end
40
-
41
22
  def run
42
23
  EventMachine.run do
43
24
  @agents.map { |a| a.run }
@@ -1,3 +1,3 @@
1
1
  module Qswarm
2
- VERSION = "0.0.21"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
8
8
  s.authors = ["Mark Cheverton"]
9
9
  s.email = ["mark.cheverton@ecafe.org"]
10
10
  s.homepage = "http://github.com/ennui2342/qswarm"
11
- s.summary = %q{Distributed queue based agents in Ruby}
12
- s.description = %q{Framework for writing distributed agents hanging off an AMQP message bus}
11
+ s.summary = %q{Streaming event processing DSL for Ruby}
12
+ s.description = %q{Defines a DSL to allow stream processing from various sources for output to various sinks}
13
13
 
14
14
  s.rubyforge_project = "qswarm"
15
15
 
@@ -27,4 +27,8 @@ Gem::Specification.new do |s|
27
27
  s.add_dependency 'uuid'
28
28
  s.add_dependency 'json'
29
29
  s.add_dependency 'andand'
30
+ s.add_dependency 'nokogiri'
31
+ s.add_dependency 'tweetstream'
32
+ s.add_dependency 'twitter'
33
+ s.add_dependency 'trollop'
30
34
  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.21
4
+ version: 1.0.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-21 00:00:00.000000000Z
12
+ date: 2013-12-17 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: eventmachine
16
- requirement: &70093220631160 !ruby/object:Gem::Requirement
16
+ requirement: &70217708776120 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70093220631160
24
+ version_requirements: *70217708776120
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: amqp
27
- requirement: &70093220630740 !ruby/object:Gem::Requirement
27
+ requirement: &70217708775700 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70093220630740
35
+ version_requirements: *70217708775700
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: uuid
38
- requirement: &70093220630320 !ruby/object:Gem::Requirement
38
+ requirement: &70217708775280 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :runtime
45
45
  prerelease: false
46
- version_requirements: *70093220630320
46
+ version_requirements: *70217708775280
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: json
49
- requirement: &70093220629900 !ruby/object:Gem::Requirement
49
+ requirement: &70217708774860 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :runtime
56
56
  prerelease: false
57
- version_requirements: *70093220629900
57
+ version_requirements: *70217708774860
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: andand
60
- requirement: &70093220629480 !ruby/object:Gem::Requirement
60
+ requirement: &70217708774440 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ! '>='
@@ -65,9 +65,53 @@ dependencies:
65
65
  version: '0'
66
66
  type: :runtime
67
67
  prerelease: false
68
- version_requirements: *70093220629480
69
- description: Framework for writing distributed agents hanging off an AMQP message
70
- bus
68
+ version_requirements: *70217708774440
69
+ - !ruby/object:Gem::Dependency
70
+ name: nokogiri
71
+ requirement: &70217708774020 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *70217708774020
80
+ - !ruby/object:Gem::Dependency
81
+ name: tweetstream
82
+ requirement: &70217708773600 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :runtime
89
+ prerelease: false
90
+ version_requirements: *70217708773600
91
+ - !ruby/object:Gem::Dependency
92
+ name: twitter
93
+ requirement: &70217708773180 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :runtime
100
+ prerelease: false
101
+ version_requirements: *70217708773180
102
+ - !ruby/object:Gem::Dependency
103
+ name: trollop
104
+ requirement: &70217708772760 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :runtime
111
+ prerelease: false
112
+ version_requirements: *70217708772760
113
+ description: Defines a DSL to allow stream processing from various sources for output
114
+ to various sinks
71
115
  email:
72
116
  - mark.cheverton@ecafe.org
73
117
  executables:
@@ -82,15 +126,12 @@ files:
82
126
  - bin/qswarm
83
127
  - lib/qswarm.rb
84
128
  - lib/qswarm/agent.rb
85
- - lib/qswarm/broker.rb
129
+ - lib/qswarm/connection.rb
130
+ - lib/qswarm/connections/amqp.rb
131
+ - lib/qswarm/connections/logger.rb
132
+ - lib/qswarm/connections/twitter.rb
133
+ - lib/qswarm/connections/xmpp.rb
86
134
  - lib/qswarm/dsl.rb
87
- - lib/qswarm/listener.rb
88
- - lib/qswarm/loggable.rb
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
94
135
  - lib/qswarm/swarm.rb
95
136
  - lib/qswarm/version.rb
96
137
  - qswarm.gemspec
@@ -117,5 +158,5 @@ rubyforge_project: qswarm
117
158
  rubygems_version: 1.8.15
118
159
  signing_key:
119
160
  specification_version: 3
120
- summary: Distributed queue based agents in Ruby
161
+ summary: Streaming event processing DSL for Ruby
121
162
  test_files: []
@@ -1,118 +0,0 @@
1
- require 'eventmachine'
2
- require 'amqp'
3
- require 'cgi'
4
-
5
- require 'qswarm/dsl'
6
-
7
- module Qswarm
8
- class Broker
9
- include Qswarm::Loggable
10
- extend Qswarm::DSL
11
-
12
- dsl_accessor :name, :host, :port, :user, :pass, :vhost, :exchange_type, :exchange_name, :durable
13
- @@connection = {}
14
-
15
- def initialize(name, &block)
16
- @name = name
17
-
18
- # Set some defaults
19
- @host = 'localhost'
20
- @port = 5672
21
- @user = 'guest'
22
- @pass = 'guest'
23
- @vhost = ''
24
- @exchange_type = :direct
25
- @exchange_name = ''
26
- @durable = true
27
- @prefetch = nil
28
-
29
- # if block.arity == 1
30
- # yield self
31
- # else
32
- # instance_eval &block
33
- # end
34
-
35
- self.instance_eval(&block)
36
-
37
- @queues = {}
38
- @channels = {}
39
- @exchange = nil
40
-
41
- Signal.trap("INT") do
42
- @@connection["#{@host}:#{@port}#{@vhost}"].close do
43
- EM.stop { exit }
44
- end
45
- end
46
- end
47
-
48
- def queue name, routing_key = '', args = nil
49
- @queues["#{name}/#{routing_key}"] ||= begin
50
- logger.debug "Binding queue #{name}/#{routing_key}"
51
- @queues["#{name}/#{routing_key}"] = channel(name, routing_key).queue(name, args).bind(exchange(channel(name, routing_key)), :routing_key => routing_key)
52
- end
53
- end
54
-
55
- def exchange(channel = nil)
56
- @exchange ||= begin
57
- @exchange = AMQP::Exchange.new(channel ||= AMQP::Channel.new(connection, :auto_recovery => true), @exchange_type, @exchange_name, :durable => @durable) do |exchange|
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
62
- end
63
- end
64
- end
65
-
66
- # ruby-amqp currently limits to 1 consumer per queue (to be fixed in future) so can't pool channels
67
- def channel name, routing_key = ''
68
- @channels["#{name}/#{routing_key}"] ||= begin
69
- logger.debug "Opening channel for #{name}/#{routing_key}"
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
75
- end
76
- end
77
-
78
- def connection
79
- # Pool connections at the class level
80
- @@connection["#{@host}:#{@port}#{@vhost}"] ||= begin
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_connection_interruption do |connection|
94
- # reconnect in 10 seconds
95
- logger.error "AMQP connection interruption, 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"
101
- end
102
- @@connection["#{@host}:#{@port}#{@vhost}"].on_error do |connection, connection_close|
103
- logger.error "AMQP connection has been closed. Reply code = #{connection_close.reply_code}, reply text = #{connection_close.reply_text}"
104
- if connection_close.reply_code == 320
105
- logger.error "Set a 30s reconnection timer"
106
- # every 30 seconds
107
- connection.periodically_reconnect(30)
108
- end
109
- end
110
- end
111
- end
112
- end
113
-
114
- def to_s
115
- "amqp://#{@user}:#{@pass}@#{@host}:#{@port}/#{CGI.escape(@vhost)}"
116
- end
117
- end
118
- end
@@ -1,99 +0,0 @@
1
- require 'uuid'
2
- require 'eventmachine'
3
-
4
- require 'qswarm/dsl'
5
- require 'qswarm/speaker'
6
-
7
- module Qswarm
8
- class Listener
9
- include Qswarm::Loggable
10
- extend Qswarm::DSL
11
-
12
- dsl_accessor :name, :broker, :format
13
- attr_reader :agent
14
-
15
- def initialize(agent, name, args, &block)
16
- @agent = agent
17
- @name = name.to_s
18
- @speakers = []
19
- @sinks = []
20
- @format = :json
21
- @instances = nil
22
-
23
- @queue_args = { :auto_delete => true, :durable => true, :exclusive => true }
24
- @subscribe_args = { :exclusive => false, :ack => false }
25
-
26
- @speaker = args.delete :speaker unless args.nil?
27
-
28
- # @subscribe_args.merge! args.delete(:subscribe) unless args.nil?
29
- @queue_args.merge! args unless args.nil?
30
-
31
- self.instance_eval(&block)
32
- end
33
-
34
- def bind(routing_key, options = nil)
35
- @bind = routing_key
36
- @queue_args.merge! options unless options.nil?
37
- logger.info "Binding listener #{@name} < #{routing_key}"
38
- end
39
-
40
- # Not sure about this
41
- def subscribe(*options)
42
- Array[*options].each { |o| @subscribe_args[o] = true }
43
- end
44
-
45
- def ack?
46
- @subscribe_args[:ack]
47
- end
48
-
49
- def swarm(instances = nil)
50
- @instances = instances
51
- end
52
-
53
- # Should this be subscribe options?
54
- def uniq
55
- @uuid = '-' + UUID.generate
56
- end
57
-
58
- def speak(name, args = nil, &block)
59
- if !args.nil? && !args[:type].nil?
60
- require "qswarm/speakers/#{args[:type].downcase}"
61
- @speakers << eval("Qswarm::Speakers::#{args[:type].capitalize}").new(self, name, args, &block)
62
- else
63
- @speakers << Qswarm::Speaker.new(self, name, args, &block)
64
- end
65
- logger.info "Registering speaker: #{name} < #{@name}"
66
- end
67
-
68
- def get_broker(name = nil)
69
- name ||= @broker
70
- @agent.get_broker(name)
71
- end
72
-
73
- def run
74
- # Any setup that needs to be done
75
- @speakers.map { |s| s.run }
76
-
77
- @bind ||= @name
78
- logger.info "Listening on #{@name} < #{@bind}"
79
-
80
- get_broker.channel(@name, @bind).prefetch(@instances) unless @instances.nil?
81
- get_broker.queue(@name + @uuid ||= '', @bind, @queue_args).subscribe(@subscribe_args) do |metadata, payload|
82
- logger.debug "[#{@agent.name}] Received '#{payload.inspect}' on listener #{@name}/#{metadata.routing_key}"
83
-
84
- running = @speakers.map { |s| s.object_id }
85
- callback = proc do |speaker|
86
- running.delete speaker.object_id
87
- metadata.ack if ack? && running.empty?
88
- end
89
-
90
- @speakers.map do |speaker|
91
- EM.defer nil, callback do
92
- speaker.parse(metadata, payload)
93
- speaker
94
- end
95
- end
96
- end
97
- end
98
- end
99
- end