spreadsheet_agent 0.0.1 → 0.0.2

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.
@@ -0,0 +1,13 @@
1
+ ---
2
+ guser: dmlond@gmail.com
3
+ gpass: dyarvckqviimvyuv
4
+ spreadsheet_name: google_agent_test
5
+ reply_email: test_reply@test.com
6
+ send_to: test_send@test.com
7
+ key_fields:
8
+ testentry:
9
+ rank: 2
10
+ required: 1
11
+ testpage:
12
+ rank: 1
13
+ required: 1
@@ -1,16 +1,15 @@
1
- # Author: Darin London
2
- # The license of this source is "MIT Licence"
3
-
1
+ require 'spreadsheet_agent/agent'
2
+ require 'spreadsheet_agent/error'
4
3
  require 'spreadsheet_agent/db'
4
+ require 'spreadsheet_agent/runner'
5
5
  require 'socket'
6
6
  require 'open3'
7
7
  require 'capture_io'
8
8
  require 'mail'
9
9
 
10
+ # @note The license of this source is "MIT Licence"
10
11
  # A Distributed Agent System using Google Spreadsheets
11
12
  #
12
- # Version 0.01
13
- #
14
13
  # SpreadsheetAgent is a framework for creating massively distributed pipelines
15
14
  # across many different servers, each using the same google spreadsheet as a
16
15
  # control panel. It is extensible, and flexible. It doesnt specify what
@@ -29,390 +28,7 @@ require 'mail'
29
28
  # * You can define any fields necessary, but you must specify a 'ready' and a 'complete' field
30
29
  # * You must define at least 1 key field, and the key field must be specified as required in the :config (see SpreadsheetAgent::Db)
31
30
  # * You should then define fields named for agent_bin/#{ field_name }_agent.rb for each agent that you plan to deploy in your pipeline
32
- #
31
+ # @version 0.01
32
+ # @author Darin London Copyright 2013
33
33
  module SpreadsheetAgent
34
-
35
- # SpreadsheetAgent::Agent is designed to make it easy to create a single task which connects to
36
- # a field within a record on a page within the configured SpreadsheetAgent compatible Google Spreadsheet,
37
- # runs, and reports whether the job completed or ended in error. An agent can be configured to only run
38
- # when certain prerequisite fields have completed. The data in these fields can be filled in by other
39
- # SpreadsheetAgent::Agents, SpreadsheetAgent::Runners, or humans. Compute node configuration is available
40
- # to prevent the agent from running more than a certain number of instances of itself, or not run if certain
41
- # other agents or processes are running on the node. Finally, an agent can be configured to subsume another
42
- # agent, and fill in the completion field for that agent in addition to its own when it completes successfully.
43
- #
44
- # extends SpreadsheetAgent::Db
45
- class Agent < SpreadsheetAgent::Db
46
-
47
- # The name of the field in the page to which the agent should report status
48
- attr_accessor :agent_name
49
-
50
- # The name of the Page on the Google Spreadsheet that contains the record to be worked on by the agent
51
- attr_accessor :page_name
52
-
53
- # hash of key-value pairs. The keys are defined in config/agent.conf.yml. The values
54
- # specify the values for those fields in the record on the page for which the agent is running.
55
- # All keys configured as 'required: 1' in config/agent.conf.yml must be included in the keys hash
56
- attr_accessor :keys
57
-
58
- # Boolean. When true, the agent code will print verbosely to STDERR. When false, and the process!
59
- # returns a failure status, the agent will email all stdout and stderr to the email specified in the
60
- # :config send_to value
61
- attr_accessor :debug
62
-
63
- # Optional array of prerequisite fields that must contain a 1 in them for the record on the page before
64
- # the agent will attempt to run
65
- attr_accessor :prerequisites
66
-
67
- # Optional integer. This works on Linux with ps. The agent will not attempt to run if there are
68
- # max_selves instances running
69
- attr_accessor :max_selves
70
-
71
- # Hash of process_name to number of max_instances. This works on Linux with ps. If the agent detects
72
- # the specified number of max_instances of the given process (based on a line match), it will not
73
- # attempt to run
74
- attr_accessor :conflicts_with
75
-
76
- # Array of fields on the record which this agent subsumes. If the agent completes successfully these
77
- # fields will be updated with a 1 in addition to the field for the agent
78
- attr_accessor :subsumes
79
-
80
- # Readonly access to the GoogleDrive::Worksheet that is being access by the agent.
81
- attr_reader :worksheet
82
-
83
- # create a new SpreadsheetAgent::Agent with the following:
84
- # == required configuration parameters:
85
- # * agent_name
86
- # * page_name
87
- # * keys
88
- #
89
- # == optional parameters:
90
- # * config_file: (see SpreadsheetAgent::DB)
91
- # * debug
92
- # * prerequisites
93
- # * max_selves
94
- # * conflicts_with
95
- # * subsumes
96
- #
97
- def initialize(attributes)
98
- @agent_name = attributes[:agent_name]
99
- @page_name = attributes[:page_name]
100
- @keys = attributes[:keys].clone
101
- unless @agent_name && @page_name && @keys
102
- raise SpreadsheetAgentError, "agent_name, page_name, and keys attributes are required!"
103
- end
104
- @config_file = attributes[:config_file]
105
- build_db()
106
-
107
- @worksheet = @db.worksheet_by_title(@page_name)
108
- @debug = attributes[:debug]
109
- if attributes[:prerequisites]
110
- @prerequisites = attributes[:prerequisites].clone
111
- end
112
-
113
- @max_selves = attributes[:max_selves]
114
- if attributes[:conflicts_with]
115
- @conflicts_with = attributes[:conflicts_with].clone
116
- end
117
- if attributes[:subsumes]
118
- @subsumes = attributes[:subsumes].clone
119
- end
120
- end
121
-
122
- # If the agent does not have any conflicting processes (max_selves or conflicts_with)
123
- # and if the entry is ready (field 'ready' has a 1), and all prerequisite fields have a 1,
124
- # gets the GoogleDrive::List record, and passes it to the supplied agent_code PROC as argument.
125
- # This PROC must return a required boolean field indicating success or failure, and an optional
126
- # hash of key - value fields that will be updated on the GoogleDrive::List record. Note, the updates
127
- # are made regardless of the value of success. In fact, the agent can be configured to update
128
- # different fields based on success or failure. Also, note that any value can be stored in the
129
- # hash. This allows the agent to communicate any useful information to the google spreadsheet for other
130
- # agents (SpreadsheetAgent::Agent, SpreadsheetAgent::Runner, or human) to use. The PROC must try at all
131
- # costs to avoid terminating. If an error is encountered, it should return false for the success field
132
- # to signal that the process failed. If no errors are encountered it should return true for the success
133
- # field.
134
- #
135
- # Exits successfully, enters a 1 in the agent_name field
136
- # $agent->process! do |entry|
137
- # true
138
- # end
139
- #
140
- # Same, but also updates the 'notice' field in the record along with the 1 in the agent_name field
141
- # $agent->process! do |entry|
142
- # [true, {:notice => 'There were 30 files processed'}]
143
- # end
144
- #
145
- # Fails, enters f:#{hostname} in the agent_name field
146
- # $agent->process! do |entry|
147
- # false
148
- #
149
- # Same, but also updates the 'notice' field in the record along with the failure notice
150
- # $agent->process! do |entry|
151
- # [false, {:notice => 'There were 10 files left to process!' }]
152
- # end
153
- #
154
- # This agent passes different parameters based on success or failure
155
- # $agent->process! do |entry|
156
- # if $success
157
- # true
158
- # else
159
- # [ false, {:notice => 'there were 10 remaining files'}]
160
- # end
161
- # end
162
- #
163
- def process!(&agent_code)
164
- @worksheet.reload
165
- no_problems = true
166
- capture_output = nil
167
- unless @debug
168
- capture_output = CaptureIO.new
169
- capture_output.start
170
- end
171
-
172
- begin
173
- return true if has_conflicts()
174
- (runnable, entry) = run_entry()
175
- return false unless entry
176
- return true unless runnable
177
-
178
- success, update_entry = agent_code.call(entry)
179
- if success
180
- complete_entry(update_entry)
181
- else
182
- fail_entry(update_entry)
183
- end
184
- rescue
185
- $stderr.puts "#{ $! }"
186
- no_problems = false
187
- end
188
- unless capture_output.nil?
189
- if no_problems
190
- capture_output.stop
191
- else
192
- mail_error(capture_output.stop)
193
- end
194
- end
195
- return no_problems
196
- end
197
-
198
- # Returns the GoogleDrive::List object for the specified keys
199
- def get_entry
200
- this_entry = nil
201
- if @worksheet
202
- @worksheet.list.each do |this_row|
203
- keep_row = true
204
-
205
- @config['key_fields'].keys.reject { |key_field|
206
- !(@config['key_fields'][key_field]["required"]) && !(@keys[key_field])
207
- }.each do |key|
208
- break unless keep_row
209
- keep_row = (this_row[key] == @keys[key])
210
- end
211
-
212
- if keep_row
213
- return this_row
214
- end
215
- end
216
- end
217
- end
218
-
219
- private
220
-
221
- def has_conflicts
222
- return unless (@max_selves || @conflicts_with) # nothing conflicts here
223
-
224
- running_conflicters = {}
225
- self_name = File.basename $0
226
-
227
- begin
228
- conflicting_in = Open3.popen3('ps','-eo','pid,command')[1]
229
- conflicting_in.lines.each do |line|
230
- unless(
231
- (line.match(/emacs\s+|vim*\s+|pico\s+/)) ||
232
- (line.match("#{ $$ }"))
233
- )
234
- if @max_selves && line.match(self_name)
235
- if running_conflicters[@agent_name].nil?
236
- running_conflicters[@agent_name] = 1
237
- else
238
- running_conflicters[@agent_name] += 1
239
- end
240
-
241
- if running_conflicters[@agent_name] == @max_selves
242
- $stderr.puts "max_selves limit reached" if @debug
243
- conflicting_in.close
244
- return true
245
- end
246
- end
247
-
248
- if @conflicts_with
249
- @conflicts_with.keys.each do |conflicter|
250
- if line.match(conflicter)
251
- if running_conflicters[conflicter].nil?
252
- running_conflicters[conflicter] = 1
253
- else
254
- running_conflicters[conflicter] += 1
255
- end
256
- if running_conflicters[conflicter] >= @conflicts_with[conflicter]
257
- $stderr.puts "conflicts with #{ conflicter }" if @debug
258
- conflicting_in.close
259
- return true
260
- end
261
- end
262
- end
263
- end
264
- end
265
- end
266
- conflicting_in.close
267
- return false
268
-
269
- rescue
270
- $stderr.puts "Couldnt check conflicts #{ $! }" if @debug
271
- return true
272
- end
273
-
274
- end
275
-
276
- # this call initiates a race resistant attempt to make sure that there is only 1
277
- # clear 'winner' among N potential agents attempting to run the same goal on the
278
- # same spreadsheet agent's cell
279
- def run_entry
280
- entry = get_entry()
281
- output = '';
282
- @keys.keys.select { |k| @config['key_fields'][k] && @keys[k] }.each do |key|
283
- output += [ key, @keys[key] ].join(' ') + " "
284
- end
285
-
286
- unless entry
287
- $stderr.puts "#{ output } is not supported on #{ @page_name }" if @debug
288
- return
289
- end
290
-
291
- unless entry['ready'] == "1"
292
- $stderr.puts "#{ output } is not ready to run #{ @agent_name }" if @debug
293
- return false, entry
294
- end
295
-
296
- if entry['complete'] == "1"
297
- $stderr.puts "All goals are completed for #{ output }" if @debug
298
- return false, entry
299
- end
300
-
301
- if entry[@agent_name]
302
- (status, running_hostname) = entry[@agent_name].split(':')
303
-
304
- case status
305
- when 'r'
306
- $stderr.puts " #{ output } is already running #{ @agent_name } on #{ running_hostname }" if @debug
307
- return false, entry
308
-
309
- when "1"
310
- $stderr.puts " #{ output } has already run #{ @agent_name }" if @debug
311
- return false, entry
312
-
313
- when 'F'
314
- $stderr.puts " #{ output } has already Failed #{ @agent_name }" if @debug
315
- return false, entry
316
- end
317
- end
318
-
319
- if @prerequisites
320
- @prerequisites.each do |prereq_field|
321
- unless entry[prereq_field] == "1"
322
- $stderr.puts " #{ output } has not finished #{ prereq_field }" if @debug
323
- return false, entry
324
- end
325
- end
326
- end
327
-
328
- # first attempt to set the hostname of the machine as the value of the agent
329
- hostname = Socket.gethostname;
330
- begin
331
- entry.update @agent_name => "r:#{ hostname }"
332
- @worksheet.save
333
-
334
- rescue GoogleDrive::Error
335
- # this is a collision, which is to be treated as if it is not runnable
336
- $stderr.puts " #{ output } lost #{ @agent_name } on #{hostname}" if @debug
337
- return false, entry
338
- end
339
-
340
- sleep 3
341
- begin
342
- @worksheet.reload
343
- rescue GoogleDrive::Error
344
- # this is a collision, which is to be treated as if it is not runnable
345
- $stderr.puts " #{ output } lost #{ @agent_name } on #{hostname}" if @debug
346
- return false, entry
347
- end
348
-
349
- check = entry[@agent_name]
350
- (status, running_hostname) = check.split(':')
351
- if hostname == running_hostname
352
- return true, entry
353
- end
354
- $stderr.puts " #{ output } lost #{ @agent_name } on #{hostname}" if @debug
355
- return false, entry
356
- end
357
-
358
- def complete_entry(update_entry)
359
- if update_entry.nil?
360
- update_entry = {}
361
- end
362
-
363
- if @subsumes && @subsumes.length > 0
364
- @subsumes.each do |subsumed_agent|
365
- update_entry[subsumed_agent] = 1
366
- end
367
- end
368
-
369
- update_entry[@agent_name] = 1
370
- entry = get_entry()
371
- entry.update update_entry
372
- @worksheet.save
373
- end
374
-
375
- def fail_entry(update_entry)
376
- if update_entry.nil?
377
- update_entry = { }
378
- end
379
- hostname = Socket.gethostname
380
- update_entry[@agent_name] = "F:#{ hostname }"
381
- entry = get_entry()
382
- entry.update update_entry
383
- @worksheet.save
384
- end
385
-
386
- def mail_error(error_message)
387
- output = ''
388
- @keys.keys.each do |key|
389
- output += [key, @keys[key] ].join(' ') + " "
390
- end
391
-
392
- prefix = [Socket.gethostname, output, @agent_name ].join(' ')
393
- begin
394
- Mail.defaults do
395
- delivery_method :smtp, {
396
- :address => "smtp.gmail.com",
397
- :port => 587,
398
- :domain => Socket.gethostname,
399
- :user_name => @config['guser'],
400
- :password => @config['gpass'],
401
- :authentication => 'plain',
402
- :enable_starttls_auto => true }
403
- end
404
-
405
- mail = Mail.new do
406
- from @config['reply_email']
407
- to @config['send_to']
408
- subject prefix
409
- body error_message.to_s
410
- end
411
-
412
- mail.deliver!
413
- rescue
414
- #DO NOTHING
415
- end
416
- end
417
- end
418
34
  end
@@ -0,0 +1,408 @@
1
+ require 'spreadsheet_agent/db'
2
+
3
+ module SpreadsheetAgent
4
+
5
+ # @note The license of this source is "MIT Licence"
6
+ # SpreadsheetAgent::Agent is designed to make it easy to create a single task which connects to
7
+ # a field within a record on a page within the configured SpreadsheetAgent compatible Google Spreadsheet,
8
+ # runs supplied code, and reports whether the job completed or ended in error. An agent can be configured
9
+ # to only run when certain prerequisite fields have completed. The data in these fields can be filled in by
10
+ # other SpreadsheetAgent::Agents, SpreadsheetAgent::Runners, or humans. Compute node configuration is available
11
+ # to prevent the agent from running more than a certain number of instances of itself, or not run if certain
12
+ # other agents or processes are running on the node. Finally, an agent can be configured to subsume another
13
+ # agent, and fill in the completion field for that agent in addition to its own when it completes successfully.
14
+ # @author Darin London Copyright 2013
15
+ class Agent < SpreadsheetAgent::Db
16
+
17
+ # The name of the field in the page to which the agent should report status
18
+ # @return [String]
19
+ attr_accessor :agent_name
20
+
21
+ # The name of the Page on the Google Spreadsheet that contains the record to be worked on by the agent
22
+ # @return [String]
23
+ attr_accessor :page_name
24
+
25
+ # Hash used to find the entry on the Google Spreadsheet Worksheet
26
+ # Keys are defined in config/agent.conf.yml.
27
+ # All keys configured as 'required: 1' must be included in the keys hash.
28
+ # Values specify values for those fields in the record on the page for which the agent is running.
29
+ # @return [Hash]
30
+ attr_accessor :keys
31
+
32
+ # Specify whether to print debug information (default false).
33
+ # When true, the agent code will print verbosely to STDERR. When false, and the process!
34
+ # returns a failure status, the agent will email all stdout and stderr to the email specified in the
35
+ # :config send_to value
36
+ # @return [Boolean]
37
+ attr_accessor :debug
38
+
39
+ # Optional array of prerequisites.
40
+ # If supplied, each entry is treated as a field name on the Google Worksheet which must contain a 1 in it for the record on the page before
41
+ # this agent will attempt to run.
42
+ # @return [Array]
43
+ attr_accessor :prerequisites
44
+
45
+ # @note This works on Linux with ps.
46
+ # Maximum number of instances of this agent to run on any particular server.
47
+ # If specified, newly instantiated agents will not attempt to run process! if
48
+ # there are max_selves instances already running on the same server. If not
49
+ # specified, all instances will attempt to run.
50
+ # @return [Integer]
51
+ attr_accessor :max_selves
52
+
53
+ # @note This works on Linux with ps.
54
+ # List of other processes, and the maximum number of running instances of the process that are allowed before this agent should avoid running on the given server.
55
+ # Hash of process_name => number of max_instances. If specified, each key is treated as a process name in ps. If the agent detects the specified number of
56
+ # max_instances of the given process (based on a line match), it will not attempt to run. If not specified, it will run regardless of the other processes
57
+ # already running on a server.
58
+ # @return [Hash]
59
+ attr_accessor :conflicts_with
60
+
61
+ # List of fields (agent or otherwise) that this agent should also complete when it completes successfully.
62
+ # Each entry is treated as a fields on the record which this agent subsumes. If the agent completes successfully these
63
+ # fields will be updated with a 1 in addition to the field for the agent.
64
+ # @return [Array]
65
+ attr_accessor :subsumes
66
+
67
+ # The GoogleDrive::Worksheet[http://rubydoc.info/gems/google_drive/0.3.6/GoogleDrive/Worksheet] that is being access by the agent.
68
+ # @return [GoogleDrive::Worksheet]
69
+ attr_reader :worksheet
70
+
71
+ # create a new SpreadsheetAgent::Agent
72
+ # @param attributes [Hash] keys are the attribute names, values are their values
73
+ # @option attributes [String] agent_name REQUIRED
74
+ # @option attributes [String] page_name REQUIRED
75
+ # @option attributes [Hash] keys REQUIRED
76
+ # @option attributes [String] config_file (see SpreadsheetAgent::DB)
77
+ # @option attributes [Boolean] debug
78
+ # @option attributes [Array] prerequisites
79
+ # @option attributes [Integer] max_selves
80
+ # @option attributes [Hash] conflicts_with
81
+ # @option attributes [Array] subsumes
82
+ def initialize(attributes)
83
+ @agent_name = attributes[:agent_name]
84
+ @page_name = attributes[:page_name]
85
+ @keys = attributes[:keys].clone
86
+ unless @agent_name && @page_name && @keys
87
+ raise SpreadsheetAgentError, "agent_name, page_name, and keys attributes are required!"
88
+ end
89
+ @config_file = attributes[:config_file]
90
+ build_db()
91
+
92
+ @worksheet = @db.worksheet_by_title(@page_name)
93
+ @debug = attributes[:debug]
94
+ if attributes[:prerequisites]
95
+ @prerequisites = attributes[:prerequisites].clone
96
+ end
97
+
98
+ @max_selves = attributes[:max_selves]
99
+ if attributes[:conflicts_with]
100
+ @conflicts_with = attributes[:conflicts_with].clone
101
+ end
102
+ if attributes[:subsumes]
103
+ @subsumes = attributes[:subsumes].clone
104
+ end
105
+ end
106
+
107
+ # If the agent does not have any conflicting processes (max_selves or conflicts_with)
108
+ # and if the entry field 'ready' has a 1, and any supplied prerequisite fields have a 1,
109
+ # gets the GoogleDrive::List[http://rubydoc.info/gems/google_drive/0.3.6/GoogleDrive/List] record, and
110
+ # passes it to the supplied Proc. This PROC must return a required boolean field indicating success or failure,
111
+ # and an optional hash of key - value fields that will be updated on the GoogleDrive::List record. Note, the updates
112
+ # are made regardless of the value of success. In fact, the agent can be configured to update
113
+ # different fields based on success or failure. Also, note that any value can be stored in the
114
+ # hash. This allows the agent to communicate any useful information to the google spreadsheet for other
115
+ # agents (SpreadsheetAgent::Agent, SpreadsheetAgent::Runner, or human) to use. The Proc must try at all
116
+ # costs to avoid terminating. If an error is encountered, it should return false for the success field
117
+ # to signal that the process failed. If no errors are encountered it should return true for the success
118
+ # field.
119
+ #
120
+ # @example Exit successfully, enters a 1 in the agent_name field
121
+ # $agent->process! do |entry|
122
+ # true
123
+ # end
124
+ #
125
+ # @example Same, but also updates the 'notice' field in the record along with the 1 in the agent_name field
126
+ # $agent->process! do |entry|
127
+ # [true, {:notice => 'There were 30 files processed'}]
128
+ # end
129
+ #
130
+ # @example Fails, enters f:server_hostname in the agent_name field
131
+ # $agent->process! do |entry|
132
+ # false
133
+ #
134
+ # @example Same, but also updates the 'notice' field in the record along with the failure notice
135
+ # $agent->process! do |entry|
136
+ # [false, {:notice => 'There were 10 files left to process!' }]
137
+ # end
138
+ #
139
+ # @example This agent passes different parameters based on success or failure
140
+ # $agent->process! do |entry|
141
+ # if $success
142
+ # true
143
+ # else
144
+ # [ false, {:notice => 'there were 10 remaining files'}]
145
+ # end
146
+ # end
147
+ #
148
+ # @param agent_code [Proc] Code to process entry
149
+ # @yieldparam [GoogleDrive::List] entry
150
+ # @yieldreturn [Boolean, Hash] success, (optional) hash of fields to update and values to update on the fields
151
+ def process!(&agent_code)
152
+ @worksheet.reload
153
+ no_problems = true
154
+ capture_output = nil
155
+ unless @debug
156
+ capture_output = CaptureIO.new
157
+ capture_output.start
158
+ end
159
+
160
+ begin
161
+ return true if has_conflicts()
162
+ (runnable, entry) = run_entry()
163
+ return false unless entry
164
+ return true unless runnable
165
+
166
+ success, update_entry = agent_code.call(entry)
167
+ if success
168
+ complete_entry(update_entry)
169
+ else
170
+ fail_entry(update_entry)
171
+ end
172
+ rescue
173
+ $stderr.puts "#{ $! }"
174
+ no_problems = false
175
+ end
176
+ unless capture_output.nil?
177
+ if no_problems
178
+ capture_output.stop
179
+ else
180
+ mail_error(capture_output.stop)
181
+ end
182
+ end
183
+ return no_problems
184
+ end
185
+
186
+ # The GoogleDrive::List[http://rubydoc.info/gems/google_drive/0.3.6/GoogleDrive/List] for the specified keys
187
+ # @return [GoogleDrive::List]
188
+ def get_entry
189
+ this_entry = nil
190
+ if @worksheet
191
+ @worksheet.list.each do |this_row|
192
+ keep_row = true
193
+
194
+ @config['key_fields'].keys.reject { |key_field|
195
+ !(@config['key_fields'][key_field]["required"]) && !(@keys[key_field])
196
+ }.each do |key|
197
+ break unless keep_row
198
+ keep_row = (this_row[key] == @keys[key])
199
+ end
200
+
201
+ if keep_row
202
+ return this_row
203
+ end
204
+ end
205
+ end
206
+ end
207
+
208
+ private
209
+
210
+ def has_conflicts
211
+ return unless (@max_selves || @conflicts_with) # nothing conflicts here
212
+
213
+ running_conflicters = {}
214
+ self_name = File.basename $0
215
+
216
+ begin
217
+ conflicting_in = Open3.popen3('ps','-eo','pid,command')[1]
218
+ conflicting_in.lines.each do |line|
219
+ unless(
220
+ (line.match(/emacs\s+|vim*\s+|pico\s+/)) ||
221
+ (line.match("#{ $$ }"))
222
+ )
223
+ if @max_selves && line.match(self_name)
224
+ if running_conflicters[@agent_name].nil?
225
+ running_conflicters[@agent_name] = 1
226
+ else
227
+ running_conflicters[@agent_name] += 1
228
+ end
229
+
230
+ if running_conflicters[@agent_name] == @max_selves
231
+ $stderr.puts "max_selves limit reached" if @debug
232
+ conflicting_in.close
233
+ return true
234
+ end
235
+ end
236
+
237
+ if @conflicts_with
238
+ @conflicts_with.keys.each do |conflicter|
239
+ if line.match(conflicter)
240
+ if running_conflicters[conflicter].nil?
241
+ running_conflicters[conflicter] = 1
242
+ else
243
+ running_conflicters[conflicter] += 1
244
+ end
245
+ if running_conflicters[conflicter] >= @conflicts_with[conflicter]
246
+ $stderr.puts "conflicts with #{ conflicter }" if @debug
247
+ conflicting_in.close
248
+ return true
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+ end
255
+ conflicting_in.close
256
+ return false
257
+
258
+ rescue
259
+ $stderr.puts "Couldnt check conflicts #{ $! }" if @debug
260
+ return true
261
+ end
262
+
263
+ end
264
+
265
+ # this call initiates a race resistant attempt to make sure that there is only 1
266
+ # clear 'winner' among N potential agents attempting to run the same goal on the
267
+ # same spreadsheet agent's cell
268
+ def run_entry
269
+ entry = get_entry()
270
+ output = '';
271
+ @keys.keys.select { |k| @config['key_fields'][k] && @keys[k] }.each do |key|
272
+ output += [ key, @keys[key] ].join(' ') + " "
273
+ end
274
+
275
+ unless entry
276
+ $stderr.puts "#{ output } is not supported on #{ @page_name }" if @debug
277
+ return
278
+ end
279
+
280
+ unless entry['ready'] == "1"
281
+ $stderr.puts "#{ output } is not ready to run #{ @agent_name }" if @debug
282
+ return false, entry
283
+ end
284
+
285
+ if entry['complete'] == "1"
286
+ $stderr.puts "All goals are completed for #{ output }" if @debug
287
+ return false, entry
288
+ end
289
+
290
+ if entry[@agent_name]
291
+ (status, running_hostname) = entry[@agent_name].split(':')
292
+
293
+ case status
294
+ when 'r'
295
+ $stderr.puts " #{ output } is already running #{ @agent_name } on #{ running_hostname }" if @debug
296
+ return false, entry
297
+
298
+ when "1"
299
+ $stderr.puts " #{ output } has already run #{ @agent_name }" if @debug
300
+ return false, entry
301
+
302
+ when 'F'
303
+ $stderr.puts " #{ output } has already Failed #{ @agent_name }" if @debug
304
+ return false, entry
305
+ end
306
+ end
307
+
308
+ if @prerequisites
309
+ @prerequisites.each do |prereq_field|
310
+ unless entry[prereq_field] == "1"
311
+ $stderr.puts " #{ output } has not finished #{ prereq_field }" if @debug
312
+ return false, entry
313
+ end
314
+ end
315
+ end
316
+
317
+ # first attempt to set the hostname of the machine as the value of the agent
318
+ hostname = Socket.gethostname;
319
+ begin
320
+ entry.update @agent_name => "r:#{ hostname }"
321
+ @worksheet.save
322
+
323
+ rescue GoogleDrive::Error
324
+ # this is a collision, which is to be treated as if it is not runnable
325
+ $stderr.puts " #{ output } lost #{ @agent_name } on #{hostname}" if @debug
326
+ return false, entry
327
+ end
328
+
329
+ sleep 3
330
+ begin
331
+ @worksheet.reload
332
+ rescue GoogleDrive::Error
333
+ # this is a collision, which is to be treated as if it is not runnable
334
+ $stderr.puts " #{ output } lost #{ @agent_name } on #{hostname}" if @debug
335
+ return false, entry
336
+ end
337
+
338
+ check = entry[@agent_name]
339
+ (status, running_hostname) = check.split(':')
340
+ if hostname == running_hostname
341
+ return true, entry
342
+ end
343
+ $stderr.puts " #{ output } lost #{ @agent_name } on #{hostname}" if @debug
344
+ return false, entry
345
+ end
346
+
347
+ def complete_entry(update_entry)
348
+ if update_entry.nil?
349
+ update_entry = {}
350
+ end
351
+
352
+ if @subsumes && @subsumes.length > 0
353
+ @subsumes.each do |subsumed_agent|
354
+ update_entry[subsumed_agent] = 1
355
+ end
356
+ end
357
+
358
+ update_entry[@agent_name] = 1
359
+ entry = get_entry()
360
+ entry.update update_entry
361
+ @worksheet.save
362
+ end
363
+
364
+ def fail_entry(update_entry)
365
+ if update_entry.nil?
366
+ update_entry = { }
367
+ end
368
+ hostname = Socket.gethostname
369
+ update_entry[@agent_name] = "F:#{ hostname }"
370
+ entry = get_entry()
371
+ entry.update update_entry
372
+ @worksheet.save
373
+ end
374
+
375
+ def mail_error(error_message)
376
+ output = ''
377
+ @keys.keys.each do |key|
378
+ output += [key, @keys[key] ].join(' ') + " "
379
+ end
380
+
381
+ prefix = [Socket.gethostname, output, @agent_name ].join(' ')
382
+ begin
383
+ Mail.defaults do
384
+ delivery_method :smtp, {
385
+ :address => "smtp.gmail.com",
386
+ :port => 587,
387
+ :domain => Socket.gethostname,
388
+ :user_name => @config['guser'],
389
+ :password => @config['gpass'],
390
+ :authentication => 'plain',
391
+ :enable_starttls_auto => true }
392
+ end
393
+
394
+ mail = Mail.new do
395
+ from @config['reply_email']
396
+ to @config['send_to']
397
+ subject prefix
398
+ body error_message.to_s
399
+ end
400
+
401
+ mail.deliver!
402
+ rescue
403
+ #DO NOTHING
404
+ end
405
+ end
406
+
407
+ end #SpreadsheetAgent::Agent
408
+ end #SpreadsheetAgent
@@ -1,6 +1,3 @@
1
- # Author: Darin London
2
- # The license of this source is "MIT Licence"
3
-
4
1
  require 'google_drive'
5
2
  require 'psych'
6
3
 
@@ -1,6 +1,3 @@
1
- # Author: Darin London
2
- # The license of this source is "MIT Licence"
3
-
4
1
  module SpreadsheetAgent
5
2
 
6
3
  # SpreadsheetAgent::Error is an extension of Error that SpreadsheetAgent classes throw
@@ -4,7 +4,10 @@ require 'spreadsheet_agent/db'
4
4
  class TC_SpreadsheetAgentDbTest < Test::Unit::TestCase
5
5
  def test_instantiate_db
6
6
  conf_file = File.expand_path(File.dirname( __FILE__ )) + '/../config/agent.conf.yml'
7
- assert File.exists?( conf_file ), "You must create a valid test Google Spreadsheet and a valid #{ conf_file } configuration file pointing to it to run the tests. See README.txt file for more information on how to run the tests."
7
+ unless File.exists?( conf_file )
8
+ $stderr.puts "You must create a valid test Google Spreadsheet and a valid #{ conf_file } configuration file pointing to it to run the tests. See README.txt file for more information on how to run the tests."
9
+ exit
10
+ end
8
11
 
9
12
  google_db = SpreadsheetAgent::Db.new()
10
13
  assert_not_nil google_db
@@ -13,7 +13,7 @@ class TC_SpreadsheetAgentRunnerTest < Test::Unit::TestCase
13
13
 
14
14
  unless File.exists? @config_file
15
15
  $stderr.puts "You must create a valid test Google Spreadsheet and a valid #{ @config_file } configuration file pointing to it to run the tests. See README.txt file for more information on how to run the tests."
16
- exit(1)
16
+ exit
17
17
  end
18
18
  @test_agent_bin = find_bin() + 'agent_bin'
19
19
  @testing_pages = nil
@@ -14,7 +14,7 @@ class TC_SpreadsheetAgentTest < Test::Unit::TestCase
14
14
 
15
15
  unless File.exists? @config_file
16
16
  $stderr.puts "You must create a valid test Google Spreadsheet and a valid #{ @config_file } configuration file pointing to it to run the tests. See README.txt file for more information on how to run the tests."
17
- exit(1)
17
+ exit
18
18
  end
19
19
 
20
20
  @testing_page_name = 'testing'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spreadsheet_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -116,6 +116,7 @@ executables: []
116
116
  extensions: []
117
117
  extra_rdoc_files: []
118
118
  files:
119
+ - lib/spreadsheet_agent/agent.rb
119
120
  - lib/spreadsheet_agent/db.rb
120
121
  - lib/spreadsheet_agent/error.rb
121
122
  - lib/spreadsheet_agent/runner.rb
@@ -125,6 +126,7 @@ files:
125
126
  - test/spreadsheet_agent_db_test.rb
126
127
  - test/spreadsheet_agent_runner_test.rb
127
128
  - test/spreadsheet_agent_test.rb
129
+ - config/test.config.yml
128
130
  homepage: http://rubygems.org/gems/spreadsheet_agent
129
131
  licenses:
130
132
  - MIT
@@ -155,3 +157,4 @@ test_files:
155
157
  - test/spreadsheet_agent_db_test.rb
156
158
  - test/spreadsheet_agent_runner_test.rb
157
159
  - test/spreadsheet_agent_test.rb
160
+ has_rdoc: