Class: Radfish::Client
- Inherits:
-
Object
- Object
- Radfish::Client
- Includes:
- Debuggable
- Defined in:
- lib/radfish/client.rb
Constant Summary collapse
- BOOT_PROGRESS_ORDER =
Canonical Redfish BootProgress order (normalized to snake_case), earliest to latest. Used to decide when a host has reached OR passed a requested state.
%i[ none primary_processor_initialization_started bus_initialization_started memory_initialization_started secondary_processor_initialization_started pci_resource_config_started system_hardware_initialization_complete setup_entered os_boot_started os_running ].freeze
Instance Attribute Summary collapse
-
#adapter ⇒ Object
readonly
Returns the value of attribute adapter.
-
#vendor ⇒ Object
readonly
Returns the value of attribute vendor.
-
#verbosity ⇒ Object
Returns the value of attribute verbosity.
Class Method Summary collapse
Instance Method Summary collapse
- #adapter_class ⇒ Object
- #bmc ⇒ Object
-
#boot ⇒ Object
Boot facade: client.boot.stale_uefi_entries / client.boot.disable_entries(match:).
-
#boot_progress ⇒ Object
Normalized last BootProgress state (a snake_case symbol), or nil when the BMC omits BootProgress entirely (iDRAC8).
-
#controllers ⇒ Object
Storage convenience API Return normalized Controller objects while keeping adapter APIs intact.
- #create_virtual_disk(**options) ⇒ Object
- #delete_volume(volume_odata_id) ⇒ Object
- #disable_local_key_management(controller_id:) ⇒ Object
-
#drives(controller) ⇒ Object
Public storage API (not backward-compatible): require a Controller object.
-
#enable_local_key_management(controller_id:, passphrase:, key_id:) ⇒ Object
Storage management methods - pass through to adapter These are Dell-specific for now but will be generalized when other vendors add support.
- #info ⇒ Object
-
#initialize(host:, username:, password:, vendor: nil, **options) ⇒ Client
constructor
A new instance of Client.
-
#login ⇒ Object
Core methods that should always be available.
- #logout ⇒ Object
-
#method_missing(method, *args, **kwargs, &block) ⇒ Object
Delegate all method calls to the adapter.
- #pci ⇒ Object
- #power ⇒ Object
-
#power_state ⇒ Object
Live BMC power state (e.g. "On"/"Off"), read straight from the adapter.
- #respond_to_missing?(method, include_private = false) ⇒ Boolean
- #service_tag ⇒ Object
- #supported_features ⇒ Object
-
#system ⇒ Object
Lazy-loading API methods that return structured data.
- #thermal ⇒ Object
- #vendor_name ⇒ Object
- #volumes(controller) ⇒ Object
-
#wait_for_boot_progress(target, timeout: nil, poll: 20) ⇒ Object
Wait until the host reaches (or passes)
targetBootProgress, bounded bytimeoutor, when not given, the adapter's per-model POST ceiling.
Methods included from Debuggable
Constructor Details
#initialize(host:, username:, password:, vendor: nil, **options) ⇒ Client
Returns a new instance of Client.
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 60 61 62 63 64 65 |
# File 'lib/radfish/client.rb', line 10 def initialize(host:, username:, password:, vendor: nil, **) @verbosity = [:verbosity] || 0 # Auto-detect vendor if not specified if vendor.nil? detector = VendorDetector.new( host: host, username: username, password: password, port: [:port] || 443, use_ssl: .fetch(:use_ssl, true), verify_ssl: .fetch(:verify_ssl, false), host_header: [:host_header] ) detector.verbosity = @verbosity @vendor = detector.detect if @vendor.nil? raise UnsupportedVendorError, "Could not detect vendor for #{host}:#{[:port] || 443}. Please check: 1) The host is reachable, 2) Credentials are correct (#{username}), 3) The BMC supports Redfish API" end debug "Auto-detected vendor: #{@vendor}", 1, :green else @vendor = vendor.to_s.downcase debug "Using specified vendor: #{@vendor}", 1, :cyan end # Get the adapter class for this vendor adapter_class = Radfish.get_adapter(@vendor) if adapter_class.nil? # Try to load the adapter gem dynamically begin require "radfish/#{@vendor}_adapter" adapter_class = Radfish.get_adapter(@vendor) rescue LoadError # Adapter gem not installed end end if adapter_class.nil? raise UnsupportedVendorError, "No adapter available for vendor: #{@vendor}. " \ "Please install the radfish-#{@vendor} gem or use a supported vendor." end # Create the adapter instance @adapter = adapter_class.new( host: host, username: username, password: password, ** ) # Pass verbosity to adapter @adapter.verbosity = @verbosity if @adapter.respond_to?(:verbosity=) end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(method, *args, **kwargs, &block) ⇒ Object
Delegate all method calls to the adapter
83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/radfish/client.rb', line 83 def method_missing(method, *args, **kwargs, &block) if @adapter.respond_to?(method) if kwargs.empty? @adapter.send(method, *args, &block) else @adapter.send(method, *args, **kwargs, &block) end else super end end |
Instance Attribute Details
#adapter ⇒ Object (readonly)
Returns the value of attribute adapter.
7 8 9 |
# File 'lib/radfish/client.rb', line 7 def adapter @adapter end |
#vendor ⇒ Object (readonly)
Returns the value of attribute vendor.
7 8 9 |
# File 'lib/radfish/client.rb', line 7 def vendor @vendor end |
#verbosity ⇒ Object
Returns the value of attribute verbosity.
8 9 10 |
# File 'lib/radfish/client.rb', line 8 def verbosity @verbosity end |
Class Method Details
.connect(host:, username:, password:, vendor: nil, **options) ⇒ Object
67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/radfish/client.rb', line 67 def self.connect(host:, username:, password:, vendor: nil, **) client = new(host: host, username: username, password: password, vendor: vendor, **) if block_given? begin client.login yield client ensure client.logout end else client end end |
Instance Method Details
#adapter_class ⇒ Object
113 114 115 |
# File 'lib/radfish/client.rb', line 113 def adapter_class @adapter.class end |
#bmc ⇒ Object
123 124 125 |
# File 'lib/radfish/client.rb', line 123 def bmc @bmc ||= BmcInfo.new(self) end |
#boot ⇒ Object
Boot facade: client.boot.stale_uefi_entries / client.boot.disable_entries(match:).
132 133 134 |
# File 'lib/radfish/client.rb', line 132 def boot @boot ||= BootInfo.new(self) end |
#boot_progress ⇒ Object
Normalized last BootProgress state (a snake_case symbol), or nil when the BMC omits BootProgress entirely (iDRAC8). nil means "cannot observe", never "not running".
159 160 161 162 163 |
# File 'lib/radfish/client.rb', line 159 def boot_progress return nil unless @adapter.respond_to?(:boot_progress) @adapter.boot_progress end |
#controllers ⇒ Object
Storage convenience API Return normalized Controller objects while keeping adapter APIs intact.
233 234 235 236 |
# File 'lib/radfish/client.rb', line 233 def controllers raw = @adapter.storage_controllers Array(raw).map { |c| build_controller(c) } end |
#create_virtual_disk(**options) ⇒ Object
269 270 271 272 273 274 275 |
# File 'lib/radfish/client.rb', line 269 def create_virtual_disk(**) if @adapter.respond_to?(:create_virtual_disk) @adapter.create_virtual_disk(**) else raise NotImplementedError, "Virtual disk creation not supported by #{@vendor} adapter" end end |
#delete_volume(volume_odata_id) ⇒ Object
277 278 279 280 281 282 283 |
# File 'lib/radfish/client.rb', line 277 def delete_volume(volume_odata_id) if @adapter.respond_to?(:delete_volume) @adapter.delete_volume(volume_odata_id) else raise NotImplementedError, "Volume deletion not supported by #{@vendor} adapter" end end |
#disable_local_key_management(controller_id:) ⇒ Object
261 262 263 264 265 266 267 |
# File 'lib/radfish/client.rb', line 261 def disable_local_key_management(controller_id:) if @adapter.respond_to?(:disable_local_key_management) @adapter.disable_local_key_management(controller_id: controller_id) else raise NotImplementedError, "Local key management not supported by #{@vendor} adapter" end end |
#drives(controller) ⇒ Object
Public storage API (not backward-compatible): require a Controller object.
239 240 241 242 |
# File 'lib/radfish/client.rb', line 239 def drives(controller) raise ArgumentError, "Controller required" unless controller.is_a?(Controller) @adapter.drives(controller) end |
#enable_local_key_management(controller_id:, passphrase:, key_id:) ⇒ Object
Storage management methods - pass through to adapter These are Dell-specific for now but will be generalized when other vendors add support
253 254 255 256 257 258 259 |
# File 'lib/radfish/client.rb', line 253 def enable_local_key_management(controller_id:, passphrase:, key_id:) if @adapter.respond_to?(:enable_local_key_management) @adapter.enable_local_key_management(controller_id: controller_id, passphrase: passphrase, key_id: key_id) else raise NotImplementedError, "Local key management not supported by #{@vendor} adapter" end end |
#info ⇒ Object
221 222 223 224 225 226 227 228 229 |
# File 'lib/radfish/client.rb', line 221 def info { vendor: @vendor, adapter: adapter_class.name, features: supported_features, host: @adapter.host, base_url: @adapter.base_url } end |
#login ⇒ Object
Core methods that should always be available
101 102 103 |
# File 'lib/radfish/client.rb', line 101 def login @adapter.login end |
#logout ⇒ Object
105 106 107 |
# File 'lib/radfish/client.rb', line 105 def logout @adapter.logout end |
#pci ⇒ Object
195 196 197 |
# File 'lib/radfish/client.rb', line 195 def pci @pci ||= PciInfo.new(self) end |
#power ⇒ Object
127 128 129 |
# File 'lib/radfish/client.rb', line 127 def power @power ||= PowerInfo.new(self) end |
#power_state ⇒ Object
Live BMC power state (e.g. "On"/"Off"), read straight from the adapter. Replaces callers reaching through the adapter to the vendor client's own power-state call.
138 139 140 |
# File 'lib/radfish/client.rb', line 138 def power_state @adapter.power_status end |
#respond_to_missing?(method, include_private = false) ⇒ Boolean
95 96 97 |
# File 'lib/radfish/client.rb', line 95 def respond_to_missing?(method, include_private = false) @adapter.respond_to?(method, include_private) || super end |
#service_tag ⇒ Object
199 200 201 202 |
# File 'lib/radfish/client.rb', line 199 def service_tag # Get service_tag from system info system.service_tag end |
#supported_features ⇒ Object
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 |
# File 'lib/radfish/client.rb', line 204 def supported_features # Return a list of features this adapter supports features = [] # Check which modules are included features << :power if @adapter.respond_to?(:power_status) features << :system if @adapter.respond_to?(:system_info) features << :storage if @adapter.respond_to?(:storage_controllers) features << :virtual_media if @adapter.respond_to?(:virtual_media) features << :boot if @adapter.respond_to?(:boot_options) features << :jobs if @adapter.respond_to?(:jobs) features << :utility if @adapter.respond_to?(:sel_log) features << :network if @adapter.respond_to?(:get_bmc_network) || @adapter.respond_to?(:set_bmc_network) features end |
#system ⇒ Object
Lazy-loading API methods that return structured data
119 120 121 |
# File 'lib/radfish/client.rb', line 119 def system @system ||= SystemInfo.new(self) end |
#thermal ⇒ Object
191 192 193 |
# File 'lib/radfish/client.rb', line 191 def thermal @thermal ||= ThermalInfo.new(self) end |
#vendor_name ⇒ Object
109 110 111 |
# File 'lib/radfish/client.rb', line 109 def vendor_name @vendor end |
#volumes(controller) ⇒ Object
244 245 246 247 248 |
# File 'lib/radfish/client.rb', line 244 def volumes(controller) raise ArgumentError, "Controller required" unless controller.is_a?(Controller) raw = @adapter.volumes(controller) Array(raw).map { |v| build_volume(v, controller) } end |
#wait_for_boot_progress(target, timeout: nil, poll: 20) ⇒ Object
Wait until the host reaches (or passes) target BootProgress, bounded by timeout or, when
not given, the adapter's per-model POST ceiling. Returns the observed state on success, or nil
when the BMC does not report BootProgress (nothing to wait on -- the caller uses other signals).
Raises Radfish::BootProgressTimeout on a stall.
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/radfish/client.rb', line 169 def wait_for_boot_progress(target, timeout: nil, poll: 20) target = target.to_sym ceiling = timeout || (@adapter.respond_to?(:boot_progress_ceiling) ? @adapter.boot_progress_ceiling(target) : 900) deadline = Time.now + ceiling target_idx = BOOT_PROGRESS_ORDER.index(target) loop do state = boot_progress return nil if state.nil? # BMC omits BootProgress (iDRAC8): unobservable, not a failure return state if state == target state_idx = BOOT_PROGRESS_ORDER.index(state) return state if target_idx && state_idx && state_idx >= target_idx if Time.now > deadline raise BootProgressTimeout, "BootProgress did not reach #{target.inspect} within #{ceiling}s (last: #{state.inspect})" end sleep poll end end |