Skip to content

Commit d20515f

Browse files
committed
Fixes #38498 - Add Reboot power operation to Redfish provider
Bug #3073 is caused by 3 things: 1. Foreman core doesn't call BMC proxy's API correctly - Changing power state to 'Reboot' via Web GUI calls BMC proxy's 'soft' power action - Changing power state to 'Reset' via Web GUI calls BMC proxy's 'cycle' power action 2. Foreman BMC proxy doesn't support reboot at all BMC provider 3. Foreman BMC proxy doesn't support reset(powerreset) at Redfish provider 2nd & 3rd item are mentioned in #38498. And this PR fixes 2nd item. For backward compatibility, poweraction_fix_3_17 capability is introduced to smart proxy at PRs related to #38498. PR related to #3073 adds logic to determine if smart proxy supports this capability into Foreman core.
1 parent 910ba9c commit d20515f

File tree

9 files changed

+156
-3
lines changed

9 files changed

+156
-3
lines changed

modules/bmc/base.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ def powercycle
4646
raise NotImplementedError.new
4747
end
4848

49+
def powerreboot
50+
raise NotImplementedError.new
51+
end
52+
4953
def bootdevice
5054
raise NotImplementedError.new
5155
end

modules/bmc/bmc_api.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ class Api < ::Sinatra::Base
163163
put "/:host/chassis/power/?:action?" do
164164
# return hint on valid options
165165
if params[:action].nil?
166-
return { :actions => ["on", "off", "cycle", "soft", "reset"] }.to_json
166+
return { :actions => ["on", "off", "cycle", "soft", "reset", "reboot"] }.to_json
167167
end
168168
bmc_setup
169169
begin
@@ -178,6 +178,8 @@ class Api < ::Sinatra::Base
178178
{ :action => params[:action], :result => @bmc.poweroff(true) }.to_json
179179
when "reset"
180180
{ :action => params[:action], :result => @bmc.powerreset }.to_json
181+
when "reboot"
182+
{ :action => params[:action], :result => @bmc.powerreboot }.to_json
181183
else
182184
{ :error => "The action: #{params[:action]} is not a valid action" }.to_json
183185
end

modules/bmc/bmc_plugin.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class Plugin < Proxy::Plugin
1111
capability 'shell'
1212
capability 'ssh'
1313
capability -> { Proxy::BMC::IPMI.providers_installed }
14+
capability 'poweraction_fix_3_17'
1415

1516
# Load IPMI to ensure the capabilities can be determined
1617
load_classes do

modules/bmc/redfish.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ def powercycle
7474
poweraction('ForceRestart')
7575
end
7676

77+
def powerreboot
78+
poweraction('GracefulRestart')
79+
end
80+
7781
def poweron
7882
poweraction('On')
7983
end

test/bmc/bmc_api_test.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
require 'json'
33
require 'bmc/bmc_plugin'
44
require 'bmc/bmc_api'
5+
require 'bmc/redfish'
6+
require 'test/bmc/redfish_test_helper'
57

68
ENV['RACK_ENV'] = 'test'
79

@@ -13,6 +15,7 @@
1315

1416
class BmcApiTest < Test::Unit::TestCase
1517
include Rack::Test::Methods
18+
include RedfishTestHelper
1619

1720
def app
1821
Proxy::BMC::Api.new
@@ -25,6 +28,7 @@ def setup
2528
provider ||= ENV["ipmiprovider"] || "ipmitool"
2629
@args = { 'bmc_provider' => provider, 'blah' => 'test' }
2730
authorize user, pass
31+
mask_redfish_acceess
2832
end
2933

3034
def test_api_throws_401_error_when_auth_is_not_provided
@@ -572,6 +576,13 @@ def test_api_throws_400_error_when_get_sensors_action_list_execption
572576
assert_equal 400, last_response.status
573577
end
574578

579+
def test_api_calls_redfish_provider_reboot
580+
expect = Proxy::BMC::Redfish.any_instance.stubs(:powerreboot)
581+
test_args = { 'bmc_provider' => 'redfish' }
582+
put "/#{@host}/chassis/power/reboot", test_args
583+
assert expect.once
584+
end
585+
575586
def test_api_can_pass_options_in_body
576587
Rubyipmi.stubs(:is_provider_installed?).returns(true)
577588
args = { 'bmc_provider' => 'freeipmi', :options => {:driver => 'lan20', :privilege => 'USER'} }.to_json

test/bmc/bmc_redfish_test.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
require 'test_helper'
2+
require 'bmc/bmc_plugin'
3+
require 'bmc/redfish'
4+
require 'test/bmc/redfish_test_helper'
5+
require 'json'
6+
7+
class BmcRedfishTest < Test::Unit::TestCase
8+
include RedfishTestHelper
9+
10+
def setup
11+
@host = "host"
12+
@protocol = "https"
13+
mask_redfish_acceess(protocol: @protocol, host: @host)
14+
@args = { :username => "user", :password => "pass", :host => @host }
15+
@bmc = Proxy::BMC::Redfish.new(@args)
16+
end
17+
18+
def test_redfish_provider_reboot
19+
stub_request(:post, "#{@protocol}://#{@host}#{SYSTEM_DATA['Actions']['#ComputerSystem.Reset']['target']}").
20+
with(body: JSON.generate({ "ResetType" => "GracefulRestart" })).
21+
to_return(status: 200, body: JSON.generate({}))
22+
assert @bmc.powerreboot
23+
end
24+
end

test/bmc/bmc_test.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ def test_should_power_reset
5656
assert bmc.powerreset
5757
end
5858

59+
def test_should_power_reboot
60+
assert_raise(NotImplementedError) do
61+
bmc.powerreboot
62+
end
63+
end
64+
5965
def test_should_bootpxe
6066
Rubyipmi::Ipmitool::Chassis.any_instance.expects(:bootpxe).returns(true)
6167
bmc.bootpxe

test/bmc/integration_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def test_features
1616
mod = response['bmc']
1717
refute_nil(mod)
1818
assert_equal('running', mod['state'], Proxy::LogBuffer::Buffer.instance.info[:failed_modules][:bmc])
19-
assert_equal(['redfish', 'shell', 'ssh'], mod['capabilities'])
19+
assert_equal(['poweraction_fix_3_17', 'redfish', 'shell', 'ssh'], mod['capabilities'])
2020

2121
assert_equal({}, mod['settings'])
2222
end
@@ -32,7 +32,7 @@ def test_features_with_freeipmi_installed
3232
mod = response['bmc']
3333
refute_nil(mod)
3434
assert_equal('running', mod['state'], Proxy::LogBuffer::Buffer.instance.info[:failed_modules][:bmc])
35-
assert_equal(['freeipmi', 'redfish', 'shell', 'ssh'], mod['capabilities'])
35+
assert_equal(['freeipmi', 'poweraction_fix_3_17', 'redfish', 'shell', 'ssh'], mod['capabilities'])
3636

3737
assert_equal({}, mod['settings'])
3838
end

test/bmc/redfish_test_helper.rb

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
require 'json'
2+
3+
module RedfishTestHelper
4+
ROOT_DATA = {
5+
"Name" => "Redfish Server Test Mock",
6+
"Systems" => {
7+
"@odata.id" => "/redfish/v1/Systems",
8+
},
9+
"Managers" => {
10+
"@odata.id" => "/redfish/v1/Managers",
11+
},
12+
"@odata.id" => "/redfish/v1",
13+
}.freeze
14+
15+
SYSTEMS_DATA = {
16+
"@odata.id" => "/redfish/v1/Systems",
17+
"Members" => [
18+
{ "@odata.id" => "/redfish/v1/Systems/TESTSYSTEM" },
19+
],
20+
}.freeze
21+
22+
SYSTEM_DATA = {
23+
"@odata.id" => "/redfish/v1/Systems/TESTSYSTEM",
24+
"Manufacture" => "Server Vendor",
25+
"IndicatorLED" => "Off",
26+
"PowerState" => "On",
27+
"Boot" => {
28+
"BootSourceOverrideTarget" => "Pxe",
29+
},
30+
"Model" => "TestServer A",
31+
"SerialNumber" => "AA645",
32+
"AssetTag" => "ax",
33+
"Actions" => {
34+
"#ComputerSystem.Reset" => {
35+
"target" => "/redfish/v1/Systems/TESTSYSTEM/Actions/ComputerSystem.Reset",
36+
},
37+
},
38+
}.freeze
39+
40+
MANAGERS_DATA = {
41+
"@odata.id" => "/redfish/v1/Managers",
42+
"Members" => [
43+
{ "@odata.id" => "/redfish/v1/Managers/TESTBMC" },
44+
],
45+
}.freeze
46+
47+
MANAGER_DATA = {
48+
"@odata.id" => "/redfish/v1/Managers/TESTBMC",
49+
"FirmwareVersion" => "v1.0.0",
50+
"UUID" => "dff64837-f76b-40c2-b1a2-4717a78d828d",
51+
"EthernetInterfaces" => {
52+
"@odata.id" => "/redfish/v1/Managers/TESTBMC/EthernetInterfaces",
53+
},
54+
}.freeze
55+
56+
ETHERNET_INTERFACES_DATA = {
57+
"@odata.id" => "/redfish/v1/Managers/TESTBMC/EthernetInterfaces",
58+
"Members" => [
59+
{ "@odata.id" => "/redfish/v1/Managers/TESTBMC/EthernetInterfaces/BMC" },
60+
],
61+
}.freeze
62+
63+
BMC_NIC_DATA = {
64+
"@odata.id" => "/redfish/v1/Managers/TESTBMC/EthernetInterfaces/BMC",
65+
"MACAddress" => "52:54:00:01:23:45",
66+
"IPv4Addresses" => [
67+
{ "Address" => "192.168.124.123",
68+
"Gateway" => "192.168.124.1",
69+
"SubnetMask" => "255.255.255.0",
70+
"AddressOrigin" => "DHCP" },
71+
],
72+
"VLAN" => {
73+
"VLANEnable" => true,
74+
"VLANId" => 10,
75+
},
76+
}.freeze
77+
78+
def mask_redfish_acceess(protocol: "https", host: "host")
79+
# root
80+
stub_request(:get, "#{protocol}://#{host}#{ROOT_DATA['@odata.id']}").
81+
to_return(status: 200, body: JSON.generate(ROOT_DATA))
82+
# root.systems
83+
stub_request(:get, "#{protocol}://#{host}#{SYSTEMS_DATA['@odata.id']}").
84+
to_return(status: 200, body: JSON.generate(SYSTEMS_DATA))
85+
# root.system
86+
stub_request(:get, "#{protocol}://#{host}#{SYSTEM_DATA['@odata.id']}").
87+
to_return(status: 200, body: JSON.generate(SYSTEM_DATA))
88+
# root.managers
89+
stub_request(:get, "#{protocol}://#{host}#{MANAGERS_DATA['@odata.id']}").
90+
to_return(status: 200, body: JSON.generate(MANAGERS_DATA))
91+
# root.manager
92+
stub_request(:get, "#{protocol}://#{host}#{MANAGER_DATA['@odata.id']}").
93+
to_return(status: 200, body: JSON.generate(MANAGER_DATA))
94+
# root.manager.ethernetinterfaces
95+
stub_request(:get, "#{protocol}://#{host}#{ETHERNET_INTERFACES_DATA['@odata.id']}").
96+
to_return(status: 200, body: JSON.generate(ETHERNET_INTERFACES_DATA))
97+
# bmcnic
98+
stub_request(:get, "#{protocol}://#{host}#{BMC_NIC_DATA['@odata.id']}").
99+
to_return(status: 200, body: JSON.generate(BMC_NIC_DATA))
100+
end
101+
end

0 commit comments

Comments
 (0)