hybrid_platforms_conductor 32.4.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/bin/nodes_to_deploy +11 -5
  3. data/lib/hybrid_platforms_conductor/deployer.rb +9 -8
  4. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +147 -68
  5. data/lib/hybrid_platforms_conductor/hpc_plugins/platform_handler/platform_handler_plugin.rb.sample +1 -1
  6. data/lib/hybrid_platforms_conductor/hpc_plugins/provisioner/proxmox.rb +1 -1
  7. data/lib/hybrid_platforms_conductor/hpc_plugins/test/deploy_freshness.rb +1 -1
  8. data/lib/hybrid_platforms_conductor/hpc_plugins/test/file_system.rb +1 -1
  9. data/lib/hybrid_platforms_conductor/hpc_plugins/test/hostname.rb +1 -1
  10. data/lib/hybrid_platforms_conductor/hpc_plugins/test/ip.rb +1 -1
  11. data/lib/hybrid_platforms_conductor/hpc_plugins/test/local_users.rb +1 -1
  12. data/lib/hybrid_platforms_conductor/hpc_plugins/test/mounts.rb +1 -1
  13. data/lib/hybrid_platforms_conductor/hpc_plugins/test/orphan_files.rb +1 -1
  14. data/lib/hybrid_platforms_conductor/hpc_plugins/test/spectre.rb +6 -7
  15. data/lib/hybrid_platforms_conductor/hpc_plugins/test/vulnerabilities.rb +7 -6
  16. data/lib/hybrid_platforms_conductor/nodes_handler.rb +45 -1
  17. data/lib/hybrid_platforms_conductor/services_handler.rb +9 -13
  18. data/lib/hybrid_platforms_conductor/version.rb +1 -1
  19. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/config_dsl_spec.rb +35 -0
  20. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/connections_spec.rb +57 -2
  21. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/global_helpers_spec.rb +68 -12
  22. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/node_helpers_spec.rb +1 -1
  23. data/spec/hybrid_platforms_conductor_test/api/actions_executor/connectors/ssh/remote_actions_spec.rb +47 -9
  24. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/common_spec.rb +28 -0
  25. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/config_dsl_spec.rb +71 -0
  26. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/git_diff_impacts_spec.rb +10 -0
  27. data/spec/hybrid_platforms_conductor_test/executables/nodes_to_deploy_spec.rb +25 -0
  28. data/spec/hybrid_platforms_conductor_test/helpers/cmd_runner_helpers.rb +1 -5
  29. data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +37 -9
  30. data/spec/hybrid_platforms_conductor_test/helpers/deployer_helpers.rb +14 -14
  31. data/spec/hybrid_platforms_conductor_test/helpers/deployer_test_helpers.rb +70 -11
  32. data/spec/hybrid_platforms_conductor_test/helpers/platforms_handler_helpers.rb +1 -1
  33. data/spec/hybrid_platforms_conductor_test/helpers/provisioner_proxmox_helpers.rb +2 -2
  34. metadata +12 -11
@@ -13,7 +13,7 @@ describe HybridPlatformsConductor::ActionsExecutor do
13
13
 
14
14
  it 'provides an SSH URL that can be used by other processes to connect to this node' do
15
15
  with_test_platform_for_remote_testing do
16
- expect(test_connector.ssh_url).to eq 'test_user@hpc.node'
16
+ expect(test_connector.ssh_url).to eq 'hpc.node'
17
17
  end
18
18
  end
19
19
 
@@ -6,7 +6,7 @@ describe HybridPlatformsConductor::ActionsExecutor do
6
6
 
7
7
  it 'executes bash commands remotely' do
8
8
  with_test_platform_for_remote_testing(
9
- expected_cmds: [[/.+\/ssh test_user@hpc\.node \/bin\/bash <<'EOF'\nbash_cmd.bash\nEOF/, proc { [0, 'Bash commands executed on node', ''] }]],
9
+ expected_cmds: [[/.+\/ssh hpc\.node \/bin\/bash <<'EOF'\nbash_cmd.bash\nEOF/, proc { [0, 'Bash commands executed on node', ''] }]],
10
10
  expected_stdout: 'Bash commands executed on node'
11
11
  ) do
12
12
  test_connector.remote_bash('bash_cmd.bash')
@@ -17,7 +17,7 @@ describe HybridPlatformsConductor::ActionsExecutor do
17
17
  with_test_platform_for_remote_testing(
18
18
  expected_cmds: [
19
19
  [
20
- /.+\/ssh test_user@hpc\.node \/bin\/bash <<'EOF'\nbash_cmd.bash\nEOF/,
20
+ /.+\/ssh hpc\.node \/bin\/bash <<'EOF'\nbash_cmd.bash\nEOF/,
21
21
  proc 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|
22
22
  expect(timeout).to eq 5
23
23
  [0, '', '']
@@ -33,7 +33,7 @@ describe HybridPlatformsConductor::ActionsExecutor do
33
33
  it 'executes interactive commands remotely' do
34
34
  with_test_platform_for_remote_testing do
35
35
  expect(test_connector).to receive(:system) do |cmd|
36
- expect(cmd).to match /^.+\/ssh test_user@hpc\.node$/
36
+ expect(cmd).to match /^.+\/ssh hpc\.node$/
37
37
  end
38
38
  test_connector.remote_interactive
39
39
  end
@@ -43,7 +43,7 @@ describe HybridPlatformsConductor::ActionsExecutor do
43
43
  with_test_platform_for_remote_testing(
44
44
  expected_cmds: [
45
45
  [
46
- /cd \/path\/to && tar\s+--create\s+--gzip\s+--file -\s+src.file \| \/.+\/ssh\s+test_user@hpc\.node\s+"tar\s+--extract\s+--gunzip\s+--file -\s+--directory \/remote_path\/to\/dst.dir\s+--owner root\s+"/,
46
+ /cd \/path\/to && tar\s+--create\s+--gzip\s+--file -\s+src.file \| \/.+\/ssh\s+hpc\.node\s+"tar\s+--extract\s+--gunzip\s+--file -\s+--directory \/remote_path\/to\/dst.dir\s+--owner root\s+"/,
47
47
  proc { [0, '', ''] }
48
48
  ]
49
49
  ]
@@ -56,7 +56,7 @@ describe HybridPlatformsConductor::ActionsExecutor do
56
56
  with_test_platform_for_remote_testing(
57
57
  expected_cmds: [
58
58
  [
59
- /cd \/path\/to && tar\s+--create\s+--gzip\s+--file -\s+src.file \| \/.+\/ssh\s+test_user@hpc\.node\s+"tar\s+--extract\s+--gunzip\s+--file -\s+--directory \/remote_path\/to\/dst.dir\s+--owner root\s+"/,
59
+ /cd \/path\/to && tar\s+--create\s+--gzip\s+--file -\s+src.file \| \/.+\/ssh\s+hpc\.node\s+"tar\s+--extract\s+--gunzip\s+--file -\s+--directory \/remote_path\/to\/dst.dir\s+--owner root\s+"/,
60
60
  proc 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|
61
61
  expect(timeout).to eq 5
62
62
  [0, '', '']
@@ -76,7 +76,7 @@ describe HybridPlatformsConductor::ActionsExecutor do
76
76
  [
77
77
  /.+\/hpc_temp_cmds_.+\.sh$/,
78
78
  proc do |received_cmd|
79
- expect(File.read(received_cmd)).to match /.+\/ssh test_user@hpc\.node \/bin\/bash <<'EOF'\n#{Regexp.escape(cmd)}\nEOF/
79
+ expect(File.read(received_cmd)).to match /.+\/ssh hpc\.node \/bin\/bash <<'EOF'\n#{Regexp.escape(cmd)}\nEOF/
80
80
  [0, 'Bash commands executed on node', '']
81
81
  end
82
82
  ]
@@ -92,7 +92,7 @@ describe HybridPlatformsConductor::ActionsExecutor do
92
92
  with_test_platform_for_remote_testing(
93
93
  expected_cmds: [
94
94
  [
95
- /cd \/path\/to && tar\s+--create\s+--gzip\s+--file -\s+src.file \| \/.+\/ssh\s+test_user@hpc\.node\s+"sudo tar\s+--extract\s+--gunzip\s+--file -\s+--directory \/remote_path\/to\/dst.dir\s+--owner root\s+"/,
95
+ /cd \/path\/to && tar\s+--create\s+--gzip\s+--file -\s+src.file \| \/.+\/ssh\s+hpc\.node\s+"sudo -u root tar\s+--extract\s+--gunzip\s+--file -\s+--directory \/remote_path\/to\/dst.dir\s+--owner root\s+"/,
96
96
  proc { [0, '', ''] }
97
97
  ]
98
98
  ]
@@ -101,11 +101,25 @@ describe HybridPlatformsConductor::ActionsExecutor do
101
101
  end
102
102
  end
103
103
 
104
+ it 'copies files remotely with a different sudo' do
105
+ with_test_platform_for_remote_testing(
106
+ expected_cmds: [
107
+ [
108
+ /cd \/path\/to && tar\s+--create\s+--gzip\s+--file -\s+src.file \| \/.+\/ssh\s+hpc\.node\s+"other_sudo --user root tar\s+--extract\s+--gunzip\s+--file -\s+--directory \/remote_path\/to\/dst.dir\s+--owner root\s+"/,
109
+ proc { [0, '', ''] }
110
+ ]
111
+ ],
112
+ additional_config: 'sudo_for { |user| "other_sudo --user #{user}" }'
113
+ ) do
114
+ test_connector.remote_copy('/path/to/src.file', '/remote_path/to/dst.dir', sudo: true)
115
+ end
116
+ end
117
+
104
118
  it 'copies files remotely with a different owner' do
105
119
  with_test_platform_for_remote_testing(
106
120
  expected_cmds: [
107
121
  [
108
- /cd \/path\/to && tar\s+--create\s+--gzip\s+--file -\s+--owner remote_user\s+src.file \| \/.+\/ssh\s+test_user@hpc\.node\s+"tar\s+--extract\s+--gunzip\s+--file -\s+--directory \/remote_path\/to\/dst.dir\s+--owner root\s+"/,
122
+ /cd \/path\/to && tar\s+--create\s+--gzip\s+--file -\s+--owner remote_user\s+src.file \| \/.+\/ssh\s+hpc\.node\s+"tar\s+--extract\s+--gunzip\s+--file -\s+--directory \/remote_path\/to\/dst.dir\s+--owner root\s+"/,
109
123
  proc { [0, '', ''] }
110
124
  ]
111
125
  ]
@@ -118,7 +132,7 @@ describe HybridPlatformsConductor::ActionsExecutor do
118
132
  with_test_platform_for_remote_testing(
119
133
  expected_cmds: [
120
134
  [
121
- /cd \/path\/to && tar\s+--create\s+--gzip\s+--file -\s+--group remote_group\s+src.file \| \/.+\/ssh\s+test_user@hpc\.node\s+"tar\s+--extract\s+--gunzip\s+--file -\s+--directory \/remote_path\/to\/dst.dir\s+--owner root\s+"/,
135
+ /cd \/path\/to && tar\s+--create\s+--gzip\s+--file -\s+--group remote_group\s+src.file \| \/.+\/ssh\s+hpc\.node\s+"tar\s+--extract\s+--gunzip\s+--file -\s+--directory \/remote_path\/to\/dst.dir\s+--owner root\s+"/,
122
136
  proc { [0, '', ''] }
123
137
  ]
124
138
  ]
@@ -127,6 +141,30 @@ describe HybridPlatformsConductor::ActionsExecutor do
127
141
  end
128
142
  end
129
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
+
130
168
  end
131
169
 
132
170
  end
@@ -124,4 +124,32 @@ describe HybridPlatformsConductor::NodesHandler do
124
124
  end
125
125
  end
126
126
 
127
+ it 'computes the correct sudo for different nodes' do
128
+ with_test_platform(
129
+ {
130
+ nodes: {
131
+ 'node1' => {},
132
+ 'node2' => {},
133
+ 'node3' => {}
134
+ }
135
+ },
136
+ false,
137
+ '
138
+ for_nodes(%w[node1 node2]) do
139
+ sudo_for { |user| "alt_sudo1 -p #{user}" }
140
+ end
141
+ for_nodes(\'node2\') do
142
+ sudo_for { |user| "alt_sudo2 -q #{user}" }
143
+ end
144
+ '
145
+ ) do
146
+ expect(test_nodes_handler.sudo_on('node1')).to eq 'alt_sudo1 -p root'
147
+ expect(test_nodes_handler.sudo_on('node1', 'test_user')).to eq 'alt_sudo1 -p test_user'
148
+ expect(test_nodes_handler.sudo_on('node2')).to eq 'alt_sudo2 -q root'
149
+ expect(test_nodes_handler.sudo_on('node2', 'test_user')).to eq 'alt_sudo2 -q test_user'
150
+ expect(test_nodes_handler.sudo_on('node3')).to eq 'sudo -u root'
151
+ expect(test_nodes_handler.sudo_on('node3', 'test_user')).to eq 'sudo -u test_user'
152
+ end
153
+ end
154
+
127
155
  end
@@ -0,0 +1,71 @@
1
+ describe HybridPlatformsConductor::NodesHandler do
2
+
3
+ context 'checking config DSL' do
4
+
5
+ it 'adds helpers for master cmdbs' do
6
+ with_test_platform(
7
+ {
8
+ nodes: {
9
+ 'node1' => {},
10
+ 'node2' => {},
11
+ 'node3' => {}
12
+ }
13
+ },
14
+ false,
15
+ '
16
+ master_cmdbs(
17
+ test_cmdb: :property1,
18
+ test_cmdb2: :property2
19
+ )
20
+ for_nodes(\'node2\') do
21
+ master_cmdbs(test_cmdb: :property3)
22
+ end
23
+ '
24
+ ) do
25
+ register_test_cmdb(%i[test_cmdb test_cmdb2])
26
+ expect(test_config.cmdb_masters).to eq [
27
+ {
28
+ nodes_selectors_stack: [],
29
+ cmdb_masters: {
30
+ test_cmdb: [:property1],
31
+ test_cmdb2: [:property2]
32
+ }
33
+ },
34
+ {
35
+ nodes_selectors_stack: ['node2'],
36
+ cmdb_masters: {
37
+ test_cmdb: [:property3]
38
+ }
39
+ }
40
+ ]
41
+ end
42
+ end
43
+
44
+ it 'adds helpers for configurable sudo' do
45
+ with_test_platform(
46
+ {
47
+ nodes: {
48
+ 'node1' => {},
49
+ 'node2' => {},
50
+ 'node3' => {}
51
+ }
52
+ },
53
+ false,
54
+ '
55
+ sudo_for { |user| "alt_sudo1 -p #{user}" }
56
+ for_nodes(\'node2\') do
57
+ sudo_for { |user| "alt_sudo2 -q #{user}" }
58
+ end
59
+ '
60
+ ) do
61
+ expect(test_config.sudo_procs.size).to eq 2
62
+ expect(test_config.sudo_procs[0][:nodes_selectors_stack]).to eq []
63
+ expect(test_config.sudo_procs[0][:sudo_proc].call('test_user')).to eq 'alt_sudo1 -p test_user'
64
+ expect(test_config.sudo_procs[1][:nodes_selectors_stack]).to eq ['node2']
65
+ expect(test_config.sudo_procs[1][:sudo_proc].call('test_user')).to eq 'alt_sudo2 -q test_user'
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+ end
@@ -33,6 +33,16 @@ describe HybridPlatformsConductor::NodesHandler do
33
33
  end
34
34
  end
35
35
 
36
+ it 'fails when the commit id is invalid' do
37
+ with_test_platform({}, true) do
38
+ with_cmd_runner_mocked([
39
+ [/cd .+\/my_remote_platform && git --no-pager diff --no-color invalid_id/, proc { raise HybridPlatformsConductor::CmdRunner::UnexpectedExitCodeError, 'Mocked git error due to an invalid commit id' }]
40
+ ]) do
41
+ expect { test_nodes_handler.impacted_nodes_from_git_diff('my_remote_platform', from_commit: 'invalid_id') }.to raise_error HybridPlatformsConductor::NodesHandler::GitError, 'Mocked git error due to an invalid commit id'
42
+ end
43
+ end
44
+ end
45
+
36
46
  it 'diffs to another commit if asked' do
37
47
  with_test_platform({}, true) do
38
48
  with_cmd_runner_mocked([
@@ -106,6 +106,31 @@ describe 'nodes_to_deploy executable' do
106
106
  end
107
107
  end
108
108
 
109
+ it 'considers nodes having invalid commit ids in their logs to be deployed' do
110
+ with_test_platform_for_nodes_to_deploy do
111
+ expect_actions_executor_runs([proc do |actions|
112
+ expect(actions).to eq(
113
+ 'node1' => { remote_bash: "cd /var/log/deployments && ls -t | head -1 | xargs sed '/===== STDOUT =====/q'" },
114
+ 'node2' => { remote_bash: "cd /var/log/deployments && ls -t | head -1 | xargs sed '/===== STDOUT =====/q'" }
115
+ )
116
+ {
117
+ 'node1' => [0, "repo_name_0: platform\nexit_status: 0\ncommit_id_0: abcdef1", ''],
118
+ 'node2' => [0, "repo_name_0: platform\nexit_status: 0\ncommit_id_0: abcdef2", '']
119
+ }
120
+ end])
121
+ expect(test_nodes_handler).to receive(:impacted_nodes_from_git_diff).with('platform', from_commit: 'abcdef1', to_commit: 'master') do
122
+ raise HybridPlatformsConductor::NodesHandler::GitError, 'Mocked git error due to an invalid commit id'
123
+ end
124
+ expect(test_nodes_handler).to receive(:impacted_nodes_from_git_diff).with('platform', from_commit: 'abcdef2', to_commit: 'master') { [%w[], [], [], false] }
125
+ exit_code, stdout, stderr = run 'nodes_to_deploy'
126
+ expect(exit_code).to eq 0
127
+ expect(stdout).to eq <<~EOS
128
+ ===== Nodes to deploy =====
129
+ node1
130
+ EOS
131
+ end
132
+ end
133
+
109
134
  it 'ignores impacts if asked' do
110
135
  with_test_platform_for_nodes_to_deploy do
111
136
  exit_code, stdout, stderr = run 'nodes_to_deploy', '--ignore-deployed-info'
@@ -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
@@ -10,6 +10,7 @@ module HybridPlatformsConductorTest
10
10
  # Parameters::
11
11
  # * *nodes_connections* (Hash<String, Hash<Symbol,Object> >): Nodes' connections info, per node name:
12
12
  # * *connection* (String): Connection string (fqdn, IP...) used by SSH
13
+ # * *ip* (String): IP used by SSH (can be different from connection in case of transformed SSH) [default: connection]
13
14
  # * *user* (String): User used by SSH
14
15
  # * *times* (Integer): Number of times this connection should be used [default: 1]
15
16
  # * *control_master_create_error* (String or nil): Error to simulate during the SSH ControlMaster creation, or nil for none [default: nil]
@@ -18,6 +19,7 @@ module HybridPlatformsConductorTest
18
19
  # * *with_control_master_destroy* (Boolean): Do we destroy the control master? [default: true]
19
20
  # * *with_strict_host_key_checking* (Boolean): Do we use strict host key checking? [default: true]
20
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]
21
23
  # Result::
22
24
  # * Array< [String or Regexp, Proc] >: The expected commands that should be used, and their corresponding mocked code
23
25
  def ssh_expected_commands_for(
@@ -26,23 +28,31 @@ module HybridPlatformsConductorTest
26
28
  with_control_master_check: false,
27
29
  with_control_master_destroy: true,
28
30
  with_strict_host_key_checking: true,
29
- with_batch_mode: true
31
+ with_batch_mode: true,
32
+ with_session_exec: true
30
33
  )
31
34
  nodes_connections.map do |node, node_connection_info|
32
35
  node_connection_info[:times] = 1 unless node_connection_info.key?(:times)
33
36
  ssh_commands_once = []
34
37
  ssh_commands_per_connection = []
35
38
  if with_strict_host_key_checking
39
+ ip = node_connection_info[:ip] || node_connection_info[:connection]
36
40
  ssh_commands_once.concat([
37
41
  [
38
- "ssh-keyscan #{node_connection_info[:connection]}",
39
- proc { [0, "#{node_connection_info[:connection]} ssh-rsa fake_host_key_for_#{node_connection_info[:connection]}", ''] }
42
+ "ssh-keyscan #{ip}",
43
+ proc { [0, "#{ip} ssh-rsa fake_host_key_for_#{ip}", ''] }
40
44
  ]
41
45
  ])
42
46
  end
43
47
  if with_control_master_create
44
48
  ssh_commands_per_connection << [
45
- /^.+\/ssh #{with_batch_mode ? '-o BatchMode=yes ' : ''}-o ControlMaster=yes -o ControlPersist=yes #{Regexp.escape(node_connection_info[:user])}@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,
46
56
  proc do
47
57
  control_file = test_actions_executor.connector(:ssh).send(:control_master_file, node_connection_info[:connection], '22', node_connection_info[:user])
48
58
  # Fail if the ControlMaster file already exists, as would SSH do if the file is stalled
@@ -51,6 +61,9 @@ module HybridPlatformsConductorTest
51
61
  elsif node_connection_info[:control_master_create_error].nil?
52
62
  # Really touch a fake control file, as ssh connector checks for its existence
53
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
54
67
  [0, '', '']
55
68
  else
56
69
  [255, '', node_connection_info[:control_master_create_error]]
@@ -60,13 +73,13 @@ module HybridPlatformsConductorTest
60
73
  end
61
74
  if with_control_master_check
62
75
  ssh_commands_per_connection << [
63
- /^.+\/ssh -O check #{Regexp.escape(node_connection_info[:user])}@hpc\.#{Regexp.escape(node)}$/,
76
+ /^.+\/ssh -O check hpc\.#{Regexp.escape(node)}$/,
64
77
  proc { [0, '', ''] }
65
78
  ]
66
79
  end
67
80
  if with_control_master_destroy
68
81
  ssh_commands_per_connection << [
69
- /^.+\/ssh -O exit #{Regexp.escape(node_connection_info[:user])}@hpc\.#{Regexp.escape(node)} 2>&1 \| grep -v 'Exit request sent\.'$/,
82
+ /^.+\/ssh -O exit hpc\.#{Regexp.escape(node)} 2>&1 \| grep -v 'Exit request sent\.'$/,
70
83
  proc do
71
84
  # Really mock the control file deletion
72
85
  File.unlink(test_actions_executor.connector(:ssh).send(:control_master_file, node_connection_info[:connection], '22', node_connection_info[:user]))
@@ -94,9 +107,23 @@ module HybridPlatformsConductorTest
94
107
  # * *expected_stderr* (String): Expected stderr after client code execution [default: '']
95
108
  # * *timeout* (Integer or nil): Timeout to prepare the connector for [default: nil]
96
109
  # * *password* (String or nil): Password to set for the node, or nil for none [default: nil]
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]
97
112
  # * Proc: Client code to execute testing
98
- def with_test_platform_for_remote_testing(expected_cmds: [], expected_stdout: '', expected_stderr: '', timeout: nil, password: nil)
99
- with_test_platform(nodes: { 'node' => { meta: { host_ip: '192.168.42.42' } } }) do
113
+ def with_test_platform_for_remote_testing(
114
+ expected_cmds: [],
115
+ expected_stdout: '',
116
+ expected_stderr: '',
117
+ timeout: nil,
118
+ password: nil,
119
+ additional_config: '',
120
+ session_exec: true
121
+ )
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
100
127
  with_cmd_runner_mocked(
101
128
  [
102
129
  ['which env', proc { [0, "/usr/bin/env\n", ''] }],
@@ -105,7 +132,8 @@ module HybridPlatformsConductorTest
105
132
  (password ? [['sshpass -V', proc { [0, "sshpass 1.06\n", ''] }]] : []) +
106
133
  ssh_expected_commands_for(
107
134
  { 'node' => { connection: '192.168.42.42', user: 'test_user' } },
108
- with_batch_mode: password.nil?
135
+ with_batch_mode: password.nil?,
136
+ with_session_exec: session_exec
109
137
  ) +
110
138
  expected_cmds
111
139
  ) do
@@ -9,12 +9,12 @@ module HybridPlatformsConductorTest
9
9
  # Parameters::
10
10
  # * *action* (Hash<Symbol,Object>): The action to check
11
11
  # * *node* (String): The concerned node
12
- # * *sudo* (Boolean): Is sudo supposed to be used? [default: true]
13
- def expect_action_to_lock_node(action, node, sudo: true)
12
+ # * *sudo* (String or nil): sudo supposed to be used, or nil if none [default: 'sudo -u root']
13
+ def expect_action_to_lock_node(action, node, sudo: 'sudo -u root')
14
14
  expect(action[:scp].size).to eq 1
15
15
  expect(action[:scp].first[0]).to match /^.+\/mutex_dir$/
16
16
  expect(action[:scp].first[1]).to eq '.'
17
- expect(action[:remote_bash]).to eq "while ! #{sudo ? 'sudo ' : ''}./mutex_dir lock /tmp/hybrid_platforms_conductor_deploy_lock \"$(ps -o ppid= -p $$)\"; do echo -e 'Another deployment is running on #{node}. Waiting for it to finish to continue...' ; sleep 5 ; done"
17
+ expect(action[:remote_bash]).to eq "while ! #{sudo ? "#{sudo} " : ''}./mutex_dir lock /tmp/hybrid_platforms_conductor_deploy_lock \"$(ps -o ppid= -p $$)\"; do echo -e 'Another deployment is running on #{node}. Waiting for it to finish to continue...' ; sleep 5 ; done"
18
18
  end
19
19
 
20
20
  # Expect a given action to be releasing the mutex on a given node
@@ -22,9 +22,9 @@ module HybridPlatformsConductorTest
22
22
  # Parameters::
23
23
  # * *action* (Hash<Symbol,Object>): The action to check
24
24
  # * *node* (String): The concerned node
25
- # * *sudo* (Boolean): Is sudo supposed to be used? [default: true]
26
- def expect_action_to_unlock_node(action, node, sudo: true)
27
- expect(action).to eq(remote_bash: "#{sudo ? 'sudo ' : ''}./mutex_dir unlock /tmp/hybrid_platforms_conductor_deploy_lock")
25
+ # * *sudo* (String or nil): sudo supposed to be used, or nil if none [default: 'sudo -u root']
26
+ def expect_action_to_unlock_node(action, node, sudo: 'sudo -u root')
27
+ expect(action).to eq(remote_bash: "#{sudo ? "#{sudo} " : ''}./mutex_dir unlock /tmp/hybrid_platforms_conductor_deploy_lock")
28
28
  end
29
29
 
30
30
  # Expect a given set of actions to be a deployment
@@ -33,12 +33,12 @@ module HybridPlatformsConductorTest
33
33
  # * *actions* (Object): Actions
34
34
  # * *nodes* (String or Array<String>): Node (or list of nodes) that should be checked
35
35
  # * *check* (Boolean): Is the deploy only a check? [default: false]
36
- # * *sudo* (Boolean): Is sudo supposed to be used? [default: true]
36
+ # * *sudo* (String or nil): sudo supposed to be used, or nil if none [default: 'sudo -u root']
37
37
  # * *expected_actions* (Array<Object>): Additional expected actions [default: []]
38
38
  # * *mocked_result* (Hash<String, [Object, String, String]>): Expected result of the actions, per node, or nil for success [default: nil]
39
39
  # Result::
40
40
  # * Hash<String, [Integer or Symbol, String, String] >: Expected result of those expected actions
41
- def expect_actions_to_deploy_on(actions, nodes, check: false, sudo: true, expected_actions: [], mocked_result: nil)
41
+ def expect_actions_to_deploy_on(actions, nodes, check: false, sudo: 'sudo -u root', expected_actions: [], mocked_result: nil)
42
42
  nodes = [nodes] if nodes.is_a?(String)
43
43
  mocked_result = Hash[nodes.map { |node| [node, [0, "#{check ? 'Check' : 'Deploy'} successful", '']] }] if mocked_result.nil?
44
44
  expect(actions.size).to eq nodes.size
@@ -57,8 +57,8 @@ module HybridPlatformsConductorTest
57
57
  # Parameters::
58
58
  # * *actions* (Object): Actions
59
59
  # * *nodes* (String or Array<String>): Node (or list of nodes) that should be checked
60
- # * *sudo* (Boolean): Is sudo supposed to be used? [default: true]
61
- def expect_actions_to_unlock(actions, nodes, sudo: true)
60
+ # * *sudo* (String or nil): sudo supposed to be used, or nil if none [default: 'sudo -u root']
61
+ def expect_actions_to_unlock(actions, nodes, sudo: 'sudo -u root')
62
62
  nodes = [nodes] if nodes.is_a?(String)
63
63
  expect(actions.size).to eq nodes.size
64
64
  nodes.each do |node|
@@ -73,17 +73,17 @@ module HybridPlatformsConductorTest
73
73
  # Parameters::
74
74
  # * *actions* (Object): Actions
75
75
  # * *nodes* (String or Array<String>): Node (or list of nodes) that should be checked
76
- # * *sudo* (Boolean): Is sudo supposed to be used? [default: true]
77
- def expect_actions_to_upload_logs(actions, nodes, sudo: true)
76
+ # * *sudo* (String or nil): sudo supposed to be used, or nil if none [default: 'sudo -u root']
77
+ def expect_actions_to_upload_logs(actions, nodes, sudo: 'sudo -u root')
78
78
  nodes = [nodes] if nodes.is_a?(String)
79
79
  expect(actions.size).to eq nodes.size
80
80
  nodes.each do |node|
81
81
  expect(actions.key?(node)).to eq true
82
- expect(actions[node][:remote_bash]).to eq "#{sudo ? 'sudo ' : ''}mkdir -p /var/log/deployments"
82
+ expect(actions[node][:remote_bash]).to eq "#{sudo ? "#{sudo} " : ''}mkdir -p /var/log/deployments"
83
83
  expect(actions[node][:scp].first[1]).to eq '/var/log/deployments'
84
84
  expect(actions[node][:scp][:group]).to eq 'root'
85
85
  expect(actions[node][:scp][:owner]).to eq 'root'
86
- expect(actions[node][:scp][:sudo]).to eq sudo
86
+ expect(actions[node][:scp][:sudo]).to eq (!sudo.nil?)
87
87
  end
88
88
  Hash[nodes.map { |node| [node, [0, 'Logs uploaded', '']] }]
89
89
  end