Class: RedfishClient::Resource

Inherits:
Object
  • Object
show all
Defined in:
lib/redfish_client/resource.rb

Overview

Resource is basic building block of Redfish client and serves as a container for the data that is retrieved from the Redfish service.

When we interact with the Redfish service, resource will wrap the data retrieved from the service API and offer us dot-notation accessors for values stored.

Resource will also load any sub-resource on demand when we access it. For example, if we have a root Redfish resource stored in root, accessing root.SessionService will automatically fetch the appropriate resource from the API.

In order to reduce the amount of requests being sent to the service, resource can also utilise caching connector. If we would like to get fresh values from the service, #refresh call will flush the cache and retrieve fresh data from the remote.

Direct Known Subclasses

Root

Defined Under Namespace

Classes: NoODataId, NoResource, Timeout

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connector, oid: nil, raw: nil) ⇒ Resource

Create new resource.

Resource can be created either by passing in OpenData identifier or supplying the content (hash). In the first case, connector will be used to fetch the resource data. In the second case, resource only wraps the passed-in hash and does no fetching.

Parameters:

  • connector (RedfishClient::Connector)

    connector that will be used to fetch the resources

  • oid (String) (defaults to: nil)

    OpenData id of the resource

  • raw (Hash) (defaults to: nil)

    raw content to populate resource with

Raises:

  • (NoResource)

    resource cannot be retrieved from the service



74
75
76
77
78
79
80
81
# File 'lib/redfish_client/resource.rb', line 74

def initialize(connector, oid: nil, raw: nil)
  @connector = connector
  if oid
    initialize_from_service(oid)
  else
    @raw = raw
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(symbol, *_args, &_block) ⇒ Object

Convenience access for resource data.

Calling resource.Value is exactly the same as resource["Value"].



139
140
141
# File 'lib/redfish_client/resource.rb', line 139

def method_missing(symbol, *_args, &_block)
  self[symbol.to_s]
end

Instance Attribute Details

#headersHash (readonly)

Headers, returned from the service when resource has been constructed.

Returns:

  • (Hash)

    resource headers



37
38
39
# File 'lib/redfish_client/resource.rb', line 37

def headers
  @headers
end

#rawHash (readonly)

Raw data that has been used to construct resource by either fetching it from the remote API or by being passed-in as a parameter to constructor.

Returns:

  • (Hash)

    resource raw data



43
44
45
# File 'lib/redfish_client/resource.rb', line 43

def raw
  @raw
end

Instance Method Details

#[](attr) ⇒ Object

Access resource content.

This function offers a way of accessing resource data in the same way that hash exposes its content.

Parameters:

  • attr (String)

    key for accessing data

Returns:

  • associated value or nil if attr is missing



111
112
113
# File 'lib/redfish_client/resource.rb', line 111

def [](attr)
  build_resource(raw[attr])
end

#delete(field: "@odata.id", path: nil, payload: nil, headers: nil) ⇒ RedfishClient::Response

Issue a DELETE requests to the endpoint of the resource.

If the resource has no @odata.id field, NoODataId error will be raised, since deleting non-networked resources makes no sense and probably indicates bug in library consumer.

Parameters:

  • field (String, Symbol) (defaults to: "@odata.id")

    path lookup field

  • path (String) (defaults to: nil)

    path to patch

  • payload (Hash<String, >) (defaults to: nil)

    data to send

  • headers (Hash<String, String>) (defaults to: nil)

    additional headers for this request only

Returns:

Raises:

  • (NoODataId)

    resource has no OpenData id



291
292
293
# File 'lib/redfish_client/resource.rb', line 291

def delete(field: "@odata.id", path: nil, payload: nil, headers: nil)
  request(:delete, field, path, payload, headers)
end

#dig(*keys) ⇒ Object

Safely access nested resource content.

This function is an equivalent of safe navigation operator that can be used with arbitrary keys.

Calling res.dig("a", "b", "c") is equivalent to res.a&.b&.c and res["a"] && res["a"]["b"] && res["a"]["b"]["c"].

Returns:

  • associated value or nil if any key is missing



124
125
126
# File 'lib/redfish_client/resource.rb', line 124

def dig(*keys)
  keys.reduce(self) { |a, k| a.nil? ? nil : a[k] }
end

#etagString?

Get ETag from the response headers or resource property.

Redfish services may provide ETag in two ways:

  1. HTTP ETag header (preferred)
  2. @odata.etag property in the resource (fallback)

This method checks both locations, prioritizing the HTTP header.

Returns:

  • (String, nil)

    ETag value or nil if not present



54
55
56
57
58
59
60
# File 'lib/redfish_client/resource.rb', line 54

def etag
  # Prefer HTTP header (used for If-Match header)
  return @headers["etag"] if @headers&.key?("etag")

  # Fallback to @odata.etag property
  raw["@odata.etag"]
end

#get(field: "@odata.id", path: nil, headers: nil) ⇒ RedfishClient::Response

Issue a GET requests to the selected endpoint.

By default, GET request will be sent to the path, stored in @odata.id field. Source field can be changed by specifying the field parameter when calling this function. Specifying the path argument will bypass the field lookup altogether and issue a GET request directly to the selected path.

If the resource has no lookup field, NoODataId error will be raised, since posting to non-networked resources makes no sense and probably indicates bug in library consumer.

Parameters:

  • field (String, Symbol) (defaults to: "@odata.id")

    path lookup field

  • path (String) (defaults to: nil)

    path to post to

  • headers (Hash<String, String>) (defaults to: nil)

    additional headers for this request only

Returns:

Raises:

  • (NoODataId)

    resource has no OpenData id



206
207
208
# File 'lib/redfish_client/resource.rb', line 206

def get(field: "@odata.id", path: nil, headers: nil)
  request(:get, field, path, nil, headers)
end

#key?(name) ⇒ Boolean

Test if resource contains required key.

Parameters:

  • name (String, Symbol)

    key name to test

Returns:

  • (Boolean)

    inclusion test result



132
133
134
# File 'lib/redfish_client/resource.rb', line 132

def key?(name)
  raw.key?(name.to_s)
end

#patch(field: "@odata.id", path: nil, payload: nil, headers: nil, etag: nil) ⇒ RedfishClient::Response

Issue a PATCH requests to the selected endpoint.

Works exactly the same as the #post method, but issued a PATCH request to the server.

Parameters:

  • field (String, Symbol) (defaults to: "@odata.id")

    path lookup field

  • path (String) (defaults to: nil)

    path to patch

  • payload (Hash<String, >) (defaults to: nil)

    data to send

  • headers (Hash<String, String>) (defaults to: nil)

    additional headers for this request only

  • etag (String, nil) (defaults to: nil)

    optional ETag value for If-Match header

Returns:

Raises:

  • (NoODataId)

    resource has no OpenData id



247
248
249
# File 'lib/redfish_client/resource.rb', line 247

def patch(field: "@odata.id", path: nil, payload: nil, headers: nil, etag: nil)
  request(:patch, field, path, payload, headers, etag)
end

#patch_if_match(payload, field: "@odata.id", path: nil, headers: nil) ⇒ RedfishClient::Response

Issue a PATCH request using the ETag from this resource.

This is a convenience method that uses the ETag value obtained when this resource was retrieved from the service. The resource must be fetched via GET (e.g., using client.find()) before calling this method, so that the ETag is available in the response headers.

If no ETag is present in the headers, this method will perform a regular PATCH without the If-Match header.

Examples:

Basic usage

# First, retrieve the resource (GET request with ETag in response)
system = client.find("/redfish/v1/Systems/1")

# Then, update with ETag validation (PATCH with If-Match header)
system.patch_if_match({ "AssetTag" => "Server-001" })

Parameters:

  • payload (Hash<String, >)

    data to send

  • field (String, Symbol) (defaults to: "@odata.id")

    path lookup field

  • path (String) (defaults to: nil)

    path to patch

  • headers (Hash<String, String>) (defaults to: nil)

    additional headers for this request only

Returns:

Raises:

  • (NoODataId)

    resource has no OpenData id



274
275
276
277
# File 'lib/redfish_client/resource.rb', line 274

def patch_if_match(payload, field: "@odata.id", path: nil, headers: nil)
  current_etag = etag
  patch(field: field, path: path, payload: payload, headers: headers, etag: current_etag)
end

#post(field: "@odata.id", path: nil, payload: nil, headers: nil) ⇒ RedfishClient::Response

Issue a POST requests to the selected endpoint.

By default, POST request will be sent to the path, stored in @odata.id field. Source field can be changed by specifying the field parameter when calling this function. Specifying the path argument will bypass the field lookup altogether and POST directly to the requested path.

In order to avoid having to manually serialize data to JSON, this function call takes Hash as a payload and encodes it before sending it to the endpoint.

If the resource has no lookup field, NoODataId error will be raised, since posting to non-networked resources makes no sense and probably indicates bug in library consumer.

Parameters:

  • field (String, Symbol) (defaults to: "@odata.id")

    path lookup field

  • path (String) (defaults to: nil)

    path to post to

  • payload (Hash<String, >) (defaults to: nil)

    data to send

  • headers (Hash<String, String>) (defaults to: nil)

    additional headers for this request only

Returns:

Raises:

  • (NoODataId)

    resource has no OpenData id



231
232
233
# File 'lib/redfish_client/resource.rb', line 231

def post(field: "@odata.id", path: nil, payload: nil, headers: nil)
  request(:post, field, path, payload, headers)
end

#refreshObject

Refresh resource content from the API

Caling this method will ensure that the resource data is in sync with the Redfish API, invalidating any caches as necessary.



299
300
301
302
303
304
305
306
# File 'lib/redfish_client/resource.rb', line 299

def refresh
  return unless self["@odata.id"]

  # TODO(@tadeboro): raise more sensible exception if resource cannot be
  # refreshed.
  @connector.reset(self["@odata.id"])
  initialize_from_service(self["@odata.id"])
end

#request(method, field, path, payload = nil, headers = nil, etag = nil) ⇒ RedfishClient::Response

Issue a requests to the selected endpoint.

By default, request will be sent to the path, stored in @odata.id field. Source field can be changed by specifying the field parameter when calling this function. Specifying the path argument will bypass the field lookup altogether and issue a request directly to the selected path.

If the resource has no lookup field, NoODataId error will be raised, since posting to non-networked resources makes no sense and probably indicates bug in library consumer.

Parameters:

  • method (Symbol)

    HTTP method (:get, :post, :patch or :delete)

  • field (String, Symbol)

    path lookup field

  • path (String)

    path to post to

  • payload (defaults to: nil)

    Hash<String, >] data to send

  • headers (Hash<String, String>) (defaults to: nil)

    additional headers for this request only

  • etag (String, nil) (defaults to: nil)

    optional ETag value for If-Match header

Returns:

Raises:

  • (NoODataId)

    resource has no OpenData id



174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/redfish_client/resource.rb', line 174

def request(method, field, path, payload = nil, headers = nil, etag = nil)
  @connector.add_headers(headers) if headers&.any?
  target_path = get_path(field, path)

  # Use etag-aware patch method when etag is provided
  if method == :patch && etag
    # Forward ETag value from caller to Connector#patch as keyword argument
    @connector.patch(target_path, payload, etag: etag)
  else
    @connector.request(method, target_path, payload)
  end
ensure
  @connector.remove_headers(headers) if headers&.any?
end

#respond_to_missing?(symbol, include_private = false) ⇒ Boolean

Returns:

  • (Boolean)


143
144
145
# File 'lib/redfish_client/resource.rb', line 143

def respond_to_missing?(symbol, include_private = false)
  key?(symbol.to_s) || super
end

#to_sString

Pretty-print the wrapped content.

Returns:

  • (String)

    JSON-serialized raw data



150
151
152
# File 'lib/redfish_client/resource.rb', line 150

def to_s
  JSON.pretty_generate(raw)
end

#wait(response, retries: 10, delay: 1) ⇒ RedfishClient::Response

Wait for the potentially async operation to terminate

Note that this can be safely called on response from non-async operations where the function will return immediately and without making any additional requests to the service.

Parameters:

  • response (RedfishClient::Response)

    response

  • retries (Integer) (defaults to: 10)

    number of retries

  • delay (Integer) (defaults to: 1)

    number of seconds between retries

Returns:

Raises:

  • (Timeout)

    if the operation did not terminate in time



94
95
96
97
98
99
100
101
102
# File 'lib/redfish_client/resource.rb', line 94

def wait(response, retries: 10, delay: 1)
  retries.times do |_i|
    return response if response.done?

    sleep(delay)
    response = get(path: response.monitor)
  end
  raise Timeout, "Async operation did not terminate in allotted time"
end