documentcloud-cloud-crowd 0.1.0 → 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/README +8 -8
- data/cloud-crowd.gemspec +8 -8
- data/config/config.example.ru +8 -2
- data/config/config.example.yml +6 -15
- data/examples/process_pdfs_example.rb +1 -1
- data/examples/word_count_example.rb +1 -0
- data/lib/cloud-crowd.rb +6 -5
- data/lib/cloud_crowd/action.rb +11 -7
- data/lib/cloud_crowd/asset_store/filesystem_store.rb +5 -0
- data/lib/cloud_crowd/asset_store/s3_store.rb +7 -3
- data/lib/cloud_crowd/asset_store.rb +1 -1
- data/lib/cloud_crowd/command_line.rb +14 -53
- data/lib/cloud_crowd/exceptions.rb +4 -0
- data/lib/cloud_crowd/helpers/authorization.rb +2 -2
- data/lib/cloud_crowd/helpers/resources.rb +0 -20
- data/lib/cloud_crowd/models/job.rb +25 -26
- data/lib/cloud_crowd/models/node_record.rb +81 -0
- data/lib/cloud_crowd/models/work_unit.rb +70 -30
- data/lib/cloud_crowd/models.rb +1 -1
- data/lib/cloud_crowd/node.rb +87 -0
- data/lib/cloud_crowd/schema.rb +19 -16
- data/lib/cloud_crowd/{app.rb → server.rb} +25 -30
- data/lib/cloud_crowd/worker.rb +50 -74
- data/public/css/admin_console.css +26 -14
- data/public/images/server.png +0 -0
- data/public/js/admin_console.js +45 -18
- data/test/acceptance/test_failing_work_units.rb +1 -1
- data/test/acceptance/{test_app.rb → test_server.rb} +15 -15
- data/test/acceptance/test_word_count.rb +3 -9
- data/test/blueprints.rb +0 -1
- data/test/config/config.ru +1 -1
- data/test/config/config.yml +1 -3
- data/test/unit/test_configuration.rb +1 -1
- data/test/unit/test_job.rb +1 -0
- data/test/unit/test_work_unit.rb +2 -4
- data/views/index.erb +13 -8
- metadata +9 -9
- data/lib/cloud_crowd/daemon.rb +0 -95
- data/lib/cloud_crowd/models/worker_record.rb +0 -61
- data/lib/cloud_crowd/runner.rb +0 -15
@@ -134,40 +134,43 @@ body {
|
|
134
134
|
}
|
135
135
|
#sidebar_header {
|
136
136
|
position: absolute;
|
137
|
+
width: 250px;
|
137
138
|
top: 5px; left: 8px;
|
138
139
|
color: #404040;
|
139
140
|
text-shadow: 0px 1px 1px #eee;
|
140
141
|
}
|
141
|
-
#sidebar_header.
|
142
|
-
#sidebar_header .
|
142
|
+
#sidebar_header.no_nodes .no_nodes,
|
143
|
+
#sidebar_header .has_nodes {
|
143
144
|
display: block;
|
144
145
|
}
|
145
|
-
#sidebar_header .
|
146
|
-
#sidebar_header.
|
146
|
+
#sidebar_header .no_nodes,
|
147
|
+
#sidebar_header.no_nodes .has_nodes {
|
147
148
|
display: none;
|
148
149
|
}
|
149
|
-
#
|
150
|
+
#nodes {
|
150
151
|
position: absolute;
|
151
152
|
padding: 2px 0;
|
152
153
|
top: 21px; left: 0; bottom: 21px;
|
153
154
|
width: 298px;
|
154
155
|
overflow-y: auto; overflow-x: hidden;
|
155
156
|
}
|
156
|
-
#
|
157
|
+
#nodes .node, #nodes .worker {
|
157
158
|
border: 1px solid transparent;
|
158
159
|
margin: 1px 7px;
|
159
160
|
padding-left: 18px;
|
160
|
-
font-size: 11px;
|
161
|
-
line-height: 22px;
|
162
|
-
background: url(/images/bullet_white.png) no-repeat left center;
|
163
|
-
cursor: pointer;
|
164
161
|
}
|
165
|
-
#
|
166
|
-
|
167
|
-
|
162
|
+
#nodes .node {
|
163
|
+
font-size: 11px;
|
164
|
+
line-height: 22px;
|
165
|
+
background: url(/images/server.png) no-repeat left center;
|
166
|
+
}
|
167
|
+
#nodes .worker {
|
168
|
+
font-size: 10px;
|
169
|
+
line-height: 18px;
|
170
|
+
cursor: pointer;
|
168
171
|
background: url(/images/bullet_green.png) no-repeat left center;
|
169
172
|
}
|
170
|
-
#
|
173
|
+
#nodes .worker:hover {
|
171
174
|
border: 1px solid #aaa;
|
172
175
|
border-radius: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px;
|
173
176
|
background-color: #ccc;
|
@@ -216,6 +219,15 @@ body {
|
|
216
219
|
text-shadow: 0px 1px 1px #eee;
|
217
220
|
margin-bottom: 10px;
|
218
221
|
}
|
222
|
+
.legend_box {
|
223
|
+
display: inline-block;
|
224
|
+
width: 10px; height: 10px;
|
225
|
+
border: 1px solid #bbb;
|
226
|
+
position: relative;
|
227
|
+
top: 1px;
|
228
|
+
margin: 0 1px;
|
229
|
+
background-color: #a1003d;
|
230
|
+
}
|
219
231
|
.graph {
|
220
232
|
height: 150px;
|
221
233
|
}
|
Binary file
|
data/public/js/admin_console.js
CHANGED
@@ -22,26 +22,30 @@ window.Console = {
|
|
22
22
|
|
23
23
|
// All options for drawing the system graphs.
|
24
24
|
GRAPH_OPTIONS : {
|
25
|
-
xaxis
|
26
|
-
yaxis
|
27
|
-
legend
|
28
|
-
grid
|
25
|
+
xaxis : {mode : 'time', timeformat : '%M:%S'},
|
26
|
+
yaxis : {tickDecimals : 0},
|
27
|
+
legend : {show : false},
|
28
|
+
grid : {backgroundColor : '#7f7f7f', color : '#555', tickColor : '#666', borderWidth : 2}
|
29
29
|
},
|
30
30
|
JOBS_COLOR : '#db3a0f',
|
31
|
-
|
31
|
+
NODES_COLOR : '#1870ab',
|
32
|
+
WORKERS_COLOR : '#45a4e5',
|
32
33
|
WORK_UNITS_COLOR : '#ffba14',
|
33
34
|
|
34
35
|
// Starting the console begins polling the server.
|
35
36
|
initialize : function() {
|
36
37
|
this._jobsHistory = [];
|
38
|
+
this._nodesHistory = [];
|
37
39
|
this._workersHistory = [];
|
38
40
|
this._workUnitsHistory = [];
|
39
|
-
this._histories = [this._jobsHistory, this._workersHistory, this._workUnitsHistory];
|
41
|
+
this._histories = [this._jobsHistory, this._nodesHistory, this._workersHistory, this._workUnitsHistory];
|
40
42
|
this._queue = $('#jobs');
|
41
43
|
this._workerInfo = $('#worker_info');
|
42
44
|
this._disconnected = $('#disconnected');
|
43
45
|
$(window).bind('resize', Console.renderGraphs);
|
44
|
-
$('#
|
46
|
+
$('#nodes .worker').live('click', Console.getWorkerInfo);
|
47
|
+
$('#workers_legend').css({background : this.WORKERS_COLOR});
|
48
|
+
$('#nodes_legend').css({background : this.NODES_COLOR});
|
45
49
|
this.getStatus();
|
46
50
|
$.each(this.PRELOAD_IMAGES, function(){ var i = new Image(); i.src = this; });
|
47
51
|
},
|
@@ -51,13 +55,14 @@ window.Console = {
|
|
51
55
|
getStatus : function() {
|
52
56
|
$.ajax({url : '/status', dataType : 'json', success : function(resp) {
|
53
57
|
Console._jobs = resp.jobs;
|
54
|
-
Console.
|
58
|
+
Console._nodes = resp.nodes;
|
55
59
|
Console._workUnitCount = resp.work_unit_count;
|
60
|
+
Console._workerCount = Console.countWorkers();
|
56
61
|
Console.recordDataPoint();
|
57
62
|
if (Console._disconnected.is(':visible')) Console._disconnected.fadeOut(Console.ANIMATION_SPEED);
|
58
63
|
$('#queue').toggleClass('no_jobs', Console._jobs.length <= 0);
|
59
64
|
Console.renderJobs();
|
60
|
-
Console.
|
65
|
+
Console.renderNodes();
|
61
66
|
Console.renderGraphs();
|
62
67
|
setTimeout(Console.getStatus, Console.POLL_INTERVAL);
|
63
68
|
}, error : function(request, status, errorThrown) {
|
@@ -66,6 +71,13 @@ window.Console = {
|
|
66
71
|
}});
|
67
72
|
},
|
68
73
|
|
74
|
+
// Count the total number of workers in the current list of nodes.
|
75
|
+
countWorkers : function() {
|
76
|
+
var sum = 0;
|
77
|
+
for (var i=0; i < this._nodes.length; i++) sum += this._nodes[i].workers.length;
|
78
|
+
return sum;
|
79
|
+
},
|
80
|
+
|
69
81
|
// Render an individual job afresh.
|
70
82
|
renderJob : function(job) {
|
71
83
|
this._queue.append('<div class="job" id="job_' + job.id + '" style="width:' + job.width + '%; background: #' + job.color + ';"><div class="completion ' + (job.percent_complete <= 0 ? 'zero' : '') + '" style="width:' + job.percent_complete + '%;"></div><div class="percent_complete">' + job.percent_complete + '%</div><div class="job_id">#' + job.id + '</div></div>');
|
@@ -105,12 +117,19 @@ window.Console = {
|
|
105
117
|
},
|
106
118
|
|
107
119
|
// Re-render all workers from scratch each time.
|
108
|
-
|
120
|
+
renderNodes : function() {
|
109
121
|
var header = $('#sidebar_header');
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
122
|
+
var nc = this._nodes.length, wc = this._workerCount;
|
123
|
+
$('.has_nodes', header).html(nc + " Node" + (nc != 1 ? 's' : '') + " / " + wc + " Worker" + (wc != 1 ? 's' : ''));
|
124
|
+
header.toggleClass('no_nodes', this._nodes.length <= 0);
|
125
|
+
$('#nodes').html($.map(this._nodes, function(node) {
|
126
|
+
var html = "";
|
127
|
+
html += '<div class="node">' + node.host + '</div>';
|
128
|
+
html += $.map(node.workers, function(pid) {
|
129
|
+
var name = pid + '@' + node.host;
|
130
|
+
return '<div class="worker" rel="' + name + '">' + name + '</div>';
|
131
|
+
}).join('');
|
132
|
+
return html;
|
114
133
|
}).join(''));
|
115
134
|
},
|
116
135
|
|
@@ -118,7 +137,8 @@ window.Console = {
|
|
118
137
|
recordDataPoint : function() {
|
119
138
|
var timestamp = (new Date()).getTime();
|
120
139
|
this._jobsHistory.push([timestamp, this._jobs.length]);
|
121
|
-
this.
|
140
|
+
this._nodesHistory.push([timestamp, this._nodes.length]);
|
141
|
+
this._workersHistory.push([timestamp, this._workerCount]);
|
122
142
|
this._workUnitsHistory.push([timestamp, this._workUnitCount]);
|
123
143
|
$.each(this._histories, function() {
|
124
144
|
if (this.length > Console.MAX_DATA_POINTS) this.shift();
|
@@ -127,9 +147,16 @@ window.Console = {
|
|
127
147
|
|
128
148
|
// Convert our recorded data points into a format Flot can understand.
|
129
149
|
renderGraphs : function() {
|
130
|
-
$.plot($('#
|
131
|
-
|
132
|
-
|
150
|
+
$.plot($('#work_units_graph'), [
|
151
|
+
{label : 'Work Units in Queue', color : Console.WORK_UNITS_COLOR, data : Console._workUnitsHistory}
|
152
|
+
], Console.GRAPH_OPTIONS);
|
153
|
+
$.plot($('#jobs_graph'), [
|
154
|
+
{label : 'Jobs in Queue', color : Console.JOBS_COLOR, data : Console._jobsHistory}
|
155
|
+
], Console.GRAPH_OPTIONS);
|
156
|
+
$.plot($('#workers_graph'), [
|
157
|
+
{label : 'Nodes', color : Console.NODES_COLOR, data : Console._nodesHistory},
|
158
|
+
{label : 'Workers', color : Console.WORKERS_COLOR, data : Console._workersHistory}
|
159
|
+
], Console.GRAPH_OPTIONS);
|
133
160
|
},
|
134
161
|
|
135
162
|
// Request the Worker info from the central server.
|
@@ -4,7 +4,7 @@ require 'test_helper'
|
|
4
4
|
class FailingWorkUnitsTest < Test::Unit::TestCase
|
5
5
|
|
6
6
|
should "retry work units when they fail" do
|
7
|
-
browser = Rack::Test::Session.new(Rack::MockSession.new(CloudCrowd::
|
7
|
+
browser = Rack::Test::Session.new(Rack::MockSession.new(CloudCrowd::Server))
|
8
8
|
|
9
9
|
browser.post '/jobs', :job => {
|
10
10
|
'action' => 'failure_testing',
|
@@ -1,14 +1,14 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class
|
3
|
+
class ServerTest < Test::Unit::TestCase
|
4
4
|
|
5
5
|
include Rack::Test::Methods
|
6
6
|
|
7
7
|
def app
|
8
|
-
CloudCrowd::
|
8
|
+
CloudCrowd::Server
|
9
9
|
end
|
10
10
|
|
11
|
-
context "The CloudCrowd::
|
11
|
+
context "The CloudCrowd::Server (Sinatra)" do
|
12
12
|
|
13
13
|
setup do
|
14
14
|
CloudCrowd::Job.destroy_all
|
@@ -17,7 +17,7 @@ class AppTest < Test::Unit::TestCase
|
|
17
17
|
|
18
18
|
should "be able to render the Operations Center (GET /)" do
|
19
19
|
get '/'
|
20
|
-
assert last_response.body.include? '<div id="
|
20
|
+
assert last_response.body.include? '<div id="nodes">'
|
21
21
|
assert last_response.body.include? '<div id="graphs">'
|
22
22
|
end
|
23
23
|
|
@@ -29,17 +29,17 @@ class AppTest < Test::Unit::TestCase
|
|
29
29
|
assert resp['work_unit_count'] == 2
|
30
30
|
end
|
31
31
|
|
32
|
-
should "be able to check in a worker daemon, and then check out a work unit" do
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
32
|
+
# should "be able to check in a worker daemon, and then check out a work unit" do
|
33
|
+
# put '/worker', :name => '101@localhost', :thread_status => 'sleeping'
|
34
|
+
# assert last_response.successful? && last_response.empty?
|
35
|
+
# post '/work', :worker_name => '101@localhost', :worker_actions => 'graphics_magick'
|
36
|
+
# checked_out = JSON.parse(last_response.body)
|
37
|
+
# assert checked_out['action'] == 'graphics_magick'
|
38
|
+
# assert checked_out['attempts'] == 0
|
39
|
+
# assert checked_out['status'] == CloudCrowd::PROCESSING
|
40
|
+
# status_check = JSON.parse(get('/worker/101@localhost').body)
|
41
|
+
# assert checked_out == status_check
|
42
|
+
# end
|
43
43
|
|
44
44
|
should "have a heartbeat" do
|
45
45
|
assert get('/heartbeat').body == 'buh-bump'
|
@@ -6,7 +6,7 @@ class WordCountTest < Test::Unit::TestCase
|
|
6
6
|
|
7
7
|
setup do
|
8
8
|
@asset_store = AssetStore.new
|
9
|
-
@browser = Rack::Test::Session.new(Rack::MockSession.new(CloudCrowd::
|
9
|
+
@browser = Rack::Test::Session.new(Rack::MockSession.new(CloudCrowd::Server))
|
10
10
|
@browser.put('/worker', :name => 'test_worker', :thread_status => 'sleeping')
|
11
11
|
post_job_to_count_words_in_this_file
|
12
12
|
@job_id = JSON.parse(@browser.last_response.body)['id']
|
@@ -24,15 +24,9 @@ class WordCountTest < Test::Unit::TestCase
|
|
24
24
|
end
|
25
25
|
|
26
26
|
should "be able to perform the processing stage of a word_count" do
|
27
|
-
|
28
|
-
assert @browser.last_response.ok?
|
29
|
-
info = JSON.parse(@browser.last_response.body)
|
30
|
-
assert info['status'] == 1
|
31
|
-
assert info['action'] == 'word_count'
|
32
|
-
assert info['input'] == "file://#{File.expand_path(__FILE__)}"
|
33
|
-
action = CloudCrowd.actions['word_count'].new(info['status'], info['input'], info['options'], @asset_store)
|
27
|
+
action = CloudCrowd.actions['word_count'].new(1, "file://#{File.expand_path(__FILE__)}", {}, @asset_store)
|
34
28
|
count = action.process
|
35
|
-
assert count ==
|
29
|
+
assert count == 104
|
36
30
|
end
|
37
31
|
|
38
32
|
end
|
data/test/blueprints.rb
CHANGED
data/test/config/config.ru
CHANGED
data/test/config/config.yml
CHANGED
@@ -5,7 +5,7 @@ class ConfigurationTest < Test::Unit::TestCase
|
|
5
5
|
context "CloudCrowd Configuration" do
|
6
6
|
|
7
7
|
should "have read in config.yml" do
|
8
|
-
assert CloudCrowd.config[:
|
8
|
+
assert CloudCrowd.config[:max_workers] == 10
|
9
9
|
assert CloudCrowd.config[:storage] == 'filesystem'
|
10
10
|
end
|
11
11
|
|
data/test/unit/test_job.rb
CHANGED
@@ -26,6 +26,7 @@ class JobTest < Test::Unit::TestCase
|
|
26
26
|
should "know its completion status" do
|
27
27
|
assert !@job.all_work_units_complete?
|
28
28
|
@unit.update_attributes(:status => CloudCrowd::SUCCEEDED, :output => '{"output":"hello"}')
|
29
|
+
@job.check_for_completion
|
29
30
|
assert @job.reload.all_work_units_complete?
|
30
31
|
assert @job.percent_complete == 100
|
31
32
|
assert @job.outputs == "[\"hello\"]"
|
data/test/unit/test_work_unit.rb
CHANGED
@@ -21,8 +21,6 @@ class WorkUnitTest < Test::Unit::TestCase
|
|
21
21
|
assert @unit.complete?
|
22
22
|
@unit.status = CloudCrowd::FAILED
|
23
23
|
assert @unit.complete?
|
24
|
-
@unit.expects :check_for_job_completion
|
25
|
-
@unit.save
|
26
24
|
end
|
27
25
|
|
28
26
|
should "have JSON that includes job attributes" do
|
@@ -34,10 +32,10 @@ class WorkUnitTest < Test::Unit::TestCase
|
|
34
32
|
end
|
35
33
|
|
36
34
|
should "be able to retry, on failure" do
|
37
|
-
@unit.update_attribute :
|
35
|
+
@unit.update_attribute :worker_pid, 7337
|
38
36
|
assert @unit.attempts == 0
|
39
37
|
@unit.fail('oops', 10)
|
40
|
-
assert @unit.
|
38
|
+
assert @unit.worker_pid == nil
|
41
39
|
assert @unit.attempts == 1
|
42
40
|
assert @unit.processing?
|
43
41
|
@unit.fail('oops again', 10)
|
data/views/index.erb
CHANGED
@@ -36,25 +36,30 @@
|
|
36
36
|
<div id="sidebar_background"></div>
|
37
37
|
<div id="sidebar_bottom" class="sidebar_back"></div>
|
38
38
|
<div id="sidebar_header" class="small_caps">
|
39
|
-
<span class="
|
40
|
-
<span class="
|
39
|
+
<span class="has_nodes"></span>
|
40
|
+
<span class="no_nodes">No Nodes Online…</span>
|
41
41
|
</div>
|
42
|
-
<div id="
|
43
|
-
<%# Render target for workers. %>
|
42
|
+
<div id="nodes">
|
43
|
+
<%# Render target for nodes and workers. %>
|
44
44
|
</div>
|
45
45
|
</div>
|
46
46
|
|
47
47
|
<div id="graphs">
|
48
48
|
<div class="graph_container">
|
49
|
-
<div class="graph_title">Work Units in Queue
|
49
|
+
<div class="graph_title">Work Units in Queue</div>
|
50
50
|
<div id="work_units_graph" class="graph"></div>
|
51
51
|
</div>
|
52
52
|
<div class="graph_container">
|
53
|
-
<div class="graph_title">Jobs in Queue
|
53
|
+
<div class="graph_title">Jobs in Queue</div>
|
54
54
|
<div id="jobs_graph" class="graph"></div>
|
55
55
|
</div>
|
56
56
|
<div class="graph_container">
|
57
|
-
<div class="graph_title">
|
57
|
+
<div class="graph_title">
|
58
|
+
Active Nodes
|
59
|
+
(<span id="nodes_legend" class="legend_box"></span>)
|
60
|
+
and Workers
|
61
|
+
(<span id="workers_legend" class="legend_box"></span>)
|
62
|
+
</div>
|
58
63
|
<div id="workers_graph" class="graph"></div>
|
59
64
|
</div>
|
60
65
|
</div>
|
@@ -67,7 +72,7 @@
|
|
67
72
|
<div id="worker_job_id">job #<span class="job_id"></span> / work unit #<span class="work_unit_id"></span></div>
|
68
73
|
</div>
|
69
74
|
<div id="worker_sleeping">
|
70
|
-
worker
|
75
|
+
worker exiting…
|
71
76
|
</div>
|
72
77
|
</div>
|
73
78
|
</div>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: documentcloud-cloud-crowd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Ashkenas
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-09-
|
12
|
+
date: 2009-09-15 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -63,14 +63,14 @@ dependencies:
|
|
63
63
|
version: 1.10.0
|
64
64
|
version:
|
65
65
|
- !ruby/object:Gem::Dependency
|
66
|
-
name:
|
66
|
+
name: thin
|
67
67
|
type: :runtime
|
68
68
|
version_requirement:
|
69
69
|
version_requirements: !ruby/object:Gem::Requirement
|
70
70
|
requirements:
|
71
71
|
- - ">="
|
72
72
|
- !ruby/object:Gem::Version
|
73
|
-
version: 1.
|
73
|
+
version: 1.2.4
|
74
74
|
version:
|
75
75
|
- !ruby/object:Gem::Dependency
|
76
76
|
name: faker
|
@@ -144,23 +144,22 @@ files:
|
|
144
144
|
- examples/word_count_example.rb
|
145
145
|
- lib/cloud-crowd.rb
|
146
146
|
- lib/cloud_crowd/action.rb
|
147
|
-
- lib/cloud_crowd/app.rb
|
148
147
|
- lib/cloud_crowd/asset_store/filesystem_store.rb
|
149
148
|
- lib/cloud_crowd/asset_store/s3_store.rb
|
150
149
|
- lib/cloud_crowd/asset_store.rb
|
151
150
|
- lib/cloud_crowd/command_line.rb
|
152
|
-
- lib/cloud_crowd/daemon.rb
|
153
151
|
- lib/cloud_crowd/exceptions.rb
|
154
152
|
- lib/cloud_crowd/helpers/authorization.rb
|
155
153
|
- lib/cloud_crowd/helpers/resources.rb
|
156
154
|
- lib/cloud_crowd/helpers.rb
|
157
155
|
- lib/cloud_crowd/inflector.rb
|
158
156
|
- lib/cloud_crowd/models/job.rb
|
157
|
+
- lib/cloud_crowd/models/node_record.rb
|
159
158
|
- lib/cloud_crowd/models/work_unit.rb
|
160
|
-
- lib/cloud_crowd/models/worker_record.rb
|
161
159
|
- lib/cloud_crowd/models.rb
|
162
|
-
- lib/cloud_crowd/
|
160
|
+
- lib/cloud_crowd/node.rb
|
163
161
|
- lib/cloud_crowd/schema.rb
|
162
|
+
- lib/cloud_crowd/server.rb
|
164
163
|
- lib/cloud_crowd/worker.rb
|
165
164
|
- LICENSE
|
166
165
|
- public/css/admin_console.css
|
@@ -171,6 +170,7 @@ files:
|
|
171
170
|
- public/images/header_back.png
|
172
171
|
- public/images/logo.png
|
173
172
|
- public/images/queue_fill.png
|
173
|
+
- public/images/server.png
|
174
174
|
- public/images/server_error.png
|
175
175
|
- public/images/sidebar_bottom.png
|
176
176
|
- public/images/sidebar_top.png
|
@@ -181,7 +181,7 @@ files:
|
|
181
181
|
- public/js/flot.js
|
182
182
|
- public/js/jquery.js
|
183
183
|
- README
|
184
|
-
- test/acceptance/
|
184
|
+
- test/acceptance/test_server.rb
|
185
185
|
- test/acceptance/test_failing_work_units.rb
|
186
186
|
- test/acceptance/test_word_count.rb
|
187
187
|
- test/blueprints.rb
|
data/lib/cloud_crowd/daemon.rb
DELETED
@@ -1,95 +0,0 @@
|
|
1
|
-
CloudCrowd.configure(ENV['CLOUD_CROWD_CONFIG'])
|
2
|
-
|
3
|
-
module CloudCrowd
|
4
|
-
|
5
|
-
# A CloudCrowd::Daemon, started by the Daemons gem, runs a CloudCrowd::Worker in
|
6
|
-
# a loop, continually fetching and processing WorkUnits from the central
|
7
|
-
# server.
|
8
|
-
#
|
9
|
-
# The Daemon backs off and pings the central server less frequently
|
10
|
-
# when there isn't any work to be done, and speeds back up when there is.
|
11
|
-
#
|
12
|
-
# The `crowd` command responds to all the usual methods that the Daemons gem
|
13
|
-
# supports.
|
14
|
-
class Daemon
|
15
|
-
|
16
|
-
# The back-off factor used to slow down requests for new work units
|
17
|
-
# when the queue is empty.
|
18
|
-
WAIT_MULTIPLIER = 1.5
|
19
|
-
|
20
|
-
MIN_WAIT = CloudCrowd.config[:min_worker_wait]
|
21
|
-
MAX_WAIT = CloudCrowd.config[:max_worker_wait]
|
22
|
-
|
23
|
-
def initialize
|
24
|
-
@wait_time = MIN_WAIT
|
25
|
-
@worker = Worker.new
|
26
|
-
Signal.trap('INT') { kill_worker_and_exit }
|
27
|
-
Signal.trap('KILL') { kill_worker_and_exit }
|
28
|
-
Signal.trap('TERM') { kill_worker_and_exit }
|
29
|
-
end
|
30
|
-
|
31
|
-
# Spin up our worker and monitoring threads. The monitor's the boss, and
|
32
|
-
# will feel no compunction in killing the worker thread if necessary.
|
33
|
-
# Check in before starting up. If check in fails, there's no sense in going.
|
34
|
-
def run
|
35
|
-
@worker.check_in('starting')
|
36
|
-
@work_thread = run_worker
|
37
|
-
@monitor_thread = run_monitor
|
38
|
-
@monitor_thread.join
|
39
|
-
end
|
40
|
-
|
41
|
-
|
42
|
-
private
|
43
|
-
|
44
|
-
# Loop forever, fetching WorkUnits and processing them.
|
45
|
-
def run_worker
|
46
|
-
Thread.new do
|
47
|
-
loop do
|
48
|
-
@worker.fetch_work_unit
|
49
|
-
if @worker.has_work?
|
50
|
-
@wait_time = MIN_WAIT
|
51
|
-
while @worker.has_work?
|
52
|
-
@worker.run
|
53
|
-
sleep 0.01 # So as to listen for incoming signals.
|
54
|
-
end
|
55
|
-
else
|
56
|
-
@wait_time = [@wait_time * WAIT_MULTIPLIER, MAX_WAIT].min
|
57
|
-
sleep @wait_time
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
# Checks in to let the central server know it's still alive every
|
64
|
-
# CHECK_IN_INTERVAL seconds. Restarts the work_thread if it has died.
|
65
|
-
def run_monitor
|
66
|
-
Thread.new do
|
67
|
-
sleep Worker::CHECK_IN_INTERVAL
|
68
|
-
loop do
|
69
|
-
@work_thread = run_monitor unless @work_thread.alive? || @exit_started
|
70
|
-
@worker.check_in(@work_thread.status)
|
71
|
-
sleep Worker::CHECK_IN_INTERVAL
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
def running?
|
77
|
-
@work_thread.alive? || @monitor_thread.alive?
|
78
|
-
end
|
79
|
-
|
80
|
-
# At exit, kill the worker thread, gently at first, then forcefully.
|
81
|
-
def kill_worker_and_exit
|
82
|
-
@worker.check_out
|
83
|
-
@exit_started = Time.now
|
84
|
-
@work_thread.kill && @monitor_thread.kill
|
85
|
-
sleep 0.3 while running? && Time.now - @exit_started < WORKER_EXIT_WAIT
|
86
|
-
return Process.exit unless running?
|
87
|
-
@work_thread.kill! && @monitor_thread.kill!
|
88
|
-
Process.exit
|
89
|
-
end
|
90
|
-
|
91
|
-
end
|
92
|
-
|
93
|
-
end
|
94
|
-
|
95
|
-
CloudCrowd::Daemon.new.run
|
@@ -1,61 +0,0 @@
|
|
1
|
-
module CloudCrowd
|
2
|
-
|
3
|
-
# A WorkerRecord is a recording of an active worker daemon running remotely.
|
4
|
-
# Every time it checks in, we keep track of its status. The attributes shown
|
5
|
-
# here may lag their actual values by up to Worker::CHECK_IN_INTERVAL seconds.
|
6
|
-
class WorkerRecord < ActiveRecord::Base
|
7
|
-
|
8
|
-
EXPIRES_AFTER = 2 * Worker::CHECK_IN_INTERVAL
|
9
|
-
|
10
|
-
has_one :work_unit
|
11
|
-
|
12
|
-
validates_presence_of :name, :thread_status
|
13
|
-
|
14
|
-
before_destroy :clear_work_units
|
15
|
-
|
16
|
-
named_scope :alive, lambda { {:conditions => ['updated_at > ?', Time.now - EXPIRES_AFTER]} }
|
17
|
-
named_scope :dead, lambda { {:conditions => ['updated_at <= ?', Time.now - EXPIRES_AFTER]} }
|
18
|
-
|
19
|
-
# Save a Worker's current status to the database.
|
20
|
-
def self.check_in(params)
|
21
|
-
attrs = {:thread_status => params[:thread_status], :updated_at => Time.now}
|
22
|
-
self.find_or_create_by_name(params[:name]).update_attributes!(attrs)
|
23
|
-
end
|
24
|
-
|
25
|
-
# Remove a terminated Worker's record from the database.
|
26
|
-
def self.check_out(params)
|
27
|
-
self.find_by_name(params[:name]).destroy
|
28
|
-
end
|
29
|
-
|
30
|
-
# We consider the worker to be alive if it's checked in more recently
|
31
|
-
# than twice the expected interval ago.
|
32
|
-
def alive?
|
33
|
-
updated_at > Time.now - EXPIRES_AFTER
|
34
|
-
end
|
35
|
-
|
36
|
-
# Derive the Worker's PID on the remote machine from the name.
|
37
|
-
def pid
|
38
|
-
@pid ||= self.name.split('@').first
|
39
|
-
end
|
40
|
-
|
41
|
-
# Derive the hostname from the Worker's name.
|
42
|
-
def hostname
|
43
|
-
@hostname ||= self.name.split('@').last
|
44
|
-
end
|
45
|
-
|
46
|
-
def to_json(opts={})
|
47
|
-
{
|
48
|
-
'name' => name,
|
49
|
-
'status' => work_unit && work_unit.display_status,
|
50
|
-
}.to_json
|
51
|
-
end
|
52
|
-
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def clear_work_units
|
57
|
-
WorkUnit.update_all('worker_record_id = null', "worker_record_id = #{id}")
|
58
|
-
end
|
59
|
-
|
60
|
-
end
|
61
|
-
end
|
data/lib/cloud_crowd/runner.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
# This is the script that kicks off a single CloudCrowd::Daemon. Rely on
|
2
|
-
# cloud-crowd.rb for autoloading of all the code we need.
|
3
|
-
|
4
|
-
require "#{File.dirname(__FILE__)}/../cloud-crowd"
|
5
|
-
|
6
|
-
FileUtils.mkdir('log') unless File.exists?('log')
|
7
|
-
|
8
|
-
Daemons.run("#{CloudCrowd::ROOT}/lib/cloud_crowd/daemon.rb", {
|
9
|
-
:app_name => "cloud_crowd_worker",
|
10
|
-
:dir_mode => :normal,
|
11
|
-
:dir => 'log',
|
12
|
-
:multiple => true,
|
13
|
-
:backtrace => true,
|
14
|
-
:log_output => true
|
15
|
-
})
|