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 [email protected].
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