cloudq 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/bin/cloudq +24 -0
- data/lib/cloudq/app.rb +46 -0
- data/lib/cloudq/job.rb +47 -0
- data/lib/cloudq/version.rb +3 -0
- data/lib/cloudq.rb +15 -0
- data/lib/rack/params.rb +49 -0
- data/readme.md +171 -0
- metadata +173 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Tom Wilson
|
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.
|
data/bin/cloudq
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), '..','lib', 'cloudq')
|
4
|
+
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
if ARGV.first
|
8
|
+
project = ARGV.first
|
9
|
+
FileUtils.mkdir_p project
|
10
|
+
|
11
|
+
config_ru = ["require 'cloudq'", "run Cloudq::App"].join("\n")
|
12
|
+
File.open(File.join(project, 'config.ru'),'w').write(config_ru)
|
13
|
+
gemfile = ['source :rubygems', "gem 'thin'", "gem 'cloudq'"].join("\n")
|
14
|
+
File.open(File.join(project, 'Gemfile'),'w').write(gemfile)
|
15
|
+
puts "****** Sucessfully Created #{project} *******"
|
16
|
+
else
|
17
|
+
puts "Cloudq Remote Job Queue Framework..."
|
18
|
+
puts "(Please supply a project name for your cloudq server...)"
|
19
|
+
end
|
20
|
+
# puts "Running Cloudq on port:9292"
|
21
|
+
# puts "Press Ctrl-C to exit"
|
22
|
+
# # try to run in current directory
|
23
|
+
# gem 'rack', '>=0'
|
24
|
+
# load Gem.bin_path('rack', 'rackup', version)
|
data/lib/cloudq/app.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Cloudq
|
2
|
+
class App < Sinatra::Base
|
3
|
+
register Sinatra::Async
|
4
|
+
|
5
|
+
use Rack::Params
|
6
|
+
|
7
|
+
def self.version
|
8
|
+
"0.0.6"
|
9
|
+
end
|
10
|
+
|
11
|
+
aget '/' do
|
12
|
+
body "Welcome to Cloudq Framework"
|
13
|
+
end
|
14
|
+
|
15
|
+
# Post Job to the Queue
|
16
|
+
apost "/:queue" do |q|
|
17
|
+
halt 500 if params["job"].nil?
|
18
|
+
Job.create(params["job"].merge(:queue => q))
|
19
|
+
status = { :status => "success" }.to_json
|
20
|
+
body status
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
# Get Job from the Queue
|
25
|
+
aget "/:queue" do |q|
|
26
|
+
job = Job.where(:queue => q.to_sym, :workflow_state => :queued).first
|
27
|
+
if job
|
28
|
+
job.reserve!
|
29
|
+
body job.to_json
|
30
|
+
else
|
31
|
+
status = { :status => :empty }.to_json
|
32
|
+
body status
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Remove Job from the Queue
|
37
|
+
adelete "/:queue/:id" do |q, id|
|
38
|
+
job = Job.where(:queue => q.to_sym, :id => id).first
|
39
|
+
halt 404 unless job
|
40
|
+
job.delete!
|
41
|
+
status = { :status => "success" }.to_json
|
42
|
+
body status
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
data/lib/cloudq/job.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
if ENV['MONGOHQ_URL']
|
2
|
+
MongoMapper.config = {:production => {'uri' => ENV['MONGOHQ_URL']}}
|
3
|
+
MongoMapper.connect(:production)
|
4
|
+
else
|
5
|
+
MongoMapper.database = 'cloudq'
|
6
|
+
end
|
7
|
+
|
8
|
+
module Cloudq
|
9
|
+
class Job
|
10
|
+
include MongoMapper::Document
|
11
|
+
before_validation :write_initial_state, :on => :create
|
12
|
+
|
13
|
+
key :queue, String
|
14
|
+
key :klass, String
|
15
|
+
key :args, Hash
|
16
|
+
key :workflow_state, String
|
17
|
+
|
18
|
+
timestamps!
|
19
|
+
|
20
|
+
include Workflow
|
21
|
+
|
22
|
+
workflow do
|
23
|
+
state :queued do
|
24
|
+
event :reserve, :transitions_to => :reserved
|
25
|
+
end
|
26
|
+
state :reserved do
|
27
|
+
event :delete, :transitions_to => :deleted
|
28
|
+
end
|
29
|
+
state :deleted
|
30
|
+
end
|
31
|
+
|
32
|
+
def load_workflow_state
|
33
|
+
self[:workflow_state]
|
34
|
+
end
|
35
|
+
|
36
|
+
def persist_workflow_state(new_value)
|
37
|
+
self[:workflow_state] = new_value
|
38
|
+
save!
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def write_initial_state
|
43
|
+
update_attributes(self.class.workflow_column => current_state.to_s) if load_workflow_state.blank?
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
data/lib/cloudq.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'json'
|
3
|
+
require 'mongo_mapper'
|
4
|
+
require 'sinatra'
|
5
|
+
require 'sinatra/async'
|
6
|
+
require 'workflow'
|
7
|
+
|
8
|
+
require_relative 'rack/params'
|
9
|
+
|
10
|
+
module Cloudq; end
|
11
|
+
|
12
|
+
require_relative 'cloudq/job'
|
13
|
+
require_relative 'cloudq/app'
|
14
|
+
|
15
|
+
|
data/lib/rack/params.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Rack
|
2
|
+
class Params
|
3
|
+
URL_ENCODED = %r{^application/x-www-form-urlencoded}
|
4
|
+
JSON_ENCODED = %r{^application/json}
|
5
|
+
|
6
|
+
# A middle ware to parse params. This will parse both the
|
7
|
+
# query string parameters and the body and place them into
|
8
|
+
# the _params_ hash of the Goliath::Env for the request.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# use Rack::Params
|
12
|
+
#
|
13
|
+
# -- Borrowed from Goliath -- goliath.io
|
14
|
+
|
15
|
+
def initialize(app)
|
16
|
+
@app = app
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
env['params'] = retrieve_params(env)
|
21
|
+
@app.call(env)
|
22
|
+
end
|
23
|
+
|
24
|
+
def retrieve_params(env)
|
25
|
+
params = {}
|
26
|
+
params.merge!(::Rack::Utils.parse_nested_query(env['QUERY_STRING']))
|
27
|
+
|
28
|
+
if env['rack.input']
|
29
|
+
post_params = ::Rack::Utils::Multipart.parse_multipart(env)
|
30
|
+
unless post_params
|
31
|
+
body = env['rack.input'].read
|
32
|
+
env['rack.input'].rewind
|
33
|
+
|
34
|
+
post_params = case(env['CONTENT_TYPE'])
|
35
|
+
when URL_ENCODED then
|
36
|
+
::Rack::Utils.parse_nested_query(body)
|
37
|
+
when JSON_ENCODED then
|
38
|
+
JSON.parse(body)
|
39
|
+
else
|
40
|
+
{}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
params.merge!(post_params)
|
44
|
+
end
|
45
|
+
|
46
|
+
params
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/readme.md
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
# Cloudq Framework
|
2
|
+
|
3
|
+
Cloudq is a rack-based job queue framework.
|
4
|
+
|
5
|
+
## What does that mean?
|
6
|
+
|
7
|
+
Cloudq is a job queue, but instead of containing a ton of features, it focuses on doing job queue basics:
|
8
|
+
|
9
|
+
* Publish a Job to a Queue
|
10
|
+
* Reserve a Job from a Queue
|
11
|
+
* Delete a Job from a Queue
|
12
|
+
|
13
|
+
Using the rack application architecture, and taking advantage of rack middleware, adding features or embedding
|
14
|
+
into other applications is very easy.
|
15
|
+
|
16
|
+
Currently, it does depend on access to a mongodb datastore. Either the standard localhost or you can
|
17
|
+
set the MONGOHQ_URL env variable. This does not mean that any application you join with Cloudq needs to write to mongodb, just the job queue is connected to mongo.
|
18
|
+
|
19
|
+
# What about security?
|
20
|
+
# What about monitoring?
|
21
|
+
# etc???
|
22
|
+
|
23
|
+
The answer is simple, RACK MIDDLEWARE! If you want to implement ssl encryption and enforce SSL traffic, use
|
24
|
+
rack middleware. If you want to attach a slick admin
|
25
|
+
interface that monitors your queues, then use the rack
|
26
|
+
map function to attach another rack-based application to your config.ru file.
|
27
|
+
|
28
|
+
It really is that easy, and Cloudq gives you the flexibility to integrate almast anyway you can think of.
|
29
|
+
|
30
|
+
# How do I get started?
|
31
|
+
|
32
|
+
1. Install Mongodb
|
33
|
+
|
34
|
+
First you need to install MongoDb and have a mongodb service running. Or you can signup for a free account on
|
35
|
+
MongoHq.com.
|
36
|
+
|
37
|
+
2. Install Ruby version 1.9.2 or above
|
38
|
+
|
39
|
+
Make sure you have Ruby 1.9.2 installed on your machine, if you do not have 1.9.2, check out [RVM](https://rvm.beginrescueend.com/). Ruby 1.9.2 comes with rubygems, which will allow you to install the cloudq gem.
|
40
|
+
|
41
|
+
3. Install the Cloudq Gem
|
42
|
+
|
43
|
+
gem install cloudq
|
44
|
+
|
45
|
+
4. Build your cloudq server
|
46
|
+
|
47
|
+
cloudq [your name]
|
48
|
+
|
49
|
+
This will create a directory and add a config.ru and Gemfile. And if you don't want to add any other features,
|
50
|
+
you are good to go. All you have to do is cd into your directory and run:
|
51
|
+
|
52
|
+
|
53
|
+
thin start
|
54
|
+
|
55
|
+
And your cloudq server is up and running!
|
56
|
+
|
57
|
+
----------------
|
58
|
+
|
59
|
+
# Deploy
|
60
|
+
|
61
|
+
You can deploy Cloudq just like any rails application, but Heroku is an awesome platform to deploy:
|
62
|
+
|
63
|
+
# make sure you have your project in a git repo
|
64
|
+
|
65
|
+
git init
|
66
|
+
git add .
|
67
|
+
git commit -am "First Commit"
|
68
|
+
|
69
|
+
# deploy to heroku in 2 simple steps
|
70
|
+
|
71
|
+
heroku create
|
72
|
+
git push heroku master
|
73
|
+
|
74
|
+
|
75
|
+
# Contribution
|
76
|
+
|
77
|
+
Feel free to for fork the code and make pull requests.. But also create some Rack Middleware that helps add an
|
78
|
+
awesome feature stack to the Cloudq.
|
79
|
+
|
80
|
+
Here is a link to an awesome chapter in the Rails 3 in a Nutshell on Rack.
|
81
|
+
|
82
|
+
[http://rails-nutshell.labs.oreilly.com/ch07.html](http://rails-nutshell.labs.oreilly.com/ch07.html)
|
83
|
+
|
84
|
+
|
85
|
+
# API
|
86
|
+
|
87
|
+
The api pattern is like so:
|
88
|
+
|
89
|
+
/ [ Queue Name ] / [ Action ]
|
90
|
+
|
91
|
+
and
|
92
|
+
|
93
|
+
/ [ Queue Name ] / [ Action ] / [ id ]
|
94
|
+
|
95
|
+
|
96
|
+
## Add to Queue
|
97
|
+
|
98
|
+
post /myqueue
|
99
|
+
klass: 'Archive'
|
100
|
+
args: { [ args here ] }
|
101
|
+
|
102
|
+
## Reserve Message
|
103
|
+
|
104
|
+
get /myqueue
|
105
|
+
|
106
|
+
# JSON =>
|
107
|
+
{
|
108
|
+
klass: 'Archive'
|
109
|
+
args: {}
|
110
|
+
id: 1234
|
111
|
+
}
|
112
|
+
|
113
|
+
## Delete Message
|
114
|
+
|
115
|
+
delete /myqueue/1234
|
116
|
+
|
117
|
+
|
118
|
+
---
|
119
|
+
|
120
|
+
You can create as many queues as you want, when you post your first
|
121
|
+
message in the queue, it will create that queue, and when you perform
|
122
|
+
reserver, the worker will pull in a first in, first out process.
|
123
|
+
|
124
|
+
---
|
125
|
+
|
126
|
+
## Publish Job in Ruby
|
127
|
+
|
128
|
+
gem install cloudq_client
|
129
|
+
|
130
|
+
require 'cloudq_client/publish'
|
131
|
+
|
132
|
+
Cloudq::Connection.url = 'http://donuts.com'
|
133
|
+
Cloudq::Publish.job(:make_donuts, 'Donut', :types => [:glazed,
|
134
|
+
:chocolate])
|
135
|
+
|
136
|
+
|
137
|
+
---
|
138
|
+
|
139
|
+
### Worker in Ruby
|
140
|
+
|
141
|
+
gem install cloudq_client
|
142
|
+
|
143
|
+
require 'cloudq_client/cosume'
|
144
|
+
require 'Donut'
|
145
|
+
|
146
|
+
Cloudq::Connection.url = 'http://donuts.com'
|
147
|
+
loop do
|
148
|
+
Cloudq::Consume.job(:make_donuts)
|
149
|
+
sleep 5
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
---
|
154
|
+
|
155
|
+
The worker can be implemented in any language, but the api is designed
|
156
|
+
to return a json message that will call the perform class method and
|
157
|
+
pass in arguments to that method for processing:
|
158
|
+
|
159
|
+
klass = Object.const_get(payload[:klass].capitalize)
|
160
|
+
klass.perform(*payload[:args])
|
161
|
+
|
162
|
+
|
163
|
+
|
164
|
+
---
|
165
|
+
|
166
|
+
|
167
|
+
|
168
|
+
|
169
|
+
|
170
|
+
|
171
|
+
|
metadata
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cloudq
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.7
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tom Wilson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-04-19 00:00:00 -04:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rspec
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 2.5.0
|
25
|
+
type: :development
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rack-test
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: "0"
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: bundler
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: thin
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
type: :runtime
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: sinatra
|
62
|
+
prerelease: false
|
63
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
type: :runtime
|
70
|
+
version_requirements: *id005
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: bson_ext
|
73
|
+
prerelease: false
|
74
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
type: :runtime
|
81
|
+
version_requirements: *id006
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: mongo_mapper
|
84
|
+
prerelease: false
|
85
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: "0"
|
91
|
+
type: :runtime
|
92
|
+
version_requirements: *id007
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: eventmachine
|
95
|
+
prerelease: false
|
96
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: 1.0.0.beta.3
|
102
|
+
type: :runtime
|
103
|
+
version_requirements: *id008
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: async_sinatra
|
106
|
+
prerelease: false
|
107
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: "0"
|
113
|
+
type: :runtime
|
114
|
+
version_requirements: *id009
|
115
|
+
- !ruby/object:Gem::Dependency
|
116
|
+
name: workflow
|
117
|
+
prerelease: false
|
118
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: "0"
|
124
|
+
type: :runtime
|
125
|
+
version_requirements: *id010
|
126
|
+
description: The Cloudq Framework makes it easy to build remote job queue servers
|
127
|
+
email:
|
128
|
+
- tom@jackhq.com
|
129
|
+
executables:
|
130
|
+
- cloudq
|
131
|
+
extensions: []
|
132
|
+
|
133
|
+
extra_rdoc_files: []
|
134
|
+
|
135
|
+
files:
|
136
|
+
- lib/cloudq/app.rb
|
137
|
+
- lib/cloudq/job.rb
|
138
|
+
- lib/cloudq/version.rb
|
139
|
+
- lib/cloudq.rb
|
140
|
+
- lib/rack/params.rb
|
141
|
+
- LICENSE
|
142
|
+
- readme.md
|
143
|
+
- bin/cloudq
|
144
|
+
has_rdoc: true
|
145
|
+
homepage: http://github.com/twilson63/cloudq
|
146
|
+
licenses: []
|
147
|
+
|
148
|
+
post_install_message:
|
149
|
+
rdoc_options: []
|
150
|
+
|
151
|
+
require_paths:
|
152
|
+
- - lib
|
153
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
154
|
+
none: false
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: "0"
|
159
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
160
|
+
none: false
|
161
|
+
requirements:
|
162
|
+
- - ">="
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: 1.3.6
|
165
|
+
requirements: []
|
166
|
+
|
167
|
+
rubyforge_project:
|
168
|
+
rubygems_version: 1.6.2
|
169
|
+
signing_key:
|
170
|
+
specification_version: 3
|
171
|
+
summary: A Ruby Framework for a Remote Job Queue Server
|
172
|
+
test_files: []
|
173
|
+
|