naf 2.1.12 → 2.1.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/Gemfile +4 -3
  2. data/RELEASE_NOTES.rdoc +17 -4
  3. data/app/assets/images/download.png +0 -0
  4. data/app/assets/javascripts/dataTablesTemplates/jobs.js +37 -0
  5. data/app/controllers/naf/historical_jobs_controller.rb +30 -0
  6. data/app/controllers/naf/log_parsers_controller.rb +9 -0
  7. data/app/helpers/naf/application_helper.rb +1 -1
  8. data/app/models/logical/naf/application_schedule.rb +7 -3
  9. data/app/models/logical/naf/log_parser/base.rb +1 -0
  10. data/app/models/logical/naf/log_parser/job.rb +4 -3
  11. data/app/models/logical/naf/log_parser/job_downloader.rb +156 -0
  12. data/app/models/logical/naf/log_parser/runner.rb +4 -3
  13. data/app/models/logical/naf/metric_sender.rb +62 -0
  14. data/app/models/process/naf/database_models_cleanup.rb +91 -0
  15. data/app/models/process/naf/runner.rb +52 -35
  16. data/app/views/naf/historical_jobs/_button_control.html.erb +64 -0
  17. data/app/views/naf/historical_jobs/index.json.erb +26 -5
  18. data/app/views/naf/historical_jobs/show.html.erb +20 -29
  19. data/app/views/naf/log_viewer/_job_log_download_button.html.erb +11 -0
  20. data/app/views/naf/log_viewer/_job_logs.html.erb +3 -0
  21. data/app/views/naf/log_viewer/_log_display.html.erb +4 -4
  22. data/app/views/naf/log_viewer/_log_layout.html.erb +5 -0
  23. data/config/routes.rb +4 -0
  24. data/lib/naf.rb +8 -0
  25. data/lib/naf/configuration.rb +5 -1
  26. data/lib/naf/version.rb +1 -1
  27. data/naf.gemspec +5 -2
  28. data/spec/controllers/naf/log_parsers_controller_spec.rb +35 -0
  29. data/spec/models/logical/naf/application_schedule_spec.rb +41 -0
  30. data/spec/models/logical/naf/construction_zone/boss_spec.rb +5 -0
  31. data/spec/models/logical/naf/construction_zone/foreman_spec.rb +6 -3
  32. data/spec/models/logical/naf/job_downloader_spec.rb +72 -0
  33. data/spec/models/logical/naf/job_statuses/errored_spec.rb +33 -0
  34. data/spec/models/logical/naf/job_statuses/finished_less_minute_spec.rb +33 -0
  35. data/spec/models/logical/naf/job_statuses/finished_spec.rb +33 -0
  36. data/spec/models/logical/naf/job_statuses/queued_spec.rb +34 -0
  37. data/spec/models/logical/naf/job_statuses/running_spec.rb +37 -0
  38. data/spec/models/logical/naf/job_statuses/terminated_spec.rb +33 -0
  39. data/spec/models/logical/naf/job_statuses/waiting_spec.rb +33 -0
  40. metadata +80 -6
@@ -137,6 +137,18 @@
137
137
  "#", { class: "terminate" }
138
138
  %></td>
139
139
  </tr>
140
+ <% elsif !['Terminating'].include?(@logical_job.status) -%>
141
+ <tr>
142
+ <td>Enqueue Application</td>
143
+ <td><% params = { class: "re-enqueue", content: "#{naf.historical_jobs_path}/reenqueue", id: @historical_job.id } %>
144
+ <% if @historical_job.application_id.present? -%>
145
+ <% params[:app_id] = @historical_job.application.id %>
146
+ <% end %>
147
+ <%= link_to image_tag('control_play_blue.png',
148
+ class: 'action',
149
+ title: "Re-enqueue one instance of job #{@historical_job.id}"),
150
+ "#", params %>
151
+ </td>
140
152
  <% end %>
141
153
  </tbody>
142
154
  </table>
@@ -163,8 +175,12 @@
163
175
  </tbody>
164
176
  </table>
165
177
  </br>
166
- <%= render partial: 'naf/log_viewer/log_layout', locals: { record_id: @historical_job.id, record_type: 'job' } %>
167
-
178
+ <%= render partial: 'naf/log_viewer/log_layout', locals: {
179
+ record_id: @historical_job.id,
180
+ record_type: 'job',
181
+ runner_name: @logical_job.runner,
182
+ server: @logical_job.started_on_machine.try(:server_name),
183
+ status: @logical_job.status } %>
168
184
  </div>
169
185
  <% end %>
170
186
 
@@ -177,30 +193,5 @@
177
193
  logs_url: "#{http_protocol}#{@logical_job.runner}#{naf.logs_log_parsers_path}",
178
194
  record_type: 'job' } %>
179
195
 
180
- <% content_for :javascripts do %>
181
- <script type='text/javascript'>
182
- jQuery(document).ready(function () {
183
- jQuery(document).delegate('.terminate', "click", function(){
184
- var answer = confirm("You are terminating this job. Are you sure you want to do this?");
185
- if (!answer) {
186
- return false;
187
- }
188
- var id = <%= @historical_job.id %>;
189
- jQuery.ajax({
190
- url: id,
191
- type:'POST',
192
- dataType:'json',
193
- data:{ "historical_job[request_to_terminate]": 1, "historical_job_id": id, "_method": "put" },
194
- success:function (data) {
195
- if (data.success) {
196
- var title = data.title ? data.title : data.command
197
- jQuery("<p id='notice'>A Job " + title + " was terminated!</p>").
198
- appendTo('#flash_message').slideDown().delay(5000).slideUp();
199
- setTimeout('window.location.reload()', 5600);
200
- }
201
- }
202
- });
203
- });
204
- });
205
- </script>
206
- <% end %>
196
+ <!-- This partial controls terminate and re-enqueue links -->
197
+ <% render partial: 'button_control', locals: { historical_job_id: @historical_job.id } %>
@@ -0,0 +1,11 @@
1
+ <% style_text = "vertical-align: bottom;" %>
2
+ <% if status == 'Queued' || server.blank?
3
+ style_text << " display: none;"
4
+ end %>
5
+ <%= button_tag(style: "#{style_text}") do
6
+ link_to 'Download',
7
+ link_path,
8
+ style: "color: black;"
9
+ end
10
+ %>
11
+ </button>
@@ -48,6 +48,9 @@
48
48
  </br>
49
49
 
50
50
  <%= submit_tag("Search the logs", id: 'log_search_submit') %>
51
+ <%= render partial: 'job_log_download_button', locals: { server: @job.started_on_machine.try(:server_name),
52
+ status: logical_job.status,
53
+ link_path: "#{http_protocol}#{logical_job.runner}#{naf.download_log_parsers_path}?record_id=#{@job.id}&record_type=job" } %>
51
54
  &nbsp;&nbsp;&nbsp;
52
55
  <%= link_to 'Back to Job', { controller: 'historical_jobs', action: 'show', id: @job.id } %>
53
56
  </div>
@@ -129,7 +129,7 @@
129
129
  jQuery('#stdout').text('')
130
130
  jQuery('#stdout').append(logs);
131
131
  if(jQuery('#stdout').text() == '') {
132
- logs = "No logs were found for this search query!<br>";
132
+ logs = "<pre style='display: inline; word-wrap: break-word;'>No logs were found for this search query!</pre><br>";
133
133
  setAutoScrollOff();
134
134
  }
135
135
  jQuery("#stdout").scrollTop(jQuery('#stdout')[0].scrollHeight);
@@ -138,7 +138,7 @@
138
138
  }
139
139
  else{
140
140
  if(logs == '' && jQuery('#stdout')[0].children.length == 0) {
141
- logs = "&nbsp;&nbsp;<span>No logs were found!<br></span>";
141
+ logs = "<span><pre style='display: inline; word-wrap: break-word;'>No logs were found!</pre><br></span>";
142
142
  setAutoScrollOff();
143
143
  }
144
144
 
@@ -165,10 +165,10 @@
165
165
  }
166
166
  },
167
167
  error: function(response) {
168
- message = '&nbsp;&nbsp;<span>Failed to retrieve ' + '<%= record_type %>' + '(' +
168
+ message = '<span><pre style="display: inline; word-wrap: break-word;">Failed to retrieve ' + '<%= record_type %>' + '(' +
169
169
  '<%= record_id %>' + ') logs from ' + '<%= logs_url %>'.match(/^https?\:\/\/([^\/?#]+)/).pop() + '. Please refer to ' +
170
170
  'Naf FAQs on the' + ' wiki'.link('https://github.com/fiksu/naf/wiki/Frequently-Asked-Questions') +
171
- ' for further information.<br></span>';
171
+ ' for further information.</pre><br></span>';
172
172
 
173
173
  jQuery('#stdout').prepend(message);
174
174
  }
@@ -47,6 +47,11 @@
47
47
  <%= link_to 'Auto Scroll (ON)', '#', id: :auto_scroll %>
48
48
  </br></br>
49
49
  <%= submit_tag("Search the logs", id: 'log_search_submit') %>
50
+ <% if record_type == 'job' %>
51
+ <%= render partial: 'naf/log_viewer/job_log_download_button', locals: {
52
+ server: server, status: status,
53
+ link_path: "#{http_protocol}#{runner_name}#{naf.download_log_parsers_path}?record_id=#{record_id}&record_type=job" } %>
54
+ <% end %>
50
55
  <%= link_to 'Full Screen',
51
56
  { controller: 'log_viewer', action: 'index', record_id: record_id, record_type: record_type },
52
57
  { class: 'full_screen' } %>
@@ -1,5 +1,8 @@
1
1
  Naf::Engine.routes.draw do
2
2
  resources :historical_jobs, except: [:edit] do
3
+ collection do
4
+ post :reenqueue
5
+ end
3
6
  resources :historical_job_affinity_tabs, except: [:destroy]
4
7
  end
5
8
 
@@ -31,6 +34,7 @@ Naf::Engine.routes.draw do
31
34
  resources :log_parsers, only: [] do
32
35
  collection do
33
36
  get :logs
37
+ get :download
34
38
  end
35
39
  end
36
40
  resources :status, only: [:index]
data/lib/naf.rb CHANGED
@@ -45,6 +45,14 @@ module Naf
45
45
  configuration.simple_cluster_authenticator_cookie_expiration_time
46
46
  end
47
47
 
48
+ def metric_tags
49
+ configuration.metric_tags
50
+ end
51
+
52
+ def metric_send_delay
53
+ configuration.metric_send_delay
54
+ end
55
+
48
56
  def using_another_database?
49
57
  model_class != ActiveRecord::Base
50
58
  end
@@ -8,7 +8,9 @@ module Naf
8
8
  :layout,
9
9
  :default_page_options,
10
10
  :api_domain_cookie_name,
11
- :simple_cluster_authenticator_cookie_expiration_time
11
+ :simple_cluster_authenticator_cookie_expiration_time,
12
+ :metric_tags,
13
+ :metric_send_delay
12
14
 
13
15
  def initialize
14
16
  @model_class = "::ActiveRecord::Base"
@@ -19,6 +21,8 @@ module Naf
19
21
  @api_controller_class = "Naf::ApiSimpleClusterAuthenticatorApplicationController"
20
22
  @simple_cluster_authenticator_cookie_expiration_time = 1.week
21
23
  @api_domain_cookie_name = "naf_#{Rails.application.class.parent.name.underscore}"
24
+ @metric_tags = ["#{Rails.env}"]
25
+ @metric_send_delay = 120
22
26
  end
23
27
 
24
28
  end
@@ -1,3 +1,3 @@
1
1
  module Naf
2
- VERSION = '2.1.12'
2
+ VERSION = '2.1.13'
3
3
  end
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
8
8
  s.name = 'naf'
9
9
  s.version = Naf::VERSION
10
10
  s.license = 'New BSD License'
11
- s.date = '2014-05-05'
11
+ s.date = '2014-07-08'
12
12
  s.summary = 'Creates infrastructure for a customizable and robust Postgres-backed script scheduling/running'
13
13
  s.description = 'A cloud based distributed cron, application framework and operations console. Naf works as a distributed script running ' +
14
14
  'system that provides scheduling, logging, alarming, machine redundancy, and the ability to set constraint during script execution'
@@ -24,8 +24,11 @@ Gem::Specification.new do |s|
24
24
  s.add_dependency 'jquery-rails'
25
25
  s.add_dependency 'will_paginate'
26
26
  s.add_dependency 'facter', '~> 1.7.5'
27
+ s.add_dependency 'aws-sdk', '>= 1.1.0'
28
+ s.add_dependency 'yajl-ruby', '>= 1.1.0'
29
+ s.add_dependency 'dogstatsd-ruby', '>= 1.2.0'
27
30
  s.add_development_dependency 'pg'
28
- s.add_development_dependency 'rspec-rails'
31
+ s.add_development_dependency 'rspec-rails', '~> 2.14.0'
29
32
  s.add_development_dependency 'factory_girl_rails', '~> 4.0.0'
30
33
  s.add_development_dependency 'awesome_print'
31
34
 
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ module Naf
4
+
5
+ describe LogParsersController do
6
+
7
+ before do
8
+ Logical::Naf::LogParser::JobDownloader.any_instance.stub(:logs_for_download).and_return("Test Log String")
9
+ end
10
+
11
+ it "raises no exceptions" do
12
+ assert_nothing_raised do
13
+ get :download, {'record_id' => 3}
14
+ end
15
+ end
16
+
17
+ it "has a successful response" do
18
+ get :download, {'record_id' => 3}
19
+ assert_response(:success)
20
+ end
21
+
22
+ it "returns the correct string" do
23
+ get :download, {'record_id' => 3}
24
+ expect(response.body).to eql("Test Log String\n")
25
+ end
26
+
27
+ it "has the right disposition of attachment" do
28
+ get :download, {'record_id' => 3}
29
+ disposition = response.header["Content-Disposition"]
30
+ expect(disposition.include?("attachment")).to be_true
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ module Logical
4
+ module Naf
5
+
6
+ describe ApplicationSchedule do
7
+
8
+ describe '#exact_time_of_day' do
9
+
10
+ #----------------
11
+ # *** Methods ***
12
+ #++++++++++++++++
13
+
14
+ def update_schedule(run_interval)
15
+ schedule.run_interval = run_interval
16
+ schedule.save
17
+
18
+ ::Logical::Naf::ApplicationSchedule.new(schedule)
19
+ end
20
+
21
+ #------------------------
22
+ # *** Shared Examples ***
23
+ #++++++++++++++++++++++++
24
+
25
+ shared_examples 'displays the hour correctly' do |run_interval, time|
26
+ it { update_schedule(run_interval).exact_time_of_day.should == time }
27
+ end
28
+
29
+ let!(:schedule) { FactoryGirl.create(:schedule_base) }
30
+
31
+ it_should_behave_like 'displays the hour correctly', 10, '12:10 AM'
32
+ it_should_behave_like 'displays the hour correctly', 480, '08:00 AM'
33
+ it_should_behave_like 'displays the hour correctly', 2880, '12:00 AM'
34
+ it_should_behave_like 'displays the hour correctly', 20160, '12:00 AM'
35
+ it_should_behave_like 'displays the hour correctly', 46080, '12:00 AM'
36
+
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -32,6 +32,11 @@ module Logical::Naf::ConstructionZone
32
32
  end
33
33
  end
34
34
 
35
+ before do
36
+ ::Naf::HistoricalJob.delete_all
37
+ ::Naf::ApplicationType.all
38
+ end
39
+
35
40
  describe '#enqueue_application' do
36
41
  let(:application) { FactoryGirl.create(:application) }
37
42
  let(:prereq) { FactoryGirl.create(:job) }
@@ -44,13 +44,16 @@ module Logical::Naf::ConstructionZone
44
44
  describe 'run group restriction is set to limited per machine' do
45
45
  let!(:machine) { FactoryGirl.create(:machine) }
46
46
  let(:historical_job) { FactoryGirl.create(:job, application_run_group_name: 'test') }
47
- let(:tab) { FactoryGirl.create(:machine_job_affinity_tab, historical_job_id: historical_job.id) }
47
+ let!(:tab) { FactoryGirl.create(:machine_job_affinity_tab, historical_job_id: historical_job.id) }
48
48
 
49
49
  before do
50
- FactoryGirl.create(:queued_job, application_run_group_name: 'test', id: historical_job.id)
50
+ FactoryGirl.create(:queued_job, application_run_group_name: 'test',
51
+ id: historical_job.id,
52
+ historical_job: historical_job)
51
53
  FactoryGirl.create(:running_job_base, application_run_group_name: 'test',
52
54
  started_on_machine_id: machine.id,
53
- application_run_group_name: 'test')
55
+ historical_job: historical_job,
56
+ id: historical_job.id)
54
57
  end
55
58
 
56
59
  it 'return false when application does not have affinity associated with machine' do
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ module Logical
4
+ module Naf
5
+ module LogParser
6
+ describe JobDownloader do
7
+ # Create a test file, making sure to not overwrite a current file.
8
+
9
+ let!(:test_record_id) { 3 }
10
+ let!(:test_file_path) { "#{::Naf::PREFIX_PATH}/#{::Naf.schema_name}/jobs/#{test_record_id}" }
11
+ let!(:test_file_name) { "1_20140613_161535.json" }
12
+ let!(:test_file_text) { "{\n" +
13
+ " \"line_number\": 1,\n" +
14
+ " \"output_time\": \"2014-06-13 16:15:35.689\",\n" +
15
+ " \"message\": \"140613 12:15:35.689 pid=6020 jid=3 Process::Naf::LogArchiver INFO Starting to save files to s3...\"\n" +
16
+ "}" }
17
+ let!(:test_file_output) { "1 2014-06-13 16:15:35.689: 140613 12:15:35.689 pid=6020 jid=3 Process::Naf::LogArchiver INFO Starting to save files to s3...\n" }
18
+ let!(:segments_to_reset) { {} }
19
+ let!(:file_preserved) { false }
20
+
21
+ before() do
22
+ # Create a file/directory for example log. If directory exists, move it and save it
23
+ segments_to_reset = {}
24
+ total_path = ""
25
+ file_preserved = false
26
+
27
+ test_file_path.split("/").each do |segment|
28
+ unless segment == ""
29
+ segments_to_reset[segment] = false
30
+ unless File.exists?(total_path + segment + "/")
31
+ Dir.mkdir(total_path + segment + "/")
32
+ segments_to_reset[segment] = true
33
+ end
34
+ total_path += segment + "/"
35
+ end
36
+ end
37
+
38
+ if File.exists?("#{test_file_path}/#{test_file_name}")
39
+ FileUtils.mv("#{test_file_path}/#{test_file_name}", "#{test_file_path}/#{test_file_name}-preserving")
40
+ file_preserved = true
41
+ end
42
+
43
+ File.open("#{test_file_path}/#{test_file_name}", 'w') { |file| file.write(test_file_text) }
44
+ end
45
+
46
+ after() do
47
+ # Delete the example log file. If a directory was saved before, move it back
48
+ File.delete("#{test_file_path}/#{test_file_name}")
49
+
50
+ if file_preserved
51
+ FileUtils.mv("#{test_file_path}/#{test_file_name}-preserving", "#{test_file_path}/#{test_file_name}")
52
+ end
53
+
54
+ current_path = test_file_path
55
+ test_file_path.split("/").reverse_each do |segment|
56
+ if segments_to_reset[segment]
57
+ Dir.rmdir(current_path)
58
+ current_path = current_path[0..-(segment.size + 2)]
59
+ end
60
+ end
61
+
62
+ end
63
+
64
+ it "returns the correct log" do
65
+ job_log_downloader = JobDownloader.new({ 'record_id' => test_record_id })
66
+ job_log_downloader.logs_for_download.should eql(test_file_output)
67
+ end
68
+
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ module Logical
4
+ module Naf
5
+ module JobStatuses
6
+
7
+ describe Errored do
8
+
9
+ context "no conditions" do
10
+
11
+ let!(:conditions) { "" }
12
+
13
+ it "returns executable query" do
14
+ expect { ActiveRecord::Base.connection.execute(Errored.all(conditions)) }.to_not raise_error
15
+ end
16
+
17
+ end
18
+
19
+ context "with conditions" do
20
+
21
+ let!(:conditions) { "TEST STRING" }
22
+
23
+ it "adds conditions to returned sql string" do
24
+ expect(Errored.all(conditions).include?(conditions)).to be_true
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ module Logical
4
+ module Naf
5
+ module JobStatuses
6
+
7
+ describe FinishedLessMinute do
8
+
9
+ context "no conditions" do
10
+
11
+ let!(:conditions) { "" }
12
+
13
+ it "returns executable query" do
14
+ expect { ActiveRecord::Base.connection.execute(FinishedLessMinute.all(conditions)) }.to_not raise_error
15
+ end
16
+
17
+ end
18
+
19
+ context "with conditions" do
20
+
21
+ let!(:conditions) { "TEST STRING" }
22
+
23
+ it "adds conditions to returned sql string" do
24
+ expect(FinishedLessMinute.all(conditions).include?(conditions)).to be_true
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+ end
33
+ end