Class: HybridPlatformsConductor::HpcPlugins::Connector::Ssh
- Defined in:
- lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb
Overview
Connect to node using SSH
Defined Under Namespace
Modules: PlatformsDslSsh Classes: NotConnectableError
Constant Summary collapse
- TMP_SSH_SUB_DIR =
String: Sub-path of the system’s temporary directory where temporary SSH config are generated
'hpc_ssh'- MAX_CMD_ARG_LENGTH =
Integer: Max size for an argument that can be executed without getting through an intermediary file
131_055
Constants included from LoggerHelpers
LoggerHelpers::LEVELS_MODIFIERS, LoggerHelpers::LEVELS_TO_STDERR
Instance Attribute Summary collapse
-
#auth_password ⇒ Object
Do we expect some connections to require password authentication? [default: false] Boolean.
-
#passwords ⇒ Object
Passwords to be used, per node [default: {}] Hash<String, String>.
-
#ssh_gateway_user ⇒ Object
Name of the gateway user to be used.
-
#ssh_gateways_conf ⇒ Object
Name of the gateways configuration, or nil if no gateway.
-
#ssh_strict_host_key_checking ⇒ Object
Do we use strict host key checking in our SSH commands? [default: true] Boolean.
-
#ssh_use_control_master ⇒ Object
Do we use the control master? [default: true] Boolean.
-
#ssh_user ⇒ Object
User name used in SSH connections.
Instance Method Summary collapse
-
#connectable_nodes_from(nodes) ⇒ Object
Select nodes where this connector can connect.
-
#init ⇒ Object
Initialize the connector.
-
#options_parse(options_parser) ⇒ Object
Complete an option parser with options meant to control this connector [API] - This method is optional [API] - @cmd_runner can be used [API] - @nodes_handler can be used.
-
#remote_bash(bash_cmds) ⇒ Object
Run bash commands on a given node.
-
#remote_copy(from, to, sudo: false, owner: nil, group: nil) ⇒ Object
Copy a file to the remote node in a directory [API] - This method is mandatory [API] - If defined, then with_connection_to has been called before this method.
-
#remote_interactive ⇒ Object
Execute an interactive shell on the remote node [API] - This method is mandatory [API] - If defined, then with_connection_to has been called before this method.
-
#ssh_config(ssh_exec: 'ssh', known_hosts_file: nil, nodes: @nodes_handler.known_nodes) ⇒ Object
Get an SSH configuration content giving access to nodes of the platforms with the current configuration.
-
#ssh_exec ⇒ Object
Get the ssh executable to be used when connecting to the current node.
-
#ssh_url ⇒ Object
Get the ssh URL to be used to connect to the current node.
-
#validate_params ⇒ Object
Validate that parsed parameters are valid [API] - This method is optional [API] - @cmd_runner can be used [API] - @nodes_handler can be used.
-
#with_connection_to(nodes, no_exception: false) ⇒ Object
Prepare connections to a given set of nodes.
Methods inherited from Connector
Methods inherited from Plugin
extend_config_dsl_with, #initialize, valid?
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
Constructor Details
This class inherits a constructor from HybridPlatformsConductor::Connector
Instance Attribute Details
#auth_password ⇒ Object
Do we expect some connections to require password authentication? [default: false] Boolean
120 121 122 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 120 def auth_password @auth_password end |
#passwords ⇒ Object
Passwords to be used, per node [default: {}] Hash<String, String>
116 117 118 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 116 def passwords @passwords end |
#ssh_gateway_user ⇒ Object
Name of the gateway user to be used. [default: ENV or ubradm]
String
96 97 98 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 96 def ssh_gateway_user @ssh_gateway_user end |
#ssh_gateways_conf ⇒ Object
Name of the gateways configuration, or nil if no gateway. [default: ENV or nil]
Symbol or nil
100 101 102 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 100 def ssh_gateways_conf @ssh_gateways_conf end |
#ssh_strict_host_key_checking ⇒ Object
Do we use strict host key checking in our SSH commands? [default: true] Boolean
108 109 110 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 108 def ssh_strict_host_key_checking @ssh_strict_host_key_checking end |
#ssh_use_control_master ⇒ Object
Do we use the control master? [default: true] Boolean
112 113 114 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 112 def ssh_use_control_master @ssh_use_control_master end |
Instance Method Details
#connectable_nodes_from(nodes) ⇒ Object
Select nodes where this connector can connect.
- API
-
This method is mandatory
-
- API
-
@cmd_runner can be used
-
- API
-
@nodes_handler can be used
-
- Parameters
-
nodes (Array<String>): List of candidate nodes
- Result
-
Array<String>: List of nodes we can connect to from the candidates
197 198 199 200 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 197 def connectable_nodes_from(nodes) @nodes_handler. nodes, :host_ip nodes.select { |node| @nodes_handler.get_host_ip_of(node) } end |
#init ⇒ Object
Initialize the connector. This can be used to initialize global variables that are used for this connector
- API
-
This method is optional
-
- API
-
@cmd_runner can be used
-
- API
-
@nodes_handler can be used
-
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 130 def init # Default values @ssh_user = ENV['hpc_ssh_user'] @ssh_user = ENV['USER'] if @ssh_user.nil? || @ssh_user.empty? @ssh_use_control_master = true @ssh_strict_host_key_checking = true @passwords = {} @auth_password = false @ssh_gateways_conf = ENV['hpc_ssh_gateways_conf'].nil? ? nil : ENV['hpc_ssh_gateways_conf'].to_sym @ssh_gateway_user = ENV['hpc_ssh_gateway_user'].nil? ? 'ubradm' : ENV['hpc_ssh_gateway_user'] # The map of existing ssh directories that have been created, per node that can access them # Array< String, Array<String> > @ssh_dirs = {} # Mutex protecting the map to make sure it's thread-safe @ssh_dirs_mutex = Mutex.new # Temporary directory used by all ActionsExecutors, even from different processes @tmp_dir = "#{Dir.tmpdir}/#{TMP_SSH_SUB_DIR}" FileUtils.mkdir_p @tmp_dir end |
#options_parse(options_parser) ⇒ Object
Complete an option parser with options meant to control this connector
- API
-
This method is optional
-
- API
-
@cmd_runner can be used
-
- API
-
@nodes_handler can be used
-
- Parameters
-
options_parser (OptionParser): The option parser to complete
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 157 def () .on('-g', '--ssh-gateway-user USER', "Name of the gateway user to be used by the gateways. Can also be set from environment variable hpc_ssh_gateway_user. Defaults to #{@ssh_gateway_user}.") do |user| @ssh_gateway_user = user end .on('-j', '--ssh-no-control-master', 'If used, don\'t create SSH control masters for connections.') do @ssh_use_control_master = false end .on('-q', '--ssh-no-host-key-checking', 'If used, don\'t check for SSH host keys.') do @ssh_strict_host_key_checking = false end .on('-u', '--ssh-user USER', 'Name of user to be used in SSH connections (defaults to hpc_ssh_user or USER environment variables)') do |user| @ssh_user = user end .on('-w', '--password', 'If used, then expect SSH connections to ask for a password.') do @auth_password = true end .on('-y', '--ssh-gateways-conf GATEWAYS_CONF', "Name of the gateways configuration to be used. Can also be set from environment variable hpc_ssh_gateways_conf.") do |gateway| @ssh_gateways_conf = gateway.to_sym end end |
#remote_bash(bash_cmds) ⇒ Object
Run bash commands on a given node.
- API
-
This method is mandatory
-
- API
-
If defined, then with_connection_to has been called before this method.
-
- API
-
@cmd_runner can be used
-
- API
-
@nodes_handler can be used
-
- API
-
@node can be used to access the node on which we execute the remote bash
-
- API
-
@timeout can be used to know when the action should fail
-
- API
-
@stdout_io can be used to send stdout output
-
- API
-
@stderr_io can be used to send stderr output
-
- Parameters
-
bash_cmds (String): Bash commands to execute
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 235 def remote_bash(bash_cmds) ssh_cmd = if @nodes_handler.get_ssh_session_exec_of(@node) == 'false' # When ExecSession is disabled we need to use stdin directly "{ cat | #{ssh_exec} #{ssh_url} -T; } <<'EOF'\n#{bash_cmds}\nEOF" else "#{ssh_exec} #{ssh_url} /bin/bash <<'EOF'\n#{bash_cmds}\nEOF" end # Due to a limitation of Process.spawn, each individual argument is limited to 128KB of size. # Therefore we need to make sure that if bash_cmds exceeds MAX_CMD_ARG_LENGTH bytes (considering EOF chars) then we use an intermediary shell script to store the commands. if bash_cmds.size > MAX_CMD_ARG_LENGTH # Write the commands in a file temp_file = "#{Dir.tmpdir}/hpc_temp_cmds_#{Digest::MD5.hexdigest(bash_cmds)}.sh" File.open(temp_file, 'w+') do |file| file.write ssh_cmd file.chmod 0700 end begin run_cmd(temp_file) ensure File.unlink(temp_file) end else run_cmd ssh_cmd end end |
#remote_copy(from, to, sudo: false, owner: nil, group: nil) ⇒ Object
Copy a file to the remote node in a directory
- API
-
This method is mandatory
-
- API
-
If defined, then with_connection_to has been called before this method.
-
- API
-
@cmd_runner can be used
-
- API
-
@nodes_handler can be used
-
- API
-
@node can be used to access the node on which we execute the remote bash
-
- API
-
@timeout can be used to know when the action should fail
-
- API
-
@stdout_io can be used to send stdout output
-
- API
-
@stderr_io can be used to send stderr output
-
- Parameters
-
from (String): Local file to copy
-
to (String): Remote directory to copy to
-
sudo (Boolean): Do we use sudo to copy? [default: false]
-
owner (String or nil): Owner to be used when copying the files, or nil for current one [default: nil]
-
group (String or nil): Group to be used when copying the files, or nil for current one [default: nil]
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 298 def remote_copy(from, to, sudo: false, owner: nil, group: nil) if @nodes_handler.get_ssh_session_exec_of(@node) == 'false' # We don't have ExecSession, so don't use ssh, but scp instead. run_cmd "scp -S #{ssh_exec} #{from} #{ssh_url}:#{to}" else run_cmd " cd \#{File.dirname(from)} && \\\n tar \\\n --create \\\n --gzip \\\n --file - \\\n \#{owner.nil? ? '' : \"--owner \#{owner}\"} \\\n \#{group.nil? ? '' : \"--group \#{group}\"} \\\n \#{File.basename(from)} | \\\n \#{ssh_exec} \\\n \#{ssh_url} \\\n \\\"\#{sudo ? \"\#{@nodes_handler.sudo_on(@node)} \" : ''}tar \\\n --extract \\\n --gunzip \\\n --file - \\\n --directory \#{to} \\\n --owner root \\\n \\\"\n EOS\n end\nend\n" |
#remote_interactive ⇒ Object
Execute an interactive shell on the remote node
- API
-
This method is mandatory
-
- API
-
If defined, then with_connection_to has been called before this method.
-
- API
-
@cmd_runner can be used
-
- API
-
@nodes_handler can be used
-
- API
-
@node can be used to access the node on which we execute the remote bash
-
- API
-
@timeout can be used to know when the action should fail
-
- API
-
@stdout_io can be used to send stdout output
-
- API
-
@stderr_io can be used to send stderr output
-
271 272 273 274 275 276 277 278 279 280 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 271 def remote_interactive interactive_cmd = "#{ssh_exec} #{ssh_url}" out interactive_cmd # As we're not using run_cmd here, make sure we handle the dry_run switch ourselves if @cmd_runner.dry_run out 'Won\'t execute interactive shell in dry_run mode' else system interactive_cmd end end |
#ssh_config(ssh_exec: 'ssh', known_hosts_file: nil, nodes: @nodes_handler.known_nodes) ⇒ Object
Get an SSH configuration content giving access to nodes of the platforms with the current configuration
- Parameters
-
ssh_exec (String): SSH command to be used [default: ‘ssh’]
-
known_hosts_file (String or nil): Path to the known hosts file, or nil for default [default: nil]
-
nodes (Array<String>): List of nodes to generate the config for [default: @nodes_handler.known_nodes]
- Result
-
String: The SSH config
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 349 def ssh_config(ssh_exec: 'ssh', known_hosts_file: nil, nodes: @nodes_handler.known_nodes) config_content = " ############\n # GATEWAYS #\n ############\n\n \#{@ssh_gateways_conf.nil? || [email protected]_gateways.include?(@ssh_gateways_conf) ? '' : @config.ssh_for_gateway(@ssh_gateways_conf, ssh_exec: ssh_exec, user: @ssh_user)}\n\n #############\n # ENDPOINTS #\n #############\n\n EOS\n\n # Add each node\n # Query for the metadata of all nodes at once\n @nodes_handler.prefetch_metadata_of nodes, %i[private_ips hostname host_ip description]\n nodes.sort.each do |node|\n # Generate the conf for the node\n connection, connection_user, gateway, gateway_user = connection_info_for(node, no_exception: true)\n if connection.nil?\n config_content << \"# \#{node} - Not connectable using SSH - \#{@nodes_handler.get_description_of(node) || ''}\\n\"\n else\n config_content << \"# \#{node} - \#{connection} - \#{@nodes_handler.get_description_of(node) || ''}\\n\"\n config_content << \"Host \#{ssh_aliases_for(node).join(' ')}\\n\"\n config_content << \" Hostname \#{connection}\\n\"\n config_content << \" User \\\"\#{connection_user}\\\"\\n\" if connection_user != @ssh_user\n config_content << \" ProxyCommand \#{ssh_exec} -q -W %h:%p \#{gateway_user}@\#{gateway}\\n\" unless gateway.nil?\n if @passwords.key?(node)\n config_content << \" PreferredAuthentications password\\n\"\n config_content << \" PubkeyAuthentication no\\n\"\n end\n end\n config_content << \"\\n\"\n end\n # Add global definitions at the end of the SSH config, as they might be overriden by previous ones, and first match wins.\n config_content << <<~EOS\n ###########\n # GLOBALS #\n ###########\n\n Host *\n User \#{@ssh_user}\n # Default control socket path to be used when multiplexing SSH connections\n ControlPath \#{control_master_file('%h', '%p', '%r')}\n \#{open_ssh_major_version >= 7 ? 'PubkeyAcceptedKeyTypes +ssh-dss' : ''}\n \#{known_hosts_file.nil? ? '' : \"UserKnownHostsFile \#{known_hosts_file}\"}\n \#{@ssh_strict_host_key_checking ? '' : 'StrictHostKeyChecking no'}\n\n EOS\n config_content\nend\n" |
#ssh_exec ⇒ Object
Get the ssh executable to be used when connecting to the current node
- Result
-
String: The ssh executable
329 330 331 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 329 def ssh_exec ssh_exec_for @node end |
#ssh_url ⇒ Object
Get the ssh URL to be used to connect to the current node
- Result
-
String: The ssh URL connecting to the current node
337 338 339 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 337 def ssh_url "hpc.#{@node}" end |
#validate_params ⇒ Object
Validate that parsed parameters are valid
- API
-
This method is optional
-
- API
-
@cmd_runner can be used
-
- API
-
@nodes_handler can be used
-
182 183 184 185 186 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 182 def validate_params raise 'No SSH user name specified. Please use --ssh-user option or hpc_ssh_user environment variable to set it.' if @ssh_user.nil? || @ssh_user.empty? known_gateways = @config.known_gateways raise "Unknown gateway configuration provided: #{@ssh_gateways_conf}. Possible values are: #{known_gateways.join(', ')}." if !@ssh_gateways_conf.nil? && !known_gateways.include?(@ssh_gateways_conf) end |
#with_connection_to(nodes, no_exception: false) ⇒ Object
Prepare connections to a given set of nodes. Useful to prefetch metadata or open bulk connections.
- API
-
This method is optional
-
- API
-
@cmd_runner can be used
-
- API
-
@nodes_handler can be used
-
- Parameters
-
nodes (Array<String>): Nodes to prepare the connection to
-
no_exception (Boolean): Should we still continue if some nodes have connection errors? [default: false]
-
Proc: Code called with the connections prepared.
- Parameters
-
connected_nodes (Array<String>): The list of connected nodes (should be equal to nodes unless no_exception == true and some nodes failed to connect)
214 215 216 217 218 |
# File 'lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb', line 214 def with_connection_to(nodes, no_exception: false) with_ssh_master_to(nodes, no_exception: no_exception) do |connected_nodes| yield connected_nodes end end |