Class: Capissh::ConnectionManager

Inherits:
Object
  • Object
show all
Defined in:
lib/capissh/connection_manager.rb

Defined Under Namespace

Classes: DefaultConnectionFactory, GatewayConnectionFactory

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ ConnectionManager

Instantiates a new ConnectionManager object. options must be a hash containing any of the following keys:

  • gateway: (optional), ssh gateway

  • logger: (optional), a Capissh::Logger instance

  • ssh_options: (optional), options for Net::SSH

  • verbose: (optional), verbosity level for ssh

  • user: (optional), user for all servers

  • port: (optional), port for all servers

  • password: (optional), password for all servers

  • many more that I haven’t written yet



85
86
87
88
89
90
91
# File 'lib/capissh/connection_manager.rb', line 85

def initialize(options={})
  @options = options.dup
  @gateway = @options.delete(:gateway)
  @logger  = @options[:logger] || Capissh::Logger.default
  Thread.current[:sessions] = {}
  Thread.current[:failed_sessions] = []
end

Instance Attribute Details

#loggerObject (readonly)

Returns the value of attribute logger.



93
94
95
# File 'lib/capissh/connection_manager.rb', line 93

def logger
  @logger
end

Instance Method Details

#connect!(servers, options = {}) ⇒ Object

Used to force connections to be made to the current task’s servers. Connections are normally made lazily in Capissh–you can use this to force them open before performing some operation that might be time-sensitive.



118
119
120
# File 'lib/capissh/connection_manager.rb', line 118

def connect!(servers, options={})
  execute_on_servers(servers, options) { }
end

#connection_factoryObject

Returns the object responsible for establishing new SSH connections. The factory will respond to #connect_to, which can be used to establish connections to servers defined via ServerDefinition objects.



125
126
127
128
129
130
131
132
133
134
# File 'lib/capissh/connection_manager.rb', line 125

def connection_factory
  @connection_factory ||= begin
    if @gateway
      logger.debug "establishing connection to gateway `#{@gateway.inspect}'"
      GatewayConnectionFactory.new(@gateway, @options)
    else
      DefaultConnectionFactory.new(@options)
    end
  end
end

#establish_connections_to(servers) ⇒ Object

Ensures that there are active sessions for each server in the list.



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/capissh/connection_manager.rb', line 137

def establish_connections_to(servers)
  # force the connection factory to be instantiated synchronously,
  # otherwise we wind up with multiple gateway instances, because
  # each connection is done in parallel.
  connection_factory

  failed_servers = []
  servers_array = Array(servers)

  threads = servers_array.map { |server| establish_connection_to(server, failed_servers) }
  threads.each { |t| t.join }

  if failed_servers.any?
    messages = failed_servers.map { |h| "#{h[:server]} (#{h[:error].class}: #{h[:error].message})" }
    error = ConnectionError.new("connection failed for: #{messages.join(', ')}")
    error.hosts = failed_servers.map { |h| h[:server] }.each {|server| failed!(server) }
    raise error
  end

  servers_array.map {|server| sessions[server] }
end

#execute_on_servers(servers, options = {}, &block) ⇒ Object

Determines the set of servers and establishes connections to them, and then yields that list of servers.

All options will be used to find servers. (see find_servers)

The additional options below will also be used as follows:

  • on_no_matching_servers: (optional), set to :continue to return instead of raising when no servers are found

  • max_hosts: (optional), integer to batch commands in chunks of hosts

  • continue_on_error: (optionsal), continue on connection errors

Raises:

  • (ArgumentError)


182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/capissh/connection_manager.rb', line 182

def execute_on_servers(servers, options={}, &block)
  raise ArgumentError, "expected a block" unless block_given?

  connect_to_servers = ServerDefinition.wrap_list(*servers)

  if options[:continue_on_error]
    connect_to_servers.delete_if { |s| has_failed?(s) }
  end

  if connect_to_servers.empty?
    raise Capissh::NoMatchingServersError, "no servers found to match #{options.inspect}" unless options[:continue_on_no_matching_servers]
    return
  end

  logger.trace "servers: #{connect_to_servers.map { |s| s.host }.inspect}"

  max_hosts = (options[:max_hosts] || connect_to_servers.size).to_i
  is_subset = max_hosts < connect_to_servers.size

  if max_hosts <= 0
    raise Capissh::NoMatchingServersError, "max_hosts is invalid for #{options.inspect}" unless options[:continue_on_no_matching_servers]
    return
  end

  # establish connections to those servers in groups of max_hosts, as necessary
  connect_to_servers.each_slice(max_hosts) do |servers_slice|
    begin
      establish_connections_to(servers_slice)
    rescue ConnectionError => error
      raise error unless options[:continue_on_error]
      error.hosts.each do |h|
        servers_slice.delete(h)
        failed!(h)
      end
    end

    begin
      yield servers_slice.map { |server| sessions[server] }
    rescue RemoteError => error
      raise error unless options[:continue_on_error]
      error.hosts.each { |h| failed!(h) }
    end

    # if dealing with a subset (e.g., :max_hosts is less than the
    # number of servers available) teardown the subset of connections
    # that were just made, so that we can make room for the next subset.
    teardown_connections_to(servers_slice) if is_subset
  end
end

#failed!(server) ⇒ Object

Indicate that the given server could not be connected to.



104
105
106
# File 'lib/capissh/connection_manager.rb', line 104

def failed!(server)
  Thread.current[:failed_sessions] << server
end

#has_failed?(server) ⇒ Boolean

Query whether previous connection attempts to the given server have failed.

Returns:

  • (Boolean)


110
111
112
# File 'lib/capissh/connection_manager.rb', line 110

def has_failed?(server)
  Thread.current[:failed_sessions].include?(server)
end

#sessionsObject

A hash of the SSH sessions that are currently open and available. Because sessions are constructed lazily, this will only contain connections to those servers that have been the targets of one or more executed tasks. Stored on a per-thread basis to improve thread-safety.



99
100
101
# File 'lib/capissh/connection_manager.rb', line 99

def sessions
  Thread.current[:sessions] ||= {}
end

#teardown_connections_to(servers) ⇒ Object

Destroys sessions for each server in the list.



160
161
162
163
164
165
166
167
168
# File 'lib/capissh/connection_manager.rb', line 160

def teardown_connections_to(servers)
  servers.each do |server|
    begin
      sessions.delete(server).close
    rescue IOError
      # the TCP connection is already dead
    end
  end
end