cloudist 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "amqp"
4
+ gem "json"
5
+ gem "activesupport"
6
+
7
+ # Add dependencies to develop your gem here.
8
+ # Include everything needed to run rake, tests, features, etc.
9
+ group :development do
10
+ gem "rspec", "~> 2.3.0"
11
+ gem "moqueue", :git => "git://github.com/customink/moqueue.git"
12
+ gem "mocha"
13
+ gem "bundler", "~> 1.0.0"
14
+ gem "jeweler", "~> 1.5.2"
15
+ gem "rcov", ">= 0"
16
+ gem "reek", "~> 1.2.8"
17
+ gem "roodi", "~> 2.1.0"
18
+ end
@@ -0,0 +1,61 @@
1
+ GIT
2
+ remote: git://github.com/customink/moqueue.git
3
+ revision: 091a8f57e5c79b0b25e152b4d5230e4031797d62
4
+ specs:
5
+ moqueue (0.1.4)
6
+ amqp
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ activesupport (3.0.3)
12
+ amqp (0.6.7)
13
+ eventmachine (>= 0.12.4)
14
+ diff-lcs (1.1.2)
15
+ eventmachine (0.12.10)
16
+ git (1.2.5)
17
+ jeweler (1.5.2)
18
+ bundler (~> 1.0.0)
19
+ git (>= 1.2.5)
20
+ rake
21
+ json (1.4.6)
22
+ mocha (0.9.10)
23
+ rake
24
+ rake (0.8.7)
25
+ rcov (0.9.9)
26
+ reek (1.2.8)
27
+ ruby2ruby (~> 1.2)
28
+ ruby_parser (~> 2.0)
29
+ sexp_processor (~> 3.0)
30
+ roodi (2.1.0)
31
+ ruby_parser
32
+ rspec (2.3.0)
33
+ rspec-core (~> 2.3.0)
34
+ rspec-expectations (~> 2.3.0)
35
+ rspec-mocks (~> 2.3.0)
36
+ rspec-core (2.3.1)
37
+ rspec-expectations (2.3.0)
38
+ diff-lcs (~> 1.1.2)
39
+ rspec-mocks (2.3.0)
40
+ ruby2ruby (1.2.5)
41
+ ruby_parser (~> 2.0)
42
+ sexp_processor (~> 3.0)
43
+ ruby_parser (2.0.5)
44
+ sexp_processor (~> 3.0)
45
+ sexp_processor (3.0.5)
46
+
47
+ PLATFORMS
48
+ ruby
49
+
50
+ DEPENDENCIES
51
+ activesupport
52
+ amqp
53
+ bundler (~> 1.0.0)
54
+ jeweler (~> 1.5.2)
55
+ json
56
+ mocha
57
+ moqueue!
58
+ rcov
59
+ reek (~> 1.2.8)
60
+ roodi (~> 2.1.0)
61
+ rspec (~> 2.3.0)
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Ivan Vanderbyl
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,113 @@
1
+ Cloudist
2
+ ========
3
+
4
+ Cloudist is a super fast job queue for high demand and scalable tasks. It uses AMQP (RabbitMQ mainly) for message store are
5
+ distribution, while providing a simple DSL for handling jobs and responses.
6
+
7
+ Cloudist can be used within Rails or just about any Ruby app to distribute long running tasks, such as encoding a video, generating PDFs, scraping site data
8
+ or even just sending emails. Unlike other job queues (DelayedJob etc) Cloudist does not load your entire Rails stack into memory for every worker, and it is not designed to, instead it
9
+ expects all the data your worker requires to be sent in the initial job request. This means your workers stay slim and can scale very quickly and even run on EC2 micros outside your applications
10
+ network without any further configuration.
11
+
12
+ Another way Cloudist differs from other AMQP based job queues like Minion is it allows workers to report events, logs, system stats and replies back to the application which distributed the
13
+ job, and unlike database based job queues, there is almost no delay between messages, except network latency of course.
14
+
15
+ Installation
16
+ ------------
17
+
18
+ gem install cloudist
19
+
20
+ Or if your app has a Gemfile:
21
+
22
+ gem 'cloudist'
23
+
24
+ Usage
25
+ -----
26
+
27
+ Cloudist requires an EventMachine reactor loop and an AMQP connection, so if your application is already using one, or your web server supplies one (for example Thin) these examples will work
28
+ out of the box. Otherwise simply wrap everything inside this block:
29
+
30
+ Cloudist.settings = {:user => 'guest'} # Standard AMQP settings
31
+ Cloudist.start {
32
+ # usual stuff here
33
+ worker {
34
+ # define a worker
35
+ }
36
+ }
37
+
38
+ This will start and AMQP connection and EM loop then yield everything inside it.
39
+
40
+ In your worker:
41
+
42
+ Cloudist.worker {
43
+ job('make.sandwich') {
44
+ # Make sandwich here
45
+
46
+ # Your worker has access to the data sent from the server in the 'data' attribute
47
+ data # => {:bread => "white", :sauce => 'bbq'}
48
+
49
+ # Fire the finished event
50
+ finished!
51
+ }
52
+ }
53
+
54
+ In your application:
55
+
56
+ job = Cloudist.enqueue('make.sandwich', :bread => "white", :sauce => 'bbq')
57
+
58
+ Cloudist.listen(job) {
59
+ event('finished') {
60
+ # Called when we finish making a sandwich
61
+ }
62
+ }
63
+
64
+ You don't need to listen to responses immediately, if you store the job_id you can listen to responses at any time in the near future.
65
+
66
+ You can also queue jobs outside an EventMachine loop using Cloudist.enqueue but this will be very slow as it has to connect to your message queue first.
67
+
68
+ Acknowledgements
69
+ -------
70
+
71
+ Portions of this gem are based on code from the following projects:
72
+
73
+ - Heroku's Droid gem
74
+ - Lizzy
75
+ - Minion
76
+ - Nanite
77
+ - Smith
78
+
79
+ Contributing to Cloudist
80
+ ------------------------
81
+
82
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
83
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
84
+ * Fork the project
85
+ * Start a feature/bugfix branch e.g. git checkout -b feature-my-awesome-idea or bugfix-this-does-not-work
86
+ * Commit and push until you are happy with your contribution
87
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
88
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
89
+
90
+ Copyright
91
+ ---------
92
+
93
+ Copyright (c) 2011 Ivan Vanderbyl.
94
+
95
+ Permission is hereby granted, free of charge, to any person obtaining
96
+ a copy of this software and associated documentation files (the
97
+ "Software"), to deal in the Software without restriction, including
98
+ without limitation the rights to use, copy, modify, merge, publish,
99
+ distribute, sublicense, and/or sell copies of the Software, and to
100
+ permit persons to whom the Software is furnished to do so, subject to
101
+ the following conditions:
102
+
103
+ The above copyright notice and this permission notice shall be
104
+ included in all copies or substantial portions of the Software.
105
+
106
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
107
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
108
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
109
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
110
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
111
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
112
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
113
+
@@ -0,0 +1,63 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "cloudist"
16
+ gem.homepage = "http://github.com/ivanvanderbyl/cloudist"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Super fast job queue using AMQP}
19
+ gem.description = %Q{Cloudist is a simple, highly scalable job queue for Ruby applications, it can run within Rails, or on EC2, and does not load your entire Rails stack like delayed job does.}
20
+ gem.email = "ivanvanderbyl@me.com"
21
+ gem.authors = ["Ivan Vanderbyl"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rspec/core'
30
+ require 'rspec/core/rake_task'
31
+ RSpec::Core::RakeTask.new(:spec) do |spec|
32
+ spec.pattern = FileList['spec/**/*_spec.rb']
33
+ end
34
+
35
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.rcov = true
38
+ end
39
+
40
+ require 'reek/rake/task'
41
+ Reek::Rake::Task.new do |t|
42
+ t.fail_on_error = true
43
+ t.verbose = false
44
+ t.source_files = 'lib/**/*.rb'
45
+ end
46
+
47
+ require 'roodi'
48
+ require 'roodi_task'
49
+ RoodiTask.new do |t|
50
+ t.verbose = false
51
+ end
52
+
53
+ task :default => :spec
54
+
55
+ require 'rake/rdoctask'
56
+ Rake::RDocTask.new do |rdoc|
57
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
58
+
59
+ rdoc.rdoc_dir = 'rdoc'
60
+ rdoc.title = "cloudist #{version}"
61
+ rdoc.rdoc_files.include('README*')
62
+ rdoc.rdoc_files.include('lib/**/*.rb')
63
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.2
@@ -0,0 +1,19 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+ require "rubygems"
3
+ require "cloudist"
4
+
5
+ ENV["AMQP_URL"] = 'amqp://test_pilot:t35t_p1l0t!@ec2-50-16-134-211.compute-1.amazonaws.com:5672/'
6
+
7
+ ::Signal.trap('INT') { Cloudist.stop }
8
+ ::Signal.trap('TERM'){ Cloudist.stop }
9
+
10
+ Cloudist.start {
11
+
12
+ payload = Cloudist::Payload.new({:event => "started"})
13
+
14
+ q = Cloudist::ReplyQueue.new('temp.reply.make.sandwich')
15
+ q.setup
16
+ q.publish_to_q(payload)
17
+
18
+ stop
19
+ }
@@ -0,0 +1,17 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+ require "rubygems"
3
+ require "cloudist"
4
+
5
+ Cloudist.signal_trap!
6
+
7
+ Cloudist.start {
8
+
9
+ log.info("Dispatching sandwich making job...")
10
+ enqueue('make.sandwich', {:bread => 'white'})
11
+
12
+ # Listen to all sandwich jobs
13
+ listen('make.sandwich') {
14
+ Cloudist.log.info("Make sandwich event: #{id}")
15
+ }
16
+
17
+ }
@@ -0,0 +1,21 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+ require "rubygems"
3
+ require "cloudist"
4
+
5
+ Cloudist.signal_trap!
6
+
7
+ Cloudist.start {
8
+ log.info("Started Worker")
9
+
10
+ worker {
11
+ job('make.sandwich') {
12
+ # Fire the started event
13
+ started!
14
+
15
+ log.info("JOB (#{id}) Make sandwich with #{data[:bread]} bread")
16
+ log.debug(data.inspect)
17
+
18
+ finished!
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,141 @@
1
+ require 'uri'
2
+ require 'json'
3
+ require "active_support/hash_with_indifferent_access"
4
+ require "amqp"
5
+ require "mq"
6
+ require "logger"
7
+ require "digest/md5"
8
+
9
+ $:.unshift File.dirname(__FILE__)
10
+ require "cloudist/core_ext/string"
11
+ require "cloudist/errors"
12
+ require "cloudist/utils"
13
+ require "cloudist/basic_queue"
14
+ require "cloudist/job_queue"
15
+ require "cloudist/reply_queue"
16
+ require "cloudist/publisher"
17
+ require "cloudist/payload"
18
+ require "cloudist/request"
19
+ require "cloudist/worker"
20
+ require "cloudist/listener"
21
+ require "cloudist/job"
22
+
23
+ module Cloudist
24
+ class << self
25
+ # Start the Cloudist loop
26
+ #
27
+ # Cloudist.start {
28
+ # # Do stuff in here
29
+ # }
30
+ #
31
+ # == Options
32
+ # * :user => 'name'
33
+ # * :pass => 'secret'
34
+ # * :host => 'localhost'
35
+ # * :port => 5672
36
+ # * :vhost => /
37
+ #
38
+ # Refer to default config below for how to set these as defaults
39
+ #
40
+ def start(options = {}, &block)
41
+ config = settings.update(options)
42
+ AMQP.start(config) do
43
+ self.instance_eval(&block)
44
+ end
45
+ end
46
+
47
+ # Define a worker. Must be called inside start loop
48
+ #
49
+ # worker {
50
+ # job('make.sandwich') {}
51
+ # }
52
+ #
53
+ # Refer to examples.
54
+ def worker(options = {}, &block)
55
+ _worker = Cloudist::Worker.new(options)
56
+ _worker.instance_eval(&block)
57
+ return _worker
58
+ end
59
+
60
+ # Accepts either a queue name or a job instance returned from enqueue.
61
+ # This method operates in two modes, when given a queue name, it
62
+ # will return all responses regardless of job id so you can use the job
63
+ # id to lookup a database record to update etc.
64
+ # When given a job instance it will only return messages from that job.
65
+ def listen(job_or_queue_name, &block)
66
+ _listener = Cloudist::Listener.new(job_or_queue_name)
67
+ _listener.subscribe(&block)
68
+ return _listener
69
+ end
70
+
71
+ # Enqueues a job.
72
+ # Takes a queue name and data hash to be sent to the worker.
73
+ # Returns Job instance
74
+ # Use Job#id to reference job later on.
75
+ def enqueue(job_queue_name, data = nil)
76
+ raise EnqueueError, "Incorrect arguments, you must include data when enquing job" if data.nil?
77
+ # TODO: Detect if inside loop, if not use bunny sync
78
+ Cloudist::Publisher.enqueue(job_queue_name, data)
79
+ end
80
+
81
+ # Call this at anytime inside the loop to exit the app.
82
+ def stop_safely
83
+ ::EM.add_timer(0.2) {
84
+ ::AMQP.stop {
85
+ ::EM.stop
86
+ }
87
+ }
88
+ end
89
+
90
+ alias :stop :stop_safely
91
+
92
+ def closing?
93
+ ::AMQP.closing?
94
+ end
95
+
96
+ def log
97
+ @@log ||= Logger.new($stdout)
98
+ end
99
+
100
+ def log=(log)
101
+ @@log = log
102
+ end
103
+
104
+ def handle_error(e)
105
+ log.error "#{e.class}: #{e.message}"#, :exception => e
106
+ log.error e.backtrace.join("\n")
107
+ end
108
+
109
+ def version
110
+ @@version ||= File.read(File.dirname(__FILE__) + '/../VERSION').strip
111
+ end
112
+
113
+ def default_settings
114
+ uri = URI.parse(ENV["AMQP_URL"] || 'amqp://guest:guest@localhost:5672/')
115
+ {
116
+ :vhost => uri.path,
117
+ :host => uri.host,
118
+ :user => uri.user,
119
+ :port => uri.port || 5672,
120
+ :pass => uri.password
121
+ }
122
+ rescue Object => e
123
+ raise "invalid AMQP_URL: (#{uri.inspect}) #{e.class} -> #{e.message}"
124
+ end
125
+
126
+ def settings
127
+ @@settings ||= default_settings
128
+ end
129
+
130
+ def settings=(settings_hash)
131
+ @@settings = default_settings.update(settings_hash)
132
+ end
133
+
134
+ def signal_trap!
135
+ ::Signal.trap('INT') { Cloudist.stop }
136
+ ::Signal.trap('TERM'){ Cloudist.stop }
137
+ end
138
+
139
+ end
140
+
141
+ end