hybrid_platforms_conductor 32.6.0 → 32.7.0

Sign up to get free protection for your applications and to get access to all the features.
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