Skip to content

Commit 7f788aa

Browse files
committed
feat: #229 restore data from backups
1 parent a51160e commit 7f788aa

26 files changed

+621
-171
lines changed

Diff for: _ide_helper_actions.php

+15
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,21 @@ class CreateService {}
6969
*/
7070
class StartDeployment {}
7171

72+
namespace App\Actions\Workers;
73+
74+
/**
75+
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Service $service, \App\Models\DeploymentData\Process $process, \App\Models\DeploymentData\Worker $worker, \App\Models\Backup $backup = null)
76+
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Service $service, \App\Models\DeploymentData\Process $process, \App\Models\DeploymentData\Worker $worker, \App\Models\Backup $backup = null)
77+
* @method static \Illuminate\Foundation\Bus\PendingDispatch dispatch(\App\Models\Service $service, \App\Models\DeploymentData\Process $process, \App\Models\DeploymentData\Worker $worker, \App\Models\Backup $backup = null)
78+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchIf(bool $boolean, \App\Models\Service $service, \App\Models\DeploymentData\Process $process, \App\Models\DeploymentData\Worker $worker, \App\Models\Backup $backup = null)
79+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchUnless(bool $boolean, \App\Models\Service $service, \App\Models\DeploymentData\Process $process, \App\Models\DeploymentData\Worker $worker, \App\Models\Backup $backup = null)
80+
* @method static dispatchSync(\App\Models\Service $service, \App\Models\DeploymentData\Process $process, \App\Models\DeploymentData\Worker $worker, \App\Models\Backup $backup = null)
81+
* @method static dispatchNow(\App\Models\Service $service, \App\Models\DeploymentData\Process $process, \App\Models\DeploymentData\Worker $worker, \App\Models\Backup $backup = null)
82+
* @method static dispatchAfterResponse(\App\Models\Service $service, \App\Models\DeploymentData\Process $process, \App\Models\DeploymentData\Worker $worker, \App\Models\Backup $backup = null)
83+
* @method static void run(\App\Models\Service $service, \App\Models\DeploymentData\Process $process, \App\Models\DeploymentData\Worker $worker, \App\Models\Backup $backup = null)
84+
*/
85+
class ExecuteWorker {}
86+
7287
namespace Lorisleiva\Actions\Concerns;
7388

7489
/**

Diff for: app/Actions/Workers/ExecuteWorker.php

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
<?php
2+
3+
namespace App\Actions\Workers;
4+
5+
use App\Models\Backup;
6+
use App\Models\BackupStatus;
7+
use App\Models\DeploymentData\LaunchMode;
8+
use App\Models\DeploymentData\Process;
9+
use App\Models\DeploymentData\Worker;
10+
use App\Models\Node;
11+
use App\Models\NodeTaskGroup;
12+
use App\Models\NodeTaskGroupType;
13+
use App\Models\Service;
14+
use Illuminate\Console\Command;
15+
use Illuminate\Support\Facades\DB;
16+
use Illuminate\Validation\ValidationException;
17+
use Lorisleiva\Actions\ActionRequest;
18+
use Lorisleiva\Actions\Concerns\AsAction;
19+
20+
class ExecuteWorker
21+
{
22+
use AsAction;
23+
24+
public string $commandSignature = 'app:workers:execute {--service-id=} {--process=} {--worker=} {--backup-id=}';
25+
26+
public string $commandDescription = 'Execute a worker';
27+
28+
public function handle(Service $service, Process $process, Worker $worker, ?Backup $backup = null): void
29+
{
30+
DB::transaction(function () use ($service, $process, $worker, $backup) {
31+
$taskGroupType = match (true) {
32+
isset($worker->backupCreate) => NodeTaskGroupType::BackupCreate,
33+
isset($worker->backupRestore) => NodeTaskGroupType::BackupRestore,
34+
default => NodeTaskGroupType::ExecuteWorker,
35+
};
36+
37+
$deployment = $service->latestDeployment;
38+
39+
$taskGroup = NodeTaskGroup::create([
40+
'type' => $taskGroupType,
41+
'swarm_id' => $service->swarm_id,
42+
'node_id' => $process->placementNodeId,
43+
'invoker_id' => $deployment->latestTaskGroup->invoker_id,
44+
'team_id' => $service->team_id,
45+
]);
46+
47+
$backup = match ($worker->launchMode) {
48+
LaunchMode::BackupCreate => $this->createBackup($service, $process, $worker, $taskGroup),
49+
LaunchMode::BackupRestore => $backup,
50+
default => null,
51+
};
52+
53+
$tasks = [];
54+
55+
$tasks = [
56+
...$tasks,
57+
...$worker->asNodeTasks($deployment, $process, desiredReplicas: 1, backup: $backup),
58+
];
59+
60+
$taskGroup->tasks()->createMany($tasks);
61+
});
62+
}
63+
64+
public function asController(ActionRequest $request, Service $service, string $process, string $worker): void
65+
{
66+
$backup = $request->input('backup') ? Backup::findOrFail($request->input('backup')) : null;
67+
68+
[$service, $process, $worker] = $this->validate($service, $process, $worker);
69+
70+
$this->handle($service, $process, $worker, $backup);
71+
}
72+
73+
public function asCommand(Command $command)
74+
{
75+
76+
$service = Service::findOrFail($command->option('service-id'));
77+
78+
[$service, $process, $worker] = $this->validate($service, $command->option('process'), $command->option('worker'));
79+
80+
$backup = $command->option('backup-id') ? Backup::findOrFail($command->option('backup-id')) : null;
81+
82+
$this->handle($service, $process, $worker, $backup);
83+
}
84+
85+
private function validate(Service $service, $process, $worker): array
86+
{
87+
$process = $service->latestDeployment->data->findProcess($process);
88+
if (! $process) {
89+
throw ValidationException::withMessages(['process' => 'Process not found']);
90+
}
91+
92+
$worker = $process->findWorker($worker);
93+
if (! $worker) {
94+
throw ValidationException::withMessages(['worker' => 'Worker not found']);
95+
}
96+
97+
if ($worker->launchMode === LaunchMode::Daemon) {
98+
throw ValidationException::withMessages(['worker' => 'Daemon workers cannot be executed']);
99+
}
100+
101+
if ($process->placementNodeId) {
102+
Node::findOrFail($process->placementNodeId);
103+
}
104+
105+
return [$service, $process, $worker];
106+
}
107+
108+
private function createBackup(Service $service, Process $process, Worker $worker, NodeTaskGroup $taskGroup): Backup
109+
{
110+
$s3Storage = $service->swarm->data->findS3Storage($worker->backupCreate->s3StorageId);
111+
if ($s3Storage === null) {
112+
throw ValidationException::withMessages(['s3StorageId' => "Could not find S3 storage {$worker->backupCreate->s3StorageId} in swarm {$service->swarm_id}."]);
113+
}
114+
115+
$date = now()->format('Y-m-d_His');
116+
117+
$ext = $worker->backupCreate->archive->format->value;
118+
119+
$backupFilePath = "/{$service->slug}/{$process->name}/{$worker->name}/{$service->slug}-{$process->name}-{$worker->name}-{$date}.{$ext}";
120+
121+
// FIXME: what to do with backups which are "in progress" now for the same worker?
122+
$backup = new Backup;
123+
124+
$backup->forceFill([
125+
'team_id' => $service->team_id,
126+
'task_group_id' => $taskGroup->id,
127+
'service_id' => $service->id,
128+
'process' => $process->dockerName,
129+
'worker' => $worker->dockerName,
130+
's3_storage_id' => $s3Storage->id,
131+
'dest_path' => $backupFilePath,
132+
// FIXME: add "pending" status
133+
'status' => BackupStatus::InProgress,
134+
'started_at' => now(),
135+
]);
136+
137+
$backup->save();
138+
139+
return $backup;
140+
}
141+
}

Diff for: app/Console/Commands/ExecuteScheduledWorker.php

-124
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace App\Events\NodeTasks\DownloadS3File;
4+
5+
use App\Events\NodeTasks\BaseTaskEvent;
6+
7+
class DownloadS3FileCompleted extends BaseTaskEvent {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace App\Events\NodeTasks\DownloadS3File;
4+
5+
use App\Events\NodeTasks\BaseTaskEvent;
6+
7+
class DownloadS3FileFailed extends BaseTaskEvent {}

Diff for: app/Http/Controllers/ServiceBackupController.php

+17-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace App\Http\Controllers;
44

55
use App\Models\Backup;
6+
use App\Models\DeploymentData\LaunchMode;
67
use App\Models\Service;
78
use Inertia\Inertia;
89
use Inertia\Response;
@@ -14,6 +15,21 @@ public function index(Service $service): Response
1415
$backups = Backup::where('service_id', $service->id)->latest()->paginate();
1516
$s3Storages = $service->swarm->data->s3Storages;
1617

17-
return Inertia::render('Services/Backups', ['service' => $service, 'backups' => $backups, 's3Storages' => $s3Storages]);
18+
$restoreWorkers = collect($service->latestDeployment->data->processes)
19+
->flatMap(fn ($process) => $process->workers)
20+
->filter(fn ($worker) => $worker->launchMode === LaunchMode::BackupRestore)
21+
->map(fn ($worker) => [
22+
'name' => $worker->name,
23+
'command' => $worker->command,
24+
'dockerName' => $worker->dockerName,
25+
])
26+
->values();
27+
28+
return Inertia::render('Services/Backups', [
29+
'service' => $service,
30+
'backups' => $backups,
31+
's3Storages' => $s3Storages,
32+
'restoreWorkers' => $restoreWorkers,
33+
]);
1834
}
1935
}

Diff for: app/Http/Controllers/SwarmController.php

+15-4
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,18 @@ public function updateDockerRegistries(Swarm $swarm, Request $request)
9090

9191
foreach ($swarmData->registries as $registry) {
9292
$previous = $swarm->data->findRegistry($registry->id);
93-
if ($registry->sameAs($previous)) {
94-
$registry->password = $previous->password;
9593

94+
if ($previous) {
95+
if (! $registry->username) {
96+
$registry->username = $previous->username;
97+
}
98+
99+
if (! $registry->password) {
100+
$registry->password = $previous->password;
101+
}
102+
}
103+
104+
if ($registry->sameAs($previous)) {
96105
continue;
97106
}
98107

@@ -160,9 +169,12 @@ public function updateS3Storages(Swarm $swarm, Request $request)
160169

161170
foreach ($swarmData->s3Storages as $s3Storage) {
162171
$previous = $swarm->data->findS3Storage($s3Storage->id);
163-
if ($s3Storage->sameAs($previous)) {
172+
173+
if ($previous && ! $s3Storage->secretKey) {
164174
$s3Storage->secretKey = $previous->secretKey;
175+
}
165176

177+
if ($s3Storage->sameAs($previous)) {
166178
continue;
167179
}
168180

@@ -177,7 +189,6 @@ public function updateS3Storages(Swarm $swarm, Request $request)
177189
'type' => NodeTaskType::CreateS3Storage,
178190
'meta' => CreateS3StorageMeta::validateAndCreate($taskMeta),
179191
'payload' => [
180-
'PrevConfigName' => $previous?->dockerName,
181192
'S3StorageSpec' => [
182193
'Endpoint' => $s3Storage->endpoint,
183194
'AccessKey' => $s3Storage->accessKey,

Diff for: app/Models/DeploymentData/BackupCreateOptions.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class BackupCreateOptions extends Data
88
{
99
public function __construct(
1010
public string $s3StorageId,
11-
public ?ArchiveOptions $archive,
11+
public ArchiveOptions $archive,
1212
public ?Volume $backupVolume,
1313
) {}
1414
}

Diff for: app/Models/DeploymentData/LaunchMode.php

+1
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ public function maxInitialReplicas(): int
3737
}
3838

3939
const CRONJOB_LAUNCH_MODES = [LaunchMode::Cronjob, LaunchMode::BackupCreate];
40+
const BACKUP_LAUNCH_MODES = [LaunchMode::BackupCreate, LaunchMode::BackupRestore];

0 commit comments

Comments
 (0)