Begin adding schedule processing jobs.

This commit is contained in:
Dane Everitt 2017-09-16 23:10:00 -05:00
parent c0d7e02481
commit c5f2dfd6f6
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
16 changed files with 626 additions and 49 deletions

View file

@ -0,0 +1,110 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Console\Commands\Schedule;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Pterodactyl\Services\Schedules\ProcessScheduleService;
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
class ProcessRunnableCommand extends Command
{
/**
* @var \Carbon\Carbon
*/
protected $carbon;
/**
* @var string
*/
protected $description = 'Process schedules in the database and determine which are ready to run.';
/**
* @var \Pterodactyl\Services\Schedules\ProcessScheduleService
*/
protected $processScheduleService;
/**
* @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface
*/
protected $repository;
/**
* @var string
*/
protected $signature = 'p:schedule:process';
/**
* ProcessRunnableCommand constructor.
*
* @param \Carbon\Carbon $carbon
* @param \Pterodactyl\Services\Schedules\ProcessScheduleService $processScheduleService
* @param \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface $repository
*/
public function __construct(
Carbon $carbon,
ProcessScheduleService $processScheduleService,
ScheduleRepositoryInterface $repository
) {
parent::__construct();
$this->carbon = $carbon;
$this->processScheduleService = $processScheduleService;
$this->repository = $repository;
}
/**
* Handle command execution.
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function handle()
{
$schedules = $this->repository->getSchedulesToProcess($this->carbon->now()->toAtomString());
$bar = $this->output->createProgressBar(count($schedules));
foreach ($schedules as $schedule) {
if (! $schedule->tasks instanceof Collection || count($schedule->tasks) < 1) {
$bar->advance();
return;
}
$this->processScheduleService->handle($schedule);
if ($this->input->isInteractive()) {
$this->line(trans('command/messages.schedule.output_line', [
'schedule' => $schedule->name,
'hash' => $schedule->hashid,
]));
}
$bar->advance();
}
$this->line('');
}
}

View file

@ -10,6 +10,7 @@ use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Pterodactyl\Console\Commands\Location\MakeLocationCommand; use Pterodactyl\Console\Commands\Location\MakeLocationCommand;
use Pterodactyl\Console\Commands\User\DisableTwoFactorCommand; use Pterodactyl\Console\Commands\User\DisableTwoFactorCommand;
use Pterodactyl\Console\Commands\Location\DeleteLocationCommand; use Pterodactyl\Console\Commands\Location\DeleteLocationCommand;
use Pterodactyl\Console\Commands\Schedule\ProcessRunnableCommand;
class Kernel extends ConsoleKernel class Kernel extends ConsoleKernel
{ {
@ -25,17 +26,7 @@ class Kernel extends ConsoleKernel
InfoCommand::class, InfoCommand::class,
MakeLocationCommand::class, MakeLocationCommand::class,
MakeUserCommand::class, MakeUserCommand::class,
// \Pterodactyl\Console\Commands\MakeUser::class, ProcessRunnableCommand::class,
// \Pterodactyl\Console\Commands\ShowVersion::class,
// \Pterodactyl\Console\Commands\UpdateEnvironment::class,
// \Pterodactyl\Console\Commands\RunTasks::class,
// \Pterodactyl\Console\Commands\ClearTasks::class,
// \Pterodactyl\Console\Commands\ClearServices::class,
// \Pterodactyl\Console\Commands\UpdateEmailSettings::class,
// \Pterodactyl\Console\Commands\CleanServiceBackup::class,
// \Pterodactyl\Console\Commands\AddNode::class,
// \Pterodactyl\Console\Commands\MakeLocationCommand::class,
// \Pterodactyl\Console\Commands\RebuildServer::class,
]; ];
/** /**
@ -45,8 +36,8 @@ class Kernel extends ConsoleKernel
*/ */
protected function schedule(Schedule $schedule) protected function schedule(Schedule $schedule)
{ {
$schedule->command('pterodactyl:tasks')->everyMinute()->withoutOverlapping(); // $schedule->command('pterodactyl:tasks')->everyMinute()->withoutOverlapping();
$schedule->command('pterodactyl:tasks:clearlog')->twiceDaily(3, 15); // $schedule->command('pterodactyl:tasks:clearlog')->twiceDaily(3, 15);
$schedule->command('pterodactyl:cleanservices')->twiceDaily(1, 13); // $schedule->command('pterodactyl:cleanservices')->twiceDaily(1, 13);
} }
} }

View file

@ -41,4 +41,12 @@ interface ScheduleRepositoryInterface extends RepositoryInterface
* @return \Illuminate\Support\Collection * @return \Illuminate\Support\Collection
*/ */
public function getScheduleWithTasks($schedule); public function getScheduleWithTasks($schedule);
/**
* Return all of the schedules that should be processed.
*
* @param string $timestamp
* @return \Illuminate\Support\Collection
*/
public function getSchedulesToProcess($timestamp);
} }

View file

@ -26,4 +26,22 @@ namespace Pterodactyl\Contracts\Repository;
interface TaskRepositoryInterface extends RepositoryInterface interface TaskRepositoryInterface extends RepositoryInterface
{ {
/**
* Get a task and the server relationship for that task.
*
* @param int $id
* @return \Pterodactyl\Models\Task
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function getTaskWithServer($id);
/**
* Returns the next task in a schedule.
*
* @param int $schedule the ID of the schedule to select the next task from
* @param int $index the index of the current task
* @return null|\Pterodactyl\Models\Task
*/
public function getNextTask($schedule, $index);
} }

View file

@ -0,0 +1,191 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Jobs\Schedule;
use Exception;
use Carbon\Carbon;
use Pterodactyl\Jobs\Job;
use Webmozart\Assert\Assert;
use InvalidArgumentException;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
use Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface;
use Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface;
class RunTaskJob extends Job implements ShouldQueue
{
use DispatchesJobs, InteractsWithQueue, SerializesModels;
/**
* @var \Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface
*/
protected $commandRepository;
/**
* @var \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface
*/
protected $powerRepository;
/**
* @var int
*/
protected $schedule;
/**
* @var int
*/
public $task;
/**
* @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface
*/
protected $taskRepository;
/**
* RunTaskJob constructor.
*
* @param int $task
* @param int $schedule
*/
public function __construct($task, $schedule)
{
Assert::integerish($task, 'First argument passed to constructor must be numeric, received %s.');
$this->queue = app()->make('config')->get('pterodactyl.queues.standard');
$this->task = $task;
$this->schedule = $schedule;
}
/**
* Run the job and send actions to the daemon running the server.
*
* @param \Pterodactyl\Contracts\Repository\Daemon\CommandRepositoryInterface $commandRepository
* @param \Pterodactyl\Contracts\Repository\Daemon\PowerRepositoryInterface $powerRepository
* @param \Pterodactyl\Contracts\Repository\TaskRepositoryInterface $taskRepository
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\Daemon\InvalidPowerSignalException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function handle(
CommandRepositoryInterface $commandRepository,
PowerRepositoryInterface $powerRepository,
TaskRepositoryInterface $taskRepository
) {
$this->commandRepository = $commandRepository;
$this->powerRepository = $powerRepository;
$this->taskRepository = $taskRepository;
$task = $this->taskRepository->getTaskWithServer($this->task);
$server = $task->server;
// Perform the provided task aganist the daemon.
switch ($task->action) {
case 'power':
$this->powerRepository->setNode($server->node_id)
->setAccessServer($server->uuid)
->setAccessToken($server->daemonSecret)
->sendSignal($task->payload);
break;
case 'command':
$this->commandRepository->setNode($server->node_id)
->setAccessServer($server->uuid)
->setAccessToken($server->daemonSecret)
->send($task->payload);
break;
default:
throw new InvalidArgumentException('Cannot run a task that points to a non-existant action.');
}
$this->markTaskNotQueued();
$this->queueNextTask($task->sequence_id);
}
/**
* Handle a failure while sending the action to the daemon or otherwise processing the job.
*
* @param null|\Exception $exception
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function failed(Exception $exception = null)
{
$this->markTaskNotQueued();
$this->markScheduleComplete();
}
/**
* Get the next task in the schedule and queue it for running after the defined period of wait time.
*
* @param int $sequence
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
private function queueNextTask($sequence)
{
$nextTask = $this->taskRepository->getNextTask($this->schedule, $sequence);
if (is_null($nextTask)) {
$this->markScheduleComplete();
return;
}
$this->taskRepository->update($nextTask->id, ['is_queued' => true]);
$this->dispatch((new self($nextTask->id, $this->schedule))->delay($nextTask->time_offset));
}
/**
* Marks the parent schedule as being complete.
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
private function markScheduleComplete()
{
$repository = app()->make(ScheduleRepositoryInterface::class);
$repository->withoutFresh()->update($this->schedule, [
'is_processing' => false,
'last_run_at' => app()->make(Carbon::class)->now()->toDateTimeString(),
]);
}
/**
* Mark a specific task as no longer being queued.
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
private function markTaskNotQueued()
{
$repository = app()->make(TaskRepositoryInterface::class);
$repository->update($this->task, ['is_queued' => false]);
}
}

View file

@ -117,8 +117,8 @@ class Schedule extends Model implements CleansAttributes, ValidableContract
'cron_minute' => 'string', 'cron_minute' => 'string',
'is_active' => 'boolean', 'is_active' => 'boolean',
'is_processing' => 'boolean', 'is_processing' => 'boolean',
'last_run_at' => 'nullable|timestamp', 'last_run_at' => 'nullable|date',
'next_run_at' => 'nullable|timestamp', 'next_run_at' => 'nullable|date',
]; ];
/** /**

View file

@ -27,12 +27,13 @@ namespace Pterodactyl\Models;
use Sofa\Eloquence\Eloquence; use Sofa\Eloquence\Eloquence;
use Sofa\Eloquence\Validable; use Sofa\Eloquence\Validable;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Znck\Eloquent\Traits\BelongsToThrough;
use Sofa\Eloquence\Contracts\CleansAttributes; use Sofa\Eloquence\Contracts\CleansAttributes;
use Sofa\Eloquence\Contracts\Validable as ValidableContract; use Sofa\Eloquence\Contracts\Validable as ValidableContract;
class Task extends Model implements CleansAttributes, ValidableContract class Task extends Model implements CleansAttributes, ValidableContract
{ {
use Eloquence, Validable; use BelongsToThrough, Eloquence, Validable;
/** /**
* The table associated with the model. * The table associated with the model.
@ -130,14 +131,11 @@ class Task extends Model implements CleansAttributes, ValidableContract
/** /**
* Return the server a task is assigned to, acts as a belongsToThrough. * Return the server a task is assigned to, acts as a belongsToThrough.
* *
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo * @return \Znck\Eloquent\Relations\BelongsToThrough
* @throws \Exception
*/ */
public function server() public function server()
{ {
if ($schedule = $this->schedule) { return $this->belongsToThrough(Server::class, Schedule::class);
return $schedule->server();
} else {
throw new \InvalidArgumentException('Instance of Task must have an associated Schedule in the database.');
}
} }
} }

View file

@ -52,4 +52,15 @@ class ScheduleRepository extends EloquentRepository implements ScheduleRepositor
{ {
return $this->getBuilder()->with('tasks')->find($schedule, $this->getColumns()); return $this->getBuilder()->with('tasks')->find($schedule, $this->getColumns());
} }
/**
* {@inheritdoc}
*/
public function getSchedulesToProcess($timestamp)
{
return $this->getBuilder()->with('tasks')
->where('is_active', true)
->where('next_run_at', '<=', $timestamp)
->get($this->getColumns());
}
} }

View file

@ -42,33 +42,28 @@ class TaskRepository extends EloquentRepository implements TaskRepositoryInterfa
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getParentTasksWithChainCount($server) public function getTaskWithServer($id)
{ {
Assert::numeric($server, 'First argument passed to GetParentTasksWithChainCount must be numeric, received %s.'); Assert::integerish($id, 'First argument passed to getTaskWithServer must be numeric, received %s.');
return $this->getBuilder()->withCount('chained')->where([
['server_id', '=', $server],
['parent_task_id', '=', null],
])->get($this->getColumns());
}
/**
* {@inheritdoc}
*/
public function getTaskForServer($task, $server)
{
Assert::numeric($task, 'First argument passed to getTaskForServer must be numeric, received %s.');
Assert::numeric($server, 'Second argument passed to getTaskForServer must be numeric, received %s.');
$instance = $this->getBuilder()->with('chained')->where([
['server_id', '=', $server],
['parent_task_id', '=', null],
])->find($task, $this->getColumns());
$instance = $this->getBuilder()->with('server')->find($id, $this->getColumns());
if (! $instance) { if (! $instance) {
throw new RecordNotFoundException; throw new RecordNotFoundException;
} }
return $instance; return $instance;
} }
/**
* {@inheritdoc}
*/
public function getNextTask($schedule, $index)
{
Assert::integerish($schedule, 'First argument passed to getNextTask must be integer, received %s.');
Assert::integerish($index, 'Second argument passed to getNextTask must be integer, received %s.');
return $this->getBuilder()->where('schedule_id', '=', $schedule)
->where('sequence_id', '=', $index + 1)
->first($this->getColumns());
}
} }

View file

@ -0,0 +1,80 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Services\Schedules;
use Cron\CronExpression;
use Webmozart\Assert\Assert;
use Pterodactyl\Models\Schedule;
use Pterodactyl\Services\Schedules\Tasks\RunTaskService;
use Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface;
class ProcessScheduleService
{
protected $repository;
protected $runnerService;
public function __construct(
RunTaskService $runnerService,
ScheduleRepositoryInterface $repository
) {
$this->repository = $repository;
$this->runnerService = $runnerService;
}
/**
* Process a schedule and push the first task onto the queue worker.
*
* @param int|\Pterodactyl\Models\Schedule $schedule
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function handle($schedule)
{
Assert::true(($schedule instanceof Schedule || is_digit($schedule)),
'First argument passed to handle must be instance of \Pterodactyl\Models\Schedule or an integer, received %s.'
);
if (($schedule instanceof Schedule && ! $schedule->relationLoaded('tasks')) || ! $schedule instanceof Schedule) {
$schedule = $this->repository->getScheduleWithTasks(is_digit($schedule) ? $schedule : $schedule->id);
}
$formattedCron = sprintf('%s %s %s * %s *',
$schedule->cron_minute,
$schedule->cron_hour,
$schedule->cron_day_of_month,
$schedule->cron_day_of_week
);
$this->repository->update($schedule->id, [
'is_processing' => true,
'next_run_at' => CronExpression::factory($formattedCron)->getNextRunDate(),
]);
$task = $schedule->tasks->where('sequence_id', 1)->first();
$this->runnerService->handle($task);
}
}

View file

@ -24,6 +24,7 @@
namespace Pterodactyl\Services\Schedules; namespace Pterodactyl\Services\Schedules;
use Cron\CronExpression;
use Webmozart\Assert\Assert; use Webmozart\Assert\Assert;
use Pterodactyl\Models\Server; use Pterodactyl\Models\Server;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
@ -83,6 +84,7 @@ class ScheduleCreationService
$server = ($server instanceof Server) ? $server->id : $server; $server = ($server instanceof Server) ? $server->id : $server;
$data['server_id'] = $server; $data['server_id'] = $server;
$data['next_run_at'] = $this->getCronTimestamp($data);
$this->connection->beginTransaction(); $this->connection->beginTransaction();
$schedule = $this->repository->create($data); $schedule = $this->repository->create($data);
@ -103,4 +105,22 @@ class ScheduleCreationService
return $schedule; return $schedule;
} }
/**
* Return a DateTime object after parsing the cron data provided.
*
* @param array $data
* @return \DateTime
*/
private function getCronTimestamp(array $data)
{
$formattedCron = sprintf('%s %s %s * %s *',
array_get($data, 'cron_minute', '*'),
array_get($data, 'cron_hour', '*'),
array_get($data, 'cron_day_of_month', '*'),
array_get($data, 'cron_day_of_week', '*')
);
return CronExpression::factory($formattedCron)->getNextRunDate();
}
} }

View file

@ -0,0 +1,75 @@
<?php
/*
* Pterodactyl - Panel
* Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
namespace Pterodactyl\Services\Schedules\Tasks;
use Pterodactyl\Models\Task;
use Illuminate\Contracts\Bus\Dispatcher;
use Pterodactyl\Jobs\Schedule\RunTaskJob;
use Pterodactyl\Contracts\Repository\TaskRepositoryInterface;
class RunTaskService
{
/**
* @var \Illuminate\Contracts\Bus\Dispatcher
*/
protected $dispatcher;
/**
* @var \Pterodactyl\Contracts\Repository\TaskRepositoryInterface
*/
protected $repository;
/**
* RunTaskService constructor.
*
* @param \Illuminate\Contracts\Bus\Dispatcher $dispatcher
* @param \Pterodactyl\Contracts\Repository\TaskRepositoryInterface $repository
*/
public function __construct(
Dispatcher $dispatcher,
TaskRepositoryInterface $repository
) {
$this->dispatcher = $dispatcher;
$this->repository = $repository;
}
/**
* Push a single task onto the queue.
*
* @param int|\Pterodactyl\Models\Task $task
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function handle($task)
{
if (! $task instanceof Task) {
$task = $this->repository->find($task);
}
$this->repository->update($task->id, ['is_queued' => true]);
$this->dispatcher->dispatch((new RunTaskJob($task->id, $task->schedule_id))->delay($task->time_offset));
}
}

View file

@ -39,7 +39,8 @@
"spatie/laravel-fractal": "^4.0", "spatie/laravel-fractal": "^4.0",
"watson/validating": "^3.0", "watson/validating": "^3.0",
"webmozart/assert": "^1.2", "webmozart/assert": "^1.2",
"webpatser/laravel-uuid": "^2.0" "webpatser/laravel-uuid": "^2.0",
"znck/belongs-to-through": "^2.3"
}, },
"require-dev": { "require-dev": {
"barryvdh/laravel-debugbar": "^2.4", "barryvdh/laravel-debugbar": "^2.4",

54
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "15a4dc6de122bc1e47d1d9ca3b1224d6", "content-hash": "46a0a06ec8f3af50ed6ec05c2bb3b9a3",
"packages": [ "packages": [
{ {
"name": "aws/aws-sdk-php", "name": "aws/aws-sdk-php",
@ -3891,6 +3891,58 @@
"UUID RFC4122" "UUID RFC4122"
], ],
"time": "2017-08-30T20:45:29+00:00" "time": "2017-08-30T20:45:29+00:00"
},
{
"name": "znck/belongs-to-through",
"version": "v2.3.1",
"source": {
"type": "git",
"url": "https://github.com/znck/belongs-to-through.git",
"reference": "8ac53e9134072902a8d3f3e18c327d4a8fd70d3d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/znck/belongs-to-through/zipball/8ac53e9134072902a8d3f3e18c327d4a8fd70d3d",
"reference": "8ac53e9134072902a8d3f3e18c327d4a8fd70d3d",
"shasum": ""
},
"require": {
"illuminate/database": "~5.0"
},
"require-dev": {
"fabpot/php-cs-fixer": "^1.11",
"orchestra/testbench": "~3.0",
"phpunit/php-code-coverage": "^3.3",
"phpunit/phpunit": "~5.0",
"satooshi/php-coveralls": "^1.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Znck\\Eloquent\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Rahul Kadyan",
"email": "hi@znck.me"
}
],
"description": "Adds belongsToThrough relation to laravel models",
"homepage": "https://github.com/znck/belongs-to-through",
"keywords": [
"belongsToThrough",
"eloquent",
"laravel",
"model",
"models",
"znck"
],
"time": "2017-07-23T13:11:16+00:00"
} }
], ],
"packages-dev": [ "packages-dev": [

View file

@ -51,4 +51,7 @@ return [
], ],
'2fa_disabled' => '2-Factor authentication has been disabled for :email.', '2fa_disabled' => '2-Factor authentication has been disabled for :email.',
], ],
'schedule' => [
'output_line' => 'Dispatching job for first task in `:schedule` (:hash).',
],
]; ];

View file

@ -41,6 +41,11 @@ class ScheduleCreationServiceTest extends TestCase
*/ */
protected $connection; protected $connection;
/**
* @var \Cron\CronExpression
*/
protected $cron;
/** /**
* @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface * @var \Pterodactyl\Contracts\Repository\ScheduleRepositoryInterface
*/ */
@ -64,6 +69,7 @@ class ScheduleCreationServiceTest extends TestCase
parent::setUp(); parent::setUp();
$this->connection = m::mock(ConnectionInterface::class); $this->connection = m::mock(ConnectionInterface::class);
$this->cron = m::mock('overload:\Cron\CronExpression');
$this->repository = m::mock(ScheduleRepositoryInterface::class); $this->repository = m::mock(ScheduleRepositoryInterface::class);
$this->taskCreationService = m::mock(TaskCreationService::class); $this->taskCreationService = m::mock(TaskCreationService::class);
@ -78,8 +84,14 @@ class ScheduleCreationServiceTest extends TestCase
$schedule = factory(Schedule::class)->make(); $schedule = factory(Schedule::class)->make();
$server = factory(Server::class)->make(); $server = factory(Server::class)->make();
$this->cron->shouldReceive('factory')->with('* * * * * *')->once()->andReturnSelf()
->shouldReceive('getNextRunDate')->withNoArgs()->once()->andReturn('nextDate');
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->repository->shouldReceive('create')->with(['server_id' => $server->id, 'test_data' => 'test_value'])->once()->andReturn($schedule); $this->repository->shouldReceive('create')->with([
'server_id' => $server->id,
'next_run_at' => 'nextDate',
'test_data' => 'test_value',
])->once()->andReturn($schedule);
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
$response = $this->service->handle($server, ['test_data' => 'test_value']); $response = $this->service->handle($server, ['test_data' => 'test_value']);
@ -96,8 +108,14 @@ class ScheduleCreationServiceTest extends TestCase
$schedule = factory(Schedule::class)->make(); $schedule = factory(Schedule::class)->make();
$server = factory(Server::class)->make(); $server = factory(Server::class)->make();
$this->cron->shouldReceive('factory')->with('* * * * * *')->once()->andReturnSelf()
->shouldReceive('getNextRunDate')->withNoArgs()->once()->andReturn('nextDate');
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->repository->shouldReceive('create')->with(['server_id' => $server->id, 'test_data' => 'test_value'])->once()->andReturn($schedule); $this->repository->shouldReceive('create')->with([
'next_run_at' => 'nextDate',
'server_id' => $server->id,
'test_data' => 'test_value',
])->once()->andReturn($schedule);
$this->taskCreationService->shouldReceive('handle')->with($schedule, [ $this->taskCreationService->shouldReceive('handle')->with($schedule, [
'time_interval' => 'm', 'time_interval' => 'm',
'time_value' => 10, 'time_value' => 10,
@ -123,8 +141,14 @@ class ScheduleCreationServiceTest extends TestCase
{ {
$schedule = factory(Schedule::class)->make(); $schedule = factory(Schedule::class)->make();
$this->cron->shouldReceive('factory')->with('* * * * * *')->once()->andReturnSelf()
->shouldReceive('getNextRunDate')->withNoArgs()->once()->andReturn('nextDate');
$this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull(); $this->connection->shouldReceive('beginTransaction')->withNoArgs()->once()->andReturnNull();
$this->repository->shouldReceive('create')->with(['server_id' => 1234, 'test_data' => 'test_value'])->once()->andReturn($schedule); $this->repository->shouldReceive('create')->with([
'next_run_at' => 'nextDate',
'server_id' => 1234,
'test_data' => 'test_value',
])->once()->andReturn($schedule);
$this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull(); $this->connection->shouldReceive('commit')->withNoArgs()->once()->andReturnNull();
$response = $this->service->handle(1234, ['test_data' => 'test_value']); $response = $this->service->handle(1234, ['test_data' => 'test_value']);