Skip to content

Commit c5f2dfd

Browse files
committed
Begin adding schedule processing jobs.
1 parent c0d7e02 commit c5f2dfd

File tree

16 files changed

+624
-47
lines changed

16 files changed

+624
-47
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
/*
3+
* Pterodactyl - Panel
4+
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
namespace Pterodactyl\Console\Commands\Schedule;
26+
27+
use Carbon\Carbon;
28+
use Illuminate\Console\Command;
29+
use Illuminate\Support\Collection;
30+
use Pterodactyl\Services\Schedules\ProcessScheduleService;
31+
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
32+
33+
class ProcessRunnableCommand extends Command
34+
{
35+
/**
36+
* @var \Carbon\Carbon
37+
*/
38+
protected $carbon;
39+
40+
/**
41+
* @var string
42+
*/
43+
protected $description = 'Process schedules in the database and determine which are ready to run.';
44+
45+
/**
46+
* @var \Pterodactyl\Services\Schedules\ProcessScheduleService
47+
*/
48+
protected $processScheduleService;
49+
50+
/**
51+
* @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface
52+
*/
53+
protected $repository;
54+
55+
/**
56+
* @var string
57+
*/
58+
protected $signature = 'p:schedule:process';
59+
60+
/**
61+
* ProcessRunnableCommand constructor.
62+
*
63+
* @param \Carbon\Carbon $carbon
64+
* @param \Pterodactyl\Services\Schedules\ProcessScheduleService $processScheduleService
65+
* @param \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface $repository
66+
*/
67+
public function __construct(
68+
Carbon $carbon,
69+
ProcessScheduleService $processScheduleService,
70+
ScheduleRepositoryInterface $repository
71+
) {
72+
parent::__construct();
73+
74+
$this->carbon = $carbon;
75+
$this->processScheduleService = $processScheduleService;
76+
$this->repository = $repository;
77+
}
78+
79+
/**
80+
* Handle command execution.
81+
*
82+
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
83+
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
84+
*/
85+
public function handle()
86+
{
87+
$schedules = $this->repository->getSchedulesToProcess($this->carbon->now()->toAtomString());
88+
89+
$bar = $this->output->createProgressBar(count($schedules));
90+
foreach ($schedules as $schedule) {
91+
if (! $schedule->tasks instanceof Collection || count($schedule->tasks) < 1) {
92+
$bar->advance();
93+
94+
return;
95+
}
96+
97+
$this->processScheduleService->handle($schedule);
98+
if ($this->input->isInteractive()) {
99+
$this->line(trans('command/messages.schedule.output_line', [
100+
'schedule' => $schedule->name,
101+
'hash' => $schedule->hashid,
102+
]));
103+
}
104+
105+
$bar->advance();
106+
}
107+
108+
$this->line('');
109+
}
110+
}

app/Console/Kernel.php

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Pterodactyl\Console\Commands\Location\MakeLocationCommand;
1111
use Pterodactyl\Console\Commands\User\DisableTwoFactorCommand;
1212
use Pterodactyl\Console\Commands\Location\DeleteLocationCommand;
13+
use Pterodactyl\Console\Commands\Schedule\ProcessRunnableCommand;
1314

1415
class Kernel extends ConsoleKernel
1516
{
@@ -25,17 +26,7 @@ class Kernel extends ConsoleKernel
2526
InfoCommand::class,
2627
MakeLocationCommand::class,
2728
MakeUserCommand::class,
28-
// \Pterodactyl\Console\Commands\MakeUser::class,
29-
// \Pterodactyl\Console\Commands\ShowVersion::class,
30-
// \Pterodactyl\Console\Commands\UpdateEnvironment::class,
31-
// \Pterodactyl\Console\Commands\RunTasks::class,
32-
// \Pterodactyl\Console\Commands\ClearTasks::class,
33-
// \Pterodactyl\Console\Commands\ClearServices::class,
34-
// \Pterodactyl\Console\Commands\UpdateEmailSettings::class,
35-
// \Pterodactyl\Console\Commands\CleanServiceBackup::class,
36-
// \Pterodactyl\Console\Commands\AddNode::class,
37-
// \Pterodactyl\Console\Commands\MakeLocationCommand::class,
38-
// \Pterodactyl\Console\Commands\RebuildServer::class,
29+
ProcessRunnableCommand::class,
3930
];
4031

4132
/**
@@ -45,8 +36,8 @@ class Kernel extends ConsoleKernel
4536
*/
4637
protected function schedule(Schedule $schedule)
4738
{
48-
$schedule->command('pterodactyl:tasks')->everyMinute()->withoutOverlapping();
49-
$schedule->command('pterodactyl:tasks:clearlog')->twiceDaily(3, 15);
50-
$schedule->command('pterodactyl:cleanservices')->twiceDaily(1, 13);
39+
// $schedule->command('pterodactyl:tasks')->everyMinute()->withoutOverlapping();
40+
// $schedule->command('pterodactyl:tasks:clearlog')->twiceDaily(3, 15);
41+
// $schedule->command('pterodactyl:cleanservices')->twiceDaily(1, 13);
5142
}
5243
}

app/Contracts/Repository/ScheduleRepositoryInterface.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,12 @@ public function getServerSchedules($server);
4141
* @return \Illuminate\Support\Collection
4242
*/
4343
public function getScheduleWithTasks($schedule);
44+
45+
/**
46+
* Return all of the schedules that should be processed.
47+
*
48+
* @param string $timestamp
49+
* @return \Illuminate\Support\Collection
50+
*/
51+
public function getSchedulesToProcess($timestamp);
4452
}

app/Contracts/Repository/TaskRepositoryInterface.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,22 @@
2626

2727
interface TaskRepositoryInterface extends RepositoryInterface
2828
{
29+
/**
30+
* Get a task and the server relationship for that task.
31+
*
32+
* @param int $id
33+
* @return \Pterodactyl\Models\Task
34+
*
35+
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
36+
*/
37+
public function getTaskWithServer($id);
38+
39+
/**
40+
* Returns the next task in a schedule.
41+
*
42+
* @param int $schedule the ID of the schedule to select the next task from
43+
* @param int $index the index of the current task
44+
* @return null|\Pterodactyl\Models\Task
45+
*/
46+
public function getNextTask($schedule, $index);
2947
}

app/Jobs/Schedule/RunTaskJob.php

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<?php
2+
/*
3+
* Pterodactyl - Panel
4+
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
25+
namespace Pterodactyl\Jobs\Schedule;
26+
27+
use Exception;
28+
use Carbon\Carbon;
29+
use Pterodactyl\Jobs\Job;
30+
use Webmozart\Assert\Assert;
31+
use InvalidArgumentException;
32+
use Illuminate\Queue\SerializesModels;
33+
use Illuminate\Queue\InteractsWithQueue;
34+
use Illuminate\Contracts\Queue\ShouldQueue;
35+
use Illuminate\Foundation\Bus\DispatchesJobs;
36+
use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
37+
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
38+
use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface;
39+
use Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface;
40+
41+
class RunTaskJob extends Job implements ShouldQueue
42+
{
43+
use DispatchesJobs, InteractsWithQueue, SerializesModels;
44+
45+
/**
46+
* @var \Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface
47+
*/
48+
protected $commandRepository;
49+
50+
/**
51+
* @var \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface
52+
*/
53+
protected $powerRepository;
54+
55+
/**
56+
* @var int
57+
*/
58+
protected $schedule;
59+
60+
/**
61+
* @var int
62+
*/
63+
public $task;
64+
65+
/**
66+
* @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface
67+
*/
68+
protected $taskRepository;
69+
70+
/**
71+
* RunTaskJob constructor.
72+
*
73+
* @param int $task
74+
* @param int $schedule
75+
*/
76+
public function __construct($task, $schedule)
77+
{
78+
Assert::integerish($task, 'First argument passed to constructor must be numeric, received %s.');
79+
80+
$this->queue = app()->make('config')->get('pterodactyl.queues.standard');
81+
$this->task = $task;
82+
$this->schedule = $schedule;
83+
}
84+
85+
/**
86+
* Run the job and send actions to the daemon running the server.
87+
*
88+
* @param \Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface $commandRepository
89+
* @param \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface $powerRepository
90+
* @param \Pterodactyl\Contracts\Repository\TaskRepositoryInterface $taskRepository
91+
*
92+
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
93+
* @throws \Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException
94+
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
95+
*/
96+
public function handle(
97+
CommandRepositoryInterface $commandRepository,
98+
PowerRepositoryInterface $powerRepository,
99+
TaskRepositoryInterface $taskRepository
100+
) {
101+
$this->commandRepository = $commandRepository;
102+
$this->powerRepository = $powerRepository;
103+
$this->taskRepository = $taskRepository;
104+
105+
$task = $this->taskRepository->getTaskWithServer($this->task);
106+
$server = $task->server;
107+
108+
// Perform the provided task aganist the daemon.
109+
switch ($task->action) {
110+
case 'power':
111+
$this->powerRepository->setNode($server->node_id)
112+
->setAccessServer($server->uuid)
113+
->setAccessToken($server->daemonSecret)
114+
->sendSignal($task->payload);
115+
break;
116+
case 'command':
117+
$this->commandRepository->setNode($server->node_id)
118+
->setAccessServer($server->uuid)
119+
->setAccessToken($server->daemonSecret)
120+
->send($task->payload);
121+
break;
122+
default:
123+
throw new InvalidArgumentException('Cannot run a task that points to a non-existant action.');
124+
}
125+
126+
$this->markTaskNotQueued();
127+
$this->queueNextTask($task->sequence_id);
128+
}
129+
130+
/**
131+
* Handle a failure while sending the action to the daemon or otherwise processing the job.
132+
*
133+
* @param null|\Exception $exception
134+
*
135+
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
136+
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
137+
*/
138+
public function failed(Exception $exception = null)
139+
{
140+
$this->markTaskNotQueued();
141+
$this->markScheduleComplete();
142+
}
143+
144+
/**
145+
* Get the next task in the schedule and queue it for running after the defined period of wait time.
146+
*
147+
* @param int $sequence
148+
*
149+
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
150+
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
151+
*/
152+
private function queueNextTask($sequence)
153+
{
154+
$nextTask = $this->taskRepository->getNextTask($this->schedule, $sequence);
155+
if (is_null($nextTask)) {
156+
$this->markScheduleComplete();
157+
158+
return;
159+
}
160+
161+
$this->taskRepository->update($nextTask->id, ['is_queued' => true]);
162+
$this->dispatch((new self($nextTask->id, $this->schedule))->delay($nextTask->time_offset));
163+
}
164+
165+
/**
166+
* Marks the parent schedule as being complete.
167+
*
168+
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
169+
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
170+
*/
171+
private function markScheduleComplete()
172+
{
173+
$repository = app()->make(ScheduleRepositoryInterface::class);
174+
$repository->withoutFresh()->update($this->schedule, [
175+
'is_processing' => false,
176+
'last_run_at' => app()->make(Carbon::class)->now()->toDateTimeString(),
177+
]);
178+
}
179+
180+
/**
181+
* Mark a specific task as no longer being queued.
182+
*
183+
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
184+
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
185+
*/
186+
private function markTaskNotQueued()
187+
{
188+
$repository = app()->make(TaskRepositoryInterface::class);
189+
$repository->update($this->task, ['is_queued' => false]);
190+
}
191+
}

app/Models/Schedule.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ class Schedule extends Model implements CleansAttributes, ValidableContract
117117
'cron_minute' => 'string',
118118
'is_active' => 'boolean',
119119
'is_processing' => 'boolean',
120-
'last_run_at' => 'nullable|timestamp',
121-
'next_run_at' => 'nullable|timestamp',
120+
'last_run_at' => 'nullable|date',
121+
'next_run_at' => 'nullable|date',
122122
];
123123

124124
/**

0 commit comments

Comments
 (0)