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.

AttributeTypeRequiredDefaultDescription
cmdconstYesredfishCommand Name
namestringYesName to uniquely identify the teststep. This name will show up in reports, logs and the WebUI
transportTransportObjectNolocalWhere the validator process runs — defaults to local (the runner). See Transport. The BMC connection itself is always HTTPS.
optionsOptionsObjectNoAdditional command options
parametersRedfishOptionsYesOptions passed into the redfish cmd

Quick start

Run the schema validator against a BMC:

- cmd: redfish
  name: Redfish schema conformance
  parameters:
    host: "[[attributes.BMC]]"
    auth: { user: root, password: "[[attributes.BMCPassword]]" }
    validate: service
  transport: { proto: local }
  options: { timeout: 20m }

Check a few board values with the native inventory engine. Most rules are four fields — a target, a property, a comparison, a value:

- cmd: redfish
  name: Board sanity
  parameters:
    host: "[[attributes.BMC]]"
    auth: { user: root, password: "[[attributes.BMCPassword]]" }
    validate: inventory
    inventory:
      rules:
        - { path: /redfish/v1, prop: RedfishVersion, op: exists }
        - { path: /redfish/v1/Systems/system, prop: PowerState, op: eq, value: "On" }
        - { path: /redfish/v1/Managers/bmc, prop: Status/Health, op: eq, value: OK }
  transport: { proto: local }

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:

- cmd: redfish
  name: Manager.Reset restarts the BMC
  parameters:
    host: "[[attributes.BMC]]"
    auth: { user: root, password: "[[attributes.BMCPassword]]" }
    validate: request
    request:
      method: POST
      path: /redfish/v1/Managers/bmc/Actions/Manager.Reset
      body: { ResetType: GracefulRestart }
      status: [200, 202, 204]
      restart: { down: 3m, up: 10m }
  transport: { proto: local }

Run the interop validator against a profile carried inline (no profile file needs to be staged on the runner):

- cmd: redfish
  name: Redfish interop profile
  parameters:
    host: "[[attributes.BMC]]"
    auth: { user: root, password: "[[attributes.BMCPassword]]" }
    validate: interop
    interop:
      profileDoc:
        SchemaDefinition: RedfishInteroperabilityProfile.v1_0_0
        ProfileName: BaselineManageability
        ProfileVersion: "1.0.0"
        Protocol: { MinVersion: "1.6.0" }
        Resources:
          ComputerSystem:
            PropertyRequirements:
              PowerState: { ReadRequirement: Mandatory }
              BiosVersion: { ReadRequirement: Mandatory }
  transport: { proto: local }

Alternatively set interop.profile to the path of a profile JSON already on the runner.

Redfish Options

AttributeTypeRequiredDefaultDescription
hoststringYesBMC host (and optional :port) of the Redfish service, without scheme. HTTPS is assumed.
authAuthObjectYesRedfish HTTP credentials the validators authenticate with against the BMC.
validatestring or []stringYesEngine(s) to run: service, interop, protocol, inventory, request, events. A single string or an array.
fatalstringNomustSeverity that fails the step: must (only MUST-level fails), recommended (MUST+RECOMMENDED), or all (any fail/warn).
insecurebooleanNotrueSkip TLS certificate verification (lab BMCs use self-signed certs). Enforced by the inventory and protocol engines; the service/interop validators always connect leniently.
serviceEngineOptionsNoOptions for the schema-conformance engine (Redfish-Service-Validator).
interopEngineOptionsNoOptions for the profile engine (Redfish-Interop-Validator). profile or profileDoc is required when interop is selected.
protocolEngineOptionsNoOptions for the protocol engine (Redfish-Protocol-Validator).
inventoryInventoryOptionsNoNative board-specific value checks.
requestRequestOptionsNoThe one request the request engine sends. Required when request is selected.
eventsEventsOptionsNoThe 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. Set insecure: false for 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, and protocol engines run over the transport. The native inventory, request and events engines are HTTPS clients that always run on the runner (and request reads its file there), so ssh does not change where their requests originate. If the BMC is unreachable from the runner, keep them off ssh-only targets.

Redfish Auth Options

AttributeTypeRequiredDefaultDescription
userstringYes, unless mode: noneRedfish username.
passwordstringYes, unless mode: noneRedfish password.
modestringNobasicbasic 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.

AttributeApplies toTypeRequiredDescription
scopeservicestringNoPath to crawl as a subtree (--payload Tree); empty crawls the whole tree. For single-resource validation use extraArgs: ["--payload", "Single", "<uri>"].
oemCheckservicebooleanNoValidate OEM subtrees (omit to use the tool default).
profileinteropstringOne of profile/profileDocPath to an interop profile JSON already present on the runner. Use this or profileDoc.
profileDocinteropobjectOne of profile/profileDocA 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.
extraArgsall[]stringNoRaw 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.

AttributeTypeRequiredDefaultDescription
rules[]InventoryRuleNoInline value-match rules.
expectedstringNoPath to a YAML/JSON file of rules (a bare list or a {rules: [...]} object).
retry{timeout, interval}NoRead 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).

AttributeTypeRequiredDescription
pathstringTarget (one of)Resource URI, e.g. /redfish/v1/Systems/system.
chassisstringTarget (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[]stringNoFollow 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.
propstringSource (one of)Property on the target. May be slash-nested to reach into objects, e.g. Status/Health.
collectionstringSource (one of)A sub-collection link whose member count is checked (use with count/ge/le/range).
forEachstringSource (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.
whereMemberFilterNoforEach only: only members matching this predicate are checked; the rest are skipped (not failed).
all[]objectSource (one of)Composition (AND): passes iff every {prop, op, value} condition passes. Use instead of a single prop/op/value.
any[]objectSource (one of)Composition (OR): passes iff at least one {prop, op, value} condition passes.
opstringNoComparison — see below (default eq, or exists semantics for presence).
valuestringDepends on opExpected value, min..max or a tolerance such as 12±5% for range. {{ VAR }} from an earlier step works here.
variablestringNoStore the value this rule read (a prop, or a collection count) for later steps. See Redfish Variables.
headerstringSource (one of)request responses only: read this response header instead of a property.

Comparison ops:

opMeaning
existsProperty is present.
eq / neString equality / inequality.
matchesvalue is a regex matched against the property.
containsvalue is an exact element of an array property, or a substring of a scalar.
ge / leNumeric >= / <= (inclusive), on a collection count or a numeric property.
rangeNumeric inclusive band; value is min..max (e.g. 4..8), or a value with a tolerance: 12±5%, 12+-0.6.
countCollection member count equals value.
distinctforEach only: no two members hold the same non-empty value. One result for the whole collection, naming the members that share a value.
sumforEach 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}.

AttributeTypeRequiredDefaultDescription
propstringYesMember property to test (may be slash-nested).
opstringNoeqPredicate comparison (eq, ne, ge, le, matches, exists, contains).
valuestringNoPredicate 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:

# No sensor is linked twice.
- chassis: "*Baseboard*"
  forEach: Sensors
  prop: "@odata.id"
  op: distinct
# The PSU input power adds up to a value an earlier step stored.
- chassis: "*Baseboard*"
  forEach: Sensors
  where: { prop: Name, op: matches, value: '^PSU[01]_Input_Power$' }
  prop: Reading
  op: sum
  value: "{{ DCMI_W }}±15%"

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 }}:

- path: /redfish/v1/Managers/bmc
  prop: FirmwareVersion
  op: exists
  variable: BMC_VERSION

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.

    validate: inventory
    inventory:
      rules:
        - { path: /redfish/v1/Systems/system, prop: PowerState, value: "On" }
      retry: { timeout: 2m, interval: 5s }

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.

AttributeTypeRequiredDefaultDescription
methodstringYesGET, POST, PATCH, PUT or DELETE.
pathstringYesResource or action URI; {{ VAR }} from an earlier step works here.
statusinteger or []integerYesThe status code, or the list of codes, that pass.
bodyobjectNoJSON body.
headersmap[string]stringNoExtra request headers, e.g. If-Match.
filestringNoUpload this file as the body. It is read on the runner, streamed, and sent with its exact length.
uploadstringNooctet-streamoctet-stream: the file is the whole body (for HttpPushUri). multipart: UpdateParameters from body, then UpdateFile (for MultipartHttpPushUri).
rules[]InventoryRuleNoChecks on the response, or with path/chassis on the BMC after the request.
task{wait, interval}NoFollow the Task the BMC returns until it ends.
restart{down, up, interval}NoThe request restarts the BMC: wait until it stops answering, then until it answers again.
retry{timeout, interval}NoRepeat the rules with path/chassis until they pass, after the task and the restart.
timeoutstringNo1m, 10m with a fileTimeout 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:

    request:
      method: PATCH
      path: /redfish/v1/AccountService
      headers: { If-Match: '"stale"' }
      body: { AccountLockoutThreshold: 5 }
      status: 412

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:

    request:
      method: POST
      path: /redfish/v1/SessionService/Sessions
      body: { UserName: operator, Password: "[[attributes.OperatorPassword]]" }
      status: 201
      rules:
        - { prop: "@odata.id", op: matches, value: "^/redfish/v1/SessionService/Sessions/", variable: SESSION }
# a later step:
    request: { method: DELETE, path: "{{ SESSION }}", status: [200, 204] }

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:

    request:
      method: GET
      path: /redfish/v1/AccountService
      status: 200
      rules:
        - { header: ETag, op: exists, variable: ETAG }
# a later step: the current ETag is accepted
    request:
      method: PATCH
      path: /redfish/v1/AccountService
      headers: { If-Match: '"{{ ETAG }}"' }
      body: { AccountLockoutThreshold: 5 }
      status: 200

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.

- cmd: redfish
  name: The BMC boots the update image
  parameters:
    host: "[[attributes.BMC]]"
    auth: { user: root, password: "[[attributes.BMCPassword]]" }
    validate: request
    request:
      method: POST
      path: /redfish/v1/UpdateService/update
      file: "[[input.UpdateImage]]"
      status: 202
      task: { wait: 10m }
      restart: { down: 3m, up: 10m }
      rules:
        - { path: /redfish/v1/Managers/bmc, prop: FirmwareVersion, op: ne, value: "{{ OLD_VERSION }}" }
  transport: { proto: local }

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:

    request:
      method: POST
      path: /redfish/v1/Systems/system/Actions/ComputerSystem.Reset
      body: { ResetType: "On" }
      status: [200, 204]
      rules:
        - { path: /redfish/v1/Systems/system, prop: PowerState, value: "On" }
      retry: { timeout: 5m, interval: 5s }

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.

AttributeTypeRequiredDescription
waitstringYesHow long to listen, from when the stream is opened.
expect[]objectYesThe events that must arrive.
triggerobjectNoOne request sent once the stream is open (method, path, body, headers, status). A refused trigger skips the event checks.
filterstringNoThe subscription’s $filter, passed to the BMC as written.
- cmd: redfish
  name: The BMC delivers a test event
  parameters:
    host: "[[attributes.BMC]]"
    auth: { user: root, password: "[[attributes.BMCPassword]]" }
    validate: events
    events:
      trigger:
        method: POST
        path: /redfish/v1/EventService/Actions/EventService.SubmitTestEvent
        body: { MessageId: Base.1.13.0.Success, Message: fwci test event, OriginOfCondition: /redfish/v1/Systems/system, Severity: OK }
        status: 204
      wait: 30s
      expect:
        - { prop: MessageId, value: Base.1.13.0.Success }
        - all:
            - { prop: OriginOfCondition/@odata.id, value: /redfish/v1/Systems/system }
            - { prop: Severity, value: OK }
  transport: { proto: local }

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):

- cmd: redfish
  name: Redfish schema conformance
  parameters:
    host: "[[attributes.BMC]]"
    auth:
      user: root
      password: "[[attributes.BMCPassword]]"
    validate: service
  transport:
    proto: local
  options:
    timeout: 20m

Inventory with fleet-aware checks — counts, per-member (forEach / where / range), multi-hop (via), and composition (all / any):

- cmd: redfish
  name: Redfish inventory
  parameters:
    host: "[[attributes.BMC]]"
    auth:
      user: root
      password: "[[attributes.BMCPassword]]"
    validate: inventory
    inventory:
      rules:
        # ServiceRoot advertises a version; the BMC reports health.
        - path: /redfish/v1
          prop: RedfishVersion
          op: exists
        - path: /redfish/v1/Managers/bmc
          prop: Status/Health
          op: eq
          value: OK
        # At least one DIMM is populated (exact counts vary per SKU).
        - path: /redfish/v1/Systems/system
          collection: Memory
          op: ge
          value: "1"
        # Every populated DIMM has a sane capacity; empty slots are skipped.
        - path: /redfish/v1/Systems/system
          forEach: Memory
          where:
            prop: Status/State
            op: ne
            value: Absent
          prop: CapacityMiB
          op: ge
          value: "1024"
        # Every CPU reports a plausible core count.
        - path: /redfish/v1/Systems/system
          forEach: Processors
          prop: TotalCores
          op: range
          value: "1..256"
        # Multi-hop: chassis -> PowerSubsystem -> every PSU healthy (one rule, dynamic id).
        - chassis: "*Baseboard*"
          via: [PowerSubsystem]
          forEach: PowerSupplies
          prop: Status/Health
          op: eq
          value: OK
        # Composition: System is On AND healthy; accept either BIOS property.
        - path: /redfish/v1/Systems/system
          all:
            - { prop: PowerState, op: eq, value: "On" }
            - { prop: Status/Health, op: eq, value: OK }
        - path: /redfish/v1/Systems/system
          any:
            - { prop: BiosVersion, op: exists }
            - { prop: BiosRevision, op: exists }
  transport:
    proto: local
  options:
    timeout: 5m