mongo_agent 0.0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ee39eefc1decdd3c2831ccad3130d9e6f2836ba3
4
+ data.tar.gz: be4191785f44ef768946182a95b0cca390ad5229
5
+ SHA512:
6
+ metadata.gz: 8943b3c9a745020ddf853ff1f89d637c0a09a874987d4d8256527cdd143582d75922d5a9170fcbc8fa9dd54c9a8d2e8e882981778ff7b1aa0396660c1242eb49
7
+ data.tar.gz: 641d079a4dc1a092b9a4d566d2fafb90af2e46d0542b55664ece1f9a2b1c8618a61dc21d773d65106824063ed0f8b02c1422ab9e7a9c417f4396a772c6099cdd
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+ gem 'moped'
3
+ gem 'rspec'
4
+ gem 'yard'
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'rake'
2
+ require 'yard'
3
+
4
+ begin
5
+ require 'rspec/core/rake_task'
6
+ RSpec::Core::RakeTask.new(:spec)
7
+ rescue LoadError
8
+ end
9
+
10
+ YARD::Rake::YardocTask.new do |t|
11
+ t.files = ['lib/**/*.rb'] # optional
12
+ end
@@ -0,0 +1,270 @@
1
+ require 'moped'
2
+
3
+ module MongoAgent
4
+
5
+ # @note The license of this source is "MIT Licence"
6
+
7
+ # MongoAgent::Agent is designed to make it easy to create an agent that works on tasks
8
+ # queued up in a specified queue. A task is a document in a queue defined in
9
+ # the MONGO_HOST MONGO_DB. A task must be created by another agent (MongoAgent::Agent,
10
+ # or Human) in the queue. It must have, at minimum, the following fields:
11
+ # agent_name: string, name of the agent that should process the task
12
+ # ready: must be true for an agent to process the task
13
+ # When the agent finds a ready task for its agent_name, it registers itself with the
14
+ # task document by updating its agent_host field to the host that is running the
15
+ # agent, and sets ready to false (meaning it is actually running). When the agent
16
+ # completes the task, it sets the complete flag to true. If the agent encounters
17
+ # an error during processing, it can signal failure by setting the errors_encountered
18
+ # field to true at the time its complete flag is updated to true.
19
+ #
20
+ # When an Agent creates a task, it can pass a variety of messages as JSON in the task
21
+ # that can be used by the processing agent. The processing agent can also pass a
22
+ # variety of messages to other agents when they review the task. These messages
23
+ # are just fields added to the task Document when it is created or updated by
24
+ # an agent.
25
+ # Any MongoAgent::Agent depends on the following environment variables to configure its
26
+ # MongoDB connection:
27
+ # MONGO_HOST: host URL for the MongoDB, can be in any form that mongod itself can
28
+ # use, e.g. host, host:port, etc.
29
+ # MONGO_DB: the name of the Document store in the MongoDB to use to find its queue.
30
+ # it will be created if it does not exist
31
+
32
+ # @author Darin London Copyright 2014
33
+ class Agent
34
+
35
+ # This holds the Moped::Session object that can be used to query information from the MongoDB
36
+ # hosted by the MONGO_HOST environment variable
37
+ # @return [Moped::Session]
38
+ attr_reader :db
39
+
40
+ # This holds the log while work! is running
41
+ # log will be a Hash with the following keys:
42
+ # tasks_processed: int number of tasks processed (success of failure)
43
+ # failed_tasks: int number of tasks that have failed
44
+ # The log is passed to the block that is assigned to process_while (see below)
45
+ # @return [Hash]
46
+ attr_reader :log
47
+
48
+ # This holds a block that will be passed the log as an argument and return true
49
+ # as long as the agent should continue to process tasks when work! is called,
50
+ # and false when work! should stop and return.
51
+ # If not set, the agent will continue to process tasks until it is killed when
52
+ # work! is called
53
+ # @return [Block]
54
+ attr_accessor :process_while
55
+
56
+ # The name of the agent for which tasks will be taken from the queue
57
+ # @return [String]
58
+ attr_accessor :name
59
+
60
+ # The name of the task queue that contains the tasks on which this agent will work.
61
+ # @return [String]
62
+ attr_accessor :queue
63
+
64
+ # number of seconds to sleep between each call to process! when running agent.work! or agent.process_while
65
+ # default 5
66
+ attr_accessor :sleep_between
67
+
68
+ # create a new MongoAgent::Agent
69
+ # @param attributes [Hash] with name, queue, and optional sleep_between
70
+ # @option attributes [String] name REQUIRED
71
+ # @option attributes [String] queue REQUIRED
72
+ # @option attributes [Int] sleep_between OPTIONAL
73
+ # @raise [MongoAgent::Error] name and queue are missing
74
+ def initialize(attributes = nil)
75
+ if attributes.nil?
76
+ raise MongoAgent::Error, "attributes Hash required with name and queue keys required"
77
+ end
78
+ @name = attributes[:name]
79
+ @queue = attributes[:queue]
80
+ unless @name && @queue
81
+ raise MongoAgent::Error, "attributes[:name] and attributes[:queue] are required!"
82
+ end
83
+ build_db()
84
+ if attributes[:sleep_between]
85
+ @sleep_between = attributes[:sleep_between]
86
+ else
87
+ @sleep_between = 5
88
+ end
89
+ @log = {
90
+ tasks_processed: 0,
91
+ failed_tasks: 0
92
+ }
93
+ @process_while = ->(log) { true }
94
+ end
95
+
96
+ # If a task for the agent is found that is ready, process! registers itself with the task
97
+ # by setting ready to false, and setting its hostname on the :agent_host field, and then
98
+ # passes the task to the supplied block. This block must return a required boolean field
99
+ # indicating success or failure, and an optional hash of key - value fields that will be
100
+ # updated on the task Document. Note, the updates are made regardless of the value of
101
+ # success. In fact, the agent can be configured to update different fields based on
102
+ # success or failure. Also, note that any key, value supported by JSON can be stored
103
+ # in the hash. This allows the agent to communicate any useful information to the task
104
+ # for other agents (MongoAgent::Agent or human) to use. The block must try at all costs
105
+ # to avoid terminating. If an error is encountered, block should return false for the
106
+ # success field to signal that the process failed. If no errors are encountered block
107
+ # should return true for the success field.
108
+ #
109
+ # @example Exit successfully and sets :complete to true on the task
110
+ # @agent->process! do |task_hash|
111
+ # foo = task_hash[:foo]
112
+ # # do something with foo to perform a task
113
+ # true
114
+ # end
115
+ #
116
+ # @example Same, but also sets the 'files_processed' field
117
+ # @agent->process! { |task_hash|
118
+ # # ... operation using task_hash for information
119
+ # [true, {:files_processed => 30}]
120
+ # }
121
+ #
122
+ # @example Fails, sets :complete to true, and :error_encountered to true
123
+ # @failure = ->(task_hash){
124
+ # begin
125
+ # # ... failing operation using task_hash for information
126
+ # return true
127
+ # rescue
128
+ # return false
129
+ # end
130
+ # }
131
+ #
132
+ # @agent->process!(&@failure)
133
+ #
134
+ # @example Same, but also sets the 'notice' field
135
+ # @agent->process! do |task_hash|
136
+ # ...
137
+ # [false, {:notice => 'There were 10 files left to process!' }]
138
+ # end
139
+ #
140
+ # @example This agent passes different parameters based on success or failure
141
+ # $agent->process! { |task_hash|
142
+ # # ... process and set $success true or false
143
+ # if $success
144
+ # [ $success, {:files_processed => 100} ]
145
+ # else
146
+ # [ $success, {:files_remaining => 10}]
147
+ # end
148
+ # }
149
+ #
150
+ # @param agent_code [Block, Lambda, or Method] Code to process a task
151
+ # @yieldparam Task [Hash]
152
+ # @yieldreturn [Boolean, Hash] success, (optional) hash of fields to update and values to update on the task
153
+ def process!(&agent_code)
154
+ (runnable, task) = register()
155
+ return unless runnable
156
+ (success, update) = agent_code.call(task)
157
+ @log[:tasks_processed] += 1
158
+ if success
159
+ complete_task(task, update)
160
+ else
161
+ fail_task(task, update)
162
+ end
163
+ return
164
+ end
165
+
166
+ # Iteratively runs process! on the supplied Block, then sleeps :sleep_between
167
+ # between each attempt. Block should match the specifications of what can
168
+ # be passed to process! (see above).
169
+ #
170
+ # If @process_while is set to a Block, Lambda, or Method, then it is called after
171
+ # each task is processed, and passed the current @log. As long as the
172
+ # Block returns true, work! will continue to process. work! will stop processing
173
+ # tasks when the Block returns false.
174
+ #
175
+ # @example process 3 entries and then exit
176
+ # @agent.process_while = ->(log) {
177
+ # (log[:tasks_processed] < 3)
178
+ # }
179
+ # @agent.work! { |task_hash|
180
+ # #... do something with task_hash and return true of false just as in process!
181
+ # }
182
+ #
183
+ # @example process until errors are encountered and then exit
184
+ # @agent.process_while = ->(log) {
185
+ # not(log[:errors_encountered])
186
+ # }
187
+ # @agent.work! { |task_hash|
188
+ # #... do something with task_hash and return true of false just as in process!
189
+ # }
190
+ # $stderr.puts " #{ @agent.log[:errors_encountered ] } errors were encountered during work."
191
+ # @param agent_code [Block, Lambda, or Method] Code to process a task
192
+ # @yieldparam Task Hash
193
+ # @yieldreturn [Boolean, Hash] success, (optional) hash of fields to update and values to update on the task
194
+ def work!(&agent_code)
195
+
196
+ while (@process_while.call(@log))
197
+ process!(&agent_code)
198
+ sleep @sleep_between
199
+ end
200
+ end
201
+
202
+ # get A MONGO_DB[queue] Moped::Query, either for the specified query Hash, or, when
203
+ # query is nil, all that are currently ready for the @name. This can be used to
204
+ # scan through the tasks on the @queue to perform aggregation tasks:
205
+ # @example collecting information
206
+ # @agent->get_tasks({
207
+ # agent_name: @agent->name,
208
+ # error_encountered: true
209
+ # }).each do |task|
210
+ # $stderr.puts "ERROR:\n#{ task.inspect }\n"
211
+ # end
212
+ #
213
+ # @example update ready to true for tasks that need intervention before they can run
214
+ # @agent->get_tasks({
215
+ # agent_name: @agent->name,
216
+ # waiting_for_information: true
217
+ # }).each do |task|
218
+ # task.update('$set' => {ready: true, waiting_form_information: false})
219
+ # end
220
+ #
221
+ # @param query [Hash] (optional) any query to find tasks
222
+ # @return [Moped::Query]
223
+ def get_tasks(query = nil)
224
+ if query.nil?
225
+ return @db[@queue].find({agent_name: @name, ready: true})
226
+ else
227
+ return @db[@queue].find(query)
228
+ end
229
+ end
230
+
231
+ private
232
+
233
+ def register
234
+ task = get_tasks().first
235
+ unless task
236
+ $stderr.puts "there are no ready tasks for #{@name} in queue #{@queue}"
237
+ return false
238
+ end
239
+
240
+ hostname = Socket.gethostname
241
+ get_tasks({ _id: task[:_id] }).update('$set' => {ready: false, agent_host: "#{hostname}" })
242
+ return true, task
243
+ end
244
+
245
+ def complete_task(task, update = nil)
246
+ if update.nil?
247
+ update = {}
248
+ end
249
+ update[:complete] = true
250
+ update[:error_encountered] = false
251
+ get_tasks({ _id: task[:_id] }).update('$set' => update)
252
+ end
253
+
254
+ def fail_task(task, update = nil)
255
+ @log[:failed_tasks] += 1
256
+ if update.nil?
257
+ update = {}
258
+ end
259
+ update[:complete] = true
260
+ update[:error_encountered] = true
261
+ get_tasks({ _id: task[:_id] }).update('$set' => update)
262
+ end
263
+
264
+ def build_db
265
+ @db = Moped::Session.new([ ENV['MONGO_HOST'] ])
266
+ @db.use ENV['MONGO_DB']
267
+ end
268
+
269
+ end #MongoAgent::Agent
270
+ end #MongoAgent
@@ -0,0 +1,27 @@
1
+ require 'moped'
2
+
3
+ module MongoAgent
4
+
5
+ # MongoAgent::Db is a class that is meant to be extended by MongoAgent classes. It
6
+ # stores shared code to instantiate and provide access to a MongoDB Document Store and
7
+ # Moped::Session object for use by the extending classes to access their MongoDB Document Store
8
+ #
9
+ # It depends on the following environment variables to configure its MongoDB connect:
10
+ # MONGO_HOST: host URL for the MongoDB, can be in any form that mongod itself can
11
+ # use, e.g. host:port, etc.
12
+ # MONGO_DB: the name of the Document store in the MongoDB to use for all activities
13
+ # will be created if does not exist
14
+
15
+ class Db
16
+
17
+ # This holds the Moped::Session object that can be used to query information from the MongoDB
18
+ # hosted by the MONGO_HOST environment variable
19
+ attr_reader :db
20
+
21
+ # This is for internal use by SpreadsheetAgent classes that extend SpreadsheetAgent::Db
22
+ def build_db
23
+ @db = Moped::Session.new([ ENV['MONGO_HOST'] ])
24
+ @db.use ENV['MONGO_DB']
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,8 @@
1
+ module MongoAgent
2
+
3
+ # MongoAgent::Error is an extension of Error that SpreadsheetAgent classes throw
4
+ # when critical errors are encountered
5
+ class Error < RuntimeError
6
+ end
7
+
8
+ end
@@ -0,0 +1,24 @@
1
+ require 'mongo_agent/agent'
2
+ require 'mongo_agent/error'
3
+ require 'socket'
4
+
5
+ # @note The license of this source is "MIT Licence"
6
+ # A Distributed Agent System using MongoDB
7
+ #
8
+ # MongoAgent is a framework for creating massively distributed pipelines
9
+ # across many different servers, each using the same MongoDB as a
10
+ # control panel. It is extensible, and flexible. It doesnt specify what
11
+ # goals any pipeline should be working towards, or which goals are prerequisites
12
+ # for other goals, but it does provide logic for easily defining these relationships
13
+ # based on your own needs. It does this by providing a subsumption architecture,
14
+ # whereby many small, highly focused agents are written to perform specific goals,
15
+ # and also know what resources they require to perform them. Agents can be coded to
16
+ # subsume other agents upon successful completion. In addition, it is
17
+ # designed from the beginning to support the creation of simple human-computational
18
+ # workflows.
19
+ #
20
+ # MongoAgent requires MongoDB and Moped
21
+ # @version 0.01
22
+ # @author Darin London Copyright 2014
23
+ module MongoAgent
24
+ end
@@ -0,0 +1,353 @@
1
+ require 'mongo_agent'
2
+ require 'moped'
3
+ require 'psych'
4
+ require 'socket'
5
+
6
+ RSpec.describe "MongoAgent::Agent" do
7
+ let (:test_agent_name) { 'test_agent' }
8
+ let (:test_agent_queue) { 'testqueue' }
9
+ let (:test_agent_sleep_between) { 10 }
10
+ let (:expected_default_sleep_between) { 5 }
11
+ let (:test_task_params) {[
12
+ 'foo',
13
+ 'bar',
14
+ 'baz',
15
+ 'bleb'
16
+ ]}
17
+ let (:expected_hostname) { Socket.gethostname }
18
+ before (:each) do
19
+ @db = Moped::Session.new([ ENV['MONGO_HOST'] ])
20
+ @db.use ENV['MONGO_DB']
21
+ end
22
+
23
+ after (:each) do
24
+ @db.drop
25
+ end
26
+
27
+ context "initialization" do
28
+ it "throws MongoAgent::Error without an attributes Hash" do
29
+ expect {
30
+ MongoAgent::Agent.new
31
+ }.to raise_error { |error|
32
+ expect(error).to be_a(MongoAgent::Error)
33
+ }
34
+ end
35
+
36
+ it "throws MongoAgent::Error without attributes[:name]" do
37
+ expect {
38
+ MongoAgent::Agent.new({queue: test_agent_queue})
39
+ }.to raise_error { |error|
40
+ expect(error).to be_a(MongoAgent::Error)
41
+ }
42
+ end
43
+
44
+ it 'throws MongoAgent::Error without attributes[:queue]' do
45
+ expect {
46
+ MongoAgent::Agent.new({ name: test_agent_name })
47
+ }.to raise_error { |error|
48
+ expect(error).to be_a(MongoAgent::Error)
49
+ }
50
+ end
51
+
52
+ it 'creates MongoAgent::Agent with default sleep_between when provided a name and queue' do
53
+ expect {
54
+ @agent = MongoAgent::Agent.new({
55
+ name: test_agent_name,
56
+ queue: test_agent_queue
57
+ })
58
+ }.to_not raise_error
59
+ expect(@agent.sleep_between).to eq(expected_default_sleep_between)
60
+ end
61
+
62
+ it 'allows optional attributes[:sleep_between]' do
63
+ expect {
64
+ @agent = MongoAgent::Agent.new({
65
+ name: test_agent_name,
66
+ queue: test_agent_queue,
67
+ sleep_between: test_agent_sleep_between
68
+ })
69
+ }.to_not raise_error
70
+ expect(@agent.sleep_between).to eq(test_agent_sleep_between)
71
+ end
72
+ end #initialization
73
+
74
+ context "process!" do
75
+ subject {
76
+ MongoAgent::Agent.new({
77
+ name: test_agent_name,
78
+ queue: test_agent_queue
79
+ })
80
+ }
81
+
82
+ context "with no tasks in queue" do
83
+ it 'returns without processing any tasks' do
84
+ called = false
85
+ subject.process! {
86
+ called = true
87
+ true
88
+ }
89
+ expect(called).to eq(false)
90
+ expect(subject.log[:tasks_processed]).to eq(0)
91
+ expect(subject.log[:failed_tasks]).to eq(0)
92
+ end
93
+ end # with no tasks in queue
94
+
95
+ context "with no ready tasks in queue" do
96
+ before(:each) do
97
+ @db[test_agent_queue].insert( test_task_params.collect{|param|
98
+ {agent_name: test_agent_name, test_param: param}
99
+ })
100
+ end
101
+
102
+ it 'return without processing any tasks' do
103
+ called = false
104
+ subject.process! {
105
+ called = true
106
+ true
107
+ }
108
+ expect(called).to eq(false)
109
+ expect(subject.log[:tasks_processed]).to eq(0)
110
+ expect(subject.log[:failed_tasks]).to eq(0)
111
+ end
112
+ end # with no ready tasks in queue
113
+
114
+ context "with ready tasks" do
115
+ before (:each) do
116
+ @db[test_agent_queue].insert( test_task_params.collect{|param|
117
+ {agent_name: test_agent_name, ready: true, test_param: param}
118
+ })
119
+ end
120
+
121
+ context "success" do
122
+ context "default" do
123
+ it 'updates complete, agent_host but not :error_encountered' do
124
+ called = false
125
+ processed_param = nil
126
+ subject.process! { |task|
127
+ processed_param = task[:test_param]
128
+ called = true
129
+ true
130
+ }
131
+ expect(processed_param).to be
132
+ expect(test_task_params.include?(processed_param)).to eq(true)
133
+ expect(called).to eq(true)
134
+ expect(subject.log[:tasks_processed]).to eq(1)
135
+ expect(subject.log[:failed_tasks]).to eq(0)
136
+ processed_task = @db[test_agent_queue].find(
137
+ {agent_name: test_agent_name, test_param: processed_param}
138
+ ).first
139
+ expect(processed_task).to be
140
+ expect(processed_task[:complete]).to eq(true)
141
+ expect(processed_task[:error_encountered]).to eq(false)
142
+ expect(processed_task[:ready]).to eq(false)
143
+ expect(processed_task[:agent_host]).to eq(expected_hostname)
144
+ end
145
+ end #default
146
+
147
+ context "with update" do
148
+ it 'updates complete, agent_host, and update params, but not error_encountered' do
149
+ called = false
150
+ processed_param = nil
151
+ subject.process! { |task|
152
+ processed_param = task[:test_param]
153
+ called = true
154
+ [true, {test_update: 'updated'}]
155
+ }
156
+ expect(processed_param).to be
157
+ expect(test_task_params.include?(processed_param)).to eq(true)
158
+ expect(called).to eq(true)
159
+ expect(subject.log[:tasks_processed]).to eq(1)
160
+ expect(subject.log[:failed_tasks]).to eq(0)
161
+ processed_task = @db[test_agent_queue].find(
162
+ {agent_name: test_agent_name, test_param: processed_param}
163
+ ).first
164
+ expect(processed_task).to be
165
+ expect(processed_task[:complete]).to eq(true)
166
+ expect(processed_task[:error_encountered]).to eq(false)
167
+ expect(processed_task[:ready]).to eq(false)
168
+ expect(processed_task[:agent_host]).to eq(expected_hostname)
169
+ expect(processed_task[:test_update]).to be
170
+ expect(processed_task[:test_update]).to eq('updated')
171
+ end
172
+ end #with update
173
+ end #success
174
+
175
+ context "failure" do
176
+ context "default" do
177
+ it 'updates complete, agent_host, and error_encountered' do
178
+ called = false
179
+ processed_param = nil
180
+ subject.process! { |task|
181
+ processed_param = task[:test_param]
182
+ called = true
183
+ false
184
+ }
185
+ expect(processed_param).to be
186
+ expect(test_task_params.include?(processed_param)).to eq(true)
187
+ expect(called).to eq(true)
188
+ expect(subject.log[:tasks_processed]).to eq(1)
189
+ expect(subject.log[:failed_tasks]).to eq(1)
190
+ processed_task = @db[test_agent_queue].find(
191
+ {agent_name: test_agent_name, test_param: processed_param}
192
+ ).first
193
+ expect(processed_task).to be
194
+ expect(processed_task[:complete]).to eq(true)
195
+ expect(processed_task[:error_encountered]).to eq(true)
196
+ expect(processed_task[:ready]).to eq(false)
197
+ expect(processed_task[:agent_host]).to eq(expected_hostname)
198
+ end
199
+ end #default
200
+
201
+ context "with update" do
202
+ it 'updates complete, agent_host, update params, and error_encountered' do
203
+ called = false
204
+ processed_param = nil
205
+ subject.process! { |task|
206
+ processed_param = task[:test_param]
207
+ called = true
208
+ [false, {test_update: 'updated'}]
209
+ }
210
+ expect(processed_param).to be
211
+ expect(test_task_params.include?(processed_param)).to eq(true)
212
+ expect(called).to eq(true)
213
+ expect(subject.log[:tasks_processed]).to eq(1)
214
+ expect(subject.log[:failed_tasks]).to eq(1)
215
+ processed_task = @db[test_agent_queue].find(
216
+ {agent_name: test_agent_name, test_param: processed_param}
217
+ ).first
218
+ expect(processed_task).to be
219
+ expect(processed_task[:complete]).to eq(true)
220
+ expect(processed_task[:error_encountered]).to eq(true)
221
+ expect(processed_task[:ready]).to eq(false)
222
+ expect(processed_task[:agent_host]).to eq(expected_hostname)
223
+ expect(processed_task[:test_update]).to be
224
+ expect(processed_task[:test_update]).to eq('updated')
225
+ end
226
+ end #with update
227
+ end #failure
228
+ end # with ready tasks
229
+ end #process!
230
+
231
+ context "work!" do
232
+ subject {
233
+ MongoAgent::Agent.new({
234
+ name: test_agent_name,
235
+ queue: test_agent_queue
236
+ })
237
+ }
238
+
239
+ context "with no tasks in queue" do
240
+ before (:each) do
241
+ @attempts = 0
242
+ subject.process_while = -> (log) {
243
+ @attempts += 1
244
+ ( @attempts < 4 )
245
+ }
246
+ end
247
+
248
+ it 'returns without processing any tasks' do
249
+ called = false
250
+ subject.work! {
251
+ called = true
252
+ true
253
+ }
254
+ expect(called).to eq(false)
255
+ expect(subject.log[:tasks_processed]).to eq(0)
256
+ expect(subject.log[:failed_tasks]).to eq(0)
257
+ end
258
+ end # with no ready tasks
259
+
260
+ context "with no ready tasks in queue" do
261
+ before (:each) do
262
+ @db[test_agent_queue].insert( test_task_params.collect{|param|
263
+ {agent_name: test_agent_name, test_param: param}
264
+ })
265
+ @attempts = 0
266
+ subject.process_while = -> (log) {
267
+ @attempts += 1
268
+ ( @attempts < 4 )
269
+ }
270
+ end
271
+
272
+ it 'returns without processing any tasks' do
273
+ called = false
274
+ subject.work! {
275
+ called = true
276
+ true
277
+ }
278
+ expect(called).to eq(false)
279
+ expect(subject.log[:tasks_processed]).to eq(0)
280
+ expect(subject.log[:failed_tasks]).to eq(0)
281
+ end
282
+ end # with no ready tasks in queue
283
+
284
+ context "with ready tasks" do
285
+ before (:each) do
286
+ @db[test_agent_queue].insert( test_task_params.collect{|param|
287
+ {agent_name: test_agent_name, ready: true, test_param: param}
288
+ })
289
+ subject.process_while = -> (log) {
290
+ (log[:tasks_processed] < 2)
291
+ }
292
+ end
293
+
294
+ it "will process! until process_while returns false" do
295
+ called = 0
296
+ subject.work! {
297
+ called += 1
298
+ true
299
+ }
300
+ expect(called).to eq(2)
301
+ expect(subject.log[:tasks_processed] < 2).to eq(false)
302
+ expect(subject.log[:tasks_processed]).to eq(2)
303
+ expect(subject.log[:failed_tasks]).to eq(0)
304
+ end
305
+ end # with ready tasks
306
+ end #work
307
+
308
+ context "get_tasks" do
309
+ let (:error_document) {{
310
+ agent_name: test_agent_name,
311
+ test_param: 'finished',
312
+ agent_host: 'daaedcedddeaaf',
313
+ test_param: 'failed_entry',
314
+ complete: true,
315
+ ready: false,
316
+ errors_encounterd: true
317
+ }}
318
+
319
+ subject {
320
+ MongoAgent::Agent.new({
321
+ name: test_agent_name,
322
+ queue: test_agent_queue
323
+ })
324
+ }
325
+
326
+ before (:each) do
327
+ @db[test_agent_queue].insert( test_task_params.collect{|param|
328
+ {agent_name: test_agent_name, ready: true, test_param: param}
329
+ })
330
+ @db[test_agent_queue].insert(error_document)
331
+ end
332
+
333
+ it "returns all ready tasks in queue by default" do
334
+ tasks = subject.get_tasks
335
+ expect(tasks.count).to eq(test_task_params.count)
336
+ tasks.each do |task|
337
+ expect(task[:ready]).to eq(true)
338
+ end
339
+ end
340
+
341
+ it "return only tasks that match the query provided" do
342
+ tasks = subject.get_tasks({agent_name: test_agent_name, agent_host: error_document[:agent_host]})
343
+ expect(tasks.count).to eq(1)
344
+ returned_task = tasks.first
345
+ returned_task.keys.each do |key|
346
+ if error_document.keys.include? key
347
+ expect(returned_task[key]).to eq(error_document[key])
348
+ end
349
+ end
350
+ end
351
+ end #get_task
352
+
353
+ end #MongoAgentTest::Agent
@@ -0,0 +1,87 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
4
+ # file to always be loaded, without a need to explicitly require it in any files.
5
+ #
6
+ # Given that it is always loaded, you are encouraged to keep this file as
7
+ # light-weight as possible. Requiring heavyweight dependencies from this file
8
+ # will add to the boot time of your test suite on EVERY test run, even for an
9
+ # individual file that may not need all of that loaded. Instead, consider making
10
+ # a separate helper file that requires the additional dependencies and performs
11
+ # the additional setup, and require it from the spec files that actually need it.
12
+ #
13
+ # The `.rspec` file also contains a few flags that are not defaults but that
14
+ # users commonly want.
15
+ #
16
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17
+ RSpec.configure do |config|
18
+ # rspec-expectations config goes here. You can use an alternate
19
+ # assertion/expectation library such as wrong or the stdlib/minitest
20
+ # assertions if you prefer.
21
+ config.expect_with :rspec do |expectations|
22
+ # This option will default to `true` in RSpec 4. It makes the `description`
23
+ # and `failure_message` of custom matchers include text for helper methods
24
+ # defined using `chain`, e.g.:
25
+ # be_bigger_than(2).and_smaller_than(4).description
26
+ # # => "be bigger than 2 and smaller than 4"
27
+ # ...rather than:
28
+ # # => "be bigger than 2"
29
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
30
+ end
31
+
32
+ # rspec-mocks config goes here. You can use an alternate test double
33
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
34
+ config.mock_with :rspec do |mocks|
35
+ # Prevents you from mocking or stubbing a method that does not exist on
36
+ # a real object. This is generally recommended, and will default to
37
+ # `true` in RSpec 4.
38
+ mocks.verify_partial_doubles = true
39
+ end
40
+
41
+ # The settings below are suggested to provide a good initial experience
42
+ # with RSpec, but feel free to customize to your heart's content.
43
+ # These two settings work together to allow you to limit a spec run
44
+ # to individual examples or groups you care about by tagging them with
45
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
46
+ # get run.
47
+ config.filter_run :focus
48
+ config.run_all_when_everything_filtered = true
49
+
50
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
51
+ # For more details, see:
52
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
53
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
54
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
55
+ config.disable_monkey_patching!
56
+
57
+ # This setting enables warnings. It's recommended, but in some cases may
58
+ # be too noisy due to issues in dependencies.
59
+ config.warnings = true
60
+
61
+ # Many RSpec users commonly either run the entire suite or an individual
62
+ # file, and it's useful to allow more verbose output when running an
63
+ # individual spec file.
64
+ if config.files_to_run.one?
65
+ # Use the documentation formatter for detailed output,
66
+ # unless a formatter has already been configured
67
+ # (e.g. via a command-line flag).
68
+ config.default_formatter = 'doc'
69
+ end
70
+
71
+ # Print the 10 slowest examples and example groups at the
72
+ # end of the spec run, to help surface which specs are running
73
+ # particularly slow.
74
+ config.profile_examples = 10
75
+
76
+ # Run specs in random order to surface order dependencies. If you find an
77
+ # order dependency and want to debug it, you can fix the order by providing
78
+ # the seed, which is printed after each run.
79
+ # --seed 1234
80
+ config.order = :random
81
+
82
+ # Seed global randomization in this process using the `--seed` CLI option.
83
+ # Setting this allows you to use `--seed` to deterministically reproduce
84
+ # test failures related to randomization by passing the same `--seed` value
85
+ # as the one that triggered the failure.
86
+ Kernel.srand config.seed
87
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongo_agent
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Darin London
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-20 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |
14
+ MongoAgent is a framework for creating distributed pipelines across many
15
+ different servers. It is extensible, and flexible. It does not specify what goals
16
+ should be processed. It simply provides the foundation for using a MongoDB as
17
+ a messaging queue between many different agents processing tasks defined in the
18
+ same queue. It is designed from the beginning to support the creation of simple
19
+ human-computational workflows.
20
+ email: darin.london@duke.edu
21
+ executables: []
22
+ extensions: []
23
+ extra_rdoc_files: []
24
+ files:
25
+ - ".rspec"
26
+ - Gemfile
27
+ - Rakefile
28
+ - lib/mongo_agent.rb
29
+ - lib/mongo_agent/agent.rb
30
+ - lib/mongo_agent/db.rb
31
+ - lib/mongo_agent/error.rb
32
+ - spec/mongo_agent_spec.rb
33
+ - spec/spec_helper.rb
34
+ homepage: http://rubygems.org/gems/mongo_agent
35
+ licenses:
36
+ - MIT
37
+ metadata: {}
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 2.2.2
55
+ signing_key:
56
+ specification_version: 4
57
+ summary: MongoAgent is a framework for creating distributed pipelines across many
58
+ different servers, each using the same MongoDB as a control panel.
59
+ test_files:
60
+ - spec/mongo_agent_spec.rb
61
+ - spec/spec_helper.rb
62
+ has_rdoc: