Module: IDRAC::Power

Included in:
Client
Defined in:
lib/idrac/power.rb

Constant Summary collapse

RESTART_TYPES =

Reset types that restart the host rather than leave it down. A restart never settles on "Off", so it must never be waited on — or escalated to — as if it were a shutdown.

%w[ForceRestart GracefulRestart PowerCycle].freeze

Instance Method Summary collapse

Instance Method Details

#get_power_stateObject



139
140
141
142
143
144
145
146
# File 'lib/idrac/power.rb', line 139

def get_power_state
  ensure_authenticated!
  
  # Get system information
  response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1?$select=PowerState")

  JSON.parse(response.body)&.dig("PowerState")
end

#get_power_usage_wattsObject Also known as: power_consumption_watts



148
149
150
151
152
153
154
# File 'lib/idrac/power.rb', line 148

def get_power_usage_watts
  ensure_authenticated!
  
  response = authenticated_request(:get, "/redfish/v1/Chassis/System.Embedded.1/Power")

  JSON.parse(response.body)&.dig("PowerControl", 0, "PowerConsumedWatts")&.to_f
end

#power_off(wait: true, kind: "ForceOff") ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/idrac/power.rb', line 65

def power_off(wait: true, kind: "ForceOff")
  # A restart is not a shutdown. Sent down the power-off path it fails the wait for "Off" and
  # escalates to ForceOff, which leaves the host powered down. Route it to the restart instead.
  return reboot(kind: kind, wait: wait) if RESTART_TYPES.include?(kind)

  ensure_authenticated!
  
  puts "Powering off server...".light_cyan
  
  # Check current power state first
  current_state = get_power_state rescue "Unknown"
  if current_state == "Off"
    puts "Server is already powered OFF.".yellow
    return false
  end
  
  # Send power off command
  path = "/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset"
  payload = { "ResetType" => kind }
  
  response = authenticated_request(:post, path, body: payload.to_json)

  if response.status == 409
    puts "Server is already powered OFF.".yellow
  end
  
  # Wait for power state change if requested
  if wait
    success = wait_for_power_state(target_state: "Off", tries: 6)
    
    # If graceful shutdown failed, try force shutdown
    if !success && kind != "ForceOff"
      return power_off(wait: wait, kind: "ForceOff")
    end
  end
  
  return true
end

#power_on(wait: true) ⇒ Object

Raises:



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/idrac/power.rb', line 6

def power_on(wait: true)
  ensure_authenticated!
  
  puts "Powering on server...".light_cyan
  
  # Check current power state first
  current_state = get_power_state rescue "Unknown"
  if current_state == "On"
    puts "Server is already powered ON.".yellow
    return false
  end
  
  # Send power on command (Reset with ResetType=On)
  path = "/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset"
  payload = { "ResetType" => "On" }
  
  tries = 10
  while tries > 0
    response = authenticated_request(:post, path, body: payload.to_json)
    
    case response.status
    when 200, 204
      puts "Server power on command sent successfully".green
      break
    when 409
      begin
        error_data = JSON.parse(response.body)
        if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"] &&
           error_data["error"]["@Message.ExtendedInfo"].any? { |m| m["Message"] =~ /Server is already powered ON/ }
          puts "Server is already powered ON.".yellow
          return false
        else
          raise Error, "Failed to power on: #{error_data.inspect}"
        end
      rescue JSON::ParserError
        raise Error, "Failed to power on with status 409: #{response.body}"
      end
    when 500
      puts "[iDRAC 500] Server is busy...".red
      tries -= 1
      puts "Retrying... #{tries}/10".yellow if tries > 0
      sleep 10
    else
      raise Error, "Unknown response code #{response.status}: #{response.body}"
    end
  end
  
  raise Error, "Failed to power on after 10 retries" if tries <= 0
  
  # Wait for power state change if requested
  wait_for_power_state(target_state: "On", tries: 10) if wait
  
  return true
end

#reboot(kind: "ForceRestart", wait: false) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/idrac/power.rb', line 104

def reboot(kind: "ForceRestart", wait: false)
  ensure_authenticated!
  
  puts "Rebooting server (#{kind})...".light_cyan

  # Check current power state first
  current_state = get_power_state rescue "Unknown"
  if current_state == "Off"
    puts "Server is currently off, powering on instead of rebooting".yellow
    return power_on(wait: wait)
  end
  
  path = "/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset"
  response = authenticated_request(:post, path, body: { "ResetType" => kind }.to_json)
  
  if response.status >= 200 && response.status < 300
    puts "Server reboot command sent successfully".green
  elsif response.status == 409 && kind != "GracefulRestart"
    error_data = JSON.parse(response.body) rescue nil
    puts "Received conflict (409) error from iDRAC: #{error_data.inspect}"
    # Try gracefulRestart as an alternative
    puts "Trying GracefulRestart instead...".yellow
    response = authenticated_request(:post, path, body: { "ResetType" => "GracefulRestart" }.to_json)
    raise Error, "Failed to reboot server. Status code: #{response.status}" unless response.status.between?(200, 299)
  else
    raise Error, "Failed to reboot server. Status code: #{response.status}"
  end
  
  # "On" is the only terminal state a restart reaches. Never wait for "Off" here: the host may
  # never report it, and treating that as failure is what turned a reboot into a shutdown.
  wait_for_power_state(target_state: "On", tries: 12) if wait
  
  return true
end