Class: Emissary::Daemon

Inherits:
Object show all
Includes:
ServerController
Defined in:
lib/emissary/daemon.rb

Constant Summary collapse

SHUTDOWN_RETRY =
1
MAX_RESTARTS =
10
REQUIRED_AGENTS =
[ :emissary, :ping, :error ]

Constants included from ServerController

ServerController::DEFAULT_PID_FILE_MODE, ServerController::SIGNALS

Instance Attribute Summary collapse

Attributes included from ServerController

#pid_file, #pid_file_mode

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ServerController

#alive?, #create_pid_file, #delete_pid_file, #pid, #pid_dir, #retrieve_pid, #trap_signals

Constructor Details

#initialize(name, opts = {}) ⇒ Daemon

Returns a new instance of Daemon.

Raises:



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/emissary/daemon.rb', line 140

def initialize(name, opts = {})

  @operators = {}
  @name = name
  @mutex = Mutex.new
  @shutdown = nil
  
  self.class.const_set('STARTUP_OPTS', opts.clone_deep)
  @config_file = File.expand_path(opts.delete(:config_file) || '/etc/emissary/config.ini')
  @config = Daemon.get_config(config_file, STARTUP_OPTS)
  
  self.class.const_set('CONFIG_FILE', @config_file)
  
  @logger = Emissary::Logger.instance
  @logger.level = @config[:general][:log_level]

  @pid_file = config[:general][:pid_file]
  @pid_file_mode = config[:general][:pid_file_mode] || DEFAULT_PID_FILE_MODE

  ary = %w[name config_file].map { |var|
    self.send(var.to_sym).nil? ? var : nil
  }.compact
  raise Error, "These variables are required: #{ary.join(', ')}." unless ary.empty?
end

Instance Attribute Details

#configObject

Returns the value of attribute config.



137
138
139
# File 'lib/emissary/daemon.rb', line 137

def config
  @config
end

#config_fileObject

Returns the value of attribute config_file.



137
138
139
# File 'lib/emissary/daemon.rb', line 137

def config_file
  @config_file
end

#loggerObject

Returns the value of attribute logger.



137
138
139
# File 'lib/emissary/daemon.rb', line 137

def logger
  @logger
end

#nameObject

Returns the value of attribute name.



137
138
139
# File 'lib/emissary/daemon.rb', line 137

def name
  @name
end

#operatorsObject (readonly)

Returns the value of attribute operators.



138
139
140
# File 'lib/emissary/daemon.rb', line 138

def operators
  @operators
end

#stateObject

Returns the value of attribute state.



137
138
139
# File 'lib/emissary/daemon.rb', line 137

def state
  @state
end

Class Method Details

.get_config(config_file, opts = {}) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
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
# File 'lib/emissary/daemon.rb', line 165

def self.get_config(config_file, opts = {})
  config = Daemon.validate_config!(Emissary::ConfigFile.new(config_file))

  config[:general][:daemonize] = opts.delete(:daemonize) || false
  
  config[:general][:agents] ||= 'all'
  config[:general][:agents] = if config[:general][:agents].instance_of? String
    config[:general][:agents].split(/\s*,\s*/)
  else
    config[:general][:agents].to_a
  end
  
  config[:general][:log_level] = opts.delete(:log_level) || config[:general][:log_level] || 'NOTICE'

  unless (log_level = config[:general][:log_level]).kind_of? Fixnum
    case log_level
      when /^(LOG_){0,1}([A-Z]+)$/i
        log_level = Emissary::Logger::CONSTANT_NAME_MAP[$2]
      when Symbol
        log_level = Emissary::Logger::CONSTANT_NAME_MAP[log_level]
      when /[0-9]+/
        log_level = log_level.to_i
    end
    config[:general][:log_level] = log_level
  end
  
  config[:general][:pid_dir]  = opts.delete(:pid_dir)  || '/var/run'
  
  # set up defaults
  config[:agents] ||= {}
  config[:agents][:emissary] ||= {}
  config[:agents][:emissary][:config_file] = File.expand_path(config_file)
  config[:agents][:emissary][:config_path] = File.dirname(File.expand_path(config_file))

  
  config[:general][:operators].each do |operator|
    config[operator.to_sym].each do |name,data|
        # setup the enabled and disabled agents on a per operator basis
        agents = data[:agents].blank? ? config[:general][:agents] : if data[:agents].kind_of?(Array)
          data[:agents]
        else
          data[:agents].split(/\s*,\s*/)
        end
        
        disable = agents.select { |v| v =~ /^-/ }.inject([]) { |a,v| a << v.gsub(/^-/,'').to_sym }
        disable.include?(:all) && disable.delete_if { |v| v != :all }
        
        enable = agents.select { |v| v !~ /^-/ }.inject([]) { |a,v| a << v.to_sym; a }
        enable.include?(:all) && enable.delete_if { |v| v != :all }
        
        # don't let the user disable REQUIRED AGENTS
        disable -= REQUIRED_AGENTS
        
        enable = ( enable.include?(:all) ? [ :all ] : enable | REQUIRED_AGENTS )
        
                                            if not (conflicts = (enable - (enable - disable))).empty?
                                                    raise "Conflicting enabled/disabled agents: [#{conflicts.join(', ')}] - you can not both specifically enable and disable an agent!"
                                            end
        
        # now copy over the agent specific settings and
        # append __enabled__ and __disabled__ list
        data[:agents] = config[:agents].clone
        data[:agents][:__enabled__] = enable
        data[:agents][:__disabled__] = disable
    end
  end

  config
end

.validate_config!(config) ⇒ Object



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
# File 'lib/emissary/daemon.rb', line 235

def self.validate_config!(config)
  unless config[:general]
    raise ::Emissary::ConfigValidationError.new(Exception, "Missing 'general' section in configuration file")
  end
  
  unless config[:general][:operators]
    logger.debug config[:general].inspect
    raise ::Emissary::ConfigValidationError.new(Exception, "[general][operators] not set")
  end
  
  unless config[:general][:operators].kind_of? Array
    raise ::Emissary::ConfigValidationError.new(Exception, "[general][operators] not a list")
  end
  
  config[:general][:operators].each do |operator|
    operator = operator.to_sym
    unless config[operator]
      raise ::Emissary::ConfigValidationError.new(Exception, "Missing Operator Section '#{operator}'")
    end
    
    unless config[operator].kind_of? Hash
      raise ::Emissary::ConfigValidationError.new(Exception, "Operator Section '#{operator}' not a dictionary of operators")
    end
  end

  config
end

Instance Method Details

#become_daemonObject



263
264
265
266
267
268
# File 'lib/emissary/daemon.rb', line 263

def become_daemon
  # switch to syslog mode for logging
  @logger.mode  = Emissary::Logger::LOG_SYSLOG
  Daemonize::daemonize(nil, name)
  create_pid_file
end

#call_operatorsObject



278
279
280
281
282
283
284
285
286
# File 'lib/emissary/daemon.rb', line 278

def call_operators
  config[:general][:operators].each do |operator|
    opsym = operator.to_sym
    config[opsym].each do |name,data|
      op = Emissary.call operator, data.merge({:signature => name, :parent_pid => $$})
      @operators[op.signature] = { :operator => op, :start_count => 0 }
    end
  end
end

#can_startup?(operator) ⇒ Boolean

Returns:

  • (Boolean)


270
271
272
273
274
275
276
# File 'lib/emissary/daemon.rb', line 270

def can_startup? operator
  result = true
  result &= (!operator[:daemon] || !operator[:daemon].alive?)
  result &= operator[:start_count] < MAX_RESTARTS
  result &= (not @shutting_down)
  result
end

#reconfigObject Also known as: hup



288
289
290
291
292
293
294
295
296
297
298
# File 'lib/emissary/daemon.rb', line 288

def reconfig
  Emissary.logger.warn "Reloading configuration."
  begin
    new_config = Daemon.get_config(config_file, STARTUP_OPTS)
  rescue Exception => e
    Emissary.logger.error "Unable to reload configuration due to error:\n#{e.message}\n\t#{e.backtrace.join("\n\t")}"
  else
    @config = new_config
  end
  self.restart
end

#restartObject Also known as: usr1



300
301
302
303
# File 'lib/emissary/daemon.rb', line 300

def restart
  shutdown false
  startup
end

#shutdown(do_exit = true) ⇒ Object Also known as: int, term, kill, exit



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
# File 'lib/emissary/daemon.rb', line 320

def shutdown do_exit = true
  Emissary.logger.info "Shutdown Requested - Stopping operators"

  @operators.each_key do |name|
    operator = do_exit ? @operators.delete(name) : @operators[name]
    
    Emissary.logger.notice "Shutting down operator '#{name}' - current status: #{operator[:daemon].alive? ? 'running' : 'stopped'}"
    while operator[:daemon].alive? 
      Emissary.logger.debug "[SHUTDOWN] Hanging Up on Operator call '#{name}' (process: #{operator[:daemon_pid]})"
      # should have shutdown above but, let's be sure here
      operator[:daemon].shutdown if operator[:daemon].alive?
    end

    # Our shutdowns don't count toward restart limit for operators
    # We're only protecting against multiple failed starts with it.
    operator[:start_count] -= 1 unless operator[:start_count] <= 0
  end
  
  if do_exit
    Emissary.logger.info "Shutdown Complete - Exiting..."
    exit!(0)
  end
end

#start_run_loopObject



344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# File 'lib/emissary/daemon.rb', line 344

def start_run_loop
  while not @shutting_down do
    @operators.each do |name,operator|
      if operators[:start_count].to_i > MAX_RESTARTS
        ::Emissary.logger.warning "Start Count > MAX_RESTARTS for operator '#{name}' - removing from list of operators..."
        @operators.delete(name) 
        next
      end
      
      if can_startup? operator
        Emissary.logger.notice "Starting up Operator: #{name}"
        
        server_data = {
            :operator => @operators[name][:operator],
            :pid_file => File.join(pid_dir, "emop_#{name}"),
        }

        operator[:server] = Emissary::Server.new("emop_#{name}", server_data)
        operator[:daemon] = Servolux::Daemon.new(:server => operator[:server])

        # if the daemon is already alive before we've called startup
        # then some other process started it, so we don't bother
        if operator[:daemon].alive? 
          Emissary.logger.warning "Operator '#{name}' already running with pid '#{operator[:daemon].get_pid}'."
          @operators.delete(name)
          next
        end

        operator[:daemon].startup false
        operator[:parent_pid] = retrieve_pid rescue $$
        operator[:daemon_pid] = operator[:daemon].get_pid
        operator[:start_count] += 1

        if operator[:start_count] >= MAX_RESTARTS
          Emissary.logger.warning "Operator '#{name}' has been restarted #{MAX_RESTARTS} times. " +
                                  "I will not attempt to restart it anymore."
        end

        Emissary.logger.notice "Forked Operator '#{name}' with pid #{operator[:daemon_pid]}"
      end
    end

    # if there are no operators left, then there is no point
    # continue - so exit...
    if @operators.length <= 0
      Emissary.logger.notice "No operators left - shutting down." 
      shutdown true
    end

    sleep DAEMON_RECHECK_INTERVAL
  end
end

#startupObject



305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/emissary/daemon.rb', line 305

def startup
  return if alive?
  
  begin
    become_daemon if config[:general][:daemonize]
    trap_signals
    call_operators
    start_run_loop
  rescue StandardError => e
    Emissary.logger.error "Error Starting up: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
  ensure
    delete_pid_file
  end
end