scales-worker 0.0.1.beta.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Thomas Fankhauser
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/bin/scales-worker ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'scales-worker'
4
+
5
+ Scales.try_to_setup_env!
6
+ Scales::Worker.run!
@@ -0,0 +1,19 @@
1
+ require "scales-worker/boot/autoload"
2
+
3
+ module Scales
4
+ module Worker
5
+ autoload :Application, "scales-worker/application"
6
+ autoload :Job, "scales-worker/job"
7
+ autoload :Response, "scales-worker/response"
8
+ autoload :Worker, "scales-worker/worker"
9
+ autoload :Pusher, "scales-worker/pusher"
10
+ autoload :Cache, "scales-worker/cache"
11
+ autoload :Path, "scales-worker/path"
12
+ autoload :Status, "scales-worker/status"
13
+ end
14
+
15
+ autoload :Up, "scales/up"
16
+ end
17
+
18
+ require "scales-worker/version"
19
+ require "scales-worker/base"
@@ -0,0 +1,7 @@
1
+ module Scales
2
+ module Worker
3
+ module Application
4
+ autoload :Rails, "scales-worker/application/rails"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ module Scales
2
+ module Worker
3
+ module Application
4
+ module Rails
5
+ ENV['RAILS_ENV'] ||= Scales.env
6
+
7
+ class << self
8
+
9
+ @@app = nil
10
+
11
+ def initialize_app!
12
+ @@app ||= load_application.initialize!
13
+ raise "Could not load Rails Application" if @@app.nil?
14
+ @@app
15
+ end
16
+ alias_method :app, :initialize_app!
17
+ alias_method :initialize_environment!, :initialize_app!
18
+
19
+ def name
20
+ "Rails #{app.class.to_s.split("::").first} (#{ENV['RAILS_ENV']})"
21
+ end
22
+
23
+ private
24
+
25
+ def load_application
26
+ before_modules = Object.constants
27
+ require './config/application.rb'
28
+ after_modules = Object.constants
29
+ delta_modules = after_modules - before_modules
30
+
31
+ Kernel.const_get(delta_modules.last)::Application
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,52 @@
1
+ module Scales
2
+ module Worker
3
+ class << self
4
+
5
+ def run!
6
+ Worker.new.work!
7
+ end
8
+
9
+ end
10
+ end
11
+
12
+ # Cache Methods
13
+ class << self
14
+
15
+ def push(params)
16
+ Worker::Cache::Push.push(params)
17
+ end
18
+
19
+ def append(params)
20
+ Worker::Cache.class_for(params).append(params)
21
+ end
22
+
23
+ def prepend(params)
24
+ Worker::Cache.class_for(params).prepend(params)
25
+ end
26
+
27
+ def set(params)
28
+ Worker::Cache.class_for(params).set(params)
29
+ end
30
+
31
+ def replace(params)
32
+ Worker::Cache.class_for(params).replace(params)
33
+ end
34
+
35
+ def remove(format, params)
36
+ Worker::Cache.class_for(format).remove(params)
37
+ end
38
+
39
+ def destroy(*paths)
40
+ Worker::Cache::Destroy.destroy(*paths)
41
+ end
42
+
43
+ def update(*paths, params)
44
+ Worker::Cache::Update.update(*paths, params)
45
+ end
46
+
47
+ def get(path)
48
+ Storage::Sync::get_content(path)
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,9 @@
1
+ require 'scales-core'
2
+
3
+ autoload :Rake, "scales-worker/boot/initializers/rake"
4
+ autoload :Nokogiri, "scales-worker/boot/initializers/nokogiri"
5
+ autoload :JsonPath, "scales-worker/boot/initializers/jsonpath"
6
+
7
+ module Rake
8
+ autoload :TaskLib, "scales-worker/boot/initializers/rake"
9
+ end
@@ -0,0 +1 @@
1
+ require 'jsonpath'
@@ -0,0 +1 @@
1
+ require 'nokogiri'
@@ -0,0 +1,2 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
@@ -0,0 +1,28 @@
1
+ module Scales
2
+ module Worker
3
+ module Cache
4
+ autoload :HTML, "scales-worker/cache/html"
5
+ autoload :JSON, "scales-worker/cache/json"
6
+ autoload :XML, "scales-worker/cache/xml"
7
+ autoload :Push, "scales-worker/cache/push"
8
+ autoload :Destroy, "scales-worker/cache/destroy"
9
+ autoload :Update, "scales-worker/cache/update"
10
+
11
+ class << self
12
+
13
+ def class_for params_or_format
14
+ params = params_or_format.is_a?(Symbol) ? { params_or_format => nil } : params_or_format
15
+
16
+ return HTML if params.keys.include?(:html)
17
+ return JSON if params.keys.include?(:json)
18
+ return XML if params.keys.include?(:xml)
19
+ end
20
+
21
+ def resource_or_partial?(path)
22
+ (path =~ /^\//) ? "resource" : "partial"
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ module Scales
2
+ module Worker
3
+ module Cache
4
+ module Destroy
5
+ class << self
6
+
7
+ def destroy(*paths)
8
+ paths.each do |path|
9
+ publish_destroy(path)
10
+ Storage::Sync.del_content(path)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def publish_destroy(path)
17
+ data = {
18
+ :path => path,
19
+ :type => "destroy_#{Cache.resource_or_partial?(path)}"
20
+ }
21
+ Storage::Sync.connection.publish "scales_monitor_events", data.to_json
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,72 @@
1
+ module Scales
2
+ module Worker
3
+ module Cache
4
+ module HTML
5
+ include Helper::ContentTypes
6
+
7
+ class << self
8
+
9
+ def append(params = {})
10
+ html = params[:html]
11
+ path = params[:to] || params[:at]
12
+ change_content_at(path, params[:select]) do |selection|
13
+ selection.each{ |element| element.children.last.after html }
14
+ end
15
+ end
16
+
17
+ def prepend(params = {})
18
+ html = params[:html]
19
+ path = params[:to] || params[:at]
20
+ change_content_at(path, params[:select]) do |selection|
21
+ selection.each{ |element| element.children.first.before html }
22
+ end
23
+ end
24
+
25
+ def set(params = {})
26
+ html = params[:html]
27
+ path = params[:to] || params[:at]
28
+ change_content_at(path, params[:select]) do |selection|
29
+ selection.each{ |element| element.inner_html = html }
30
+ end
31
+ end
32
+
33
+ def replace(params = {})
34
+ html = params[:html]
35
+ path = params[:to] || params[:at]
36
+ change_content_at(path, params[:select]) do |selection|
37
+ selection.each{ |element| element.replace html }
38
+ end
39
+ end
40
+
41
+ def remove(params = {})
42
+ path = params[:to] || params[:at]
43
+ change_content_at(path, params[:select]) do |selection|
44
+ selection.each{ |element| element.remove }
45
+ end
46
+ end
47
+
48
+
49
+ private
50
+
51
+ def is_html_page?(page)
52
+ page.match /<html>/
53
+ end
54
+
55
+ def change_content_at path, selector
56
+ raise 'No path defined like this :to => "/tracks"' if path.nil?
57
+ raise 'No selector defined like this :select => "#tracks"' if selector.nil?
58
+
59
+ html = Storage::Sync.get_content(path)
60
+ html = is_html_page?(html) ? Nokogiri::HTML.parse(html) : Nokogiri::HTML.fragment(html)
61
+
62
+ yield html.css(selector)
63
+
64
+ Storage::Sync.set_content(path, html.inner_html)
65
+ html.inner_html
66
+ end
67
+
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,68 @@
1
+ module Scales
2
+ module Worker
3
+ module Cache
4
+ module JSON
5
+ include Helper::ContentTypes
6
+
7
+ class << self
8
+
9
+ def append(params = {})
10
+ json = params[:json]
11
+ path = params[:to] || params[:at]
12
+ change_content_at(path, params[:select]) do |selection, selector|
13
+ selection.gsub(selector){ |selection| selection << parse(json) }
14
+ end
15
+ end
16
+
17
+ def prepend(params = {})
18
+ json = params[:json]
19
+ path = params[:to] || params[:at]
20
+ change_content_at(path, params[:select]) do |selection, selector|
21
+ selection.gsub(selector){ |selection| (selection.reverse << parse(json)).reverse }
22
+ end
23
+ end
24
+
25
+ def set(params = {})
26
+ json = params[:json]
27
+ path = params[:to] || params[:at]
28
+ change_content_at(path, params[:select]) do |selection, selector|
29
+ selection.gsub(selector){ |selection| selection = parse(json) }
30
+ end
31
+ end
32
+
33
+ def replace(params = {})
34
+ set(params)
35
+ end
36
+
37
+ def remove(params = {})
38
+ path = params[:to] || params[:at]
39
+ change_content_at(path, params[:select]) do |selection, selector|
40
+ selection.delete(selector)
41
+ end
42
+ end
43
+
44
+
45
+ private
46
+
47
+ def parse(json)
48
+ ::JSON.parse(json)
49
+ end
50
+
51
+ def change_content_at path, selector
52
+ raise 'No path defined like this :to => "/tracks.json"' if path.nil?
53
+ raise 'No selector defined like this :select => "#tracks"' if selector.nil?
54
+
55
+ json = Storage::Sync.get_content(path)
56
+ json = JsonPath.for(json)
57
+
58
+ json = yield json, selector
59
+
60
+ Storage::Sync.set_content(path, json.to_hash.to_json)
61
+ json
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,35 @@
1
+ module Scales
2
+ module Worker
3
+ module Cache
4
+ module Push
5
+ include Helper::ContentTypes
6
+
7
+ class << self
8
+
9
+ def push(params = {})
10
+ path = params.delete(:to)
11
+ format, content = params.to_a.first
12
+ raise "Don't know where to push, missing :to => '/a/path' parameter" if path.nil?
13
+ raise "Unknown format :#{format}" if format.to_content_type.nil?
14
+
15
+ publish_push(path, format)
16
+ Storage::Sync.set_content(path, content)
17
+ content
18
+ end
19
+
20
+ private
21
+
22
+ def publish_push(path, format)
23
+ data = {
24
+ :path => path,
25
+ :format => format.to_s.upcase,
26
+ :type => "push_#{Cache.resource_or_partial?(path)}"
27
+ }
28
+ Storage::Sync.connection.publish "scales_monitor_events", data.to_json
29
+ end
30
+
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,20 @@
1
+ module Scales
2
+ module Worker
3
+ module Cache
4
+ module Update
5
+ include Helper::ContentTypes
6
+
7
+ class << self
8
+
9
+ def update(*paths, params)
10
+ raise "Please define a format like this :format => :html" unless params.is_a?(Hash)
11
+ format = params.delete(:format)
12
+ raise "Unknown format :#{format}" if format.to_content_type.nil?
13
+ paths.each{ |path| Thread.current[:post_process_queue] << { :format => format, :to => path }}
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,68 @@
1
+ module Scales
2
+ module Worker
3
+ module Cache
4
+ module XML
5
+ include Helper::ContentTypes
6
+
7
+ class << self
8
+
9
+ def append(params = {})
10
+ xml = params[:xml]
11
+ path = params[:to] || params[:at]
12
+ change_content_at(path, params[:select]) do |selection|
13
+ selection.each{ |element| element.children.last.after xml }
14
+ end
15
+ end
16
+
17
+ def prepend(params = {})
18
+ xml = params[:xml]
19
+ path = params[:to] || params[:at]
20
+ change_content_at(path, params[:select]) do |selection|
21
+ selection.each{ |element| element.children.first.before xml }
22
+ end
23
+ end
24
+
25
+ def set(params = {})
26
+ xml = params[:xml]
27
+ path = params[:to] || params[:at]
28
+ change_content_at(path, params[:select]) do |selection|
29
+ selection.each{ |element| element.inner_html = xml }
30
+ end
31
+ end
32
+
33
+ def replace(params = {})
34
+ xml = params[:xml]
35
+ path = params[:to] || params[:at]
36
+ change_content_at(path, params[:select]) do |selection|
37
+ selection.each{ |element| element.replace xml }
38
+ end
39
+ end
40
+
41
+ def remove(params = {})
42
+ path = params[:to] || params[:at]
43
+ change_content_at(path, params[:select]) do |selection|
44
+ selection.each{ |element| element.remove }
45
+ end
46
+ end
47
+
48
+
49
+ private
50
+
51
+ def change_content_at path, selector
52
+ raise 'No path defined like this :to => "/tracks.xml"' if path.nil?
53
+ raise 'No selector defined like this :select => "/tracks"' if selector.nil?
54
+
55
+ xml = Storage::Sync.get_content(path)
56
+ xml = Nokogiri::XML.parse(xml)
57
+
58
+ yield xml.xpath(selector)
59
+
60
+ Storage::Sync.set_content(path, xml.inner_html)
61
+ xml.inner_html
62
+ end
63
+
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,15 @@
1
+ module Scales
2
+ module Worker
3
+ module Job
4
+ class << self
5
+
6
+ def to_env(job)
7
+ env = JSON.parse(job)
8
+ env['rack.input'] = StringIO.new(env['rack.input'])
9
+ env
10
+ end
11
+
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,89 @@
1
+ module Scales
2
+ module Worker
3
+ module Path
4
+ include Helper::ContentTypes
5
+
6
+ class << self
7
+
8
+ def parse_path(original_path)
9
+ path, query = original_path.split "?"
10
+ [original_path, path, query]
11
+ end
12
+
13
+ def with_options_to_env(options)
14
+ full, path, query = parse_path(options[:to])
15
+ content_type = options[:format].to_content_type
16
+
17
+ {
18
+ "GATEWAY_INTERFACE"=>"CGI/1.1",
19
+ "CONTENT_TYPE" => content_type,
20
+ "PATH_INFO"=>path,
21
+ "QUERY_STRING"=>query,
22
+ "REMOTE_ADDR"=>"127.0.0.1",
23
+ "REMOTE_HOST"=>"loopback",
24
+ "REQUEST_METHOD"=>"GET",
25
+ "REQUEST_URI"=>"http://localhost:3000#{full}",
26
+ "SCRIPT_NAME"=>"",
27
+ "SERVER_NAME"=>"localhost",
28
+ "SERVER_PORT"=>"3000",
29
+ "SERVER_PROTOCOL"=>"HTTP/1.1",
30
+ "SERVER_SOFTWARE"=>"Scales-Server #{Scales::Worker::VERSION}",
31
+ "HTTP_HOST"=>"localhost:3000",
32
+ "HTTP_USER_AGENT"=>"Scales-Worker #{Scales::Worker::VERSION}",
33
+ "HTTP_ACCEPT"=>"*/*",
34
+ "HTTP_COOKIE"=>"",
35
+ "HTTP_ACCEPT_LANGUAGE"=>"en-us",
36
+ "HTTP_ACCEPT_ENCODING"=>"gzip, deflate",
37
+ "HTTP_CONNECTION"=>"keep-alive",
38
+ "rack.version"=>[1, 1],
39
+ "rack.input"=>StringIO.new,
40
+ "rack.multithread"=>false,
41
+ "rack.multiprocess"=>false,
42
+ "rack.run_once"=>false,
43
+ "rack.url_scheme"=>"http",
44
+ "HTTP_VERSION"=>"HTTP/1.1",
45
+ "REQUEST_PATH"=>path,
46
+ "ORIGINAL_FULLPATH"=>full
47
+ }
48
+ end
49
+
50
+ def to_env(options, env)
51
+ full, path, query = parse_path(options[:to])
52
+ content_type = options[:format].to_content_type rescue env["CONTENT_TYPE"]
53
+ {
54
+ "GATEWAY_INTERFACE"=>"CGI/1.1",
55
+ "CONTENT_TYPE" => content_type,
56
+ "PATH_INFO"=>path,
57
+ "QUERY_STRING"=>query,
58
+ "REMOTE_ADDR"=>"127.0.0.1",
59
+ "REMOTE_HOST"=>"loopback",
60
+ "REQUEST_METHOD"=>"GET",
61
+ "REQUEST_URI"=>"http://localhost:3000#{full}",
62
+ "SCRIPT_NAME"=>"",
63
+ "SERVER_NAME"=>"localhost",
64
+ "SERVER_PORT"=>"3000",
65
+ "SERVER_PROTOCOL"=>"HTTP/1.1",
66
+ "SERVER_SOFTWARE"=>"Scales-Server #{Scales::Worker::VERSION}",
67
+ "HTTP_HOST"=>"localhost:3000",
68
+ "HTTP_USER_AGENT"=>"Scales-Worker #{Scales::Worker::VERSION}",
69
+ "HTTP_ACCEPT"=>"*/*",
70
+ "HTTP_COOKIE"=>env["HTTP_COOKIE"],
71
+ "HTTP_ACCEPT_LANGUAGE"=>env["HTTP_ACCEPT_LANGUAGE"],
72
+ "HTTP_ACCEPT_ENCODING"=>env["HTTP_ACCEPT_ENCODING"],
73
+ "HTTP_CONNECTION"=>"keep-alive",
74
+ "rack.version"=>[1, 1],
75
+ "rack.input"=>StringIO.new,
76
+ "rack.multithread"=>false,
77
+ "rack.multiprocess"=>false,
78
+ "rack.run_once"=>false,
79
+ "rack.url_scheme"=>"http",
80
+ "HTTP_VERSION"=>"HTTP/1.1",
81
+ "REQUEST_PATH"=>path,
82
+ "ORIGINAL_FULLPATH"=>full
83
+ }
84
+ end
85
+
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,75 @@
1
+ module Scales
2
+ module Worker
3
+ class Pusher
4
+ attr_reader :app
5
+ attr_reader :type
6
+ attr_reader :total
7
+ attr_reader :done
8
+ attr_reader :progress
9
+
10
+ def initialize(type = Application::Rails)
11
+ @type, @app = type, type.app
12
+ reset_progress!
13
+ end
14
+
15
+ def reset_progress!
16
+ @total, @done = 0, 0
17
+ end
18
+
19
+ def progress
20
+ ((@done.to_f / @total.to_f) * 100).to_i rescue 0
21
+ end
22
+
23
+ def process!(path)
24
+ env = Path.with_options_to_env(path)
25
+
26
+ response = @app.call(env)
27
+ response.last.close
28
+
29
+ Storage::Sync.set_content(path[:to], Response.to_string(response)) if path[:push]
30
+
31
+ env
32
+ end
33
+
34
+ def post_process!(env)
35
+ while path = Thread.current[:post_process_queue].pop
36
+ request = Path.to_env(path, env)
37
+
38
+ begin
39
+ response = @app.call(request)
40
+ response.last.close
41
+ rescue Exception => e
42
+ puts e
43
+ end
44
+ end
45
+ end
46
+
47
+ # Process a single path in thread
48
+ def process_push!(path)
49
+ Thread.current[:post_process_queue] = []
50
+ env = process!(path)
51
+ post_process!(env)
52
+ @done += 1
53
+ end
54
+
55
+ def push!(paths)
56
+ raise "No Paths added".red if paths.nil? or paths.empty?
57
+
58
+ puts "Environment: #{Scales.env}".green
59
+ puts "Application: #{@type.name}".green
60
+ puts "Path: #{Dir.pwd}".green
61
+ puts "Redis: #{Scales.config.host}:#{Scales.config.port}/#{Scales.config.database}".green
62
+
63
+ @total, @done = paths.size, 0
64
+ paths.each do |path|
65
+ print "Pushing paths: #{progress}% (#{@done}/#{@total})".green
66
+ process_push!(path)
67
+ print "\r"
68
+ end
69
+
70
+ puts "Pushing paths: #{progress}% (#{@done}/#{@total})".green
71
+ puts "Done.".green
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,40 @@
1
+ module Scales
2
+ module Worker
3
+ module Response
4
+ class << self
5
+
6
+ def to_job(id, response)
7
+ status, headers, chunked_body = response
8
+
9
+ headers = headers.dup.keep_if do |key, value|
10
+ key.match /^HTTP_/ or
11
+ [
12
+ 'Location',
13
+ 'Content-Type',
14
+ 'Set-Cookie',
15
+ 'REQUEST_METHOD',
16
+ 'SCRIPT_NAME',
17
+ 'PATH_INFO',
18
+ 'QUERY_STRING',
19
+ 'SERVER_NAME',
20
+ 'SERVER_PORT'
21
+ ].include?(key)
22
+ end
23
+ headers['scales.id'] = id
24
+
25
+ body = ""
26
+ chunked_body.each{ |chunk| body << chunk }
27
+
28
+ [status, headers, body]
29
+ end
30
+
31
+ def to_string(response)
32
+ body = ""
33
+ response.last.each{ |chunk| body << chunk }
34
+ body
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,72 @@
1
+ module Scales
2
+ module Worker
3
+
4
+ class Status
5
+ attr_reader :key, :id, :address, :port
6
+
7
+ def initialize address, port = nil
8
+ @id = SecureRandom.hex(8)
9
+ @key = "scales_worker_#{@id}"
10
+ @address, @port = address.to_s, port.to_s
11
+ end
12
+
13
+ def start!
14
+ data = {
15
+ :id => @id,
16
+ :key => @key,
17
+ :type => "worker_started",
18
+ :spawned_at => Time.now.to_i,
19
+ :env => Scales.env,
20
+ :ip => @address,
21
+ :port => @port
22
+ }
23
+ json = JSON.generate(data)
24
+
25
+ Storage::Sync.connection.set(@key, json)
26
+ Storage::Sync.connection.publish("scales_monitor_events", json)
27
+ @already_stopped = false
28
+ end
29
+
30
+ def stop!
31
+ return if @already_stopped
32
+
33
+ data = {
34
+ :id => @id,
35
+ :key => @key,
36
+ :type => "worker_stopped"
37
+ }
38
+ json = JSON.generate(data)
39
+ Storage::Sync.connection.del(@key)
40
+ Storage::Sync.connection.publish("scales_monitor_events", json)
41
+ @already_stopped = true
42
+ end
43
+
44
+ def took_request_from_queue!(job)
45
+ data = {
46
+ :id => job['scales.id'],
47
+ :worker_id => @id,
48
+ :type => "worker_took_request_from_queue",
49
+ :path => job['PATH_INFO'],
50
+ :method => job['REQUEST_METHOD']
51
+ }
52
+ json = JSON.generate(data)
53
+ Storage::Sync.connection.publish("scales_monitor_events", json)
54
+ end
55
+
56
+ def put_response_in_queue!(response)
57
+ data = {
58
+ :id => response[1]['scales.id'],
59
+ :worker_id => @id,
60
+ :type => "worker_put_response_in_queue",
61
+ :path => response[1]['PATH_INFO'],
62
+ :method => response[1]['REQUEST_METHOD'],
63
+ :status => response[0]
64
+ }
65
+ json = JSON.generate(data)
66
+ Storage::Sync.connection.publish("scales_monitor_events", json)
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,5 @@
1
+ module Scales
2
+ module Worker
3
+ VERSION = "0.0.1.beta.1" unless defined?(Scales::Worker::VERSION)
4
+ end
5
+ end
@@ -0,0 +1,85 @@
1
+ module Scales
2
+ module Worker
3
+ class Worker
4
+ attr_reader :app
5
+ attr_reader :type
6
+
7
+ def initialize(type = Application::Rails)
8
+ @type, @app, @status = type, type.app, Status.new("localhost")
9
+ at_exit{ @status.stop! }
10
+ end
11
+
12
+ def parse(job)
13
+ Job.to_env(job)
14
+ end
15
+
16
+ def process!(job)
17
+ env = parse(job)
18
+ id = env['scales.id']
19
+
20
+ @status.took_request_from_queue!(env)
21
+
22
+ begin
23
+ response = @app.call(env)
24
+ response.last.close
25
+ [id, Response.to_job(id, response)]
26
+ rescue
27
+ [id, [500, {}, ""]]
28
+ end
29
+ end
30
+
31
+ def post_process!(job)
32
+ env = parse(job)
33
+ while path = Thread.current[:post_process_queue].pop
34
+ request = Path.to_env(path, env)
35
+
36
+ begin
37
+ response = @app.call(request)
38
+ response.last.close
39
+ rescue Exception => e
40
+ puts e
41
+ end
42
+ end
43
+ end
44
+
45
+ # Wait for a request, process it, publish the response and exit
46
+ def process_request!(should_wait_for_request_to_finish = false)
47
+ job = Scales::Queue::Sync.pop
48
+ id, response = nil, nil
49
+
50
+ Thread.abort_on_exception = true
51
+ thread = Thread.new do
52
+ Thread.current[:post_process_queue] = []
53
+ id, response = process!(job)
54
+ print "#{id} -> " + "#{response.first}".green + " - #{Thread.current[:post_process_queue].size} post jobs -> "
55
+ post_process!(job)
56
+ print "done".green + " - publishing -> "
57
+ @status.put_response_in_queue!(response)
58
+ Scales::PubSub::Sync.publish("scales_response_#{id}", JSON.generate(response))
59
+ puts "done".green
60
+ end
61
+
62
+ thread.join if should_wait_for_request_to_finish
63
+
64
+ [id, response]
65
+ end
66
+
67
+ # Loop the processing of requests
68
+ def work!
69
+ @status.start!
70
+
71
+ puts "Environment: #{Scales.env}".green
72
+ puts "Application: #{@type.name}".green
73
+ puts "Path: #{Dir.pwd}".green
74
+ puts "Redis: #{Scales.config.host}:#{Scales.config.port}/#{Scales.config.database}".green
75
+
76
+ begin
77
+ loop{ process_request! }
78
+ rescue Interrupt => e
79
+ puts "Goodbye".green
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+ end
data/lib/scales/up.rb ADDED
@@ -0,0 +1,51 @@
1
+ module Scales
2
+ class Up < ::Rake::TaskLib
3
+ include ::Rake::DSL if defined?(::Rake::DSL)
4
+ include Helper::ContentTypes
5
+
6
+ attr_reader :name
7
+ attr_reader :app
8
+ attr_accessor :paths
9
+
10
+ def push format, options
11
+ paths << { :format => format, :push => true }.merge(options)
12
+ end
13
+
14
+ def update *new_paths, params
15
+ raise "Please define a format like this :format => :html" unless params.is_a?(Hash)
16
+ format = params.delete(:format)
17
+ raise "Unknown format :#{format}" if format.to_content_type.nil?
18
+ new_paths.each{ |path| paths << { :format => format, :to => path }}
19
+ end
20
+
21
+ def initialize(*args)
22
+ @name = args.shift || :up
23
+ @paths = []
24
+ @app = nil
25
+ @pusher = Scales::Worker::Pusher.new
26
+
27
+ desc "Scale up task" unless ::Rake.application.last_comment
28
+ task name do
29
+ RakeFileUtils.send(:verbose, verbose) do
30
+ @app = Up.application.initialize_environment!
31
+
32
+ yield self if block_given?
33
+
34
+ @pusher.push!(@paths)
35
+ end
36
+ end
37
+ end
38
+
39
+ class << self
40
+ @@application = Worker::Application::Rails
41
+
42
+ def application
43
+ @@application
44
+ end
45
+
46
+ def application=(application)
47
+ @@application = application
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ require 'scales-worker'
2
+
3
+ Scales::Up.application = Scales::Worker::Application::Rails
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scales-worker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.beta.1
5
+ prerelease: 6
6
+ platform: ruby
7
+ authors:
8
+ - Thomas Fankhauser
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: scales-core
16
+ requirement: &70223081169640 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - =
20
+ - !ruby/object:Gem::Version
21
+ version: 0.0.1.beta.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70223081169640
25
+ - !ruby/object:Gem::Dependency
26
+ name: rails
27
+ requirement: &70223081168560 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 3.2.6
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70223081168560
36
+ - !ruby/object:Gem::Dependency
37
+ name: sqlite3
38
+ requirement: &70223081167480 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 1.3.6
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70223081167480
47
+ - !ruby/object:Gem::Dependency
48
+ name: rspec
49
+ requirement: &70223081166840 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '2.11'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70223081166840
58
+ - !ruby/object:Gem::Dependency
59
+ name: rake
60
+ requirement: &70223081166000 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: 0.9.2.2
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: *70223081166000
69
+ - !ruby/object:Gem::Dependency
70
+ name: nokogiri
71
+ requirement: &70223081165260 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: 1.5.5
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *70223081165260
80
+ - !ruby/object:Gem::Dependency
81
+ name: jsonpath
82
+ requirement: &70223081164100 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: 0.5.0
88
+ type: :runtime
89
+ prerelease: false
90
+ version_requirements: *70223081164100
91
+ description: Super Scale Caching Framework - Worker
92
+ email:
93
+ - tommylefunk@googlemail.com
94
+ executables:
95
+ - scales-worker
96
+ extensions: []
97
+ extra_rdoc_files: []
98
+ files:
99
+ - LICENSE
100
+ - lib/scales/up/rails.rb
101
+ - lib/scales/up.rb
102
+ - lib/scales-worker/application/rails.rb
103
+ - lib/scales-worker/application.rb
104
+ - lib/scales-worker/base.rb
105
+ - lib/scales-worker/boot/autoload.rb
106
+ - lib/scales-worker/boot/initializers/jsonpath.rb
107
+ - lib/scales-worker/boot/initializers/nokogiri.rb
108
+ - lib/scales-worker/boot/initializers/rake.rb
109
+ - lib/scales-worker/cache/destroy.rb
110
+ - lib/scales-worker/cache/html.rb
111
+ - lib/scales-worker/cache/json.rb
112
+ - lib/scales-worker/cache/push.rb
113
+ - lib/scales-worker/cache/update.rb
114
+ - lib/scales-worker/cache/xml.rb
115
+ - lib/scales-worker/cache.rb
116
+ - lib/scales-worker/job.rb
117
+ - lib/scales-worker/path.rb
118
+ - lib/scales-worker/pusher.rb
119
+ - lib/scales-worker/response.rb
120
+ - lib/scales-worker/status.rb
121
+ - lib/scales-worker/version.rb
122
+ - lib/scales-worker/worker.rb
123
+ - lib/scales-worker.rb
124
+ - !binary |-
125
+ YmluL3NjYWxlcy13b3JrZXI=
126
+ homepage: http://itscales.org
127
+ licenses: []
128
+ post_install_message:
129
+ rdoc_options: []
130
+ require_paths:
131
+ - lib
132
+ required_ruby_version: !ruby/object:Gem::Requirement
133
+ none: false
134
+ requirements:
135
+ - - ! '>='
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ none: false
140
+ requirements:
141
+ - - ! '>'
142
+ - !ruby/object:Gem::Version
143
+ version: 1.3.1
144
+ requirements: []
145
+ rubyforge_project:
146
+ rubygems_version: 1.8.11
147
+ signing_key:
148
+ specification_version: 3
149
+ summary: Launches an instance of a rack app like Rails and processes jobs from the
150
+ request queue.
151
+ test_files: []