foreman_remote_execution 14.1.0 → 14.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/models/job_template.rb +1 -0
- data/app/views/template_invocations/show.js.erb +1 -1
- data/db/migrate/20240312133027_extend_template_invocation_events.rb +9 -0
- data/lib/foreman_remote_execution/engine.rb +1 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/lib/tasks/foreman_remote_execution_tasks.rake +3 -0
- data/test/benchmark/run_hosts_job_benchmark.rb +70 -0
- data/test/benchmark/targeting_benchmark.rb +31 -0
- data/test/factories/foreman_remote_execution_factories.rb +147 -0
- data/test/functional/api/v2/foreign_input_sets_controller_test.rb +58 -0
- data/test/functional/api/v2/job_invocations_controller_test.rb +446 -0
- data/test/functional/api/v2/job_templates_controller_test.rb +110 -0
- data/test/functional/api/v2/registration_controller_test.rb +73 -0
- data/test/functional/api/v2/remote_execution_features_controller_test.rb +34 -0
- data/test/functional/api/v2/template_invocations_controller_test.rb +33 -0
- data/test/functional/cockpit_controller_test.rb +16 -0
- data/test/functional/job_invocations_controller_test.rb +132 -0
- data/test/functional/job_templates_controller_test.rb +31 -0
- data/test/functional/ui_job_wizard_controller_test.rb +16 -0
- data/test/graphql/mutations/job_invocations/create_test.rb +58 -0
- data/test/graphql/queries/job_invocation_query_test.rb +31 -0
- data/test/graphql/queries/job_invocations_query_test.rb +35 -0
- data/test/helpers/remote_execution_helper_test.rb +46 -0
- data/test/support/remote_execution_helper.rb +5 -0
- data/test/test_plugin_helper.rb +9 -0
- data/test/unit/actions/run_host_job_test.rb +115 -0
- data/test/unit/actions/run_hosts_job_test.rb +214 -0
- data/test/unit/api_params_test.rb +25 -0
- data/test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb +29 -0
- data/test/unit/concerns/host_extensions_test.rb +219 -0
- data/test/unit/concerns/nic_extensions_test.rb +9 -0
- data/test/unit/execution_task_status_mapper_test.rb +92 -0
- data/test/unit/input_template_renderer_test.rb +503 -0
- data/test/unit/job_invocation_composer_test.rb +974 -0
- data/test/unit/job_invocation_report_template_test.rb +60 -0
- data/test/unit/job_invocation_test.rb +232 -0
- data/test/unit/job_template_effective_user_test.rb +37 -0
- data/test/unit/job_template_test.rb +316 -0
- data/test/unit/remote_execution_feature_test.rb +86 -0
- data/test/unit/remote_execution_provider_test.rb +298 -0
- data/test/unit/renderer_scope_input_test.rb +49 -0
- data/test/unit/targeting_test.rb +206 -0
- data/test/unit/template_invocation_input_value_test.rb +38 -0
- metadata +39 -2
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'test_plugin_helper'
|
2
|
+
require 'securerandom'
|
3
|
+
|
4
|
+
module ForemanRemoteExecution
|
5
|
+
class RunHostsJobTest < ActiveSupport::TestCase
|
6
|
+
include Dynflow::Testing
|
7
|
+
|
8
|
+
# Adding run_step_id wich is needed in RunHostsJob as a quick fix
|
9
|
+
# it will be added to dynflow in the future see https://github.com/Dynflow/dynflow/pull/391
|
10
|
+
# rubocop:disable Style/ClassAndModuleChildren
|
11
|
+
class Dynflow::Testing::DummyPlannedAction
|
12
|
+
def run_step_id
|
13
|
+
Dynflow::Testing.get_id
|
14
|
+
end
|
15
|
+
end
|
16
|
+
# rubocop:enable Style/ClassAndModuleChildren
|
17
|
+
|
18
|
+
let(:host) { FactoryBot.create(:host, :with_execution) }
|
19
|
+
let(:proxy) { host.remote_execution_proxies('SSH')[:subnet].first }
|
20
|
+
let(:targeting) { FactoryBot.create(:targeting, :search_query => "name = #{host.name}", :user => User.current) }
|
21
|
+
let(:job_invocation) do
|
22
|
+
FactoryBot.build(:job_invocation, :with_template).tap do |invocation|
|
23
|
+
invocation.targeting = targeting
|
24
|
+
invocation.description = 'Some short description'
|
25
|
+
invocation.password = 'changeme'
|
26
|
+
invocation.key_passphrase = 'changemetoo'
|
27
|
+
invocation.effective_user_password = 'sudopassword'
|
28
|
+
invocation.save
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
let(:uuid) { SecureRandom.uuid }
|
33
|
+
let(:task) do
|
34
|
+
OpenStruct.new(:id => uuid).tap do |o|
|
35
|
+
o.stubs(:add_missing_task_groups)
|
36
|
+
o.stubs(:task_groups).returns([])
|
37
|
+
o.stubs(:pending?).returns(true)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
let(:action) do
|
41
|
+
create_action(Actions::RemoteExecution::RunHostsJob).tap do |action|
|
42
|
+
action.expects(:action_subject).with(job_invocation, job_features: [])
|
43
|
+
ForemanTasks::Task::DynflowTask.stubs(:where).returns(mock.tap { |m| m.stubs(:first! => task) })
|
44
|
+
end
|
45
|
+
end
|
46
|
+
let(:planned) do
|
47
|
+
plan_action action, job_invocation
|
48
|
+
end
|
49
|
+
|
50
|
+
let(:delayed) do
|
51
|
+
action.delay({ :start_at => Time.now.getlocal }, job_invocation)
|
52
|
+
action
|
53
|
+
end
|
54
|
+
|
55
|
+
before do
|
56
|
+
ProxyAPI::ForemanDynflow::DynflowProxy.any_instance.stubs(:tasks_count).returns(0)
|
57
|
+
User.current = users :admin
|
58
|
+
action
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'targeting resolving' do
|
62
|
+
it 'resolves dynamic targeting in plan' do
|
63
|
+
targeting.targeting_type = 'dynamic_query'
|
64
|
+
assert_not targeting.resolved?
|
65
|
+
delayed
|
66
|
+
assert_not targeting.resolved?
|
67
|
+
planned
|
68
|
+
assert_includes targeting.hosts, host
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'resolves the hosts on static targeting in delay' do
|
72
|
+
assert_not targeting.resolved?
|
73
|
+
delayed
|
74
|
+
assert_includes targeting.hosts, host
|
75
|
+
# Verify Targeting#resolve_hosts! won't be hit again
|
76
|
+
targeting.expects(:resolve_hosts!).never
|
77
|
+
planned
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'resolves the hosts on static targeting in plan phase if not resolved yet' do
|
81
|
+
planned
|
82
|
+
assert_includes targeting.hosts, host
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'triggers the RunHostJob actions on the resolved hosts in run phase' do
|
87
|
+
planned.expects(:output).at_most(5).returns(:planned_count => 0)
|
88
|
+
planned.expects(:trigger).with { |*args| args[0] == Actions::RemoteExecution::RunHostJob }
|
89
|
+
planned.create_sub_plans
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'uses the BindJobInvocation middleware' do
|
93
|
+
planned
|
94
|
+
assert_equal job_invocation.task_id, uuid
|
95
|
+
end
|
96
|
+
|
97
|
+
# In plan phase this is handled by #action_subject
|
98
|
+
# which is expected in tests
|
99
|
+
it 'sets input in delay phase when delayed' do
|
100
|
+
job_invocation_hash = delayed.input[:job_invocation]
|
101
|
+
assert_equal job_invocation_hash['id'], job_invocation.id
|
102
|
+
assert_equal job_invocation_hash['name'], job_invocation.job_category
|
103
|
+
assert_equal job_invocation_hash['description'], job_invocation.description
|
104
|
+
planned # To make the expectations happy
|
105
|
+
end
|
106
|
+
|
107
|
+
describe '#proxy_batch_size' do
|
108
|
+
it 'defaults to Setting[foreman_tasks_proxy_batch_size]' do
|
109
|
+
Setting.expects(:[]).with('foreman_tasks_proxy_batch_size').returns(14)
|
110
|
+
planned
|
111
|
+
assert_equal 14, planned.proxy_batch_size
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'gets the provider value' do
|
115
|
+
provider = mock('provider')
|
116
|
+
provider.expects(:proxy_batch_size).returns(15)
|
117
|
+
JobTemplate.any_instance.expects(:provider).returns(provider)
|
118
|
+
|
119
|
+
assert_equal 15, planned.proxy_batch_size
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe 'concurrency control' do
|
124
|
+
let(:level) { 5 }
|
125
|
+
|
126
|
+
it 'can be disabled' do
|
127
|
+
assert_nil planned.concurrency_limit
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'can limit concurrency level' do
|
131
|
+
job_invocation.expects(:concurrency_level).twice.returns(level)
|
132
|
+
assert_equal level, planned.concurrency_limit
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe 'notifications' do
|
137
|
+
it 'creates drawer notification on succeess' do
|
138
|
+
blueprint = planned.job_invocation.build_notification
|
139
|
+
blueprint.expects(:deliver!)
|
140
|
+
planned.job_invocation.expects(:build_notification).returns(blueprint)
|
141
|
+
planned.notify_on_success(nil)
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'creates drawer notification on failure' do
|
145
|
+
blueprint = planned.job_invocation.build_notification
|
146
|
+
blueprint.expects(:deliver!)
|
147
|
+
planned.job_invocation.expects(:build_notification).returns(blueprint)
|
148
|
+
planned.notify_on_failure(nil)
|
149
|
+
end
|
150
|
+
|
151
|
+
describe 'ignoring drawer notification' do
|
152
|
+
before do
|
153
|
+
blueprint = planned.job_invocation.build_notification
|
154
|
+
blueprint.expects(:deliver!)
|
155
|
+
planned.job_invocation.expects(:build_notification).returns(blueprint)
|
156
|
+
end
|
157
|
+
|
158
|
+
let(:mail) do
|
159
|
+
object = mock
|
160
|
+
object.stubs(:deliver_now)
|
161
|
+
object
|
162
|
+
end
|
163
|
+
|
164
|
+
describe 'for user subscribed to all' do
|
165
|
+
before do
|
166
|
+
planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::ALL_JOBS))
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'sends the mail notification on success' do
|
170
|
+
RexJobMailer.expects(:job_finished).returns(mail)
|
171
|
+
planned.notify_on_success(nil)
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'sends the mail notification on failure' do
|
175
|
+
RexJobMailer.expects(:job_finished).returns(mail)
|
176
|
+
planned.notify_on_failure(nil)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
describe 'for user subscribed to failures' do
|
181
|
+
before do
|
182
|
+
planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::FAILED_JOBS))
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'it does not send the mail notification on success' do
|
186
|
+
RexJobMailer.expects(:job_finished).never
|
187
|
+
planned.notify_on_success(nil)
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'sends the mail notification on failure' do
|
191
|
+
RexJobMailer.expects(:job_finished).returns(mail)
|
192
|
+
planned.notify_on_failure(nil)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
describe 'for user subscribed to successful jobs' do
|
197
|
+
before do
|
198
|
+
planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::SUCCEEDED_JOBS))
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'sends the mail notification on success' do
|
202
|
+
RexJobMailer.expects(:job_finished).returns(mail)
|
203
|
+
planned.notify_on_success(nil)
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'does not send the mail notification on failure' do
|
207
|
+
RexJobMailer.expects(:job_finished).never
|
208
|
+
planned.notify_on_failure(nil)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test_plugin_helper'
|
4
|
+
|
5
|
+
class ApiParamsTest < ActiveSupport::TestCase
|
6
|
+
describe '#format_datetime' do
|
7
|
+
let(:params) { JobInvocationComposer::ApiParams.allocate }
|
8
|
+
|
9
|
+
it 'leaves empty string as is' do
|
10
|
+
assert_equal params.send(:format_datetime, ''), ''
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'honors explicitly supplied time zone' do
|
14
|
+
Time.use_zone(ActiveSupport::TimeZone['America/New_York']) do
|
15
|
+
assert_equal '2022-07-08 08:53', params.send(:format_datetime, '2022-07-08 12:53:20 UTC')
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'implicitly honors current user\'s time zone' do
|
20
|
+
Time.use_zone(ActiveSupport::TimeZone['America/New_York']) do
|
21
|
+
assert_equal '2022-07-08 12:53', params.send(:format_datetime, '2022-07-08 12:53:20')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'test_plugin_helper'
|
2
|
+
|
3
|
+
class ForemanRemoteExecutionForemanTasksCleanerExtensionsTest < ActiveSupport::TestCase
|
4
|
+
# Apply the same stubbing as in foreman-tasks
|
5
|
+
before do
|
6
|
+
# To stop dynflow from backing up actions, execution_plans and steps
|
7
|
+
ForemanTasks.dynflow.world.persistence.adapter.stubs(:backup_to_csv)
|
8
|
+
ForemanTasks::Cleaner.any_instance.stubs(:say) # Make the tests silent
|
9
|
+
# Hack to make the tests pass due to ActiveRecord shenanigans
|
10
|
+
ForemanTasks::Cleaner.any_instance.stubs(:delete_orphaned_dynflow_tasks)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'tries to delete associated job invocations' do
|
14
|
+
job = FactoryBot.create(:job_invocation, :with_task)
|
15
|
+
ForemanTasks::Cleaner.new(:filter => "id = #{job.task.id}").delete
|
16
|
+
assert_empty JobInvocation.where(:id => job.id)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'removes orphaned job invocations' do
|
20
|
+
job = FactoryBot.create(:job_invocation, :with_task)
|
21
|
+
assert_equal 1, JobInvocation.where(:id => job.id).count
|
22
|
+
job.task.delete
|
23
|
+
job.reload
|
24
|
+
assert_nil job.task
|
25
|
+
refute_nil job.task_id
|
26
|
+
ForemanTasks::Cleaner.new(:filter => '').delete
|
27
|
+
assert_empty JobInvocation.where(:id => job.id)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'test_plugin_helper'
|
2
|
+
|
3
|
+
class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
|
4
|
+
let(:provider) { 'SSH' }
|
5
|
+
|
6
|
+
before { User.current = FactoryBot.build(:user, :admin) }
|
7
|
+
after { User.current = nil }
|
8
|
+
|
9
|
+
describe 'ssh specific params' do
|
10
|
+
let(:host) { FactoryBot.create(:host, :with_execution) }
|
11
|
+
let(:sshkey) { 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQ foo@example.com' }
|
12
|
+
|
13
|
+
before do
|
14
|
+
SmartProxy.any_instance.stubs(:pubkey).returns(sshkey)
|
15
|
+
Setting[:remote_execution_ssh_user] = 'root'
|
16
|
+
Setting[:remote_execution_effective_user_method] = 'sudo'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'has ssh user in the parameters' do
|
20
|
+
assert_equal Setting[:remote_execution_ssh_user], host.host_param('remote_execution_ssh_user')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'can override ssh user' do
|
24
|
+
host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_user', :value => 'amy')
|
25
|
+
assert_equal 'amy', host.host_param('remote_execution_ssh_user')
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'has effective user method in the parameters' do
|
29
|
+
assert_equal Setting[:remote_execution_effective_user_method], host.host_param('remote_execution_effective_user_method')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'can override effective user method' do
|
33
|
+
host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_effective_user_method', :value => 'su')
|
34
|
+
assert_equal 'su', host.host_param('remote_execution_effective_user_method')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'has ssh keys in the parameters' do
|
38
|
+
assert_includes host.remote_execution_ssh_keys, sshkey
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'merges ssh keys from host parameters and proxies' do
|
42
|
+
key = 'ssh-rsa not-even-a-key something@somewhere.com'
|
43
|
+
host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_keys', :value => [key])
|
44
|
+
assert_includes host.host_param('remote_execution_ssh_keys'), key
|
45
|
+
assert_includes host.host_param('remote_execution_ssh_keys'), sshkey
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'merges ssh key as a string from host parameters and proxies' do
|
49
|
+
key = 'ssh-rsa not-even-a-key something@somewhere.com'
|
50
|
+
host.host_parameters << FactoryBot.create(:host_parameter, :host => host, :name => 'remote_execution_ssh_keys', :value => key)
|
51
|
+
assert_includes host.host_param('remote_execution_ssh_keys'), key
|
52
|
+
assert_includes host.host_param('remote_execution_ssh_keys'), sshkey
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'has ssh keys in the parameters even when no user specified' do
|
56
|
+
# this is a case, when using the helper in provisioning templates
|
57
|
+
FactoryBot.create(:smart_proxy, :ssh)
|
58
|
+
host.interfaces.first.subnet.remote_execution_proxies.clear
|
59
|
+
User.current = nil
|
60
|
+
assert_includes host.remote_execution_ssh_keys, sshkey
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'host has multiple nics' do
|
65
|
+
let(:host) { FactoryBot.build(:host, :with_execution) }
|
66
|
+
|
67
|
+
it 'should only have one execution interface' do
|
68
|
+
host.interfaces << FactoryBot.build(:nic_managed)
|
69
|
+
host.interfaces.each { |interface| interface.execution = true }
|
70
|
+
assert_predicate host, :valid?
|
71
|
+
assert_equal 1, host.interfaces.count(&:execution?)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'returns the execution interface' do
|
75
|
+
assert_kind_of Nic::Managed, host.execution_interface
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'scoped search' do
|
80
|
+
let(:job) do
|
81
|
+
job = FactoryBot.create(:job_invocation, :with_task)
|
82
|
+
job.template_invocations << FactoryBot.create(:template_invocation, :with_host, :with_failed_task)
|
83
|
+
job
|
84
|
+
end
|
85
|
+
|
86
|
+
let(:job2) do
|
87
|
+
job = FactoryBot.create(:job_invocation, :with_task)
|
88
|
+
job.template_invocations << FactoryBot.create(:template_invocation, :with_host, :with_failed_task)
|
89
|
+
job
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'finds hosts for job_invocation.id' do
|
93
|
+
found_ids = Host.search_for("job_invocation.id = #{job.id}").map(&:id).sort
|
94
|
+
assert_equal job.template_invocations_host_ids.sort, found_ids
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'finds hosts by job_invocation.result' do
|
98
|
+
success, failed = job.template_invocations
|
99
|
+
.partition { |template| template.run_host_job_task.result == 'success' }
|
100
|
+
found_ids = Host.search_for('job_invocation.result = success').map(&:id)
|
101
|
+
assert_equal success.map(&:host_id), found_ids
|
102
|
+
found_ids = Host.search_for('job_invocation.result = failed').map(&:id)
|
103
|
+
assert_equal failed.map(&:host_id), found_ids
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'finds hosts by job_invocation.id and job_invocation.result' do
|
107
|
+
# Force evaluation of the jobs
|
108
|
+
job
|
109
|
+
job2
|
110
|
+
|
111
|
+
assert_equal 2, Host.search_for("job_invocation.id = #{job.id}").count
|
112
|
+
assert_equal 2, Host.search_for("job_invocation.id = #{job2.id}").count
|
113
|
+
assert_equal 2, Host.search_for('job_invocation.result = success').count
|
114
|
+
assert_equal 2, Host.search_for('job_invocation.result = failed').count
|
115
|
+
|
116
|
+
success, failed = job.template_invocations
|
117
|
+
.partition { |template| template.run_host_job_task.result == 'success' }
|
118
|
+
found_ids = Host.search_for("job_invocation.id = #{job.id} AND job_invocation.result = success").map(&:id)
|
119
|
+
assert_equal success.map(&:host_id), found_ids
|
120
|
+
found_ids = Host.search_for("job_invocation.id = #{job.id} AND job_invocation.result = failed").map(&:id)
|
121
|
+
assert_equal failed.map(&:host_id), found_ids
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe 'proxy determination strategies' do
|
126
|
+
context 'subnet strategy' do
|
127
|
+
let(:host) { FactoryBot.build(:host, :with_execution) }
|
128
|
+
it { assert_includes host.remote_execution_proxies(provider)[:subnet], host.subnet.remote_execution_proxies.first }
|
129
|
+
end
|
130
|
+
|
131
|
+
context 'fallback strategy' do
|
132
|
+
let(:host) { FactoryBot.build(:host, :with_tftp_subnet) }
|
133
|
+
|
134
|
+
context 'enabled' do
|
135
|
+
before do
|
136
|
+
Setting[:remote_execution_fallback_proxy] = true
|
137
|
+
host.subnet.tftp.features << FactoryBot.create(:feature, :ssh)
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'returns a fallback proxy' do
|
141
|
+
assert_includes host.remote_execution_proxies(provider)[:fallback], host.subnet.tftp
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'disabled' do
|
146
|
+
before do
|
147
|
+
Setting[:remote_execution_fallback_proxy] = false
|
148
|
+
host.subnet.tftp.features << FactoryBot.create(:feature, :ssh)
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'returns no proxy' do
|
152
|
+
assert_empty host.remote_execution_proxies(provider)[:fallback]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'global strategy' do
|
158
|
+
let(:tax_organization) { FactoryBot.build(:organization) }
|
159
|
+
let(:tax_location) { FactoryBot.build(:location) }
|
160
|
+
let(:host) { FactoryBot.build(:host, :organization => tax_organization, :location => tax_location) }
|
161
|
+
let(:proxy_in_taxonomies) { FactoryBot.create(:smart_proxy, :ssh, :organizations => [tax_organization], :locations => [tax_location]) }
|
162
|
+
let(:proxy_no_taxonomies) { FactoryBot.create(:smart_proxy, :ssh) }
|
163
|
+
|
164
|
+
context 'enabled' do
|
165
|
+
before { Setting[:remote_execution_global_proxy] = true }
|
166
|
+
|
167
|
+
it 'returns correct proxies confined by taxonomies' do
|
168
|
+
proxy_in_taxonomies
|
169
|
+
proxy_no_taxonomies
|
170
|
+
assert_includes host.remote_execution_proxies(provider)[:global], proxy_in_taxonomies
|
171
|
+
refute_includes host.remote_execution_proxies(provider)[:global], proxy_no_taxonomies
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
context 'disabled' do
|
176
|
+
before { Setting[:remote_execution_global_proxy] = false }
|
177
|
+
it 'returns no proxy' do
|
178
|
+
assert_empty host.remote_execution_proxies(provider)[:global]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
describe '#execution_scope' do
|
185
|
+
let(:host) { FactoryBot.create(:host) }
|
186
|
+
let(:infra_host) { FactoryBot.create(:host, :with_infrastructure_facet) }
|
187
|
+
|
188
|
+
before do
|
189
|
+
host
|
190
|
+
infra_host
|
191
|
+
end
|
192
|
+
|
193
|
+
context 'without infrastructure host permission' do
|
194
|
+
it 'omits the infrastructure host' do
|
195
|
+
setup_user('view', 'hosts')
|
196
|
+
|
197
|
+
hosts = ::Host::Managed.execution_scope
|
198
|
+
assert_includes hosts, host
|
199
|
+
refute_includes hosts, infra_host
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
context 'with infrastructure host permission' do
|
204
|
+
it 'finds the host as admin' do
|
205
|
+
assert User.current.admin?
|
206
|
+
hosts = ::Host::Managed.execution_scope
|
207
|
+
assert_includes hosts, host
|
208
|
+
assert_includes hosts, infra_host
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'finds the host as user with needed permissions' do
|
212
|
+
setup_user('execute_jobs_on', 'infrastructure_hosts')
|
213
|
+
hosts = ::Host::Managed.execution_scope
|
214
|
+
assert_includes hosts, host
|
215
|
+
assert_includes hosts, infra_host
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'test_plugin_helper'
|
2
|
+
|
3
|
+
class ForemanRemoteExecutionNicExtensionsTest < ActiveSupport::TestCase
|
4
|
+
let(:host) { FactoryBot.create(:host) }
|
5
|
+
|
6
|
+
it 'sets the first primary interface as the execution interface' do
|
7
|
+
assert_equal host.interfaces.first, host.execution_interface
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'test_plugin_helper'
|
2
|
+
|
3
|
+
class ExecutionTaskStatusMapperTest < ActiveSupport::TestCase
|
4
|
+
describe '.sql_conditions_for(status)' do
|
5
|
+
let(:subject) { HostStatus::ExecutionStatus::ExecutionTaskStatusMapper }
|
6
|
+
|
7
|
+
it 'accepts status number as well as string representation' do
|
8
|
+
assert_equal subject.sql_conditions_for('failed'), subject.sql_conditions_for(HostStatus::ExecutionStatus::ERROR)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'does not find any task for unknown state' do
|
12
|
+
assert_equal [ '1 = 0' ], subject.sql_conditions_for(-1)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:task) { ForemanTasks::Task.new }
|
17
|
+
let(:mapper) { HostStatus::ExecutionStatus::ExecutionTaskStatusMapper.new(task) }
|
18
|
+
|
19
|
+
describe '#status' do
|
20
|
+
let(:subject) { mapper }
|
21
|
+
|
22
|
+
describe 'is queued' do
|
23
|
+
context 'when there is no task' do
|
24
|
+
before { subject.task = nil }
|
25
|
+
specify { assert_equal HostStatus::ExecutionStatus::QUEUED, subject.status }
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'when the task is scheduled in future' do
|
29
|
+
before { subject.task.state = 'scheduled' }
|
30
|
+
specify { assert_equal HostStatus::ExecutionStatus::QUEUED, subject.status }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'when task is stopped' do
|
35
|
+
before { subject.task.state = 'stopped' }
|
36
|
+
|
37
|
+
describe 'is succeeded' do
|
38
|
+
context 'without error' do
|
39
|
+
before { subject.task.result = 'success' }
|
40
|
+
specify { assert_equal HostStatus::ExecutionStatus::OK, subject.status }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'is failed' do
|
45
|
+
context 'with error' do
|
46
|
+
before { subject.task.result = 'error' }
|
47
|
+
specify { assert_equal HostStatus::ExecutionStatus::ERROR, subject.status }
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'without error but just with warning (sub task failed)' do
|
51
|
+
before { subject.task.result = 'warning' }
|
52
|
+
specify { assert_equal HostStatus::ExecutionStatus::ERROR, subject.status }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when task is pending' do
|
58
|
+
before { subject.task.state = 'running' }
|
59
|
+
|
60
|
+
describe 'is pending' do
|
61
|
+
specify { assert_equal HostStatus::ExecutionStatus::RUNNING, subject.status }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#status_label' do
|
67
|
+
let(:subject) { mapper.status_label }
|
68
|
+
|
69
|
+
context 'status is OK' do
|
70
|
+
before do
|
71
|
+
mapper.task.state = 'stopped'
|
72
|
+
mapper.task.result = 'success'
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'returns ok label' do
|
76
|
+
assert_equal HostStatus::ExecutionStatus::STATUS_NAMES[HostStatus::ExecutionStatus::OK], subject
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'status is ERROR' do
|
81
|
+
before do
|
82
|
+
mapper.task.state = 'stopped'
|
83
|
+
mapper.task.result = 'error'
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'returns failed label' do
|
87
|
+
assert_equal HostStatus::ExecutionStatus::STATUS_NAMES[HostStatus::ExecutionStatus::ERROR], subject
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|