hybrid_platforms_conductor 32.3.6 → 32.6.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 +33 -12
  4. data/lib/hybrid_platforms_conductor/hpc_plugins/connector/ssh.rb +82 -28
  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 +41 -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 +23 -9
  24. data/spec/hybrid_platforms_conductor_test/api/deployer/config_dsl_spec.rb +15 -0
  25. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/common_spec.rb +28 -0
  26. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/config_dsl_spec.rb +71 -0
  27. data/spec/hybrid_platforms_conductor_test/api/nodes_handler/git_diff_impacts_spec.rb +10 -0
  28. data/spec/hybrid_platforms_conductor_test/executables/nodes_to_deploy_spec.rb +25 -0
  29. data/spec/hybrid_platforms_conductor_test/helpers/connector_ssh_helpers.rb +17 -7
  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 +137 -33
  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 +13 -11
@@ -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
  ]
@@ -0,0 +1,15 @@
1
+ describe HybridPlatformsConductor::Deployer do
2
+
3
+ context 'checking deployer specific config DSL' do
4
+
5
+ it 'declares a packaging timeout' do
6
+ with_repository do |repository|
7
+ with_platforms('packaging_timeout 666') do
8
+ expect(test_config.packaging_timeout_secs).to eq 666
9
+ end
10
+ end
11
+ end
12
+
13
+ end
14
+
15
+ 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'
@@ -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]
@@ -33,16 +34,17 @@ module HybridPlatformsConductorTest
33
34
  ssh_commands_once = []
34
35
  ssh_commands_per_connection = []
35
36
  if with_strict_host_key_checking
37
+ ip = node_connection_info[:ip] || node_connection_info[:connection]
36
38
  ssh_commands_once.concat([
37
39
  [
38
- "ssh-keyscan #{node_connection_info[:connection]}",
39
- proc { [0, "#{node_connection_info[:connection]} ssh-rsa fake_host_key_for_#{node_connection_info[:connection]}", ''] }
40
+ "ssh-keyscan #{ip}",
41
+ proc { [0, "#{ip} ssh-rsa fake_host_key_for_#{ip}", ''] }
40
42
  ]
41
43
  ])
42
44
  end
43
45
  if with_control_master_create
44
46
  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$/,
47
+ /^.+\/ssh #{with_batch_mode ? '-o BatchMode=yes ' : ''}-o ControlMaster=yes -o ControlPersist=yes hpc\.#{Regexp.escape(node)} true$/,
46
48
  proc do
47
49
  control_file = test_actions_executor.connector(:ssh).send(:control_master_file, node_connection_info[:connection], '22', node_connection_info[:user])
48
50
  # Fail if the ControlMaster file already exists, as would SSH do if the file is stalled
@@ -60,13 +62,13 @@ module HybridPlatformsConductorTest
60
62
  end
61
63
  if with_control_master_check
62
64
  ssh_commands_per_connection << [
63
- /^.+\/ssh -O check #{Regexp.escape(node_connection_info[:user])}@hpc\.#{Regexp.escape(node)}$/,
65
+ /^.+\/ssh -O check hpc\.#{Regexp.escape(node)}$/,
64
66
  proc { [0, '', ''] }
65
67
  ]
66
68
  end
67
69
  if with_control_master_destroy
68
70
  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\.'$/,
71
+ /^.+\/ssh -O exit hpc\.#{Regexp.escape(node)} 2>&1 \| grep -v 'Exit request sent\.'$/,
70
72
  proc do
71
73
  # Really mock the control file deletion
72
74
  File.unlink(test_actions_executor.connector(:ssh).send(:control_master_file, node_connection_info[:connection], '22', node_connection_info[:user]))
@@ -94,9 +96,17 @@ module HybridPlatformsConductorTest
94
96
  # * *expected_stderr* (String): Expected stderr after client code execution [default: '']
95
97
  # * *timeout* (Integer or nil): Timeout to prepare the connector for [default: nil]
96
98
  # * *password* (String or nil): Password to set for the node, or nil for none [default: nil]
99
+ # * *additional_config* (String): Additional config [default: '']
97
100
  # * 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
101
+ def with_test_platform_for_remote_testing(
102
+ expected_cmds: [],
103
+ expected_stdout: '',
104
+ expected_stderr: '',
105
+ timeout: nil,
106
+ password: nil,
107
+ additional_config: ''
108
+ )
109
+ with_test_platform({ nodes: { 'node' => { meta: { host_ip: '192.168.42.42' } } } }, false, additional_config) do
100
110
  with_cmd_runner_mocked(
101
111
  [
102
112
  ['which env', proc { [0, "/usr/bin/env\n", ''] }],
@@ -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
@@ -1,3 +1,5 @@
1
+ require 'timeout'
2
+
1
3
  module HybridPlatformsConductorTest
2
4
 
3
5
  module Helpers
@@ -18,7 +20,7 @@ module HybridPlatformsConductorTest
18
20
  #
19
21
  # Parameters::
20
22
  # * *services* (Hash<String, Array<String> >): Expected nodes that should be deployed, with their corresponding services [default: { 'node' => %w[service] }]
21
- # * *sudo* (Boolean): Do we expect sudo to be used in commands? [default: true]
23
+ # * *sudo* (String or nil): sudo supposed to be used, or nil if none [default: 'sudo -u root']
22
24
  # * *check_mode* (Boolean): Are we testing in check mode? [default: @check_mode]
23
25
  # * *mocked_deploy_result* (Hash or nil): Mocked result of the deployment actions, or nil to use the helper's default [default: nil]
24
26
  # * *additional_expected_actions* (Array): Additional expected actions [default: []]
@@ -26,7 +28,7 @@ module HybridPlatformsConductorTest
26
28
  # * *expect_actions_timeout* (Integer or nil): Expected timeout in actions, or nil for none [default: nil]
27
29
  def expected_actions_for_deploy_on(
28
30
  services: { 'node' => %w[service] },
29
- sudo: true,
31
+ sudo: 'sudo -u root',
30
32
  check_mode: @check_mode,
31
33
  mocked_deploy_result: nil,
32
34
  additional_expected_actions: [],
@@ -76,8 +78,11 @@ module HybridPlatformsConductorTest
76
78
  #
77
79
  # Parameters::
78
80
  # * *nodes_info* (Hash): Node info to give the platform [default: 1 node having 1 service]
81
+ # * *expect_package* (Boolean): Should we expect packaging? [default: true]
82
+ # * *expect_prepare_for_deploy* (Boolean): Should we expect calls to prepare for deploy? [default: true]
83
+ # * *expect_connections_to_nodes* (Boolean): Should we expect connections to nodes? [default: true]
79
84
  # * *expect_default_actions* (Boolean): Should we expect default actions? [default: true]
80
- # * *expect_sudo* (Boolean): Do we expect sudo to be used in commands? [default: true]
85
+ # * *expect_sudo* (String or nil): Expected sudo command, or nil if none [default: 'sudo -u root']
81
86
  # * *expect_secrets* (Hash): Secrets to be expected during deployment [default: {}]
82
87
  # * *expect_local_environment* (Boolean): Expected local environment flag [default: false]
83
88
  # * *expect_additional_actions* (Array): Additional expected actions [default: []]
@@ -90,8 +95,11 @@ module HybridPlatformsConductorTest
90
95
  # * *repository* (String): Path to the repository
91
96
  def with_platform_to_deploy(
92
97
  nodes_info: { nodes: { 'node' => { services: %w[service] } } },
98
+ expect_package: true,
99
+ expect_prepare_for_deploy: true,
100
+ expect_connections_to_nodes: true,
93
101
  expect_default_actions: true,
94
- expect_sudo: true,
102
+ expect_sudo: 'sudo -u root',
95
103
  expect_secrets: {},
96
104
  expect_local_environment: false,
97
105
  expect_additional_actions: [],
@@ -102,40 +110,52 @@ module HybridPlatformsConductorTest
102
110
  )
103
111
  platform_name = check_mode ? 'platform' : 'my_remote_platform'
104
112
  with_test_platform(nodes_info, !check_mode, additional_config) do |repository|
105
- with_connections_mocked_on(nodes_info[:nodes].keys) do
106
- # Mock the ServicesHandler accesses
107
- expect_services_to_deploy = Hash[nodes_info[:nodes].map do |node, node_info|
108
- [node, node_info[:services]]
109
- end]
110
- unless check_mode
111
- expect(test_services_handler).to receive(:deploy_allowed?).with(
112
- services: expect_services_to_deploy,
113
- secrets: expect_secrets,
114
- local_environment: expect_local_environment
115
- ) do
116
- nil
117
- end
113
+ # Mock the ServicesHandler accesses
114
+ expect_services_to_deploy = Hash[nodes_info[:nodes].map do |node, node_info|
115
+ [node, node_info[:services]]
116
+ end]
117
+ unless check_mode
118
+ expect(test_services_handler).to receive(:deploy_allowed?).with(
119
+ services: expect_services_to_deploy,
120
+ secrets: expect_secrets,
121
+ local_environment: expect_local_environment
122
+ ) do
123
+ nil
118
124
  end
125
+ end
126
+ if expect_package
119
127
  expect(test_services_handler).to receive(:package).with(
120
128
  services: expect_services_to_deploy,
121
129
  secrets: expect_secrets,
122
130
  local_environment: expect_local_environment
123
131
  )
132
+ else
133
+ expect(test_services_handler).not_to receive(:package)
134
+ end
135
+ if expect_prepare_for_deploy
124
136
  expect(test_services_handler).to receive(:prepare_for_deploy).with(
125
137
  services: expect_services_to_deploy,
126
138
  secrets: expect_secrets,
127
139
  local_environment: expect_local_environment,
128
140
  why_run: check_mode
129
141
  )
130
- expect_actions_executor_runs(expected_actions_for_deploy_on(
131
- services: expect_services_to_deploy,
132
- check_mode: check_mode,
133
- sudo: expect_sudo,
134
- additional_expected_actions: expect_additional_actions,
135
- expect_concurrent_actions: expect_concurrent_actions,
136
- expect_actions_timeout: expect_actions_timeout
137
- )) if expect_default_actions
138
- test_deployer.use_why_run = true if check_mode
142
+ else
143
+ expect(test_services_handler).not_to receive(:prepare_for_deploy)
144
+ end
145
+ test_deployer.use_why_run = true if check_mode
146
+ if expect_connections_to_nodes
147
+ with_connections_mocked_on(nodes_info[:nodes].keys) do
148
+ expect_actions_executor_runs(expected_actions_for_deploy_on(
149
+ services: expect_services_to_deploy,
150
+ check_mode: check_mode,
151
+ sudo: expect_sudo,
152
+ additional_expected_actions: expect_additional_actions,
153
+ expect_concurrent_actions: expect_concurrent_actions,
154
+ expect_actions_timeout: expect_actions_timeout
155
+ )) if expect_default_actions
156
+ yield repository
157
+ end
158
+ else
139
159
  yield repository
140
160
  end
141
161
  end
@@ -181,12 +201,21 @@ module HybridPlatformsConductorTest
181
201
  end
182
202
 
183
203
  it 'deploys on 1 node using root' do
184
- with_platform_to_deploy(expect_sudo: false) do
204
+ with_platform_to_deploy(expect_sudo: nil) do
185
205
  test_actions_executor.connector(:ssh).ssh_user = 'root'
186
206
  expect(test_deployer.deploy_on('node')).to eq('node' => expected_deploy_result)
187
207
  end
188
208
  end
189
209
 
210
+ it 'deploys on 1 node using an alternate sudo' do
211
+ with_platform_to_deploy(
212
+ expect_sudo: 'other_sudo --user root',
213
+ additional_config: 'sudo_for { |user| "other_sudo --user #{user}" }'
214
+ ) do
215
+ expect(test_deployer.deploy_on('node')).to eq('node' => expected_deploy_result)
216
+ end
217
+ end
218
+
190
219
  it 'deploys on 1 node using 1 secret' do
191
220
  with_platform_to_deploy(expect_secrets: { 'secret1' => 'password1' }) do
192
221
  test_deployer.secrets = [{ 'secret1' => 'password1' }]
@@ -207,9 +236,9 @@ module HybridPlatformsConductorTest
207
236
  nodes_info: { nodes: { 'node' => { meta: { image: 'debian_9' }, services: %w[service] } } },
208
237
  expect_local_environment: true,
209
238
  expect_additional_actions: [
210
- { remote_bash: 'sudo apt update && sudo apt install -y ca-certificates' },
239
+ { remote_bash: 'sudo -u root apt update && sudo -u root apt install -y ca-certificates' },
211
240
  {
212
- remote_bash: 'sudo update-ca-certificates',
241
+ remote_bash: 'sudo -u root update-ca-certificates',
213
242
  scp: {
214
243
  certs_dir => '/usr/local/share/ca-certificates',
215
244
  :sudo => true
@@ -224,6 +253,31 @@ module HybridPlatformsConductorTest
224
253
  end
225
254
  end
226
255
 
256
+ it 'deploys on 1 node in local environment with certificates to install using hpc_certificates on Debian and an alternate sudo' do
257
+ with_certs_dir do |certs_dir|
258
+ with_platform_to_deploy(
259
+ nodes_info: { nodes: { 'node' => { meta: { image: 'debian_9' }, services: %w[service] } } },
260
+ expect_sudo: 'other_sudo --user root',
261
+ expect_local_environment: true,
262
+ expect_additional_actions: [
263
+ { remote_bash: 'other_sudo --user root apt update && other_sudo --user root apt install -y ca-certificates' },
264
+ {
265
+ remote_bash: 'other_sudo --user root update-ca-certificates',
266
+ scp: {
267
+ certs_dir => '/usr/local/share/ca-certificates',
268
+ :sudo => true
269
+ }
270
+ }
271
+ ],
272
+ additional_config: 'sudo_for { |user| "other_sudo --user #{user}" }'
273
+ ) do
274
+ ENV['hpc_certificates'] = certs_dir
275
+ test_deployer.local_environment = true
276
+ expect(test_deployer.deploy_on('node')).to eq('node' => expected_deploy_result)
277
+ end
278
+ end
279
+ end
280
+
227
281
  it 'deploys on 1 node with certificates to install using hpc_certificates on Debian but ignores them in non-local environment' do
228
282
  with_certs_dir do |certs_dir|
229
283
  with_platform_to_deploy(nodes_info: { nodes: { 'node' => { meta: { image: 'debian_9' }, services: %w[service] } } }) do
@@ -237,7 +291,7 @@ module HybridPlatformsConductorTest
237
291
  with_certs_dir do |certs_dir|
238
292
  with_platform_to_deploy(
239
293
  nodes_info: { nodes: { 'node' => { meta: { image: 'debian_9' }, services: %w[service] } } },
240
- expect_sudo: false,
294
+ expect_sudo: nil,
241
295
  expect_local_environment: true,
242
296
  expect_additional_actions: [
243
297
  { remote_bash: 'apt update && apt install -y ca-certificates' },
@@ -264,9 +318,9 @@ module HybridPlatformsConductorTest
264
318
  nodes_info: { nodes: { 'node' => { meta: { image: 'centos_7' }, services: %w[service] } } },
265
319
  expect_local_environment: true,
266
320
  expect_additional_actions: [
267
- { remote_bash: 'sudo yum install -y ca-certificates' },
321
+ { remote_bash: 'sudo -u root yum install -y ca-certificates' },
268
322
  {
269
- remote_bash: ['sudo update-ca-trust enable', 'sudo update-ca-trust extract'],
323
+ remote_bash: ['sudo -u root update-ca-trust enable', 'sudo -u root update-ca-trust extract'],
270
324
  scp: {
271
325
  "#{certs_dir}/test_cert.crt" => '/etc/pki/ca-trust/source/anchors',
272
326
  :sudo => true
@@ -281,11 +335,36 @@ module HybridPlatformsConductorTest
281
335
  end
282
336
  end
283
337
 
338
+ it 'deploys on 1 node with certificates to install using hpc_certificates on CentOS and an alternate sudo' do
339
+ with_certs_dir do |certs_dir|
340
+ with_platform_to_deploy(
341
+ nodes_info: { nodes: { 'node' => { meta: { image: 'centos_7' }, services: %w[service] } } },
342
+ expect_sudo: 'other_sudo --user root',
343
+ expect_local_environment: true,
344
+ expect_additional_actions: [
345
+ { remote_bash: 'other_sudo --user root yum install -y ca-certificates' },
346
+ {
347
+ remote_bash: ['other_sudo --user root update-ca-trust enable', 'other_sudo --user root update-ca-trust extract'],
348
+ scp: {
349
+ "#{certs_dir}/test_cert.crt" => '/etc/pki/ca-trust/source/anchors',
350
+ :sudo => true
351
+ }
352
+ }
353
+ ],
354
+ additional_config: 'sudo_for { |user| "other_sudo --user #{user}" }'
355
+ ) do
356
+ ENV['hpc_certificates'] = certs_dir
357
+ test_deployer.local_environment = true
358
+ expect(test_deployer.deploy_on('node')).to eq('node' => expected_deploy_result)
359
+ end
360
+ end
361
+ end
362
+
284
363
  it 'deploys on 1 node with certificates to install using hpc_certificates on CentOS using root' do
285
364
  with_certs_dir do |certs_dir|
286
365
  with_platform_to_deploy(
287
366
  nodes_info: { nodes: { 'node' => { meta: { image: 'centos_7' }, services: %w[service] } } },
288
- expect_sudo: false,
367
+ expect_sudo: nil,
289
368
  expect_local_environment: true,
290
369
  expect_additional_actions: [
291
370
  { remote_bash: 'yum install -y ca-certificates' },
@@ -360,6 +439,31 @@ module HybridPlatformsConductorTest
360
439
  end
361
440
  end
362
441
 
442
+ it 'fails when packaging timeout has been reached while taking the futex' do
443
+ with_platform_to_deploy(
444
+ additional_config: 'packaging_timeout 1',
445
+ expect_package: false,
446
+ expect_prepare_for_deploy: false,
447
+ expect_connections_to_nodes: false
448
+ ) do
449
+ # Simulate another process taking the packaging futex
450
+ futex_file = HybridPlatformsConductor::Deployer.const_get(:PACKAGING_FUTEX_FILE)
451
+ Futex.new(futex_file).open do
452
+ # Expect the error to be raised within 2 seconds (as it should timeout after 1 second)
453
+ begin
454
+ Timeout::timeout(2) {
455
+ expect { test_deployer.deploy_on('node') }.to raise_error(
456
+ Futex::CantLock,
457
+ /can't get exclusive access to the file #{Regexp.escape(futex_file)} because of the lock at #{Regexp.escape(futex_file)}\.lock, after 1\.\d+s of waiting/
458
+ )
459
+ }
460
+ rescue Timeout::Error
461
+ raise 'The packaging timeout (set to 1 seconds) did not fire within 2 seconds. Looks like it is not working properly.'
462
+ end
463
+ end
464
+ end
465
+ end
466
+
363
467
  context 'checking deployment retries' do
364
468
 
365
469
  # Prepare a platform ready to test deployments' retries on.