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 +4 -4
- data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +66 -41
- data/lib/hybrid_platforms_conductor/version.rb +1 -1
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb +16 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb +24 -0
- data/spec/hybrid_platforms_conductor_test/helpers/cmd_runner_helpers.rb +1 -5
- data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +23 -5
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 34ae09b6107332c4f51418677736cabed358f01c3e5c47ff2f5752f6c597a809
|
|
4
|
+
data.tar.gz: 99358ffeb3d2f70819c5408c18b5b8d8e5531331da0c96073e5577f02cebcb72
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 =
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
--
|
|
309
|
-
--
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb
CHANGED
|
@@ -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' } },
|
data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|