Class: JobManager::Application

Inherits:
Object
  • Object
show all
Defined in:
lib/jobmanager/application.rb

Defined Under Namespace

Classes: ComposedError, Error

Instance Method Summary collapse

Constructor Details

#initialize(program_name, argv, config_file, override_terminal_shell = nil) ⇒ Application

Description:

This method creates an Application instance. It also reads in the configuration file and the command line options.

Parameters:

program_name

The name of the program.

argv

A list of command line arguments.

config_file

The jobmanager configuration file name.

override_terminal_shell

Whether to override this method’s internal determination of whether the calling process has been invoked by a shell attached to a terminal. If the value is nil, this method will make its own determination. This is purely for debugging purposes.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/jobmanager/application.rb', line 80

def initialize(program_name, 
               argv, 
               config_file,
               override_terminal_shell = nil)

  begin
    @mgr_logger = nil
    @email_ready = false
    @program_name = program_name
    @argv = argv
    @config_file = config_file
    @user_name = Etc.getpwuid(Process::Sys.getuid).name()
    
    @terminal_shell = STDIN.tty? || STDOUT.tty? || STDERR.tty?
    if (override_terminal_shell != nil)
      @terminal_shell = override_terminal_shell
    end
    
    # If any of this process's standard iostreams are connected to
    # a terminal, this process has been invoked directly or
    # indirectly by the command line shell.  As a result, let's log
    # central log messages to STDOUT.
    if (@terminal_shell)
      @mgr_logger = ApplicationIOLogger.new(STDOUT,
                                            @user_name)
      
      str =  "\n"
      str << "*NOTE* "
      str << "jobmanager has been invoked interactively.  Ignoring the "
      str << "central_log_* configuration parameters, and logging all output to "
      str << "the terminal.\n\n"
      
      print str
      
    else
      # None of this process's standard iostreams are connected to
      # a terminal.  This process has most likely been invoked by
      # cron.  Log all central log messages to syslog.  The user
      # may have specified in the configuration file for all
      # central log messages to be directed to a file.  However,
      # until we successfully process the configuration file, we
      # do not know the log mode they have chosen.  Thus,
      # temporarily log central log messages to syslog until we
      # have read this configuration parameter.  No messages will
      # actually be written to syslog unless an error condition
      # occurs (such as not being able to read or parse the
      # configuration file itself!)
      @mgr_logger = ApplicationSyslogLogger.new(@program_name,
                                                @user_name)
    end
  
    # @mgr_logger is now valid! We can print to @mgr_logger in
    # rescue statements going forwards.
    # This method populates @config with the configuration from
    # the configuration file and the command line parameters.
    gather_options(@program_name, @argv, @config_file)
    
    # This is done so that if any methods called by this
    # application call any of the standard print commands (print,
    # puts, etc) directly, these messages will be printed to
    # @mgr_logger.
    $stderr = $stdout = @mgr_logger
    
    # Print the config in debug mode.
    if (@mgr_logger.level <= Logger::DEBUG)
      @mgr_logger.debug("Configuration:\n #{@config}\n")
    end
  rescue => e
    handle_exception(e)
    # Re-raise the exception to notify bin/jobmanager to exit!
    raise e
  end
end

Instance Method Details

#handle_exception(e) ⇒ Object



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/jobmanager/application.rb', line 154

def handle_exception(e)
  if (@mgr_logger)
    case e
    when ComposedError
      @mgr_logger.record_tagged_exception(e.message, e.contained_exception)
    when Error
      @mgr_logger.record_exception(e)
    else
      @mgr_logger.record_tagged_exception("Unexpected Error", e)
    end
    
    begin
      if (@email_ready) then email_results(false) end
    rescue => e
      @mgr_logger.record_tagged_exception("Error emailing results", e)
    end
    
    @mgr_logger.close() 
    @mgr_logger = nil
  end
  
  $stderr = STDERR
  $stdout = STDOUT
end

#runObject

Description:

This method runs the jobmanager application.



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/jobmanager/application.rb', line 183

def run()      
  success = false
  
  job_log_file = File.join(@config.job_logs_directory, "#{@config.job_name}.log")
  @mgr_logger.debug("Job log file: #{job_log_file}.")
  
  #
  # Create a stream that only opens the specified file when the
  # first write is called.  This is to prevent empty files from
  # being created.
  #
  job_log_file_stream = OpenOnFirstWriteFile.new(job_log_file, "a")
  
  #
  # The output of the job that will be invoked will be sent back
  # to this process via a pipe (inside invoke_command).  This
  # process will use the job's output for 2 purposes.  It will be
  # forwarded to a job log file and the output will be used later
  # when emailing results.  invoke_command takes only 1
  # job_output_stream.  We could pass a string stream to
  # invoke_command, and after write the contents to the job's log
  # file, and also include it in the emailed results.  However,
  # this is less than optimal, as the job output will not be
  # written to the log file in real time; it will not be written
  # until after the job has completed.  For long jobs, this can
  # leave the job owner with no way of determining the status of
  # the job while it is running.  Instead, we construct a
  # TeeStream which is a stream that forwards the messages to a
  # list of streams.  In this case, we will initialize the
  # TeeStream to forward strings to the job log file stream and a
  # string stream (which will be later used for emailing results).
  #
  job_output = ""
  begin
    job_stream = TeeStream.new(:file => job_log_file_stream,
                               :stringio => StringIO.new())
    if (@config.job_log_prepend_date_time)
      job_stream = TimestampedStream.new(job_stream, @config.job_log_prepend_date_time_format)
    end
    
    optional_args = {}
    if @config.timeout then optional_args[:timeout] = @config.timeout end
    optional_args[:command_path] = @config.command_path
    
    # invoke the command - fork and exec the process, and wait til it finishes to reap the status.
    success = System::invoke_command(@config.command, 
                                     job_stream,
                                     @mgr_logger,
                                     optional_args)
    
    if (job_stream.is_a?(TimestampedStream))
      job_stream = job_stream.stream()
    end
    job_output = job_stream.stream(:stringio).string()
    job_stream.close()
    
  rescue => e
    @mgr_logger.record_tagged_exception("Error running job", e)
  end
  
  # Rotate the job log file.
  if (@config.rotate_job_log && File.file?(job_log_file))
    begin
      logrotate_options = { 
        :count => @config.number_of_job_logs,
        :date_time_ext => @config.date_time_extension,
        :date_time_format => @config.date_time_extension_format,
        :gzip => @config.zip_rotated_log_file
      }
      
      result = LogRotate.rotate_file(job_log_file, logrotate_options)
      
      @mgr_logger.info("Job log rotated to #{result.new_rotated_file}.")
    rescue => e
      success = false
      @mgr_logger.record_tagged_exception("Error rotating file #{job_log_file}", e)
    end
  end
  
  # Email the results!
  begin
    if (@config.email_condition == :always || 
        (@config.email_condition == :on_failure && !success) ||
        (@config.email_condition == :on_job_output_or_failure && (!success || job_output.length > 0)))
      
      email_results(success, job_output)
    end
  rescue => e
    @mgr_logger.record_tagged_exception("Error emailing results", e)
  end
  
rescue => e
  handle_exception(e)
  raise e
  
ensure
  if (@mgr_logger) then 
    @mgr_logger.close() 
    @mgr_logger = nil
  end
  
  $stderr = STDERR
  $stdout = STDOUT
end