summaryrefslogtreecommitdiff
path: root/lib/chef/provider/service/upstart.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/chef/provider/service/upstart.rb')
-rw-r--r--lib/chef/provider/service/upstart.rb232
1 files changed, 232 insertions, 0 deletions
diff --git a/lib/chef/provider/service/upstart.rb b/lib/chef/provider/service/upstart.rb
new file mode 100644
index 0000000000..763a2aa92b
--- /dev/null
+++ b/lib/chef/provider/service/upstart.rb
@@ -0,0 +1,232 @@
+#
+# Author:: Bryan McLellan <btm@loftninjas.org>
+# Copyright:: Copyright (c) 2010 Bryan McLellan
+# 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 'chef/provider/service'
+require 'chef/provider/service/simple'
+require 'chef/mixin/command'
+require 'chef/util/file_edit'
+
+class Chef
+ class Provider
+ class Service
+ class Upstart < Chef::Provider::Service::Simple
+ UPSTART_STATE_FORMAT = /\w+ \(?(\w+)\)?[\/ ](\w+)/
+
+ # Upstart does more than start or stop a service, creating multiple 'states' [1] that a service can be in.
+ # In chef, when we ask a service to start, we expect it to have started before performing the next step
+ # since we have top down dependencies. Which is to say we may follow witha resource next that requires
+ # that service to be running. According to [2] we can trust that sending a 'goal' such as start will not
+ # return until that 'goal' is reached, or some error has occured.
+ #
+ # [1] http://upstart.ubuntu.com/wiki/JobStates
+ # [2] http://www.netsplit.com/2008/04/27/upstart-05-events/
+
+ def initialize(new_resource, run_context)
+ # TODO: re-evaluate if this is needed after integrating cookbook fix
+ raise ArgumentError, "run_context cannot be nil" unless run_context
+ super
+
+ run_context.node
+
+ @job = @new_resource.service_name
+
+ if @new_resource.parameters
+ @new_resource.parameters.each do |key, value|
+ @job << " #{key}=#{value}"
+ end
+ end
+
+ platform, version = Chef::Platform.find_platform_and_version(run_context.node)
+ if platform == "ubuntu" && (8.04..9.04).include?(version.to_f)
+ @upstart_job_dir = "/etc/event.d"
+ @upstart_conf_suffix = ""
+ else
+ @upstart_job_dir = "/etc/init"
+ @upstart_conf_suffix = ".conf"
+ end
+
+ @command_success = true # new_resource.status_command= false, means upstart used
+ @config_file_found = true
+ @upstart_command_success = true
+ end
+
+ def define_resource_requirements
+ # Do not call super, only call shared requirements
+ shared_resource_requirements
+ requirements.assert(:all_actions) do |a|
+ if !@command_success
+ whyrun_msg = @new_resource.status_command ? "Provided status command #{@new_resource.status_command} failed." :
+ "Could not determine upstart state for service"
+ end
+ a.assertion { @command_success }
+ # no failure here, just document the assumptions made.
+ a.whyrun "#{whyrun_msg} Assuming service installed and not running."
+ end
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @config_file_found }
+ # no failure here, just document the assumptions made.
+ a.whyrun "Could not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}. Assuming service is disabled."
+ end
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Service.new(@new_resource.name)
+ @current_resource.service_name(@new_resource.service_name)
+
+ # Get running/stopped state
+ # We do not support searching for a service via ps when using upstart since status is a native
+ # upstart function. We will however support status_command in case someone wants to do something special.
+ if @new_resource.status_command
+ Chef::Log.debug("#{@new_resource} you have specified a status command, running..")
+
+ begin
+ if run_command_with_systems_locale(:command => @new_resource.status_command) == 0
+ @current_resource.running true
+ end
+ rescue Chef::Exceptions::Exec
+ @command_success = false
+ @current_resource.running false
+ nil
+ end
+ else
+ begin
+ if upstart_state == "running"
+ @current_resource.running true
+ else
+ @current_resource.running false
+ end
+ rescue Chef::Exceptions::Exec
+ @command_success = false
+ @current_resource.running false
+ nil
+ end
+ end
+ # Get enabled/disabled state by reading job configuration file
+ if ::File.exists?("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
+ Chef::Log.debug("#{@new_resource} found #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
+ ::File.open("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}",'r') do |file|
+ while line = file.gets
+ case line
+ when /^start on/
+ Chef::Log.debug("#{@new_resource} enabled: #{line.chomp}")
+ @current_resource.enabled true
+ break
+ when /^#start on/
+ Chef::Log.debug("#{@new_resource} disabled: #{line.chomp}")
+ @current_resource.enabled false
+ break
+ end
+ end
+ end
+ else
+ @config_file_found = false
+ Chef::Log.debug("#{@new_resource} did not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
+ @current_resource.enabled false
+ end
+
+ @current_resource
+ end
+
+ def start_service
+ # Calling start on a service that is already started will return 1
+ # Our 'goal' when we call start is to ensure the service is started
+ if @current_resource.running
+ Chef::Log.debug("#{@new_resource} already running, not starting")
+ else
+ if @new_resource.start_command
+ super
+ else
+ run_command_with_systems_locale(:command => "/sbin/start #{@job}")
+ end
+ end
+ end
+
+ def stop_service
+ # Calling stop on a service that is already stopped will return 1
+ # Our 'goal' when we call stop is to ensure the service is stopped
+ unless @current_resource.running
+ Chef::Log.debug("#{@new_resource} not running, not stopping")
+ else
+ if @new_resource.stop_command
+ super
+ else
+ run_command_with_systems_locale(:command => "/sbin/stop #{@job}")
+ end
+ end
+ end
+
+ def restart_service
+ if @new_resource.restart_command
+ super
+ # Upstart always provides restart functionality so we don't need to mimic it with stop/sleep/start.
+ # Older versions of upstart would fail on restart if the service was currently stopped, check for that. LP:430883
+ else @new_resource.supports[:restart]
+ if @current_resource.running
+ run_command_with_systems_locale(:command => "/sbin/restart #{@job}")
+ else
+ start_service
+ end
+ end
+ end
+
+ def reload_service
+ if @new_resource.reload_command
+ super
+ else
+ # upstart >= 0.6.3-4 supports reload (HUP)
+ run_command_with_systems_locale(:command => "/sbin/reload #{@job}")
+ end
+ end
+
+ # https://bugs.launchpad.net/upstart/+bug/94065
+
+ def enable_service
+ Chef::Log.debug("#{@new_resource} upstart lacks inherent support for enabling services, editing job config file")
+ conf = Chef::Util::FileEdit.new("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
+ conf.search_file_replace(/^#start on/, "start on")
+ conf.write_file
+ end
+
+ def disable_service
+ Chef::Log.debug("#{@new_resource} upstart lacks inherent support for disabling services, editing job config file")
+ conf = Chef::Util::FileEdit.new("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
+ conf.search_file_replace(/^start on/, "#start on")
+ conf.write_file
+ end
+
+ def upstart_state
+ command = "/sbin/status #{@job}"
+ status = popen4(command) do |pid, stdin, stdout, stderr|
+ stdout.each_line do |line|
+ # rsyslog stop/waiting
+ # service goal/state
+ # OR
+ # rsyslog (stop) waiting
+ # service (goal) state
+ line =~ UPSTART_STATE_FORMAT
+ data = Regexp.last_match
+ return data[2]
+ end
+ end
+ end
+
+ end
+ end
+ end
+end