diff options
Diffstat (limited to 'lib/chef/application/windows_service.rb')
-rw-r--r-- | lib/chef/application/windows_service.rb | 338 |
1 files changed, 0 insertions, 338 deletions
diff --git a/lib/chef/application/windows_service.rb b/lib/chef/application/windows_service.rb deleted file mode 100644 index 8975556f75..0000000000 --- a/lib/chef/application/windows_service.rb +++ /dev/null @@ -1,338 +0,0 @@ -# -# Author:: Christopher Maier (<maier@lambda.local>) -# Copyright:: Copyright (c) Chef Software Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require_relative "../../chef" -require_relative "../monologger" -require_relative "../application" -require_relative "../client" -require_relative "../config" -require_relative "../handler/error_report" -require_relative "../log" -require_relative "../http" -require "mixlib/cli" unless defined?(Mixlib::CLI) -require "socket" unless defined?(Socket) -require "uri" unless defined?(URI) -require "win32/daemon" -require_relative "../mixin/shell_out" -require "chef-utils/dist" unless defined?(ChefUtils::Dist) - -class Chef - class Application - class WindowsService < ::Win32::Daemon - include Mixlib::CLI - include Chef::Mixin::ShellOut - - option :config_file, - short: "-c CONFIG", - long: "--config CONFIG", - default: "#{Chef::Config.etc_chef_dir}/client.rb", - description: "The configuration file to use for #{ChefUtils::Dist::Infra::PRODUCT} runs." - - option :log_location, - short: "-L LOGLOCATION", - long: "--logfile LOGLOCATION", - description: "Set the log file location." - - option :splay, - short: "-s SECONDS", - long: "--splay SECONDS", - description: "The splay time for running at intervals, in seconds.", - proc: lambda { |s| s.to_i } - - option :interval, - short: "-i SECONDS", - long: "--interval SECONDS", - description: "Set the number of seconds to wait between #{ChefUtils::Dist::Infra::PRODUCT} runs.", - proc: lambda { |s| s.to_i } - - DEFAULT_LOG_LOCATION ||= "#{Chef::Config.c_chef_dir}/client.log".freeze - - def service_init - @service_action_mutex = Mutex.new - @service_signal = ConditionVariable.new - - reconfigure - Chef::Log.info("#{ChefUtils::Dist::Infra::CLIENT} Service initialized") - end - - 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? - # Grab the service_action_mutex to make a chef-client run - @service_action_mutex.synchronize do - - Chef::Log.info("Next #{ChefUtils::Dist::Infra::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("#{ChefUtils::Dist::Infra::CLIENT} service is starting a #{ChefUtils::Dist::Infra::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 - - # Daemon class needs to have all the signal callbacks return - # before service_main returns. - Chef::Log.trace("Giving signal callbacks some time to exit...") - sleep 1 - Chef::Log.trace("Exiting service...") - end - - ################################################################################ - # Control Signal Callback Methods - ################################################################################ - - 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 #{ChefUtils::Dist::Infra::PRODUCT} run is happening on this system.") - Chef::Log.info("Service will stop when run is completed.") - run_warning_displayed = true - end - - Chef::Log.trace("Waiting for #{ChefUtils::Dist::Infra::PRODUCT} run...") - sleep 1 - end - end - Chef::Log.info("Service is stopping....") - end - - 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 #{ChefUtils::Dist::Infra::PRODUCT} run is happening.") - Chef::Log.info("Service will pause once it's completed.") - else - Chef::Log.info("Service is pausing....") - end - end - - 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 - - def service_shutdown - Chef::Log.info("SHUTDOWN signal received from the OS.") - - # Treat shutdown similar to stop. - - service_stop - end - - ################################################################################ - # Internal Methods - ################################################################################ - - private - - # Initializes Chef::Client instance and runs it - 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. - - Chef::Log.info "Starting #{ChefUtils::Dist::Infra::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? - # log_location might be an event logger and if so we cannot pass as a command argument - # but shed no tears! If the logger is an event logger, it must have been configured - # as such in the config file and chef-client will use that when no arg is passed here - config_params += " -L #{resolve_log_location}" if resolve_log_location.is_a?(String) - - # Starts a new process and waits till the process exits - - result = shell_out( - "#{ChefUtils::Dist::Infra::CLIENT}.bat #{config_params}", - timeout: Chef::Config[:windows_service][:watchdog_timeout], - logger: Chef::Log - ) - Chef::Log.trace (result.stdout).to_s - Chef::Log.trace (result.stderr).to_s - rescue Mixlib::ShellOut::CommandTimeout => e - Chef::Log.error "#{ChefUtils::Dist::Infra::CLIENT} timed out\n(#{e})" - Chef::Log.error(<<-EOF) - Your #{ChefUtils::Dist::Infra::CLIENT} run timed out. You can increase the time #{ChefUtils::Dist::Infra::CLIENT} is given - to complete by configuring windows_service.watchdog_timeout in your client.rb. - EOF - rescue Mixlib::ShellOut::ShellCommandFailed => e - Chef::Log.warn "Not able to start #{ChefUtils::Dist::Infra::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 - - def apply_config(config_file_path) - Chef::Config.from_file(config_file_path) - Chef::Config.merge!(config) - end - - # Lifted from Chef::Application, with addition of optional startup parameters - # for playing nicely with Windows Services - def reconfigure(startup_parameters = []) - configure_chef startup_parameters - configure_logging - - Chef::Config[:chef_server_url] = config[:chef_server_url] if config.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 - - # Lifted from application.rb - # See application.rb for related comments. - - def configure_logging - Chef::Log.init(MonoLogger.new(resolve_log_location)) - if want_additional_logger? - configure_stdout_logger - end - Chef::Log.level = resolve_log_level - end - - def configure_stdout_logger - stdout_logger = MonoLogger.new(STDOUT) - stdout_logger.formatter = Chef::Log.logger.formatter - Chef::Log.loggers << stdout_logger - end - - # Based on config and whether or not STDOUT is a tty, should we setup a - # secondary logger for stdout? - def want_additional_logger? - ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && !Chef::Config[:daemonize] - end - - # Use of output formatters is assumed if `force_formatter` is set or if - # `force_logger` is not set - def using_output_formatter? - Chef::Config[:force_formatter] || !Chef::Config[:force_logger] - end - - def auto_log_level? - Chef::Config[:log_level] == :auto - end - - def resolve_log_location - # STDOUT is the default log location, but makes no sense for a windows service - Chef::Config[:log_location] == STDOUT ? DEFAULT_LOG_LOCATION : Chef::Config[:log_location] - end - - # if log_level is `:auto`, convert it to :warn (when using output formatter) - # or :info (no output formatter). See also +using_output_formatter?+ - def resolve_log_level - if auto_log_level? - if using_output_formatter? - :warn - else - :info - end - else - Chef::Config[:log_level] - end - end - - 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 %r{^(http|https)://} - Chef::HTTP.new("").streaming_request(config[:config_file]) { |f| apply_config(f.path) } - else - ::File.open(config[:config_file]) { |f| apply_config(f.path) } - end - rescue Errno::ENOENT - Chef::Log.warn("*****************************************") - Chef::Log.warn("Did not find config file: #{config[:config_file]}. Using command line options instead.") - Chef::Log.warn("*****************************************") - - Chef::Config.merge!(config) - rescue SocketError - Chef::Application.fatal!("Error getting config file #{Chef::Config[:config_file]}") - rescue Chef::Exceptions::ConfigurationError => error - Chef::Application.fatal!("Error processing config file #{Chef::Config[:config_file]} with error #{error.message}") - rescue Exception => error - Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}") - end - end - - end - end -end - -# To run this file as a service, it must be called as a script from within -# the Windows Service framework. In that case, kick off the main loop! -if __FILE__ == $0 - Chef::Application::WindowsService.mainloop -end |