mortar 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -128,6 +128,7 @@ module Mortar
128
128
  def self.prepare_run(cmd, args=[])
129
129
  command = parse(cmd)
130
130
 
131
+
131
132
  if args.include?('-h') || args.include?('--help')
132
133
  args.unshift(cmd) unless cmd =~ /^-.*/
133
134
  cmd = 'help'
@@ -138,6 +139,15 @@ module Mortar
138
139
  if %w( -v --version ).include?(cmd)
139
140
  cmd = 'version'
140
141
  command = parse(cmd)
142
+ # Check if the command tried matches a command file. If it does, the command exists, but doesn't have an index action
143
+ # Otherwise it would have been picked up by the original parse command.
144
+ elsif Dir[File.join(File.dirname(__FILE__), "command", "*.rb")].find { |file| file.include?(cmd) }
145
+ display "#{cmd} command requires arguments"
146
+ display
147
+ # Display the command's help message
148
+ args.unshift(cmd) unless cmd =~ /^-.*/
149
+ cmd = 'help'
150
+ command = parse('help')
141
151
  else
142
152
  error([
143
153
  "`#{cmd}` is not a mortar command.",
@@ -36,8 +36,13 @@ class Mortar::Command::Clusters < Mortar::Command::Base
36
36
  validate_arguments!
37
37
 
38
38
  clusters = api.get_clusters().body['clusters']
39
- display_table(clusters,
39
+ if not clusters.empty?
40
+ display_table(clusters,
40
41
  %w( cluster_id size status_description cluster_type_description start_timestamp duration),
41
42
  ['cluster_id', 'Size (# of Nodes)', 'Status', 'Type', 'Start Timestamp', 'Elapsed Time'])
43
+ else
44
+ display("There are no running or recent clusters")
45
+ end
46
+
42
47
  end
43
48
  end
@@ -66,9 +66,7 @@ class Mortar::Command::Describe < Mortar::Command::Base
66
66
  is_finished =
67
67
  Mortar::API::Describe::STATUSES_COMPLETE.include?(describe_result["status_code"])
68
68
 
69
- redisplay("Status: %s %s" % [
70
- describe_result['status_description'] + (is_finished ? "" : "..."),
71
- is_finished ? " " : spinner(ticks)],
69
+ redisplay("[#{spinner(ticks)}] Calculating schema for #{alias_name} and ancestors...",
72
70
  is_finished) # only display newline on last message
73
71
  if is_finished
74
72
  display
@@ -124,7 +124,7 @@ class Mortar::Command::Jobs < Mortar::Command::Base
124
124
  display
125
125
  display("Job status can be viewed on the web at:\n\n #{response['web_job_url']}")
126
126
  display
127
- display("Or by running:\n\n mortar jobs:status #{response['job_id']}")
127
+ display("Or by running:\n\n mortar jobs:status #{response['job_id']} --poll")
128
128
  display
129
129
  end
130
130
 
@@ -135,6 +135,9 @@ class Mortar::Command::Jobs < Mortar::Command::Base
135
135
  #
136
136
  # Check the status of a job.
137
137
  #
138
+ # -p, --poll # Poll the status of a job
139
+ #
140
+ #
138
141
  #Examples:
139
142
  #
140
143
  # $ mortar jobs:status 2000cbbba40a860a6f000000
@@ -149,43 +152,80 @@ class Mortar::Command::Jobs < Mortar::Command::Base
149
152
  error("Usage: mortar jobs:status JOB_ID\nMust specify JOB_ID.")
150
153
  end
151
154
 
152
- job_status = api.get_job(job_id).body
153
-
154
- job_display_entries = {
155
- "status" => job_status["status_description"],
156
- "progress" => "#{job_status["progress"]}%",
157
- "cluster_id" => job_status["cluster_id"],
158
- "job submitted at" => job_status["start_timestamp"],
159
- "job began running at" => job_status["running_timestamp"],
160
- "job finished at" => job_status["stop_timestamp"],
161
- "job running for" => job_status["duration"],
162
- "job run with parameters" => job_status["parameters"],
163
- "error" => job_status["error"]
164
- }
165
-
166
- unless job_status["error"].nil? || job_status["error"]["message"].nil?
167
- error_context = get_error_message_context(job_status["error"]["message"])
168
- unless error_context == ""
169
- job_status["error"]["help"] = error_context
155
+ # Inner function to display the hash table when the job is complte
156
+ def display_job_status(job_status)
157
+ job_display_entries = {
158
+ "status" => job_status["status_description"],
159
+ "progress" => "#{job_status["progress"]}%",
160
+ "cluster_id" => job_status["cluster_id"],
161
+ "job submitted at" => job_status["start_timestamp"],
162
+ "job began running at" => job_status["running_timestamp"],
163
+ "job finished at" => job_status["stop_timestamp"],
164
+ "job running for" => job_status["duration"],
165
+ "job run with parameters" => job_status["parameters"],
166
+ }
167
+
168
+
169
+ unless job_status["error"].nil? || job_status["error"]["message"].nil?
170
+ error_context = get_error_message_context(job_status["error"]["message"])
171
+ unless error_context == ""
172
+ job_status["error"]["help"] = error_context
173
+ end
174
+ job_status["error"].each_pair do |key, value|
175
+ job_display_entries["error - #{key}"] = value
176
+ end
170
177
  end
178
+
179
+ if job_status["num_hadoop_jobs"] && job_status["num_hadoop_jobs_succeeded"]
180
+ job_display_entries["hadoop jobs complete"] =
181
+ '%0.2f / %0.2f' % [job_status["num_hadoop_jobs_succeeded"], job_status["num_hadoop_jobs"]]
182
+ end
183
+
184
+ if job_status["outputs"] && job_status["outputs"].length > 0
185
+ job_display_entries["outputs"] = Hash[job_status["outputs"].select{|o| o["alias"]}.collect do |output|
186
+ output_hash = {}
187
+ output_hash["location"] = output["location"] if output["location"]
188
+ output_hash["records"] = output["records"] if output["records"]
189
+ [output['alias'], output_hash]
190
+ end]
191
+ end
192
+
193
+ styled_header("#{job_status["project_name"]}: #{job_status["pigscript_name"]} (job_id: #{job_status["job_id"]})")
194
+ styled_hash(job_display_entries)
171
195
  end
172
196
 
173
- if job_status["num_hadoop_jobs"] && job_status["num_hadoop_jobs_succeeded"]
174
- job_display_entries["hadoop jobs complete"] =
175
- '%0.2f / %0.2f' % [job_status["num_hadoop_jobs_succeeded"], job_status["num_hadoop_jobs"]]
176
- end
177
-
178
- if job_status["outputs"] && job_status["outputs"].length > 0
179
- job_display_entries["outputs"] = Hash[job_status["outputs"].select{|o| o["alias"]}.collect do |output|
180
- output_hash = {}
181
- output_hash["location"] = output["location"] if output["location"]
182
- output_hash["records"] = output["records"] if output["records"]
183
- [output['alias'], output_hash]
184
- end]
197
+ # If polling the status
198
+ if options[:poll]
199
+ ticking(polling_interval) do |ticks|
200
+ job_status = api.get_job(job_id).body
201
+ # If the job is complete exit and display the table normally
202
+ if Mortar::API::Jobs::STATUSES_COMPLETE.include?(job_status["status_code"] )
203
+ redisplay("")
204
+ display_job_status(job_status)
205
+ break
206
+ end
207
+
208
+ # If the job is running show the progress bar
209
+ if job_status["status_code"] == Mortar::API::Jobs::STATUS_RUNNING
210
+ progressbar = "=" + ("=" * (job_status["progress"].to_i / 5)) + ">"
211
+
212
+ if job_status["num_hadoop_jobs"] && job_status["num_hadoop_jobs_succeeded"]
213
+ hadoop_jobs_ratio_complete =
214
+ '%0.2f / %0.2f' % [job_status["num_hadoop_jobs_succeeded"], job_status["num_hadoop_jobs"]]
215
+ end
216
+
217
+ printf("\r[#{spinner(ticks)}] Status: [%-22s] %s%% Complete (%s MapReduce jobs finished)", progressbar, job_status["progress"], hadoop_jobs_ratio_complete)
218
+
219
+ # If the job is not complete, but not in the running state, just display its status
220
+ else
221
+ redisplay("[#{spinner(ticks)}] Status: #{job_status['status_description']}")
222
+ end
223
+ end
224
+ # If not polling, get the job status and display the results
225
+ else
226
+ job_status = api.get_job(job_id).body
227
+ display_job_status(job_status)
185
228
  end
186
-
187
- styled_header("#{job_status["project_name"]}: #{job_status["pigscript_name"]} (job_id: #{job_status["job_id"]})")
188
- styled_hash(job_display_entries)
189
229
  end
190
230
 
191
231
  # jobs:stop JOB_ID
@@ -105,6 +105,41 @@ class Mortar::Command::Projects < Mortar::Command::Base
105
105
  end
106
106
 
107
107
  end
108
+
109
+ # projects:set_remote PROJECT
110
+ #
111
+ # Adds the Mortar remote to the local git project. This is necessary for successfully executing many of the Mortar commands.
112
+ #
113
+ #Example:
114
+ #
115
+ # $ mortar projects:set_remote my_project
116
+ #
117
+ def set_remote
118
+ project_name = shift_argument
119
+
120
+ unless project_name
121
+ error("Usage: mortar projects:set_remote PROJECT\nMust specify PROJECT.")
122
+ end
123
+
124
+ unless git.has_dot_git?
125
+ error("Can only set the remote for an existing git project. Please run:\n\ngit init\ngit add .\ngit commit -a -m \"first commit\"\n\nto initialize your project in git.")
126
+ end
127
+
128
+ if git.remotes(git_organization).include?("mortar")
129
+ display("The remote has already been set for project: #{project_name}")
130
+ return
131
+ end
132
+
133
+ projects = api.get_projects().body["projects"]
134
+ project = projects.find { |p| p['name'] == project_name}
135
+ unless project
136
+ error("No project named: #{project_name} exists. You can create this project using:\n\n mortar projects:create")
137
+ end
138
+
139
+ git.remote_add("mortar", project['git_url'])
140
+ display("Successfully added the mortar remote to the #{project_name} project")
141
+
142
+ end
108
143
 
109
144
  # projects:clone PROJECT
110
145
  #
@@ -66,10 +66,8 @@ class Mortar::Command::Validate < Mortar::Command::Base
66
66
  validate_result = api.get_validate(validate_id).body
67
67
  is_finished =
68
68
  Mortar::API::Validate::STATUSES_COMPLETE.include?(validate_result["status_code"])
69
-
70
- redisplay("Status: %s %s" % [
71
- validate_result['status_description'] + (is_finished ? "" : "..."),
72
- is_finished ? " " : spinner(ticks)],
69
+
70
+ redisplay("[#{spinner(ticks)}] Checking your script for problems with: Pig syntax, Python syntax, and S3 data access",
73
71
  is_finished) # only display newline on last message
74
72
  if is_finished
75
73
  display
@@ -12,7 +12,7 @@
12
12
  * User-Defined Functions (UDFs)
13
13
  */
14
14
 
15
- REGISTER '../udfs/python/<%= script_name %>.py' using streaming_python as <%= script_name %>;
15
+ REGISTER '../udfs/python/<%= script_name %>.py' USING streaming_python AS <%= script_name %>;
16
16
  <% end %>
17
17
 
18
18
  -- This is an example of loading up input data
@@ -1,3 +1,4 @@
1
+ Gemfile.lock
1
2
  *.pyc
2
3
  *.class
3
4
  *.log
@@ -10,7 +10,7 @@
10
10
  /**
11
11
  * User-Defined Functions (UDFs)
12
12
  */
13
- REGISTER '../udfs/python/<%= project_name %>.py' using streaming_python as <%= project_name %>;
13
+ REGISTER '../udfs/python/<%= project_name %>.py' USING streaming_python AS <%= project_name %>;
14
14
 
15
15
  -- This is an example of loading up input data
16
16
  my_input_data = LOAD '$INPUT_PATH'
@@ -16,5 +16,5 @@
16
16
 
17
17
  module Mortar
18
18
  # see http://semver.org/
19
- VERSION = "0.2.0"
19
+ VERSION = "0.2.1"
20
20
  end
@@ -52,8 +52,7 @@ STDOUT
52
52
  mock(Mortar::Auth.api).get_clusters().returns(Excon::Response.new(:body => {"clusters" => []}))
53
53
  stderr, stdout = execute("clusters", nil, nil)
54
54
  stdout.should == <<-STDOUT
55
- cluster_id Size (# of Nodes) Status Type Start Timestamp Elapsed Time
56
- ---------- ----------------- ------ ---- --------------- ------------
55
+ There are no running or recent clusters
57
56
  STDOUT
58
57
  end
59
58
  end
@@ -86,7 +86,7 @@ Taking code snapshot... done
86
86
  Sending code snapshot to Mortar... done
87
87
  Starting describe... done
88
88
 
89
- \r\e[0KStatus: Pending... /\r\e[0KStatus: Gateway starting... -\r\e[0KStatus: Starting pig... \\\r\e[0KStatus: Success
89
+ \r\e[0K[/] Calculating schema for my_alias and ancestors...\r\e[0K[-] Calculating schema for my_alias and ancestors...\r\e[0K[\\] Calculating schema for my_alias and ancestors...\r\e[0K[|] Calculating schema for my_alias and ancestors...
90
90
 
91
91
  Results available at https://api.mortardata.com/describe/c571a8c7f76a4fd4a67c103d753e2dd5
92
92
  Opening web browser to show results... done
@@ -119,7 +119,7 @@ Taking code snapshot... done
119
119
  Sending code snapshot to Mortar... done
120
120
  Starting describe... done
121
121
 
122
- \r\e[0KStatus: Pending... /\r\e[0KStatus: Failed
122
+ \r\e[0K[/] Calculating schema for my_alias and ancestors...\r\e[0K[-] Calculating schema for my_alias and ancestors...
123
123
 
124
124
  STDOUT
125
125
  stderr.should == <<-STDERR
@@ -56,7 +56,7 @@ Job status can be viewed on the web at:
56
56
 
57
57
  Or by running:
58
58
 
59
- mortar jobs:status c571a8c7f76a4fd4a67c103d753e2dd5
59
+ mortar jobs:status c571a8c7f76a4fd4a67c103d753e2dd5 --poll
60
60
 
61
61
  STDOUT
62
62
  end
@@ -87,7 +87,7 @@ Job status can be viewed on the web at:
87
87
 
88
88
  Or by running:
89
89
 
90
- mortar jobs:status c571a8c7f76a4fd4a67c103d753e2dd5
90
+ mortar jobs:status c571a8c7f76a4fd4a67c103d753e2dd5 --poll
91
91
 
92
92
  STDOUT
93
93
  end
@@ -116,7 +116,7 @@ Job status can be viewed on the web at:
116
116
 
117
117
  Or by running:
118
118
 
119
- mortar jobs:status c571a8c7f76a4fd4a67c103d753e2dd5
119
+ mortar jobs:status c571a8c7f76a4fd4a67c103d753e2dd5 --poll
120
120
 
121
121
  STDOUT
122
122
  end
@@ -339,11 +339,10 @@ STDOUT
339
339
  stdout.should == <<-STDOUT
340
340
  === myproject: my_script (job_id: c571a8c7f76a4fd4a67c103d753e2dd5)
341
341
  cluster_id: e2790e7e8c7d48e39157238d58191346
342
- error:
343
- column_number: 34
344
- line_number: 43
345
- message: An error occurred and here's some more info
346
- type: RuntimeError
342
+ error - column_number: 34
343
+ error - line_number: 43
344
+ error - message: An error occurred and here's some more info
345
+ error - type: RuntimeError
347
346
  hadoop jobs complete: 0.00 / 4.00
348
347
  job began running at: 2012-02-28T03:41:52.613000+00:00
349
348
  job finished at: 2012-02-28T03:45:52.613000+00:00
@@ -354,6 +353,85 @@ job running for: 6 mins
354
353
  job submitted at: 2012-02-28T03:35:42.831000+00:00
355
354
  progress: 55%
356
355
  status: Execution error
356
+ STDOUT
357
+ end
358
+ end
359
+ it "gets status for a running job using polling" do
360
+ with_git_initialized_project do |p|
361
+ job_id = "c571a8c7f76a4fd4a67c103d753e2dd5"
362
+ pigscript_name = "my_script"
363
+ project_name = "myproject"
364
+ status_code = Mortar::API::Jobs::STATUS_RUNNING
365
+ progress = 0
366
+ cluster_id = "e2790e7e8c7d48e39157238d58191346"
367
+ start_timestamp = "2012-02-28T03:35:42.831000+00:00"
368
+ stop_timestamp = "2012-02-28T03:44:52.613000+00:00"
369
+ running_timestamp = "2012-02-28T03:41:52.613000+00:00"
370
+ parameters = {"my_param_1" => "value1", "MY_PARAM_2" => "3"}
371
+
372
+ mock(Mortar::Auth.api).get_job(job_id).returns(Excon::Response.new(:body => {"job_id" => job_id,
373
+ "pigscript_name" => pigscript_name,
374
+ "project_name" => project_name,
375
+ "status_code" => status_code,
376
+ "status_description" => "Execution error",
377
+ "progress" => progress,
378
+ "cluster_id" => cluster_id,
379
+ "start_timestamp" => start_timestamp,
380
+ "running_timestamp" => running_timestamp,
381
+ "duration" => "6 mins",
382
+ "num_hadoop_jobs" => 4,
383
+ "num_hadoop_jobs_succeeded" => 0,
384
+ "parameters" => parameters
385
+ }))
386
+
387
+ status_code = Mortar::API::Jobs::STATUS_SUCCESS
388
+ progress = 100
389
+ outputs = [{'name'=> 'hottest_songs_of_the_decade',
390
+ 'records' => 10,
391
+ 'alias' => 'output_data',
392
+ 'location' => 's3n://my-bucket/my-folder/hottest_songs_of_the_decade/output_data'},
393
+ {'name'=> 'hottest_songs_of_the_decade',
394
+ 'records' => 100,
395
+ 'alias' => 'output_data_2',
396
+ 'location' => 's3n://my-bucket/my-folder/hottest_songs_of_the_decade/output_data_2'}]
397
+
398
+ mock(Mortar::Auth.api).get_job(job_id).returns(Excon::Response.new(:body => {"job_id" => job_id,
399
+ "pigscript_name" => pigscript_name,
400
+ "project_name" => project_name,
401
+ "status_code" => status_code,
402
+ "status_description" => "Success",
403
+ "progress" => progress,
404
+ "cluster_id" => cluster_id,
405
+ "start_timestamp" => start_timestamp,
406
+ "running_timestamp" => running_timestamp,
407
+ "stop_timestamp" => stop_timestamp,
408
+ "duration" => "6 mins",
409
+ "num_hadoop_jobs" => 4,
410
+ "num_hadoop_jobs_succeeded" => 4,
411
+ "parameters" => parameters,
412
+ "outputs" => outputs
413
+ }))
414
+ stderr, stdout = execute("jobs:status c571a8c7f76a4fd4a67c103d753e2dd5 -p", p, @git)
415
+ stdout.should == <<-STDOUT
416
+ \r[/] Status: [=> ] 0% Complete (0.00 / 4.00 MapReduce jobs finished)\r\e[0K=== myproject: my_script (job_id: c571a8c7f76a4fd4a67c103d753e2dd5)
417
+ cluster_id: e2790e7e8c7d48e39157238d58191346
418
+ hadoop jobs complete: 4.00 / 4.00
419
+ job began running at: 2012-02-28T03:41:52.613000+00:00
420
+ job finished at: 2012-02-28T03:44:52.613000+00:00
421
+ job run with parameters:
422
+ MY_PARAM_2: 3
423
+ my_param_1: value1
424
+ job running for: 6 mins
425
+ job submitted at: 2012-02-28T03:35:42.831000+00:00
426
+ outputs:
427
+ output_data:
428
+ location: s3n://my-bucket/my-folder/hottest_songs_of_the_decade/output_data
429
+ records: 10
430
+ output_data_2:
431
+ location: s3n://my-bucket/my-folder/hottest_songs_of_the_decade/output_data_2
432
+ records: 100
433
+ progress: 100%
434
+ status: Success
357
435
  STDOUT
358
436
  end
359
437
  end
@@ -380,7 +458,6 @@ STDOUT
380
458
 
381
459
 
382
460
  end
383
-
384
461
  end
385
462
  end
386
463
  end
@@ -129,6 +129,62 @@ STDOUT
129
129
  end
130
130
 
131
131
  end
132
+
133
+ context("set_remote") do
134
+
135
+ it "sets the remote of a project" do
136
+ with_git_initialized_project do |p|
137
+ project_name = p.name
138
+ project_git_url = "git@github.com:mortarcode-dev/#{project_name}"
139
+ `git remote rm mortar`
140
+ mock(Mortar::Auth.api).get_projects().returns(Excon::Response.new(:body => {"projects" => [ { "name" => project_name, "status" => Mortar::API::Projects::STATUS_ACTIVE, "git_url" => project_git_url } ] })).ordered
141
+
142
+ mock(@git).remote_add("mortar", project_git_url)
143
+
144
+ stderr, stdout = execute("projects:set_remote #{project_name}", p, @git)
145
+ stdout.should == <<-STDOUT
146
+ Successfully added the mortar remote to the myproject project
147
+ STDOUT
148
+ end
149
+ end
150
+
151
+ it "remote already added" do
152
+ with_git_initialized_project do |p|
153
+ project_name = p.name
154
+
155
+ stderr, stdout = execute("projects:set_remote #{project_name}", p, @git)
156
+ stdout.should == <<-STDERR
157
+ The remote has already been set for project: myproject
158
+ STDERR
159
+ end
160
+ end
161
+
162
+ it "No project given" do
163
+ with_git_initialized_project do |p|
164
+ stderr, stdout = execute("projects:set_remote", p, @git)
165
+ stderr.should == <<-STDERR
166
+ ! Usage: mortar projects:set_remote PROJECT
167
+ ! Must specify PROJECT.
168
+ STDERR
169
+ end
170
+ end
171
+
172
+ it "No project with that name" do
173
+ with_git_initialized_project do |p|
174
+ project_name = p.name
175
+ project_git_url = "git@github.com:mortarcode-dev/#{project_name}"
176
+ mock(Mortar::Auth.api).get_projects().returns(Excon::Response.new(:body => {"projects" => [ { "name" => "derp", "status" => Mortar::API::Projects::STATUS_ACTIVE, "git_url" => project_git_url } ] })).ordered
177
+ `git remote rm mortar`
178
+
179
+ stderr, stdout = execute("projects:set_remote #{project_name}", p, @git)
180
+ stderr.should == <<-STDERR
181
+ ! No project named: myproject exists. You can create this project using:
182
+ !
183
+ ! mortar projects:create
184
+ STDERR
185
+ end
186
+ end
187
+ end
132
188
 
133
189
 
134
190
  context("clone") do
@@ -71,7 +71,7 @@ Taking code snapshot... done
71
71
  Sending code snapshot to Mortar... done
72
72
  Starting validate... done
73
73
 
74
- \r\e[0KStatus: Pending... /\r\e[0KStatus: GATEWAY_STARTING... -\r\e[0KStatus: Starting... \\\r\e[0KStatus: Success
74
+ \r\e[0K[/] Checking your script for problems with: Pig syntax, Python syntax, and S3 data access\r\e[0K[-] Checking your script for problems with: Pig syntax, Python syntax, and S3 data access\r\e[0K[\\] Checking your script for problems with: Pig syntax, Python syntax, and S3 data access\r\e[0K[|] Checking your script for problems with: Pig syntax, Python syntax, and S3 data access
75
75
 
76
76
  Your script is valid.
77
77
  STDOUT
@@ -103,7 +103,7 @@ Taking code snapshot... done
103
103
  Sending code snapshot to Mortar... done
104
104
  Starting validate... done
105
105
 
106
- \r\e[0KStatus: Pending... /\r\e[0KStatus: Failed
106
+ \r\e[0K[/] Checking your script for problems with: Pig syntax, Python syntax, and S3 data access\r\e[0K[-] Checking your script for problems with: Pig syntax, Python syntax, and S3 data access
107
107
 
108
108
  STDOUT
109
109
  stderr.should == <<-STDERR
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mortar
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
4
+ hash: 21
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 0
10
- version: 0.2.0
9
+ - 1
10
+ version: 0.2.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Mortar Data