foreman_remote_execution 0.0.6 → 0.0.7
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/app/assets/javascripts/execution_interface.js +6 -0
- data/app/assets/javascripts/template_invocation.js +18 -0
- data/app/assets/stylesheets/template_invocation.css.scss +53 -0
- data/app/controllers/api/v2/job_invocations_controller.rb +64 -0
- data/app/controllers/job_invocations_controller.rb +11 -2
- data/app/controllers/template_invocations_controller.rb +16 -0
- data/app/helpers/remote_execution_helper.rb +78 -15
- data/app/lib/actions/middleware/bind_job_invocation.rb +29 -0
- data/app/lib/actions/remote_execution/run_host_job.rb +20 -53
- data/app/lib/actions/remote_execution/run_hosts_job.rb +30 -9
- data/app/lib/actions/remote_execution/run_proxy_command.rb +64 -3
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +43 -5
- data/app/models/concerns/foreman_remote_execution/nic_extensions.rb +25 -0
- data/app/models/concerns/foreman_remote_execution/subnet_extensions.rb +10 -0
- data/app/models/concerns/foreman_remote_execution/taxonomy_extensions.rb +16 -0
- data/app/models/input_template_renderer.rb +5 -1
- data/app/models/job_invocation.rb +74 -7
- data/app/models/job_invocation_api_composer.rb +62 -0
- data/app/models/job_invocation_composer.rb +4 -1
- data/app/models/job_template.rb +2 -4
- data/app/models/setting/remote_execution.rb +6 -2
- data/app/models/target_remote_execution_proxy.rb +4 -0
- data/app/models/template_input.rb +14 -2
- data/app/models/template_invocation.rb +12 -1
- data/app/models/template_invocation_input_value.rb +1 -1
- data/app/overrides/execution_interface.rb +9 -0
- data/app/overrides/foreman/nics/_execution_interface.html.erb +1 -0
- data/app/overrides/foreman/subnets/_rex_tab.html.erb +1 -0
- data/app/overrides/foreman/subnets/_rex_tab_pane.html.erb +5 -0
- data/app/overrides/subnet_proxies.rb +9 -0
- data/app/services/proxy_load_balancer.rb +30 -0
- data/app/views/api/v2/job_invocations/base.json.rabl +3 -0
- data/app/views/api/v2/job_invocations/create.json.rabl +3 -0
- data/app/views/api/v2/job_invocations/index.json.rabl +3 -0
- data/app/views/api/v2/job_invocations/main.json.rabl +3 -0
- data/app/views/api/v2/job_invocations/show.json.rabl +18 -0
- data/app/views/api/v2/job_templates/base.json.rabl +1 -1
- data/app/views/job_invocations/_form.html.erb +9 -0
- data/app/views/job_invocations/_tab_hosts.html.erb +10 -14
- data/app/views/job_invocations/_tab_overview.html.erb +2 -9
- data/app/views/job_invocations/show.html.erb +8 -1
- data/app/views/job_invocations/show.js.erb +6 -0
- data/app/views/template_invocations/_output_line_set.html.erb +7 -0
- data/app/views/template_invocations/_refresh.js.erb +7 -0
- data/app/views/template_invocations/show.html.erb +38 -0
- data/app/views/template_invocations/show.js.erb +16 -0
- data/config/routes.rb +4 -0
- data/db/migrate/20150826191632_create_target_remote_execution_proxies.rb +14 -0
- data/db/migrate/20150903192731_add_execution_to_interface.rb +22 -0
- data/doc/source/design/index.md +5 -0
- data/doc/source/design/wireframes.pdf +0 -0
- data/lib/foreman_remote_execution.rb +1 -1
- data/lib/foreman_remote_execution/engine.rb +13 -4
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/factories/foreman_remote_execution_factories.rb +46 -1
- data/test/functional/api/v2/job_invocations_controller_test.rb +45 -0
- data/test/test_plugin_helper.rb +18 -0
- data/test/unit/actions/run_hosts_job_test.rb +13 -8
- data/test/unit/actions/run_proxy_command_test.rb +109 -5
- data/test/unit/concerns/host_extensions_test.rb +90 -0
- data/test/unit/concerns/nic_extensions_test.rb +9 -0
- data/test/unit/input_template_renderer_test.rb +24 -10
- data/test/unit/job_invocation_api_composer_test.rb +117 -0
- data/test/unit/job_invocation_composer_test.rb +16 -1
- data/test/unit/job_invocation_test.rb +48 -3
- data/test/unit/proxy_load_balancer_test.rb +25 -0
- metadata +57 -4
data/config/routes.rb
CHANGED
|
@@ -23,8 +23,12 @@ Rails.application.routes.draw do
|
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
resources :template_invocations, :only => [:show]
|
|
27
|
+
|
|
26
28
|
namespace :api, :defaults => {:format => 'json'} do
|
|
27
29
|
scope "(:apiv)", :module => :v2, :defaults => {:apiv => 'v2'}, :apiv => /v1|v2/, :constraints => ApiConstraints.new(:version => 2, :default => true) do
|
|
30
|
+
resources :job_invocations, :except => [:new, :edit, :update, :destroy]
|
|
31
|
+
|
|
28
32
|
resources :job_templates, :except => [:new, :edit] do
|
|
29
33
|
(resources :locations, :only => [:index, :show]) if SETTINGS[:locations_enabled]
|
|
30
34
|
(resources :organizations, :only => [:index, :show]) if SETTINGS[:organizations_enabled]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class CreateTargetRemoteExecutionProxies < ActiveRecord::Migration
|
|
2
|
+
def change
|
|
3
|
+
create_table :target_remote_execution_proxies do |t|
|
|
4
|
+
t.integer :remote_execution_proxy_id
|
|
5
|
+
t.integer :target_id
|
|
6
|
+
t.string :target_type
|
|
7
|
+
|
|
8
|
+
t.timestamps
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
add_index :target_remote_execution_proxies, :remote_execution_proxy_id, :name => 'target_remote_execution_proxies_proxy_id'
|
|
12
|
+
add_index :target_remote_execution_proxies, [:target_id, :target_type], :name => 'target_remote_execution_proxies_target_id_target_type'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class FakeNic < ActiveRecord::Base
|
|
2
|
+
self.table_name = 'nics'
|
|
3
|
+
|
|
4
|
+
def type
|
|
5
|
+
Nic::Managed
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class AddExecutionToInterface < ActiveRecord::Migration
|
|
10
|
+
def up
|
|
11
|
+
add_column :nics, :execution, :boolean, :default => false
|
|
12
|
+
|
|
13
|
+
FakeNic.reset_column_information
|
|
14
|
+
FakeNic.all.each do |nic|
|
|
15
|
+
nic.update_column(:execution, true) if nic.primary
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def down
|
|
20
|
+
remove_column :nics, :execution
|
|
21
|
+
end
|
|
22
|
+
end
|
data/doc/source/design/index.md
CHANGED
|
Binary file
|
|
@@ -42,10 +42,10 @@ module ForemanRemoteExecution
|
|
|
42
42
|
permission :destroy_job_templates, { :job_templates => [:destroy],
|
|
43
43
|
:'api/v2/job_templates' => [:destroy] }, :resource_type => 'JobTemplate'
|
|
44
44
|
permission :lock_job_templates, { :job_templates => [:lock, :unlock] }, :resource_type => 'JobTemplate'
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
permission :create_job_invocations, { :job_invocations => [:new, :create, :refresh, :rerun],
|
|
46
|
+
'api/v2/job_invocations' => [:create] }, :resource_type => 'JobInvocation'
|
|
47
|
+
permission :view_job_invocations, { :job_invocations => [:index, :show, :auto_complete_search], :template_invocations => [:show],
|
|
48
|
+
'api/v2/job_invocations' => [:index, :show] }, :resource_type => 'JobInvocation'
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
# Add a new role called 'ForemanRemoteExecution' if it doesn't exist
|
|
@@ -100,14 +100,23 @@ module ForemanRemoteExecution
|
|
|
100
100
|
# similarly, attr_accessible :template_inputs_attributes does not work with STI
|
|
101
101
|
(Template.descendants + [Template]).each { |klass| klass.send(:include, ForemanRemoteExecution::TemplateExtensions) }
|
|
102
102
|
|
|
103
|
+
(Taxonomy.descendants + [Taxonomy]).each { |klass| klass.send(:include, ForemanRemoteExecution::TaxonomyExtensions) }
|
|
104
|
+
|
|
103
105
|
User.send(:include, ForemanRemoteExecution::UserExtensions)
|
|
104
106
|
(Host::Base.descendants + [Host::Base]).each do |klass|
|
|
105
107
|
klass.send(:include, ForemanRemoteExecution::HostExtensions)
|
|
106
108
|
klass.send(:include, ForemanTasks::Concerns::HostActionSubject)
|
|
107
109
|
end
|
|
110
|
+
|
|
111
|
+
(Nic::Base.descendants + [Nic::Base]).each do |klass|
|
|
112
|
+
klass.send(:include, ForemanRemoteExecution::NicExtensions)
|
|
113
|
+
end
|
|
114
|
+
|
|
108
115
|
Bookmark.send(:include, ForemanRemoteExecution::BookmarkExtensions)
|
|
109
116
|
HostsHelper.send(:include, ForemanRemoteExecution::HostsHelperExtensions)
|
|
110
117
|
|
|
118
|
+
Subnet.send(:include, ForemanRemoteExecution::SubnetExtensions)
|
|
119
|
+
|
|
111
120
|
# We need to explicitly force to load the Task model due to Rails loader
|
|
112
121
|
# having issues with resolving it to Rake::Task otherwise
|
|
113
122
|
require_dependency 'foreman_tasks/task'
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
FactoryGirl.define do
|
|
2
|
+
|
|
2
3
|
factory :job_template do |f|
|
|
3
4
|
f.sequence(:name) { |n| "Job template #{n}" }
|
|
5
|
+
sequence(:job_name) { |n| "job name #{n}" }
|
|
4
6
|
f.template 'id'
|
|
5
7
|
f.provider_type 'Ssh'
|
|
6
8
|
|
|
@@ -13,7 +15,7 @@ FactoryGirl.define do
|
|
|
13
15
|
|
|
14
16
|
factory :template_input do |f|
|
|
15
17
|
f.sequence(:name) { |n| "Template input #{n}" }
|
|
16
|
-
f.input_type
|
|
18
|
+
f.input_type 'user'
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
factory :targeting do |f|
|
|
@@ -46,3 +48,46 @@ FactoryGirl.define do
|
|
|
46
48
|
f.sequence(:value) { |n| "Input Value #{n}" }
|
|
47
49
|
end
|
|
48
50
|
end
|
|
51
|
+
|
|
52
|
+
FactoryGirl.modify do
|
|
53
|
+
factory :feature do
|
|
54
|
+
trait :ssh do
|
|
55
|
+
name 'Ssh'
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
factory :smart_proxy do
|
|
60
|
+
trait :ssh do
|
|
61
|
+
features { [FactoryGirl.build(:feature, :ssh)] }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
factory :subnet do
|
|
66
|
+
trait :execution do
|
|
67
|
+
remote_execution_proxies { [FactoryGirl.build(:smart_proxy, :ssh)] }
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
factory :host do
|
|
72
|
+
trait :with_execution do
|
|
73
|
+
managed
|
|
74
|
+
domain
|
|
75
|
+
subnet do
|
|
76
|
+
overrides = {
|
|
77
|
+
:remote_execution_proxies => [FactoryGirl.create(:smart_proxy, :ssh)]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
overrides[:locations] = [location] unless location.nil?
|
|
81
|
+
overrides[:organizations] = [organization] unless organization.nil?
|
|
82
|
+
|
|
83
|
+
FactoryGirl.create(
|
|
84
|
+
:subnet,
|
|
85
|
+
overrides
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
interfaces do
|
|
89
|
+
[FactoryGirl.build(:nic_primary_and_provision, :ip => subnet.network.sub(/0\Z/, '1'), :execution => true)]
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require 'test_plugin_helper'
|
|
2
|
+
|
|
3
|
+
module Api
|
|
4
|
+
module V2
|
|
5
|
+
class JobInvocationsControllerTest < ActionController::TestCase
|
|
6
|
+
setup do
|
|
7
|
+
@invocation = FactoryGirl.create(:job_invocation, :with_template)
|
|
8
|
+
@template = FactoryGirl.create(:job_template, :with_input)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
test "should get index" do
|
|
12
|
+
get :index
|
|
13
|
+
invocations = ActiveSupport::JSON.decode(@response.body)
|
|
14
|
+
refute_empty invocations, "Should response with invocation"
|
|
15
|
+
assert_response :success
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
test "should get invocation detail" do
|
|
19
|
+
get :show, :id => @invocation.id
|
|
20
|
+
assert_response :success
|
|
21
|
+
template = ActiveSupport::JSON.decode(@response.body)
|
|
22
|
+
refute_empty template
|
|
23
|
+
assert_equal template["job_name"], @invocation.job_name
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
test "should create valid without template_id" do
|
|
27
|
+
attrs = { :job_name => @template.job_name, :name => 'RandomName', :targeting_type => 'static_query', :search_query => "foobar"}
|
|
28
|
+
post :create, :job_invocation => attrs
|
|
29
|
+
|
|
30
|
+
invocation = ActiveSupport::JSON.decode(@response.body)
|
|
31
|
+
assert_equal attrs[:job_name], invocation['job_name']
|
|
32
|
+
assert_response 200
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
test "should create valid with template_id" do
|
|
36
|
+
attrs = { :job_name => @template.job_name, :name => 'RandomName', :template_id => @template.id,:targeting_type => 'static_query', :search_query => "foobar"}
|
|
37
|
+
post :create, :job_invocation => attrs
|
|
38
|
+
|
|
39
|
+
invocation = ActiveSupport::JSON.decode(@response.body)
|
|
40
|
+
assert_equal attrs[:job_name], invocation['job_name']
|
|
41
|
+
assert_response 200
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/test/test_plugin_helper.rb
CHANGED
|
@@ -6,3 +6,21 @@ require 'dynflow/testing'
|
|
|
6
6
|
# Add plugin to FactoryGirl's paths
|
|
7
7
|
FactoryGirl.definition_file_paths << File.join(File.dirname(__FILE__), 'factories')
|
|
8
8
|
FactoryGirl.reload
|
|
9
|
+
|
|
10
|
+
# Foreman's setup doesn't handle cleaning up for Minitest::Spec
|
|
11
|
+
DatabaseCleaner.strategy = :transaction
|
|
12
|
+
|
|
13
|
+
class Minitest::Spec
|
|
14
|
+
class << self
|
|
15
|
+
alias_method :context, :describe
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
before :each do
|
|
19
|
+
DatabaseCleaner.start
|
|
20
|
+
Setting::RemoteExecution.load_defaults
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
after :each do
|
|
24
|
+
DatabaseCleaner.clean
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -2,28 +2,27 @@
|
|
|
2
2
|
require "test_plugin_helper"
|
|
3
3
|
|
|
4
4
|
module ForemanRemoteExecution
|
|
5
|
-
class RunHostsJobTest <
|
|
5
|
+
class RunHostsJobTest < ActiveSupport::TestCase
|
|
6
6
|
include Dynflow::Testing
|
|
7
7
|
|
|
8
|
-
let(:
|
|
9
|
-
let(:
|
|
10
|
-
let(:script) { 'ping -c 5 redhat.com' }
|
|
8
|
+
let(:host) { FactoryGirl.create(:host, :with_execution) }
|
|
9
|
+
let(:proxy) { host.remote_execution_proxies('Ssh')[:subnet].first }
|
|
11
10
|
let(:targeting) { FactoryGirl.create(:targeting, :search_query => "name = #{host.name}", :user => User.current) }
|
|
12
11
|
let(:job_invocation) do
|
|
13
|
-
FactoryGirl.build(:job_invocation).tap do |invocation|
|
|
12
|
+
FactoryGirl.build(:job_invocation, :with_template).tap do |invocation|
|
|
14
13
|
invocation.targeting = targeting
|
|
15
14
|
invocation.save
|
|
16
15
|
end
|
|
17
16
|
end
|
|
18
|
-
let(:host) { FactoryGirl.create(:host) }
|
|
19
17
|
let(:action) do
|
|
20
18
|
action = create_action(Actions::RemoteExecution::RunHostsJob)
|
|
21
19
|
action.expects(:action_subject).with(job_invocation)
|
|
22
|
-
|
|
20
|
+
ForemanTasks::Task::DynflowTask.stubs(:find_by_external_id!).returns(OpenStruct.new(:id => '123'))
|
|
23
21
|
plan_action(action, job_invocation)
|
|
24
22
|
end
|
|
25
23
|
|
|
26
24
|
before do
|
|
25
|
+
ProxyAPI::ForemanDynflow::DynflowProxy.any_instance.stubs(:tasks_count).returns(0)
|
|
27
26
|
User.current = users :admin
|
|
28
27
|
action
|
|
29
28
|
end
|
|
@@ -33,8 +32,14 @@ module ForemanRemoteExecution
|
|
|
33
32
|
end
|
|
34
33
|
|
|
35
34
|
it 'triggers the RunHostJob actions on the resolved hosts in run phase' do
|
|
36
|
-
|
|
35
|
+
template_invocation = job_invocation.template_invocation_for_host(host)
|
|
36
|
+
action.expects(:trigger).with(Actions::RemoteExecution::RunHostJob, job_invocation, host, template_invocation, proxy, {})
|
|
37
37
|
action.create_sub_plans
|
|
38
38
|
end
|
|
39
|
+
|
|
40
|
+
it 'uses the BindJobInvocation middleware' do
|
|
41
|
+
action
|
|
42
|
+
job_invocation.last_task_id.must_equal '123'
|
|
43
|
+
end
|
|
39
44
|
end
|
|
40
45
|
end
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
require "test_plugin_helper"
|
|
2
2
|
|
|
3
3
|
module ForemanRemoteExecution
|
|
4
|
-
class RunProxyCommandTest <
|
|
4
|
+
class RunProxyCommandTest < ActiveSupport::TestCase
|
|
5
5
|
include Dynflow::Testing
|
|
6
6
|
|
|
7
|
-
let(:
|
|
7
|
+
let(:host) { FactoryGirl.build(:host, :with_execution) }
|
|
8
|
+
let(:proxy) { host.remote_execution_proxies('Ssh')[:subnet].first }
|
|
8
9
|
let(:hostname) { 'myhost.example.com' }
|
|
9
10
|
let(:script) { 'ping -c 5 redhat.com' }
|
|
11
|
+
let(:connection_options) { { 'retry_interval' => 15, 'retry_count' => 4, 'timeout' => 60 } }
|
|
10
12
|
let(:action) do
|
|
11
|
-
create_and_plan_action(Actions::RemoteExecution::RunProxyCommand, proxy,
|
|
13
|
+
create_and_plan_action(Actions::RemoteExecution::RunProxyCommand, proxy, host.name, script)
|
|
12
14
|
end
|
|
15
|
+
let(:timestamp) { 1_443_194_805.9192207 }
|
|
13
16
|
|
|
14
17
|
it 'plans for running the command action on server' do
|
|
15
|
-
assert_run_phase action, { :hostname =>
|
|
18
|
+
assert_run_phase action, { :hostname => host.name,
|
|
16
19
|
:script => script,
|
|
17
20
|
:proxy_url => proxy.url,
|
|
18
|
-
:effective_user => nil
|
|
21
|
+
:effective_user => nil,
|
|
22
|
+
:connection_options => connection_options }
|
|
19
23
|
end
|
|
20
24
|
|
|
21
25
|
it 'sends to command to ssh provider' do
|
|
@@ -26,5 +30,105 @@ module ForemanRemoteExecution
|
|
|
26
30
|
action.rescue_strategy.must_equal ::Dynflow::Action::Rescue::Skip
|
|
27
31
|
end
|
|
28
32
|
|
|
33
|
+
describe '#live_output' do
|
|
34
|
+
let(:task) { ForemanTasks::Task.new }
|
|
35
|
+
|
|
36
|
+
let(:action) do
|
|
37
|
+
planned_action = create_and_plan_action(Actions::RemoteExecution::RunProxyCommand, proxy, hostname, script)
|
|
38
|
+
create_action_presentation(Actions::RemoteExecution::RunProxyCommand).tap do |action|
|
|
39
|
+
action.stubs(:task).returns(task)
|
|
40
|
+
action.stubs(:proxy).returns(ProxyAPI::ForemanDynflow::DynflowProxy.new(:url => proxy.url))
|
|
41
|
+
action.instance_variable_set('@input', planned_action.input)
|
|
42
|
+
action.output.merge!(:proxy_task_id => '123')
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
let(:live_output) do
|
|
47
|
+
action.live_output
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe 'when the task is finished' do
|
|
51
|
+
before do
|
|
52
|
+
task.state = 'stopped'
|
|
53
|
+
task.ended_at = timestamp + 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe 'the task finished sucessfully' do
|
|
57
|
+
before do
|
|
58
|
+
action.output.merge!(:proxy_output => { :result => [{ 'output_type' => 'stdout', 'output' => 'Hello', 'timestamp' => timestamp}],
|
|
59
|
+
:exit_status => 0 })
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "doesn't fetch data from proxy anymore" do
|
|
63
|
+
action.proxy.expects(:status_of_task).never
|
|
64
|
+
live_output.size.must_equal 2
|
|
65
|
+
live_output[0]['output_type'].must_equal 'stdout'
|
|
66
|
+
live_output[0]['output'].must_equal 'Hello'
|
|
67
|
+
live_output[0]['timestamp'].must_be_kind_of Float
|
|
68
|
+
live_output[1]['output_type'].must_equal 'stdout'
|
|
69
|
+
live_output[1]['output'].must_equal 'Exit status: 0'
|
|
70
|
+
live_output[1]['timestamp'].must_be_kind_of Float
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe 'there was not output data from proxy' do
|
|
75
|
+
before do
|
|
76
|
+
action.output.merge!(:proxy_output => {})
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "doesn't fetch data from proxy anymore" do
|
|
80
|
+
action.proxy.expects(:status_of_task).never
|
|
81
|
+
live_output.size.must_equal 1
|
|
82
|
+
live_output[0]['output_type'].must_equal 'debug'
|
|
83
|
+
live_output[0]['output'].must_equal 'No output'
|
|
84
|
+
live_output[0]['timestamp'].must_be_kind_of Float
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
describe 'when something went wrong while fetching the data' do
|
|
90
|
+
before do
|
|
91
|
+
action.proxy.expects(:status_of_task).raises("Something went wrong")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "reports the failure as part of the live output" do
|
|
95
|
+
live_output.size.must_equal 1
|
|
96
|
+
live_output.first['output_type'].must_equal 'debug'
|
|
97
|
+
live_output.first['output'].must_equal 'Error loading data from proxy: RuntimeError - Something went wrong'
|
|
98
|
+
live_output.first['timestamp'].must_be_kind_of Float
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
describe 'when there was some connection error while running the command' do
|
|
103
|
+
before do
|
|
104
|
+
action.output.merge!(:proxy_task_id => nil,
|
|
105
|
+
:metadata => { :failed_proxy_tasks => [action.send(:format_exception, RuntimeError.new('Connection error'))]})
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "reports the failure as part of the live output" do
|
|
109
|
+
live_output.size.must_equal 1
|
|
110
|
+
live_output.first['output_type'].must_equal 'debug'
|
|
111
|
+
live_output.first['output'].must_equal 'Initialization error: RuntimeError - Connection error'
|
|
112
|
+
live_output.first['timestamp'].must_be_kind_of Float
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
describe 'when proxy returns valid data' do
|
|
117
|
+
before do
|
|
118
|
+
action.proxy.expects(:status_of_task).returns('actions' =>
|
|
119
|
+
[{ 'class' => 'Proxy::RemoteExecution::Ssh::CommandAction',
|
|
120
|
+
'output' => { 'result' => [ { 'output_type' => 'stdout',
|
|
121
|
+
'output' => 'Hello',
|
|
122
|
+
'timestamp' => timestamp }]}}])
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it "reports the failure as part of the live output" do
|
|
126
|
+
live_output.size.must_equal 1
|
|
127
|
+
live_output.first['output_type'].must_equal 'stdout'
|
|
128
|
+
live_output.first['output'].must_equal 'Hello'
|
|
129
|
+
live_output.first['timestamp'].must_be_kind_of Float
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
29
133
|
end
|
|
30
134
|
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
require 'test_plugin_helper'
|
|
2
|
+
|
|
3
|
+
describe ForemanRemoteExecution::HostExtensions do
|
|
4
|
+
let(:provider) { 'Ssh' }
|
|
5
|
+
|
|
6
|
+
before do
|
|
7
|
+
User.current = FactoryGirl.build(:user, :admin)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
after { User.current = nil }
|
|
11
|
+
|
|
12
|
+
context 'host has multiple nics' do
|
|
13
|
+
let(:host) { FactoryGirl.build(:host, :with_execution) }
|
|
14
|
+
|
|
15
|
+
it 'should only have one execution interface' do
|
|
16
|
+
host.interfaces << FactoryGirl.build(:nic_managed)
|
|
17
|
+
host.interfaces.each { |interface| interface.execution = true }
|
|
18
|
+
host.wont_be :valid?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it 'returns the execution interface' do
|
|
22
|
+
host.execution_interface.must_be_kind_of Nic::Managed
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe 'proxy determination strategies' do
|
|
27
|
+
context 'subnet strategy' do
|
|
28
|
+
let(:host) { FactoryGirl.build(:host, :with_execution) }
|
|
29
|
+
it { host.remote_execution_proxies(provider)[:subnet].must_include host.subnet.remote_execution_proxies.first }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context 'fallback strategy' do
|
|
33
|
+
let(:host) { FactoryGirl.build(:host, :with_puppet) }
|
|
34
|
+
|
|
35
|
+
context 'enabled' do
|
|
36
|
+
before do
|
|
37
|
+
Setting[:remote_execution_fallback_proxy] = true
|
|
38
|
+
host.puppet_proxy.features << FactoryGirl.create(:feature, :ssh)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'returns a fallback proxy' do
|
|
42
|
+
host.remote_execution_proxies(provider)[:fallback].must_include host.puppet_proxy
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context 'disabled' do
|
|
47
|
+
before do
|
|
48
|
+
Setting[:remote_execution_fallback_proxy] = false
|
|
49
|
+
host.puppet_proxy.features << FactoryGirl.create(:feature, :ssh)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'returns no proxy' do
|
|
53
|
+
host.remote_execution_proxies(provider)[:fallback].must_be_empty
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
context 'global strategy' do
|
|
59
|
+
let(:organization) { FactoryGirl.build(:organization) }
|
|
60
|
+
let(:location) { FactoryGirl.build(:location) }
|
|
61
|
+
let(:host) { FactoryGirl.build(:host, :organization => organization, :location => location) }
|
|
62
|
+
let(:proxy_in_taxonomies) { FactoryGirl.create(:smart_proxy, :ssh, :organizations => [organization], :locations => [location]) }
|
|
63
|
+
let(:proxy_no_taxonomies) { FactoryGirl.create(:smart_proxy, :ssh) }
|
|
64
|
+
|
|
65
|
+
context 'enabled' do
|
|
66
|
+
before { Setting[:remote_execution_global_proxy] = true }
|
|
67
|
+
|
|
68
|
+
it 'returns correct proxies confined by taxonomies' do
|
|
69
|
+
proxy_in_taxonomies
|
|
70
|
+
proxy_no_taxonomies
|
|
71
|
+
host.remote_execution_proxies(provider)[:global].must_include proxy_in_taxonomies
|
|
72
|
+
host.remote_execution_proxies(provider)[:global].wont_include proxy_no_taxonomies
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'returns all proxies when there\'s no taxonomies' do
|
|
76
|
+
Taxonomy.stubs(:enabled_taxonomies).returns([])
|
|
77
|
+
host.remote_execution_proxies(provider)[:global].must_include proxy_in_taxonomies
|
|
78
|
+
host.remote_execution_proxies(provider)[:global].must_include proxy_no_taxonomies
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
context 'disabled' do
|
|
83
|
+
before { Setting[:remote_execution_global_proxy] = false }
|
|
84
|
+
it 'returns no proxy' do
|
|
85
|
+
host.remote_execution_proxies(provider)[:global].must_be_empty
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|