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 +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
|