Connecting devices

A device talks to Qawk exactly as it talks to hawkBit. If you have devices already updating from hawkBit, changing one URL is the entire migration.

How a device talks to Qawk

Devices poll:

http://<server>:8080/<tenant>/controller/v1/<controller id>

The loop is always the same four steps, and the device drives all of them — Qawk never reaches into a machine:

  1. Poll. "Anything for me?" The answer carries how long to wait before asking again, and a link when there is work.
  2. Fetch. Follow the link. It says what to install, with download URLs and hashes, and whether the install is forced, may be deferred, or must wait for a maintenance window.
  3. Download and install. Downloads support Range, so a device on a bad line resumes rather than starting over.
  4. Report. Say how it went. Everything turns on this — gates, error thresholds and system rollbacks are all driven by what devices report.

Authenticating a device

KindHeaderUse it when
Gateway tokenAuthorization: GatewayToken <key> One shared secret for a fleet. A device that has never been seen registers itself on its first poll.
Target tokenAuthorization: TargetToken <key> A secret per device. Stronger, but each device must be created first.

Turn gateway tokens on in the console (Configuration) or through the API:

A='-u admin:a-long-admin-password'
curl $A -X PUT -H 'Content-Type: application/json' -d '{"value": true}' \
  http://localhost:8080/rest/v1/system/configs/authentication.gatewaytoken.enabled
curl $A -X PUT -H 'Content-Type: application/json' -d '{"value": "a-random-token"}' \
  http://localhost:8080/rest/v1/system/configs/authentication.gatewaytoken.key
Careful

A gateway token is a fleet-wide secret. Anything holding it can register a device and take updates. Keep it out of images that leave your control, put the server behind TLS, and rotate it if a device is lost.

Talking to it over HTTPS

Use https://. A device sends its token on every poll, and over plain HTTP anyone on the path has it — and with a gateway token, has the whole fleet.

If the server's certificate comes from a public authority, there is nothing to do: the device already trusts it. The two cases that need work are a certificate from your own internal authority and a self-signed one.

Telling a device to trust your authority

Put the authority's certificate on the device, where its TLS library looks — on most Linux images:

cp your-ca.pem /usr/local/share/ca-certificates/your-ca.crt
update-ca-certificates

Or point SWUpdate at it directly, which keeps it out of the system store:

suricatta: {
  url    = "https://updates.example.com";
  tenant = "DEFAULT";
  id     = "device-0001";
  gatewaytoken = "a-random-token";
  cafile = "/etc/ssl/certs/your-ca.pem";
};

Check it from the device before blaming anything else:

curl --cacert /etc/ssl/certs/your-ca.pem https://updates.example.com/qawk/v1/info
Careful

Never turn verification off to make it work. A device that accepts any certificate accepts any server, and an update server that can be impersonated is a way to install anything on your fleet. If the handshake fails, the usual cause is a certificate whose subjectAltName does not list the name the device actually uses — not the device.

Client certificates

With QAWK_TLS_CLIENT_CA set, the server demands a certificate from the device as well, checked during the handshake before any request exists. The device needs its own key and certificate:

suricatta: {
  url      = "https://updates.example.com";
  id       = "device-0001";
  cafile   = "/etc/ssl/certs/your-ca.pem";
  sslkey   = "/etc/ssl/private/device-0001-key.pem";
  sslcert  = "/etc/ssl/certs/device-0001.pem";
};

Generating those, and running the server this way, is in Installing the server.

SWUpdate (suricatta)

The common case. In swupdate.cfg:

suricatta: {
  url          = "https://updates.example.com";
  tenant       = "DEFAULT";
  id           = "device-0001";
  gatewaytoken = "a-random-token";
};

That is all. id is the controller id — what the device is called everywhere in Qawk, so use something you can find a machine by.

Note

Maintenance windows and SWUpdate. SWUpdate answers a deployment it is told to skip with closed/success and "Skipped Update.". hawkBit takes that at face value and marks the device updated when it is not. Qawk, outside a maintenance window, reads it as the skip being acknowledged: the action stays open and scheduled, and the device installs when the window opens. This is a deliberate difference from hawkBit, and it is what makes maintenance windows usable with SWUpdate at all.

The attributes that matter

A device reports attributes in configData. In hawkBit they are searchable labels. In Qawk they decide what the device is: which channel it joins, which centre it is in, which system it belongs to and which component of it. Get them right and the rest of Qawk arranges itself.

AttributeDrivesExample
ringWhich channel adopts it, via a channel rule.attribute.ring==beta
centeridWhich centre it is in — and so, which channel.attribute.centerid==c03
device_typeWhich component of a system it is.attribute.device_type==device2
deviceWhich system it belongs to.attribute.device==device-07
os_versionNothing by itself — useful to search and to see.attribute.os_version==2.1.0

The names are yours: the channel rule, the centre setting and the system type each name the field they read. Those above are the demo's, and are a reasonable set to copy.

PUT /DEFAULT/controller/v1/device-0001/configData
{
  "mode": "merge",
  "data": {
    "ring": "beta",
    "centerid": "c03",
    "device_type": "device2",
    "device": "device-07",
    "os_version": "2.1.0"
  }
}
Good to know

Report them on every boot. A device that reports its centre once and never again is fine until it is replaced or reflashed. A device that reports on every start is always in the right place, and a machine swapped into a lane picks up that lane's identity by itself.

Getting software in

This is hawkBit's Management API, unchanged: create a software module, upload its artifact, put it in a distribution set. Anything written for hawkBit does it, and so does the console.

A='-u admin:a-long-admin-password'; Q=http://localhost:8080

SM=$(curl -s $A -X POST -H 'Content-Type: application/json' \
  -d '[{"name":"app","version":"1.2.0","type":"application"}]' \
  $Q/rest/v1/softwaremodules | python3 -c 'import sys,json;print(json.load(sys.stdin)[0]["id"])')

curl -s $A -X POST -F "file=@app-1.2.0.swu" $Q/rest/v1/softwaremodules/$SM/artifacts

curl -s $A -X POST -H 'Content-Type: application/json' \
  -d '[{"name":"app","version":"1.2.0","type":"app","modules":[{"id":'$SM'}]}]' \
  $Q/rest/v1/distributionsets

Simulated devices

You do not need hardware to test any of this. qawk-sim registers, reports the attributes a real device would, polls, takes what it is given, waits a random while and reports. It speaks the real device API over the real network — it is not a mock inside the server.

# 50 devices reporting ring=lab, polling every 30 s
demo/simulate.sh --url http://localhost:8080 --token <token> --fleet lab:50

# several groups at once, and a name to tell runs apart
demo/simulate.sh --name shop --url http://localhost:8080 --token <token> \
  --fleet dev:20 --fleet beta:40 --fleet prod:500

# 16 systems -- a device1 with two device2 and a device3 -- in four centres
demo/simulate.sh --name centres --url http://localhost:8080 --token <token> --systems 16

demo/simulate.sh --list            # what is running
demo/simulate.sh --stop shop       # stop one run; --stop alone stops them all
docker logs -f qawk-sim-shop       # what they are doing, every 10 s

A simulated device's controller id is <name>-<group>-<n> (shop-prod-017). It answers a deployment outside its maintenance window the way SWUpdate does, and a download-only one as downloaded.

What a simulated device is, and what it is not

A simulated device is not a container of its own, and there is no operating system, no filesystem and no application inside it. One qawk-sim process — one container, however many devices you asked for — runs each device as a separate HTTP client with its own controller id, its own attributes, its own token and its own polling clock. Five hundred devices are five hundred conversations from one container.

What is real is everything the server can see:

RealPretended
Registration, the gateway or target token, and every poll of the device API over the network.—
The attributes it reports — device_type, centerid, the system key — which is what channel rules, centres and system types match on.—
The deployment it is offered, the order the orchestrator offers it in, and every feedback message: proceeding, closed/success, closed/failure.—
—The download. It never fetches the artifact; it does not even open the download URL. The bytes you uploaded are served to real devices, never to these.
—The installation. It sleeps between -install-min and -install-max and then reports success.
—The failure and the rollback. A device told to fail sleeps the same while and then answers closed/failure with “rolled back to the previous version”. Nothing was installed, so nothing was undone — the message is the whole of it.
There is no application to log into

A simulated device has no shell, no IP of its own and no hello app: it simulates installing and rolling back, and nothing else. That is enough to exercise every part of Qawk — channels, waves, gates, error thresholds, centres, the orchestrator's component order and a whole system rolling back — because all of those are decided by the server from what a device reports, and a simulated device reports exactly what a real one does.

What it cannot tell you is whether your image boots and your application runs. For that you need the real thing: flash a board, point SWUpdate's suricatta at Qawk (above) and watch the same release land on it. Qawk cannot tell the two apart — which is the point.

Putting simulated devices in centres

Add :centers= to a group and its devices are shared out among centres, reporting centerid exactly as a real one would. Anything after -- goes straight to qawk-sim:

demo/simulate.sh --url http://localhost:8080 --token <token> -- \
  -fleet beta:40:centers=1-2 \
  -fleet prod:200:centers=3-4 \
  -system device:16:device1=1,device2=2,device3=1:centers=4
FormMeans
centers=4Shared out among c01…c04.
centers=3-4Only c03 and c04 — so one channel's machines sit in one set of sites and another channel's elsewhere.
absentNo centre at all. The device reaches a channel by that channel's rule instead.
Good to know

The centres are shared on purpose. A -fleet and a -system both asking for centers=4 land in the same c01…c04 — because a centre is a place, and a place holds the lane computers with their terminals and the machines that stand alone. Put the centre in a channel and all of it follows, which is the thing worth seeing.

This is what the demo does: beta's devices in c01 and c02, prod's in c03 and c04, and the systems spread across all four — so every centre holds both kinds, 28 machines each. dev (the lab) and expo (a trade show) have no centre, which is what a device reaching a channel by rule looks like beside one reaching it by place.

Making things fail on purpose

This is the part worth knowing about, because you cannot trust a safety mechanism you have never seen fire:

FlagDoesShows you
a module named brokenFails on every device.a release halting on its threshold.
-- -fail-rate 0.1One deployment in ten fails, at random.how a real, imperfect fleet looks.
-- -fail-where device=device-07,device_type=device3,set=2.0Those devices fail, only for that set.one component of one system failing — and the whole system rolling back.
-- -install-min 5s -install-max 60sHow long an install takes.waves and timeouts behaving.
--interval 10sHow often each device polls.everything, faster.

Everything after -- goes to qawk-sim unchanged; docker run --rm --entrypoint qawk-sim qawk:local -help lists it all.

Load

qawk-load measures how many polls a server holds:

docker run --rm --network host --ulimit nofile=65536:65536 \
  --entrypoint qawk-load qawk:local \
  -url http://localhost:8080 -token <token> \
  -devices 10000 -interval 30s -ramp 30s -duration 180s -act

It prints requests per second, latency percentiles and errors. See Scaling for what the numbers mean.

API

Every device operation is in the Device API reference, with the call in curl, Python, JavaScript, Go and PowerShell.