woodhouse 0.1.1
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/.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
|