naf 2.1.12 → 2.1.13

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.
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