Remove DaemonConnectionException (#885)

* remove DaemonConnectionException

* update tests
This commit is contained in:
Boy132 2025-01-07 22:58:04 +01:00 committed by GitHub
parent 6fcf4173d3
commit c93a836ad8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 237 additions and 484 deletions

View File

@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
use Illuminate\Validation\Factory as ValidatorFactory; use Illuminate\Validation\Factory as ValidatorFactory;
use App\Repositories\Daemon\DaemonPowerRepository; use App\Repositories\Daemon\DaemonPowerRepository;
use App\Exceptions\Http\Connection\DaemonConnectionException; use Exception;
class BulkPowerActionCommand extends Command class BulkPowerActionCommand extends Command
{ {
@ -71,7 +71,7 @@ class BulkPowerActionCommand extends Command
try { try {
$powerRepository->setServer($server)->send($action); $powerRepository->setServer($server)->send($action);
} catch (DaemonConnectionException $exception) { } catch (Exception $exception) {
$this->output->error(trans('command/messages.server.power.action_failed', [ $this->output->error(trans('command/messages.server.power.action_failed', [
'name' => $server->name, 'name' => $server->name,
'id' => $server->id, 'id' => $server->id,

View File

@ -1,73 +0,0 @@
<?php
namespace App\Exceptions\Http\Connection;
use Exception;
use Illuminate\Http\Response;
use App\Exceptions\DisplayException;
use Illuminate\Support\Facades\Context;
class DaemonConnectionException extends DisplayException
{
private int $statusCode = Response::HTTP_GATEWAY_TIMEOUT;
/**
* Every request to the daemon instance will return a unique X-Request-Id header
* which allows for all errors to be efficiently tied to a specific request that
* triggered them, and gives users a more direct method of informing hosts when
* something goes wrong.
*/
private ?string $requestId;
/**
* Throw a displayable exception caused by a daemon connection error.
*/
public function __construct(?Exception $previous, bool $useStatusCode = true)
{
/** @var \GuzzleHttp\Psr7\Response|null $response */
$response = method_exists($previous, 'getResponse') ? $previous->getResponse() : null;
$this->requestId = $response?->getHeaderLine('X-Request-Id');
Context::add('request_id', $this->requestId);
if ($useStatusCode) {
$this->statusCode = is_null($response) ? $this->statusCode : $response->getStatusCode();
// There are rare conditions where daemon encounters a panic condition and crashes the
// request being made after content has already been sent over the wire. In these cases
// you can end up with a "successful" response code that is actual an error.
//
// Handle those better here since we shouldn't ever end up in this exception state and
// be returning a 2XX level response.
if ($this->statusCode < 400) {
$this->statusCode = Response::HTTP_BAD_GATEWAY;
}
}
if (is_null($response)) {
$message = 'Could not establish a connection to the machine running this server. Please try again.';
} else {
$message = sprintf('There was an error while communicating with the machine running this server. This error has been logged, please try again. (code: %s) (request_id: %s)', $response->getStatusCode(), $this->requestId ?? '<nil>');
}
// Attempt to pull the actual error message off the response and return that if it is not
// a 500 level error.
if ($this->statusCode < 500 && !is_null($response)) {
$body = json_decode($response->getBody()->__toString(), true);
$message = sprintf('An error occurred on the remote host: %s. (request id: %s)', $body['error'] ?? $message, $this->requestId ?? '<nil>');
}
$level = $this->statusCode >= 500 && $this->statusCode !== 504
? DisplayException::LEVEL_ERROR
: DisplayException::LEVEL_WARNING;
parent::__construct($message, $previous, $level);
}
/**
* Return the HTTP status code for this exception.
*/
public function getStatusCode(): int
{
return $this->statusCode;
}
}

View File

@ -3,10 +3,10 @@
namespace App\Filament\Server\Pages; namespace App\Filament\Server\Pages;
use App\Enums\ServerState; use App\Enums\ServerState;
use App\Exceptions\Http\Connection\DaemonConnectionException;
use App\Facades\Activity; use App\Facades\Activity;
use App\Models\Permission; use App\Models\Permission;
use App\Models\Server; use App\Models\Server;
use App\Repositories\Daemon\DaemonServerRepository;
use Exception; use Exception;
use Filament\Facades\Filament; use Filament\Facades\Filament;
use Filament\Forms\Components\Actions\Action; use Filament\Forms\Components\Actions\Action;
@ -18,8 +18,6 @@ use Filament\Forms\Components\TextInput;
use Filament\Forms\Form; use Filament\Forms\Form;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use Filament\Support\Enums\Alignment; use Filament\Support\Enums\Alignment;
use GuzzleHttp\Exception\TransferException;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Number; use Illuminate\Support\Number;
class Settings extends ServerFormPage class Settings extends ServerFormPage
@ -202,17 +200,23 @@ class Settings extends ServerFormPage
->modalHeading('Are you sure you want to reinstall the server?') ->modalHeading('Are you sure you want to reinstall the server?')
->modalDescription('Some files may be deleted or modified during this process, please back up your data before continuing.') ->modalDescription('Some files may be deleted or modified during this process, please back up your data before continuing.')
->modalSubmitActionLabel('Yes, Reinstall') ->modalSubmitActionLabel('Yes, Reinstall')
->action(function (Server $server) { ->action(function (Server $server, DaemonServerRepository $serverRepository) {
abort_unless(auth()->user()->can(Permission::ACTION_SETTINGS_REINSTALL, $server), 403); abort_unless(auth()->user()->can(Permission::ACTION_SETTINGS_REINSTALL, $server), 403);
$server->fill(['status' => ServerState::Installing])->save(); $server->fill(['status' => ServerState::Installing])->save();
try { try {
Http::daemon($server->node)->post(sprintf( $serverRepository->reinstall();
'/api/servers/%s/reinstall', } catch (Exception $exception) {
$server->uuid report($exception);
));
} catch (TransferException $exception) { Notification::make()
throw new DaemonConnectionException($exception); ->danger()
->title('Server Reinstall failed')
->body($exception->getMessage())
->send();
return;
} }
Activity::event('server:settings.reinstall') Activity::event('server:settings.reinstall')
@ -220,7 +224,7 @@ class Settings extends ServerFormPage
Notification::make() Notification::make()
->success() ->success()
->title('Server Reinstall Started') ->title('Server Reinstall started')
->send(); ->send();
}), }),
]) ])

View File

@ -70,4 +70,12 @@ abstract class ApplicationApiController extends Controller
{ {
return new Response('', Response::HTTP_NO_CONTENT); return new Response('', Response::HTTP_NO_CONTENT);
} }
/**
* Return an HTTP/406 response for the API.
*/
protected function returnNotAcceptable(): Response
{
return new Response('', Response::HTTP_NOT_ACCEPTABLE);
}
} }

View File

@ -10,6 +10,7 @@ use App\Repositories\Daemon\DaemonServerRepository;
use App\Services\Servers\ReinstallServerService; use App\Services\Servers\ReinstallServerService;
use App\Services\Servers\SuspensionService; use App\Services\Servers\SuspensionService;
use App\Services\Servers\TransferServerService; use App\Services\Servers\TransferServerService;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Response; use Illuminate\Http\Response;
class ServerManagementController extends ApplicationApiController class ServerManagementController extends ApplicationApiController
@ -80,19 +81,19 @@ class ServerManagementController extends ApplicationApiController
} }
// Node was not viable // Node was not viable
return new Response('', Response::HTTP_NOT_ACCEPTABLE); return $this->returnNotAcceptable();
} }
/** /**
* Cancels a transfer of a server to a new node. * Cancels a transfer of a server to a new node.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function cancelTransfer(ServerWriteRequest $request, Server $server): Response public function cancelTransfer(ServerWriteRequest $request, Server $server): Response
{ {
if (!$transfer = $server->transfer) { if (!$transfer = $server->transfer) {
// Server is not transferring // Server is not transferring
return new Response('', Response::HTTP_NOT_ACCEPTABLE); return $this->returnNotAcceptable();
} }
$transfer->successful = true; $transfer->successful = true;

View File

@ -2,12 +2,15 @@
namespace App\Http\Controllers\Api\Application\Servers; namespace App\Http\Controllers\Api\Application\Servers;
use App\Exceptions\Model\DataValidationException;
use App\Models\User; use App\Models\User;
use App\Models\Server; use App\Models\Server;
use App\Services\Servers\StartupModificationService; use App\Services\Servers\StartupModificationService;
use App\Transformers\Api\Application\ServerTransformer; use App\Transformers\Api\Application\ServerTransformer;
use App\Http\Controllers\Api\Application\ApplicationApiController; use App\Http\Controllers\Api\Application\ApplicationApiController;
use App\Http\Requests\Api\Application\Servers\UpdateServerStartupRequest; use App\Http\Requests\Api\Application\Servers\UpdateServerStartupRequest;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Validation\ValidationException;
class StartupController extends ApplicationApiController class StartupController extends ApplicationApiController
{ {
@ -22,9 +25,9 @@ class StartupController extends ApplicationApiController
/** /**
* Update the startup and environment settings for a specific server. * Update the startup and environment settings for a specific server.
* *
* @throws \Illuminate\Validation\ValidationException * @throws ValidationException
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
* @throws \App\Exceptions\Model\DataValidationException * @throws DataValidationException
*/ */
public function index(UpdateServerStartupRequest $request, Server $server): array public function index(UpdateServerStartupRequest $request, Server $server): array
{ {

View File

@ -10,27 +10,25 @@ use GuzzleHttp\Exception\BadResponseException;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
use App\Http\Controllers\Api\Client\ClientApiController; use App\Http\Controllers\Api\Client\ClientApiController;
use App\Http\Requests\Api\Client\Servers\SendCommandRequest; use App\Http\Requests\Api\Client\Servers\SendCommandRequest;
use App\Exceptions\Http\Connection\DaemonConnectionException; use Exception;
use Illuminate\Http\Client\ConnectionException;
class CommandController extends ClientApiController class CommandController extends ClientApiController
{ {
/** /**
* Send a command to a running server. * Send a command to a running server.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function index(SendCommandRequest $request, Server $server): Response public function index(SendCommandRequest $request, Server $server): Response
{ {
try { try {
$server->send($request->input('command')); $server->send($request->input('command'));
} catch (DaemonConnectionException $exception) { } catch (Exception $exception) {
$previous = $exception->getPrevious(); $previous = $exception->getPrevious();
if ($previous instanceof BadResponseException) { if ($previous instanceof BadResponseException) {
if ( if ($previous->getResponse() instanceof ResponseInterface && $previous->getResponse()->getStatusCode() === Response::HTTP_BAD_GATEWAY) {
$previous->getResponse() instanceof ResponseInterface
&& $previous->getResponse()->getStatusCode() === Response::HTTP_BAD_GATEWAY
) {
throw new HttpException(Response::HTTP_BAD_GATEWAY, 'Server must be online in order to send commands.', $exception); throw new HttpException(Response::HTTP_BAD_GATEWAY, 'Server must be online in order to send commands.', $exception);
} }
} }
@ -38,7 +36,9 @@ class CommandController extends ClientApiController
throw $exception; throw $exception;
} }
Activity::event('server:console.command')->property('command', $request->input('command'))->log(); Activity::event('server:console.command')
->property('command', $request->input('command'))
->log();
return $this->returnNoContent(); return $this->returnNoContent();
} }

View File

@ -22,6 +22,7 @@ use App\Http\Requests\Api\Client\Servers\Files\CompressFilesRequest;
use App\Http\Requests\Api\Client\Servers\Files\DecompressFilesRequest; use App\Http\Requests\Api\Client\Servers\Files\DecompressFilesRequest;
use App\Http\Requests\Api\Client\Servers\Files\GetFileContentsRequest; use App\Http\Requests\Api\Client\Servers\Files\GetFileContentsRequest;
use App\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest; use App\Http\Requests\Api\Client\Servers\Files\WriteFileContentRequest;
use Illuminate\Http\Client\ConnectionException;
class FileController extends ClientApiController class FileController extends ClientApiController
{ {
@ -38,7 +39,7 @@ class FileController extends ClientApiController
/** /**
* Returns a listing of files in a given directory. * Returns a listing of files in a given directory.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function directory(ListFilesRequest $request, Server $server): array public function directory(ListFilesRequest $request, Server $server): array
{ {
@ -63,7 +64,9 @@ class FileController extends ClientApiController
config('panel.files.max_edit_size') config('panel.files.max_edit_size')
); );
Activity::event('server:file.read')->property('file', $request->get('file'))->log(); Activity::event('server:file.read')
->property('file', $request->get('file'))
->log();
return new Response($response, Response::HTTP_OK, ['Content-Type' => 'text/plain']); return new Response($response, Response::HTTP_OK, ['Content-Type' => 'text/plain']);
} }
@ -102,13 +105,17 @@ class FileController extends ClientApiController
/** /**
* Writes the contents of the specified file to the server. * Writes the contents of the specified file to the server.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function write(WriteFileContentRequest $request, Server $server): JsonResponse public function write(WriteFileContentRequest $request, Server $server): JsonResponse
{ {
$this->fileRepository->setServer($server)->putContent($request->get('file'), $request->getContent()); $this->fileRepository
->setServer($server)
->putContent($request->get('file'), $request->getContent());
Activity::event('server:file.write')->property('file', $request->get('file'))->log(); Activity::event('server:file.write')
->property('file', $request->get('file'))
->log();
return new JsonResponse([], Response::HTTP_NO_CONTENT); return new JsonResponse([], Response::HTTP_NO_CONTENT);
} }
@ -154,7 +161,7 @@ class FileController extends ClientApiController
/** /**
* Copies a file on the server. * Copies a file on the server.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function copy(CopyFileRequest $request, Server $server): JsonResponse public function copy(CopyFileRequest $request, Server $server): JsonResponse
{ {
@ -162,13 +169,15 @@ class FileController extends ClientApiController
->setServer($server) ->setServer($server)
->copyFile($request->input('location')); ->copyFile($request->input('location'));
Activity::event('server:file.copy')->property('file', $request->input('location'))->log(); Activity::event('server:file.copy')
->property('file', $request->input('location'))
->log();
return new JsonResponse([], Response::HTTP_NO_CONTENT); return new JsonResponse([], Response::HTTP_NO_CONTENT);
} }
/** /**
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function compress(CompressFilesRequest $request, Server $server): array public function compress(CompressFilesRequest $request, Server $server): array
{ {
@ -188,7 +197,7 @@ class FileController extends ClientApiController
} }
/** /**
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function decompress(DecompressFilesRequest $request, Server $server): JsonResponse public function decompress(DecompressFilesRequest $request, Server $server): JsonResponse
{ {
@ -210,7 +219,7 @@ class FileController extends ClientApiController
/** /**
* Deletes files or folders for the server in the given root directory. * Deletes files or folders for the server in the given root directory.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function delete(DeleteFileRequest $request, Server $server): JsonResponse public function delete(DeleteFileRequest $request, Server $server): JsonResponse
{ {
@ -230,7 +239,7 @@ class FileController extends ClientApiController
/** /**
* Updates file permissions for file(s) in the given root directory. * Updates file permissions for file(s) in the given root directory.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function chmod(ChmodFilesRequest $request, Server $server): JsonResponse public function chmod(ChmodFilesRequest $request, Server $server): JsonResponse
{ {

View File

@ -9,6 +9,7 @@ use App\Transformers\Api\Client\StatsTransformer;
use App\Repositories\Daemon\DaemonServerRepository; use App\Repositories\Daemon\DaemonServerRepository;
use App\Http\Controllers\Api\Client\ClientApiController; use App\Http\Controllers\Api\Client\ClientApiController;
use App\Http\Requests\Api\Client\Servers\GetServerRequest; use App\Http\Requests\Api\Client\Servers\GetServerRequest;
use Illuminate\Http\Client\ConnectionException;
class ResourceUtilizationController extends ClientApiController class ResourceUtilizationController extends ClientApiController
{ {
@ -25,7 +26,7 @@ class ResourceUtilizationController extends ClientApiController
* 20 seconds at a time to ensure that repeated requests to this endpoint do not cause * 20 seconds at a time to ensure that repeated requests to this endpoint do not cause
* a flood of unnecessary API calls. * a flood of unnecessary API calls.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function __invoke(GetServerRequest $request, Server $server): array public function __invoke(GetServerRequest $request, Server $server): array
{ {

View File

@ -11,7 +11,7 @@ use App\Models\ServerTransfer;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use App\Exceptions\Http\Connection\DaemonConnectionException; use Illuminate\Http\Client\ConnectionException;
class ServerTransferController extends Controller class ServerTransferController extends Controller
{ {
@ -75,7 +75,7 @@ class ServerTransferController extends Controller
->setServer($server) ->setServer($server)
->setNode($transfer->oldNode) ->setNode($transfer->oldNode)
->delete(); ->delete();
} catch (DaemonConnectionException $exception) { } catch (ConnectionException $exception) {
logger()->warning($exception, ['transfer_id' => $server->transfer->id]); logger()->warning($exception, ['transfer_id' => $server->transfer->id]);
} }

View File

@ -11,8 +11,9 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Bus\DispatchesJobs;
use App\Services\Backups\InitiateBackupService; use App\Services\Backups\InitiateBackupService;
use App\Repositories\Daemon\DaemonPowerRepository; use App\Repositories\Daemon\DaemonPowerRepository;
use App\Exceptions\Http\Connection\DaemonConnectionException;
use App\Services\Files\DeleteFilesService; use App\Services\Files\DeleteFilesService;
use Exception;
use Illuminate\Http\Client\ConnectionException;
class RunTaskJob extends Job implements ShouldQueue class RunTaskJob extends Job implements ShouldQueue
{ {
@ -72,10 +73,10 @@ class RunTaskJob extends Job implements ShouldQueue
default: default:
throw new \InvalidArgumentException('Invalid task action provided: ' . $this->task->action); throw new \InvalidArgumentException('Invalid task action provided: ' . $this->task->action);
} }
} catch (\Exception $exception) { } catch (Exception $exception) {
// If this isn't a DaemonConnectionException on a task that allows for failures // If this isn't a ConnectionException on a task that allows for failures
// throw the exception back up the chain so that the task is stopped. // throw the exception back up the chain so that the task is stopped.
if (!($this->task->continue_on_failure && $exception instanceof DaemonConnectionException)) { if (!($this->task->continue_on_failure && $exception instanceof ConnectionException)) {
throw $exception; throw $exception;
} }
} }
@ -87,7 +88,7 @@ class RunTaskJob extends Job implements ShouldQueue
/** /**
* Handle a failure while sending the action to the daemon or otherwise processing the job. * Handle a failure while sending the action to the daemon or otherwise processing the job.
*/ */
public function failed(?\Exception $exception = null): void public function failed(): void
{ {
$this->markTaskNotQueued(); $this->markTaskNotQueued();
$this->markScheduleComplete(); $this->markScheduleComplete();

View File

@ -310,7 +310,7 @@ class Node extends Model
// @phpstan-ignore-next-line // @phpstan-ignore-next-line
return resolve(DaemonConfigurationRepository::class) return resolve(DaemonConfigurationRepository::class)
->setNode($this) ->setNode($this)
->getSystemInformation(connectTimeout: 3); ->getSystemInformation();
} catch (Exception $exception) { } catch (Exception $exception) {
$message = str($exception->getMessage()); $message = str($exception->getMessage());

View File

@ -3,11 +3,8 @@
namespace App\Repositories\Daemon; namespace App\Repositories\Daemon;
use Illuminate\Http\Client\Response; use Illuminate\Http\Client\Response;
use Webmozart\Assert\Assert;
use App\Models\Backup; use App\Models\Backup;
use App\Models\Server; use Illuminate\Http\Client\ConnectionException;
use GuzzleHttp\Exception\TransferException;
use App\Exceptions\Http\Connection\DaemonConnectionException;
class DaemonBackupRepository extends DaemonRepository class DaemonBackupRepository extends DaemonRepository
{ {
@ -26,64 +23,42 @@ class DaemonBackupRepository extends DaemonRepository
/** /**
* Tells the remote Daemon to begin generating a backup for the server. * Tells the remote Daemon to begin generating a backup for the server.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function backup(Backup $backup): Response public function backup(Backup $backup): Response
{ {
Assert::isInstanceOf($this->server, Server::class); return $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/backup",
[
try { 'adapter' => $this->adapter ?? config('backups.default'),
return $this->getHttpClient()->post( 'uuid' => $backup->uuid,
sprintf('/api/servers/%s/backup', $this->server->uuid), 'ignore' => implode("\n", $backup->ignored_files),
[ ]
'adapter' => $this->adapter ?? config('backups.default'), );
'uuid' => $backup->uuid,
'ignore' => implode("\n", $backup->ignored_files),
]
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
} }
/** /**
* Sends a request to daemon to begin restoring a backup for a server. * Sends a request to daemon to begin restoring a backup for a server.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function restore(Backup $backup, ?string $url = null, bool $truncate = false): Response public function restore(Backup $backup, ?string $url = null, bool $truncate = false): Response
{ {
Assert::isInstanceOf($this->server, Server::class); return $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/backup/$backup->uuid/restore",
[
try { 'adapter' => $backup->disk,
return $this->getHttpClient()->post( 'truncate_directory' => $truncate,
sprintf('/api/servers/%s/backup/%s/restore', $this->server->uuid, $backup->uuid), 'download_url' => $url ?? '',
[ ]
'adapter' => $backup->disk, );
'truncate_directory' => $truncate,
'download_url' => $url ?? '',
]
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
} }
/** /**
* Deletes a backup from the daemon. * Deletes a backup from the daemon.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function delete(Backup $backup): Response public function delete(Backup $backup): Response
{ {
Assert::isInstanceOf($this->server, Server::class); return $this->getHttpClient()->delete("/api/servers/{$this->server->uuid}/backup/$backup->uuid");
try {
return $this->getHttpClient()->delete(
sprintf('/api/servers/%s/backup/%s', $this->server->uuid, $backup->uuid)
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
} }
} }

View File

@ -3,8 +3,7 @@
namespace App\Repositories\Daemon; namespace App\Repositories\Daemon;
use App\Models\Node; use App\Models\Node;
use GuzzleHttp\Exception\TransferException; use Illuminate\Http\Client\ConnectionException;
use App\Exceptions\Http\Connection\DaemonConnectionException;
use Illuminate\Http\Client\Response; use Illuminate\Http\Client\Response;
class DaemonConfigurationRepository extends DaemonRepository class DaemonConfigurationRepository extends DaemonRepository
@ -12,20 +11,13 @@ class DaemonConfigurationRepository extends DaemonRepository
/** /**
* Returns system information from the daemon instance. * Returns system information from the daemon instance.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function getSystemInformation(?int $version = null, int $connectTimeout = 5): array public function getSystemInformation(): array
{ {
try { return $this->getHttpClient()
$response = $this ->connectTimeout(3)
->getHttpClient() ->get('/api/system')->throw()->json();
->connectTimeout($connectTimeout)
->get('/api/system' . (!is_null($version) ? '?v=' . $version : ''));
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
return $response->json() ?? [];
} }
/** /**
@ -33,17 +25,10 @@ class DaemonConfigurationRepository extends DaemonRepository
* this instance using a passed-in model. This allows us to change plenty of information * this instance using a passed-in model. This allows us to change plenty of information
* in the model, and still use the old, pre-update model to actually make the HTTP request. * in the model, and still use the old, pre-update model to actually make the HTTP request.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function update(Node $node): Response public function update(Node $node): Response
{ {
try { return $this->getHttpClient()->post('/api/update', $node->getConfiguration());
return $this->getHttpClient()->post(
'/api/update',
$node->getConfiguration(),
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
} }
} }

View File

@ -2,15 +2,10 @@
namespace App\Repositories\Daemon; namespace App\Repositories\Daemon;
use Carbon\CarbonInterval;
use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Http\Client\Response; use Illuminate\Http\Client\Response;
use Webmozart\Assert\Assert;
use App\Models\Server;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\TransferException;
use App\Exceptions\Http\Server\FileSizeTooLargeException; use App\Exceptions\Http\Server\FileSizeTooLargeException;
use App\Exceptions\Http\Connection\DaemonConnectionException; use Illuminate\Http\Client\ConnectionException;
class DaemonFileRepository extends DaemonRepository class DaemonFileRepository extends DaemonRepository
{ {
@ -19,23 +14,16 @@ class DaemonFileRepository extends DaemonRepository
* *
* @param int|null $notLargerThan the maximum content length in bytes * @param int|null $notLargerThan the maximum content length in bytes
* *
* @throws \GuzzleHttp\Exception\TransferException * @throws ConnectionException
* @throws \App\Exceptions\Http\Server\FileSizeTooLargeException * @throws FileSizeTooLargeException
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
* @throws FileNotFoundException * @throws FileNotFoundException
*/ */
public function getContent(string $path, ?int $notLargerThan = null): string public function getContent(string $path, ?int $notLargerThan = null): string
{ {
Assert::isInstanceOf($this->server, Server::class); $response = $this->getHttpClient()->get("/api/servers/{$this->server->uuid}/files/contents",
['file' => $path]
try { );
$response = $this->getHttpClient()->get(
sprintf('/api/servers/%s/files/contents', $this->server->uuid),
['file' => $path]
);
} catch (ClientException|TransferException $exception) {
throw new DaemonConnectionException($exception);
}
$length = $response->header('Content-Length'); $length = $response->header('Content-Length');
if ($notLargerThan && $length > $notLargerThan) { if ($notLargerThan && $length > $notLargerThan) {
@ -53,215 +41,145 @@ class DaemonFileRepository extends DaemonRepository
* Save new contents to a given file. This works for both creating and updating * Save new contents to a given file. This works for both creating and updating
* a file. * a file.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function putContent(string $path, string $content): Response public function putContent(string $path, string $content): Response
{ {
Assert::isInstanceOf($this->server, Server::class); return $this->getHttpClient()
->withQueryParameters(['file' => $path])
try { ->withBody($content)
return $this->getHttpClient() ->post("/api/servers/{$this->server->uuid}/files/write");
->withQueryParameters(['file' => $path])
->withBody($content)
->post(sprintf('/api/servers/%s/files/write', $this->server->uuid));
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
} }
/** /**
* Return a directory listing for a given path. * Return a directory listing for a given path.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function getDirectory(string $path): array public function getDirectory(string $path): array
{ {
Assert::isInstanceOf($this->server, Server::class); return $this->getHttpClient()->get("/api/servers/{$this->server->uuid}/files/list-directory",
['directory' => $path]
try { )->json();
$response = $this->getHttpClient()->get(
sprintf('/api/servers/%s/files/list-directory', $this->server->uuid),
['directory' => $path]
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
return $response->json();
} }
/** /**
* Creates a new directory for the server in the given $path. * Creates a new directory for the server in the given $path.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function createDirectory(string $name, string $path): Response public function createDirectory(string $name, string $path): Response
{ {
Assert::isInstanceOf($this->server, Server::class); return $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/files/create-directory",
[
try { 'name' => $name,
return $this->getHttpClient()->post( 'path' => $path,
sprintf('/api/servers/%s/files/create-directory', $this->server->uuid), ]
[ );
'name' => $name,
'path' => $path,
]
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
} }
/** /**
* Renames or moves a file on the remote machine. * Renames or moves a file on the remote machine.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function renameFiles(?string $root, array $files): Response public function renameFiles(?string $root, array $files): Response
{ {
Assert::isInstanceOf($this->server, Server::class); return $this->getHttpClient()->put("/api/servers/{$this->server->uuid}/files/rename",
[
try { 'root' => $root ?? '/',
return $this->getHttpClient()->put( 'files' => $files,
sprintf('/api/servers/%s/files/rename', $this->server->uuid), ]
[ );
'root' => $root ?? '/',
'files' => $files,
]
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
} }
/** /**
* Copy a given file and give it a unique name. * Copy a given file and give it a unique name.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function copyFile(string $location): Response public function copyFile(string $location): Response
{ {
Assert::isInstanceOf($this->server, Server::class); return $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/files/copy",
['location' => $location]
try { );
return $this->getHttpClient()->post(
sprintf('/api/servers/%s/files/copy', $this->server->uuid),
[
'location' => $location,
]
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
} }
/** /**
* Delete a file or folder for the server. * Delete a file or folder for the server.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function deleteFiles(?string $root, array $files): Response public function deleteFiles(?string $root, array $files): Response
{ {
Assert::isInstanceOf($this->server, Server::class); return $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/files/delete",
[
try { 'root' => $root ?? '/',
return $this->getHttpClient()->post( 'files' => $files,
sprintf('/api/servers/%s/files/delete', $this->server->uuid), ]
[ );
'root' => $root ?? '/',
'files' => $files,
]
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
} }
/** /**
* Compress the given files or folders in the given root. * Compress the given files or folders in the given root.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function compressFiles(?string $root, array $files): array public function compressFiles(?string $root, array $files): array
{ {
Assert::isInstanceOf($this->server, Server::class); return $this->getHttpClient()
// Wait for up to 15 minutes for the archive to be completed when calling this endpoint
try { // since it will likely take quite awhile for large directories.
$response = $this->getHttpClient() ->timeout(60 * 15)
// Wait for up to 15 minutes for the archive to be completed when calling this endpoint ->post("/api/servers/{$this->server->uuid}/files/compress",
// since it will likely take quite awhile for large directories. [
->timeout(60 * 15) 'root' => $root ?? '/',
->post( 'files' => $files,
sprintf('/api/servers/%s/files/compress', $this->server->uuid), ]
[ )->json();
'root' => $root ?? '/',
'files' => $files,
]
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
return $response->json();
} }
/** /**
* Decompresses a given archive file. * Decompresses a given archive file.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function decompressFile(?string $root, string $file): Response public function decompressFile(?string $root, string $file): Response
{ {
Assert::isInstanceOf($this->server, Server::class); return $this->getHttpClient()
// Wait for up to 15 minutes for the archive to be completed when calling this endpoint
try { // since it will likely take quite awhile for large directories.
return $this->getHttpClient() ->timeout(60 * 15)
// Wait for up to 15 minutes for the decompress to be completed when calling this endpoint ->post("/api/servers/{$this->server->uuid}/files/decompress",
// since it will likely take quite awhile for large directories. [
->timeout((int) CarbonInterval::minutes(15)->totalSeconds) 'root' => $root ?? '/',
->post( 'file' => $file,
sprintf('/api/servers/%s/files/decompress', $this->server->uuid), ]
[ );
'root' => $root ?? '/',
'file' => $file,
]
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
} }
/** /**
* Chmods the given files. * Chmods the given files.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function chmodFiles(?string $root, array $files): Response public function chmodFiles(?string $root, array $files): Response
{ {
Assert::isInstanceOf($this->server, Server::class); return $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/files/chmod",
[
try { 'root' => $root ?? '/',
return $this->getHttpClient()->post( 'files' => $files,
sprintf('/api/servers/%s/files/chmod', $this->server->uuid), ]
[ );
'root' => $root ?? '/',
'files' => $files,
]
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
} }
/** /**
* Pulls a file from the given URL and saves it to the disk. * Pulls a file from the given URL and saves it to the disk.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function pull(string $url, ?string $directory, array $params = []): Response public function pull(string $url, ?string $directory, array $params = []): Response
{ {
Assert::isInstanceOf($this->server, Server::class);
$attributes = [ $attributes = [
'url' => $url, 'url' => $url,
'root' => $directory ?? '/', 'root' => $directory ?? '/',
@ -270,39 +188,25 @@ class DaemonFileRepository extends DaemonRepository
'foreground' => $params['foreground'] ?? null, 'foreground' => $params['foreground'] ?? null,
]; ];
try { return $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/files/pull", array_filter($attributes, fn ($value) => !is_null($value)));
return $this->getHttpClient()->post(
sprintf('/api/servers/%s/files/pull', $this->server->uuid),
array_filter($attributes, fn ($value) => !is_null($value))
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
} }
/** /**
* Searches all files in the directory (and its subdirectories) for the given search term. * Searches all files in the directory (and its subdirectories) for the given search term.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function search(string $searchTerm, ?string $directory): array public function search(string $searchTerm, ?string $directory): array
{ {
Assert::isInstanceOf($this->server, Server::class); return $this->getHttpClient()
// Wait for up to 2 minutes for the search to be completed when calling this endpoint
try { // since it will likely take quite awhile for large directories.
$response = $this->getHttpClient() ->timeout(60 * 2)
->timeout(120) ->get("/api/servers/{$this->server->uuid}/files/search",
->get( [
sprintf('/api/servers/%s/files/search', $this->server->uuid), 'pattern' => $searchTerm,
[ 'directory' => $directory ?? '/',
'pattern' => $searchTerm, ]
'directory' => $directory ?? '/', )->json();
]
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
return $response->json();
} }
} }

View File

@ -2,30 +2,20 @@
namespace App\Repositories\Daemon; namespace App\Repositories\Daemon;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\Response; use Illuminate\Http\Client\Response;
use Webmozart\Assert\Assert;
use App\Models\Server;
use GuzzleHttp\Exception\TransferException;
use App\Exceptions\Http\Connection\DaemonConnectionException;
class DaemonPowerRepository extends DaemonRepository class DaemonPowerRepository extends DaemonRepository
{ {
/** /**
* Sends a power action to the server instance. * Sends a power action to the server instance.
* *
* @throws \App\Exceptions\Http\Connection\DaemonConnectionException * @throws ConnectionException
*/ */
public function send(string $action): Response public function send(string $action): Response
{ {
Assert::isInstanceOf($this->server, Server::class); return $this->getHttpClient()->post("/api/servers/{$this->server->uuid}/power",
['action' => $action],
try { );
return $this->getHttpClient()->post(
sprintf('/api/servers/%s/power', $this->server->uuid),
['action' => $action],
);
} catch (TransferException $exception) {
throw new DaemonConnectionException($exception);
}
} }
} }

View File

@ -17,9 +17,7 @@ class DaemonServerRepository extends DaemonRepository
public function getDetails(): array public function getDetails(): array
{ {
try { try {
return $this->getHttpClient()->get( return $this->getHttpClient()->get("/api/servers/{$this->server->uuid}")->throw()->json();
sprintf('/api/servers/%s', $this->server->uuid)
)->throw()->json();
} catch (RequestException $exception) { } catch (RequestException $exception) {
$cfId = $exception->response->header('Cf-Ray'); $cfId = $exception->response->header('Cf-Ray');
$cfCache = $exception->response->header('Cf-Cache-Status'); $cfCache = $exception->response->header('Cf-Cache-Status');

View File

@ -11,7 +11,7 @@ use Illuminate\Database\ConnectionInterface;
use App\Extensions\Backups\BackupManager; use App\Extensions\Backups\BackupManager;
use App\Repositories\Daemon\DaemonBackupRepository; use App\Repositories\Daemon\DaemonBackupRepository;
use App\Exceptions\Service\Backup\BackupLockedException; use App\Exceptions\Service\Backup\BackupLockedException;
use App\Exceptions\Http\Connection\DaemonConnectionException; use Illuminate\Http\Client\ConnectionException;
class DeleteBackupService class DeleteBackupService
{ {
@ -48,8 +48,9 @@ class DeleteBackupService
$this->connection->transaction(function () use ($backup) { $this->connection->transaction(function () use ($backup) {
try { try {
$this->daemonBackupRepository->setServer($backup->server)->delete($backup); $this->daemonBackupRepository->setServer($backup->server)->delete($backup);
} catch (DaemonConnectionException $exception) { } catch (ConnectionException $exception) {
$previous = $exception->getPrevious(); $previous = $exception->getPrevious();
// Don't fail the request if the Daemon responds with a 404, just assume the backup // Don't fail the request if the Daemon responds with a 404, just assume the backup
// doesn't actually exist and remove its reference from the Panel as well. // doesn't actually exist and remove its reference from the Panel as well.
if (!$previous instanceof ClientException || $previous->getResponse()->getStatusCode() !== Response::HTTP_NOT_FOUND) { if (!$previous instanceof ClientException || $previous->getResponse()->getStatusCode() !== Response::HTTP_NOT_FOUND) {

View File

@ -2,9 +2,9 @@
namespace App\Services\Files; namespace App\Services\Files;
use App\Exceptions\Http\Connection\DaemonConnectionException;
use App\Models\Server; use App\Models\Server;
use App\Repositories\Daemon\DaemonFileRepository; use App\Repositories\Daemon\DaemonFileRepository;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Str; use Illuminate\Support\Str;
class DeleteFilesService class DeleteFilesService
@ -19,7 +19,7 @@ class DeleteFilesService
/** /**
* Deletes the given files. * Deletes the given files.
* *
* @throws DaemonConnectionException * @throws ConnectionException
*/ */
public function handle(Server $server, array $files): void public function handle(Server $server, array $files): void
{ {

View File

@ -6,8 +6,8 @@ use Illuminate\Support\Str;
use App\Models\Node; use App\Models\Node;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use App\Repositories\Daemon\DaemonConfigurationRepository; use App\Repositories\Daemon\DaemonConfigurationRepository;
use App\Exceptions\Http\Connection\DaemonConnectionException;
use App\Exceptions\Service\Node\ConfigurationNotPersistedException; use App\Exceptions\Service\Node\ConfigurationNotPersistedException;
use Illuminate\Http\Client\ConnectionException;
class NodeUpdateService class NodeUpdateService
{ {
@ -42,7 +42,7 @@ class NodeUpdateService
$node->fqdn = $updated->fqdn; $node->fqdn = $updated->fqdn;
$this->configurationRepository->setNode($node)->update($updated); $this->configurationRepository->setNode($node)->update($updated);
} catch (DaemonConnectionException $exception) { } catch (ConnectionException $exception) {
logger()->warning($exception, ['node_id' => $node->id]); logger()->warning($exception, ['node_id' => $node->id]);
// Never actually throw these exceptions up the stack. If we were able to change the settings // Never actually throw these exceptions up the stack. If we were able to change the settings

View File

@ -9,7 +9,6 @@ use App\Jobs\Schedule\RunTaskJob;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use App\Exceptions\DisplayException; use App\Exceptions\DisplayException;
use App\Repositories\Daemon\DaemonServerRepository; use App\Repositories\Daemon\DaemonServerRepository;
use App\Exceptions\Http\Connection\DaemonConnectionException;
class ProcessScheduleService class ProcessScheduleService
{ {
@ -53,13 +52,7 @@ class ProcessScheduleService
return; return;
} }
} catch (\Exception $exception) { } catch (Exception) {
if (!$exception instanceof DaemonConnectionException) {
// If we encountered some exception during this process that wasn't just an
// issue connecting to daemon run the failed sequence for a job. Otherwise we
// can just quietly mark the task as completed without actually running anything.
$job->failed($exception);
}
$job->failed(); $job->failed();
return; return;
@ -73,8 +66,8 @@ class ProcessScheduleService
// so we need to manually trigger it and then continue with the exception throw. // so we need to manually trigger it and then continue with the exception throw.
try { try {
$this->dispatcher->dispatchNow($job); $this->dispatcher->dispatchNow($job);
} catch (\Exception $exception) { } catch (Exception $exception) {
$job->failed($exception); $job->failed();
throw $exception; throw $exception;
} }

View File

@ -8,7 +8,7 @@ use App\Models\Allocation;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use App\Exceptions\DisplayException; use App\Exceptions\DisplayException;
use App\Repositories\Daemon\DaemonServerRepository; use App\Repositories\Daemon\DaemonServerRepository;
use App\Exceptions\Http\Connection\DaemonConnectionException; use Illuminate\Http\Client\ConnectionException;
class BuildModificationService class BuildModificationService
{ {
@ -64,7 +64,7 @@ class BuildModificationService
if (!empty($updateData['build'])) { if (!empty($updateData['build'])) {
try { try {
$this->daemonServerRepository->setServer($server)->sync(); $this->daemonServerRepository->setServer($server)->sync();
} catch (DaemonConnectionException $exception) { } catch (ConnectionException $exception) {
logger()->warning($exception, ['server_id' => $server->id]); logger()->warning($exception, ['server_id' => $server->id]);
} }
} }

View File

@ -7,7 +7,6 @@ use App\Models\Server;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use App\Traits\Services\ReturnsUpdatedModels; use App\Traits\Services\ReturnsUpdatedModels;
use App\Repositories\Daemon\DaemonServerRepository; use App\Repositories\Daemon\DaemonServerRepository;
use App\Exceptions\Http\Connection\DaemonConnectionException;
use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\ConnectionException;
class DetailsModificationService class DetailsModificationService
@ -42,7 +41,7 @@ class DetailsModificationService
if ($server->owner_id !== $owner) { if ($server->owner_id !== $owner) {
try { try {
$this->serverRepository->setServer($server)->revokeUserJTI($owner); $this->serverRepository->setServer($server)->revokeUserJTI($owner);
} catch (ConnectionException|DaemonConnectionException) { } catch (ConnectionException) {
// Do nothing. A failure here is not ideal, but it is likely to be caused by daemon // Do nothing. A failure here is not ideal, but it is likely to be caused by daemon
// being offline, or in an entirely broken state. Remember, these tokens reset every // being offline, or in an entirely broken state. Remember, these tokens reset every
// few minutes by default, we're just trying to help it along a little quicker. // few minutes by default, we're just trying to help it along a little quicker.

View File

@ -8,7 +8,7 @@ use App\Models\Server;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use App\Repositories\Daemon\DaemonServerRepository; use App\Repositories\Daemon\DaemonServerRepository;
use App\Services\Databases\DatabaseManagementService; use App\Services\Databases\DatabaseManagementService;
use App\Exceptions\Http\Connection\DaemonConnectionException; use Illuminate\Http\Client\ConnectionException;
class ServerDeletionService class ServerDeletionService
{ {
@ -43,12 +43,12 @@ class ServerDeletionService
{ {
try { try {
$this->daemonServerRepository->setServer($server)->delete(); $this->daemonServerRepository->setServer($server)->delete();
} catch (DaemonConnectionException $exception) { } catch (ConnectionException $exception) {
// If there is an error not caused a 404 error and this isn't a forced delete, // If there is an error not caused a 404 error and this isn't a forced delete,
// go ahead and bail out. We specifically ignore a 404 since that can be assumed // go ahead and bail out. We specifically ignore a 404 since that can be assumed
// to be a safe error, meaning the server doesn't exist at all on daemon so there // to be a safe error, meaning the server doesn't exist at all on daemon so there
// is no reason we need to bail out from that. // is no reason we need to bail out from that.
if (!$this->force && $exception->getStatusCode() !== Response::HTTP_NOT_FOUND) { if (!$this->force && $exception->getCode() !== Response::HTTP_NOT_FOUND) {
throw $exception; throw $exception;
} }
@ -61,7 +61,7 @@ class ServerDeletionService
foreach ($server->databases as $database) { foreach ($server->databases as $database) {
try { try {
$this->databaseManagementService->delete($database); $this->databaseManagementService->delete($database);
} catch (\Exception $exception) { } catch (Exception $exception) {
if (!$this->force) { if (!$this->force) {
throw $exception; throw $exception;
} }

View File

@ -7,7 +7,6 @@ use App\Enums\SuspendAction;
use Filament\Notifications\Notification; use Filament\Notifications\Notification;
use App\Models\Server; use App\Models\Server;
use App\Repositories\Daemon\DaemonServerRepository; use App\Repositories\Daemon\DaemonServerRepository;
use Doctrine\DBAL\Exception\ConnectionException;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
class SuspensionService class SuspensionService
@ -47,11 +46,7 @@ class SuspensionService
'status' => $isSuspending ? ServerState::Suspended : null, 'status' => $isSuspending ? ServerState::Suspended : null,
]); ]);
try { // Tell daemon to re-sync the server state.
// Tell daemon to re-sync the server state. $this->daemonServerRepository->setServer($server)->sync();
$this->daemonServerRepository->setServer($server)->sync();
} catch (ConnectionException $exception) {
throw $exception;
}
} }
} }

View File

@ -2,14 +2,12 @@
namespace App\Services\Servers; namespace App\Services\Servers;
use App\Exceptions\Http\Connection\DaemonConnectionException;
use App\Models\Allocation; use App\Models\Allocation;
use App\Models\Node; use App\Models\Node;
use App\Models\Server; use App\Models\Server;
use App\Models\ServerTransfer; use App\Models\ServerTransfer;
use App\Services\Nodes\NodeJWTService; use App\Services\Nodes\NodeJWTService;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use GuzzleHttp\Exception\TransferException;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
use Lcobucci\JWT\Token\Plain; use Lcobucci\JWT\Token\Plain;
@ -26,21 +24,17 @@ class TransferServerService
private function notify(Server $server, Plain $token): void private function notify(Server $server, Plain $token): void
{ {
try { Http::daemon($server->node)->post('/api/transfer', [
Http::daemon($server->node)->post('/api/transfer', [ 'json' => [
'json' => [ 'server_id' => $server->uuid,
'server_id' => $server->uuid, 'url' => $server->node->getConnectionAddress() . "/api/servers/$server->uuid/archive",
'url' => $server->node->getConnectionAddress() . "/api/servers/$server->uuid/archive", 'token' => 'Bearer ' . $token->toString(),
'token' => 'Bearer ' . $token->toString(), 'server' => [
'server' => [ 'uuid' => $server->uuid,
'uuid' => $server->uuid, 'start_on_completion' => false,
'start_on_completion' => false,
],
], ],
])->toPsrResponse(); ],
} catch (TransferException $exception) { ])->toPsrResponse();
throw new DaemonConnectionException($exception);
}
} }
/** /**

View File

@ -3,7 +3,6 @@
namespace App\Services\Subusers; namespace App\Services\Subusers;
use App\Events\Server\SubUserRemoved; use App\Events\Server\SubUserRemoved;
use App\Exceptions\Http\Connection\DaemonConnectionException;
use App\Facades\Activity; use App\Facades\Activity;
use App\Models\Server; use App\Models\Server;
use App\Models\Subuser; use App\Models\Subuser;
@ -30,7 +29,7 @@ class SubuserDeletionService
try { try {
$this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id); $this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id);
} catch (ConnectionException|DaemonConnectionException $exception) { } catch (ConnectionException $exception) {
// Don't block this request if we can't connect to the daemon instance. // Don't block this request if we can't connect to the daemon instance.
logger()->warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]); logger()->warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]);

View File

@ -2,7 +2,6 @@
namespace App\Services\Subusers; namespace App\Services\Subusers;
use App\Exceptions\Http\Connection\DaemonConnectionException;
use App\Facades\Activity; use App\Facades\Activity;
use App\Models\Server; use App\Models\Server;
use App\Models\Subuser; use App\Models\Subuser;
@ -39,7 +38,7 @@ class SubuserUpdateService
try { try {
$this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id); $this->serverRepository->setServer($server)->revokeUserJTI($subuser->user_id);
} catch (ConnectionException|DaemonConnectionException $exception) { } catch (ConnectionException $exception) {
// Don't block this request if we can't connect to the daemon instance. Chances are it is // Don't block this request if we can't connect to the daemon instance. Chances are it is
// offline and the token will be invalid once daemon boots back. // offline and the token will be invalid once daemon boots back.
logger()->warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]); logger()->warning($exception, ['user_id' => $subuser->user_id, 'server_id' => $server->id]);

View File

@ -10,8 +10,8 @@ use App\Models\Server;
use App\Models\Permission; use App\Models\Permission;
use GuzzleHttp\Exception\BadResponseException; use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Psr7\Response as GuzzleResponse; use GuzzleHttp\Psr7\Response as GuzzleResponse;
use App\Exceptions\Http\Connection\DaemonConnectionException;
use App\Tests\Integration\Api\Client\ClientApiIntegrationTestCase; use App\Tests\Integration\Api\Client\ClientApiIntegrationTestCase;
use Illuminate\Http\Client\ConnectionException;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
class CommandControllerTest extends ClientApiIntegrationTestCase class CommandControllerTest extends ClientApiIntegrationTestCase
@ -77,11 +77,7 @@ class CommandControllerTest extends ClientApiIntegrationTestCase
[$user, $server] = $this->generateTestAccount(); [$user, $server] = $this->generateTestAccount();
$server = \Mockery::mock($server)->makePartial(); $server = \Mockery::mock($server)->makePartial();
$server->expects('send')->andThrows( $server->expects('send')->andThrows(new ConnectionException(previous: new BadResponseException('', new Request('GET', 'test'), new GuzzleResponse(Response::HTTP_BAD_GATEWAY))));
new DaemonConnectionException(
new BadResponseException('', new Request('GET', 'test'), new GuzzleResponse(Response::HTTP_BAD_GATEWAY))
)
);
$this->instance(Server::class, $server); $this->instance(Server::class, $server);

View File

@ -5,17 +5,14 @@ namespace App\Tests\Integration\Jobs\Schedule;
use App\Enums\ServerState; use App\Enums\ServerState;
use Carbon\Carbon; use Carbon\Carbon;
use Carbon\CarbonImmutable; use Carbon\CarbonImmutable;
use GuzzleHttp\Psr7\Request;
use App\Models\Task; use App\Models\Task;
use GuzzleHttp\Psr7\Response;
use App\Models\Server; use App\Models\Server;
use App\Models\Schedule; use App\Models\Schedule;
use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\Bus;
use App\Jobs\Schedule\RunTaskJob; use App\Jobs\Schedule\RunTaskJob;
use GuzzleHttp\Exception\BadResponseException;
use App\Tests\Integration\IntegrationTestCase; use App\Tests\Integration\IntegrationTestCase;
use App\Repositories\Daemon\DaemonPowerRepository; use App\Repositories\Daemon\DaemonPowerRepository;
use App\Exceptions\Http\Connection\DaemonConnectionException; use Illuminate\Http\Client\ConnectionException;
class RunTaskJobTest extends IntegrationTestCase class RunTaskJobTest extends IntegrationTestCase
{ {
@ -126,12 +123,10 @@ class RunTaskJobTest extends IntegrationTestCase
$mock = \Mockery::mock(DaemonPowerRepository::class); $mock = \Mockery::mock(DaemonPowerRepository::class);
$this->instance(DaemonPowerRepository::class, $mock); $this->instance(DaemonPowerRepository::class, $mock);
$mock->expects('setServer->send')->andThrow( $mock->expects('setServer->send')->andThrow(new ConnectionException());
new DaemonConnectionException(new BadResponseException('Bad request', new Request('GET', '/test'), new Response()))
);
if (!$continueOnFailure) { if (!$continueOnFailure) {
$this->expectException(DaemonConnectionException::class); $this->expectException(ConnectionException::class);
} }
Bus::dispatchSync(new RunTaskJob($task)); Bus::dispatchSync(new RunTaskJob($task));

View File

@ -12,7 +12,7 @@ use App\Services\Backups\DeleteBackupService;
use App\Tests\Integration\IntegrationTestCase; use App\Tests\Integration\IntegrationTestCase;
use App\Repositories\Daemon\DaemonBackupRepository; use App\Repositories\Daemon\DaemonBackupRepository;
use App\Exceptions\Service\Backup\BackupLockedException; use App\Exceptions\Service\Backup\BackupLockedException;
use App\Exceptions\Http\Connection\DaemonConnectionException; use Illuminate\Http\Client\ConnectionException;
class DeleteBackupServiceTest extends IntegrationTestCase class DeleteBackupServiceTest extends IntegrationTestCase
{ {
@ -54,11 +54,7 @@ class DeleteBackupServiceTest extends IntegrationTestCase
$backup = Backup::factory()->create(['server_id' => $server->id]); $backup = Backup::factory()->create(['server_id' => $server->id]);
$mock = $this->mock(DaemonBackupRepository::class); $mock = $this->mock(DaemonBackupRepository::class);
$mock->expects('setServer->delete')->with($backup)->andThrow( $mock->expects('setServer->delete')->with($backup)->andThrow(new ConnectionException(previous: new ClientException('', new Request('DELETE', '/'), new Response(404))));
new DaemonConnectionException(
new ClientException('', new Request('DELETE', '/'), new Response(404))
)
);
$this->app->make(DeleteBackupService::class)->handle($backup); $this->app->make(DeleteBackupService::class)->handle($backup);
@ -73,13 +69,9 @@ class DeleteBackupServiceTest extends IntegrationTestCase
$backup = Backup::factory()->create(['server_id' => $server->id]); $backup = Backup::factory()->create(['server_id' => $server->id]);
$mock = $this->mock(DaemonBackupRepository::class); $mock = $this->mock(DaemonBackupRepository::class);
$mock->expects('setServer->delete')->with($backup)->andThrow( $mock->expects('setServer->delete')->with($backup)->andThrow(new ConnectionException(previous: new ClientException('', new Request('DELETE', '/'), new Response(500))));
new DaemonConnectionException(
new ClientException('', new Request('DELETE', '/'), new Response(500))
)
);
$this->expectException(DaemonConnectionException::class); $this->expectException(ConnectionException::class);
$this->app->make(DeleteBackupService::class)->handle($backup); $this->app->make(DeleteBackupService::class)->handle($backup);

View File

@ -3,16 +3,13 @@
namespace App\Tests\Integration\Services\Servers; namespace App\Tests\Integration\Services\Servers;
use Mockery\MockInterface; use Mockery\MockInterface;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use App\Models\Server; use App\Models\Server;
use App\Models\Allocation; use App\Models\Allocation;
use GuzzleHttp\Exception\RequestException;
use App\Exceptions\DisplayException; use App\Exceptions\DisplayException;
use App\Tests\Integration\IntegrationTestCase; use App\Tests\Integration\IntegrationTestCase;
use App\Repositories\Daemon\DaemonServerRepository; use App\Repositories\Daemon\DaemonServerRepository;
use App\Services\Servers\BuildModificationService; use App\Services\Servers\BuildModificationService;
use App\Exceptions\Http\Connection\DaemonConnectionException; use Illuminate\Http\Client\ConnectionException;
class BuildModificationServiceTest extends IntegrationTestCase class BuildModificationServiceTest extends IntegrationTestCase
{ {
@ -149,11 +146,7 @@ class BuildModificationServiceTest extends IntegrationTestCase
$server = $this->createServerModel(); $server = $this->createServerModel();
$this->daemonServerRepository->expects('setServer->sync')->andThrows( $this->daemonServerRepository->expects('setServer->sync')->andThrows(new ConnectionException());
new DaemonConnectionException(
new RequestException('Bad request', new Request('GET', '/test'), new Response())
)
);
$response = $this->getService()->handle($server, ['memory' => 256, 'disk' => 10240]); $response = $this->getService()->handle($server, ['memory' => 256, 'disk' => 10240]);

View File

@ -3,16 +3,13 @@
namespace App\Tests\Integration\Services\Servers; namespace App\Tests\Integration\Services\Servers;
use Mockery\MockInterface; use Mockery\MockInterface;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use App\Models\Database; use App\Models\Database;
use App\Models\DatabaseHost; use App\Models\DatabaseHost;
use GuzzleHttp\Exception\BadResponseException;
use App\Tests\Integration\IntegrationTestCase; use App\Tests\Integration\IntegrationTestCase;
use App\Services\Servers\ServerDeletionService; use App\Services\Servers\ServerDeletionService;
use App\Repositories\Daemon\DaemonServerRepository; use App\Repositories\Daemon\DaemonServerRepository;
use App\Services\Databases\DatabaseManagementService; use App\Services\Databases\DatabaseManagementService;
use App\Exceptions\Http\Connection\DaemonConnectionException; use Illuminate\Http\Client\ConnectionException;
class ServerDeletionServiceTest extends IntegrationTestCase class ServerDeletionServiceTest extends IntegrationTestCase
{ {
@ -59,11 +56,9 @@ class ServerDeletionServiceTest extends IntegrationTestCase
{ {
$server = $this->createServerModel(); $server = $this->createServerModel();
$this->expectException(DaemonConnectionException::class); $this->expectException(ConnectionException::class);
$this->daemonServerRepository->expects('setServer->delete')->withNoArgs()->andThrows( $this->daemonServerRepository->expects('setServer->delete')->withNoArgs()->andThrows(new ConnectionException());
new DaemonConnectionException(new BadResponseException('Bad request', new Request('GET', '/test'), new Response()))
);
$this->getService()->handle($server); $this->getService()->handle($server);
@ -77,9 +72,7 @@ class ServerDeletionServiceTest extends IntegrationTestCase
{ {
$server = $this->createServerModel(); $server = $this->createServerModel();
$this->daemonServerRepository->expects('setServer->delete')->withNoArgs()->andThrows( $this->daemonServerRepository->expects('setServer->delete')->withNoArgs()->andThrows(new ConnectionException(code: 404));
new DaemonConnectionException(new BadResponseException('Bad request', new Request('GET', '/test'), new Response(404)))
);
$this->getService()->handle($server); $this->getService()->handle($server);
@ -94,9 +87,7 @@ class ServerDeletionServiceTest extends IntegrationTestCase
{ {
$server = $this->createServerModel(); $server = $this->createServerModel();
$this->daemonServerRepository->expects('setServer->delete')->withNoArgs()->andThrows( $this->daemonServerRepository->expects('setServer->delete')->withNoArgs()->andThrows(new ConnectionException());
new DaemonConnectionException(new BadResponseException('Bad request', new Request('GET', '/test'), new Response(500)))
);
$this->getService()->withForce()->handle($server); $this->getService()->withForce()->handle($server);