neptune 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +6 -0
- data/bin/neptune +0 -1
- data/doc/AppControllerClient.html +23 -16
- data/doc/AppControllerException.html +2 -1
- data/doc/BabelHelper.html +19 -15
- data/doc/BadConfigurationException.html +3 -0
- data/doc/CommonFunctions.html +12 -16
- data/doc/FileNotFoundException.html +3 -0
- data/doc/NeptuneHelper.html +55 -39
- data/doc/Object.html +14 -17
- data/doc/bin/neptune.html +1 -1
- data/doc/created.rid +7 -7
- data/doc/lib/app_controller_client_rb.html +1 -1
- data/doc/lib/babel_rb.html +1 -1
- data/doc/lib/common_functions_rb.html +1 -1
- data/doc/lib/custom_exceptions_rb.html +1 -1
- data/doc/lib/neptune_rb.html +1 -1
- data/lib/app_controller_client.rb +33 -1
- data/lib/babel.rb +29 -9
- data/lib/common_functions.rb +15 -11
- data/lib/custom_exceptions.rb +8 -0
- data/lib/neptune.rb +91 -58
- data/test/unit/test_babel.rb +82 -2
- metadata +4 -4
data/doc/Object.html
CHANGED
@@ -135,13 +135,8 @@
|
|
135
135
|
|
136
136
|
<div id="description">
|
137
137
|
|
138
|
-
<p>
|
139
|
-
|
140
|
-
monkey-patched method should be job, while the others could probably be
|
141
|
-
folded into either a Neptune-specific class or into <a
|
142
|
-
href="CommonFunctions.html">CommonFunctions</a>. TODO(cbunch): This
|
143
|
-
doesn’t look like it does anything - run the integration test and confirm
|
144
|
-
one way or the other.</p>
|
138
|
+
<p>Since we’re monkeypatching <a href="Object.html">Object</a> to add
|
139
|
+
neptune() and babel(), a short blurb is necessary here to make rdoc happy.</p>
|
145
140
|
|
146
141
|
</div>
|
147
142
|
|
@@ -255,13 +250,20 @@ handles this.</p>
|
|
255
250
|
<div class="method-source-code"
|
256
251
|
id="babel-source">
|
257
252
|
<pre>
|
258
|
-
<span class="ruby-comment"># File lib/babel.rb, line
|
253
|
+
<span class="ruby-comment"># File lib/babel.rb, line 43</span>
|
259
254
|
def babel(params)
|
260
255
|
<span class="ruby-comment"># Since this whole function should run asynchronously, we run it as a future.</span>
|
261
256
|
<span class="ruby-comment"># It automatically starts running in a new thread, and attempting to get the</span>
|
262
257
|
<span class="ruby-comment"># value of what this returns causes it to block until the job completes.</span>
|
263
258
|
future {
|
259
|
+
if params[:storage]
|
260
|
+
params[:is_remote] = true
|
261
|
+
else
|
262
|
+
params[:is_remote] = false
|
263
|
+
end
|
264
|
+
|
264
265
|
job_data = <span class="ruby-constant">BabelHelper</span>.convert_from_neptune_params(params)
|
266
|
+
|
265
267
|
<span class="ruby-constant">NeptuneHelper</span>.validate_storage_params(job_data) <span class="ruby-comment"># adds in S3 storage params</span>
|
266
268
|
|
267
269
|
<span class="ruby-comment"># :code is the only required parameter - everything else can use default vals</span>
|
@@ -280,12 +282,7 @@ def babel(params)
|
|
280
282
|
end
|
281
283
|
|
282
284
|
<span class="ruby-constant">BabelHelper</span>.run_job(job_data)
|
283
|
-
<span class="ruby-comment"># So actually retrieving the job's output is done via a promise, so only if</span>
|
284
|
-
<span class="ruby-comment"># the user actually uses the value do we actually go and poll for output.</span>
|
285
|
-
<span class="ruby-comment"># The running of the job is done above, outside of the promise, so</span>
|
286
|
-
<span class="ruby-comment"># the job is always run, regardless of whether or not we get its output.</span>
|
287
285
|
<span class="ruby-constant">BabelHelper</span>.wait_and_get_output(job_data)
|
288
|
-
<span class="ruby-comment"># promise { BabelHelper.wait_and_get_output(job_data) }</span>
|
289
286
|
}
|
290
287
|
end</pre>
|
291
288
|
</div>
|
@@ -323,14 +320,14 @@ vice-versa).</p>
|
|
323
320
|
<div class="method-source-code"
|
324
321
|
id="neptune-source">
|
325
322
|
<pre>
|
326
|
-
<span class="ruby-comment"># File lib/neptune.rb, line
|
323
|
+
<span class="ruby-comment"># File lib/neptune.rb, line 58</span>
|
327
324
|
def neptune(params)
|
328
|
-
<span class="ruby-
|
329
|
-
<span class="ruby-
|
325
|
+
<span class="ruby-comment"># Kernel.puts "Received a request to run a job."</span>
|
326
|
+
<span class="ruby-comment"># Kernel.puts params[:type]</span>
|
330
327
|
|
331
328
|
job_data = <span class="ruby-constant">NeptuneHelper</span>.get_job_data(params)
|
332
329
|
<span class="ruby-constant">NeptuneHelper</span>.validate_storage_params(job_data)
|
333
|
-
<span class="ruby-
|
330
|
+
<span class="ruby-comment"># Kernel.puts "job data = #{job_data.inspect}"</span>
|
334
331
|
keyname = job_data[<span class="ruby-string">"@keyname"</span>]
|
335
332
|
|
336
333
|
shadow_ip = <span class="ruby-constant">CommonFunctions</span>.get_from_yaml(keyname, :shadow)
|
data/doc/bin/neptune.html
CHANGED
data/doc/created.rid
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
|
2
|
-
bin/neptune
|
3
|
-
lib/babel.rb
|
4
|
-
lib/neptune.rb
|
5
|
-
lib/custom_exceptions.rb
|
6
|
-
lib/app_controller_client.rb
|
7
|
-
lib/common_functions.rb
|
1
|
+
Sun, 12 Feb 2012 16:40:58 -0800
|
2
|
+
bin/neptune Sun, 12 Feb 2012 16:12:33 -0800
|
3
|
+
lib/babel.rb Sun, 12 Feb 2012 16:35:05 -0800
|
4
|
+
lib/neptune.rb Sun, 12 Feb 2012 16:34:30 -0800
|
5
|
+
lib/custom_exceptions.rb Sun, 12 Feb 2012 16:18:29 -0800
|
6
|
+
lib/app_controller_client.rb Sun, 12 Feb 2012 16:13:32 -0800
|
7
|
+
lib/common_functions.rb Sun, 12 Feb 2012 16:18:14 -0800
|
data/doc/lib/babel_rb.html
CHANGED
data/doc/lib/neptune_rb.html
CHANGED
@@ -12,12 +12,27 @@ require 'timeout'
|
|
12
12
|
# long calls unless necessary.
|
13
13
|
NO_TIMEOUT = -1
|
14
14
|
|
15
|
+
|
15
16
|
# A client that uses SOAP messages to communicate with the underlying cloud
|
16
17
|
# platform (here, AppScale). This client is similar to that used in the AppScale
|
17
18
|
# Tools, but with non-Neptune SOAP calls removed.
|
18
19
|
class AppControllerClient
|
19
|
-
|
20
|
+
|
21
|
+
|
22
|
+
# The SOAP client that we use to communicate with the AppController.
|
23
|
+
attr_accessor :conn
|
24
|
+
|
25
|
+
|
26
|
+
# The IP address of the AppController that we will be connecting to.
|
27
|
+
attr_accessor :ip
|
20
28
|
|
29
|
+
|
30
|
+
# The secret string that is used to authenticate this client with
|
31
|
+
# AppControllers. It is initially generated by appscale-run-instances and can
|
32
|
+
# be found on the machine that ran that tool, or on any AppScale machine.
|
33
|
+
attr_accessor :secret
|
34
|
+
|
35
|
+
|
21
36
|
# A constructor that requires both the IP address of the machine to communicate
|
22
37
|
# with as well as the secret (string) needed to perform communication.
|
23
38
|
# AppControllers will reject SOAP calls if this secret (basically a password)
|
@@ -38,6 +53,7 @@ class AppControllerClient
|
|
38
53
|
@conn.add_method("neptune_does_file_exist", "file", "job_data", "secret")
|
39
54
|
end
|
40
55
|
|
56
|
+
|
41
57
|
# A helper method to make SOAP calls for us. This method is mainly here to
|
42
58
|
# reduce code duplication: all SOAP calls expect a certain timeout and can
|
43
59
|
# tolerate certain exceptions, so we consolidate this code into this method.
|
@@ -73,6 +89,7 @@ class AppControllerClient
|
|
73
89
|
end
|
74
90
|
end
|
75
91
|
|
92
|
+
|
76
93
|
# Initiates the start of a Neptune job, whether it be a HPC job (MPI, X10,
|
77
94
|
# or MapReduce), or a scaling job (e.g., for AppScale itself). This method
|
78
95
|
# should not be used for retrieving the output of a job or getting / setting
|
@@ -90,6 +107,7 @@ class AppControllerClient
|
|
90
107
|
return result
|
91
108
|
end
|
92
109
|
|
110
|
+
|
93
111
|
# Stores a file stored on the user's local file system in the underlying
|
94
112
|
# database. The user can specify to use either the underlying database
|
95
113
|
# that AppScale is using, or alternative storage mechanisms (as of writing,
|
@@ -104,6 +122,7 @@ class AppControllerClient
|
|
104
122
|
return result
|
105
123
|
end
|
106
124
|
|
125
|
+
|
107
126
|
# Retrieves the output of a Neptune job, stored in an underlying
|
108
127
|
# database. Within AppScale, a special application runs, referred to as the
|
109
128
|
# Repository, which provides a key-value interface to Neptune job data.
|
@@ -123,6 +142,7 @@ class AppControllerClient
|
|
123
142
|
return result
|
124
143
|
end
|
125
144
|
|
145
|
+
|
126
146
|
# Returns the ACL associated with the named piece of data stored
|
127
147
|
# in the underlying cloud platform. Right now, data can only be
|
128
148
|
# public or private, but future versions will add individual user
|
@@ -137,6 +157,7 @@ class AppControllerClient
|
|
137
157
|
return result
|
138
158
|
end
|
139
159
|
|
160
|
+
|
140
161
|
# Sets the ACL of a specified pieces of data stored in the underlying
|
141
162
|
# cloud platform. As is the case with get_acl, ACLs can be either
|
142
163
|
# public or private right now, but this will be expanded upon in
|
@@ -151,6 +172,9 @@ class AppControllerClient
|
|
151
172
|
return result
|
152
173
|
end
|
153
174
|
|
175
|
+
|
176
|
+
# Instructs the AppController to fetch the code specified and compile it.
|
177
|
+
# The result should then be placed in a location specified in the job data.
|
154
178
|
def compile_code(job_data)
|
155
179
|
result = ""
|
156
180
|
make_call(NO_TIMEOUT, false) {
|
@@ -160,6 +184,10 @@ class AppControllerClient
|
|
160
184
|
return result
|
161
185
|
end
|
162
186
|
|
187
|
+
|
188
|
+
# Asks the AppController for a list of all the Babel engines (each of which
|
189
|
+
# is a queue to store jobs and something that executes tasks) that are
|
190
|
+
# supported for the given credentials.
|
163
191
|
def get_supported_babel_engines(job_data)
|
164
192
|
result = []
|
165
193
|
make_call(NO_TIMEOUT, false) {
|
@@ -168,6 +196,10 @@ class AppControllerClient
|
|
168
196
|
return result
|
169
197
|
end
|
170
198
|
|
199
|
+
|
200
|
+
# Asks the AppController to see if the given file exists in the remote
|
201
|
+
# datastore. If extra credentials are needed for this operation, they are
|
202
|
+
# searched for within the job data.
|
171
203
|
def does_file_exist?(file, job_data)
|
172
204
|
result = false
|
173
205
|
make_call(NO_TIMEOUT, false) {
|
data/lib/babel.rb
CHANGED
@@ -35,6 +35,7 @@ SLEEP_TIME = 5 # seconds
|
|
35
35
|
# job requests.
|
36
36
|
MAX_SLEEP_TIME = 60 # seconds
|
37
37
|
|
38
|
+
|
38
39
|
# Babel provides a nice wrapper around Neptune jobs. Instead of making users
|
39
40
|
# write multiple Neptune jobs to actually run code (e.g., putting input in the
|
40
41
|
# datastore, run the job, get the output back), Babel automatically handles
|
@@ -44,7 +45,14 @@ def babel(params)
|
|
44
45
|
# It automatically starts running in a new thread, and attempting to get the
|
45
46
|
# value of what this returns causes it to block until the job completes.
|
46
47
|
future {
|
48
|
+
if params[:storage]
|
49
|
+
params[:is_remote] = true
|
50
|
+
else
|
51
|
+
params[:is_remote] = false
|
52
|
+
end
|
53
|
+
|
47
54
|
job_data = BabelHelper.convert_from_neptune_params(params)
|
55
|
+
|
48
56
|
NeptuneHelper.validate_storage_params(job_data) # adds in S3 storage params
|
49
57
|
|
50
58
|
# :code is the only required parameter - everything else can use default vals
|
@@ -63,23 +71,20 @@ def babel(params)
|
|
63
71
|
end
|
64
72
|
|
65
73
|
BabelHelper.run_job(job_data)
|
66
|
-
# So actually retrieving the job's output is done via a promise, so only if
|
67
|
-
# the user actually uses the value do we actually go and poll for output.
|
68
|
-
# The running of the job is done above, outside of the promise, so
|
69
|
-
# the job is always run, regardless of whether or not we get its output.
|
70
74
|
BabelHelper.wait_and_get_output(job_data)
|
71
|
-
# promise { BabelHelper.wait_and_get_output(job_data) }
|
72
75
|
}
|
73
76
|
end
|
74
77
|
|
75
78
|
|
76
79
|
# This module provides convenience functions for babel().
|
77
80
|
module BabelHelper
|
81
|
+
|
82
|
+
|
78
83
|
# If the user fails to give us an output location, this function will generate
|
79
84
|
# one for them, based on either the location of their code (for remotely
|
80
85
|
# specified code), or a babel parameter (for locally specified code).
|
81
86
|
def self.generate_output_location(job_data)
|
82
|
-
if job_data["@
|
87
|
+
if job_data["@storage"]
|
83
88
|
# We already know the bucket name - the same one that the user
|
84
89
|
# has told us their code is located in.
|
85
90
|
prefix = job_data["@code"].scan(/\/(.*?)\//)[0].to_s
|
@@ -90,6 +95,7 @@ module BabelHelper
|
|
90
95
|
return "/#{prefix}/babel/temp-#{CommonFunctions.get_random_alphanumeric()}"
|
91
96
|
end
|
92
97
|
|
98
|
+
|
93
99
|
# Provides a common way for callers to get the name of the bucket that
|
94
100
|
# should be used for Neptune jobs where the code is stored locally.
|
95
101
|
def self.get_bucket_for_local_data(job_data)
|
@@ -107,6 +113,7 @@ module BabelHelper
|
|
107
113
|
return bucket_name
|
108
114
|
end
|
109
115
|
|
116
|
+
|
110
117
|
# For jobs where the code is stored remotely, this method ensures that
|
111
118
|
# the code and any possible inputs actually do exist, before attempting to
|
112
119
|
# use them for computation.
|
@@ -128,16 +135,18 @@ module BabelHelper
|
|
128
135
|
}
|
129
136
|
end
|
130
137
|
|
138
|
+
|
131
139
|
# To avoid accidentally overwriting outputs from previous jobs, we first
|
132
140
|
# check to make sure an output file doesn't exist before starting a new job
|
133
141
|
# with the given name.
|
134
142
|
def self.ensure_output_does_not_exist(job_data)
|
135
143
|
file = job_data["@output"]
|
136
144
|
controller = self.get_appcontroller(job_data)
|
137
|
-
puts job_data.inspect
|
145
|
+
# Kernel.puts job_data.inspect
|
138
146
|
NeptuneHelper.require_file_to_not_exist(file, job_data, controller)
|
139
147
|
end
|
140
148
|
|
149
|
+
|
141
150
|
# Returns an AppControllerClient for the given job data.
|
142
151
|
def self.get_appcontroller(job_data)
|
143
152
|
keyname = job_data["@keyname"] || "appscale"
|
@@ -146,6 +155,7 @@ module BabelHelper
|
|
146
155
|
return AppControllerClient.new(shadow_ip, secret)
|
147
156
|
end
|
148
157
|
|
158
|
+
|
149
159
|
# Stores the user's code (and the directory it's in, and directories in the
|
150
160
|
# same directory as the user's code, since there could be libraries used)
|
151
161
|
# in the remote datastore.
|
@@ -157,6 +167,7 @@ module BabelHelper
|
|
157
167
|
return job_data["@code"]
|
158
168
|
end
|
159
169
|
|
170
|
+
|
160
171
|
# If any input files are specified, they are copied to the remote datastore
|
161
172
|
# via Neptune 'input' jobs. Inputs are assumed to be files on the local
|
162
173
|
# filesystem if they begin with a slash, and job_data gets updated with
|
@@ -176,6 +187,7 @@ module BabelHelper
|
|
176
187
|
return job_data
|
177
188
|
end
|
178
189
|
|
190
|
+
|
179
191
|
# If the user gives us local code or local inputs, this function will
|
180
192
|
# run a Neptune 'input' job to store the data remotely.
|
181
193
|
def self.put_file(local_path, job_data)
|
@@ -191,6 +203,7 @@ module BabelHelper
|
|
191
203
|
return input_data[:remote]
|
192
204
|
end
|
193
205
|
|
206
|
+
|
194
207
|
# Neptune internally uses job_data with keys of the form @name, but since the
|
195
208
|
# user has given them to us in the form :name, we convert it here.
|
196
209
|
# TODO(cgb): It looks like this conversion to/from may be unnecessary since
|
@@ -204,6 +217,7 @@ module BabelHelper
|
|
204
217
|
return job_data
|
205
218
|
end
|
206
219
|
|
220
|
+
|
207
221
|
# Neptune input jobs expect keys of the form :name, but since we've already
|
208
222
|
# converted them to the form @name, this function reverses that conversion.
|
209
223
|
def self.convert_to_neptune_params(job_data)
|
@@ -216,12 +230,17 @@ module BabelHelper
|
|
216
230
|
|
217
231
|
return neptune_params
|
218
232
|
end
|
219
|
-
|
233
|
+
|
234
|
+
|
220
235
|
# Constructs a Neptune job to run the user's code as a Babel job (task queue)
|
221
236
|
# from the given parameters.
|
222
237
|
def self.run_job(job_data)
|
223
238
|
run_data = self.convert_to_neptune_params(job_data)
|
224
|
-
|
239
|
+
|
240
|
+
# Default to babel as the job type, if the user doesn't specify one.
|
241
|
+
if run_data[:type].nil? or run_data[:type].empty?
|
242
|
+
run_data[:type] = "babel"
|
243
|
+
end
|
225
244
|
|
226
245
|
# TODO(cgb): Once AppScale+Babel gets support for RabbitMQ, change this to
|
227
246
|
# exec tasks over it, instead of locally.
|
@@ -233,6 +252,7 @@ module BabelHelper
|
|
233
252
|
return Kernel.neptune(run_data)
|
234
253
|
end
|
235
254
|
|
255
|
+
|
236
256
|
# Constructs a Neptune job to get the output of a Babel job. If the job is not
|
237
257
|
# yet finished, this function waits until it does, and then returns the output
|
238
258
|
# of the job.
|
data/lib/common_functions.rb
CHANGED
@@ -11,12 +11,15 @@ require 'yaml'
|
|
11
11
|
|
12
12
|
require 'custom_exceptions'
|
13
13
|
|
14
|
+
|
14
15
|
# A helper module that aggregates functions that are not part of Neptune's
|
15
16
|
# core functionality. Specifically, this module contains methods to scp
|
16
17
|
# files to other machines and the ability to read YAML files, which are
|
17
18
|
# often needed to determine which machine should be used for computation
|
18
19
|
# or to copy over code and input files.
|
19
20
|
module CommonFunctions
|
21
|
+
|
22
|
+
|
20
23
|
# Executes a command and returns the result. Is needed to get around
|
21
24
|
# Flexmock's inability to mock out Kernel:` (the standard shell exec
|
22
25
|
# method).
|
@@ -24,6 +27,7 @@ module CommonFunctions
|
|
24
27
|
return `#{cmd}`
|
25
28
|
end
|
26
29
|
|
30
|
+
|
27
31
|
# Returns a random string composed of alphanumeric characters, as long
|
28
32
|
# as the user requests.
|
29
33
|
def self.get_random_alphanumeric(length=10)
|
@@ -32,28 +36,27 @@ module CommonFunctions
|
|
32
36
|
possibleLength = possible.length
|
33
37
|
|
34
38
|
length.times { |index|
|
35
|
-
random << possible[rand(possibleLength)]
|
39
|
+
random << possible[Kernel.rand(possibleLength)]
|
36
40
|
}
|
37
41
|
|
38
42
|
return random
|
39
43
|
end
|
40
44
|
|
45
|
+
|
41
46
|
# Copies a file to the Shadow node (head node) within AppScale.
|
42
47
|
# The caller specifies
|
43
48
|
# the local file location, the destination where the file should be
|
44
49
|
# placed, and the name of the key to use. The keyname is typically
|
45
50
|
# specified by the Neptune job given, but defaults to ''appscale''
|
46
51
|
# if not provided.
|
47
|
-
def self.scp_to_shadow(local_file_loc,
|
48
|
-
remote_file_loc,
|
49
|
-
keyname,
|
50
|
-
is_dir=false)
|
51
|
-
|
52
|
+
def self.scp_to_shadow(local_file_loc, remote_file_loc, keyname, is_dir=false)
|
52
53
|
shadow_ip = CommonFunctions.get_from_yaml(keyname, :shadow)
|
53
54
|
ssh_key = File.expand_path("~/.appscale/#{keyname}.key")
|
54
|
-
CommonFunctions.scp_file(local_file_loc, remote_file_loc, shadow_ip,
|
55
|
+
CommonFunctions.scp_file(local_file_loc, remote_file_loc, shadow_ip,
|
56
|
+
ssh_key, is_dir)
|
55
57
|
end
|
56
|
-
|
58
|
+
|
59
|
+
|
57
60
|
# Performs the actual remote copying of files: given the IP address
|
58
61
|
# and other information from scp_to_shadow, attempts to use scp
|
59
62
|
# to copy the file over. Aborts if the scp fails, which can occur
|
@@ -62,9 +65,8 @@ module CommonFunctions
|
|
62
65
|
# actually a directory, we append the -r flag to scp as well.
|
63
66
|
def self.scp_file(local_file_loc, remote_file_loc, target_ip, public_key_loc,
|
64
67
|
is_dir=false)
|
65
|
-
cmd = ""
|
66
|
-
local_file_loc = File.expand_path(local_file_loc)
|
67
68
|
|
69
|
+
local_file_loc = File.expand_path(local_file_loc)
|
68
70
|
ssh_args = "-o StrictHostkeyChecking=no 2>&1"
|
69
71
|
ssh_args << " -r " if is_dir
|
70
72
|
|
@@ -85,7 +87,7 @@ module CommonFunctions
|
|
85
87
|
|
86
88
|
loop {
|
87
89
|
break if File.exists?(retval_loc)
|
88
|
-
sleep(5)
|
90
|
+
Kernel.sleep(5)
|
89
91
|
}
|
90
92
|
|
91
93
|
retval = (File.open(retval_loc) { |f| f.read }).chomp
|
@@ -96,6 +98,7 @@ module CommonFunctions
|
|
96
98
|
return cmd
|
97
99
|
end
|
98
100
|
|
101
|
+
|
99
102
|
# Given the AppScale keyname, reads the associated YAML file and returns
|
100
103
|
# the contents of the given tag. The required flag (default value is true)
|
101
104
|
# indicates whether a value must exist for this tag: if set to true, this
|
@@ -132,6 +135,7 @@ module CommonFunctions
|
|
132
135
|
return value
|
133
136
|
end
|
134
137
|
|
138
|
+
|
135
139
|
# Returns the secret key needed for communication with AppScale's
|
136
140
|
# Shadow node. This method is a nice frontend to the get_from_yaml
|
137
141
|
# function, as the secret is stored in a YAML file.
|