diff --git a/app/controllers/api/v2/hosts_controller.rb b/app/controllers/api/v2/hosts_controller.rb index 0dd4a381634..a279a2da87f 100644 --- a/app/controllers/api/v2/hosts_controller.rb +++ b/app/controllers/api/v2/hosts_controller.rb @@ -242,7 +242,7 @@ def disassociate api :PUT, "/hosts/:id/power", N_("Run a power operation on host") param :id, :identifier_dottable, :required => true - param :power_action, String, :required => true, :desc => N_("power action, valid actions are (on/start), (off/stop), (soft/reboot), (cycle/reset), (state/status)") + param :power_action, String, :required => true, :desc => N_("power action, valid actions are (on/start), (off/stop), reboot, reset, soft, cycle, (state/status)") def power unless @host.supports_power? diff --git a/app/services/power_manager/bmc.rb b/app/services/power_manager/bmc.rb index 18a1d2d26c4..f9ac01ce4bd 100644 --- a/app/services/power_manager/bmc.rb +++ b/app/services/power_manager/bmc.rb @@ -13,19 +13,39 @@ def ready? attr_reader :proxy + def power_action_v2 + smart_proxy = host.smart_proxies.with_features(:BMC).first + + return false unless smart_proxy + smart_proxy.has_capability?(:BMC, :power_action_v2) + end + # TODO: consider moving this to the proxy code, so we can just delegate like as with Virt. def action_map super.deep_merge({ :start => 'on', :stop => 'off', :poweroff => 'off', - :reboot => 'soft', - :reset => 'cycle', + :reboot => power_action_v2 ? 'do_reboot' : 'soft', + :reset => power_action_v2 ? 'do_reset' : 'cycle', :state => 'status', :ready? => 'ready?', }) end + # Avoid infinite loop when action_map maps :reboot to 'reboot'. + # This method was introduced as part of foreman #3073 and smart-proxy #38498 + # to explicitly handle reset/reboot actions, ensuring backward compatibility + # with older Smart Proxy implementations that do not support the new capabilities. + def do_reboot + default_action(:reboot) + end + + # For the same reason as above, this handles the case where :reset maps to 'reset'. + def do_reset + default_action(:reset) + end + def default_action(action) proxy.power(:action => action.to_s) # proxy.power(:action => 'on') end diff --git a/app/services/proxy_api/bmc.rb b/app/services/proxy_api/bmc.rb index bcf501a870b..03dcf8d07de 100644 --- a/app/services/proxy_api/bmc.rb +++ b/app/services/proxy_api/bmc.rb @@ -53,7 +53,7 @@ def power(args) full_path_as_hash = bmc_url_for_get('power', args[:action]) response = parse(get(full_path_as_hash[:path], query: full_path_as_hash[:query])) response.is_a?(Hash) ? response['result'] : response - when "on", "off", "cycle", "soft" + when "on", "off", "cycle", "soft", "reboot", "reset" res = parse put(with_provider(args), bmc_url_for('power', args[:action])) res && (res['result'] == true || res['result'] == "#{@target}: ok\n") else diff --git a/test/unit/power_manager_test.rb b/test/unit/power_manager_test.rb index 85c49455ffd..e9cd086e932 100644 --- a/test/unit/power_manager_test.rb +++ b/test/unit/power_manager_test.rb @@ -82,4 +82,54 @@ def actions_list(host) end actions.uniq end + + test "should call reboot and reset directly when power_action_v2 is supported" do + host = FactoryBot.build_stubbed(:host, :managed) + bmc_proxy_mock = mock('bmc_proxy') + nic_mock = mock('nic_bmc') + relation_mock = mock('smart_proxies_relation') + bmc_caps = mock('bmc_caps') + filtered_relation_mock = mock('filtered_relation_mock') + + host.stubs(:bmc_proxy).returns(bmc_proxy_mock) + host.stubs(:bmc_nic).returns(nic_mock) + nic_mock.stubs(:credentials_present?).returns(true) + nic_mock.stubs(:provider).returns('IPMI') + host.stubs(:bmc_available?).returns(true) + host.stubs(:smart_proxies).returns(relation_mock) + relation_mock.stubs(:with_features).with(:BMC).returns(filtered_relation_mock) + filtered_relation_mock.stubs(:first).returns(bmc_caps) + bmc_caps.stubs(:has_capability?).with(:BMC, :power_action_v2).returns(true) + + bmc_proxy_mock.expects(:power).with(:action => "reboot").returns(true) + bmc_proxy_mock.expects(:power).with(:action => "reset").returns(true) + + assert host.power.reboot + assert host.power.reset + end + + test "should fallback to soft and cycle when power_action_v2 is not supported" do + host = FactoryBot.build_stubbed(:host, :managed) + bmc_proxy_mock = mock('bmc_proxy') + nic_mock = mock('nic_bmc') + relation_mock = mock('smart_proxies_relation') + bmc_caps = mock('bmc_caps') + filtered_relation_mock = mock('filtered_relation_mock') + + host.stubs(:bmc_proxy).returns(bmc_proxy_mock) + host.stubs(:bmc_nic).returns(nic_mock) + nic_mock.stubs(:credentials_present?).returns(true) + nic_mock.stubs(:provider).returns('IPMI') + host.stubs(:bmc_available?).returns(true) + host.stubs(:smart_proxies).returns(relation_mock) + relation_mock.stubs(:with_features).with(:BMC).returns(filtered_relation_mock) + filtered_relation_mock.stubs(:first).returns(bmc_caps) + bmc_caps.stubs(:has_capability?).with(:BMC, :power_action_v2).returns(false) + + bmc_proxy_mock.expects(:power).with(:action => "soft").returns(true) + bmc_proxy_mock.expects(:power).with(:action => "cycle").returns(true) + + assert host.power.reboot + assert host.power.reset + end end diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/DetailsCard/PowerStatus/constants.js b/webpack/assets/javascripts/react_app/components/HostDetails/DetailsCard/PowerStatus/constants.js index 5cca80c9e7f..740b949523b 100644 --- a/webpack/assets/javascripts/react_app/components/HostDetails/DetailsCard/PowerStatus/constants.js +++ b/webpack/assets/javascripts/react_app/components/HostDetails/DetailsCard/PowerStatus/constants.js @@ -3,7 +3,7 @@ import { translate as __ } from '../../../../common/I18n'; export const POWER_REQURST_KEY = 'HOST_TOGGLE_POWER'; export const POWER_REQUEST_OPTIONS = { key: POWER_REQURST_KEY, params: { timeout: 30 } }; export const BASE_POWER_STATES = { off: __('Off'), on: __('On') }; -export const BMC_POWER_STATES = { soft: __('Reboot'), cycle: __('Reset') }; +export const BMC_POWER_STATES = { reboot: __('Reboot'), reset: __('Reset'), soft: __('Soft'), cycle: __('Cycle') }; export const SUPPORTED_POWER_STATES = { ...BASE_POWER_STATES, ...BMC_POWER_STATES,