Hi Josh,

After a lot of digging around, I think I have partial solution;

NOTE - This is my first attempt at writing ruby so I expect there are some 
issues with what I've written.  I only had a single host (Server 2008 R2 
64bit) to test this on, but I believe the changes I've made are generic 
enough to work on all puppet supported MS Operating Systems, 32 or 64bit. 

While the CMD.EXE is not required by the Agent once running, there Service 
Control Manager is still monitoring that process and if it dies, it will 
say the service is not running, even though the orphaned the RUBY.EXE 
process is still running.

WINDOWS SERVICE CONFIG
The process for the service doesn't need the entire environment as that of 
puppet, in order run.  The way I see it, the service needs enough 
information to act as a Windows Service and to spawn child processes of 
Puppet.  The Puppet.BAT calls Environment.BAT which does all the work to 
setup the environment variables on a per call basis.

So what I did is change the ImagePath of the pe-puppet service to call ruby 
directly;

HKLM\System\CurrentControlSet\Services\pe-puppet\ImagePath;

FROM:
"C:\Program Files (x86)\Puppet Labs\Puppet Enterprise\service\daemon.bat"

TO:
"C:\Program Files (x86)\Puppet Labs\Puppet 
Enterprise\sys\ruby\bin\rubyw.exe" -C"C:\Program Files (x86)\Puppet 
Labs\Puppet Enterprise\service" "C:\Program Files (x86)\Puppet Labs\Puppet 
Enterprise\service\daemon.rb"

That is enough information for Ruby to run the service.  Obviously the 
paths in these may differ depending on each host, BUT that can all be 
authored in the puppet MSI easily.

DAEMON.RB
I made some changes to daemon.rb (attached to this post);
* I created a basic function for Windows EventLog logging Puppet Bug 
#21641.  It doesn't register an application source so it's a bit of a hack 
and could really do with a more professional cleanup.

* I fixed up the behaiour of Puppet Agent terminating once Paused Bug #22972

* A side effect of not running the daemon from a CMD.EXE was that the call 
to get to runinterval was failing.  I suspect this is due to STDOUT not 
being available anymore.  So I used the well worn method of pipe the output 
to a file and read that instead (Lines 60-79). I still need to try RUBY.EXE 
instead of RUBYW.EXE and see if it makes a difference.

* I put the Puppet Agent run in an IF statement, which will only evaluate 
as true if the service is in a RUNNING or IDLE state (Lines 81-86)

* I think may have found a bug in the Win32 Daemon code which was taking 
the service out of PAUSED and put it into a RUNNING state whenever a 
SERVICE_INTERROGATE event is recieved.  I need to log this with 
the authours. (Lines 108-119).

* I added in a little extra logging in the Resume and Pause events.  I 
changed some of the wording in the main loop to reduce any confusion about 
"Service Resuming"

Glenn.

-- 
You received this message because you are subscribed to the Google Groups 
"Puppet Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to puppet-users+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/puppet-users/f540d5d5-f21b-455f-8ce9-a561cac3ef79%40googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
#!/usr/bin/env ruby

require 'fileutils'
require 'win32/daemon'
require 'win32/dir'
require 'win32/process'
require 'win32/eventlog'

require 'windows/synchronize'
require 'windows/handle'

class WindowsDaemon < Win32::Daemon
  include Windows::Synchronize
  include Windows::Handle
  include Windows::Process
  
  LOG_FILE =  File.expand_path(File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 
'puppet', 'var', 'log', 'windows.log'))
  LEVELS = [:debug, :info, :notice, :err]
  LEVELS.each do |level|
    define_method("log_#{level}") do |msg|
      log(msg, level)
    end
  end

  def service_init
    FileUtils.mkdir_p(File.dirname(LOG_FILE))
  end

  def service_main(*argv)
    args = argv.join(' ')
    @loglevel = LEVELS.index(argv.index('--debug') ? :debug : :notice)
    
    log_notice("Starting service: #{args}")
    puppetpid = -1
    basedir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
    puppet = File.join(basedir, 'bin', 'puppet.bat')
        
    raise_windows_event(Win32::EventLog::INFO,0x01,"Starting Puppet Agent using 
Puppet: #{puppet}")
    
    while running? do
      return if !running?

      log_notice('Service is running')

      unless File.exists?(puppet)
        log_err("File not found: '#{puppet}'")
        raise_windows_event(Win32::EventLog::ERROR,0x02,"Could not find Puppet: 
#{puppet}")
        return
      end

      return if !running?
      log_debug("Using '#{puppet}'")

      begin
        #DEBUG
        #runinterval = %x{ "#{puppet}" agent --configprint runinterval }.to_i
        #  Stdout redirection seems to fail when using this service without a 
parent CMD.EXE.  Using the old method of pipe to text file and read it.
        #  Ineffecient, but it works.
        
        runinterval = 0        
        tempfile = "#{ENV['TEMP']}\\puppetinterval.tmp"
        if (File.exists?(tempfile))
          File.delete(tempfile)
        end        
        temppid = Process.create(:command_line => "#{ENV['COMSPEC']} /C 
\"#{puppet}\" agent --configprint runinterval > #{tempfile}").process_id
        Process.waitpid(temppid)
        if (!File.exists?(tempfile))
          log_err("Agent failed to write runinterval to stdout")
        else
          runinterval = (File.read(tempfile)).to_i
        end
        if runinterval == 0
          runinterval = 1800
          log_err("Failed to determine runinterval, defaulting to 
#{runinterval} seconds")
        end
      rescue Exception => e
        log_exception(e)
        runinterval = 1800
      end

      if (state == RUNNING || state == IDLE)
        puppetpid = Process.create(:command_line => "\"#{puppet}\" agent 
--onetime #{args}").process_id
        log_debug("Process created: #{puppetpid}")
      else
        log_debug("Service is not in a state to start Puppet")    
      end

      log_debug("Service waiting for #{runinterval} seconds")
      sleep(runinterval)
      log_debug('Service woken up')
    end

    # TODO: Check if puppetpid is still running.  If so raise a warning in the 
eventlog and log. Do I let the Puppet run continue or kill the process?
    # If you kill the process, it will only kill the CMD.EXE, not the child 
RUBY process.
    # Use Win32::Process.kill(0,puppetpid) to see if it's alive
    
    log_notice('Service stopped')
  rescue Exception => e
    log_exception(e)
  end

  def service_stop
    log_notice('Service stopping')
    Thread.main.wakeup
  end
  
  def service_pause
    # I don't know why it does, but the service state eventually comes out of 
Paused and goes into Running
    # I suspect this is more of a Ruby Win32Daemon issue than this script.
    #
    # Yep, confirmed:
    # From the Win32 Services Gem; daemon.c
    #   ...Program Files (x86)\Puppet Labs\Puppet 
Enterprise\sys\ruby\lib\ruby\gems\1.9.1\gems\win32-service-0.7.2-x86-mingw32\ext\win32\daemon.c
    # Line 240: // Set the status of the service.
    # Line 241: SetTheServiceStatus(dwState, NO_ERROR, 0, 0);
    #
    # The preceding switch statement sets the dwState to RUNNING when a 
SERVICE_INTERROGATE event occurs, which is about every 60 seconds and then 
tells the SCM that this service is RUNNING
    # This is a fairly old version of the Win32 Daemon. v0.8.2 has been 
released but it looks like it has the same logic flow (Lines 107 to 132)
    # I need to raise a bug report in the Win32 Daemon Gem for this.

    log_notice('Service pausing. The service will not stay paused and will 
eventually go back into a running state.')
  end

  def service_resume
    log_notice('Service resuming')
  end
  
  def service_shutdown
    log_notice('Host is shutting down') 
  end

  # Interrogation handler is just for debug.  Can be commented out or removed 
entirely.
  def service_interrogate
    log_debug('Service is being intertogated')
  end
  
  def log_exception(e)
    log_err(e.message)
    log_err(e.backtrace.join("\n"))
    raise_windows_event(Win32::EventLog::ERROR,0x02,e.message)
  end

  def log(msg, level)
    if LEVELS.index(level) >= @loglevel
      File.open(LOG_FILE, 'a') { |f| f.puts("#{Time.now} Puppet (#{level}): 
#{msg}") }
    end
  end
  
  def raise_windows_event(type,id,message)
    begin
      eventlog = Win32::EventLog.open("Application")
        eventlog.report_event(
                :source      => "Puppet Agent",
                :event_type  => type,   # Win32::EventLog::INFO or WARN, ERROR
                :event_id    => id,     # 0x01 or 0x02, 0x03 etc.
                :data        => message # "the message"
        )
      eventlog.close
    rescue Exception => e
      # Ignore all errors
    end
  end

end

if __FILE__ == $0
  WindowsDaemon.mainloop
end

Reply via email to