class Chef::Application::WindowsService

Public Instance Methods

service_init() click to toggle source
# File lib/chef/application/windows_service.rb, line 62
def service_init
  @service_action_mutex = Mutex.new
  @service_signal = ConditionVariable.new

  reconfigure
  Chef::Log.info("Chef Client Service initialized")
end
service_main(*startup_parameters) click to toggle source
# File lib/chef/application/windows_service.rb, line 70
def service_main(*startup_parameters)
  # Chef::Config is initialized during service_init
  # Set the initial timeout to splay sleep time
  timeout = rand Chef::Config[:splay]

  while running? do
    # Grab the service_action_mutex to make a chef-client run
    @service_action_mutex.synchronize do
      begin
        Chef::Log.info("Next chef-client run will happen in #{timeout} seconds")
        @service_signal.wait(@service_action_mutex, timeout)

        # Continue only if service is RUNNING
        next if state != RUNNING

        # Reconfigure each time through to pick up any changes in the client file
        Chef::Log.info("Reconfiguring with startup parameters")
        reconfigure(startup_parameters)
        timeout = Chef::Config[:interval]

        # Honor splay sleep config
        timeout += rand Chef::Config[:splay]

        # run chef-client only if service is in RUNNING state
        next if state != RUNNING

        Chef::Log.info("Chef-Client service is starting a chef-client run...")
        run_chef_client
      rescue SystemExit => e
        # Do not raise any of the errors here in order to
        # prevent service crash
        Chef::Log.error("#{e.class}: #{e}")
      rescue Exception => e
        Chef::Log.error("#{e.class}: #{e}")
      end
    end
  end

  # Daemon class needs to have all the signal callbacks return
  # before service_main returns.
  Chef::Log.debug("Giving signal callbacks some time to exit...")
  sleep 1
  Chef::Log.debug("Exiting service...")
end
service_pause() click to toggle source
# File lib/chef/application/windows_service.rb, line 143
def service_pause
  Chef::Log.info("PAUSE request from operating system.")

  # We don't need to wake up the service_main if it's waiting
  # since this is a PAUSE signal.

  if @service_action_mutex.locked?
    Chef::Log.info("Currently a chef-client run is happening.")
    Chef::Log.info("Service will pause once it's completed.")
  else
    Chef::Log.info("Service is pausing....")
  end
end
service_resume() click to toggle source
# File lib/chef/application/windows_service.rb, line 157
def service_resume
  # We don't need to wake up the service_main if it's waiting
  # since this is a RESUME signal.

  Chef::Log.info("RESUME signal received from the OS.")
  Chef::Log.info("Service is resuming....")
end
service_shutdown() click to toggle source
# File lib/chef/application/windows_service.rb, line 165
def service_shutdown
  Chef::Log.info("SHUTDOWN signal received from the OS.")

  # Treat shutdown similar to stop.

  service_stop
end
service_stop() click to toggle source

Control Signal Callback Methods

# File lib/chef/application/windows_service.rb, line 119
def service_stop
  run_warning_displayed = false
  Chef::Log.info("STOP request from operating system.")
  loop do
    # See if a run is in flight
    if @service_action_mutex.try_lock
      # Run is not in flight. Wake up service_main to exit.
      @service_signal.signal
      @service_action_mutex.unlock
      break
    else
      unless run_warning_displayed
        Chef::Log.info("Currently a chef run is happening on this system.")
        Chef::Log.info("Service  will stop when run is completed.")
        run_warning_displayed = true
      end

      Chef::Log.debug("Waiting for chef-client run...")
      sleep 1
    end
  end
  Chef::Log.info("Service is stopping....")
end

Private Instance Methods

apply_config(config_file_path) click to toggle source
# File lib/chef/application/windows_service.rb, line 204
def apply_config(config_file_path)
  Chef::Config.from_file(config_file_path)
  Chef::Config.merge!(config)
end
auto_log_level?() click to toggle source
# File lib/chef/application/windows_service.rb, line 252
def auto_log_level?
  Chef::Config[:log_level] == :auto
end
configure_chef(startup_parameters) click to toggle source
# File lib/chef/application/windows_service.rb, line 270
def configure_chef(startup_parameters)
  # Bit of a hack ahead:
  # It is possible to specify a service's binary_path_name with arguments, like "foo.exe -x argX".
  # It is also possible to specify startup parameters separately, either via the Services manager
  # or by using the registry (I think).

  # In order to accommodate all possible sources of parameterization, we first parse any command line
  # arguments.  We then parse any startup parameters.  This works, because Mixlib::CLI reuses its internal
  # 'config' hash; thus, anything in startup parameters will override any command line parameters that
  # might be set via the service's binary_path_name
  #
  # All these parameters then get layered on top of those from Chef::Config

  parse_options # Operates on ARGV by default
  parse_options startup_parameters

  begin
    case config[:config_file]
    when /^(http|https):\/\//
      Chef::REST.new("", nil, nil).fetch(config[:config_file]) { |f| apply_config(f.path) }
    else
      ::File::open(config[:config_file]) { |f| apply_config(f.path) }
    end
  rescue Errno::ENOENT => error
    Chef::Log.warn("*****************************************")
    Chef::Log.warn("Did not find config file: #{config[:config_file]}, using command line options.")
    Chef::Log.warn("*****************************************")

    Chef::Config.merge!(config)
  rescue SocketError => error
    Chef::Application.fatal!("Error getting config file #{Chef::Config[:config_file]}", 2)
  rescue Chef::Exceptions::ConfigurationError => error
    Chef::Application.fatal!("Error processing config file #{Chef::Config[:config_file]} with error #{error.message}", 2)
  rescue Exception => error
    Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}", 2)
  end
end
configure_logging() click to toggle source

Lifted from application.rb See application.rb for related comments.

# File lib/chef/application/windows_service.rb, line 226
def configure_logging
  Chef::Log.init(MonoLogger.new(Chef::Config[:log_location]))
  if want_additional_logger?
    configure_stdout_logger
  end
  Chef::Log.level = resolve_log_level
end
configure_stdout_logger() click to toggle source
# File lib/chef/application/windows_service.rb, line 234
def configure_stdout_logger
  stdout_logger = MonoLogger.new(STDOUT)
  stdout_logger.formatter = Chef::Log.logger.formatter
  Chef::Log.loggers <<  stdout_logger
end
reconfigure(startup_parameters=[]) click to toggle source

Lifted from Chef::Application, with addition of optional startup parameters for playing nicely with Windows Services

# File lib/chef/application/windows_service.rb, line 211
def reconfigure(startup_parameters=[])
  configure_chef startup_parameters
  configure_logging

  Chef::Config[:chef_server_url] = config[:chef_server_url] if config.has_key? :chef_server_url
  unless Chef::Config[:exception_handlers].any? {|h| Chef::Handler::ErrorReport === h}
    Chef::Config[:exception_handlers] << Chef::Handler::ErrorReport.new
  end

  Chef::Config[:interval] ||= 1800
end
resolve_log_level() click to toggle source

if log_level is `:auto`, convert it to :warn (when using output formatter) or :info (no output formatter). See also using_output_formatter?

# File lib/chef/application/windows_service.rb, line 258
def resolve_log_level
  if auto_log_level?
    if using_output_formatter?
      :warn
    else
      :info
    end
  else
    Chef::Config[:log_level]
  end
end
run_chef_client() click to toggle source

Initializes Chef::Client instance and runs it

# File lib/chef/application/windows_service.rb, line 180
def run_chef_client
  # The chef client will be started in a new process. We have used shell_out to start the chef-client.
  # The log_location and config_file of the parent process is passed to the new chef-client process.
  # We need to add the --no-fork, as by default it is set to fork=true.
  begin
    Chef::Log.info "Starting chef-client in a new process"
    # Pass config params to the new process
    config_params = " --no-fork"
    config_params += " -c #{Chef::Config[:config_file]}" unless  Chef::Config[:config_file].nil?
    config_params += " -L #{Chef::Config[:log_location]}" unless Chef::Config[:log_location] == STDOUT
    # Starts a new process and waits till the process exits
    result = shell_out("chef-client #{config_params}")
    Chef::Log.debug "#{result.stdout}"
    Chef::Log.debug "#{result.stderr}"
  rescue Mixlib::ShellOut::ShellCommandFailed => e
    Chef::Log.warn "Not able to start chef-client in new process (#{e})"
  rescue => e
    Chef::Log.error e
  ensure
    # Once process exits, we log the current process' pid
    Chef::Log.info "Child process exited (pid: #{Process.pid})"
  end
end
using_output_formatter?() click to toggle source

Use of output formatters is assumed if `force_formatter` is set or if `force_logger` is not set and STDOUT is to a console (tty)

# File lib/chef/application/windows_service.rb, line 248
def using_output_formatter?
  Chef::Config[:force_formatter] || (!Chef::Config[:force_logger] && STDOUT.tty?)
end
want_additional_logger?() click to toggle source

Based on config and whether or not STDOUT is a tty, should we setup a secondary logger for stdout?

# File lib/chef/application/windows_service.rb, line 242
def want_additional_logger?
  ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && (!Chef::Config[:daemonize]) && (Chef::Config[:force_logger])
end