require 'timeout'

# Solaris 10 SMF-style services.
Puppet::Type.type(:service).provide :smf, :parent => :base do
  desc <<-EOT
    Support for Sun's new Service Management Framework.

    Starting a service is effectively equivalent to enabling it, so there is
    only support for starting and stopping services, which also enables and
    disables them, respectively.

    By specifying `manifest => "/path/to/service.xml"`, the SMF manifest will
    be imported if it does not exist.

  EOT

  defaultfor :osfamily => :solaris

  confine :osfamily => :solaris

  commands :adm => "/usr/sbin/svcadm", :svcs => "/usr/bin/svcs"
  commands :svccfg => "/usr/sbin/svccfg"

  has_feature :refreshable

  def setupservice
      if resource[:manifest]
        [command(:svcs), "-l", @resource[:name]]
        if $CHILD_STATUS.exitstatus == 1
          Puppet.notice "Importing #{@resource[:manifest]} for #{@resource[:name]}"
          svccfg :import, resource[:manifest]
        end
      end
  rescue Puppet::ExecutionFailure => detail
      raise Puppet::Error.new( "Cannot config #{self.name} to enable it: #{detail}", detail )
  end

  def self.instances
   svcs("-H", "-o", "state,fmri" ).split("\n").select{|l| l !~ /^legacy_run/ }.collect do |line|
     state,fmri = line.split(/\s+/)
     status =  case state
               when /online/; :running
               when /maintenance/; :maintenance
               when /degraded/; :degraded
               else :stopped
               end
     new({:name => fmri, :ensure => status})
   end
  end

  def enable
    self.start
  end

  def enabled?
    case self.status
    when :running
      return :true
    else
      return :false
    end
  end

  def disable
    self.stop
  end

  def restartcmd
    if Puppet::Util::Package.versioncmp(Facter.value(:operatingsystemrelease), '11.2') >= 0
      [command(:adm), :restart, "-s", @resource[:name]]
    else
      # Synchronous restart only supported in Solaris 11.2 and above
      [command(:adm), :restart, @resource[:name]]
    end
  end

  def startcmd
    self.setupservice
    case self.status
    when :maintenance, :degraded
      [command(:adm), :clear, @resource[:name]]
    else
      [command(:adm), :enable, "-rs", @resource[:name]]
    end
  end

  # Wait for the service to transition into the specified state before returning.
  # This is necessary due to the asynchronous nature of SMF services.
  # desired_state should be online, offline, disabled, or uninitialized.
  # See PUP-5474 for long-term solution to this issue.
  def wait(*desired_state)
    Timeout.timeout(60) do
      loop do
        states = self.service_states
        break if desired_state.include?(states[0]) && states[1] == '-'
        sleep(1)
      end
    end
  rescue Timeout::Error
    raise Puppet::Error.new("Timed out waiting for #{@resource[:name]} to transition states")
  end

  def start
    # Wait for the service to actually start before returning.
    super
    self.wait('online')
  end

  def stop
    # Wait for the service to actually stop before returning.
    super
    self.wait('offline', 'disabled', 'uninitialized')
  end

  def restart
    # Wait for the service to actually start before returning.
    super
    self.wait('online')
  end

  # Determine the current and next states of a service.
  def service_states
    svcs("-H", "-o", "state,nstate", @resource[:name]).chomp.split
  end

  def status
    if @resource[:status]
      super
      return
    end

    begin
      # get the current state and the next state, and if the next
      # state is set (i.e. not "-") use it for state comparison
      states = service_states
      state = states[1] == "-" ? states[0] : states[1]
    rescue Puppet::ExecutionFailure
      debug "Could not get status on service #{self.name} #{$!}"
      return :absent
    end

    case state
    when "online"
      #self.warning "matched running #{line.inspect}"
      return :running
    when "offline", "disabled", "uninitialized"
      #self.warning "matched stopped #{line.inspect}"
      return :stopped
    when "maintenance"
      return :maintenance
    when "degraded"
      return :degraded
    when "legacy_run"
      raise Puppet::Error,
        "Cannot manage legacy services through SMF"
    else
      raise Puppet::Error,
        "Unmanageable state '#{state}' on service #{self.name}"
    end

  end

  def stopcmd
    [command(:adm), :disable, "-s", @resource[:name]]
  end
end

