woodhouse 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.travis.yml +5 -0
- data/Gemfile +7 -0
- data/Guardfile +4 -0
- data/MIT-LICENSE +21 -0
- data/PROGRESS-NOTES.txt +5 -0
- data/README.markdown +152 -0
- data/Rakefile +35 -0
- data/THOUGHTS +84 -0
- data/doc/example/script-woodhouse +9 -0
- data/doc/example/woodhouse-initializer.rb +12 -0
- data/lib/generators/woodhouse_generator.rb +38 -0
- data/lib/woodhouse/dispatcher.rb +37 -0
- data/lib/woodhouse/dispatchers/bunny_dispatcher.rb +48 -0
- data/lib/woodhouse/dispatchers/common_amqp_dispatcher.rb +28 -0
- data/lib/woodhouse/dispatchers/hot_bunnies_dispatcher.rb +48 -0
- data/lib/woodhouse/dispatchers/local_dispatcher.rb +13 -0
- data/lib/woodhouse/dispatchers/local_pool_dispatcher.rb +25 -0
- data/lib/woodhouse/dispatchers.rb +19 -0
- data/lib/woodhouse/extension.rb +24 -0
- data/lib/woodhouse/extensions/new_relic/instrumentation_middleware.rb +10 -0
- data/lib/woodhouse/extensions/new_relic.rb +23 -0
- data/lib/woodhouse/extensions/progress.rb +165 -0
- data/lib/woodhouse/job.rb +76 -0
- data/lib/woodhouse/job_execution.rb +60 -0
- data/lib/woodhouse/layout.rb +290 -0
- data/lib/woodhouse/layout_builder.rb +55 -0
- data/lib/woodhouse/layout_serializer.rb +82 -0
- data/lib/woodhouse/middleware/airbrake_exceptions.rb +12 -0
- data/lib/woodhouse/middleware/assign_logger.rb +10 -0
- data/lib/woodhouse/middleware/log_dispatch.rb +21 -0
- data/lib/woodhouse/middleware/log_jobs.rb +22 -0
- data/lib/woodhouse/middleware.rb +16 -0
- data/lib/woodhouse/middleware_stack.rb +35 -0
- data/lib/woodhouse/mixin_registry.rb +27 -0
- data/lib/woodhouse/node_configuration.rb +80 -0
- data/lib/woodhouse/process.rb +41 -0
- data/lib/woodhouse/queue_criteria.rb +52 -0
- data/lib/woodhouse/rails.rb +46 -0
- data/lib/woodhouse/rails2.rb +21 -0
- data/lib/woodhouse/registry.rb +12 -0
- data/lib/woodhouse/runner.rb +60 -0
- data/lib/woodhouse/runners/bunny_runner.rb +60 -0
- data/lib/woodhouse/runners/dummy_runner.rb +11 -0
- data/lib/woodhouse/runners/hot_bunnies_runner.rb +79 -0
- data/lib/woodhouse/runners.rb +16 -0
- data/lib/woodhouse/scheduler.rb +113 -0
- data/lib/woodhouse/server.rb +80 -0
- data/lib/woodhouse/trigger_set.rb +19 -0
- data/lib/woodhouse/version.rb +3 -0
- data/lib/woodhouse/worker.rb +86 -0
- data/lib/woodhouse.rb +99 -0
- data/spec/integration/bunny_worker_process_spec.rb +32 -0
- data/spec/layout_builder_spec.rb +55 -0
- data/spec/layout_spec.rb +143 -0
- data/spec/middleware_stack_spec.rb +56 -0
- data/spec/mixin_registry_spec.rb +15 -0
- data/spec/node_configuration_spec.rb +22 -0
- data/spec/progress_spec.rb +40 -0
- data/spec/queue_criteria_spec.rb +11 -0
- data/spec/scheduler_spec.rb +41 -0
- data/spec/server_spec.rb +72 -0
- data/spec/shared_contexts.rb +70 -0
- data/spec/worker_spec.rb +28 -0
- data/woodhouse.gemspec +37 -0
- metadata +285 -0
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'woodhouse'
|
2
|
+
require 'json'
|
3
|
+
require 'digest/sha1'
|
4
|
+
|
5
|
+
module Woodhouse::Progress
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
attr_accessor :client
|
10
|
+
|
11
|
+
def install_extension(configuration, opts = {}, &blk)
|
12
|
+
install!(configuration)
|
13
|
+
end
|
14
|
+
|
15
|
+
def install!(configuration = Woodhouse.global_configuration)
|
16
|
+
self.client = Woodhouse::Progress::BunnyProgressClient
|
17
|
+
configuration.runner_middleware << Woodhouse::Progress::InjectProgress
|
18
|
+
end
|
19
|
+
|
20
|
+
def pull(job_id)
|
21
|
+
client.new(Woodhouse.global_configuration).pull(job_id)
|
22
|
+
end
|
23
|
+
|
24
|
+
def pull_raw(job_id)
|
25
|
+
client.new(Woodhouse.global_configuration).pull_raw(job_id)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
class ProgressClient
|
31
|
+
attr_accessor :config
|
32
|
+
|
33
|
+
def initialize(config)
|
34
|
+
self.config = config
|
35
|
+
end
|
36
|
+
|
37
|
+
def pull(job_id)
|
38
|
+
progress = pull_raw(job_id)
|
39
|
+
if progress
|
40
|
+
JSON.parse(progress)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def pull_raw(job_id)
|
45
|
+
pull_progress(job_id)
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def pull_progress(job_id)
|
51
|
+
raise NotImplementedError
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
class BunnyProgressClient < ProgressClient
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
def pull_progress(job_id)
|
61
|
+
bunny = Bunny.new(config.server_info)
|
62
|
+
|
63
|
+
bunny.start
|
64
|
+
begin
|
65
|
+
channel = bunny.create_channel
|
66
|
+
exchange = channel.direct("woodhouse.progress")
|
67
|
+
queue = channel.queue(job_id, :durable => true)
|
68
|
+
queue.bind(exchange, :routing_key => job_id)
|
69
|
+
payload = nil
|
70
|
+
queue.message_count.times do
|
71
|
+
_, _, next_payload = queue.pop
|
72
|
+
payload = next_payload if next_payload
|
73
|
+
end
|
74
|
+
payload
|
75
|
+
ensure
|
76
|
+
bunny.stop
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
class StatusTicker
|
84
|
+
attr_accessor :top
|
85
|
+
attr_accessor :current
|
86
|
+
attr_accessor :status
|
87
|
+
|
88
|
+
def initialize(job, name, keyw = {})
|
89
|
+
self.job = job
|
90
|
+
self.name = name
|
91
|
+
self.top = keyw[:top]
|
92
|
+
self.current = keyw.fetch(:start, 0)
|
93
|
+
self.status = keyw[:status]
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_hash
|
97
|
+
{ name => count_attributes.merge( "status" => status ) }
|
98
|
+
end
|
99
|
+
|
100
|
+
def count_attributes
|
101
|
+
{ "current" => current }.tap do |h|
|
102
|
+
h["top"] = top if top
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def tick(keyw = {})
|
107
|
+
status = keyw[:status]
|
108
|
+
count = keyw[:count]
|
109
|
+
by = keyw[:by] || 1
|
110
|
+
new_top = keyw[:top]
|
111
|
+
|
112
|
+
if status
|
113
|
+
self.status = status
|
114
|
+
end
|
115
|
+
|
116
|
+
if current
|
117
|
+
next_tick = count || current + by
|
118
|
+
|
119
|
+
self.current = next_tick
|
120
|
+
end
|
121
|
+
|
122
|
+
self.top = new_top if new_top
|
123
|
+
|
124
|
+
job.update_progress(to_hash)
|
125
|
+
end
|
126
|
+
|
127
|
+
alias call tick
|
128
|
+
|
129
|
+
protected
|
130
|
+
|
131
|
+
attr_accessor :job, :name
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
module JobWithProgress
|
136
|
+
|
137
|
+
attr_accessor :progress_sink
|
138
|
+
|
139
|
+
def status_ticker(name, keyw = {})
|
140
|
+
StatusTicker.new(self, name, keyw)
|
141
|
+
end
|
142
|
+
|
143
|
+
def update_progress(data)
|
144
|
+
job = self
|
145
|
+
Celluloid::Future.new { progress_sink.update_job(job, data) }
|
146
|
+
end
|
147
|
+
|
148
|
+
def progress_sink
|
149
|
+
@progress_sink ||= Woodhouse
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
class InjectProgress < Woodhouse::Middleware
|
155
|
+
|
156
|
+
def call(job, worker)
|
157
|
+
job.extend JobWithProgress
|
158
|
+
yield job, worker
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
Woodhouse::Extension.register :progress, Woodhouse::Progress
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
class Woodhouse::Job
|
5
|
+
attr_accessor :worker_class_name, :job_method, :arguments, :payload
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def_delegators :arguments, :each
|
9
|
+
|
10
|
+
def initialize(class_name = nil, method = nil, args = nil)
|
11
|
+
self.worker_class_name = class_name
|
12
|
+
self.job_method = method
|
13
|
+
self.arguments = args
|
14
|
+
unless arguments["_id"]
|
15
|
+
arguments["_id"] = generate_id
|
16
|
+
end
|
17
|
+
if arguments["payload"]
|
18
|
+
self.payload = arguments.delete("payload")
|
19
|
+
end
|
20
|
+
yield self if block_given?
|
21
|
+
end
|
22
|
+
|
23
|
+
def job_id
|
24
|
+
arguments["_id"]
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
{
|
29
|
+
"worker_class_name" => worker_class_name,
|
30
|
+
"job_method" => job_method,
|
31
|
+
}.merge(arguments)
|
32
|
+
end
|
33
|
+
|
34
|
+
def job_method=(value)
|
35
|
+
@job_method = value ? value.to_sym : nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def arguments=(h)
|
39
|
+
@arguments = (h || {}).inject({}){|args,(k,v)|
|
40
|
+
args[k.to_s] = v.to_s
|
41
|
+
args
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def [](key)
|
46
|
+
arguments[key.to_s]
|
47
|
+
end
|
48
|
+
|
49
|
+
def maybe(meth, *args, &blk)
|
50
|
+
if respond_to?(meth)
|
51
|
+
send(meth, *args, &blk)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# TODO: copypasted from Woodhouse::Layout::Worker. Fix that
|
56
|
+
def exchange_name
|
57
|
+
"#{worker_class_name}_#{job_method}".downcase
|
58
|
+
end
|
59
|
+
|
60
|
+
def queue_name
|
61
|
+
exchange_name
|
62
|
+
end
|
63
|
+
|
64
|
+
def describe
|
65
|
+
"#{worker_class_name}##{job_method}(#{arguments.inspect})"
|
66
|
+
end
|
67
|
+
|
68
|
+
def generate_id
|
69
|
+
SecureRandom.hex(16)
|
70
|
+
end
|
71
|
+
|
72
|
+
def payload
|
73
|
+
@payload || " "
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class Woodhouse::JobExecution
|
2
|
+
|
3
|
+
class << self
|
4
|
+
attr_accessor :fatal_error_proc
|
5
|
+
end
|
6
|
+
|
7
|
+
memory_error_rx = /((OutOf|NoMemory)Error|Java heap space)/
|
8
|
+
self.fatal_error_proc = lambda do |err|
|
9
|
+
err.class.name =~ memory_error_rx or err.message =~ memory_error_rx
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(config, job)
|
13
|
+
@config = config
|
14
|
+
@job = job
|
15
|
+
end
|
16
|
+
|
17
|
+
# Looks up the correct worker class for a job and executes it, running it
|
18
|
+
# through the runner middleware stack first. Returns true if the job finishes
|
19
|
+
# without an exception, false otherwise.
|
20
|
+
#
|
21
|
+
# If you need to keep track of exceptions raised by jobs, add middleware to
|
22
|
+
# handle them, like Woodhouse::Middleware::AirbrakeExceptions.
|
23
|
+
def execute
|
24
|
+
worker = @config.registry[@job.worker_class_name]
|
25
|
+
unless worker
|
26
|
+
raise Woodhouse::WorkerNotFoundError, "couldn't find job class #{@job.worker_class_name}"
|
27
|
+
end
|
28
|
+
work_object = worker.new
|
29
|
+
begin
|
30
|
+
@config.runner_middleware.call(@job, work_object) {|job, work_object|
|
31
|
+
work_object.send(job.job_method, job)
|
32
|
+
}
|
33
|
+
return true
|
34
|
+
rescue Woodhouse::FatalError
|
35
|
+
raise
|
36
|
+
rescue => err
|
37
|
+
if fatal_error?(err)
|
38
|
+
raise err
|
39
|
+
else
|
40
|
+
# Ignore the exception
|
41
|
+
return false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# TODO: lots of similar methods scattered around. Should refactor.
|
49
|
+
def symbolize_keys(hash)
|
50
|
+
hash.inject({}) {|h,(k,v)|
|
51
|
+
h[k.to_sym] = v
|
52
|
+
h
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def fatal_error?(err)
|
57
|
+
self.class.fatal_error_proc.call(err)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,290 @@
|
|
1
|
+
module Woodhouse
|
2
|
+
|
3
|
+
#
|
4
|
+
# A Layout describes the configuration of a set of Woodhouse Server instances.
|
5
|
+
# Each Server runs all of the workers assigned to a single Node.
|
6
|
+
#
|
7
|
+
# Layouts and their contents (Node and Worker instances) are all plain data,
|
8
|
+
# suitable to being serialized, saved out, passed around, etc.
|
9
|
+
#
|
10
|
+
# Woodhouse clients do not need to know anything about the Layout to dispatch
|
11
|
+
# jobs, but servers rely on the Layout to know which jobs to serve. The basic
|
12
|
+
# process of setting up a Woodhouse server is to create a layout with one or
|
13
|
+
# more nodes and then pass it to Woodhouse::Server to serve.
|
14
|
+
#
|
15
|
+
# There is a default layout suitable for many applications, available as
|
16
|
+
# Woodhouse::Layout.default. It has a single node named :default, which has
|
17
|
+
# the default node configuration -- one worker for every job. If you do not
|
18
|
+
# need to distribute different sets of jobs to different workers, the default
|
19
|
+
# layout should serve you.
|
20
|
+
#
|
21
|
+
# TODO: A nicer DSL for creating and tweaking Layouts.
|
22
|
+
#
|
23
|
+
class Layout
|
24
|
+
include Woodhouse::Util
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@nodes = []
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns a frozen list of the nodes assigned to this layout.
|
31
|
+
def nodes
|
32
|
+
@nodes.frozen? ? @nodes : @nodes.dup.freeze
|
33
|
+
end
|
34
|
+
|
35
|
+
# Adds a Node to this layout. If +node+ is a Symbol, a Node will be
|
36
|
+
# automatically created with that name.
|
37
|
+
#
|
38
|
+
# # Example:
|
39
|
+
#
|
40
|
+
# layout.add_node Woodhouse::Layout::Node.new(:isis)
|
41
|
+
#
|
42
|
+
# # Is equivalent to
|
43
|
+
#
|
44
|
+
# layout.add_node :isis
|
45
|
+
#
|
46
|
+
def add_node(node)
|
47
|
+
if node.respond_to?(:to_sym)
|
48
|
+
node = Woodhouse::Layout::Node.new(node.to_sym)
|
49
|
+
end
|
50
|
+
expect_arg :node, Woodhouse::Layout::Node, node
|
51
|
+
@nodes << node
|
52
|
+
node
|
53
|
+
end
|
54
|
+
|
55
|
+
# Looks up a Node by name and returns it.
|
56
|
+
def node(name)
|
57
|
+
name = name.to_sym
|
58
|
+
@nodes.detect{|node|
|
59
|
+
node.name == name
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns a frozen copy of this Layout and all of its child Node and
|
64
|
+
# Worker objects. Woodhouse::Server always takes a frozen copy of the
|
65
|
+
# layout it is given. It is thus safe to modify the same layout
|
66
|
+
# subsequently, and the changes only take effect when the layout is
|
67
|
+
# passed to the server again and Woodhouse::Server#reload is called.
|
68
|
+
def frozen_clone
|
69
|
+
clone.tap do |cloned|
|
70
|
+
cloned.nodes = @nodes.map{|node| node.frozen_clone }.freeze
|
71
|
+
cloned.freeze
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Returns a set of Changes necessary to move from +other_layout+ to this
|
76
|
+
# layout. This is used to permit live reconfiguration of servers by only
|
77
|
+
# spinning up and down nodes/workers which have changed.
|
78
|
+
def changes_from(other_layout, node)
|
79
|
+
Woodhouse::Layout::Changes.new(self, other_layout, node)
|
80
|
+
end
|
81
|
+
|
82
|
+
def dump(serializer = Woodhouse::LayoutSerializer)
|
83
|
+
serializer.dump(self)
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.load(dumped, serializer = Woodhouse::LayoutSerializer)
|
87
|
+
serializer.load(dumped)
|
88
|
+
end
|
89
|
+
|
90
|
+
# The default layout, for convenience purposes. Has one node +:default+,
|
91
|
+
# which has the default configuration (see Woodhouse::Layout::Node#default_configuration!)
|
92
|
+
def self.default
|
93
|
+
new.tap do |layout|
|
94
|
+
layout.add_node :default
|
95
|
+
layout.node(:default).default_configuration!(Woodhouse.global_configuration)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
protected
|
100
|
+
|
101
|
+
attr_writer :nodes
|
102
|
+
|
103
|
+
#
|
104
|
+
# A Node describes the set of workers present on a single Server.
|
105
|
+
#
|
106
|
+
# More information about Woodhouse's layout system can be found in the
|
107
|
+
# documentation for Woodhouse::Layout.
|
108
|
+
#
|
109
|
+
class Node
|
110
|
+
include Woodhouse::Util
|
111
|
+
|
112
|
+
attr_reader :name
|
113
|
+
|
114
|
+
def initialize(name)
|
115
|
+
@name = name.to_sym
|
116
|
+
@workers = []
|
117
|
+
end
|
118
|
+
|
119
|
+
# Returns a frozen list of workers assigned to this node.
|
120
|
+
def workers
|
121
|
+
@workers.frozen? ? @workers : @workers.dup.freeze
|
122
|
+
end
|
123
|
+
|
124
|
+
# Adds a Worker to this node.
|
125
|
+
def add_worker(worker)
|
126
|
+
expect_arg :worker, Woodhouse::Layout::Worker, worker
|
127
|
+
@workers << worker
|
128
|
+
end
|
129
|
+
|
130
|
+
def remove_worker(worker)
|
131
|
+
expect_arg :worker, Woodhouse::Layout::Worker, worker
|
132
|
+
@workers.delete(worker)
|
133
|
+
end
|
134
|
+
|
135
|
+
def worker_for_job(job)
|
136
|
+
@workers.detect {|worker|
|
137
|
+
worker.accepts_job?(job)
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
def clear
|
142
|
+
@workers.clear
|
143
|
+
end
|
144
|
+
|
145
|
+
# Configures this node with one worker per job (jobs obtained
|
146
|
+
# from Registry#each). The +default_threads+ value of the given
|
147
|
+
# +config+ is used to determine how many threads should be
|
148
|
+
# assigned to each worker.
|
149
|
+
def default_configuration!(config, options = {})
|
150
|
+
options[:threads] ||= config.default_threads
|
151
|
+
config.registry.each do |name, klass|
|
152
|
+
klass.public_instance_methods(false).each do |method|
|
153
|
+
add_worker Woodhouse::Layout::Worker.new(name, method, options)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Used by Layout#frozen_clone
|
159
|
+
def frozen_clone # :nodoc:
|
160
|
+
clone.tap do |cloned|
|
161
|
+
cloned.workers = @workers.map{|worker| worker.frozen_clone }.freeze
|
162
|
+
cloned.freeze
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
protected
|
167
|
+
|
168
|
+
attr_writer :workers
|
169
|
+
end
|
170
|
+
|
171
|
+
#
|
172
|
+
# A Worker describes a single job that is performed on a Server.
|
173
|
+
# One or more Runner actors are created for every Worker in a Node.
|
174
|
+
#
|
175
|
+
# Any Worker has three parameters used to route jobs to it:
|
176
|
+
#
|
177
|
+
# +worker_class_name+::
|
178
|
+
# This is generally a class name. It's looked up
|
179
|
+
# in a Registry and used to instantiate a job object.
|
180
|
+
# +job_method+::
|
181
|
+
# This is a method on the object called up with +worker_class_name+.
|
182
|
+
# +criteria+::
|
183
|
+
# A hash of values (actually, a QueueCriteria object) used
|
184
|
+
# to filter only specific jobs to this worker. When a job is dispatched,
|
185
|
+
# its +arguments+ are compared with a worker's +criteria+. This is
|
186
|
+
# done via an AMQP headers exchange (TODO: need to have a central document
|
187
|
+
# to reference on how Woodhouse uses AMQP and jobs are dispatched)
|
188
|
+
#
|
189
|
+
class Worker
|
190
|
+
attr_reader :worker_class_name, :job_method, :threads, :criteria
|
191
|
+
|
192
|
+
def initialize(worker_class_name, job_method, opts = {})
|
193
|
+
opts = opts.clone
|
194
|
+
self.worker_class_name = worker_class_name
|
195
|
+
self.job_method = job_method
|
196
|
+
self.threads = opts.delete(:threads) || 1
|
197
|
+
self.criteria = opts.delete(:only)
|
198
|
+
unless opts.keys.empty?
|
199
|
+
raise ArgumentError, "unknown option keys: #{opts.keys.inspect}"
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def exchange_name
|
204
|
+
"#{worker_class_name}_#{job_method}".downcase
|
205
|
+
end
|
206
|
+
|
207
|
+
def queue_name
|
208
|
+
exchange_name + criteria.queue_key
|
209
|
+
end
|
210
|
+
|
211
|
+
def worker_class_name=(value)
|
212
|
+
@worker_class_name = value.to_sym
|
213
|
+
end
|
214
|
+
|
215
|
+
def job_method=(value)
|
216
|
+
@job_method = value.to_sym
|
217
|
+
end
|
218
|
+
|
219
|
+
def threads=(value)
|
220
|
+
@threads = value.to_i
|
221
|
+
end
|
222
|
+
|
223
|
+
def criteria=(value)
|
224
|
+
@criteria = Woodhouse::QueueCriteria.new(value).freeze
|
225
|
+
end
|
226
|
+
|
227
|
+
def frozen_clone
|
228
|
+
clone.freeze
|
229
|
+
end
|
230
|
+
|
231
|
+
def describe
|
232
|
+
"#@worker_class_name##@job_method(#{@criteria.describe})"
|
233
|
+
end
|
234
|
+
|
235
|
+
def accepts_job?(job)
|
236
|
+
criteria.matches?(job.arguments)
|
237
|
+
end
|
238
|
+
|
239
|
+
# TODO: want to recognize increases and decreases in numbers of
|
240
|
+
# threads and make minimal changes
|
241
|
+
def ==(other)
|
242
|
+
[worker_class_name, job_method,
|
243
|
+
threads, criteria] ==
|
244
|
+
[other.worker_class_name, other.job_method,
|
245
|
+
other.threads, other.criteria]
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
|
250
|
+
#
|
251
|
+
# A diff between two Layouts, used to determine what workers need to be
|
252
|
+
# spun up and down when a layout change is sent to a Server.
|
253
|
+
#
|
254
|
+
class Changes
|
255
|
+
|
256
|
+
def initialize(new_layout, old_layout, node_name)
|
257
|
+
@new_layout = new_layout
|
258
|
+
@new_node = @new_layout && @new_layout.node(node_name)
|
259
|
+
@old_layout = old_layout
|
260
|
+
@old_node = @old_layout && @old_layout.node(node_name)
|
261
|
+
@node_name = node_name
|
262
|
+
end
|
263
|
+
|
264
|
+
def adds
|
265
|
+
new_workers.reject{|worker|
|
266
|
+
old_workers.member? worker
|
267
|
+
}
|
268
|
+
end
|
269
|
+
|
270
|
+
def drops
|
271
|
+
old_workers.reject{|worker|
|
272
|
+
new_workers.member? worker
|
273
|
+
}
|
274
|
+
end
|
275
|
+
|
276
|
+
private
|
277
|
+
|
278
|
+
def old_workers
|
279
|
+
@old_workers ||= @old_node ? @old_node.workers : []
|
280
|
+
end
|
281
|
+
|
282
|
+
def new_workers
|
283
|
+
@new_workers ||= @new_node ? @new_node.workers : []
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
|
290
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
class Woodhouse::LayoutBuilder
|
2
|
+
|
3
|
+
attr_reader :layout
|
4
|
+
|
5
|
+
class NodeBuilder
|
6
|
+
|
7
|
+
def initialize(config, node)
|
8
|
+
@config = config
|
9
|
+
@node = node
|
10
|
+
end
|
11
|
+
|
12
|
+
def all_workers(options = {})
|
13
|
+
@node.default_configuration! @config, options
|
14
|
+
end
|
15
|
+
|
16
|
+
def add(class_name, job_method, opts = {})
|
17
|
+
if job_method.kind_of?(Hash)
|
18
|
+
# Two-argument invocation
|
19
|
+
opts = job_method
|
20
|
+
job_method = nil
|
21
|
+
methods = @config.registry[class_name].public_instance_methods(false)
|
22
|
+
else
|
23
|
+
methods = [job_method]
|
24
|
+
end
|
25
|
+
remove(class_name, job_method, opts.empty? ? nil : opts)
|
26
|
+
methods.each do |method|
|
27
|
+
@node.add_worker Woodhouse::Layout::Worker.new(class_name, method, opts)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def remove(class_name, job_method = nil, opts = nil)
|
32
|
+
@node.workers.select{|worker|
|
33
|
+
worker.worker_class_name == class_name &&
|
34
|
+
(job_method.nil? || worker.job_method == job_method) &&
|
35
|
+
(opts.nil? || worker.criteria.criteria == opts[:only])
|
36
|
+
}.each do |worker|
|
37
|
+
@node.remove_worker(worker)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(config, layout = nil)
|
44
|
+
@config = config
|
45
|
+
@layout = layout || Woodhouse::Layout.new
|
46
|
+
@nodes ||= {}
|
47
|
+
yield self if block_given?
|
48
|
+
end
|
49
|
+
|
50
|
+
def node(name)
|
51
|
+
@layout.node(name) || @layout.add_node(name)
|
52
|
+
yield(@nodes[name] ||= NodeBuilder.new(@config, @layout.node(name)))
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|