Skip to content

Errors

The methods of the client do not throw when a call fails. A refused request, a missing privilege, a node that cannot be reached: each returns a Result, and the code decides what to do.

$result = $client->getNodes()->get('pve01')->getQemu()->get(100)->getStatus()->getStart()->vmStart();
if (!$result->isSuccessStatusCode()) {
echo "Start failed: {$result->getStatusCode()} {$result->getReasonPhrase()}";
}

A call whose Result is not checked fails silently: the code goes on as if the VM had started.

Check True when
isSuccessStatusCode() The HTTP status is 200: Proxmox VE accepted the request
responseInError() The answer has an errors member: one or more parameters were refused. The status is 400
getError() Text of those errors, one parameter per line. Empty when responseInError() is false
getReasonPhrase() The reason of any failure: Proxmox VE writes its error message here

isSuccessStatusCode() is the one to test: it is false for every failure, including those that have responseInError(). Use getError() to show which parameter was wrong:

$result = $client->set('/nodes/pve01/qemu/100/config', ['memory' => 'abc']);
if (!$result->isSuccessStatusCode()) {
echo $result->getReasonPhrase() . "\n"; // Parameter verification failed.
if ($result->responseInError()) {
echo $result->getError() . "\n"; // memory : invalid format - ...
}
}

A request that cannot be completed is also returned as a Result, built by the client, so the same check covers it:

Failure getStatusCode() getReasonPhrase()
Name not resolved, connection refused, certificate refused 0 The error of curl, for example Could not resolve host: pve99
Request timed out (the timeout of the client) 0 The error of curl, for example Connection timed out after 3001 milliseconds
An answer that is not JSON (a proxy page, another service on that port) The status received The reason received

In all three getResponse() is null.

Behind a reverse proxy that speaks HTTP/2 the reason is empty, because HTTP/2 has none: the status code is still there.

Status 500 is what Proxmox VE itself returns when it cannot do what was asked, for example for a VM id that does not exist: the reason is in getReasonPhrase().

Call Exception When
waitForTaskToFinish, taskIsRunning, getExitStatusTask, getNodeFromTask PveResultException The status of the task cannot be read, or the value is not a UPID
login PveExceptionAuthentication The user needs a second factor and none was given
create, set and the generated methods that use them InvalidArgumentException A parameter cannot be written as JSON, for example text that is not UTF-8. Nothing is sent

Both exceptions of the library carry the failed call in getResult().

Reading a member that is not in the answer, or the data of a call without an answer, does not throw: PHP raises a warning and the value is null. See Results.

An application that prefers exceptions can turn every failed call into one with a small function of its own:

use Corsinvest\ProxmoxVE\Api\PveResultException;
use Corsinvest\ProxmoxVE\Api\Result;
function ensureSuccess(Result $result)
{
if (!$result->isSuccessStatusCode()) {
throw new PveResultException($result, $result->responseInError()
? $result->getError()
: "{$result->getStatusCode()} {$result->getReasonPhrase()}");
}
if ($result->getResponse() === null) {
throw new PveResultException($result, 'The answer is not from the Proxmox VE API');
}
return $result;
}
$result = ensureSuccess($client->getNodes()->get('pve01')->getQemu()->get(100)->getStatus()->getStart()->vmStart());

A successful call is not a finished operation

Section titled “A successful call is not a finished operation”

For the operations that run as a task, a successful Result means the task was started. Whether it ended well is in the exit status of the task: see Tasks.

Because failures are results, a retry is a loop on the Result, not a catch. Retry only what is safe to repeat, and only for failures that can pass:

$result = null;
for ($attempt = 1; $attempt <= 3; $attempt++) {
$result = $client->getCluster()->getResources()->resources();
$status = $result->getStatusCode();
$canPass = $status == 0 || $status >= 500;
if ($result->isSuccessStatusCode() || !$canPass) {
break;
}
sleep($attempt * 2);
}

The other statuses (wrong parameters, no permission) give the same answer every time: retrying them is useless. A 401 from an expired ticket needs a new login, not a retry.