radfish 0.2.6 → 0.2.9

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: 93a5fb3d6426c95928fb99dea4bf9fe1ec89d9e248bdee0998ca7905130a2c0a
4
- data.tar.gz: 83d58cea483fd3b9f4a2ccf2bc9bea4671e6d905b66aea0c543a2a33f7722492
3
+ metadata.gz: 3eb7e0fad9f1c58627bb67908c36b48ee6345502dd697d060bfb2a2269168779
4
+ data.tar.gz: 1d0773d739fc075885f9c49abaf719394695af6e7120c2d42b0478638f78d0f7
5
5
  SHA512:
6
- metadata.gz: 1613faa69f988928629451af00a4ee1631945eaf869a86b823079503d87123bbde630af14fa8cb04aa4cee178794432f0267ef202c05da57946b08ec0b94fe56
7
- data.tar.gz: 3edb39da507013792b8bdb65fe8dd38d0a1bb8a65007891dec52278b95c9f99be3ca86eed7ae0048e86d4123a8fb2544b51153fee56ffdbce2f9abebe9e6edc0
6
+ metadata.gz: 86cb61d2ce44dc0443bd41236074670645e8f16b8d6daa316b54159a6ebcacdf60184cc29ccbbce10747cd878a38b596703b631b2ef8b7e7fa8f922718928ed5
7
+ data.tar.gz: ee102d72d959c36cf16f997ebf8bacb9ae8ed6811b68fc6a08961510f85565e46ec132f42bd6d58da5b06af0acfb12fa543b9e3178abc1e86b69bfde01a582b9
@@ -18,7 +18,8 @@ module Radfish
18
18
  password: password,
19
19
  port: options[:port] || 443,
20
20
  use_ssl: options.fetch(:use_ssl, true),
21
- verify_ssl: options.fetch(:verify_ssl, false)
21
+ verify_ssl: options.fetch(:verify_ssl, false),
22
+ host_header: options[:host_header]
22
23
  )
23
24
  detector.verbosity = @verbosity
24
25
  @vendor = detector.detect
@@ -186,6 +187,41 @@ module Radfish
186
187
  Array(raw).map { |v| build_volume(v, controller) }
187
188
  end
188
189
 
190
+ # Storage management methods - pass through to adapter
191
+ # These are Dell-specific for now but will be generalized when other vendors add support
192
+
193
+ def enable_local_key_management(controller_id:, passphrase:, key_id:)
194
+ if @adapter.respond_to?(:enable_local_key_management)
195
+ @adapter.enable_local_key_management(controller_id: controller_id, passphrase: passphrase, key_id: key_id)
196
+ else
197
+ raise NotImplementedError, "Local key management not supported by #{@vendor} adapter"
198
+ end
199
+ end
200
+
201
+ def disable_local_key_management(controller_id:)
202
+ if @adapter.respond_to?(:disable_local_key_management)
203
+ @adapter.disable_local_key_management(controller_id: controller_id)
204
+ else
205
+ raise NotImplementedError, "Local key management not supported by #{@vendor} adapter"
206
+ end
207
+ end
208
+
209
+ def create_virtual_disk(**options)
210
+ if @adapter.respond_to?(:create_virtual_disk)
211
+ @adapter.create_virtual_disk(**options)
212
+ else
213
+ raise NotImplementedError, "Virtual disk creation not supported by #{@vendor} adapter"
214
+ end
215
+ end
216
+
217
+ def delete_volume(volume_odata_id)
218
+ if @adapter.respond_to?(:delete_volume)
219
+ @adapter.delete_volume(volume_odata_id)
220
+ else
221
+ raise NotImplementedError, "Volume deletion not supported by #{@vendor} adapter"
222
+ end
223
+ end
224
+
189
225
  private
190
226
 
191
227
  def build_controller(raw)
@@ -241,6 +277,7 @@ module Radfish
241
277
  operation_name: fetch.call(:operation_name),
242
278
  write_cache_policy: fetch.call(:write_cache_policy),
243
279
  read_cache_policy: fetch.call(:read_cache_policy),
280
+ fastpath: fetch.call(:fastpath),
244
281
  health: fetch.call(:health),
245
282
  adapter_data: raw
246
283
  )
@@ -4,6 +4,7 @@ require 'faraday'
4
4
  require 'faraday/multipart'
5
5
  require 'faraday/retry'
6
6
  require 'logger'
7
+ require 'socket'
7
8
 
8
9
  module Radfish
9
10
  # Shared HTTP client for all BMC connections
@@ -54,13 +55,28 @@ module Radfish
54
55
  end
55
56
 
56
57
  def request(method, path, body: nil, headers: {}, auth: true, timeout: nil, **options)
57
- response = connection(auth: auth).send(method) do |req|
58
+ debug "Starting HTTP #{method.upcase} request to #{path}", 2, :yellow
59
+
60
+ # Add host header if specified (needed for SSH tunnels to iDRAC)
61
+ if @options[:host_header]
62
+ headers = headers.merge('Host' => @options[:host_header])
63
+ debug "Added Host header: #{@options[:host_header]}", 2, :cyan
64
+ end
65
+
66
+ debug "Creating connection...", 2, :yellow
67
+ conn = connection(auth: auth)
68
+ debug "Connection created, sending #{method} request...", 2, :yellow
69
+
70
+ response = conn.send(method) do |req|
71
+ debug "Setting request URL: #{path}", 3, :cyan
58
72
  req.url path
73
+ debug "Merging headers: #{headers}", 3, :cyan
59
74
  req.headers.merge!(headers)
60
75
  req.body = body if body
61
76
 
62
77
  # Override timeout if specified
63
78
  if timeout
79
+ debug "Setting timeout: #{timeout}s", 3, :cyan
64
80
  req.options.timeout = timeout
65
81
  req.options.open_timeout = [timeout / 2, 5].min
66
82
  end
@@ -69,21 +85,46 @@ module Radfish
69
85
  options.each do |key, value|
70
86
  req.options[key] = value if req.options.respond_to?(:"#{key}=")
71
87
  end
88
+ debug "Request configured, about to send...", 2, :yellow
72
89
  end
73
90
 
91
+ debug "Request completed with status: #{response.status}", 2, :green
92
+
74
93
  response
75
94
  rescue Faraday::ConnectionFailed => e
76
95
  debug "Connection failed: #{e.message}", 1, :red
77
- raise ConnectionError, "Failed to connect to #{host}: #{e.message}"
96
+ raise Radfish::ConnectionError, "Failed to connect to #{host}: #{e.message}"
78
97
  rescue Faraday::TimeoutError => e
79
98
  debug "Request timed out: #{e.message}", 1, :red
80
- raise TimeoutError, "Request to #{host} timed out: #{e.message}"
99
+ raise Radfish::TimeoutError, "Request to #{host} timed out: #{e.message}"
81
100
  rescue Faraday::SSLError => e
82
101
  debug "SSL error: #{e.message}", 1, :red
83
- raise ConnectionError, "SSL error connecting to #{host}: #{e.message}"
102
+
103
+ # Test if this might be HTTP instead of HTTPS
104
+ begin
105
+ debug "Testing if endpoint supports HTTP instead of HTTPS...", 2, :yellow
106
+ test_http_socket = TCPSocket.new(host, port)
107
+ test_http_socket.write("GET /redfish/v1 HTTP/1.1\r\nHost: #{@host_header || host}\r\nConnection: close\r\n\r\n")
108
+ response = test_http_socket.read(1024) # Read first 1KB
109
+ test_http_socket.close
110
+
111
+ if response && response.include?("HTTP/") && !response.include?("301") && !response.include?("302")
112
+ debug "HTTP response received - this endpoint might support HTTP instead of HTTPS", 1, :yellow
113
+ debug "HTTP response preview: #{response[0..200]}", 2
114
+ else
115
+ debug "No valid HTTP response - endpoint requires HTTPS but SSL failed", 2, :red
116
+ end
117
+ rescue => http_test_error
118
+ debug "HTTP test failed: #{http_test_error.message}", 2
119
+ end
120
+
121
+ raise Radfish::ConnectionError, "SSL error connecting to #{host}: #{e.message}"
84
122
  rescue => e
85
123
  debug "HTTP request failed: #{e.class} - #{e.message}", 1, :red
86
- raise Error, "HTTP request failed: #{e.message}"
124
+ debug "Exception backtrace: #{e.backtrace.first(5).join("\n")}", 1, :red
125
+
126
+ # Don't re-raise as generic error - let the original exception propagate
127
+ raise e
87
128
  end
88
129
 
89
130
  private
@@ -92,7 +133,30 @@ module Radfish
92
133
  @connections ||= {}
93
134
  cache_key = auth ? :with_auth : :without_auth
94
135
 
95
- @connections[cache_key] ||= Faraday.new(url: base_url, ssl: { verify: verify_ssl }) do |faraday|
136
+ ssl_options = {
137
+ verify: verify_ssl,
138
+ verify_mode: verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE,
139
+ # Try TLS 1.0 for older BMCs, fallback to 1.2
140
+ min_version: OpenSSL::SSL::TLS1_VERSION,
141
+ max_version: OpenSSL::SSL::TLS1_2_VERSION,
142
+ ciphers: 'ALL:!aNULL:!eNULL:!SSLv2' # More permissive for older BMCs
143
+ }
144
+ debug "SSL options: #{ssl_options.inspect}", 2, :yellow
145
+
146
+ # Test TCP connectivity first (for SSH tunnels)
147
+ if host.include?('localhost') && port
148
+ begin
149
+ debug "Testing TCP connectivity to #{host}:#{port}...", 2, :yellow
150
+ socket = TCPSocket.new(host, port)
151
+ socket.close
152
+ debug "TCP connection test successful", 2, :green
153
+ rescue => e
154
+ debug "TCP connection test failed: #{e.message}", 1, :red
155
+ raise Radfish::ConnectionError, "TCP connection test failed to #{host}:#{port}: #{e.message}"
156
+ end
157
+ end
158
+
159
+ @connections[cache_key] ||= Faraday.new(url: base_url, ssl: ssl_options) do |faraday|
96
160
  # Add authentication if credentials provided and auth is enabled
97
161
  if auth && username && password
98
162
  faraday.request :authorization, :basic, username, password
@@ -119,12 +183,8 @@ module Radfish
119
183
  Faraday::RetriableResponse
120
184
  ],
121
185
  methods: [:get, :put, :delete, :post, :patch],
122
- retry_statuses: [408, 429, 500, 502, 503, 504],
123
- retry_block: -> (env, options, retries, exception) {
124
- if verbosity > 0
125
- debug "Retry #{retries}/#{options[:max]}: #{exception&.message || "HTTP #{env.status}"}", 1, :yellow
126
- end
127
- }
186
+ retry_statuses: [408, 429, 500, 502, 503, 504]
187
+ # Removed retry_block to debug ArgumentError - can add back later
128
188
  }
129
189
 
130
190
  # Set timeouts
@@ -133,7 +193,7 @@ module Radfish
133
193
 
134
194
  # Add logging if verbose
135
195
  if verbosity > 0
136
- faraday.response :logger, Logger.new(STDOUT), bodies: verbosity >= 2 do |logger|
196
+ faraday.response :logger, Logger.new(STDOUT), { bodies: verbosity >= 2 } do |logger|
137
197
  logger.filter(/(Authorization: Basic )([^,\n]+)/, '\1[FILTERED]')
138
198
  logger.filter(/(Password"=>"?)([^,"]+)/, '\1[FILTERED]')
139
199
  logger.filter(/("password":\s*")([^"]+)/, '\1[FILTERED]')
@@ -9,13 +9,14 @@ module Radfish
9
9
  attr_reader :host, :username, :password, :port, :use_ssl, :verify_ssl
10
10
  attr_accessor :verbosity
11
11
 
12
- def initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: false)
12
+ def initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: false, host_header: nil)
13
13
  @host = host
14
14
  @username = username
15
15
  @password = password
16
16
  @port = port
17
17
  @use_ssl = use_ssl
18
18
  @verify_ssl = verify_ssl
19
+ @host_header = host_header
19
20
  @verbosity = 0
20
21
 
21
22
  # Use the shared HTTP client
@@ -26,6 +27,7 @@ module Radfish
26
27
  verify_ssl: verify_ssl,
27
28
  username: username,
28
29
  password: password,
30
+ host_header: host_header, # Pass host_header to HttpClient
29
31
  verbosity: 0, # Will be updated via verbosity= setter
30
32
  retry_count: 2, # Fewer retries for detection
31
33
  retry_delay: 0.5
@@ -39,8 +41,10 @@ module Radfish
39
41
 
40
42
  def detect
41
43
  debug "Detecting vendor for #{host}:#{port}...", 1, :cyan
44
+ debug "Host header: #{@host_header}" if @host_header
42
45
 
43
46
  # Try to get the Redfish service root
47
+ debug "Fetching service root...", 2, :yellow
44
48
  service_root = fetch_service_root
45
49
 
46
50
  unless service_root
@@ -48,6 +52,7 @@ module Radfish
48
52
  return nil
49
53
  end
50
54
 
55
+ debug "Service root fetched successfully", 2, :green
51
56
  vendor = identify_vendor(service_root)
52
57
  debug "Detected vendor: #{vendor || 'Unknown'} for #{host}:#{port}", 1, vendor ? :green : :yellow
53
58
 
@@ -58,10 +63,15 @@ module Radfish
58
63
 
59
64
  def fetch_service_root
60
65
  begin
61
- # Use a shorter timeout for vendor detection (5 seconds total)
62
- response = @http_client.get('/redfish/v1', timeout: 5)
66
+ debug "About to make HTTP GET request to /redfish/v1", 2, :yellow
67
+ # Use longer timeout for SSH tunnels (15 seconds), shorter for direct connections (5 seconds)
68
+ timeout = @host_header ? 15 : 5
69
+ debug "Using timeout: #{timeout}s (SSH tunnel detected)" if @host_header
70
+ response = @http_client.get('/redfish/v1', timeout: timeout)
71
+ debug "HTTP GET request completed", 2, :green
63
72
 
64
73
  if response.status == 200
74
+ debug "Got 200 response, parsing JSON...", 2, :green
65
75
  JSON.parse(response.body)
66
76
  elsif response.status == 401
67
77
  debug "Authentication failed (HTTP 401) - check username/password", 1, :red
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Radfish
4
- VERSION = "0.2.6"
4
+ VERSION = "0.2.9"
5
5
  end
@@ -4,7 +4,7 @@ module Radfish
4
4
  class Volume
5
5
  # Canonical, normalized attributes only.
6
6
  attr_reader :id, :name, :capacity_bytes, :raid_type, :volume_type, :drives,
7
- :encrypted, :lock_status, :stripe_size,
7
+ :encrypted, :lock_status, :stripe_size, :fastpath,
8
8
  :operation_percent_complete, :operation_name,
9
9
  :write_cache_policy, :read_cache_policy, :health,
10
10
  :adapter_data, :controller
@@ -14,6 +14,7 @@ module Radfish
14
14
  lock_status: nil, stripe_size: nil,
15
15
  operation_percent_complete: nil, operation_name: nil,
16
16
  write_cache_policy: nil, read_cache_policy: nil,
17
+ fastpath: nil,
17
18
  health: nil, adapter_data: nil)
18
19
  @client = client
19
20
  @controller = controller
@@ -30,6 +31,7 @@ module Radfish
30
31
  @operation_name = operation_name
31
32
  @write_cache_policy = write_cache_policy
32
33
  @read_cache_policy = read_cache_policy
34
+ @fastpath = fastpath
33
35
  @health = health
34
36
  @adapter_data = adapter_data
35
37
  end
@@ -53,6 +55,7 @@ module Radfish
53
55
  operation_name: operation_name,
54
56
  write_cache_policy: write_cache_policy,
55
57
  read_cache_policy: read_cache_policy,
58
+ fastpath: fastpath,
56
59
  health: health
57
60
  }.compact
58
61
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: radfish
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.2.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Siegel
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-09-10 00:00:00.000000000 Z
11
+ date: 2025-09-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor