Retrieving output from REST API exec call

I’m having an issue retrieving the output from an exec command issued from the API.

Using a simple ls command to illustrate

curl -s -k -X POST -d '{ "command": ["ls", "/"], "record-output": true, "interactive": false }' https://127.0.0.1:8443/1.0/containers/nginx/exec | jq

Produces the result

{
  "type": "async",
  "status": "Operation created",
  "status_code": 100,
  "operation": "/1.0/operations/c5e10c58-6d4b-49c3-a8b8-a0b48cec0550",
  "error_code": 0,
  "error": "",
  "metadata": {
    "id": "c5e10c58-6d4b-49c3-a8b8-a0b48cec0550",
    "class": "task",
    "description": "Executing command",
    "created_at": "2021-04-23T14:35:17.049309743+02:00",
    "updated_at": "2021-04-23T14:35:17.049309743+02:00",
    "status": "Running",
    "status_code": 103,
    "resources": {
      "containers": [
        "/1.0/containers/nginx"
      ],
      "instances": [
        "/1.0/instances/nginx"
      ]
    },
    "metadata": null,
    "may_cancel": false,
    "err": "",
    "location": "none"
  }
}

According to the documentation (https://linuxcontainers.org/lxd/docs/master/rest-api#10instancesnameexec)

I expect the return value to contain an output entry, but no such entry is present. What am I missing?

The output you’re showing indicates it’s still running, I believe if you hit it again shortly after, it should show it’s completed and metadata should point you to the right endpoints.

I tried calling the url returned in operation like so

curl -s -k https://127.0.0.1:8443/1.0/operations/0c26f718-96d1-4268-bfd8-315dba7ed93e

But that just returns an error no matter how long I wait.

{
  "error": "not found",
  "error_code": 404,
  "type": "error"
}

Operations disappear within 5s of them being complete.
In this case what you probably should do is get the UUID from the POST call and then immediately call /1.0/operations/UUID/wait. This will then return once it’s over.

I’m trying to do the same thing as lxdpls but using the unix-socket connection and I never get any results back. I get the following on a simple exec call:

curl -s --unix-socket /var/snap/lxd/common/lxd/unix.socket -H "Content-Type: application/json" -X POST -d @hello.json lxd/1.0/containers/golab/exec | jq
{
  "type": "async",
  "status": "Operation created",
  "status_code": 100,
  "operation": "/1.0/operations/d71cca5b-6e1a-4f70-944d-6fbc6f1e7435",
  "error_code": 0,
  "error": "",
  "metadata": {
    "id": "d71cca5b-6e1a-4f70-944d-6fbc6f1e7435",
    "class": "task",
    "description": "Executing command",
    "created_at": "2021-04-25T22:33:56.964351576-04:00",
    "updated_at": "2021-04-25T22:33:56.964351576-04:00",
    "status": "Running",
    "status_code": 103,
    "resources": {
      "containers": [
        "/1.0/containers/golab"
      ],
      "instances": [
        "/1.0/instances/golab"
      ]
    },
    "metadata": null,
    "may_cancel": false,
    "err": "",
    "location": "none"
  }
}

A call to /1.0/operations/d71cca5b-6e1a-4f70-944d-6fbc6f1e7435 immediately after this call returns nothing. I also get nothing if I use wait or try to list all of the operations within 5 seconds of the original call.

The hello.js file looks like:

 {
     "command": ["ls"],
     "interactive": false,
     "record-output": true
 }

If I run lxc exec golab ls, I get the expected result. What am I doing incorrectly and how do you get the results back from an exec call via the API?

stgraber@castiana:~$ curl --unix-socket /var/snap/lxd/common/lxd/unix.socket lxd$(curl -s --unix-socket /var/snap/lxd/common/lxd/unix.socket lxd/1.0/instances/a1/exec -X POST -d '{"command": ["ls", "/"], "record-output": true}' | jq -r .operation)/wait | jq .
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   660  100   660    0     0   644k      0 --:--:-- --:--:-- --:--:--  644k
{
  "type": "sync",
  "status": "Success",
  "status_code": 200,
  "operation": "",
  "error_code": 0,
  "error": "",
  "metadata": {
    "id": "04ca019e-1603-4ac0-82ec-e34dbeb2cea4",
    "class": "task",
    "description": "Executing command",
    "created_at": "2021-04-26T16:12:04.041047945-04:00",
    "updated_at": "2021-04-26T16:12:04.073573128-04:00",
    "status": "Success",
    "status_code": 200,
    "resources": {
      "containers": [
        "/1.0/containers/a1"
      ],
      "instances": [
        "/1.0/instances/a1"
      ]
    },
    "metadata": {
      "output": {
        "1": "/1.0/instances/a1/logs/exec_04ca019e-1603-4ac0-82ec-e34dbeb2cea4.stdout",
        "2": "/1.0/instances/a1/logs/exec_04ca019e-1603-4ac0-82ec-e34dbeb2cea4.stderr"
      },
      "return": 0
    },
    "may_cancel": false,
    "err": "",
    "location": "none"
  }
}
2 Likes

Great! That works perfectly. From the examples it wasn’t clear. Now it is. Thanks.

How it to set to 5 minutes?? For example… I can’t have time to enter next API request into the curl after exec. I want to understand how websockets work, there is not a single example on Python in the world with LXD websockets :(( Websokets python working but not with LXD… I want understand… Why???

It’s set to 5s because operations hold pointers to a lot of internal data, it’s not just a DB record, keeping them around keeps a lot of files open and stuff in memory which could easily have LXD use 100MBs of RAM more than it normal does.

Operations are definitely meant to be used by code, not by humans, so if you want to play with them, you’re going to need a script so that it can connect and act against it within the 5s window.

1 Like