cloudq 0.0.7
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/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
|
+
|