idrac 0.9.3 → 0.9.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/lib/idrac/client.rb +38 -66
- data/lib/idrac/license.rb +82 -316
- data/lib/idrac/system.rb +73 -154
- data/lib/idrac/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cc0edda7bbcbf4e86782d914c909557f3bf6f24e5fa40284761f0e8d550c18b5
|
|
4
|
+
data.tar.gz: c8e3b1fa48d0eba346ab6d3410894e7ecce6c211c5f8719653c99bb384222e9e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '059e0a01815b8b138f247a9a2698a415f342decd78f3a3bc2c028215a0e1d6b9bc415f3ba1e4b2badb53d598910061072e9016ca491d9bf47e63bb3935ef1c1d'
|
|
7
|
+
data.tar.gz: a72e06912c09d645b45ffa5ecb52c703489729a0c8cac87b7cef55117802b396138624f9171318d222eb56ed0defd9442b4d3a5e14d7874a62c0e57d194fa837
|
data/lib/idrac/client.rb
CHANGED
|
@@ -165,34 +165,33 @@ module IDRAC
|
|
|
165
165
|
end
|
|
166
166
|
end
|
|
167
167
|
|
|
168
|
+
# GET that returns parsed JSON on 200, nil on error. Never raises.
|
|
169
|
+
# Use for exploratory/optional endpoints that may not exist.
|
|
170
|
+
def safe_get(path)
|
|
171
|
+
response = authenticated_request(:get, path) { |r| r }
|
|
172
|
+
return nil unless response.status == 200
|
|
173
|
+
JSON.parse(response.body)
|
|
174
|
+
rescue
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
|
|
168
178
|
private
|
|
169
179
|
|
|
170
|
-
#
|
|
171
|
-
def _perform_authenticated_request(method, path, options = {}
|
|
172
|
-
# Check retry count to prevent infinite recursion
|
|
173
|
-
if retry_count >= @retry_count
|
|
174
|
-
debug "Maximum retry count reached", 1, :red
|
|
175
|
-
raise Error, "Failed to authenticate after #{@retry_count} retries"
|
|
176
|
-
end
|
|
177
|
-
|
|
180
|
+
# Single-attempt authenticated request. Retry logic is in with_retries.
|
|
181
|
+
def _perform_authenticated_request(method, path, options = {})
|
|
178
182
|
debug "Authenticated request: #{method.to_s.upcase} #{path}", 1
|
|
179
|
-
|
|
180
|
-
# Extract options and prepare headers
|
|
183
|
+
|
|
181
184
|
body = options[:body]
|
|
182
185
|
headers = options[:headers] || {}
|
|
183
186
|
timeout = options[:timeout]
|
|
184
187
|
open_timeout = options[:open_timeout]
|
|
185
|
-
|
|
188
|
+
|
|
186
189
|
headers['User-Agent'] ||= 'iDRAC Ruby Client'
|
|
187
190
|
headers['Accept'] ||= 'application/json'
|
|
188
191
|
headers['Host'] = @host_header if @host_header
|
|
189
|
-
|
|
190
|
-
#
|
|
191
|
-
|
|
192
|
-
debug "Request body: #{body}", 2
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
# Determine authentication method and set headers
|
|
192
|
+
|
|
193
|
+
debug "Request body: #{body}", 2 if body && @verbosity >= 2
|
|
194
|
+
|
|
196
195
|
if @direct_mode
|
|
197
196
|
headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
|
198
197
|
debug "Using Basic Auth for request (direct mode)", 2
|
|
@@ -200,22 +199,22 @@ module IDRAC
|
|
|
200
199
|
headers['X-Auth-Token'] = session.x_auth_token
|
|
201
200
|
debug "Using X-Auth-Token for authentication", 2
|
|
202
201
|
end
|
|
203
|
-
|
|
204
|
-
# Make request with timeout handling
|
|
202
|
+
|
|
205
203
|
response = make_request_with_timeouts(method, path, body, headers, timeout, open_timeout)
|
|
206
|
-
|
|
207
|
-
# Handle authentication and connection errors
|
|
204
|
+
|
|
208
205
|
case response.status
|
|
209
206
|
when 401, 403
|
|
210
|
-
handle_auth_failure(method, path, options,
|
|
207
|
+
handle_auth_failure(method, path, options, nil)
|
|
211
208
|
else
|
|
212
209
|
debug "Response status: #{response.status}", 2
|
|
213
210
|
response
|
|
214
211
|
end
|
|
215
212
|
rescue Faraday::ConnectionFailed, Faraday::TimeoutError, Faraday::SSLError => e
|
|
216
|
-
handle_connection_error(e, method, path, options,
|
|
213
|
+
handle_connection_error(e, method, path, options, nil)
|
|
214
|
+
rescue IDRAC::Error
|
|
215
|
+
raise
|
|
217
216
|
rescue => e
|
|
218
|
-
handle_general_error(e, method, path, options,
|
|
217
|
+
handle_general_error(e, method, path, options, nil)
|
|
219
218
|
end
|
|
220
219
|
|
|
221
220
|
# Make request with timeout handling
|
|
@@ -241,56 +240,29 @@ module IDRAC
|
|
|
241
240
|
end
|
|
242
241
|
end
|
|
243
242
|
|
|
244
|
-
# Handle authentication failures
|
|
243
|
+
# Handle authentication failures — recreate session, then raise so with_retries retries
|
|
245
244
|
def handle_auth_failure(method, path, options, retry_count)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
session.delete if session.x_auth_token
|
|
253
|
-
|
|
254
|
-
if session.create
|
|
255
|
-
debug "New session created, retrying request...", 1, :green
|
|
256
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
|
257
|
-
else
|
|
258
|
-
debug "Session creation failed, falling back to direct mode...", 1, :light_yellow
|
|
259
|
-
@direct_mode = true
|
|
260
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
|
261
|
-
end
|
|
245
|
+
debug "Authentication failed (401/403), recreating session...", 1, :light_yellow
|
|
246
|
+
session.delete if session.x_auth_token
|
|
247
|
+
|
|
248
|
+
unless session.create
|
|
249
|
+
debug "Session creation failed, falling back to direct mode", 1, :light_yellow
|
|
250
|
+
@direct_mode = true
|
|
262
251
|
end
|
|
252
|
+
|
|
253
|
+
raise Error, "Authentication failed, session recreated"
|
|
263
254
|
end
|
|
264
255
|
|
|
265
|
-
# Handle connection errors
|
|
256
|
+
# Handle connection errors — raise so with_retries can handle retry logic
|
|
266
257
|
def handle_connection_error(error, method, path, options, retry_count)
|
|
267
258
|
debug "Connection error: #{error.message}", 1, :red
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if @direct_mode || session.x_auth_token
|
|
271
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
|
272
|
-
elsif session.create
|
|
273
|
-
debug "Created new session after connection error", 1, :green
|
|
274
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
|
275
|
-
else
|
|
276
|
-
@direct_mode = true
|
|
277
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
|
278
|
-
end
|
|
259
|
+
raise Error, "Connection failed: #{error.message}"
|
|
279
260
|
end
|
|
280
|
-
|
|
281
|
-
# Handle general errors
|
|
261
|
+
|
|
262
|
+
# Handle general errors — raise so with_retries can handle retry logic
|
|
282
263
|
def handle_general_error(error, method, path, options, retry_count)
|
|
283
264
|
debug "Error during request: #{error.message}", 1, :red
|
|
284
|
-
|
|
285
|
-
if @direct_mode
|
|
286
|
-
raise Error, "Error during authenticated request: #{error.message}"
|
|
287
|
-
elsif session.create
|
|
288
|
-
debug "Created new session after error, retrying...", 1, :green
|
|
289
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
|
290
|
-
else
|
|
291
|
-
@direct_mode = true
|
|
292
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
|
293
|
-
end
|
|
265
|
+
raise Error, "Request failed: #{error.message}"
|
|
294
266
|
end
|
|
295
267
|
|
|
296
268
|
def _perform_get(path:, headers: {})
|
data/lib/idrac/license.rb
CHANGED
|
@@ -1,359 +1,125 @@
|
|
|
1
1
|
module IDRAC
|
|
2
2
|
module License
|
|
3
3
|
# Gets the license information from the iDRAC
|
|
4
|
-
# @return [Hash] License details
|
|
4
|
+
# @return [Hash, nil] License details
|
|
5
5
|
def license_info
|
|
6
|
-
# Try
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
debug "License collection: #{license_data}", 2
|
|
12
|
-
|
|
13
|
-
# Check if there are any license entries
|
|
14
|
-
if !license_data["Members"] || license_data["Members"].empty?
|
|
15
|
-
debug "No licenses found", 1, :yellow
|
|
16
|
-
return try_dell_oem_license_path()
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# Get the first license in the list
|
|
20
|
-
license_uri = license_data["Members"][0]["@odata.id"]
|
|
21
|
-
debug "Using license URI: #{license_uri}", 2
|
|
22
|
-
|
|
23
|
-
# Get detailed license information
|
|
24
|
-
license_response = authenticated_request(:get, license_uri)
|
|
25
|
-
if license_response.status != 200
|
|
26
|
-
debug "Failed to retrieve license details: #{license_response.status}", 1, :red
|
|
27
|
-
return try_dell_oem_license_path()
|
|
6
|
+
# Try standard endpoint first (iDRAC 9+)
|
|
7
|
+
if (data = safe_get("/redfish/v1/LicenseService/Licenses"))
|
|
8
|
+
if data["Members"]&.any?
|
|
9
|
+
license_uri = data["Members"][0]["@odata.id"]
|
|
10
|
+
return safe_get(license_uri) || try_dell_oem_license_path
|
|
28
11
|
end
|
|
29
|
-
|
|
30
|
-
license_details = JSON.parse(license_response.body)
|
|
31
|
-
debug "License details: #{license_details}", 2
|
|
32
|
-
|
|
33
|
-
return license_details
|
|
34
|
-
else
|
|
35
|
-
# The endpoint is not available (probably iDRAC 8)
|
|
36
|
-
debug "Standard license endpoint failed: #{response.status}, trying Dell OEM path", 1, :yellow
|
|
37
|
-
return try_dell_oem_license_path()
|
|
38
12
|
end
|
|
13
|
+
|
|
14
|
+
try_dell_oem_license_path
|
|
39
15
|
end
|
|
40
16
|
|
|
41
|
-
# Extracts the iDRAC
|
|
42
|
-
# @return [Integer, nil]
|
|
17
|
+
# Extracts the iDRAC generation (e.g. 8, 9) from license or server header
|
|
18
|
+
# @return [Integer, nil]
|
|
43
19
|
def license_version
|
|
44
|
-
# Use memoization to cache the result and avoid multiple API calls
|
|
45
20
|
@license_version ||= compute_license_version
|
|
46
21
|
end
|
|
47
|
-
|
|
48
|
-
# Clear the cached license version (useful if iDRAC state changes)
|
|
22
|
+
|
|
49
23
|
def clear_license_version_cache
|
|
50
24
|
@license_version = nil
|
|
51
25
|
end
|
|
52
|
-
|
|
26
|
+
|
|
53
27
|
private
|
|
54
|
-
|
|
28
|
+
|
|
55
29
|
def compute_license_version
|
|
56
|
-
# First try to get from license info
|
|
57
30
|
license = license_info
|
|
58
31
|
if license
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
debug "Found license version from Description: #{version}", 1
|
|
64
|
-
return version
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Try alternative fields if Description didn't work
|
|
68
|
-
if license["Name"]&.match(/iDRAC(\d+)/i)
|
|
69
|
-
version = license["Name"].match(/iDRAC(\d+)/i)[1].to_i
|
|
70
|
-
debug "Found license version from Name: #{version}", 1
|
|
71
|
-
return version
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# For Dell OEM license response format
|
|
75
|
-
if license["LicenseDescription"]&.match(/iDRAC(\d+)/i)
|
|
76
|
-
version = license["LicenseDescription"].match(/iDRAC(\d+)/i)[1].to_i
|
|
77
|
-
debug "Found license version from LicenseDescription: #{version}", 1
|
|
78
|
-
return version
|
|
32
|
+
%w[Description Name LicenseDescription].each do |field|
|
|
33
|
+
if license[field]&.match(/iDRAC(\d+)/i)
|
|
34
|
+
return $1.to_i
|
|
35
|
+
end
|
|
79
36
|
end
|
|
80
37
|
end
|
|
81
|
-
|
|
82
|
-
#
|
|
83
|
-
|
|
84
|
-
response
|
|
85
|
-
|
|
86
|
-
version = response.headers["server"].match(/iDRAC\/(\d+)/i)[1].to_i
|
|
87
|
-
debug "Found license version from server header: #{version}", 1
|
|
88
|
-
return version
|
|
38
|
+
|
|
39
|
+
# Fall back to server header
|
|
40
|
+
response = authenticated_request(:get, "/redfish/v1") { |r| r }
|
|
41
|
+
if response.headers["server"]&.match(/iDRAC\/(\d+)/i)
|
|
42
|
+
return $1.to_i
|
|
89
43
|
end
|
|
90
|
-
|
|
91
|
-
debug "Could not determine license version from license info or server header", 1, :yellow
|
|
44
|
+
|
|
92
45
|
nil
|
|
93
46
|
end
|
|
94
|
-
|
|
95
|
-
# Attempt to get license information using Dell OEM extension path (for iDRAC 8)
|
|
96
|
-
# @return [Hash, nil] License info or nil if not found
|
|
47
|
+
|
|
97
48
|
def try_dell_oem_license_path
|
|
98
|
-
# Try
|
|
99
|
-
|
|
100
|
-
"
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
# Check for license info in this response based on the path
|
|
113
|
-
if path.include?("DellLicenseManagementService")
|
|
114
|
-
return handle_dell_license_service_response(data)
|
|
115
|
-
elsif path.include?("Attributes")
|
|
116
|
-
return handle_dell_attributes_response(data)
|
|
117
|
-
elsif path.include?("iDRAC.Embedded.1") && !path.include?("Attributes")
|
|
118
|
-
return handle_dell_manager_response(data)
|
|
49
|
+
# Try Dell OEM license service (iDRAC 8)
|
|
50
|
+
if (data = safe_get("/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/DellLicenseManagementService/Licenses"))
|
|
51
|
+
if data["Members"]&.any?
|
|
52
|
+
license_uri = data["Members"][0]["@odata.id"]
|
|
53
|
+
if (details = safe_get(license_uri))
|
|
54
|
+
return {
|
|
55
|
+
"Id" => details["EntitlementID"] || "iDRAC-License",
|
|
56
|
+
"Description" => details["LicenseDescription"] || "iDRAC License",
|
|
57
|
+
"Name" => details["LicenseDescription"] || "iDRAC License",
|
|
58
|
+
"LicenseType" => details["LicenseType"] || license_type_from(details["LicenseDescription"]),
|
|
59
|
+
"Status" => { "Health" => "OK" },
|
|
60
|
+
"Removable" => true
|
|
61
|
+
}
|
|
119
62
|
end
|
|
120
|
-
else
|
|
121
|
-
debug "Dell path #{path} response status: #{response.status}", 3
|
|
122
63
|
end
|
|
123
64
|
end
|
|
124
|
-
|
|
125
|
-
#
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
"Id" => "iDRAC-License",
|
|
132
|
-
"Description" => "iDRAC8 #{license_type} License",
|
|
133
|
-
"Name" => "iDRAC License",
|
|
134
|
-
"LicenseType" => license_type,
|
|
135
|
-
"Status" => { "Health" => "OK" },
|
|
136
|
-
"Removable" => false,
|
|
137
|
-
"EntitlementID" => service_tag # Dell often uses service tag as entitlement ID
|
|
138
|
-
}
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
# Fall back to feature detection if all else fails
|
|
142
|
-
debug "All Dell OEM license paths failed, using fallback detection", 1, :yellow
|
|
143
|
-
return create_fallback_license_info()
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
# Handle response from Dell License Management Service
|
|
147
|
-
def handle_dell_license_service_response(data)
|
|
148
|
-
# Check if there are any license entries
|
|
149
|
-
if !data["Members"] || data["Members"].empty?
|
|
150
|
-
debug "No licenses found in Dell OEM path", 1, :yellow
|
|
151
|
-
return create_fallback_license_info(use_basic: true)
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
# Get the first license in the list
|
|
155
|
-
license_uri = data["Members"][0]["@odata.id"]
|
|
156
|
-
debug "Using Dell OEM license URI: #{license_uri}", 2
|
|
157
|
-
|
|
158
|
-
# Get detailed license information
|
|
159
|
-
license_response = authenticated_request(:get, license_uri)
|
|
160
|
-
if license_response.status != 200
|
|
161
|
-
debug "Failed to retrieve Dell OEM license details: #{license_response.status}", 1, :red
|
|
162
|
-
return create_fallback_license_info(use_basic: true)
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
dell_license = JSON.parse(license_response.body)
|
|
166
|
-
debug "Dell OEM license details: #{dell_license}", 2
|
|
167
|
-
|
|
168
|
-
# Convert Dell OEM license format to standard format
|
|
169
|
-
license_info = {
|
|
170
|
-
"Id" => dell_license["EntitlementID"] || "iDRAC-License",
|
|
171
|
-
"Description" => dell_license["LicenseDescription"] || "iDRAC License",
|
|
172
|
-
"Name" => dell_license["LicenseDescription"] || "iDRAC License",
|
|
173
|
-
"LicenseType" => dell_license["LicenseType"] || get_license_type_from_description(dell_license["LicenseDescription"]),
|
|
174
|
-
"Status" => { "Health" => "OK" },
|
|
175
|
-
"Removable" => true
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return license_info
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
# Handle response from Dell Manager attributes
|
|
182
|
-
def handle_dell_attributes_response(data)
|
|
183
|
-
# Look for license information in attributes
|
|
184
|
-
if data["Attributes"] && (
|
|
185
|
-
data["Attributes"]["LicensableDevice.1.LicenseInfo.1"] ||
|
|
186
|
-
data["Attributes"]["System.ServerOS.1.OSName"] ||
|
|
187
|
-
data["Attributes"]["iDRAC.Info.1.LicensingInfo"]
|
|
188
|
-
)
|
|
189
|
-
|
|
190
|
-
license_info = data["Attributes"]["LicensableDevice.1.LicenseInfo.1"] ||
|
|
191
|
-
data["Attributes"]["iDRAC.Info.1.LicensingInfo"]
|
|
192
|
-
|
|
193
|
-
if license_info
|
|
194
|
-
license_type = license_info.include?("Enterprise") ? "Enterprise" :
|
|
195
|
-
license_info.include?("Express") ? "Express" : "Basic"
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
"Id" => "iDRAC-License",
|
|
199
|
-
"Description" => "iDRAC8 #{license_type} License",
|
|
200
|
-
"Name" => "iDRAC License",
|
|
201
|
-
"LicenseType" => license_type,
|
|
202
|
-
"Status" => { "Health" => "OK" },
|
|
203
|
-
"Removable" => false
|
|
204
|
-
}
|
|
205
|
-
end
|
|
65
|
+
|
|
66
|
+
# Try manager attributes
|
|
67
|
+
if (data = safe_get("/redfish/v1/Managers/iDRAC.Embedded.1/Attributes"))
|
|
68
|
+
attr = data.dig("Attributes", "LicensableDevice.1.LicenseInfo.1") ||
|
|
69
|
+
data.dig("Attributes", "iDRAC.Info.1.LicensingInfo")
|
|
70
|
+
return build_license_hash(license_type_from(attr)) if attr
|
|
206
71
|
end
|
|
207
|
-
|
|
208
|
-
#
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
# Look for license information in Oem data
|
|
223
|
-
if data["Oem"] && data["Oem"]["Dell"]
|
|
224
|
-
dell_data = data["Oem"]["Dell"]
|
|
225
|
-
|
|
226
|
-
if dell_data["DellLicenseManagementService"]
|
|
227
|
-
# Found license service reference, but need to query it directly
|
|
228
|
-
service_uri = dell_data["DellLicenseManagementService"]["@odata.id"]
|
|
229
|
-
debug "Found license service URI: #{service_uri}", 2
|
|
230
|
-
|
|
231
|
-
service_response = authenticated_request(:get, service_uri)
|
|
232
|
-
if service_response.status == 200
|
|
233
|
-
return handle_dell_license_service_response(JSON.parse(service_response.body))
|
|
72
|
+
|
|
73
|
+
# Try manager entity OEM data
|
|
74
|
+
if (data = safe_get("/redfish/v1/Managers/iDRAC.Embedded.1"))
|
|
75
|
+
if (service_uri = data.dig("Oem", "Dell", "DellLicenseManagementService", "@odata.id"))
|
|
76
|
+
if (svc_data = safe_get(service_uri)) && svc_data["Members"]&.any?
|
|
77
|
+
if (details = safe_get(svc_data["Members"][0]["@odata.id"]))
|
|
78
|
+
return {
|
|
79
|
+
"Id" => details["EntitlementID"] || "iDRAC-License",
|
|
80
|
+
"Description" => details["LicenseDescription"] || "iDRAC License",
|
|
81
|
+
"Name" => details["LicenseDescription"] || "iDRAC License",
|
|
82
|
+
"LicenseType" => details["LicenseType"] || license_type_from(details["LicenseDescription"]),
|
|
83
|
+
"Status" => { "Health" => "OK" },
|
|
84
|
+
"Removable" => true
|
|
85
|
+
}
|
|
86
|
+
end
|
|
234
87
|
end
|
|
235
88
|
end
|
|
236
|
-
|
|
237
|
-
|
|
89
|
+
|
|
90
|
+
dell_data = data.dig("Oem", "Dell") || {}
|
|
238
91
|
if dell_data["LicenseType"] || dell_data["License"]
|
|
239
|
-
|
|
240
|
-
(dell_data["License"] && dell_data["License"].include?("Enterprise") ? "Enterprise" :
|
|
241
|
-
dell_data["License"].include?("Express") ? "Express" : "Basic")
|
|
242
|
-
|
|
243
|
-
return {
|
|
244
|
-
"Id" => "iDRAC-License",
|
|
245
|
-
"Description" => "iDRAC8 #{license_type} License",
|
|
246
|
-
"Name" => "iDRAC License",
|
|
247
|
-
"LicenseType" => license_type,
|
|
248
|
-
"Status" => { "Health" => "OK" },
|
|
249
|
-
"Removable" => false
|
|
250
|
-
}
|
|
251
|
-
end
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
# Check for license type in model name or description
|
|
255
|
-
if data["Model"] && data["Model"].include?("Enterprise")
|
|
256
|
-
return {
|
|
257
|
-
"Id" => "iDRAC-License",
|
|
258
|
-
"Description" => "iDRAC8 Enterprise License",
|
|
259
|
-
"Name" => "iDRAC License",
|
|
260
|
-
"LicenseType" => "Enterprise",
|
|
261
|
-
"Status" => { "Health" => "OK" },
|
|
262
|
-
"Removable" => false
|
|
263
|
-
}
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
# If no license info found in manager data, fall back to feature detection
|
|
267
|
-
license_type = determine_license_type()
|
|
268
|
-
return {
|
|
269
|
-
"Id" => "iDRAC-License",
|
|
270
|
-
"Description" => "iDRAC8 #{license_type} License",
|
|
271
|
-
"Name" => "iDRAC License",
|
|
272
|
-
"LicenseType" => license_type,
|
|
273
|
-
"Status" => { "Health" => "OK" },
|
|
274
|
-
"Removable" => false
|
|
275
|
-
}
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
# Get service tag from system info
|
|
279
|
-
def get_service_tag
|
|
280
|
-
response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1")
|
|
281
|
-
if response.status == 200
|
|
282
|
-
data = JSON.parse(response.body)
|
|
283
|
-
return data["SKU"] if data["SKU"] # Service tag is usually in SKU field
|
|
284
|
-
end
|
|
285
|
-
|
|
286
|
-
# Try alternate location
|
|
287
|
-
response = authenticated_request(:get, "/redfish/v1")
|
|
288
|
-
if response.status == 200
|
|
289
|
-
data = JSON.parse(response.body)
|
|
290
|
-
if data["Oem"] && data["Oem"]["Dell"] && data["Oem"]["Dell"]["ServiceTag"]
|
|
291
|
-
return data["Oem"]["Dell"]["ServiceTag"]
|
|
92
|
+
return build_license_hash(license_type_from(dell_data["LicenseType"] || dell_data["License"]))
|
|
292
93
|
end
|
|
293
94
|
end
|
|
294
|
-
|
|
295
|
-
|
|
95
|
+
|
|
96
|
+
# Last resort: detect from features
|
|
97
|
+
build_license_hash(detect_license_type)
|
|
296
98
|
end
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
return "
|
|
301
|
-
|
|
302
|
-
if
|
|
303
|
-
|
|
304
|
-
elsif description.include?("Express")
|
|
305
|
-
return "Express"
|
|
306
|
-
elsif description.include?("Datacenter")
|
|
307
|
-
return "Datacenter"
|
|
308
|
-
else
|
|
309
|
-
return "Basic"
|
|
310
|
-
end
|
|
99
|
+
|
|
100
|
+
def license_type_from(str)
|
|
101
|
+
return "Unknown" unless str
|
|
102
|
+
return "Enterprise" if str.include?("Enterprise")
|
|
103
|
+
return "Datacenter" if str.include?("Datacenter")
|
|
104
|
+
return "Express" if str.include?("Express")
|
|
105
|
+
"Basic"
|
|
311
106
|
end
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
# @return [Hash] A basic license info object
|
|
316
|
-
def create_fallback_license_info(use_basic: false)
|
|
317
|
-
# Get the iDRAC version number from server headers
|
|
318
|
-
version = nil
|
|
319
|
-
response = authenticated_request(:get, "/redfish/v1")
|
|
320
|
-
if response.headers["server"] && response.headers["server"].match(/iDRAC\/(\d+)/i)
|
|
321
|
-
version = response.headers["server"].match(/iDRAC\/(\d+)/i)[1].to_i
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
# Try to determine if it's Enterprise or Express based on available features
|
|
325
|
-
license_type = use_basic ? "Basic" : determine_license_type
|
|
326
|
-
|
|
327
|
-
license_info = {
|
|
107
|
+
|
|
108
|
+
def build_license_hash(type)
|
|
109
|
+
{
|
|
328
110
|
"Id" => "iDRAC-License",
|
|
329
|
-
"Description" =>
|
|
111
|
+
"Description" => "iDRAC #{type} License",
|
|
330
112
|
"Name" => "iDRAC License",
|
|
331
|
-
"LicenseType" =>
|
|
113
|
+
"LicenseType" => type,
|
|
332
114
|
"Status" => { "Health" => "OK" },
|
|
333
115
|
"Removable" => false
|
|
334
116
|
}
|
|
335
|
-
|
|
336
|
-
debug "Created fallback license info: #{license_info}", 2
|
|
337
|
-
license_info
|
|
338
117
|
end
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
begin
|
|
345
|
-
# For example, virtual media is typically an Enterprise feature
|
|
346
|
-
virtual_media_response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/VirtualMedia")
|
|
347
|
-
# If we successfully get virtual media, it's likely Enterprise
|
|
348
|
-
if virtual_media_response.status == 200
|
|
349
|
-
return "Enterprise"
|
|
350
|
-
end
|
|
351
|
-
rescue
|
|
352
|
-
# If the request fails, don't fail the whole method
|
|
353
|
-
end
|
|
354
|
-
|
|
355
|
-
# Default to basic license if we can't determine
|
|
356
|
-
return "Express"
|
|
118
|
+
|
|
119
|
+
def detect_license_type
|
|
120
|
+
# Virtual media is typically Enterprise-only
|
|
121
|
+
data = safe_get("/redfish/v1/Managers/iDRAC.Embedded.1/VirtualMedia")
|
|
122
|
+
data ? "Enterprise" : "Express"
|
|
357
123
|
end
|
|
358
124
|
end
|
|
359
|
-
end
|
|
125
|
+
end
|
data/lib/idrac/system.rb
CHANGED
|
@@ -314,161 +314,80 @@ module IDRAC
|
|
|
314
314
|
|
|
315
315
|
# Get PCI device information
|
|
316
316
|
def pci_devices
|
|
317
|
-
#
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
end
|
|
354
|
-
|
|
355
|
-
return pci
|
|
356
|
-
rescue JSON::ParserError
|
|
357
|
-
raise Error, "Failed to parse PCI devices response: #{response.body}"
|
|
358
|
-
end
|
|
359
|
-
else
|
|
360
|
-
# For iDRAC 8, try Dell's recommended approach using System endpoint with PCIeDevices select option
|
|
361
|
-
system_pcie_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1?$select=PCIeDevices")
|
|
362
|
-
|
|
363
|
-
if system_pcie_response.status == 200
|
|
364
|
-
begin
|
|
365
|
-
system_data = JSON.parse(system_pcie_response.body)
|
|
366
|
-
|
|
367
|
-
if system_data.key?("PCIeDevices") && !system_data["PCIeDevices"].empty?
|
|
368
|
-
pci_devices = []
|
|
369
|
-
|
|
370
|
-
# Process each PCIe device
|
|
371
|
-
system_data["PCIeDevices"].each do |device_link|
|
|
372
|
-
if device_link.is_a?(Hash) && device_link["@odata.id"]
|
|
373
|
-
device_path = device_link["@odata.id"]
|
|
374
|
-
device_response = authenticated_request(:get, device_path)
|
|
375
|
-
|
|
376
|
-
if device_response.status == 200
|
|
377
|
-
device_data = JSON.parse(device_response.body)
|
|
378
|
-
|
|
379
|
-
pci_devices << {
|
|
380
|
-
"device_class" => device_data["DeviceType"] || "Unknown",
|
|
381
|
-
"manufacturer" => device_data["Manufacturer"],
|
|
382
|
-
"name" => device_data["Name"] || device_data["Id"],
|
|
383
|
-
"description" => device_data["Description"],
|
|
384
|
-
"id" => device_data["Id"],
|
|
385
|
-
"slot_type" => device_data.dig("Oem", "Dell", "SlotType"),
|
|
386
|
-
"bus_width" => device_data.dig("Oem", "Dell", "BusWidth"),
|
|
387
|
-
"nic" => nil
|
|
388
|
-
}
|
|
389
|
-
end
|
|
390
|
-
end
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
return pci_devices unless pci_devices.empty?
|
|
394
|
-
end
|
|
395
|
-
rescue JSON::ParserError
|
|
396
|
-
# Continue to next approach
|
|
397
|
-
end
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
# Try NetworkAdapters as an alternative for finding PCIe devices (especially NICs and FC adapters)
|
|
401
|
-
nic_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/NetworkAdapters?$expand=*($levels=1)")
|
|
402
|
-
|
|
403
|
-
if nic_response.status == 200
|
|
404
|
-
begin
|
|
405
|
-
nic_data = JSON.parse(nic_response.body)
|
|
406
|
-
|
|
407
|
-
pci_devices = []
|
|
408
|
-
|
|
409
|
-
# Extract PCI info from network adapters
|
|
410
|
-
if nic_data["Members"] && !nic_data["Members"].empty?
|
|
411
|
-
nic_data["Members"].each do |adapter|
|
|
412
|
-
next unless adapter["Model"] || adapter["Manufacturer"]
|
|
413
|
-
|
|
414
|
-
# Check if this is a Fiber Channel adapter by name or model
|
|
415
|
-
is_fc = (adapter["Name"] =~ /FC/i || adapter["Model"] =~ /FC/i ||
|
|
416
|
-
adapter["Id"] =~ /FC/i || adapter["Description"] =~ /Fibre/i) ? true : false
|
|
417
|
-
|
|
418
|
-
device_class = is_fc ? "FibreChannelController" : "NetworkController"
|
|
419
|
-
|
|
420
|
-
pci_devices << {
|
|
421
|
-
"device_class" => device_class,
|
|
422
|
-
"manufacturer" => adapter["Manufacturer"],
|
|
423
|
-
"name" => adapter["Name"] || adapter["Id"],
|
|
424
|
-
"description" => adapter["Description"],
|
|
425
|
-
"id" => adapter["Id"],
|
|
426
|
-
"slot_type" => adapter.dig("Oem", "Dell", "SlotType") ||
|
|
427
|
-
(adapter["Id"] =~ /Slot\.(\d+)/ ? "Slot #{$1}" : nil),
|
|
428
|
-
"bus_width" => nil,
|
|
429
|
-
"nic" => adapter["@odata.id"]
|
|
430
|
-
}
|
|
431
|
-
end
|
|
432
|
-
|
|
433
|
-
return pci_devices unless pci_devices.empty?
|
|
434
|
-
end
|
|
435
|
-
rescue JSON::ParserError
|
|
436
|
-
# Continue to fallback
|
|
437
|
-
end
|
|
438
|
-
end
|
|
439
|
-
|
|
440
|
-
# Last resort: check if PCIeFunctions are directly available
|
|
441
|
-
pcie_functions_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/PCIeFunctions?$expand=*($levels=1)")
|
|
442
|
-
|
|
443
|
-
if pcie_functions_response.status == 200
|
|
444
|
-
begin
|
|
445
|
-
functions_data = JSON.parse(pcie_functions_response.body)
|
|
446
|
-
|
|
447
|
-
if functions_data["Members"] && !functions_data["Members"].empty?
|
|
448
|
-
pci_devices = functions_data["Members"].map do |function|
|
|
449
|
-
{
|
|
450
|
-
"device_class" => function["DeviceClass"] || "Unknown",
|
|
451
|
-
"manufacturer" => function["Manufacturer"] || "Unknown",
|
|
452
|
-
"name" => function["Name"] || function["Id"],
|
|
453
|
-
"description" => function["Description"],
|
|
454
|
-
"id" => function["Id"],
|
|
455
|
-
"slot_type" => function.dig("Oem", "Dell", "SlotType"),
|
|
456
|
-
"bus_width" => function.dig("Oem", "Dell", "DataBusWidth"),
|
|
457
|
-
"nic" => nil
|
|
458
|
-
}
|
|
459
|
-
end
|
|
460
|
-
|
|
461
|
-
return pci_devices
|
|
462
|
-
end
|
|
463
|
-
rescue JSON::ParserError
|
|
464
|
-
# Continue to fallback
|
|
465
|
-
end
|
|
466
|
-
end
|
|
467
|
-
|
|
468
|
-
# Fallback for any version when all endpoints unavailable
|
|
469
|
-
puts "PCI device information not available through standard or alternative endpoints" if @verbose
|
|
470
|
-
return []
|
|
317
|
+
# iDRAC 9+: standard PCIeDevices endpoint with expand
|
|
318
|
+
if (data = safe_get("/redfish/v1/Chassis/System.Embedded.1/PCIeDevices?$expand=*($levels=1)"))
|
|
319
|
+
return data["Members"].map { |stub|
|
|
320
|
+
fn = stub.dig("Links", "PCIeFunctions", 0, "@odata.id")
|
|
321
|
+
pcie_fn = fn ? safe_get(fn) : nil
|
|
322
|
+
{
|
|
323
|
+
"device_class" => pcie_fn&.dig("DeviceClass"),
|
|
324
|
+
"manufacturer" => stub["Manufacturer"],
|
|
325
|
+
"name" => stub["Name"],
|
|
326
|
+
"description" => stub["Description"],
|
|
327
|
+
"id" => pcie_fn&.dig("Id") || stub["Id"],
|
|
328
|
+
"slot_type" => pcie_fn&.dig("Oem", "Dell", "DellPCIeFunction", "SlotType"),
|
|
329
|
+
"bus_width" => pcie_fn&.dig("Oem", "Dell", "DellPCIeFunction", "DataBusWidth"),
|
|
330
|
+
"nic" => pcie_fn&.dig("Links", "EthernetInterfaces", 0, "@odata.id")
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# iDRAC 8: PCIeDevices via System endpoint
|
|
336
|
+
if (data = safe_get("/redfish/v1/Systems/System.Embedded.1?$select=PCIeDevices"))
|
|
337
|
+
devices = Array(data["PCIeDevices"]).filter_map { |link|
|
|
338
|
+
next unless link.is_a?(Hash) && link["@odata.id"]
|
|
339
|
+
d = safe_get(link["@odata.id"])
|
|
340
|
+
next unless d
|
|
341
|
+
{
|
|
342
|
+
"device_class" => d["DeviceType"] || "Unknown",
|
|
343
|
+
"manufacturer" => d["Manufacturer"],
|
|
344
|
+
"name" => d["Name"] || d["Id"],
|
|
345
|
+
"description" => d["Description"],
|
|
346
|
+
"id" => d["Id"],
|
|
347
|
+
"slot_type" => d.dig("Oem", "Dell", "SlotType"),
|
|
348
|
+
"bus_width" => d.dig("Oem", "Dell", "BusWidth"),
|
|
349
|
+
"nic" => nil
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return devices unless devices.empty?
|
|
471
353
|
end
|
|
354
|
+
|
|
355
|
+
# Fallback: NetworkAdapters
|
|
356
|
+
if (data = safe_get("/redfish/v1/Systems/System.Embedded.1/NetworkAdapters?$expand=*($levels=1)"))
|
|
357
|
+
devices = Array(data["Members"]).filter_map { |a|
|
|
358
|
+
next unless a["Model"] || a["Manufacturer"]
|
|
359
|
+
is_fc = [a["Name"], a["Model"], a["Id"], a["Description"]].any? { |f| f =~ /FC|Fibre/i }
|
|
360
|
+
{
|
|
361
|
+
"device_class" => is_fc ? "FibreChannelController" : "NetworkController",
|
|
362
|
+
"manufacturer" => a["Manufacturer"],
|
|
363
|
+
"name" => a["Name"] || a["Id"],
|
|
364
|
+
"description" => a["Description"],
|
|
365
|
+
"id" => a["Id"],
|
|
366
|
+
"slot_type" => a.dig("Oem", "Dell", "SlotType") || (a["Id"] =~ /Slot\.(\d+)/ ? "Slot #{$1}" : nil),
|
|
367
|
+
"bus_width" => nil,
|
|
368
|
+
"nic" => a["@odata.id"]
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return devices unless devices.empty?
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Last resort: PCIeFunctions directly
|
|
375
|
+
if (data = safe_get("/redfish/v1/Systems/System.Embedded.1/PCIeFunctions?$expand=*($levels=1)"))
|
|
376
|
+
return Array(data["Members"]).map { |fn|
|
|
377
|
+
{
|
|
378
|
+
"device_class" => fn["DeviceClass"] || "Unknown",
|
|
379
|
+
"manufacturer" => fn["Manufacturer"] || "Unknown",
|
|
380
|
+
"name" => fn["Name"] || fn["Id"],
|
|
381
|
+
"description" => fn["Description"],
|
|
382
|
+
"id" => fn["Id"],
|
|
383
|
+
"slot_type" => fn.dig("Oem", "Dell", "SlotType"),
|
|
384
|
+
"bus_width" => fn.dig("Oem", "Dell", "DataBusWidth"),
|
|
385
|
+
"nic" => nil
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
[]
|
|
472
391
|
end
|
|
473
392
|
|
|
474
393
|
# Map NICs to PCI bus IDs for Mellanox cards
|
data/lib/idrac/version.rb
CHANGED