Understanding of lxc exec REST API

Hi !
I try to understand how the lxc exec REST API can be used and, so far I fall into something that don"t work as I expect it, so I guess that there is something I misunderstand. Here is the context :

A POST to https://lxd-server:8443/1.0/containers/[container]/exec request with, for example, these parameters :
{“command”: [“systemctl”, “status”, “apache2”], “record_output”: false, “wait-for-websocket”: true, “interactive”: true, “environment”: { “HOME”: “/root”, “TERM”: “xterm-256color”, “USER”:“root”, “SYSTEMD_PAGER”: ‘’}, “width”: 192, “height”: 48};

The idea behind this is to get the apache2 status running inside the container.

This works fine. But I want the output of the ran command. So, here are the things I issue thereafter :

  1. launch a GET request to 1.0/operations/[UUID]/wait?timeout=10 (with the UUID I got from the POST request).
  2. open a websocket on wss://lxd-server:8443/1.0/ws/operations/[UUID]/websocket?secret=[secret] (I guess that this is expected to answer to the “wait-for-websocket” parameter set to true in the initial POST request). Here, secret is made from the metadata.fds.control.
  3. open a websocket on wss://lxc-server:8443/1.0/ws/operations/[UUID]/websocket?secret=[secret] (secret is build here from metadata.fds.0, to get the output of the command).

As a matter of fact, this works fine. However, what I don’t understand is that I have to wait 10 seconds (the timeout parameter) to get the output. If I don’t give the timeout parameter, things wait for ever… Ideally, I would expect to have an output as soon as the launched command is running. Here, it don’t provide much information, but with more verbose and long-run commands, the timeout can lead to a kill (or an end-of-output) that I don’t want.

What do I do wrong ? What did I misunderstand ? Is there a way to achieve what I want ?
Thanks all for your kind help.

/wait will wait until the command is done running, /wait?timeout=10 will wait until the command is done running OR timeout after 10s.

It’s unclear above if you’re attempting 1) before doing 2) and 3) or if you’re doing this in parallel.
If in parallel, then wait should return once the command is done completing, if doing it before 2) and 3) then it would indeed hang since the command hasn’t started yet (will only start once all websockets are connected).

Thanks for your kind help once again @stgraber.
I’ve been a little further and face some things I don’t quite understand.

I’ve setup a html page that execute some javascript code when the page loads.

Basically, it can be summed-up this way :

wsOp = new WebSocket (“wss://localhost:8443/ws/events?type=operation”);
wsOp.onmessage = function (evt) {
data = JSON.parse(evt.data)[‘metadata’];
switch (donnees[‘status_code’]) {
case 100: console.log (‘Operation ‘+data[‘id’]+’ created’); break;
case 101: console.log (‘Operation ‘+data[‘id’]+’ started’); break;
case 102: console.log (‘Operation ‘+data[‘id’]+’ stopped’); break;
case 103: console.log (‘Operation ‘+data[‘id’]+’ running’); break;
case 104: console.log (‘Operation ‘+data[‘id’]+’ cancelling’); break;
case 105: console.log (‘Operation ‘+data[‘id’]+’ pending’); break;
case 106: console.log (‘Operation ‘+data[‘id’]+’ starting’); break;
case 107: console.log (‘Operation ‘+data[‘id’]+’ stopping’); break;
case 108: console.log (‘Operation ‘+data[‘id’]+’ aborting’); break;
case 109: console.log (‘Operation ‘+data[‘id’]+’ freezing’); break;
case 110: console.log (‘Operation ‘+data[‘id’]+’ frozen’); break;
case 111: console.log (‘Operation ‘+data[‘id’]+’ thawed’); break;
case 200: console.log (‘Operation ‘+data[‘id’]+’ success’); break;
case 400: console.log (‘Operation ‘+data[‘id’]+’ failure’); break;
case 401: console.log (‘Operation ‘+data[‘id’]+’ cancelled’); break;
}
watchOp (‘1.0/’+data[‘id’]); // I’ll detail this function afterwards
}

Going only with this code snippet, when an operation is submitted on my local lxd server, I should see something in the javascript console log of my browser. Well, it does, but I have some few questions :

Why do I never see the codes 100, 101 ?
All I get is :
Operation [UUID] pending
Operation [UUID] running
Operation [UUID] running
Opeartion [UUID] success

For my tests, I use a XMLHttpRequest() object this way :

var http=new XMLHttpRequest();
var url=“/1.0/containers/”+container+“/exec”;
var params={“command”: [“systemctl”, “status”, App], “record_output”: false, “wait-for-websocket”: true, “interactive”: false, “environment”: { “HOME”: “/root”, “TERM”: “xterm-256color”, “USER”:“root”, “SYSTEMD_PAGER”: ‘’}, “width”: 192, “height”: 48};
http.open (“POST”, url, true);
http.setRequestHeader(“Content-type”, “application/json”);
http.send(JSON.stringify(params));

As for the WhachOp function, here is the relevant code :

function watchOp (url) {
var httpOp = new XMLHttpRequest();
httpOp.open (“GET”, url, true);
httpOp.onreadystatechange = function () {
if (httpOp.readyState == 4 && httpOp.status == 200) {
var opData = JSON.parse (httpOp.responseText)[‘metadata’];
var wsStdin = new Websocket (‘ws://localhost/ws/operations/’+opData[‘id’]+“/websocket?”+encodeURI(“secret=”+opData[‘metadata’][‘fds’][0]);
var wsStdOut = new Websocket (‘ws://localhost/ws/operations/’+opData[‘id’]+“/websocket?”+encodeURI(“secret=”+opData[‘metadata’][‘fds’][1]);
var wsStdErr = new WebSocket (‘ws://localhost/ws/operations/’+opData[‘id’]+“/websocket?”+encodeURI(“secret=”+opData[‘metadata’][‘fds’][3]);
var wsControl = new WebSocket (‘ws://localhost/ws/operations/’+opData[‘id’]+“/websocket?”+encodeURI(“secret=”+opData[‘metadata’][‘fds’][‘control’]);
var waitURI = ‘1.0/operations/’+opData[‘id’]+‘/wait?timeout=10’;
var httpWait = new XMLHttpRequest();
httpWait.open (“GET”, waitURI, true);
httpWait.onreadystatechange = function () {
if (httpWait.readyState == 4 && httpWait.status == 400) {
console.log (“Command terminated”);
}
};
httpWait.send();
}
}
};
httpOp.send();
}

I’ve thrown away all the code that makes tests upon the presence or not of each opData[‘metadata’][‘fds’][xxx] for the ease of reading. Each of the websockets have a onmessage function that displays the data received, if any, in the console.

What I don’t understand from this point are the following :

  1. following your answer, I’ve made up that the request to the /wait is made after the opening of all the websockets. However, If the params send are :

“wait-for-websocket”: true
“interactive”: false

All goes fine and the status = 200 is present. I get the apache2 status, but I don’t get the control chars (normally, there are couloured text in the output).

  1. if the params are :
    “wait-for-websocket”: true
    “interactive”: true

Then, I get the full output, from fds[0] (which is normal, according to the doc) and there is, of course, no fds[1] nor fds[2]. Yet, even if the output is better, the call to /wait?timeout=10 waits until the timeout is done.
And, I don’t get the -so waited- status = 200.
All I get is :
Operation [UUID] pending
Operation [UUID] running

I beleive I understand a little better how all this works, but there are still some things that are obscure to me.
So, if you somehow have some time to explain :
Why don’t I get the “couloured” output (I guess it has something to do with the interactive = false parameter which might have some impact on the tty allocated (or not)) ?
Why do I never get the 200 status (Success) in the second form ?

Thanks a lot in advance for having read all this long and detailed post.

I forgot… Bonus of this code (or not) if the page stays opened :
Since there is a websocket opened at first on ws/events?type=operation, operations launched outside (in a terminal for example) trigger the onmessage function related to it.
And it is triggered. I can see it in the console.
Yet, even if my code goes on and I can see in the logs of my browser that the different websockets (fds[xxx] provided they are present) are opened as expected, I never get any output.
For example : in a console if I launch on my machine “lxc exec container – systemctl status apache2”, I get the output in my console, but not in my browser… Why is that ? The docs read :

/1.0/operations/<uuid>/websocket

GET ( ?secret=SECRET )

  • Description: This connection is upgraded into a websocket connection speaking the protocol defined by the operation type. For example, in the case of an exec operation, the websocket is the bidirectional pipe for stdin/stdout/stderr to flow to and from the process inside the container. In the case of migration, it will be the primary interface over which the migration information is communicated. The secret here is the one that was provided when the operation was created. Guests are allowed to connect provided they have the right secret.

So, I should get everything from any operation, shouldn’t I ?