hybrid_platforms_conductor 32.6.0 → 32.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d6b6259b1f05bb082b4f2c6820f74b6c90dad15f5868a311337429b80d233500
4
- data.tar.gz: c5f58e3ba104ba2365addea64ab08c43a4fe9797430c950bf01258eb7bfcdc1b
3
+ metadata.gz: 34ae09b6107332c4f51418677736cabed358f01c3e5c47ff2f5752f6c597a809
4
+ data.tar.gz: 99358ffeb3d2f70819c5408c18b5b8d8e5531331da0c96073e5577f02cebcb72
5
5
  SHA512:
6
- metadata.gz: b079e89a17630ed994614bc24e76ba2154ae52fcaf3712feaaef9e823487d933d6b33b87b3aa253e8d11270d5765d061349c4a37f467e81d729d994ed1f7b72e
7
- data.tar.gz: 4a89e197bed2c87dbc10d66d8a3f6240c68deb7c2799962be282634c3c109b653c2a66725baa5ca44e38fd0bf1ec992148720047668c1c7786bd494a8e3836e7
6
+ metadata.gz: 873851a3e54538b2915df041e9dca3decde7cbfe4f7e8b82ea7102bbe5e75668ef5d7adb828979f4eab8493245f982b3e5da0bb492c116688b9151d10bc979a2
7
+ data.tar.gz: 72418b66806746d05589f08f773eb065b0811d760e35908410405f1cbd9a6fadc94aa2ec85a83ac7f314f53d6a7ebc6396460894c4f595aee9c9b0ffd65625c0
@@ -233,7 +233,13 @@ module HybridPlatformsConductor
233
233
  # Parameters::
234
234
  # * *bash_cmds* (String): Bash commands to execute
235
235
  def remote_bash(bash_cmds)
236
- ssh_cmd = "#{ssh_exec} #{ssh_url} /bin/bash <<'EOF'\n#{bash_cmds}\nEOF"
236
+ ssh_cmd =
237
+ if @nodes_handler.get_ssh_session_exec_of(@node) == 'false'
238
+ # When ExecSession is disabled we need to use stdin directly
239
+ "{ cat | #{ssh_exec} #{ssh_url} -T; } <<'EOF'\n#{bash_cmds}\nEOF"
240
+ else
241
+ "#{ssh_exec} #{ssh_url} /bin/bash <<'EOF'\n#{bash_cmds}\nEOF"
242
+ end
237
243
  # Due to a limitation of Process.spawn, each individual argument is limited to 128KB of size.
238
244
  # 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.
239
245
  if bash_cmds.size > MAX_CMD_ARG_LENGTH
@@ -290,25 +296,30 @@ module HybridPlatformsConductor
290
296
  # * *owner* (String or nil): Owner to be used when copying the files, or nil for current one [default: nil]
291
297
  # * *group* (String or nil): Group to be used when copying the files, or nil for current one [default: nil]
292
298
  def remote_copy(from, to, sudo: false, owner: nil, group: nil)
293
- run_cmd <<~EOS
294
- cd #{File.dirname(from)} && \
295
- tar \
296
- --create \
297
- --gzip \
298
- --file - \
299
- #{owner.nil? ? '' : "--owner #{owner}"} \
300
- #{group.nil? ? '' : "--group #{group}"} \
301
- #{File.basename(from)} | \
302
- #{ssh_exec} \
303
- #{ssh_url} \
304
- \"#{sudo ? "#{@nodes_handler.sudo_on(@node)} " : ''}tar \
305
- --extract \
306
- --gunzip \
299
+ if @nodes_handler.get_ssh_session_exec_of(@node) == 'false'
300
+ # We don't have ExecSession, so don't use ssh, but scp instead.
301
+ run_cmd "scp -S #{ssh_exec} #{from} #{ssh_url}:#{to}"
302
+ else
303
+ run_cmd <<~EOS
304
+ cd #{File.dirname(from)} && \
305
+ tar \
306
+ --create \
307
+ --gzip \
307
308
  --file - \
308
- --directory #{to} \
309
- --owner root \
310
- \"
311
- EOS
309
+ #{owner.nil? ? '' : "--owner #{owner}"} \
310
+ #{group.nil? ? '' : "--group #{group}"} \
311
+ #{File.basename(from)} | \
312
+ #{ssh_exec} \
313
+ #{ssh_url} \
314
+ \"#{sudo ? "#{@nodes_handler.sudo_on(@node)} " : ''}tar \
315
+ --extract \
316
+ --gunzip \
317
+ --file - \
318
+ --directory #{to} \
319
+ --owner root \
320
+ \"
321
+ EOS
322
+ end
312
323
  end
313
324
 
314
325
  # Get the ssh executable to be used when connecting to the current node
@@ -490,31 +501,45 @@ module HybridPlatformsConductor
490
501
  ssh_url = "hpc.#{node}"
491
502
  if current_users.empty?
492
503
  log_debug "[ ControlMaster - #{ssh_url} ] - Creating SSH ControlMaster..."
493
- # Create the control master
494
- ssh_control_master_start_cmd = "#{ssh_exec}#{@passwords.key?(node) || @auth_password ? '' : ' -o BatchMode=yes'} -o ControlMaster=yes -o ControlPersist=yes #{ssh_url} true"
495
504
  exit_status = nil
496
- idx_try = 0
497
- loop do
498
- stderr = nil
499
- exit_status, _stdout, stderr = @cmd_runner.run_cmd ssh_control_master_start_cmd, log_to_stdout: log_debug?, no_exception: true, timeout: timeout
500
- if exit_status == 0
501
- break
502
- elsif stderr =~ /System is booting up/
503
- if idx_try == MAX_RETRIES_FOR_BOOT
504
- if no_exception
505
- break
506
- else
507
- raise ActionsExecutor::ConnectionError, "Tried #{idx_try} times to create SSH Control Master with #{ssh_control_master_start_cmd} but system says it's booting up."
505
+ if @nodes_handler.get_ssh_session_exec_of(node) == 'false'
506
+ # Here we have to create a ControlMaster using an interactive session, as the SSH server prohibits ExecSession, and so command executions.
507
+ # We'll do that using another terminal spawned in the background.
508
+ Thread.new do
509
+ log_debug "[ ControlMaster - #{ssh_url} ] - Spawn interactive ControlMaster in separate terminal"
510
+ @cmd_runner.run_cmd "xterm -e '#{ssh_exec} -o ControlMaster=yes -o ControlPersist=yes #{ssh_url}'", log_to_stdout: log_debug?
511
+ log_debug "[ ControlMaster - #{ssh_url} ] - Separate interactive ControlMaster closed"
512
+ end
513
+ out 'External ControlMaster has been spawned.'
514
+ out 'Please login into it, keep its session opened and press enter here when done...'
515
+ $stdin.gets
516
+ exit_status = 0
517
+ else
518
+ # Create the control master
519
+ ssh_control_master_start_cmd = "#{ssh_exec}#{@passwords.key?(node) || @auth_password ? '' : ' -o BatchMode=yes'} -o ControlMaster=yes -o ControlPersist=yes #{ssh_url} true"
520
+ idx_try = 0
521
+ loop do
522
+ stderr = nil
523
+ exit_status, _stdout, stderr = @cmd_runner.run_cmd ssh_control_master_start_cmd, log_to_stdout: log_debug?, no_exception: true, timeout: timeout
524
+ if exit_status == 0
525
+ break
526
+ elsif stderr =~ /System is booting up/
527
+ if idx_try == MAX_RETRIES_FOR_BOOT
528
+ if no_exception
529
+ break
530
+ else
531
+ raise ActionsExecutor::ConnectionError, "Tried #{idx_try} times to create SSH Control Master with #{ssh_control_master_start_cmd} but system says it's booting up."
532
+ end
508
533
  end
534
+ # Wait a bit and try again
535
+ idx_try += 1
536
+ log_debug "[ ControlMaster - #{ssh_url} ] - System is booting up (try ##{idx_try}). Wait #{WAIT_TIME_FOR_BOOT} seconds before trying ControlMaster's creation again."
537
+ sleep WAIT_TIME_FOR_BOOT
538
+ elsif no_exception
539
+ break
540
+ else
541
+ raise ActionsExecutor::ConnectionError, "Error while starting SSH Control Master with #{ssh_control_master_start_cmd}: #{stderr.strip}"
509
542
  end
510
- # Wait a bit and try again
511
- idx_try += 1
512
- log_debug "[ ControlMaster - #{ssh_url} ] - System is booting up (try ##{idx_try}). Wait #{WAIT_TIME_FOR_BOOT} seconds before trying ControlMaster's creation again."
513
- sleep WAIT_TIME_FOR_BOOT
514
- elsif no_exception
515
- break
516
- else
517
- raise ActionsExecutor::ConnectionError, "Error while starting SSH Control Master with #{ssh_control_master_start_cmd}: #{stderr.strip}"
518
543
  end
519
544
  end
520
545
  if exit_status == 0
@@ -1,5 +1,5 @@
1
1
  module HybridPlatformsConductor
2
2
 
3
- VERSION = '32.6.0'
3
+ VERSION = '32.7.0'
4
4
 
5
5
  end
@@ -28,6 +28,22 @@ describe HybridPlatformsConductor::ActionsExecutor do
28
28
  end
29
29
  end
30
30
 
31
+ it 'creates an SSH master to 1 node not having Session Exec capabilities' do
32
+ with_test_platform(nodes: { 'node' => { meta: { host_ip: '192.168.42.42', ssh_session_exec: 'false' } } }) do
33
+ with_cmd_runner_mocked(
34
+ [
35
+ ['which env', proc { [0, "/usr/bin/env\n", ''] }],
36
+ ['ssh -V 2>&1', proc { [0, "OpenSSH_7.4p1 Debian-10+deb9u7, OpenSSL 1.0.2u 20 Dec 2019\n", ''] }]
37
+ ] + ssh_expected_commands_for({ 'node' => { connection: '192.168.42.42', user: 'test_user' } }, with_session_exec: false)
38
+ ) do
39
+ test_connector.ssh_user = 'test_user'
40
+ test_connector.with_connection_to(['node']) do |connected_nodes|
41
+ expect(connected_nodes).to eq ['node']
42
+ end
43
+ end
44
+ end
45
+ end
46
+
31
47
  it 'creates SSH master to several nodes' do
32
48
  with_test_platform(nodes: {
33
49
  'node1' => { meta: { host_ip: '192.168.42.1' } },
@@ -141,6 +141,30 @@ describe HybridPlatformsConductor::ActionsExecutor do
141
141
  end
142
142
  end
143
143
 
144
+ it 'executes bash commands remotely without Session Exec capabilities' do
145
+ with_test_platform_for_remote_testing(
146
+ expected_cmds: [[/^\{ cat \| .+\/ssh hpc\.node -T; } <<'EOF'\nbash_cmd.bash\nEOF$/, proc { [0, 'Bash commands executed on node', ''] }]],
147
+ expected_stdout: 'Bash commands executed on node',
148
+ session_exec: false
149
+ ) do
150
+ test_connector.remote_bash('bash_cmd.bash')
151
+ end
152
+ end
153
+
154
+ it 'copies files remotely without Session Exec capabilities' do
155
+ with_test_platform_for_remote_testing(
156
+ expected_cmds: [
157
+ [
158
+ /^scp -S .+\/ssh \/path\/to\/src.file hpc\.node:\/remote_path\/to\/dst.dir$/,
159
+ proc { [0, '', ''] }
160
+ ]
161
+ ],
162
+ session_exec: false
163
+ ) do
164
+ test_connector.remote_copy('/path/to/src.file', '/remote_path/to/dst.dir')
165
+ end
166
+ end
167
+
144
168
  end
145
169
 
146
170
  end
@@ -14,7 +14,6 @@ module HybridPlatformsConductorTest
14
14
  # * *cmd_runner* (CmdRunner): The CmdRunner to mock [default: test_cmd_runner]
15
15
  # * Proc: Code called with the command runner mocked
16
16
  def with_cmd_runner_mocked(commands, cmd_runner: test_cmd_runner)
17
- unexpected_commands = []
18
17
  remaining_expected_commands = commands.clone
19
18
  # We need to protect the access to this array as the mocked commands can be called by competing threads
20
19
  remaining_expected_commands_mutex = Mutex.new
@@ -64,13 +63,10 @@ module HybridPlatformsConductorTest
64
63
  log_stderr_to_io << mocked_stderr if !mocked_stderr.empty? && !log_stderr_to_io.nil?
65
64
  [mocked_exit_status, mocked_stdout, mocked_stderr]
66
65
  else
67
- logger.error "[ Mocked CmdRunner ] - !!! Unexpected command run: #{cmd}"
68
- unexpected_commands << cmd
69
- [:unexpected_command_to_mock, '', "Could not mock unexpected command #{cmd}"]
66
+ raise "Unexpected command run: #{cmd}"
70
67
  end
71
68
  end
72
69
  yield
73
- expect(unexpected_commands).to eq []
74
70
  expect(remaining_expected_commands).to eq([]), "Expected CmdRunner commands were not run:\n#{remaining_expected_commands.map(&:first).join("\n")}"
75
71
  # Un-mock the command runner
76
72
  allow(cmd_runner).to receive(:run_cmd).and_call_original
@@ -19,6 +19,7 @@ module HybridPlatformsConductorTest
19
19
  # * *with_control_master_destroy* (Boolean): Do we destroy the control master? [default: true]
20
20
  # * *with_strict_host_key_checking* (Boolean): Do we use strict host key checking? [default: true]
21
21
  # * *with_batch_mode* (Boolean): Do we use BatchMode when creating the control master? [default: true]
22
+ # * *with_session_exec* (Boolean): Do we use Sessien Exec capabilities when creating the control master? [default: true]
22
23
  # Result::
23
24
  # * Array< [String or Regexp, Proc] >: The expected commands that should be used, and their corresponding mocked code
24
25
  def ssh_expected_commands_for(
@@ -27,7 +28,8 @@ module HybridPlatformsConductorTest
27
28
  with_control_master_check: false,
28
29
  with_control_master_destroy: true,
29
30
  with_strict_host_key_checking: true,
30
- with_batch_mode: true
31
+ with_batch_mode: true,
32
+ with_session_exec: true
31
33
  )
32
34
  nodes_connections.map do |node, node_connection_info|
33
35
  node_connection_info[:times] = 1 unless node_connection_info.key?(:times)
@@ -44,7 +46,13 @@ module HybridPlatformsConductorTest
44
46
  end
45
47
  if with_control_master_create
46
48
  ssh_commands_per_connection << [
47
- /^.+\/ssh #{with_batch_mode ? '-o BatchMode=yes ' : ''}-o ControlMaster=yes -o ControlPersist=yes hpc\.#{Regexp.escape(node)} true$/,
49
+ if with_session_exec
50
+ /^.+\/ssh #{with_batch_mode ? '-o BatchMode=yes ' : ''}-o ControlMaster=yes -o ControlPersist=yes hpc\.#{Regexp.escape(node)} true$/
51
+ else
52
+ # Mock the user hitting enter as the Control MAster will be created in another thread and the main thread waits for user input.
53
+ expect($stdin).to receive(:gets) { "\n" }
54
+ /^xterm -e '.+\/ssh -o ControlMaster=yes -o ControlPersist=yes hpc\.#{Regexp.escape(node)}'$/
55
+ end,
48
56
  proc do
49
57
  control_file = test_actions_executor.connector(:ssh).send(:control_master_file, node_connection_info[:connection], '22', node_connection_info[:user])
50
58
  # Fail if the ControlMaster file already exists, as would SSH do if the file is stalled
@@ -53,6 +61,9 @@ module HybridPlatformsConductorTest
53
61
  elsif node_connection_info[:control_master_create_error].nil?
54
62
  # Really touch a fake control file, as ssh connector checks for its existence
55
63
  File.write(control_file, '')
64
+ # If there is no Session Exec, this is done in a separate thread.
65
+ # So keep it alive until the user wants to stop it (which is done using an ssh -O exit command).
66
+ loop { sleep 0.1 } unless with_session_exec
56
67
  [0, '', '']
57
68
  else
58
69
  [255, '', node_connection_info[:control_master_create_error]]
@@ -97,6 +108,7 @@ module HybridPlatformsConductorTest
97
108
  # * *timeout* (Integer or nil): Timeout to prepare the connector for [default: nil]
98
109
  # * *password* (String or nil): Password to set for the node, or nil for none [default: nil]
99
110
  # * *additional_config* (String): Additional config [default: '']
111
+ # * *session_exec* (Boolean): Do we mock a node having an SSH connection accepting Session Exec? [default: true]
100
112
  # * Proc: Client code to execute testing
101
113
  def with_test_platform_for_remote_testing(
102
114
  expected_cmds: [],
@@ -104,9 +116,14 @@ module HybridPlatformsConductorTest
104
116
  expected_stderr: '',
105
117
  timeout: nil,
106
118
  password: nil,
107
- additional_config: ''
119
+ additional_config: '',
120
+ session_exec: true
108
121
  )
109
- with_test_platform({ nodes: { 'node' => { meta: { host_ip: '192.168.42.42' } } } }, false, additional_config) do
122
+ with_test_platform(
123
+ { nodes: { 'node' => { meta: { host_ip: '192.168.42.42', ssh_session_exec: session_exec ? 'true' : 'false' } } } },
124
+ false,
125
+ additional_config
126
+ ) do
110
127
  with_cmd_runner_mocked(
111
128
  [
112
129
  ['which env', proc { [0, "/usr/bin/env\n", ''] }],
@@ -115,7 +132,8 @@ module HybridPlatformsConductorTest
115
132
  (password ? [['sshpass -V', proc { [0, "sshpass 1.06\n", ''] }]] : []) +
116
133
  ssh_expected_commands_for(
117
134
  { 'node' => { connection: '192.168.42.42', user: 'test_user' } },
118
- with_batch_mode: password.nil?
135
+ with_batch_mode: password.nil?,
136
+ with_session_exec: session_exec
119
137
  ) +
120
138
  expected_cmds
121
139
  ) do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hybrid_platforms_conductor
3
3
  version: !ruby/object:Gem::Version
4
- version: 32.6.0
4
+ version: 32.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Muriel Salvan