neptune 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/README +4 -0
  2. data/doc/BabelHelper.html +393 -376
  3. data/doc/BadConfigurationException.html +121 -127
  4. data/doc/CommonFunctions.html +237 -265
  5. data/doc/ExodusHelper.html +820 -0
  6. data/doc/ExodusTaskInfo.html +263 -0
  7. data/doc/FileNotFoundException.html +121 -127
  8. data/doc/NeptuneHelper.html +527 -592
  9. data/doc/NeptuneManagerClient.html +696 -0
  10. data/doc/NeptuneManagerException.html +139 -0
  11. data/doc/Object.html +334 -236
  12. data/doc/TaskInfo.html +428 -0
  13. data/doc/created.rid +8 -5
  14. data/doc/images/add.png +0 -0
  15. data/doc/images/delete.png +0 -0
  16. data/doc/images/tag_blue.png +0 -0
  17. data/doc/images/transparent.png +0 -0
  18. data/doc/index.html +74 -142
  19. data/doc/js/darkfish.js +99 -62
  20. data/doc/js/jquery.js +15 -29
  21. data/doc/js/navigation.js +142 -0
  22. data/doc/js/search.js +94 -0
  23. data/doc/js/search_index.js +1 -0
  24. data/doc/js/searcher.js +228 -0
  25. data/doc/table_of_contents.html +226 -0
  26. data/lib/babel.rb +116 -50
  27. data/lib/custom_exceptions.rb +2 -2
  28. data/lib/exodus.rb +311 -0
  29. data/lib/exodus_task_info.rb +36 -0
  30. data/lib/neptune.rb +52 -18
  31. data/lib/{app_controller_client.rb → neptune_manager_client.rb} +54 -38
  32. data/lib/task_info.rb +155 -0
  33. data/test/{unit/test_babel.rb → test_babel.rb} +161 -26
  34. data/test/{unit/test_common_functions.rb → test_common_functions.rb} +1 -1
  35. data/test/test_exodus.rb +687 -0
  36. data/test/{unit/test_neptune.rb → test_neptune.rb} +28 -17
  37. data/test/{unit/test_app_controller_client.rb → test_neptune_manager_client.rb} +15 -16
  38. data/test/test_task_info.rb +32 -0
  39. data/test/{unit/ts_all.rb → ts_all.rb} +3 -1
  40. metadata +30 -34
  41. data/doc/AppControllerClient.html +0 -702
  42. data/doc/AppControllerException.html +0 -145
  43. data/doc/bin/neptune.html +0 -56
  44. data/doc/js/quicksearch.js +0 -114
  45. data/doc/js/thickbox-compressed.js +0 -10
  46. data/doc/lib/app_controller_client_rb.html +0 -60
  47. data/doc/lib/babel_rb.html +0 -68
  48. data/doc/lib/common_functions_rb.html +0 -70
  49. data/doc/lib/custom_exceptions_rb.html +0 -54
  50. data/doc/lib/neptune_rb.html +0 -60
  51. data/test/integration/tc_c.rb +0 -57
  52. data/test/integration/tc_dfsp.rb +0 -37
  53. data/test/integration/tc_dwssa.rb +0 -38
  54. data/test/integration/tc_erlang.rb +0 -183
  55. data/test/integration/tc_mapreduce.rb +0 -282
  56. data/test/integration/tc_mpi.rb +0 -160
  57. data/test/integration/tc_storage.rb +0 -209
  58. data/test/integration/tc_upc.rb +0 -75
  59. data/test/integration/tc_x10.rb +0 -94
  60. data/test/integration/test_helper.rb +0 -135
  61. data/test/integration/ts_neptune.rb +0 -40
@@ -1,8 +1,8 @@
1
1
  # Programmer: Chris Bunch
2
2
 
3
- # A special class of exceptions that are thrown whenever the AppController
3
+ # A special class of exceptions that are thrown whenever the NeptuneManager
4
4
  # experiences an unexpected result.
5
- class AppControllerException < Exception
5
+ class NeptuneManagerException < Exception
6
6
  end
7
7
 
8
8
 
@@ -0,0 +1,311 @@
1
+ #!/usr/bin/env ruby
2
+ # Programmer: Chris Bunch
3
+
4
+
5
+ require 'babel'
6
+ require 'custom_exceptions'
7
+ require 'exodus_task_info'
8
+
9
+
10
+ # Exodus provides further improvements to Babel. Instead of making users tell
11
+ # us what compute, storage, and queue services they want to use (required for
12
+ # babel calls), Exodus will automatically handle this for us. Callers need
13
+ # to specify what clouds their job can run over, and Exodus will automatically
14
+ # select the best cloud for their job and run it there.
15
+ def exodus(jobs)
16
+ if jobs.class == Hash
17
+ job_given_as_hash = true
18
+ jobs = [jobs]
19
+ elsif jobs.class == Array
20
+ job_given_as_hash = false
21
+ ExodusHelper.ensure_all_jobs_are_hashes(jobs)
22
+ else
23
+ raise BadConfigurationException.new("jobs was a #{jobs.class}, which " +
24
+ "is not an acceptable class type")
25
+ end
26
+
27
+ tasks = []
28
+
29
+ jobs.each { |job|
30
+ ExodusHelper.ensure_all_params_are_present(job)
31
+ profiling_info = ExodusHelper.get_profiling_info(job)
32
+ clouds_to_run_task_on = ExodusHelper.get_clouds_to_run_task_on(job,
33
+ profiling_info)
34
+ babel_tasks_to_run = ExodusHelper.generate_babel_tasks(job,
35
+ clouds_to_run_task_on)
36
+ dispatched_tasks = ExodusHelper.run_job(babel_tasks_to_run)
37
+ tasks << ExodusTaskInfo.new(dispatched_tasks)
38
+ }
39
+
40
+ if job_given_as_hash
41
+ return tasks[0]
42
+ else
43
+ return tasks
44
+ end
45
+ end
46
+
47
+
48
+ # This module provides convenience functions for exodus(), to avoid cluttering
49
+ # up Object or Kernel's namespace.
50
+ module ExodusHelper
51
+
52
+
53
+ # A list of clouds that users can run tasks on via Exodus.
54
+ SUPPORTED_CLOUDS = [:AmazonEC2, :Eucalyptus, :GoogleAppEngine,
55
+ :MicrosoftAzure]
56
+
57
+
58
+ CLOUD_CREDENTIALS = {
59
+ :AmazonEC2 => [:EC2_ACCESS_KEY, :EC2_SECRET_KEY, :EC2_URL, :S3_URL,
60
+ :S3_bucket_name],
61
+ :Eucalyptus => [:EUCA_ACCESS_KEY, :EUCA_SECRET_KEY, :EUCA_URL,
62
+ :WALRUS_URL, :Walrus_bucket_name],
63
+ :GoogleAppEngine => [:appid, :appcfg_cookies, :function,
64
+ :GStorage_bucket_name],
65
+ :MicrosoftAzure => [:WAZ_Account_Name, :WAZ_Access_Key,
66
+ :WAZ_Container_Name]
67
+ }
68
+
69
+
70
+ CLOUD_BABEL_PARAMS = {
71
+ :AmazonEC2 => {
72
+ :storage => "s3",
73
+ :engine => "executor-sqs"
74
+ },
75
+ :Eucalyptus => {
76
+ :storage => "walrus",
77
+ :engine => "executor-rabbitmq"
78
+ },
79
+ :GoogleAppEngine => {
80
+ :storage => "gstorage",
81
+ :engine => "appengine-push-q"
82
+ },
83
+ :MicrosoftAzure => {
84
+ :storage => "waz-storage",
85
+ :engine => "waz-push-q"
86
+ }
87
+ }
88
+
89
+
90
+ OPTIMIZE_FOR_CHOICES = [:performance, :cost, :auto]
91
+
92
+
93
+ # Given an Array of jobs to run, ensures that they are all Hashes, the
94
+ # standard format for Neptune jobs.
95
+ def self.ensure_all_jobs_are_hashes(jobs)
96
+ jobs.each { |job|
97
+ if job.class != Hash
98
+ raise BadConfigurationException.new("A job passed to exodus() was " +
99
+ "not a Hash, but was a #{job.class}")
100
+ end
101
+ }
102
+ end
103
+
104
+
105
+ # Given an Exodus job, validates its parameters, raising a
106
+ # BadConfigurationException for any missing params.
107
+ def self.ensure_all_params_are_present(job)
108
+ if job[:clouds_to_use].nil?
109
+ raise BadConfigurationException.new(":clouds_to_use was not specified")
110
+ else
111
+ self.convert_clouds_to_use_to_array(job)
112
+ self.validate_clouds_to_use(job)
113
+ self.validate_optimize_for_param(job)
114
+ self.validate_files_argv_executable(job)
115
+ end
116
+ end
117
+
118
+
119
+ # Given a single Exodus job, checks to make sure it has either an Array
120
+ # of Strings or a single String listing the clouds that a given task can
121
+ # run on. Raises a BadConfigurationException if :clouds_to_use is not in
122
+ # the right format.
123
+ def self.convert_clouds_to_use_to_array(job)
124
+ clouds_class = job[:clouds_to_use].class
125
+ if clouds_class == Symbol
126
+ job[:clouds_to_use] = [job[:clouds_to_use]]
127
+ elsif clouds_class == Array
128
+ job[:clouds_to_use].each { |cloud|
129
+ if cloud.class != Symbol
130
+ raise BadConfigurationException.new("#{cloud} was not a String, " +
131
+ "but was a #{cloud.class}")
132
+ end
133
+ }
134
+ else
135
+ raise BadConfigurationException.new("#{job[:clouds_to_use]} was not " +
136
+ "a String or Array, but was a #{clouds_class}")
137
+ end
138
+ end
139
+
140
+
141
+ # Given a single Exodus job, checks to make sure that we can actually run
142
+ # it in this version of Neptune, and that the user has given us all the
143
+ # credentials needed to use that cloud.
144
+ def self.validate_clouds_to_use(job)
145
+ self.ensure_credentials_are_in_correct_format(job)
146
+ self.propogate_credentials_from_environment(job)
147
+
148
+ job[:clouds_to_use].each { |cloud|
149
+ if SUPPORTED_CLOUDS.include?(cloud)
150
+ CLOUD_CREDENTIALS[cloud].each { |required_credential|
151
+ val_for_credential = job[:credentials][required_credential]
152
+ if val_for_credential.nil? or val_for_credential.empty?
153
+ raise BadConfigurationException.new("To use #{cloud}, " +
154
+ "#{required_credential} must be specified.")
155
+ end
156
+ }
157
+ else
158
+ raise BadConfigurationException.new("#{cloud} was specified as in " +
159
+ ":clouds_to_use, which is not a supported cloud.")
160
+ end
161
+ }
162
+ end
163
+
164
+
165
+ def self.ensure_credentials_are_in_correct_format(job)
166
+ if job[:credentials].nil?
167
+ raise BadConfigurationException.new("No credentials were specified.")
168
+ end
169
+
170
+ if job[:credentials].class != Hash
171
+ raise BadConfigurationException.new("Credentials given were not a " +
172
+ "Hash, but were a #{job[:credentials].class}")
173
+ end
174
+ end
175
+
176
+
177
+ # Searches the caller's environment variables, and adds any that could
178
+ # be used in this Exodus job. Only takes in credentials from the
179
+ # environment if the job does not specify it.
180
+ def self.propogate_credentials_from_environment(job)
181
+ CLOUD_CREDENTIALS.each { |cloud_name, credential_list|
182
+ credential_list.each { |cred|
183
+ if job[:credentials][cred].nil? and !ENV[cred.to_s].nil?
184
+ job[:credentials][cred] = ENV[cred.to_s]
185
+ end
186
+ }
187
+ }
188
+ end
189
+
190
+
191
+ def self.validate_optimize_for_param(job)
192
+ if job[:optimize_for].nil?
193
+ raise BadConfigurationException.new(":optimize_for needs to be " +
194
+ "specified when running Exodus jobs")
195
+ end
196
+
197
+ if !OPTIMIZE_FOR_CHOICES.include?(job[:optimize_for])
198
+ raise BadConfigurationException.new("The value given for " +
199
+ ":optimize_for was not an acceptable value. Acceptable values are: " +
200
+ "#{OPTIMIZE_FOR_CHOICES.join(', ')}")
201
+ end
202
+ end
203
+
204
+
205
+ def self.validate_files_argv_executable(job)
206
+ [:code, :argv, :executable].each { |param|
207
+ if job[param].nil?
208
+ raise BadConfigurationException.new("#{param} was not specified")
209
+ end
210
+ }
211
+ end
212
+
213
+
214
+ def self.get_profiling_info(job)
215
+ key = self.get_key_from_job_data(job)
216
+ neptune_manager = BabelHelper.get_neptune_manager_client(job)
217
+ return neptune_manager.get_profiling_info(key)
218
+ end
219
+
220
+
221
+ # TODO(cgb): what is a job's key?
222
+ def self.get_key_from_job_data(job)
223
+ return job[:code]
224
+ end
225
+
226
+
227
+ def self.get_clouds_to_run_task_on(job, profiling_info)
228
+ optimize_for = job[:optimize_for]
229
+ if optimize_for == :performance or optimize_for == :cost
230
+ return self.get_minimum_val_in_data(job, profiling_info)
231
+ else
232
+ return self.find_optimal_cloud_for_task(job, profiling_info)
233
+ end
234
+ end
235
+
236
+
237
+ def self.get_minimum_val_in_data(job, profiling_info)
238
+ min_cloud = nil
239
+ min_val = 1_000_000 # infinity
240
+ optimize_for = job[:optimize_for].to_s
241
+
242
+ clouds_to_run_on = []
243
+ job[:clouds_to_use].each { |cloud|
244
+ # If we have no information on this cloud, then add it to the list
245
+ # of clouds we should run the task on, since it could potentially be
246
+ # lower than the minimum in the data we've seen so far.
247
+ if profiling_info[cloud.to_s].nil?
248
+ clouds_to_run_on << cloud
249
+ next
250
+ end
251
+
252
+ val = self.average(profiling_info[cloud.to_s][optimize_for])
253
+ if val < min_val
254
+ min_cloud = cloud
255
+ min_val = val
256
+ end
257
+ }
258
+
259
+ if !min_cloud.nil?
260
+ clouds_to_run_on << min_cloud
261
+ end
262
+
263
+ return clouds_to_run_on
264
+ end
265
+
266
+
267
+ # Given an Array of values, calculates and returns their average.
268
+ def self.average(vals)
269
+ sum = vals.reduce(0.0) { |running_total, val|
270
+ running_total + val
271
+ }
272
+
273
+ return sum / vals.length
274
+ end
275
+
276
+
277
+ def self.find_optimal_cloud_for_task(job, profiling_info)
278
+ raise NotImplementedError
279
+ end
280
+
281
+
282
+ def self.generate_babel_tasks(job, clouds_to_run_task_on)
283
+ tasks = []
284
+
285
+ clouds_to_run_task_on.each { |cloud|
286
+ task = { :type => "babel",
287
+ :code => job[:code],
288
+ :argv => job[:argv],
289
+ :executable => job[:executable],
290
+ :is_remote => false,
291
+ :run_local => false
292
+ }
293
+
294
+ CLOUD_CREDENTIALS[cloud].each { |credential|
295
+ task[credential] = job[:credentials][credential]
296
+ }
297
+
298
+ task.merge!(CLOUD_BABEL_PARAMS[cloud])
299
+ tasks << task
300
+ }
301
+
302
+ return tasks
303
+ end
304
+
305
+
306
+ def self.run_job(tasks_to_run)
307
+ return babel(tasks_to_run)
308
+ end
309
+
310
+
311
+ end
@@ -0,0 +1,36 @@
1
+ # Programmer: Chris Bunch
2
+
3
+
4
+ require 'task_info'
5
+
6
+
7
+ class ExodusTaskInfo
8
+
9
+
10
+ def initialize(dispatched_babel_tasks)
11
+ @babel_tasks = dispatched_babel_tasks
12
+ end
13
+
14
+
15
+ def to_s
16
+ method_missing(:to_s)
17
+ end
18
+
19
+
20
+ def method_missing(id, *args, &block)
21
+ loop {
22
+ @babel_tasks.each_with_index { |task, i|
23
+ begin
24
+ Timeout::timeout(2) {
25
+ result = task.send(id, *args, &block)
26
+ return result
27
+ }
28
+ rescue Timeout::Error
29
+ next
30
+ end
31
+ }
32
+ }
33
+ end
34
+
35
+
36
+ end
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/ruby
2
2
  # Programmer: Chris Bunch (cgb@cs.ucsb.edu)
3
3
 
4
- require 'app_controller_client'
5
4
  require 'common_functions'
6
5
  require 'custom_exceptions'
6
+ require 'neptune_manager_client'
7
7
 
8
8
 
9
9
  # Setting verbose to nil here suppresses the otherwise
@@ -13,7 +13,7 @@ $VERBOSE = nil
13
13
 
14
14
 
15
15
  # A list of all the Neptune job types that we support
16
- ALLOWED_JOB_TYPES = %w{acl cicero compile erlang mpi input output ssa babel upc x10}
16
+ ALLOWED_JOB_TYPES = %w{acl cicero compile erlang mpi input output ssa babel upc x10 mapreduce}
17
17
 
18
18
 
19
19
  # The string to display for disallowed job types.
@@ -32,7 +32,7 @@ NO_OUTPUT_NEEDED = ["input"]
32
32
 
33
33
  # A list of storage mechanisms that we can use to store and retrieve
34
34
  # data to for Neptune jobs.
35
- ALLOWED_STORAGE_TYPES = ["appdb", "gstorage", "s3", "walrus"]
35
+ ALLOWED_STORAGE_TYPES = %w{appdb gstorage s3 walrus waz-storage}
36
36
 
37
37
 
38
38
  # A list of jobs that require some kind of work to be done before
@@ -55,23 +55,50 @@ public
55
55
  # the user can request to run a job, retrieve a job's output, or modify the
56
56
  # access policy (ACL) for the output of a job. By default, job data is private,
57
57
  # but a Neptune job can be used to set it to public later (and vice-versa).
58
- def neptune(params)
58
+ def neptune(jobs)
59
59
  # Kernel.puts "Received a request to run a job."
60
60
  # Kernel.puts params[:type]
61
+ if jobs.class == Hash
62
+ jobs = [jobs]
63
+ end
61
64
 
62
- job_data = NeptuneHelper.get_job_data(params)
63
- NeptuneHelper.validate_storage_params(job_data)
64
- # Kernel.puts "job data = #{job_data.inspect}"
65
- keyname = job_data["@keyname"]
65
+ job_data_list = []
66
+ shadow_ip = nil
67
+ ssh_args = ""
68
+ secret = ""
69
+ controller = nil
66
70
 
67
- shadow_ip = CommonFunctions.get_from_yaml(keyname, :shadow)
68
- secret = CommonFunctions.get_secret_key(keyname)
69
- ssh_key = File.expand_path("~/.appscale/#{keyname}.key")
70
- ssh_args = "-i ~/.appscale/#{keyname}.key -o StrictHostkeyChecking=no "
71
+ jobs.each { |params|
72
+ job_data = NeptuneHelper.get_job_data(params)
73
+ NeptuneHelper.validate_storage_params(job_data)
74
+ # Kernel.puts "job data = #{job_data.inspect}"
75
+ keyname = job_data["@keyname"]
71
76
 
72
- controller = AppControllerClient.new(shadow_ip, secret)
73
- NeptuneHelper.do_preprocessing(job_data, controller)
74
- return NeptuneHelper.run_job(job_data, ssh_args, shadow_ip, secret)
77
+ shadow_ip = CommonFunctions.get_from_yaml(keyname, :shadow)
78
+ secret = CommonFunctions.get_secret_key(keyname)
79
+ ssh_key = File.expand_path("~/.appscale/#{keyname}.key")
80
+ ssh_args = "-i ~/.appscale/#{keyname}.key -o StrictHostkeyChecking=no "
81
+
82
+ controller = NeptuneManagerClient.new(shadow_ip, secret)
83
+ NeptuneHelper.do_preprocessing(job_data, controller)
84
+ job_data_list << job_data
85
+ }
86
+
87
+ if job_data_list.length == 1
88
+ return NeptuneHelper.run_job(job_data_list[0], ssh_args, shadow_ip,
89
+ secret)
90
+ else # right now we only support batch run_job operations
91
+ msg = controller.start_neptune_job(job_data_list)
92
+ result = {}
93
+ result[:msg] = msg
94
+ if result[:msg] =~ /job is now running\Z/
95
+ result[:result] = :success
96
+ else
97
+ result[:result] = :failure
98
+ end
99
+
100
+ return result
101
+ end
75
102
  end
76
103
 
77
104
 
@@ -145,6 +172,9 @@ module NeptuneHelper
145
172
  def self.preprocess_mpi(job_data, controller)
146
173
  self.require_param("@nodes_to_use", job_data)
147
174
  self.require_param("@procs_to_use", job_data)
175
+ self.require_param("@output", job_data)
176
+ self.require_param("@error", job_data)
177
+ self.require_param("@metadata", job_data)
148
178
 
149
179
  if job_data["@procs_to_use"]
150
180
  p = job_data["@procs_to_use"]
@@ -200,7 +230,7 @@ module NeptuneHelper
200
230
  end
201
231
 
202
232
 
203
- # This helper method asks the AppController if the named file exists,
233
+ # This helper method asks the NeptuneManager if the named file exists,
204
234
  # and if it does not, throws an exception.
205
235
  def self.require_file_to_exist(file, job_data, controller)
206
236
  if controller.does_file_exist?(file, job_data)
@@ -232,6 +262,8 @@ module NeptuneHelper
232
262
  self.require_param("@code", job_data)
233
263
  self.require_param("@engine", job_data)
234
264
  self.require_param("@output", job_data)
265
+ self.require_param("@error", job_data)
266
+ self.require_param("@metadata", job_data)
235
267
 
236
268
  # For most code types, the file's name given is the thing to exec.
237
269
  # For Java, the actual file to search for is whatever the user gives
@@ -243,6 +275,8 @@ module NeptuneHelper
243
275
 
244
276
  self.require_file_to_exist(code_file_name, job_data, controller)
245
277
  self.require_file_to_not_exist(job_data["@output"], job_data, controller)
278
+ self.require_file_to_not_exist(job_data["@error"], job_data, controller)
279
+ self.require_file_to_not_exist(job_data["@metadata"], job_data, controller)
246
280
 
247
281
  if job_data["@argv"]
248
282
  argv = job_data["@argv"]
@@ -275,7 +309,7 @@ module NeptuneHelper
275
309
 
276
310
  # This method takes in a hash in the format that users write neptune/babel
277
311
  # jobs in {:a => "b"} and converts it to the legacy format that Neptune
278
- # used to use {"@a" => "b"}, and is understood by the AppController.
312
+ # used to use {"@a" => "b"}, and is understood by the NeptuneManager.
279
313
  def self.get_job_data(params)
280
314
  job_data = {}
281
315
  params.each { |k, v|
@@ -500,7 +534,7 @@ module NeptuneHelper
500
534
  # This method actually runs the Neptune job, given information about the job
501
535
  # as well as information about the node to send the request to.
502
536
  def self.run_job(job_data, ssh_args, shadow_ip, secret)
503
- controller = AppControllerClient.new(shadow_ip, secret)
537
+ controller = NeptuneManagerClient.new(shadow_ip, secret)
504
538
 
505
539
  # TODO - right now the job is assumed to succeed in many cases
506
540
  # need to investigate the various failure scenarios