Learn › BMC & Server Management › Modern Management
Turn single Redfish queries into a bash fleet health check: probe liveness by status code, carve identity, thermal, and power fields with jq, then assemble a probe-first bmc-health.sh and file its report, the exact tool the DC-EAST NOC will run on deployment day.
It is 07:40 at the DC-EAST facility in Ashburn, Virginia. A deployment memo just landed in the NOC queue: fifty new Dell PowerEdge R750xs servers arrive next month, and Row 7 takes the first sixteen.
The NOC lead stops at your desk. "I am not opening sixteen browser tabs every morning to check sixteen BMCs. I want one command that tells me which boxes need attention. Build it on Bay 14."
The machine in Bay 14, Row 7 is the one you have managed since your first work order. Today it becomes the template: everything you make it say, a script will ask fifty boxes tomorrow.
That is the whole idea behind Work Order DC-EAST-WO-1015: one box is a command, a fleet is a script.
This is a Practice Zone. The diagrams below react to you: click the boxes, flip the switches. The real machine comes at the end, under Ready to practice.
Redfish looks like a wall of JSON the first time you meet it. It is not. It is a tree, and your health check only visits four rooms:
Every visit is the same move: an HTTP GET with credentials. Click through the map, then flip the credentials switch and watch what a wrong password does to it.
{ "height": 440, "caption": "Four rooms, one front desk. Click any endpoint, then flip the credentials.", "nodes": [ { "id": "you", "label": "Your script", "kind": "admin", "x": 0, "y": 190, "detail": "bmc-health.sh runs curl for the HTTP work and jq to carve fields out of the answers. Every question it asks walks this map." }, { "id": "root", "label": "/redfish/v1/", "kind": "bmc", "x": 280, "y": 190, "detail": "The service root, the front desk of the API. It answers RedfishVersion 1.13.0 and hands out links to everything else." }, { "id": "sys", "label": "Systems/1", "kind": "host", "x": 620, "y": 40, "detail": "Model, SerialNumber, BiosVersion, HostName, PowerState, Status.Health. Most of your report line lives in this one document." }, { "id": "thermal", "label": "Chassis/1/Thermal", "kind": "sensor", "x": 620, "y": 150, "detail": "Temperatures[] and Fans[]: four temperature sensors and six fans, each with a reading and its own health." }, { "id": "pwr", "label": "Chassis/1/Power", "kind": "psu", "x": 620, "y": 260, "detail": "PowerControl[] carries the live wattage draw; PowerSupplies[] lists both 1100W units." }, { "id": "mgr", "label": "Managers/1", "kind": "bmc", "x": 620, "y": 370, "detail": "The BMC itself: FirmwareVersion 6.10.30.00. It answers even when the host is powered off." } ], "edges": [ { "from": "you", "to": "root", "label": "GET + credentials", "kind": "oob" }, { "from": "root", "to": "sys", "kind": "mgmt" }, { "from": "root", "to": "thermal", "kind": "mgmt" }, { "from": "root", "to": "pwr", "kind": "mgmt" }, { "from": "root", "to": "mgr", "kind": "mgmt" } ], "toggle": { "label": "Credentials:", "on": "ADMIN:ADMIN accepted (200)", "off": "Wrong password (401)", "dimOff": ["sys", "thermal", "pwr", "mgr", "e:root-sys", "e:root-thermal", "e:root-pwr", "e:root-mgr"] } }
One switch, every door. A 401 does not close one endpoint, it closes all of them at once. That is your first fleet-debugging rule: when a box rejects you, suspect credentials before you suspect cabling.
>>> Systems/1. PowerState rides with the system identity, in the same document as Model and Status.Health. Thermal is sensor readings, and Managers/1 describes the BMC itself, not the server it manages.
A fleet tool never eyeballs JSON to decide whether a box is up. It reads the one thing every HTTP response carries before any JSON matters: the three-digit status code.
curl can throw away the body and print only that code. Three flags do it: -s silences the progress noise, -o /dev/null discards the body, and -w '%{http_code}\n' writes just the code. Against this API, three codes cover a whole morning sweep:
{ "height": 380, "caption": "The same GET carries a status code and a JSON body. Flip what the probe reads.", "nodes": [ { "id": "you", "label": "bmc-health.sh", "kind": "admin", "x": 0, "y": 170, "detail": "Your tool fires one GET and can read the response two different ways." }, { "id": "bmc", "label": "BMC :5000", "kind": "bmc", "x": 240, "y": 170, "detail": "Every HTTP response carries both a status code and a JSON body. You choose which one to look at." }, { "id": "code", "label": "Status code", "kind": "net", "x": 500, "y": 40, "detail": "200 reachable and authorized, 401 bad credentials, 404 no such resource. curl -o /dev/null -w with the http_code format prints only this." }, { "id": "body", "label": "JSON body", "kind": "net", "x": 500, "y": 300, "detail": "The full resource document. Useless on a dashboard until jq carves it." }, { "id": "jq", "label": "jq filter", "kind": "net", "x": 730, "y": 300, "detail": "jq -r pulls named fields out of the document into one clean line per box." }, { "id": "line", "label": "Report line", "kind": "host", "x": 960, "y": 170, "detail": "bay14-r750xs | PowerEdge R750xs | power=On | health=OK. The line a fleet dashboard stores." } ], "edges": [ { "from": "you", "to": "bmc", "label": "GET", "kind": "oob" }, { "from": "bmc", "to": "code", "label": "3 digits", "kind": "plain" }, { "from": "bmc", "to": "body", "label": "JSON", "kind": "plain" }, { "from": "body", "to": "jq", "kind": "plain" }, { "from": "jq", "to": "line", "kind": "plain" }, { "from": "code", "to": "line", "label": "up or down", "kind": "plain" } ], "toggle": { "label": "Probe reads:", "on": "Liveness (status code)", "off": "Health data (jq)", "dimOn": ["body", "jq", "e:bmc-body", "e:body-jq", "e:jq-line"], "dimOff": ["code", "e:bmc-code", "e:code-line"] } }
Run the liveness probe yourself. This is the exact first move your script will make on deployment day:
curl -s -o /dev/null -w '%{http_code}\n' -u ADMIN:ADMIN http://$BMC_IP:5000/redfish/v1/
prompt: ops@dc-east-ws01:~$ answer: curl -s -o /dev/null -w '%{http_code}\n' -u ADMIN:ADMIN http://$BMC_IP:5000/redfish/v1/ output: 200 hint: Silence curl with -s, discard the body with -o /dev/null, write the code with -w '%{http_code}\n', then basic auth with -u ADMIN:ADMIN and the service root URL on port 5000.
Three digits, no JSON. A monitoring system stores that number; a dashboard turns it into a green dot. Liveness checks and data pulls are two different reads of the same request, and a good script does the cheap one first.
>>> An unreachable box never sends a code: curl fails and the probe prints 000, its placeholder for no answer. A dead service refuses the connection the same way. A 401 means the API answered you and rejected the password, so the fix is in your credentials, not the network.
The probe says the box is alive. Now you want facts from it, and the facts arrive as a JSON document dozens of fields deep. Nobody scrolls that on a fifty-box morning.
jq is the carving knife. The -r flag makes it print raw text instead of quoted JSON, and a format string picks fields by name: inside double quotes, \(.FieldName) drops that field into the line.
Ask Systems/1 for the one line a fleet dashboard stores:
curl -s -u ADMIN:ADMIN http://$BMC_IP:5000/redfish/v1/Systems/1 | jq -r '"\(.HostName) | \(.Model) | power=\(.PowerState) | health=\(.Status.Health)"'
prompt: ops@dc-east-ws01:~$ answer: curl -s -u ADMIN:ADMIN http://$BMC_IP:5000/redfish/v1/Systems/1 | jq -r '"\(.HostName) | \(.Model) | power=\(.PowerState) | health=\(.Status.Health)"' output: bay14-r750xs | PowerEdge R750xs | power=On | health=OK hint: Pipe the Systems/1 GET into jq -r with a quoted format string; each field is backslash-parens around its name, like \(.HostName) and \(.Status.Health).
Read it left to right: hostname, model, power, health. Everything a morning sweep needs to know about Bay 14, in one grep-able line.
Identity is one object. Sensors come as lists: the Thermal document holds a Temperatures[] array with four entries, and each entry carries a Name, a ReadingCelsius, and its own Status.Health.
jq walks arrays with .Temperatures[]: pipe the entries into a format string and you get one line per sensor.
curl -s -u ADMIN:ADMIN http://$BMC_IP:5000/redfish/v1/Chassis/1/Thermal | jq -r '.Temperatures[] | "\(.Name): \(.ReadingCelsius)C [\(.Status.Health)]"'
prompt: ops@dc-east-ws01:~$ answer: curl -s -u ADMIN:ADMIN http://$BMC_IP:5000/redfish/v1/Chassis/1/Thermal | jq -r '.Temperatures[] | "\(.Name): \(.ReadingCelsius)C [\(.Status.Health)]"' output: CPU0 Temp: 45C [OK] CPU1 Temp: 43C [OK] Inlet Temp: 24C [OK] Exhaust Temp: 38C [OK] hint: Iterate the array with .Temperatures[] and pipe each entry into a format string built from \(.Name), \(.ReadingCelsius), and \(.Status.Health).
The same .Something[] pattern reads every array on this API: .Fans[] for the six fans, .PowerSupplies[] for both power supplies. Learn it once and every list on the tree opens up.
You now hold three commands: a probe, an identity line, a thermal sweep. A tool is those commands in a file, in the right order, with one decision at the top: if the probe does not come back 200, report the box unreachable and stop. Never pull JSON from a dead address.
Here is bmc-health.sh, assembled once here, recalled by you in the lab:
#!/bin/bash
# IronCore fleet health check, one line block per box
B="http://$BMC_IP:5000"
A="-u ADMIN:ADMIN"
code=$(curl -s -o /dev/null -w '%{http_code}' $A $B/redfish/v1/)
if [ "$code" != "200" ]
then
echo "$BMC_IP UNREACHABLE (HTTP $code)"
exit 1
fi
curl -s $A $B/redfish/v1/Systems/1 | jq -r '"\(.HostName) | \(.Model) | power=\(.PowerState) | health=\(.Status.Health)"'
curl -s $A $B/redfish/v1/Chassis/1/Thermal | jq -r '.Temperatures[] | " \(.Name): \(.ReadingCelsius)C [\(.Status.Health)]"'
curl -s $A $B/redfish/v1/Chassis/1/Power | jq -r '" Draw: \(.PowerControl[0].PowerConsumedWatts)W"'
Make it executable with chmod +x and it runs as ./bmc-health.sh. Point the same file at a different $BMC_IP and it reports a different box. That is the entire scaling story:
{ "height": 430, "caption": "The script never changes. Only the list of addresses grows.", "nodes": [ { "id": "you", "label": "You (NOC)", "kind": "admin", "x": 0, "y": 170, "detail": "One engineer, one command, however many boxes the list holds." }, { "id": "script", "label": "bmc-health.sh", "kind": "net", "x": 260, "y": 170, "detail": "Probe, pull, report. It does not care whether the list has one address or fifty." }, { "id": "b14", "label": "Bay 14 BMC", "kind": "bmc", "x": 560, "y": 30, "detail": "The live box in this lab. Everything it says today is the template for every other bay." }, { "id": "b15", "label": "Bay 15 BMC", "kind": "bmc", "x": 560, "y": 160, "detail": "Hardware arriving next month. Same API, different IP, zero new code." }, { "id": "b16", "label": "Bay 16 BMC", "kind": "bmc", "x": 560, "y": 290, "detail": "A loop over a file of IPs covers the whole row." }, { "id": "report", "label": "health-report.txt", "kind": "host", "x": 260, "y": 360, "detail": "One line block per box. The NOC reads this file, not sixteen browser tabs." } ], "edges": [ { "from": "you", "to": "script", "label": "runs", "kind": "plain" }, { "from": "script", "to": "b14", "label": "GET x4", "kind": "oob" }, { "from": "script", "to": "b15", "label": "GET x4", "kind": "oob" }, { "from": "script", "to": "b16", "label": "GET x4", "kind": "oob" }, { "from": "script", "to": "report", "label": "appends", "kind": "plain" } ], "toggle": { "label": "Scale:", "on": "Deployment day (the row)", "off": "Today (one box)", "dimOff": ["b15", "b16", "e:script-b15", "e:script-b16"] } }
>>> Probe first. On a fifty-box sweep, a hung box must cost you one probe and one UNREACHABLE line, not a stack of timeouts while three data pulls wait on a dead address in turn.
The BMC behind this lab is a standards-faithful emulator: real HTTP, real status codes, real Redfish JSON, served on port 5000. A production iDRAC serves the same schemas over HTTPS on port 443 with certificates and session tokens, so on real hardware you add TLS handling and the field names stay identical.
Time to close Work Order DC-EAST-WO-1015 before the fleet lands. On the machine you are about to open, $BMC_IP is preset and the credentials are ADMIN / ADMIN.
Six objectives stand between Bay 14 and deployment day:
1. Prove the API answers. One probe, and the only thing on screen is the code a monitoring system would read. 2. File the fleet line. The one-line report for this box: hostname, model, power, health, saved to ~/fleet-line.txt. 3. Sweep the thermals. Every temperature sensor with its reading and health, into ~/thermal-report.txt. 4. Audit the power train. Both supplies with output watts, capacity, and health, into ~/power-report.txt. 5. Assemble the tool. The probe-first health check, executable, at ~/bmc-health.sh. 6. The challenge. Run your tool the way the NOC will and file its report as ~/health-report.txt. No structure given.
Here is the deal: the workspace shows no commands. It hands you one objective at a time, and you recall the move. That recall is the whole point; it is how last hour's reading becomes your skill.
Stuck is normal. Hit Request a signal on any objective for a nudge, or ask Daemon. It knows exactly which objective you are on and will nudge, not spoil.
Your workstation dc-east-ws01 is booting in the bay below, with the Bay 14 BMC beside it on the management network. This is the last work order of the track. Launch it, and make one box teach you how to run fifty.
Practice Redfish Automation in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.