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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c8f284844e8559e5cb1873cc31e8f08bdff27f358f6b9c74e3cef2d41913ba96
4
- data.tar.gz: 02db8a1bdc940f6a345db7543dd8b3d3cb5e1b3ac16efaa63bf0e35a369f4271
3
+ metadata.gz: cc0edda7bbcbf4e86782d914c909557f3bf6f24e5fa40284761f0e8d550c18b5
4
+ data.tar.gz: c8e3b1fa48d0eba346ab6d3410894e7ecce6c211c5f8719653c99bb384222e9e
5
5
  SHA512:
6
- metadata.gz: 9151c3a3dbbf5be669d9482f5d24af9f0313f97b8fac83262c32b780bc641aeb93d1c3fbee2eeefbff8ea60006ce766b6fa0921e52a01bfcebf40142889f5a1a
7
- data.tar.gz: 79d18056308f3e4255bd19f5003d38aec5ed05c8a20222d6ce07e0bb230eeaf139cd4d94d6f428a35e24c413785a045a2b6a4121660e11ada3213d5f565dad0d
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
- # Implementation of authenticated request without retry logic
171
- def _perform_authenticated_request(method, path, options = {}, retry_count = 0)
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
- # Debug the body being sent
191
- if body && @verbosity >= 2
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, retry_count)
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, retry_count)
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, retry_count)
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
- if @direct_mode
247
- debug "Authentication failed in direct mode, retrying...", 1, :light_yellow
248
- sleep(retry_count + 1)
249
- return _perform_authenticated_request(method, path, options, retry_count + 1)
250
- else
251
- debug "Session expired, creating new session...", 1, :light_yellow
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
- sleep(retry_count + 1)
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 the standard license endpoint first (works in iDRAC 9+)
7
- response = authenticated_request(:get, "/redfish/v1/LicenseService/Licenses")
8
-
9
- if response.status == 200
10
- license_data = JSON.parse(response.body)
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 version from the license description or server header
42
- # @return [Integer, nil] The license version (e.g. 9) or nil if not found
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
- # Check the Description field, which often contains the version
60
- # Example: "iDRAC9 Enterprise License"
61
- if license["Description"]&.match(/iDRAC(\d+)/i)
62
- version = license["Description"].match(/iDRAC(\d+)/i)[1].to_i
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
- # If license info failed or didn't have version info, try to get from server header
83
- # Make a simple request to check the server header (often contains iDRAC version)
84
- response = authenticated_request(:get, "/redfish/v1")
85
- if response.headers["server"] && response.headers["server"].match(/iDRAC\/(\d+)/i)
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 several potential Dell license paths (order matters - most likely first)
99
- dell_license_paths = [
100
- "/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/DellLicenseManagementService/Licenses",
101
- "/redfish/v1/Managers/iDRAC.Embedded.1/Attributes", # iDRAC attributes might contain license info
102
- "/redfish/v1/Managers/iDRAC.Embedded.1" # Manager entity might have license info embedded
103
- ]
104
-
105
- dell_license_paths.each do |path|
106
- response = authenticated_request(:get, path)
107
-
108
- if response.status == 200
109
- debug "Found valid Dell license path: #{path}", 2
110
- data = JSON.parse(response.body)
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
- # If we couldn't find any API path that works, try the service tag detection method
126
- service_tag = get_service_tag
127
- if service_tag
128
- # Service tag is often used to indicate Enterprise licenses on Dell systems
129
- license_type = determine_license_type()
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
- # If no license attributes found, fall back to feature detection
209
- license_type = determine_license_type()
210
- return {
211
- "Id" => "iDRAC-License",
212
- "Description" => "iDRAC8 #{license_type} License",
213
- "Name" => "iDRAC License",
214
- "LicenseType" => license_type,
215
- "Status" => { "Health" => "OK" },
216
- "Removable" => false
217
- }
218
- end
219
-
220
- # Handle response from Dell Manager entity
221
- def handle_dell_manager_response(data)
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
- # Check if license info is embedded directly
89
+
90
+ dell_data = data.dig("Oem", "Dell") || {}
238
91
  if dell_data["LicenseType"] || dell_data["License"]
239
- license_type = dell_data["LicenseType"] ||
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
- nil
95
+
96
+ # Last resort: detect from features
97
+ build_license_hash(detect_license_type)
296
98
  end
297
-
298
- # Helper method to extract license type from description
299
- def get_license_type_from_description(description)
300
- return "Unknown" unless description
301
-
302
- if description.include?("Enterprise")
303
- return "Enterprise"
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
- # Creates a basic license info object based on system information
314
- # Used as a fallback when neither the standard nor Dell OEM endpoints work
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" => version ? "iDRAC#{version} #{license_type} License" : "iDRAC License",
111
+ "Description" => "iDRAC #{type} License",
330
112
  "Name" => "iDRAC License",
331
- "LicenseType" => license_type,
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
- # Attempt to determine the license type (Enterprise/Express) based on available features
341
- # @return [String] The license type (Enterprise, Express, or Basic)
342
- def determine_license_type
343
- # We can try to check for features only available in Enterprise
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
- # First try the standard PCIeDevices endpoint
318
- response = authenticated_request(:get, "/redfish/v1/Chassis/System.Embedded.1/PCIeDevices?$expand=*($levels=1)")
319
-
320
- if response.status == 200
321
- begin
322
- data = JSON.parse(response.body)
323
-
324
- pci = data["Members"].map do |stub|
325
- manufacturer = stub["Manufacturer"]
326
-
327
- # Get PCIe function details if available
328
- pcie_function = nil
329
- if stub.dig("Links", "PCIeFunctions", 0, "@odata.id")
330
- pcie_function_path = stub.dig("Links", "PCIeFunctions", 0, "@odata.id").split("v1/").last
331
- function_response = authenticated_request(:get, "/redfish/v1/#{pcie_function_path}")
332
-
333
- if function_response.status == 200
334
- pcie_function = JSON.parse(function_response.body)
335
- end
336
- end
337
-
338
- # Create device info with available data
339
- device_info = {
340
- "device_class" => pcie_function ? pcie_function["DeviceClass"] : nil,
341
- "manufacturer" => manufacturer,
342
- "name" => stub["Name"],
343
- "description" => stub["Description"],
344
- "id" => pcie_function ? pcie_function["Id"] : stub["Id"],
345
- "slot_type" => pcie_function ? pcie_function.dig("Oem", "Dell", "DellPCIeFunction", "SlotType") : nil,
346
- "bus_width" => pcie_function ? pcie_function.dig("Oem", "Dell", "DellPCIeFunction", "DataBusWidth") : nil,
347
- "nic" => pcie_function ? pcie_function.dig("Links", "EthernetInterfaces", 0, "@odata.id") : nil
348
- }
349
-
350
- puts "PCI Device: #{device_info["name"]} > #{device_info["manufacturer"]} > #{device_info["device_class"]} > #{device_info["description"]} > #{device_info["id"]}"
351
-
352
- device_info
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IDRAC
4
- VERSION = "0.9.3"
4
+ VERSION = "0.9.7"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: idrac
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.3
4
+ version: 0.9.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Siegel