Class: HybridPlatformsConductor::Provisioner

Inherits:
Plugin
  • Object
show all
Includes:
LoggerHelpers
Defined in:
lib/hybrid_platforms_conductor/provisioner.rb

Overview

Base class for any provisioner

Constant Summary

Constants included from LoggerHelpers

LoggerHelpers::LEVELS_MODIFIERS, LoggerHelpers::LEVELS_TO_STDERR

Instance Method Summary collapse

Methods included from LoggerHelpers

#err, #init_loggers, #log_component=, #log_debug?, #log_level=, #out, #section, #set_loggers_format, #stderr_device, #stderr_device=, #stderr_displayed?, #stdout_device, #stdout_device=, #stdout_displayed?, #stdouts_to_s, #with_progress_bar

Methods inherited from Plugin

extend_config_dsl_with, valid?

Constructor Details

#initialize(node, environment: 'production', logger: Logger.new(STDOUT), logger_stderr: Logger.new(STDERR), config: Config.new, cmd_runner: CmdRunner.new, nodes_handler: NodesHandler.new, actions_executor: ActionsExecutor.new) ⇒ Provisioner

Constructor

Parameters
  • node (String): Node for which we provision a running instance

  • environment (String): Environment for which this running instance is provisioned [default: ‘production’]

  • logger (Logger): Logger to be used [default: Logger.new(STDOUT)]

  • logger_stderr (Logger): Logger to be used for stderr [default: Logger.new(STDERR)]

  • config (Config): Config to be used. [default: Config.new]

  • cmd_runner (CmdRunner): Command executor to be used. [default: CmdRunner.new]

  • nodes_handler (NodesHandler): Nodes handler to be used. [default: NodesHandler.new]

  • actions_executor (ActionsExecutor): Actions Executor to be used. [default: ActionsExecutor.new]



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/hybrid_platforms_conductor/provisioner.rb', line 22

def initialize(
  node,
  environment: 'production',
  logger: Logger.new(STDOUT),
  logger_stderr: Logger.new(STDERR),
  config: Config.new,
  cmd_runner: CmdRunner.new,
  nodes_handler: NodesHandler.new,
  actions_executor: ActionsExecutor.new
)
  super(logger: logger, logger_stderr: logger_stderr, config: config)
  @node = node
  @environment = environment
  @cmd_runner = cmd_runner
  @nodes_handler = nodes_handler
  @actions_executor = actions_executor
end

Instance Method Details

#default_timeoutObject

Return the default timeout to apply when waiting for an instance to be started/stopped…

Result
  • Integer: The timeout in seconds



44
45
46
# File 'lib/hybrid_platforms_conductor/provisioner.rb', line 44

def default_timeout
  60
end

#ipObject

Return the IP address of an instance. Prerequisite: create has been called before.

API
  • This method is optional

Result
  • String or nil: The instance IP address, or nil if this information is not relevant



184
185
186
# File 'lib/hybrid_platforms_conductor/provisioner.rb', line 184

def ip
  nil
end

#wait_for_port(port, timeout = default_timeout) ⇒ Object

Wait for a given ip/port to be listening before continuing.

Parameters
  • port (Integer): Port to wait for

  • timeout (Integer): Timeout before failing, in seconds [default = default_timeout]

Result
  • Boolean: Is port listening?



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/hybrid_platforms_conductor/provisioner.rb', line 146

def wait_for_port(port, timeout = default_timeout)
  instance_ip = ip
  log_debug "[ #{@node}/#{@environment} ] - Wait for #{instance_ip}:#{port} to be opened (timeout #{timeout})..."
  port_listening = false
  remaining_timeout = timeout
  until port_listening
    start_time = Time.now
    port_listening =
      begin
        Socket.tcp(instance_ip, port, connect_timeout: remaining_timeout) { true }
      rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::EADDRNOTAVAIL, Errno::ETIMEDOUT
        log_warn "[ #{@node}/#{@environment} ] - Can't connect to #{instance_ip}:#{port}: #{$!}"
        false
      end
    sleep 1 unless port_listening
    remaining_timeout -= Time.now - start_time
    break if remaining_timeout <= 0
  end
  log_debug "[ #{@node}/#{@environment} ] - #{instance_ip}:#{port} is#{port_listening ? '' : ' not'} opened."
  port_listening
end

#wait_for_port!(port, timeout = default_timeout) ⇒ Object

Wait for a given ip/port to be listening before continuing. Fail if it does not listen.

Parameters
  • port (Integer): Port to wait for

  • timeout (Integer): Timeout before failing, in seconds [default = default_timeout]



174
175
176
# File 'lib/hybrid_platforms_conductor/provisioner.rb', line 174

def wait_for_port!(port, timeout = default_timeout)
  raise "[ #{@node}/#{@environment} ] - Instance fails to have port #{port} opened with timeout #{timeout}." unless wait_for_port(port, timeout)
end

#wait_for_state(states, timeout = default_timeout) ⇒ Object

Wait for an instance to be in a given state.

Parameters
  • states (Symbol or Array<Symbol>): States (or single state) the instance should be in

  • timeout (Integer): Timeout before failing, in seconds [default = default_timeout]

Result
  • Boolean: Is the instance in one of the expected states?



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/hybrid_platforms_conductor/provisioner.rb', line 113

def wait_for_state(states, timeout = default_timeout)
  states = [states] unless states.is_a?(Array)
  log_debug "[ #{@node}/#{@environment} ] - Wait for instance to be in state #{states.join(', ')} (timeout #{timeout})..."
  current_state = nil
  remaining_timeout = timeout
  until states.include?(current_state)
    start_time = Time.now
    current_state = state
    sleep 1 unless states.include?(current_state)
    remaining_timeout -= Time.now - start_time
    break if remaining_timeout <= 0
  end
  log_debug "[ #{@node}/#{@environment} ] - Instance is in state #{current_state}"
  states.include?(current_state)
end

#wait_for_state!(states, timeout = default_timeout) ⇒ Object

Wait for an instance to be in a given state, and fail if it can’t.

Parameters
  • states (Symbol or Array<Symbol>): States (or single state) the instance should be in

  • timeout (Integer): Timeout before failing, in seconds [default = default_timeout]



134
135
136
137
# File 'lib/hybrid_platforms_conductor/provisioner.rb', line 134

def wait_for_state!(states, timeout = default_timeout)
  states = [states] unless states.is_a?(Array)
  raise "[ #{@node}/#{@environment} ] - Instance fails to be in a state among (#{states.join(', ')}) with timeout #{timeout}. Currently in state #{state}" unless wait_for_state(states, timeout)
end

#with_running_instance(stop_on_exit: false, destroy_on_exit: false, port: nil) ⇒ Object

Provision a running instance for the needed node and environment. If the instance is already created, re-uses it. If the instance is already running, re-uses it. Enriches the nodes handler information with the instance metadata as well. Calls client code only when the instance is up and running, and fail otherwise.

Parameters
  • stop_on_exit (Boolean): Do we stop the instance when exiting? [default: false]

  • destroy_on_exit (Boolean): Do we destroy the instance when exiting? Ignored if stop_on_exit is false [default: false]

  • port (Integer or nil): Port to wait to be opened, or nil if none [default: nil]

  • Proc: Client code called with the instance up and running



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/hybrid_platforms_conductor/provisioner.rb', line 59

def with_running_instance(stop_on_exit: false, destroy_on_exit: false, port: nil)
  log_debug "[ #{@node}/#{@environment} ] - Create instance..."
  create
  begin
    wait_for_state!(%i[running created exited])
    if %i[created exited].include?(state)
      log_debug "[ #{@node}/#{@environment} ] - Start instance..."
      start
    end
    begin
      wait_for_state!(:running)
      instance_ip = ip
      if instance_ip.nil?
        log_debug "[ #{@node}/#{@environment} ] - No host_ip linked to the instance."
      elsif instance_ip != @nodes_handler.get_host_ip_of(@node)
        log_debug "[ #{@node}/#{@environment} ] - Set host_ip to #{instance_ip}."
        # The instance is running on an IP that is not the one registered by default in the metadata.
        # Make sure we update it.
        @nodes_handler. @node, :host_ip, instance_ip
        @nodes_handler. @node, :host_keys
        # Make sure the SSH transformations don't apply to this node
        @config.ssh_connection_transforms.replace(@config.ssh_connection_transforms.map do |ssh_transform_info|
          {
            nodes_selectors_stack: ssh_transform_info[:nodes_selectors_stack].map do |nodes_selector|
              @nodes_handler.select_nodes(nodes_selector).select { |selected_node| selected_node != @node }
            end,
            transform: ssh_transform_info[:transform]
          }
        end)
      end
      wait_for_port!(port) if port
      yield
    ensure
      if stop_on_exit
        log_debug "[ #{@node}/#{@environment} ] - Stop instance..."
        stop
        wait_for_state!(:exited)
      end
    end
  ensure
    if stop_on_exit && destroy_on_exit
      log_debug "[ #{@node}/#{@environment} ] - Destroy instance..."
      destroy
    end
  end
end