Class: Supermicro::Client

Inherits:
Object
  • Object
show all
Includes:
Bios, Boot, Debuggable, Jobs, License, Network, Power, Storage, System, SystemConfig, Tasks, Utility, VirtualMedia
Defined in:
lib/supermicro/client.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Network

#get_bmc_network, #set_bmc_dhcp, #set_bmc_network

Methods included from Tasks

#poll_task, #wait_for_task_completion

Methods included from SpinnerHelper

#show_progress, #with_spinner

Methods included from License

#activate_license, #check_virtual_media_license, #clear_license, #licenses

Methods included from Utility

#accounts, #clear_sel_log, #create_account, #delete_account, #sel_log, #sel_summary, #service_info, #sessions, #update_account_password

Methods included from SystemConfig

#manager_info, #manager_network_protocol, #reset_manager, #set_manager_datetime, #set_network_protocol

Methods included from Bios

#bios_attributes, #bios_error_prompt_disabled?, #bios_hdd_placeholder_enabled?, #bios_os_power_control_enabled?, #ensure_sensible_bios!, #ensure_uefi_boot, #pending_bios_settings, #reset_bios_defaults, #set_bios_attribute, #set_bios_error_prompt, #set_bios_os_power_control

Methods included from Boot

#boot, #boot_config, #boot_options, #boot_raw, #boot_to_bios_setup, #boot_to_cd, #boot_to_disk, #boot_to_pxe, #boot_to_usb, #clear_boot_override, #configure_boot_settings, #get_boot_devices, #set_boot_order, #set_boot_order_hd_first, #set_boot_override, #set_one_time_boot_to_virtual_media

Methods included from VirtualMedia

#eject_virtual_media, #find_best_virtual_media_device, #insert_virtual_media, #mount_iso_and_boot, #test_iso_accessibility, #unmount_all_media, #virtual_media, #virtual_media_status

Methods included from System

#cpus, #fans, #memory, #nics, #power_consumption, #power_consumption_watts, #psus, #system_health, #system_info, #temperatures

Methods included from Storage

#drives, #storage_controllers, #storage_summary, #volumes

Methods included from Jobs

#cancel_job, #clear_jobs!, #job_status, #jobs, #jobs_detail, #jobs_summary, #wait_for_job

Methods included from Debuggable

#debug

Methods included from Power

#power_cycle, #power_off, #power_on, #power_restart, #power_status, #reset_type_allowed

Constructor Details

#initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: false, direct_mode: false, retry_count: 3, retry_delay: 1, host_header: nil) ⇒ Client

Returns a new instance of Client.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/supermicro/client.rb', line 31

def initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: false, direct_mode: false, retry_count: 3, retry_delay: 1, host_header: nil)
  @host = host
  @username = username
  @password = password
  @port = port
  @use_ssl = use_ssl
  @verify_ssl = verify_ssl
  @direct_mode = direct_mode
  @host_header = host_header
  @verbosity = 0
  @retry_count = retry_count
  @retry_delay = retry_delay
  
  @session = Session.new(self)
  
  ObjectSpace.define_finalizer(self, self.class.finalizer(@session))
end

Instance Attribute Details

#direct_modeObject

Returns the value of attribute direct_mode.



15
16
17
# File 'lib/supermicro/client.rb', line 15

def direct_mode
  @direct_mode
end

#hostObject (readonly)

Returns the value of attribute host.



14
15
16
# File 'lib/supermicro/client.rb', line 14

def host
  @host
end

#host_headerObject (readonly)

Returns the value of attribute host_header.



14
15
16
# File 'lib/supermicro/client.rb', line 14

def host_header
  @host_header
end

#passwordObject (readonly)

Returns the value of attribute password.



14
15
16
# File 'lib/supermicro/client.rb', line 14

def password
  @password
end

#portObject (readonly)

Returns the value of attribute port.



14
15
16
# File 'lib/supermicro/client.rb', line 14

def port
  @port
end

#retry_countObject

Returns the value of attribute retry_count.



15
16
17
# File 'lib/supermicro/client.rb', line 15

def retry_count
  @retry_count
end

#retry_delayObject

Returns the value of attribute retry_delay.



15
16
17
# File 'lib/supermicro/client.rb', line 15

def retry_delay
  @retry_delay
end

#sessionObject (readonly)

Returns the value of attribute session.



14
15
16
# File 'lib/supermicro/client.rb', line 14

def session
  @session
end

#use_sslObject (readonly)

Returns the value of attribute use_ssl.



14
15
16
# File 'lib/supermicro/client.rb', line 14

def use_ssl
  @use_ssl
end

#usernameObject (readonly)

Returns the value of attribute username.



14
15
16
# File 'lib/supermicro/client.rb', line 14

def username
  @username
end

#verbosityObject

Returns the value of attribute verbosity.



15
16
17
# File 'lib/supermicro/client.rb', line 15

def verbosity
  @verbosity
end

#verify_sslObject (readonly)

Returns the value of attribute verify_ssl.



14
15
16
# File 'lib/supermicro/client.rb', line 14

def verify_ssl
  @verify_ssl
end

Class Method Details

.connect(host:, username:, password:, **options) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
# File 'lib/supermicro/client.rb', line 58

def self.connect(host:, username:, password:, **options)
  client = new(host: host, username: username, password: password, **options)
  return client unless block_given?
  
  begin
    client.
    yield client
  ensure
    client.logout
  end
end

.finalizer(session) ⇒ Object



49
50
51
52
53
54
55
56
# File 'lib/supermicro/client.rb', line 49

def self.finalizer(session)
  proc do
    begin
      session.delete if session.x_auth_token
    rescue
    end
  end
end

Instance Method Details

#authenticated_request(method, path, body: nil, headers: {}, timeout: nil, open_timeout: nil, **options) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/supermicro/client.rb', line 106

def authenticated_request(method, path, body: nil, headers: {}, timeout: nil, open_timeout: nil, **options)
  request_options = {
    body: body,
    headers: headers,
    timeout: timeout,
    open_timeout: open_timeout
  }.merge(options).compact
  
  with_retries do
    _perform_authenticated_request(method, path, request_options)
  end
end

#base_urlObject



125
126
127
128
# File 'lib/supermicro/client.rb', line 125

def base_url
  protocol = use_ssl ? 'https' : 'http'
  "#{protocol}://#{host}:#{port}"
end

#connectionObject



70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/supermicro/client.rb', line 70

def connection
  @connection ||= Faraday.new(url: base_url, ssl: { verify: verify_ssl }) do |faraday|
    faraday.request :multipart
    faraday.request :url_encoded
    faraday.adapter Faraday.default_adapter
    if @verbosity > 0
      faraday.response :logger, Logger.new(STDOUT), bodies: @verbosity >= 2 do |logger|
        logger.filter(/(Authorization: Basic )([^,\n]+)/, '\1[FILTERED]')
        logger.filter(/(Password"=>"?)([^,"]+)/, '\1[FILTERED]')
      end
    end
  end
end

#get(path:, headers: {}) ⇒ Object



119
120
121
122
123
# File 'lib/supermicro/client.rb', line 119

def get(path:, headers: {})
  with_retries do
    _perform_get(path: path, headers: headers)
  end
end

#get_firmware_versionObject



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/supermicro/client.rb', line 140

def get_firmware_version
  response = authenticated_request(:get, "/redfish/v1/Managers/1?$select=FirmwareVersion")
  
  if response.status == 200
    begin
      data = JSON.parse(response.body)
      return data["FirmwareVersion"]
    rescue JSON::ParserError
      raise Error, "Failed to parse firmware version response: #{response.body}"
    end
  else
    response = authenticated_request(:get, "/redfish/v1/Managers/1")
    
    if response.status == 200
      begin
        data = JSON.parse(response.body)
        return data["FirmwareVersion"]
      rescue JSON::ParserError
        raise Error, "Failed to parse firmware version response: #{response.body}"
      end
    else
      raise Error, "Failed to get firmware version. Status code: #{response.status}"
    end
  end
end

#handle_location(location) ⇒ Object



254
255
256
257
258
259
260
261
262
263
264
# File 'lib/supermicro/client.rb', line 254

def handle_location(location)
  return nil if location.nil? || location.empty?
  
  id = location.split("/").last
  
  if location.include?("/TaskService/Tasks/")
    wait_for_task(id)
  else
    wait_for_job(id) if respond_to?(:wait_for_job)
  end
end

#handle_response(response) ⇒ Object



242
243
244
245
246
247
248
249
250
251
252
# File 'lib/supermicro/client.rb', line 242

def handle_response(response)
  if response.headers["location"]
    return handle_location(response.headers["location"])
  end

  if response.status.between?(200, 299)
    return response.body
  else
    raise Error, "Failed to #{response.status} - #{response.body}"
  end
end

#loginObject



84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/supermicro/client.rb', line 84

def 
  if @direct_mode
    debug "Using direct mode (Basic Auth) for all requests", 1, :light_yellow
    return true
  end
  
  if session.create
    debug "Successfully logged in to Supermicro BMC using Redfish session", 1, :green
    return true
  else
    debug "Failed to create Redfish session, falling back to direct mode", 1, :light_yellow
    @direct_mode = true
    return true
  end
end

#logoutObject



100
101
102
103
104
# File 'lib/supermicro/client.rb', line 100

def logout
  session.delete if session.x_auth_token
  debug "Logged out from Supermicro BMC", 1, :green
  return true
end

#redfish_versionObject



130
131
132
133
134
135
136
137
138
# File 'lib/supermicro/client.rb', line 130

def redfish_version
  response = authenticated_request(:get, "/redfish/v1")
  if response.status == 200
    data = JSON.parse(response.body)
    data["RedfishVersion"]
  else
    raise Error, "Failed to get Redfish version: #{response.status} - #{response.body}"
  end
end

#wait_for_task(task_id) ⇒ Object



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/supermicro/client.rb', line 188

def wait_for_task(task_id)
  task = nil
  
  begin
    loop do
      task_response = authenticated_request(:get, "/redfish/v1/TaskService/Tasks/#{task_id}")
      
      case task_response.status
      when 200..299
        task = JSON.parse(task_response.body)

        if task["TaskState"] != "Running"
          break
        end
        
        percent_complete = nil
        if task["Oem"] && task["Oem"]["Supermicro"] && task["Oem"]["Supermicro"]["PercentComplete"]
          percent_complete = task["Oem"]["Supermicro"]["PercentComplete"]
          debug "Task progress: #{percent_complete}% complete", 1
        end
        
        debug "Waiting for task to complete...: #{task["TaskState"]} #{task["TaskStatus"]}", 1
        sleep 5
      else
        return { 
          status: :failed, 
          error: "Failed to check task status: #{task_response.status} - #{task_response.body}" 
        }
      end
    end
    
    if task["TaskState"] == "Completed" && task["TaskStatus"] == "OK"
      return { status: :success }
    else
      debug task.inspect, 1, :yellow
      
      messages = []
      if task["Messages"] && task["Messages"].is_a?(Array)
        messages = task["Messages"].map { |m| m["Message"] }.compact
      end
      
      return { 
        status: :failed, 
        task_state: task["TaskState"], 
        task_status: task["TaskStatus"],
        messages: messages,
        error: messages.first || "Task failed with state: #{task["TaskState"]}"
      }
    end
  rescue => e
    return { status: :error, error: "Exception monitoring task: #{e.message}" }
  end
end

#with_retries(max_retries = nil, initial_delay = nil, error_classes = nil) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/supermicro/client.rb', line 166

def with_retries(max_retries = nil, initial_delay = nil, error_classes = nil)
  max_retries ||= @retry_count
  initial_delay ||= @retry_delay
  error_classes ||= [StandardError]
  
  retries = 0
  begin
    yield
  rescue *error_classes => e
    retries += 1
    if retries <= max_retries
      delay = initial_delay * (retries ** 1.5).to_i
      debug "RETRY: #{e.message} - Attempt #{retries}/#{max_retries}, waiting #{delay}s", 1, :yellow
      sleep delay
      retry
    else
      debug "MAX RETRIES REACHED: #{e.message} after #{max_retries} attempts", 1, :red
      raise e
    end
  end
end