forked from Alex/Pterodactyl-Panel
Begin adding schedule processing jobs.
This commit is contained in:
parent
c0d7e02481
commit
c5f2dfd6f6
110
app/Console/Commands/Schedule/ProcessRunnableCommand.php
Normal file
110
app/Console/Commands/Schedule/ProcessRunnableCommand.php
Normal 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('');
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
191
app/Jobs/Schedule/RunTaskJob.php
Normal file
191
app/Jobs/Schedule/RunTaskJob.php
Normal 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]);
|
||||||
|
}
|
||||||
|
}
|
@ -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',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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.');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
80
app/Services/Schedules/ProcessScheduleService.php
Normal file
80
app/Services/Schedules/ProcessScheduleService.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
75
app/Services/Schedules/Tasks/RunTaskService.php
Normal file
75
app/Services/Schedules/Tasks/RunTaskService.php
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
@ -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
54
composer.lock
generated
@ -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": [
|
||||||
|
@ -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).',
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
@ -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']);
|
||||||
|
Loading…
Reference in New Issue
Block a user