Redfish
Run DMTF Redfish conformance testing against a BMC. Wraps the DMTF reference validators (Redfish-Service-Validator for schema, DSP8010; Redfish-Interop-Validator for profiles; Redfish-Protocol-Validator for protocol behavior, DSP0266) and adds three native engines: inventory for board-specific value checks the validators do not perform, request for writes, actions and firmware uploads, and events for the server-sent event stream. The validators run on the contest runner and connect to the BMC over HTTPS; their full reports (HTML/XLSX/TSV) are attached as downloadable artifacts while a per-check summary is folded into the step log.
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
cmd | const | Yes | redfish | Command Name |
name | string | Yes | Name to uniquely identify the teststep. This name will show up in reports, logs and the WebUI | |
transport | TransportObject | No | local | Where the validator process runs — defaults to local (the runner). See Transport. The BMC connection itself is always HTTPS. |
options | OptionsObject | No | Additional command options | |
parameters | RedfishOptions | Yes | Options passed into the redfish cmd |
Quick start
Run the schema validator against a BMC:
Check a few board values with the native inventory engine. Most rules are four fields — a target, a property, a comparison, a value:
That covers most needs. The full inventory rule reference adds counts, ranges, per-component checks (forEach), multi-hop navigation (via), composition (all/any), stored values (variable) and repeated reads (retry). Reach for them only when a simple rule can’t express what you want.
Restart the BMC and check that it comes back with the request engine:
Run the interop validator against a profile carried inline (no profile file needs to be staged on the runner):
Alternatively set interop.profile to the path of a profile JSON already on the runner.
Redfish Options
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
host | string | Yes | BMC host (and optional :port) of the Redfish service, without scheme. HTTPS is assumed. | |
auth | AuthObject | Yes | Redfish HTTP credentials the validators authenticate with against the BMC. | |
validate | string or []string | Yes | Engine(s) to run: service, interop, protocol, inventory, request, events. A single string or an array. | |
fatal | string | No | must | Severity that fails the step: must (only MUST-level fails), recommended (MUST+RECOMMENDED), or all (any fail/warn). |
insecure | boolean | No | true | Skip TLS certificate verification (lab BMCs use self-signed certs). Enforced by the inventory and protocol engines; the service/interop validators always connect leniently. |
service | EngineOptions | No | Options for the schema-conformance engine (Redfish-Service-Validator). | |
interop | EngineOptions | No | Options for the profile engine (Redfish-Interop-Validator). profile or profileDoc is required when interop is selected. | |
protocol | EngineOptions | No | Options for the protocol engine (Redfish-Protocol-Validator). | |
inventory | InventoryOptions | No | Native board-specific value checks. | |
request | RequestOptions | No | The one request the request engine sends. Required when request is selected. | |
events | EventsOptions | No | The event stream the events engine listens to. Required when events is selected. |
Select one engine per step (with options.on-failure: continue) to get an independent pass/fail per engine instead of one merged verdict.
Security
- TLS verification is off by default (
insecure: true) because lab BMCs ship self-signed certificates. Setinsecure: falsefor any non-lab/production target. Note the wrapped service/interop validators always connect leniently regardless, so rely on the native inventory and protocol engines when strict TLS matters. - The BMC password is passed to the DMTF validators as a command-line argument, so it is briefly visible in the runner host’s process list (
ps,/proc/<pid>/cmdline) while a validator runs. It is redacted from the step log. This is acceptable for lab BMCs with shared credentials; do not use credentials you would not want visible to anything with process-list access on the runner.
Transport
transport is optional and defaults to local — the validators run on the contest runner (where the DMTF tools are installed) and reach the BMC over HTTPS. Omit it for almost every deployment.
Use transport: { proto: ssh, options: { ... } } to run the validator processes on a remote SSH host instead. The cases where you need it:
- The BMC is reachable only from a jump host / bastion, not from the contest runner — a segmented or air-gapped management network. Run the validators on a host that can actually reach the BMC.
- The DMTF validators live on a different machine (an on-prem appliance or pre-provisioned host with BMC access) rather than in the runner image.
For ssh the target must have the DMTF validators on PATH (rf_service_validator, rf_interop_validator, rf_protocol_validator), and the runner needs SSH access to it (set under transport.options).
Caveat: only the
service,interop, andprotocolengines run over the transport. The nativeinventory,requestandeventsengines are HTTPS clients that always run on the runner (andrequestreads itsfilethere), sosshdoes not change where their requests originate. If the BMC is unreachable from the runner, keep them off ssh-only targets.
Redfish Auth Options
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
user | string | Yes, unless mode: none | Redfish username. | |
password | string | Yes, unless mode: none | Redfish password. | |
mode | string | No | basic | basic sends credentials on every request; session creates a SessionService token, reuses it, and logs out at the end; none sends no credentials. |
mode: none lets a test check that a resource refuses anonymous access, for example GET /redfish/v1/Managers must answer 401 while the service root /redfish/v1 stays public. Only the inventory and request engines accept it; the DMTF validators always log in. A failed session login fails the step; it does not fall back to basic.
Redfish Engine Options
The service, interop, and protocol sub-blocks tune the wrapped DMTF validators. All accept extraArgs, an array of raw passthrough flags to the underlying tool.
| Attribute | Applies to | Type | Required | Description |
|---|---|---|---|---|
scope | service | string | No | Path to crawl as a subtree (--payload Tree); empty crawls the whole tree. For single-resource validation use extraArgs: ["--payload", "Single", "<uri>"]. |
oemCheck | service | boolean | No | Validate OEM subtrees (omit to use the tool default). |
profile | interop | string | One of profile/profileDoc | Path to an interop profile JSON already present on the runner. Use this or profileDoc. |
profileDoc | interop | object | One of profile/profileDoc | A DMTF interoperability profile inline in the test config. It is written to a temp file and validated offline, so no profile file needs to be staged on the runner. Use this or profile. |
extraArgs | all | []string | No | Raw passthrough flags to the validator. |
Redfish Inventory Options
The native engine fetches resources directly and checks board-specific values. Rules come from inline rules and/or an expected file.
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
rules | []InventoryRule | No | Inline value-match rules. | |
expected | string | No | Path to a YAML/JSON file of rules (a bare list or a {rules: [...]} object). | |
retry | {timeout, interval} | No | Read all rules again every interval (default 2s) until they all pass or timeout ends. See Redfish Retry. |
Inventory Rule
Each rule names a target (where to look), a value source (what to read), and a comparison (op + value).
| Attribute | Type | Required | Description |
|---|---|---|---|
path | string | Target (one of) | Resource URI, e.g. /redfish/v1/Systems/system. |
chassis | string | Target (one of) | Glob selecting a chassis member by @odata.id, e.g. *Baseboard*. The rule reads the first member that matches, so * on a BMC with several chassis picks one of them. |
via | []string | No | Follow these single-resource link names from the base before the check (multi-hop targets, e.g. chassis → PowerSubsystem → PowerSupplies). Each segment must be an @odata.id link to a single resource, not a collection. |
prop | string | Source (one of) | Property on the target. May be slash-nested to reach into objects, e.g. Status/Health. |
collection | string | Source (one of) | A sub-collection link whose member count is checked (use with count/ge/le/range). |
forEach | string | Source (one of) | Iterate a collection link (members fetched individually) or an inline array of objects, applying the comparison to every member — one result each. Passes iff all members pass; an empty collection is SKIP. |
where | MemberFilter | No | forEach only: only members matching this predicate are checked; the rest are skipped (not failed). |
all | []object | Source (one of) | Composition (AND): passes iff every {prop, op, value} condition passes. Use instead of a single prop/op/value. |
any | []object | Source (one of) | Composition (OR): passes iff at least one {prop, op, value} condition passes. |
op | string | No | Comparison — see below (default eq, or exists semantics for presence). |
value | string | Depends on op | Expected value, min..max or a tolerance such as 12±5% for range. {{ VAR }} from an earlier step works here. |
variable | string | No | Store the value this rule read (a prop, or a collection count) for later steps. See Redfish Variables. |
header | string | Source (one of) | request responses only: read this response header instead of a property. |
Comparison ops:
| op | Meaning |
|---|---|
exists | Property is present. |
eq / ne | String equality / inequality. |
matches | value is a regex matched against the property. |
contains | value is an exact element of an array property, or a substring of a scalar. |
ge / le | Numeric >= / <= (inclusive), on a collection count or a numeric property. |
range | Numeric inclusive band; value is min..max (e.g. 4..8), or a value with a tolerance: 12±5%, 12+-0.6. |
count | Collection member count equals value. |
distinct | forEach only: no two members hold the same non-empty value. One result for the whole collection, naming the members that share a value. |
sum | forEach only: the members’ values add up to value (a number, min..max or a tolerance). A member without a number fails the sum and is named. |
Numeric ops parse both sides as numbers; a non-numeric operand fails the rule with a clear message rather than crashing. A tolerance also works with a stored value: value: "{{ DCMI_W }}±15%".
Member Filter
A forEach inclusion predicate. Only members for which it holds are checked; the rest are skipped. The canonical use is ignoring empty slots, e.g. {prop: Status/State, op: ne, value: Absent}.
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
prop | string | Yes | Member property to test (may be slash-nested). | |
op | string | No | eq | Predicate comparison (eq, ne, ge, le, matches, exists, contains). |
value | string | No | Predicate value. |
A member that cannot be read (for example a GET that returns 404) fails a forEach rule without where. A rule with where cannot evaluate its filter on that member, so it skips the member and names it in the report as “not read, where not evaluated”; the read error fails only the unfiltered rules.
Multi-hop navigation and composition
via follows a chain of single-resource links from the base before the check, so a nested target works in one rule while keeping a dynamic chassis id, e.g. chassis: "*Baseboard*" with via: [PowerSubsystem] then forEach: PowerSupplies walks chassis → PowerSubsystem → PowerSupplies. A missing link fails the rule. Current bmcweb builds serve PowerSubsystem and ThermalSubsystem; the deprecated Power and Thermal resources exist only when the BMC enables them.
all / any replace a single prop/op/value with condition groups, evaluated against the target (or against each member under forEach). With both set, the rule passes iff every all condition passes and at least one any condition passes. A failed condition shows the value it read, e.g. Status/State eq Enabled (got "Starting").
distinct and sum compare the members of a forEach with each other instead of one by one:
Redfish Variables
A rule with prop or collection stores the value it read with variable (the property, or the member count) for later steps as {{ NAME }}:
A later step compares against it, for example op: ne, value: "{{ BMC_VERSION }}" after a firmware update. The value is stored even when the rule’s check fails; nothing is stored when the property is absent. forEach and all/any rules read many values and cannot store one. Stored values keep only A-Z a-z 0-9 / _ . : + -; when that drops characters, the log says so. See Variables.
Redfish Retry
inventory.retry reads all rules again every interval (default 2s) until they all pass or timeout ends. Use it when the BMC needs time: a power state after a reset, a Manager that reports Starting right after a reboot, a value after a PATCH.
The report shows the last attempt in full, then how the retry ended (passed on attempt 4 after 15s, or still failing after 31 attempts in 1m0s) and one line per earlier failed attempt, with repeats of the same failure merged. A variable stores the value of the last attempt. Without an options timeout, the step timeout is the default plus retry.timeout; a shorter options timeout is rejected when the step is validated.
Redfish Writes and Actions
The request engine (validate: request) sends one request and checks what comes back. Use it for everything the read-only engines cannot do: actions, PATCHes, firmware uploads, creating and deleting resources. A write is sent once and never repeated.
| Attribute | Type | Required | Default | Description |
|---|---|---|---|---|
method | string | Yes | GET, POST, PATCH, PUT or DELETE. | |
path | string | Yes | Resource or action URI; {{ VAR }} from an earlier step works here. | |
status | integer or []integer | Yes | The status code, or the list of codes, that pass. | |
body | object | No | JSON body. | |
headers | map[string]string | No | Extra request headers, e.g. If-Match. | |
file | string | No | Upload this file as the body. It is read on the runner, streamed, and sent with its exact length. | |
upload | string | No | octet-stream | octet-stream: the file is the whole body (for HttpPushUri). multipart: UpdateParameters from body, then UpdateFile (for MultipartHttpPushUri). |
rules | []InventoryRule | No | Checks on the response, or with path/chassis on the BMC after the request. | |
task | {wait, interval} | No | Follow the Task the BMC returns until it ends. | |
restart | {down, up, interval} | No | The request restarts the BMC: wait until it stops answering, then until it answers again. | |
retry | {timeout, interval} | No | Repeat the rules with path/chassis until they pass, after the task and the restart. | |
timeout | string | No | 1m, 10m with a file | Timeout of the request itself. |
The step checks the status first. When it is not in status, the rules, the task and the restart are reported as not evaluated. An expected rejection is a status like any other:
Rules on the response. A rule without path or chassis reads the response body (prop, all/any; no forEach, collection or via), or with header one response header (the name in any case; op, value and variable as usual). X-Auth-Token, Set-Cookie and Authorization are credentials and are refused. A variable stores the value for later steps, so one step can create a resource and a later one can use it:
A safe write with an ETag reads it in one step and sends it in the next. The variable store drops the quotes of the ETag, so the header writes them again:
bmcweb sends strong ETags. A weak one (W/"x") is stored as W/x, and no header can turn that back into W/"x".
A rule with path or chassis is an ordinary inventory rule. It reads after the request, the task and the restart, so it can confirm that a PATCH took effect or that the BMC runs a new image.
Uploads and tasks. With task, the step follows the Task in the response body, else the Location header (a Task or a TaskMonitor). It logs each change of state and passes when the Task ends Completed and its TaskStatus is not Critical; Exception, Killed, Cancelled, a Task that disappears and the end of wait fail, with the Task’s messages. While the BMC does not answer, the step keeps polling. bmcweb accepts a multipart upload without Targets or @Redfish.OperationApplyTime in UpdateParameters (the part may be {}); an octet-stream upload to HttpPushUri needs no parameters at all.
Restarts. An update with ApplyTime: Immediate, or a Manager.Reset, restarts the BMC. With restart, the step then polls GET /redfish/v1 without credentials every interval (default 2s): the BMC must stop answering (no HTTP answer within 10 s: refused, reset or silent) within down after the request, and answer 200 again within up after it went down. Times count from the BMC’s answer to the request, so an upload’s transfer time does not count. The result reads down after 4s, answering again after 55s.
With task and restart together, the Task ends with the restart (bmcweb keeps tasks in memory): when the BMC stops answering, or the Task is gone after the BMC came back, the task check passes if the Task was Running or Completed then, and fails if it was still New. A Task that fails skips the restart check. Rules with path read the restarted BMC; with mode: session the step logs in again first.
request.retry repeats the rules with path or chassis the same way as inventory.retry, after the task and the restart; the request itself is sent once. After a ComputerSystem.Reset it waits for the new power state:
Without an options timeout, the step timeout is the default plus task.wait, restart.down, restart.up and retry.timeout; an options timeout shorter than those together is rejected when the step is validated. The log shows the request and the response with password, token, secret and key values masked; the X-Auth-Token of a response is never shown.
Redfish Events
The events engine (validate: events) opens the BMC’s server-sent event stream (GET /redfish/v1/EventService/SSE), optionally sends one trigger request, and listens for at most wait. Each expect item must match at least one received event record (a member of the event’s Events array): one prop/op/value condition, or all of a list of them. Listening ends as soon as every item has matched.
| Attribute | Type | Required | Description |
|---|---|---|---|
wait | string | Yes | How long to listen, from when the stream is opened. |
expect | []object | Yes | The events that must arrive. |
trigger | object | No | One request sent once the stream is open (method, path, body, headers, status). A refused trigger skips the event checks. |
filter | string | No | The subscription’s $filter, passed to the BMC as written. |
bmcweb sends each event as JSON over several data: lines; the step joins them as the SSE format prescribes. The log lists every received record with its MessageId, OriginOfCondition, Severity and Message, and names data that is not JSON. bmcweb’s SubmitTestEvent takes MessageId, Message, MessageArgs, OriginOfCondition, Severity, Resolution, EventGroupId and EventTimestamp, and answers 204. The stream needs an account allowed to create event subscriptions. Without an options timeout, the step timeout is the default plus wait.
Redfish Examples
Schema conformance (full tree crawl):
Inventory with fleet-aware checks — counts, per-member (forEach / where / range), multi-hop (via), and composition (all / any):