hybrid_platforms_conductor 32.6.0 → 32.8.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/deployer.rb +12 -1
- data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +90 -59
- data/lib/hybrid_platforms_conductor/hpc_plugins/test/check_deploy_and_idempotence.rb +11 -9
- data/lib/hybrid_platforms_conductor/provisioner.rb +9 -0
- data/lib/hybrid_platforms_conductor/version.rb +1 -1
- data/spec/hybrid_platforms_conductor_test.rb +1 -0
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb +49 -3
- data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb +24 -0
- data/spec/hybrid_platforms_conductor_test/api/deployer/provisioner_spec.rb +74 -10
- data/spec/hybrid_platforms_conductor_test/executables/options/common_spec.rb +2 -1
- data/spec/hybrid_platforms_conductor_test/helpers/cmd_runner_helpers.rb +33 -11
- data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +42 -8
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 539ccbcddce0dda10361f172eb18b5db378aa6e299117e4c25f3416a0e5eb3f8
|
|
4
|
+
data.tar.gz: 9e3f5c649cbc70399b462698d80794a56ce4e141b552d88df809373cb0e33b3c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f665a9dd0e2aed5c3e78fee2840db66eaa67374e81609042c42687ec65a86621ad1d6f5d0c25578526b19b8f3f534dc1c4330b5956348b83928138d0a6e30509
|
|
7
|
+
data.tar.gz: 4d7cc60f030cfe9ab7c24053b4c1fa2ef440c95a8170685157de7c4f25d54fc8d2b469303baffb98214d58e39613ae116bf42df0991832e68b8046c9b7eccae1
|
|
@@ -311,13 +311,24 @@ module HybridPlatformsConductor
|
|
|
311
311
|
environment: environment,
|
|
312
312
|
logger: @logger,
|
|
313
313
|
logger_stderr: @logger_stderr,
|
|
314
|
-
config:
|
|
314
|
+
config: sub_executable.config,
|
|
315
315
|
cmd_runner: @cmd_runner,
|
|
316
316
|
# Here we use the NodesHandler that will be bound to the sub-Deployer only, as the node's metadata might be modified by the Provisioner.
|
|
317
317
|
nodes_handler: sub_executable.nodes_handler,
|
|
318
318
|
actions_executor: @actions_executor
|
|
319
319
|
)
|
|
320
320
|
instance.with_running_instance(stop_on_exit: true, destroy_on_exit: !reuse_instance, port: 22) do
|
|
321
|
+
# Test-provisioned nodes have SSH Session Exec capabilities
|
|
322
|
+
sub_executable.nodes_handler.override_metadata_of node, :ssh_session_exec, 'true'
|
|
323
|
+
# Test-provisioned nodes use default sudo
|
|
324
|
+
sub_executable.config.sudo_procs.replace(sub_executable.config.sudo_procs.map do |sudo_proc_info|
|
|
325
|
+
{
|
|
326
|
+
nodes_selectors_stack: sudo_proc_info[:nodes_selectors_stack].map do |nodes_selector|
|
|
327
|
+
@nodes_handler.select_nodes(nodes_selector).select { |selected_node| selected_node != node }
|
|
328
|
+
end,
|
|
329
|
+
sudo_proc: sudo_proc_info[:sudo_proc]
|
|
330
|
+
}
|
|
331
|
+
end)
|
|
321
332
|
actions_executor = sub_executable.actions_executor
|
|
322
333
|
deployer = sub_executable.deployer
|
|
323
334
|
# Setup test environment for this container
|
|
@@ -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,46 @@ 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
|
+
raise "Can't spawn interactive ControlMaster to #{node} in non-interactive mode. You may want to change the hpc_interactive env variable." if ENV['hpc_interactive'] == 'false'
|
|
516
|
+
$stdin.gets
|
|
517
|
+
exit_status = 0
|
|
518
|
+
else
|
|
519
|
+
# Create the control master
|
|
520
|
+
ssh_control_master_start_cmd = "#{ssh_exec}#{@passwords.key?(node) || @auth_password ? '' : ' -o BatchMode=yes'} -o ControlMaster=yes -o ControlPersist=yes #{ssh_url} true"
|
|
521
|
+
idx_try = 0
|
|
522
|
+
loop do
|
|
523
|
+
stderr = nil
|
|
524
|
+
exit_status, _stdout, stderr = @cmd_runner.run_cmd ssh_control_master_start_cmd, log_to_stdout: log_debug?, no_exception: true, timeout: timeout
|
|
525
|
+
if exit_status == 0
|
|
526
|
+
break
|
|
527
|
+
elsif stderr =~ /System is booting up/
|
|
528
|
+
if idx_try == MAX_RETRIES_FOR_BOOT
|
|
529
|
+
if no_exception
|
|
530
|
+
break
|
|
531
|
+
else
|
|
532
|
+
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."
|
|
533
|
+
end
|
|
508
534
|
end
|
|
535
|
+
# Wait a bit and try again
|
|
536
|
+
idx_try += 1
|
|
537
|
+
log_debug "[ ControlMaster - #{ssh_url} ] - System is booting up (try ##{idx_try}). Wait #{WAIT_TIME_FOR_BOOT} seconds before trying ControlMaster's creation again."
|
|
538
|
+
sleep WAIT_TIME_FOR_BOOT
|
|
539
|
+
elsif no_exception
|
|
540
|
+
break
|
|
541
|
+
else
|
|
542
|
+
raise ActionsExecutor::ConnectionError, "Error while starting SSH Control Master with #{ssh_control_master_start_cmd}: #{stderr.strip}"
|
|
509
543
|
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
544
|
end
|
|
519
545
|
end
|
|
520
546
|
if exit_status == 0
|
|
@@ -549,28 +575,33 @@ module HybridPlatformsConductor
|
|
|
549
575
|
end
|
|
550
576
|
end
|
|
551
577
|
end
|
|
578
|
+
else
|
|
579
|
+
# We have not created any ControlMaster, but still consider the nodes to be ready to connect
|
|
580
|
+
user_locks = Hash[nodes.map { |node| [node, nil]} ]
|
|
552
581
|
end
|
|
553
582
|
yield user_locks.keys
|
|
554
583
|
ensure
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
584
|
+
if @ssh_use_control_master
|
|
585
|
+
user_locks_mutex.synchronize do
|
|
586
|
+
user_locks.each do |node, user_id|
|
|
587
|
+
with_lock_on_control_master_for(node, user_id: user_id) do |current_users, user_id|
|
|
588
|
+
ssh_url = "hpc.#{node}"
|
|
589
|
+
log_warn "[ ControlMaster - #{ssh_url} ] - Current process/thread was not part of the ControlMaster users anymore whereas it should have been" unless current_users.include?(user_id)
|
|
590
|
+
remaining_users = current_users - [user_id]
|
|
591
|
+
if remaining_users.empty?
|
|
592
|
+
# Stop the ControlMaster
|
|
593
|
+
log_debug "[ ControlMaster - #{ssh_url} ] - Stopping ControlMaster..."
|
|
594
|
+
# Dumb verbose ssh! Tricky trick to just silence what is useless.
|
|
595
|
+
# Don't fail if the connection close fails (but still log the error), as it can be seen as only a warning: it means the connection was closed anyway.
|
|
596
|
+
@cmd_runner.run_cmd "#{ssh_exec_for(node)} -O exit #{ssh_url} 2>&1 | grep -v 'Exit request sent.'", log_to_stdout: log_debug?, expected_code: 1, timeout: timeout, no_exception: true
|
|
597
|
+
log_debug "[ ControlMaster - #{ssh_url} ] - ControlMaster stopped"
|
|
598
|
+
# Uncomment if you want to test that the connection has been closed
|
|
599
|
+
# @cmd_runner.run_cmd "#{ssh_exec_for(node)} -O check #{ssh_url}", log_to_stdout: log_debug?, expected_code: 255, timeout: timeout
|
|
600
|
+
else
|
|
601
|
+
log_debug "[ ControlMaster - #{ssh_url} ] - Leaving ControlMaster started as #{remaining_users.size} processes/threads are still using it."
|
|
602
|
+
end
|
|
603
|
+
false
|
|
572
604
|
end
|
|
573
|
-
false
|
|
574
605
|
end
|
|
575
606
|
end
|
|
576
607
|
end
|
|
@@ -54,17 +54,19 @@ module HybridPlatformsConductor
|
|
|
54
54
|
instance.stop
|
|
55
55
|
instance.with_running_instance(port: 22) do
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
unless @nodes_handler.get_root_access_allowed_of(@node) == 'true'
|
|
58
|
+
# ===== Deploy removes root access
|
|
59
|
+
# Check that we can't connect with root
|
|
60
|
+
ssh_ok = false
|
|
61
|
+
begin
|
|
62
|
+
Net::SSH.start(instance.ip, 'root', password: 'root_pwd', auth_methods: ['password'], verify_host_key: :never) do |ssh|
|
|
63
|
+
ssh_ok = ssh.exec!('echo Works').strip == 'Works'
|
|
64
|
+
end
|
|
65
|
+
rescue
|
|
63
66
|
end
|
|
64
|
-
|
|
67
|
+
assert_equal ssh_ok, false, 'Root can still connect on the image after deployment'
|
|
68
|
+
# Even if we can connect using root, run the idempotence test
|
|
65
69
|
end
|
|
66
|
-
assert_equal ssh_ok, false, 'Root can still connect on the image after deployment'
|
|
67
|
-
# Even if we can connect using root, run the idempotence test
|
|
68
70
|
|
|
69
71
|
# ===== Idempotence
|
|
70
72
|
unless ssh_ok
|
|
@@ -76,6 +76,15 @@ module HybridPlatformsConductor
|
|
|
76
76
|
# Make sure we update it.
|
|
77
77
|
@nodes_handler.override_metadata_of @node, :host_ip, instance_ip
|
|
78
78
|
@nodes_handler.invalidate_metadata_of @node, :host_keys
|
|
79
|
+
# Make sure the SSH transformations don't apply to this node
|
|
80
|
+
@config.ssh_connection_transforms.replace(@config.ssh_connection_transforms.map do |ssh_transform_info|
|
|
81
|
+
{
|
|
82
|
+
nodes_selectors_stack: ssh_transform_info[:nodes_selectors_stack].map do |nodes_selector|
|
|
83
|
+
@nodes_handler.select_nodes(nodes_selector).select { |selected_node| selected_node != @node }
|
|
84
|
+
end,
|
|
85
|
+
transform: ssh_transform_info[:transform]
|
|
86
|
+
}
|
|
87
|
+
end)
|
|
79
88
|
end
|
|
80
89
|
wait_for_port!(port) if port
|
|
81
90
|
yield
|
|
@@ -94,6 +94,7 @@ module HybridPlatformsConductorTest
|
|
|
94
94
|
ENV.delete 'hpc_password_for_thycotic'
|
|
95
95
|
ENV.delete 'hpc_domain_for_thycotic'
|
|
96
96
|
ENV.delete 'hpc_certificates'
|
|
97
|
+
ENV.delete 'hpc_interactive'
|
|
97
98
|
# Set the necessary Hybrid Platforms Conductor environment variables
|
|
98
99
|
ENV['hpc_ssh_user'] = 'test_user'
|
|
99
100
|
HybridPlatformsConductor::ServicesHandler.packaged_deployments.clear
|
data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb
CHANGED
|
@@ -28,6 +28,45 @@ 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
|
+
|
|
47
|
+
it 'can\'t create an SSH master to 1 node not having Session Exec capabilities when hpc_interactive is false' do
|
|
48
|
+
with_test_platform(nodes: { 'node' => { meta: { host_ip: '192.168.42.42', ssh_session_exec: 'false' } } }) do
|
|
49
|
+
ENV['hpc_interactive'] = 'false'
|
|
50
|
+
with_cmd_runner_mocked(
|
|
51
|
+
[
|
|
52
|
+
['which env', proc { [0, "/usr/bin/env\n", ''] }],
|
|
53
|
+
['ssh -V 2>&1', proc { [0, "OpenSSH_7.4p1 Debian-10+deb9u7, OpenSSL 1.0.2u 20 Dec 2019\n", ''] }]
|
|
54
|
+
] + ssh_expected_commands_for(
|
|
55
|
+
{ 'node' => { connection: '192.168.42.42', user: 'test_user' } },
|
|
56
|
+
with_control_master_create_optional: true,
|
|
57
|
+
with_control_master_destroy: false,
|
|
58
|
+
with_session_exec: false
|
|
59
|
+
)
|
|
60
|
+
) do
|
|
61
|
+
test_connector.ssh_user = 'test_user'
|
|
62
|
+
expect do
|
|
63
|
+
test_connector.with_connection_to(['node']) do
|
|
64
|
+
end
|
|
65
|
+
end.to raise_error 'Can\'t spawn interactive ControlMaster to node in non-interactive mode. You may want to change the hpc_interactive env variable.'
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
31
70
|
it 'creates SSH master to several nodes' do
|
|
32
71
|
with_test_platform(nodes: {
|
|
33
72
|
'node1' => { meta: { host_ip: '192.168.42.1' } },
|
|
@@ -102,8 +141,14 @@ describe HybridPlatformsConductor::ActionsExecutor do
|
|
|
102
141
|
['which env', proc { [0, "/usr/bin/env\n", ''] }],
|
|
103
142
|
['ssh -V 2>&1', proc { [0, "OpenSSH_7.4p1 Debian-10+deb9u7, OpenSSL 1.0.2u 20 Dec 2019\n", ''] }]
|
|
104
143
|
] + ssh_expected_commands_for(
|
|
105
|
-
|
|
106
|
-
|
|
144
|
+
{
|
|
145
|
+
'node1' => { connection: '192.168.42.1', user: 'test_user' },
|
|
146
|
+
'node3' => { connection: '192.168.42.3', user: 'test_user' }
|
|
147
|
+
},
|
|
148
|
+
# Here the threads for node1's and node3's ControlMasters might not trigger before the one for node2, so they will not destroy it.
|
|
149
|
+
# Sometimes they don't even have time to create the Control Masters that node2 has already failed.
|
|
150
|
+
with_control_master_create_optional: true,
|
|
151
|
+
with_control_master_destroy_optional: true
|
|
107
152
|
) + ssh_expected_commands_for(
|
|
108
153
|
{
|
|
109
154
|
'node2' => { connection: '192.168.42.2', user: 'test_user', control_master_create_error: 'Can\'t connect to 192.168.42.2' }
|
|
@@ -278,7 +323,8 @@ describe HybridPlatformsConductor::ActionsExecutor do
|
|
|
278
323
|
) do
|
|
279
324
|
test_connector.ssh_use_control_master = false
|
|
280
325
|
test_connector.ssh_user = 'test_user'
|
|
281
|
-
test_connector.with_connection_to(['node']) do
|
|
326
|
+
test_connector.with_connection_to(['node']) do |connected_nodes|
|
|
327
|
+
expect(connected_nodes).to eq %w[node]
|
|
282
328
|
end
|
|
283
329
|
end
|
|
284
330
|
end
|
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
|
|
@@ -17,8 +17,8 @@ describe HybridPlatformsConductor::Deployer do
|
|
|
17
17
|
block.call
|
|
18
18
|
end
|
|
19
19
|
provisioner = nil
|
|
20
|
-
test_deployer.with_test_provisioned_instance(:test_provisioner, 'node', environment: 'hpc_testing_provisioner') do |
|
|
21
|
-
expect(
|
|
20
|
+
test_deployer.with_test_provisioned_instance(:test_provisioner, 'node', environment: 'hpc_testing_provisioner') do |sub_test_deployer, test_instance|
|
|
21
|
+
expect(sub_test_deployer.local_environment).to eq true
|
|
22
22
|
provisioner = test_instance
|
|
23
23
|
expect(test_instance.node).to eq 'node'
|
|
24
24
|
expect(test_instance.environment).to match /^#{Regexp.escape(`whoami`.strip)}_hpc_testing_provisioner_\d+_\d+_\w+$/
|
|
@@ -40,8 +40,8 @@ describe HybridPlatformsConductor::Deployer do
|
|
|
40
40
|
block.call
|
|
41
41
|
end
|
|
42
42
|
provisioner = nil
|
|
43
|
-
test_deployer.with_test_provisioned_instance(:test_provisioner, 'node', environment: 'hpc_testing_provisioner') do |
|
|
44
|
-
expect(
|
|
43
|
+
test_deployer.with_test_provisioned_instance(:test_provisioner, 'node', environment: 'hpc_testing_provisioner') do |sub_test_deployer, test_instance|
|
|
44
|
+
expect(sub_test_deployer.local_environment).to eq true
|
|
45
45
|
provisioner = test_instance
|
|
46
46
|
expect(test_instance.node).to eq 'node'
|
|
47
47
|
expect(test_instance.environment).to match /^#{Regexp.escape(`whoami`.strip)}_hpc_testing_provisioner_\d+_\d+_\w+$/
|
|
@@ -50,6 +50,70 @@ describe HybridPlatformsConductor::Deployer do
|
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
+
it 'gives a new test instance ready to be used in place of the node without SSH transformations' do
|
|
54
|
+
with_test_platform(
|
|
55
|
+
{
|
|
56
|
+
nodes: {
|
|
57
|
+
'node1' => { meta: { host_ip: '192.168.42.1', ssh_session_exec: 'false' } },
|
|
58
|
+
'node2' => { meta: { host_ip: '192.168.42.2', ssh_session_exec: 'false' } }
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
false,
|
|
62
|
+
'
|
|
63
|
+
for_nodes(%w[node1 node2]) do
|
|
64
|
+
transform_ssh_connection do |node, connection, connection_user, gateway, gateway_user|
|
|
65
|
+
["#{connection}_#{node}", "#{connection_user}_#{node}", "#{gateway}_#{node}", "#{gateway_user}_#{node}"]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
'
|
|
69
|
+
) do |repository|
|
|
70
|
+
register_plugins(:provisioner, { test_provisioner: HybridPlatformsConductorTest::TestProvisioner })
|
|
71
|
+
File.write("#{test_config.hybrid_platforms_dir}/dummy_secrets.json", '{}')
|
|
72
|
+
HybridPlatformsConductorTest::TestProvisioner.mocked_states = %i[created created running exited]
|
|
73
|
+
HybridPlatformsConductorTest::TestProvisioner.mocked_ip = '172.17.0.1'
|
|
74
|
+
expect(Socket).to receive(:tcp).with('172.17.0.1', 22, { connect_timeout: 1 }) do |&block|
|
|
75
|
+
block.call
|
|
76
|
+
end
|
|
77
|
+
test_deployer.with_test_provisioned_instance(:test_provisioner, 'node1', environment: 'hpc_testing_provisioner') do |sub_test_deployer, test_instance|
|
|
78
|
+
expect(sub_test_deployer.instance_eval { @nodes_handler.get_ssh_session_exec_of('node1') }).to eq 'true'
|
|
79
|
+
expect(sub_test_deployer.instance_eval { @nodes_handler.get_ssh_session_exec_of('node2') }).to eq 'false'
|
|
80
|
+
ssh_transforms = test_instance.instance_eval { @config.ssh_connection_transforms }
|
|
81
|
+
expect(ssh_transforms.size).to eq 1
|
|
82
|
+
expect(ssh_transforms[0][:nodes_selectors_stack]).to eq [%w[node2]]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'gives a new test instance ready to be used in place of the node without sudo specificities' do
|
|
88
|
+
with_test_platform(
|
|
89
|
+
{
|
|
90
|
+
nodes: {
|
|
91
|
+
'node1' => { meta: { host_ip: '192.168.42.1' } },
|
|
92
|
+
'node2' => { meta: { host_ip: '192.168.42.2' } }
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
false,
|
|
96
|
+
'
|
|
97
|
+
for_nodes(%w[node1 node2]) do
|
|
98
|
+
sudo_for { |user| "other_sudo --user #{user}" }
|
|
99
|
+
end
|
|
100
|
+
'
|
|
101
|
+
) do |repository|
|
|
102
|
+
register_plugins(:provisioner, { test_provisioner: HybridPlatformsConductorTest::TestProvisioner })
|
|
103
|
+
File.write("#{test_config.hybrid_platforms_dir}/dummy_secrets.json", '{}')
|
|
104
|
+
HybridPlatformsConductorTest::TestProvisioner.mocked_states = %i[created created running exited]
|
|
105
|
+
HybridPlatformsConductorTest::TestProvisioner.mocked_ip = '172.17.0.1'
|
|
106
|
+
expect(Socket).to receive(:tcp).with('172.17.0.1', 22, { connect_timeout: 1 }) do |&block|
|
|
107
|
+
block.call
|
|
108
|
+
end
|
|
109
|
+
test_deployer.with_test_provisioned_instance(:test_provisioner, 'node1', environment: 'hpc_testing_provisioner') do |sub_test_deployer, test_instance|
|
|
110
|
+
sudo_procs = test_instance.instance_eval { @config.sudo_procs }
|
|
111
|
+
expect(sudo_procs.size).to eq 1
|
|
112
|
+
expect(sudo_procs[0][:nodes_selectors_stack]).to eq [%w[node2]]
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
53
117
|
it 'does not destroy instances when asked to reuse' do
|
|
54
118
|
with_test_platform(
|
|
55
119
|
nodes: { 'node' => { meta: { host_ip: '192.168.42.42' } } }
|
|
@@ -62,8 +126,8 @@ describe HybridPlatformsConductor::Deployer do
|
|
|
62
126
|
block.call
|
|
63
127
|
end
|
|
64
128
|
provisioner = nil
|
|
65
|
-
test_deployer.with_test_provisioned_instance(:test_provisioner, 'node', environment: 'hpc_testing_provisioner', reuse_instance: true) do |
|
|
66
|
-
expect(
|
|
129
|
+
test_deployer.with_test_provisioned_instance(:test_provisioner, 'node', environment: 'hpc_testing_provisioner', reuse_instance: true) do |sub_test_deployer, test_instance|
|
|
130
|
+
expect(sub_test_deployer.local_environment).to eq true
|
|
67
131
|
provisioner = test_instance
|
|
68
132
|
expect(test_instance.node).to eq 'node'
|
|
69
133
|
expect(test_instance.environment).to eq "#{`whoami`.strip}_hpc_testing_provisioner"
|
|
@@ -84,8 +148,8 @@ describe HybridPlatformsConductor::Deployer do
|
|
|
84
148
|
block.call
|
|
85
149
|
end
|
|
86
150
|
provisioner = nil
|
|
87
|
-
test_deployer.with_test_provisioned_instance(:test_provisioner, 'node', environment: 'hpc_testing_provisioner', reuse_instance: true) do |
|
|
88
|
-
expect(
|
|
151
|
+
test_deployer.with_test_provisioned_instance(:test_provisioner, 'node', environment: 'hpc_testing_provisioner', reuse_instance: true) do |sub_test_deployer, test_instance|
|
|
152
|
+
expect(sub_test_deployer.local_environment).to eq true
|
|
89
153
|
provisioner = test_instance
|
|
90
154
|
expect(test_instance.node).to eq 'node'
|
|
91
155
|
expect(test_instance.environment).to eq "#{`whoami`.strip}_hpc_testing_provisioner"
|
|
@@ -102,7 +166,7 @@ describe HybridPlatformsConductor::Deployer do
|
|
|
102
166
|
File.write("#{test_config.hybrid_platforms_dir}/dummy_secrets.json", '{}')
|
|
103
167
|
HybridPlatformsConductorTest::TestProvisioner.mocked_states = %i[created created created exited exited]
|
|
104
168
|
expect do
|
|
105
|
-
test_deployer.with_test_provisioned_instance(:test_provisioner, 'node', environment: 'hpc_testing_provisioner') do |
|
|
169
|
+
test_deployer.with_test_provisioned_instance(:test_provisioner, 'node', environment: 'hpc_testing_provisioner') do |sub_test_deployer, test_instance|
|
|
106
170
|
end
|
|
107
171
|
end.to raise_error /\[ node\/#{Regexp.escape(`whoami`.strip)}_hpc_testing_provisioner_\d+_\d+_\w+ \] - Instance fails to be in a state among \(running\) with timeout 1\. Currently in state exited/
|
|
108
172
|
end
|
|
@@ -120,7 +184,7 @@ describe HybridPlatformsConductor::Deployer do
|
|
|
120
184
|
raise Errno::ETIMEDOUT, 'Timeout while reading from port 22'
|
|
121
185
|
end
|
|
122
186
|
expect do
|
|
123
|
-
test_deployer.with_test_provisioned_instance(:test_provisioner, 'node', environment: 'hpc_testing_provisioner') do |
|
|
187
|
+
test_deployer.with_test_provisioned_instance(:test_provisioner, 'node', environment: 'hpc_testing_provisioner') do |sub_test_deployer, test_instance|
|
|
124
188
|
end
|
|
125
189
|
end.to raise_error /\[ node\/#{Regexp.escape(`whoami`.strip)}_hpc_testing_provisioner_\d+_\d+_\w+ \] - Instance fails to have port 22 opened with timeout 1\./
|
|
126
190
|
end
|
|
@@ -50,7 +50,8 @@ describe 'executables\' common options' do
|
|
|
50
50
|
with_test_platform_for_common_options do
|
|
51
51
|
exit_code, stdout, stderr = run executable, *(['--debug'] + default_options)
|
|
52
52
|
expect(exit_code).to eq 0
|
|
53
|
-
|
|
53
|
+
# Make sure to ignore the deployment markers from stderr.
|
|
54
|
+
expect(stderr.gsub("===== [ node1 / node1_service ] - HPC Service Check ===== Begin\n===== [ node1 / node1_service ] - HPC Service Check ===== End\n", '')).to eq ''
|
|
54
55
|
end
|
|
55
56
|
end
|
|
56
57
|
|
|
@@ -8,14 +8,27 @@ module HybridPlatformsConductorTest
|
|
|
8
8
|
# Run expectations on the expected commands to be called.
|
|
9
9
|
#
|
|
10
10
|
# Parameters::
|
|
11
|
-
# * *commands* (Array<
|
|
12
|
-
# *
|
|
13
|
-
#
|
|
11
|
+
# * *commands* (Array<Array>): List of expected commands that should be called on CmdRunner. Each specification is a list containing those items:
|
|
12
|
+
# * *0* (String or Regexp): The command name or regexp matching the command name
|
|
13
|
+
# * *1* (Proc): The mocking code to be called in place of the real command:
|
|
14
|
+
# * Parameters::
|
|
15
|
+
# * Same parameters as CmdRunner@run_cmd
|
|
16
|
+
# * Result::
|
|
17
|
+
# * Same results as CmdRunner@run_cmd
|
|
18
|
+
# * *2* (Hash): Optional hash of options. Can be ommited. [default = {}]
|
|
19
|
+
# * *optional* (Boolean): If true then don't fail if the command to be mocked has not been called [default: false]
|
|
14
20
|
# * *cmd_runner* (CmdRunner): The CmdRunner to mock [default: test_cmd_runner]
|
|
15
21
|
# * Proc: Code called with the command runner mocked
|
|
16
22
|
def with_cmd_runner_mocked(commands, cmd_runner: test_cmd_runner)
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
remaining_expected_commands = commands.map do |(expected_command, command_code, options)|
|
|
24
|
+
[
|
|
25
|
+
expected_command,
|
|
26
|
+
command_code,
|
|
27
|
+
{
|
|
28
|
+
optional: false
|
|
29
|
+
}.merge(options || {})
|
|
30
|
+
]
|
|
31
|
+
end
|
|
19
32
|
# We need to protect the access to this array as the mocked commands can be called by competing threads
|
|
20
33
|
remaining_expected_commands_mutex = Mutex.new
|
|
21
34
|
allow(cmd_runner).to receive(:run_cmd) do |cmd, log_to_file: nil, log_to_stdout: true, log_stdout_to_io: nil, log_stderr_to_io: nil, expected_code: 0, timeout: nil, no_exception: false|
|
|
@@ -23,7 +36,7 @@ module HybridPlatformsConductorTest
|
|
|
23
36
|
found_command = nil
|
|
24
37
|
found_command_code = nil
|
|
25
38
|
remaining_expected_commands_mutex.synchronize do
|
|
26
|
-
remaining_expected_commands.delete_if do |(expected_command, command_code)|
|
|
39
|
+
remaining_expected_commands.delete_if do |(expected_command, command_code, _options)|
|
|
27
40
|
break unless found_command.nil?
|
|
28
41
|
if (expected_command.is_a?(String) && expected_command == cmd) || (expected_command.is_a?(Regexp) && cmd =~ expected_command)
|
|
29
42
|
found_command = expected_command
|
|
@@ -64,14 +77,23 @@ module HybridPlatformsConductorTest
|
|
|
64
77
|
log_stderr_to_io << mocked_stderr if !mocked_stderr.empty? && !log_stderr_to_io.nil?
|
|
65
78
|
[mocked_exit_status, mocked_stdout, mocked_stderr]
|
|
66
79
|
else
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
80
|
+
raise "Unexpected command run:\n#{cmd}\nRemaining expected commands:\n#{
|
|
81
|
+
remaining_expected_commands.map do |(expected_command, _command_code, _options)|
|
|
82
|
+
expected_command
|
|
83
|
+
end.join("\n")
|
|
84
|
+
}"
|
|
70
85
|
end
|
|
71
86
|
end
|
|
72
87
|
yield
|
|
73
|
-
expect(
|
|
74
|
-
|
|
88
|
+
expect(
|
|
89
|
+
remaining_expected_commands.select do |(_expected_command, _command_code, options)|
|
|
90
|
+
!options[:optional]
|
|
91
|
+
end
|
|
92
|
+
).to eq([]), "Expected CmdRunner commands were not run:\n#{
|
|
93
|
+
remaining_expected_commands.map do |(expected_command, _command_code, options)|
|
|
94
|
+
"#{options[:optional] ? '[Optional] ' : ''}#{expected_command}"
|
|
95
|
+
end.join("\n")
|
|
96
|
+
}"
|
|
75
97
|
# Un-mock the command runner
|
|
76
98
|
allow(cmd_runner).to receive(:run_cmd).and_call_original
|
|
77
99
|
end
|
|
@@ -15,19 +15,25 @@ module HybridPlatformsConductorTest
|
|
|
15
15
|
# * *times* (Integer): Number of times this connection should be used [default: 1]
|
|
16
16
|
# * *control_master_create_error* (String or nil): Error to simulate during the SSH ControlMaster creation, or nil for none [default: nil]
|
|
17
17
|
# * *with_control_master_create* (Boolean): Do we create the control master? [default: true]
|
|
18
|
+
# * *with_control_master_create_optional* (Boolean): If true, then consider the ControlMaster creation to be optional [default: false]
|
|
18
19
|
# * *with_control_master_check* (Boolean): Do we check the control master? [default: false]
|
|
19
20
|
# * *with_control_master_destroy* (Boolean): Do we destroy the control master? [default: true]
|
|
21
|
+
# * *with_control_master_destroy_optional* (Boolean): If true, then consider the ControlMaster destruction to be optional [default: false]
|
|
20
22
|
# * *with_strict_host_key_checking* (Boolean): Do we use strict host key checking? [default: true]
|
|
21
23
|
# * *with_batch_mode* (Boolean): Do we use BatchMode when creating the control master? [default: true]
|
|
24
|
+
# * *with_session_exec* (Boolean): Do we use Sessien Exec capabilities when creating the control master? [default: true]
|
|
22
25
|
# Result::
|
|
23
|
-
# * Array<
|
|
26
|
+
# * Array<Array>: The expected commands that should be used, and their corresponding mocked code and options
|
|
24
27
|
def ssh_expected_commands_for(
|
|
25
28
|
nodes_connections,
|
|
26
29
|
with_control_master_create: true,
|
|
30
|
+
with_control_master_create_optional: false,
|
|
27
31
|
with_control_master_check: false,
|
|
28
32
|
with_control_master_destroy: true,
|
|
33
|
+
with_control_master_destroy_optional: false,
|
|
29
34
|
with_strict_host_key_checking: true,
|
|
30
|
-
with_batch_mode: true
|
|
35
|
+
with_batch_mode: true,
|
|
36
|
+
with_session_exec: true
|
|
31
37
|
)
|
|
32
38
|
nodes_connections.map do |node, node_connection_info|
|
|
33
39
|
node_connection_info[:times] = 1 unless node_connection_info.key?(:times)
|
|
@@ -43,8 +49,23 @@ module HybridPlatformsConductorTest
|
|
|
43
49
|
])
|
|
44
50
|
end
|
|
45
51
|
if with_control_master_create
|
|
52
|
+
control_master_created = false
|
|
46
53
|
ssh_commands_per_connection << [
|
|
47
|
-
|
|
54
|
+
if with_session_exec
|
|
55
|
+
/^.+\/ssh #{with_batch_mode ? '-o BatchMode=yes ' : ''}-o ControlMaster=yes -o ControlPersist=yes hpc\.#{Regexp.escape(node)} true$/
|
|
56
|
+
else
|
|
57
|
+
unless ENV['hpc_interactive'] == 'false'
|
|
58
|
+
# Mock the user hitting enter as the Control Master will be created in another thread and the main thread waits for user input.
|
|
59
|
+
expect($stdin).to receive(:gets) do
|
|
60
|
+
# We have to wait for the Control Master creation thread to actually create the Control Master before hitting Enter.
|
|
61
|
+
while !control_master_created do
|
|
62
|
+
sleep 0.1
|
|
63
|
+
end
|
|
64
|
+
"\n"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
/^xterm -e '.+\/ssh -o ControlMaster=yes -o ControlPersist=yes hpc\.#{Regexp.escape(node)}'$/
|
|
68
|
+
end,
|
|
48
69
|
proc do
|
|
49
70
|
control_file = test_actions_executor.connector(:ssh).send(:control_master_file, node_connection_info[:connection], '22', node_connection_info[:user])
|
|
50
71
|
# Fail if the ControlMaster file already exists, as would SSH do if the file is stalled
|
|
@@ -53,11 +74,16 @@ module HybridPlatformsConductorTest
|
|
|
53
74
|
elsif node_connection_info[:control_master_create_error].nil?
|
|
54
75
|
# Really touch a fake control file, as ssh connector checks for its existence
|
|
55
76
|
File.write(control_file, '')
|
|
77
|
+
control_master_created = true
|
|
78
|
+
# If there is no Session Exec, this is done in a separate thread.
|
|
79
|
+
# So keep it alive until the user wants to stop it (which is done using an ssh -O exit command).
|
|
80
|
+
loop { sleep 0.1 } unless with_session_exec
|
|
56
81
|
[0, '', '']
|
|
57
82
|
else
|
|
58
83
|
[255, '', node_connection_info[:control_master_create_error]]
|
|
59
84
|
end
|
|
60
|
-
end
|
|
85
|
+
end,
|
|
86
|
+
{ optional: with_control_master_create_optional }
|
|
61
87
|
]
|
|
62
88
|
end
|
|
63
89
|
if with_control_master_check
|
|
@@ -73,7 +99,8 @@ module HybridPlatformsConductorTest
|
|
|
73
99
|
# Really mock the control file deletion
|
|
74
100
|
File.unlink(test_actions_executor.connector(:ssh).send(:control_master_file, node_connection_info[:connection], '22', node_connection_info[:user]))
|
|
75
101
|
[1, '', '']
|
|
76
|
-
end
|
|
102
|
+
end,
|
|
103
|
+
{ optional: with_control_master_destroy_optional }
|
|
77
104
|
]
|
|
78
105
|
end
|
|
79
106
|
ssh_commands_once + ssh_commands_per_connection * node_connection_info[:times]
|
|
@@ -97,6 +124,7 @@ module HybridPlatformsConductorTest
|
|
|
97
124
|
# * *timeout* (Integer or nil): Timeout to prepare the connector for [default: nil]
|
|
98
125
|
# * *password* (String or nil): Password to set for the node, or nil for none [default: nil]
|
|
99
126
|
# * *additional_config* (String): Additional config [default: '']
|
|
127
|
+
# * *session_exec* (Boolean): Do we mock a node having an SSH connection accepting Session Exec? [default: true]
|
|
100
128
|
# * Proc: Client code to execute testing
|
|
101
129
|
def with_test_platform_for_remote_testing(
|
|
102
130
|
expected_cmds: [],
|
|
@@ -104,9 +132,14 @@ module HybridPlatformsConductorTest
|
|
|
104
132
|
expected_stderr: '',
|
|
105
133
|
timeout: nil,
|
|
106
134
|
password: nil,
|
|
107
|
-
additional_config: ''
|
|
135
|
+
additional_config: '',
|
|
136
|
+
session_exec: true
|
|
108
137
|
)
|
|
109
|
-
with_test_platform(
|
|
138
|
+
with_test_platform(
|
|
139
|
+
{ nodes: { 'node' => { meta: { host_ip: '192.168.42.42', ssh_session_exec: session_exec ? 'true' : 'false' } } } },
|
|
140
|
+
false,
|
|
141
|
+
additional_config
|
|
142
|
+
) do
|
|
110
143
|
with_cmd_runner_mocked(
|
|
111
144
|
[
|
|
112
145
|
['which env', proc { [0, "/usr/bin/env\n", ''] }],
|
|
@@ -115,7 +148,8 @@ module HybridPlatformsConductorTest
|
|
|
115
148
|
(password ? [['sshpass -V', proc { [0, "sshpass 1.06\n", ''] }]] : []) +
|
|
116
149
|
ssh_expected_commands_for(
|
|
117
150
|
{ 'node' => { connection: '192.168.42.42', user: 'test_user' } },
|
|
118
|
-
with_batch_mode: password.nil
|
|
151
|
+
with_batch_mode: password.nil?,
|
|
152
|
+
with_session_exec: session_exec
|
|
119
153
|
) +
|
|
120
154
|
expected_cmds
|
|
121
155
|
) do
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hybrid_platforms_conductor
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 32.
|
|
4
|
+
version: 32.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Muriel Salvan
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-03-
|
|
11
|
+
date: 2021-03-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: range_operators
|