backend: support is_successful state for backups rather than deleting it when failing

This allows the UI to correctly show failed backups to the user and require them to manually delete those backups, rather than them mysteriously disappearing.

We can also hook into this later to send a notification to the user when the backup fails.
This commit is contained in:
Dane Everitt 2020-08-20 21:07:53 -07:00
parent 6066fa40f4
commit e3178ba6f0
No known key found for this signature in database
GPG Key ID: EEA66103B3D71F53
7 changed files with 58 additions and 25 deletions

View File

@ -17,7 +17,7 @@ class PruneOrphanedBackupsCommand extends Command
/**
* @var string
*/
protected $description = 'Removes all backups that have existed for more than "n" minutes which are not marked as completed.';
protected $description = 'Marks all backups that have not completed in the last "n" minutes as being failed.';
/**
* @param \Pterodactyl\Repositories\Eloquent\BackupRepository $repository
@ -25,7 +25,7 @@ class PruneOrphanedBackupsCommand extends Command
public function handle(BackupRepository $repository)
{
$since = $this->option('since-minutes');
if (!is_digit($since)) {
if (! is_digit($since)) {
throw new InvalidArgumentException('The --since-minutes option must be a valid numeric digit.');
}
@ -34,14 +34,18 @@ class PruneOrphanedBackupsCommand extends Command
->whereDate('created_at', '<=', CarbonImmutable::now()->subMinutes($since));
$count = $query->count();
if (!$count) {
$this->info('There are no orphaned backups to be removed.');
if (! $count) {
$this->info('There are no orphaned backups to be marked as failed.');
return;
}
$this->warn("Deleting {$count} backups that have not been marked as completed in the last {$since} minutes.");
$this->warn("Marking {$count} backups that have not been marked as completed in the last {$since} minutes as failed.");
$query->delete();
$query->update([
'is_successful' => false,
'completed_at' => CarbonImmutable::now(),
'updated_at' => CarbonImmutable::now(),
]);
}
}

View File

@ -3,6 +3,7 @@
namespace Pterodactyl\Http\Controllers\Api\Remote\Backups;
use Carbon\Carbon;
use Carbon\CarbonImmutable;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Repositories\Eloquent\BackupRepository;
@ -31,25 +32,16 @@ class BackupStatusController extends Controller
* @param \Pterodactyl\Http\Requests\Api\Remote\ReportBackupCompleteRequest $request
* @param string $backup
* @return \Illuminate\Http\JsonResponse
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function __invoke(ReportBackupCompleteRequest $request, string $backup)
{
/** @var \Pterodactyl\Models\Backup $backup */
$backup = $this->repository->findFirstWhere([['uuid', '=', $backup]]);
$this->repository->updateWhere([['uuid', '=', $backup]], [
'is_successful' => $request->input('successful') ? true : false,
'sha256_hash' => $request->input('checksum'),
'bytes' => $request->input('size'),
'completed_at' => CarbonImmutable::now(),
]);
if ($request->input('successful')) {
$this->repository->update($backup->id, [
'sha256_hash' => $request->input('checksum'),
'bytes' => $request->input('size'),
'completed_at' => Carbon::now(),
], true, true);
} else {
$this->repository->delete($backup->id);
}
return JsonResponse::create([], JsonResponse::HTTP_NO_CONTENT);
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
}
}

View File

@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property int $id
* @property int $server_id
* @property int $uuid
* @property bool $is_successful
* @property string $name
* @property string[] $ignored_files
* @property string $disk
@ -44,6 +45,7 @@ class Backup extends Model
*/
protected $casts = [
'id' => 'int',
'is_successful' => 'bool',
'bytes' => 'int',
'ignored_files' => 'array',
];
@ -59,6 +61,7 @@ class Backup extends Model
* @var array
*/
protected $attributes = [
'is_successful' => true,
'sha256_hash' => null,
'bytes' => 0,
];
@ -69,6 +72,7 @@ class Backup extends Model
public static $validationRules = [
'server_id' => 'bail|required|numeric|exists:servers,id',
'uuid' => 'required|uuid',
'is_successful' => 'boolean',
'name' => 'required|string',
'ignored_files' => 'array',
'disk' => 'required|string',

View File

@ -27,6 +27,7 @@ class BackupRepository extends EloquentRepository
return $this->getBuilder()
->withTrashed()
->where('server_id', $server)
->where('is_successful', true)
->where('created_at', '>=', Carbon::now()->subMinutes($minutes)->toDateTimeString())
->get()
->toBase();

View File

@ -2,7 +2,6 @@
namespace Pterodactyl\Services\Backups;
use Carbon\Carbon;
use Ramsey\Uuid\Uuid;
use Carbon\CarbonImmutable;
use Webmozart\Assert\Assert;
@ -101,14 +100,14 @@ class InitiateBackupService
public function handle(Server $server, string $name = null): Backup
{
// Do not allow the user to continue if this server is already at its limit.
if (! $server->backup_limit || $server->backups()->count() >= $server->backup_limit) {
if (! $server->backup_limit || $server->backups()->where('is_successful', true)->count() >= $server->backup_limit) {
throw new TooManyBackupsException($server->backup_limit);
}
$previous = $this->repository->getBackupsGeneratedDuringTimespan($server->id, 10);
if ($previous->count() >= 2) {
throw new TooManyRequestsHttpException(
Carbon::now()->diffInSeconds($previous->last()->created_at->addMinutes(10)),
CarbonImmutable::now()->diffInSeconds($previous->last()->created_at->addMinutes(10)),
'Only two backups may be generated within a 10 minute span of time.'
);
}

View File

@ -22,6 +22,7 @@ class BackupTransformer extends BaseClientTransformer
{
return [
'uuid' => $backup->uuid,
'is_successful' => $backup->is_successful,
'name' => $backup->name,
'ignored_files' => $backup->ignored_files,
'sha256_hash' => $backup->sha256_hash,

View File

@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddBackupStateColumnToBackups extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('backups', function (Blueprint $table) {
$table->boolean('is_successful')->after('uuid')->default(true);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('backups', function (Blueprint $table) {
$table->dropColumn('is_successful');
});
}
}