rplex 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gemtest ADDED
File without changes
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+ gem "sinatra","~>1.3.1"
3
+ gem 'rest-client',"~>1.6.7"
4
+ gem "hoe","~>2.12.4"
5
+ gem "rack-test","~>0.6.1"
6
+ gem 'rcov',"~>0.9.11"
7
+ gem 'rdoc',"~>3.11"
data/History.txt ADDED
@@ -0,0 +1,5 @@
1
+ === 0.0.2 / 2012-01-04
2
+ * Added backlog delivering queue sizes
3
+ === 0.0.1 / 2011-12-11
4
+ * Simple queue management and the bare minimum of the rest API: post a new job, get next job for worker
5
+ * The corresponding client library for the API
data/Manifest.txt ADDED
@@ -0,0 +1,12 @@
1
+ Gemfile
2
+ History.txt
3
+ lib/rplex.rb
4
+ lib/rplex/client.rb
5
+ lib/rplex/jobs.rb
6
+ lib/rplex/server.rb
7
+ Manifest.txt
8
+ Rakefile
9
+ README.txt
10
+ test/test_jobs.rb
11
+ test/test_server.rb
12
+ test/test_rplex.rb
data/README.txt ADDED
@@ -0,0 +1,60 @@
1
+ = rplex
2
+
3
+ http://c11865-prj.zuehlke.com
4
+
5
+ == DESCRIPTION:
6
+
7
+ Simple asynchronous data based job management
8
+
9
+ == INSTALL:
10
+
11
+ * sudo gem install rplex
12
+
13
+ === Why?
14
+ Because anything involving redis, postgres etc. is just a pain to install on Windows and we just wanted to parallelize a bunch of build tasks.
15
+
16
+ === How do you use it?
17
+ In a CI system to reduce our build times by parallelizing test execution:
18
+
19
+ The main build finishes and posts as data the revision and the location of the build artifacts to rplex. rplex queues the data for every worker that has appeared until that point.
20
+ Each worker polls the rplex queue, and then uses the information to grab the build and run a bunch of tests.
21
+
22
+ === Is that all?
23
+ Queues are created automatically when a worker tries to get data from the queue, data can be posted to a subset of the workers if necessary and that as they say is it.
24
+
25
+ == Example
26
+ Start the rplex service.
27
+
28
+ Use Rplex::Client to post data:
29
+
30
+ Rplex::Client.new("name","http://rplex.host:7777/job").new_job(job_id,data)
31
+
32
+ Use Rplex::Processor to check the queue and do something with any data present
33
+
34
+ Rplex::Processor.new(Rplex::Client.new("name","http://rplex.host:7777/job"),5).run!{|job_data| p job_data}
35
+
36
+
37
+ == LICENSE:
38
+
39
+ (The MIT License)
40
+
41
+ Copyright (c) 2011-2012 Vassilis Rizopoulos
42
+
43
+ Permission is hereby granted, free of charge, to any person obtaining
44
+ a copy of this software and associated documentation files (the
45
+ 'Software'), to deal in the Software without restriction, including
46
+ without limitation the rights to use, copy, modify, merge, publish,
47
+ distribute, sublicense, and/or sell copies of the Software, and to
48
+ permit persons to whom the Software is furnished to do so, subject to
49
+ the following conditions:
50
+
51
+ The above copyright notice and this permission notice shall be
52
+ included in all copies or substantial portions of the Software.
53
+
54
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
55
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
56
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
57
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
58
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
59
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
60
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ # -*- ruby -*-
2
+ $:.unshift File.join(File.dirname(__FILE__),"lib")
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require 'rplex'
6
+
7
+ Hoe.spec 'rplex' do |prj|
8
+ developer('Vassilis Rizopoulos', 'var@zuehlke.com')
9
+ prj.version=Rplex::Version::STRING
10
+ prj.summary = 'rplex multiplexes jobs across multiple workers'
11
+ prj.description = prj.paragraphs_of('README.txt', 1..4).join("\n\n")
12
+ prj.url = "http://github.com/damphyr/rplex"
13
+ prj.changes = prj.paragraphs_of('History.txt', 0..1).join("\n\n")
14
+ prj.extra_deps<<["sinatra", "~>1.3.1"]
15
+ prj.extra_deps<<['rest-client',"~>1.6.7"]
16
+ end
17
+
18
+ # vim: syntax=ruby
@@ -0,0 +1,68 @@
1
+ require 'rest_client'
2
+ require 'json'
3
+ module Rplex
4
+ class ClientError<RuntimeError
5
+ end
6
+
7
+ class Client
8
+ def initialize name,srv
9
+ @name=name
10
+ @service=srv
11
+ end
12
+
13
+ def new_job identifier,data,workers=[]
14
+ puts "Adding #{identifier} to #{@service}"
15
+ response=RestClient.post(@service, {"identifier"=>identifier, "data" => data,"workers"=>workers}, :content_type => :json, :accept => :json)
16
+ return response
17
+ rescue
18
+ raise ClientError, $!.message
19
+ end
20
+
21
+ def next_job
22
+ srv="#{@service}/#{@name}"
23
+ response=RestClient.get(srv,:accept => :json)
24
+ unless response.empty?
25
+ return JSON.parse(response)
26
+ else
27
+ return {}
28
+ end
29
+ rescue
30
+ raise ClientError, $!.message
31
+ end
32
+
33
+ def backlog
34
+ response=RestClient.get(@service,:accept => :json)
35
+ unless response.empty?
36
+ return JSON.parse(response)
37
+ else
38
+ return []
39
+ end
40
+ rescue
41
+ raise ClientError, $!.message
42
+ end
43
+
44
+ def to_s
45
+ "#{@name} working with #{@service}"
46
+ end
47
+ end
48
+
49
+ class Processor
50
+ def initialize client,interval=30
51
+ @client=client
52
+ @interval=interval
53
+ end
54
+
55
+ def run!
56
+ raise "You need to provide a block" unless block_given?
57
+ while true do
58
+ begin
59
+ job_data=@client.next_job
60
+ yield job_data unless job_data.empty?
61
+ rescue ClientError
62
+ puts $!
63
+ end
64
+ sleep @interval
65
+ end
66
+ end
67
+ end
68
+ end
data/lib/rplex/jobs.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'thread'
2
+
3
+ module Rplex
4
+ class InvalidData < RuntimeError
5
+ end
6
+ #Simple queue management for Rplex job data
7
+ class Overseer
8
+ def initialize
9
+ @queues={}
10
+ end
11
+ #Add a job for all workers currently active
12
+ def << job_data
13
+ add_job(job_data)
14
+ end
15
+ #Add a job.
16
+ #
17
+ #You can limit the workers it is distributed to by providing an Array
18
+ #with the worker identifiers
19
+ def add_job job_data,workers=[]
20
+ queued_in=0
21
+ workers=@queues.keys if workers.empty?
22
+ if valid?(job_data)
23
+ @queues.each do |w,q|
24
+ if workers.include?(w)
25
+ q.push(job_data)
26
+ queued_in+=1
27
+ end
28
+ end
29
+ else
30
+ raise InvalidData
31
+ end
32
+ return queued_in
33
+ end
34
+ #Get the next job for the worker
35
+ #
36
+ #If there is no Queue for the worker, create an empty one
37
+ def [](worker)
38
+ @queues[worker]||=Queue.new
39
+ @queues[worker].pop(true) rescue nil
40
+ end
41
+ #Get an array of [name,queue size]
42
+ def backlog
43
+ @queues.map{|k,v| [k,v.size]}
44
+ end
45
+ #Returns true if the job data is valid
46
+ def valid? job_data
47
+ job_data["identifier"] && job_data["data"]
48
+ rescue
49
+ false
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,55 @@
1
+ require 'sinatra/base'
2
+ require 'rplex'
3
+ require 'rplex/jobs'
4
+ require 'json'
5
+ module Rplex
6
+
7
+ class Server<Sinatra::Base
8
+ def initialize
9
+ super
10
+ @overseer = Rplex::Overseer.new
11
+ end
12
+ post '/job' do
13
+ begin
14
+ reply={}
15
+ workers= params["workers"] ? params["workers"] : []
16
+ reply[:workers]=@overseer.add_job(params,workers)
17
+ [200,{'Content-Type' => 'application/json'},reply.to_json]
18
+ rescue
19
+ status 500
20
+ end
21
+ end
22
+ get '/job/:worker' do |worker|
23
+ reply={}
24
+ reply=@overseer[worker]
25
+ if reply
26
+ [200,{'Content-Type' => 'application/json'},reply.to_json]
27
+ else
28
+ status 204
29
+ end
30
+ end
31
+ get '/' do
32
+ [200,{'Content-Type' => 'application/json'},{"version"=>Rplex::Version::STRING}.to_json]
33
+ end
34
+
35
+ get '/backlog' do
36
+ [200,{'Content-Type' => 'application/json'},@overseer.backlog.to_json]
37
+ end
38
+
39
+ def self.define_settings cfg={}
40
+ cfg||={}
41
+ #the settings that are not public
42
+ enable :logging
43
+ enable :run
44
+ enable :static
45
+ set :server, %w[thin mongrel webrick]
46
+ set :root, File.dirname(__FILE__)
47
+ #the settings that can be changed
48
+ cfg[:public_folder] ||= File.dirname(__FILE__) + '/public'
49
+ cfg[:port] ||= 7777
50
+ #set them
51
+ set :port, cfg[:port]
52
+ set :public_folder,cfg[:public_folder]
53
+ end
54
+ end
55
+ end
data/lib/rplex.rb ADDED
@@ -0,0 +1,8 @@
1
+ module Rplex
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 2
6
+ STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
7
+ end
8
+ end
data/test/test_jobs.rb ADDED
@@ -0,0 +1,37 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),"..","lib")
2
+ require "test/unit"
3
+ require "rplex/jobs"
4
+
5
+ class TestOverseer < Test::Unit::TestCase
6
+ def test_invalid_data
7
+ ov=Rplex::Overseer.new
8
+ job_data={:foo=>"8888"}
9
+ assert_raise(Rplex::InvalidData) { ov << job_data }
10
+ job_data="foo"
11
+ assert_raise(Rplex::InvalidData) { ov << job_data }
12
+ assert_raise(Rplex::InvalidData) { ov << nil }
13
+ end
14
+ def test_basic
15
+ ov=Rplex::Overseer.new
16
+ job_data={"identifier"=>"8888","data"=>{:foo=>"bar",:bar=>"foo"}}
17
+ assert_nil(ov["worker"])
18
+ #expect that there is only 1 queue there
19
+ assert_equal(1, ov<<job_data)
20
+ assert_nil(ov["slave"])
21
+ assert_equal(2, ov<<job_data)
22
+ assert_equal(1, ov.add_job(job_data,["slave"]) )
23
+ #go through the slave queue
24
+ assert_equal(job_data,ov["slave"])
25
+ assert_equal(job_data,ov["slave"])
26
+ assert_nil(ov["slave"])
27
+ end
28
+ def test_backlog
29
+ ov=Rplex::Overseer.new
30
+ assert(ov.backlog.empty?)
31
+ job_data={"identifier"=>"8888","data"=>{:foo=>"bar",:bar=>"foo"}}
32
+ ov['worker1']
33
+ ov['worker2']
34
+ ov<<job_data
35
+ assert_equal([['worker1',1],['worker2',1]], ov.backlog)
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),"..","lib")
2
+ require "test/unit"
3
+ require "rplex"
4
+
5
+ class TestRplex < Test::Unit::TestCase
6
+ def test_version
7
+ assert_equal(Rplex::Version::STRING, "#{Rplex::Version::MAJOR}.#{Rplex::Version::MINOR}.#{Rplex::Version::TINY}")
8
+ end
9
+ end
@@ -0,0 +1,42 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),"..","lib")
2
+ require 'test/unit'
3
+ require 'rubygems'
4
+ require 'rplex/server'
5
+ require 'rack/test'
6
+ require 'json'
7
+
8
+ class ServerTest < Test::Unit::TestCase
9
+ include Rack::Test::Methods
10
+
11
+ def app
12
+ Rplex::Server
13
+ end
14
+
15
+ def test_root
16
+ get '/'
17
+ assert_equal({"version"=>Rplex::Version::STRING},JSON.parse(last_response.body))
18
+ end
19
+
20
+ def test_new
21
+ post '/job', :identifier=>"8888",:data=>{:url=>"http://foo.bar/drops",:revision=>"8888"}
22
+ assert_not_nil(JSON.parse(last_response.body)["workers"])
23
+
24
+ post '/job', :foo=>"bar"
25
+ assert_equal(500, last_response.status)
26
+ end
27
+
28
+ def test_job
29
+ get '/job/worker'
30
+ assert_equal(204,last_response.status)
31
+ payload={"identifier"=>"8888","data"=>{"url"=>"http://foo.bar/drops","revision"=>"8888"}}
32
+ post '/job', payload
33
+ get '/job/worker'
34
+ assert_equal(200,last_response.status)
35
+ assert_equal(payload,JSON.parse(last_response.body))
36
+ end
37
+
38
+ def test_backlog
39
+ get '/backlog'
40
+ assert_equal(200, last_response.status)
41
+ end
42
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rplex
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Vassilis Rizopoulos
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-01-04 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: sinatra
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 25
29
+ segments:
30
+ - 1
31
+ - 3
32
+ - 1
33
+ version: 1.3.1
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: rest-client
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 1
45
+ segments:
46
+ - 1
47
+ - 6
48
+ - 7
49
+ version: 1.6.7
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: rdoc
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 19
61
+ segments:
62
+ - 3
63
+ - 10
64
+ version: "3.10"
65
+ type: :development
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: hoe
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ hash: 27
76
+ segments:
77
+ - 2
78
+ - 12
79
+ version: "2.12"
80
+ type: :development
81
+ version_requirements: *id004
82
+ description: |-
83
+ http://c11865-prj.zuehlke.com
84
+
85
+ == DESCRIPTION:
86
+
87
+ Simple asynchronous data based job management
88
+
89
+ == INSTALL:
90
+ email:
91
+ - var@zuehlke.com
92
+ executables: []
93
+
94
+ extensions: []
95
+
96
+ extra_rdoc_files:
97
+ - History.txt
98
+ - Manifest.txt
99
+ - README.txt
100
+ files:
101
+ - Gemfile
102
+ - History.txt
103
+ - lib/rplex.rb
104
+ - lib/rplex/client.rb
105
+ - lib/rplex/jobs.rb
106
+ - lib/rplex/server.rb
107
+ - Manifest.txt
108
+ - Rakefile
109
+ - README.txt
110
+ - test/test_jobs.rb
111
+ - test/test_server.rb
112
+ - test/test_rplex.rb
113
+ - .gemtest
114
+ homepage: http://github.com/damphyr/rplex
115
+ licenses: []
116
+
117
+ post_install_message:
118
+ rdoc_options:
119
+ - --main
120
+ - README.txt
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ none: false
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ hash: 3
129
+ segments:
130
+ - 0
131
+ version: "0"
132
+ required_rubygems_version: !ruby/object:Gem::Requirement
133
+ none: false
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ hash: 3
138
+ segments:
139
+ - 0
140
+ version: "0"
141
+ requirements: []
142
+
143
+ rubyforge_project: rplex
144
+ rubygems_version: 1.8.6
145
+ signing_key:
146
+ specification_version: 3
147
+ summary: rplex multiplexes jobs across multiple workers
148
+ test_files:
149
+ - test/test_jobs.rb
150
+ - test/test_rplex.rb
151
+ - test/test_server.rb