class Kitchen::Instance

An instance of a suite running on a platform. A created instance may be a local virtual machine, cloud instance, container, or even a bare metal server, which is determined by the platform's driver.

@author Fletcher Nichol <fnichol@nichol.ca>

Attributes

mutexes[RW]

@return [Hash] a hash of mutexes, arranged by Plugin class names @api private

driver[RW]

@return [Driver::Base] driver object which will manage this instance's

lifecycle actions
lifecycle_hooks[RW]

@return [LifecycleHooks] lifecycle hooks manager object

logger[R]

@return [Logger] the logger for this instance

name[R]

@return [String] name of this instance

platform[R]

@return [Platform] the target platform configuration

provisioner[RW]

@return [Provisioner::Base] provisioner object which will the setup

and invocation instructions for configuration management and other
automation tools
state_file[R]

@return [StateFile] a state file object that can be read from or written

to

@api private

suite[R]

@return [Suite] the test suite configuration

transport[RW]

@return [Transport::Base] transport object which will communicate with

an instance.
verifier[RW]

@return [Verifier] verifier object for instance to manage the verifier

installation on this instance

Public Class Methods

name_for(suite, platform) click to toggle source

Generates a name for an instance given a suite and platform.

@param suite [Suite,#name] a Suite @param platform [Platform,#name] a Platform @return [String] a normalized, consistent name for an instance

# File lib/kitchen/instance.rb, line 40
def name_for(suite, platform)
  "#{suite.name}-#{platform.name}".gsub(%r{[_,/]}, "-").delete(".")
end
new(options = {}) click to toggle source

Creates a new instance, given a suite and a platform.

@param [Hash] options configuration for a new suite @option options [Suite] :suite the suite (Required) @option options [Platform] :platform the platform (Required) @option options [Driver::Base] :driver the driver (Required) @option options [Provisioner::Base] :provisioner the provisioner @option options [Transport::Base] :transport the transport

(**Required**)

@option options [Verifier] :verifier the verifier logger (Required) @option options [Logger] :logger the instance logger

(default: Kitchen.logger)

@option options [StateFile] :state_file the state file object to use

when tracking instance  state (**Required**)

@raise [ClientError] if one or more required options are omitted

# File lib/kitchen/instance.rb, line 92
def initialize(options = {})
  validate_options(options)

  @suite           = options.fetch(:suite)
  @platform        = options.fetch(:platform)
  @name            = self.class.name_for(@suite, @platform)
  @driver          = options.fetch(:driver)
  @lifecycle_hooks = options.fetch(:lifecycle_hooks)
  @provisioner     = options.fetch(:provisioner)
  @transport       = options.fetch(:transport)
  @verifier        = options.fetch(:verifier)
  @logger          = options.fetch(:logger) { Kitchen.logger }
  @state_file      = options.fetch(:state_file)

  setup_driver
  setup_provisioner
  setup_transport
  setup_verifier
  setup_lifecycle_hooks
end

Public Instance Methods

cleanup!() click to toggle source

Clean up any per-instance resources before exiting.

@return [void]

# File lib/kitchen/instance.rb, line 297
def cleanup!
  @transport.cleanup! if @transport
end
converge() click to toggle source

Converges this running instance.

@see Provisioner::Base#call @return [self] this instance, used to chain actions

@todo rescue Driver::ActionFailed and return some kind of null object

to gracfully stop action chaining
# File lib/kitchen/instance.rb, line 138
def converge
  transition_to(:converge)
end
create() click to toggle source

Creates this instance.

@see Driver::Base#create @return [self] this instance, used to chain actions

@todo rescue Driver::ActionFailed and return some kind of null object

to gracfully stop action chaining
# File lib/kitchen/instance.rb, line 127
def create
  transition_to(:create)
end
destroy() click to toggle source

Destroys this instance.

@see Driver::Base#destroy @return [self] this instance, used to chain actions

@todo rescue Driver::ActionFailed and return some kind of null object

to gracfully stop action chaining
# File lib/kitchen/instance.rb, line 171
def destroy
  transition_to(:destroy)
end
diagnose() click to toggle source

Returns a Hash of configuration and other useful diagnostic information.

@return [Hash] a diagnostic hash

# File lib/kitchen/instance.rb, line 252
def diagnose
  result = {}
  %i{
    platform state_file driver provisioner transport verifier lifecycle_hooks
  }.each do |sym|
    obj = send(sym)
    result[sym] = obj.respond_to?(:diagnose) ? obj.diagnose : :unknown
  end
  result
end
diagnose_plugins() click to toggle source

Returns a Hash of configuration and other useful diagnostic information associated with plugins (such as loaded version, class name, etc.).

@return [Hash] a diagnostic hash

# File lib/kitchen/instance.rb, line 267
def diagnose_plugins
  result = {}
  %i{driver provisioner verifier transport}.each do |sym|
    obj = send(sym)
    result[sym] = if obj.respond_to?(:diagnose_plugin)
                    obj.diagnose_plugin
                  else
                    :unknown
                  end
  end
  result
end
doctor_action() click to toggle source

Check system and configuration for common errors.

# File lib/kitchen/instance.rb, line 242
def doctor_action
  banner "The doctor is in"
  [driver, provisioner, transport, verifier].any? do |obj|
    obj.doctor(state_file.read)
  end
end
last_action() click to toggle source

Returns the last successfully completed action state of the instance.

@return [String] a named action which was last successfully completed

# File lib/kitchen/instance.rb, line 283
def last_action
  state_file.read[:last_action]
end
last_error() click to toggle source

Returns the error encountered on the last action on the instance

@return [String] the message of the last error

# File lib/kitchen/instance.rb, line 290
def last_error
  state_file.read[:last_error]
end
login() click to toggle source

Logs in to this instance by invoking a system command, provided by the instance's transport. This could be an SSH command, telnet, or serial console session.

Note This method calls exec and will not return.

@see Kitchen::LoginCommand @see Transport::Base::Connection#login_command

# File lib/kitchen/instance.rb, line 207
def login
  state = state_file.read
  if state[:last_action].nil?
    raise UserError, "Instance #{to_str} has not yet been created"
  end

  lc = if legacy_ssh_base_driver?
         legacy_ssh_base_login(state)
       else
         transport.connection(state).login_command
       end

  debug(%{Login command: #{lc.command} #{lc.arguments.join(" ")} } \
    "(Options: #{lc.options})")
  Kernel.exec(*lc.exec_args)
end
package_action() click to toggle source

Perform package.

# File lib/kitchen/instance.rb, line 235
def package_action
  banner "Packaging remote instance"
  driver.package(state_file.read)
end
remote_exec(command) click to toggle source

Executes an arbitrary command on this instance.

@param command [String] a command string to execute

# File lib/kitchen/instance.rb, line 227
def remote_exec(command)
  transport.connection(state_file.read) do |conn|
    conn.execute(command)
  end
end
setup() click to toggle source

Sets up this converged instance for suite tests.

@see Driver::Base#setup @return [self] this instance, used to chain actions

@todo rescue Driver::ActionFailed and return some kind of null object

to gracfully stop action chaining
# File lib/kitchen/instance.rb, line 149
def setup
  transition_to(:setup)
end
test(destroy_mode = :passing) click to toggle source

Tests this instance by creating, converging and verifying. If this instance is running, it will be pre-emptively destroyed to ensure a clean slate. The instance will be left post-verify in a running state.

@param destroy_mode [Symbol] strategy used to cleanup after instance

has finished verifying (default: `:passing`)

@return [self] this instance, used to chain actions

@todo rescue Driver::ActionFailed and return some kind of null object

to gracfully stop action chaining
# File lib/kitchen/instance.rb, line 185
def test(destroy_mode = :passing)
  elapsed = Benchmark.measure do
    banner "Cleaning up any prior instances of #{to_str}"
    destroy
    banner "Testing #{to_str}"
    verify
    destroy if destroy_mode == :passing
  end
  info "Finished testing #{to_str} #{Util.duration(elapsed.real)}."
  self
ensure
  destroy if destroy_mode == :always
end
to_str() click to toggle source

Returns a displayable representation of the instance.

@return [String] an instance display string

# File lib/kitchen/instance.rb, line 116
def to_str
  "<#{name}>"
end
verify() click to toggle source

Verifies this set up instance by executing suite tests.

@see Driver::Base#verify @return [self] this instance, used to chain actions

@todo rescue Driver::ActionFailed and return some kind of null object

to gracfully stop action chaining
# File lib/kitchen/instance.rb, line 160
def verify
  transition_to(:verify)
end

Private Instance Methods

action(what, &block) click to toggle source

Times a call to an action block and handles any raised exceptions. This method ensures that the last successfully completed action is persisted to the state file. The last action state will either be the desired action that is passed in or the previous action that was persisted to the state file.

@param what [Symbol] the action to be performed @param block [Proc] a block to be called @return [Benchmark::Tms] timing information for the given action @raise [InstanceFailed] if a driver action fails to complete, signaled

by a driver raising an ActionFailed exception. Typical reasons for this
would be a driver create action failing, a chef convergence crashing
in normal course of development, failing acceptance tests in the
verify action, etc.

@raise [ActionFailed] if an unforseen or unplanned exception is raised.

This would usually indicate that a race condition was triggered, a
bug exists in a driver, provisioner, or core, a transient IO error
occured, etc.

@api private

# File lib/kitchen/instance.rb, line 521
def action(what, &block)
  state = state_file.read
  elapsed = Benchmark.measure do
    synchronize_or_call(what, state, &block)
  end
  state[:last_action] = what.to_s
  state[:last_error] = nil
  elapsed
rescue ActionFailed => e
  log_failure(what, e)
  state[:last_error] = e.class.name
  raise(InstanceFailure, failure_message(what) +
    "  Please see .kitchen/logs/#{name}.log for more details",
    e.backtrace)
rescue Exception => e # rubocop:disable Lint/RescueException
  log_failure(what, e)
  state[:last_error] = e.class.name
  raise ActionFailed,
    "Failed to complete ##{what} action: [#{e.message}]", e.backtrace
ensure
  state_file.write(state)
end
banner(*args) click to toggle source

Writes a high level message for logging and/or output.

In this case, all instance banner messages will be written to the common Kitchen logger so that the high level flow of a run can be followed in the kitchen.log file.

@api private

Calls superclass method
converge_action() click to toggle source

Perform the converge action.

@see Provisioner::Base#call @return [self] this instance, used to chain actions @api private

# File lib/kitchen/instance.rb, line 412
def converge_action
  banner "Converging #{to_str}..."
  elapsed = action(:converge) do |state|
    if legacy_ssh_base_driver?
      legacy_ssh_base_converge(state)
    else
      provisioner.check_license
      provisioner.call(state)
    end
  end
  info("Finished converging #{to_str} #{Util.duration(elapsed.real)}.")
  self
end
create_action() click to toggle source

Perform the create action.

@see Driver::Base#create @return [self] this instance, used to chain actions @api private

# File lib/kitchen/instance.rb, line 403
def create_action
  perform_action(:create, "Creating")
end
destroy_action() click to toggle source

Perform the destroy action.

@see Driver::Base#destroy @return [self] this instance, used to chain actions @api private

# File lib/kitchen/instance.rb, line 481
def destroy_action
  perform_action(:destroy, "Destroying") { state_file.destroy }
end
failure_message(what) click to toggle source

Returns a string explaining what action failed, at a high level. Used for displaying to end user.

@param what [String] an action @return [String] a failure message @api private

# File lib/kitchen/instance.rb, line 615
def failure_message(what)
  "#{what.capitalize} failed on instance #{to_str}."
end
legacy_ssh_base_converge(state) click to toggle source

Invokes `Driver#converge` on a legacy Driver, which inherits from `Kitchen::Driver::SSHBase`.

@param state [Hash] mutable instance state @deprecated When legacy Driver::SSHBase support is removed, the

`#converge` method will no longer be called on the Driver.

@api private

# File lib/kitchen/instance.rb, line 626
def legacy_ssh_base_converge(state)
  warn("Running legacy converge for '#{driver.name}' Driver")
  # TODO: Document upgrade path and provide link
  # warn("Driver authors: please read http://example.com for more details.")
  driver.converge(state)
end
legacy_ssh_base_driver?() click to toggle source

@return [TrueClass,FalseClass] whether or not the Driver inherits from

`Kitchen::Driver::SSHBase`

@deprecated When legacy Driver::SSHBase support is removed, the

`#converge` method will no longer be called on the Driver.

@api private

# File lib/kitchen/instance.rb, line 638
def legacy_ssh_base_driver?
  driver.class < Kitchen::Driver::SSHBase
end
legacy_ssh_base_login(state) click to toggle source

Invokes `Driver#login_command` on a legacy Driver, which inherits from `Kitchen::Driver::SSHBase`.

@param state [Hash] mutable instance state @deprecated When legacy Driver::SSHBase support is removed, the

`#login_command` method will no longer be called on the Driver.

@api private

# File lib/kitchen/instance.rb, line 649
def legacy_ssh_base_login(state)
  warn("Running legacy login for '#{driver.name}' Driver")
  # TODO: Document upgrade path and provide link
  # warn("Driver authors: please read http://example.com for more details.")
  driver.login_command(state)
end
legacy_ssh_base_setup(state) click to toggle source

Invokes `Driver#setup` on a legacy Driver, which inherits from `Kitchen::Driver::SSHBase`.

@param state [Hash] mutable instance state @deprecated When legacy Driver::SSHBase support is removed, the

`#setup` method will no longer be called on the Driver.

@api private

# File lib/kitchen/instance.rb, line 663
def legacy_ssh_base_setup(state)
  warn("Running legacy setup for '#{driver.name}' Driver")
  # TODO: Document upgrade path and provide link
  # warn("Driver authors: please read http://example.com for more details.")
  driver.setup(state)
end
legacy_ssh_base_verify(state) click to toggle source

Invokes `Driver#verify` on a legacy Driver, which inherits from `Kitchen::Driver::SSHBase`.

@param state [Hash] mutable instance state @deprecated When legacy Driver::SSHBase support is removed, the

`#verify` method will no longer be called on the Driver.

@api private

# File lib/kitchen/instance.rb, line 677
def legacy_ssh_base_verify(state)
  warn("Running legacy verify for '#{driver.name}' Driver")
  # TODO: Document upgrade path and provide link
  # warn("Driver authors: please read http://example.com for more details.")
  driver.verify(state)
end
log_failure(what, e) click to toggle source

Logs a failure (message and backtrace) to the instance's file logger to help with debugging and diagnosing issues without overwhelming the console output in the default case (i.e. running kitchen with :info level debugging).

@param what [String] an action @param e [Exception] an exception @api private

# File lib/kitchen/instance.rb, line 602
def log_failure(what, e)
  return if logger.logdev.nil?

  logger.logdev.error(failure_message(what))
  Error.formatted_trace(e).each { |line| logger.logdev.error(line) }
end
perform_action(verb, output_verb) { || ... } click to toggle source

Perform an arbitrary action and provide useful logging.

@param verb [Symbol] the action to be performed @param output_verb [String] a verb representing the action, suitable for

use in output logging

@yield perform optional work just after action has complted @return [self] this instance, used to chain actions @api private

# File lib/kitchen/instance.rb, line 493
def perform_action(verb, output_verb)
  banner "#{output_verb} #{to_str}..."
  elapsed = action(verb) { |state| driver.public_send(verb, state) }
  info("Finished #{output_verb.downcase} #{to_str}" \
    " #{Util.duration(elapsed.real)}.")
  yield if block_given?
  self
end
plugin_class_for_action(what) click to toggle source

Maps the given action to the plugin class associated with the action

@param what action @return [Class] Kitchen::Plugin::Base @api private

# File lib/kitchen/instance.rb, line 572
def plugin_class_for_action(what)
  {
    create: driver,
    setup: transport,
    converge: provisioner,
    verify: verifier,
    destroy: driver,
  }[what].class
end
setup_action() click to toggle source

Perform the setup action.

@see Driver::Base#setup @return [self] this instance, used to chain actions @api private

# File lib/kitchen/instance.rb, line 431
def setup_action
  banner "Setting up #{to_str}..."
  elapsed = action(:setup) do |state|
    legacy_ssh_base_setup(state) if legacy_ssh_base_driver?
  end
  info("Finished setting up #{to_str} #{Util.duration(elapsed.real)}.")
  self
end
setup_driver() click to toggle source

Perform any final configuration or preparation needed for the driver object carry out its duties.

@api private

# File lib/kitchen/instance.rb, line 343
def setup_driver
  @driver.finalize_config!(self)
  setup_plugin_mutexes(driver.class)
end
setup_lifecycle_hooks() click to toggle source

Perform any final configuration or preparation needed for the lifecycle hooks object carry out its duties.

@api private

# File lib/kitchen/instance.rb, line 352
def setup_lifecycle_hooks
  lifecycle_hooks.finalize_config!(self)
end
setup_plugin_mutexes(plugin_class) click to toggle source

If a plugin has declared via .no_parallel_for that it is not thread-safe for certain actions, create a mutex to track it.

@param plugin_class Kitchen::Plugin::Base @api private

# File lib/kitchen/instance.rb, line 330
def setup_plugin_mutexes(plugin_class)
  if plugin_class.serial_actions
    Kitchen.mutex.synchronize do
      self.class.mutexes ||= {}
      self.class.mutexes[plugin_class] = Mutex.new
    end
  end
end
setup_provisioner() click to toggle source

Perform any final configuration or preparation needed for the provisioner object carry out its duties.

@api private

# File lib/kitchen/instance.rb, line 360
def setup_provisioner
  @provisioner.finalize_config!(self)
  setup_plugin_mutexes(provisioner.class)
end
setup_transport() click to toggle source

Perform any final configuration or preparation needed for the transport object carry out its duties.

@api private

# File lib/kitchen/instance.rb, line 369
def setup_transport
  transport.finalize_config!(self)
  setup_plugin_mutexes(transport.class)
end
setup_verifier() click to toggle source

Perform any final configuration or preparation needed for the verifier object carry out its duties.

@api private

# File lib/kitchen/instance.rb, line 378
def setup_verifier
  verifier.finalize_config!(self)
  setup_plugin_mutexes(verifier.class)
end
synchronize_or_call(what, state) { |state| ... } click to toggle source

Runs a given action block through a common driver mutex if required or runs it directly otherwise. If a driver class' `.serial_actions` array includes the desired action, then the action must be run with a muxtex lock. Otherwise, it is assumed that the action can happen concurrently, or fully in parallel.

@param what [Symbol] the action to be performed @param state [Hash] a mutable state hash for this instance @param block [Proc] a block to be called @api private

# File lib/kitchen/instance.rb, line 554
def synchronize_or_call(what, state)
  plugin_class = plugin_class_for_action(what)
  if Array(plugin_class.serial_actions).include?(what)
    debug("#{to_str} is synchronizing on #{plugin_class}##{what}")
    self.class.mutexes[plugin_class].synchronize do
      debug("#{to_str} is messaging #{plugin_class}##{what}")
      yield(state)
    end
  else
    yield(state)
  end
end
transition_to(desired) click to toggle source

Perform all actions in order from last state to desired state.

@param desired [Symbol] a symbol representing the desired action state @return [self] this instance, used to chain actions @api private

# File lib/kitchen/instance.rb, line 388
def transition_to(desired)
  result = nil
  FSM.actions(last_action, desired).each do |transition|
    @lifecycle_hooks.run_with_hooks(transition, state_file) do
      result = send("#{transition}_action")
    end
  end
  result
end
use_legacy_ssh_verifier?(verifier) click to toggle source
# File lib/kitchen/instance.rb, line 450
def use_legacy_ssh_verifier?(verifier)
  verifier_busser?(verifier) || verifier_dummy?(verifier)
end
validate_options(options) click to toggle source

Validate the initial internal state of this object and raising an exception if any preconditions are not met.

@param options options hash passed into the constructor @raise [ClientError] if any validations fail @api private

# File lib/kitchen/instance.rb, line 314
def validate_options(options)
  %i{
    suite platform driver provisioner
    transport verifier state_file
  }.each do |k|
    next if options.key?(k)

    raise ClientError, "Instance#new requires option :#{k}"
  end
end
verifier_busser?(verifier) click to toggle source

returns true, if the verifier is busser

# File lib/kitchen/instance.rb, line 441
def verifier_busser?(verifier)
  !defined?(Kitchen::Verifier::Busser).nil? && verifier.is_a?(Kitchen::Verifier::Busser)
end
verifier_dummy?(verifier) click to toggle source

returns true, if the verifier is dummy

# File lib/kitchen/instance.rb, line 446
def verifier_dummy?(verifier)
  !defined?(Kitchen::Verifier::Dummy).nil? && verifier.is_a?(Kitchen::Verifier::Dummy)
end
verify_action() click to toggle source

Perform the verify action.

@see Driver::Base#verify @return [self] this instance, used to chain actions @api private

# File lib/kitchen/instance.rb, line 459
def verify_action
  banner "Verifying #{to_str}..."
  elapsed = action(:verify) do |state|
    # use special handling for legacy driver
    if legacy_ssh_base_driver? && use_legacy_ssh_verifier?(verifier)
      legacy_ssh_base_verify(state)
    elsif legacy_ssh_base_driver?
      # read ssh options from legacy driver
      verifier.call(driver.legacy_state(state))
    else
      verifier.call(state)
    end
  end
  info("Finished verifying #{to_str} #{Util.duration(elapsed.real)}.")
  self
end