add basic scheduler and queue processing for tasks

This commit is contained in:
Dane Everitt 2016-02-27 18:35:12 -05:00
parent b6719129e1
commit 1296d08dcb
11 changed files with 441 additions and 4 deletions

View file

@ -0,0 +1,83 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 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;
use DB;
use Carbon;
use Pterodactyl\Models;
use Illuminate\Console\Command;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Pterodactyl\Jobs\SendScheduledTask;
class RunTasks extends Command
{
use DispatchesJobs;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'pterodactyl:tasks';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Find and run scheduled tasks.';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$tasks = Models\Task::where('queued', 0)->where('next_run', '<=', (Carbon::now())->toAtomString())->get();
$this->info(sprintf('Preparing to queue %d tasks.', count($tasks)));
$bar = $this->output->createProgressBar(count($tasks));
foreach ($tasks as &$task) {
$bar->advance();
$this->dispatch(new SendScheduledTask(Models\Server::findOrFail($task->server), $task));
}
$bar->finish();
$this->info("\nFinished queuing tasks for running.");
}
}

View file

@ -17,6 +17,7 @@ class Kernel extends ConsoleKernel
\Pterodactyl\Console\Commands\MakeUser::class, \Pterodactyl\Console\Commands\MakeUser::class,
\Pterodactyl\Console\Commands\ShowVersion::class, \Pterodactyl\Console\Commands\ShowVersion::class,
\Pterodactyl\Console\Commands\UpdateEnvironment::class, \Pterodactyl\Console\Commands\UpdateEnvironment::class,
\Pterodactyl\Console\Commands\RunTasks::class,
]; ];
/** /**
@ -27,7 +28,6 @@ class Kernel extends ConsoleKernel
*/ */
protected function schedule(Schedule $schedule) protected function schedule(Schedule $schedule)
{ {
$schedule->command('inspire') $schedule->command('pterodactyl:tasks')->everyFiveMinutes()->withoutOverlapping();
->hourly();
} }
} }

View file

@ -0,0 +1,72 @@
<?php
namespace Pterodactyl\Jobs;
use Pterodactyl\Jobs\Job;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use DB;
use Carbon;
use Pterodactyl\Models;
use Pterodactyl\Repositories\Daemon\CommandRepository;
class SendScheduledTask extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
protected $server;
protected $task;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(Models\Server $server, Models\Task $task)
{
$this->server = $server;
$this->task = $task;
$task->queued = 1;
$task->save();
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
$time = Carbon::now();
try {
if ($this->task->action === 'command') {
$repo = new CommandRepository($this->server);
$response = $repo->send($this->task->data);
}
$this->task->fill([
'last_run' => $time,
'next_run' => $time->addMonths($this->task->month)->addWeeks($this->task->week)->addDays($this->task->day)->addHours($this->task->hour)->addMinutes($this->task->minute)->addSeconds($this->task->second),
'queued' => 0
]);
$this->task->save();
} catch (\Exception $ex) {
$wasError = true;
$response = $ex->getMessage();
throw $ex;
} finally {
$log = new Models\TaskLog;
$log->fill([
'task_id' => $this->task->id,
'run_time' => $time,
'run_status' => (int) isset($wasError),
'response' => $response
]);
$log->save();
}
}
}

View file

@ -109,7 +109,6 @@ class Node extends Model
$nodeData = self::getByID($node); $nodeData = self::getByID($node);
// @TODO: Better solution to disabling verification. Security risk.
self::$guzzle[$node] = new Client([ self::$guzzle[$node] = new Client([
'base_uri' => sprintf('%s://%s:%s/', $nodeData->scheme, $nodeData->fqdn, $nodeData->daemonListen), 'base_uri' => sprintf('%s://%s:%s/', $nodeData->scheme, $nodeData->fqdn, $nodeData->daemonListen),
'timeout' => 5.0, 'timeout' => 5.0,

63
app/Models/Task.php Normal file
View file

@ -0,0 +1,63 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 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\Models;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'tasks';
/**
* Fields that are not mass assignable.
*
* @var array
*/
protected $guarded = ['id', 'created_at', 'updated_at'];
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'server' => 'integer',
'queued' => 'integer',
];
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = ['last_run', 'next_run', 'created_at', 'updated_at'];
}

63
app/Models/TaskLog.php Normal file
View file

@ -0,0 +1,63 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 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\Models;
use Illuminate\Database\Eloquent\Model;
class TaskLog extends Model
{
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'tasks_log';
/**
* Fields that are not mass assignable.
*
* @var array
*/
protected $guarded = ['id', 'created_at', 'updated_at'];
/**
* Cast values to correct type.
*
* @var array
*/
protected $casts = [
'id' => 'integer',
'task_id' => 'integer',
'run_status' => 'integer'
];
/**
* The attributes that should be mutated to dates.
*
* @var array
*/
protected $dates = ['run_time', 'created_at', 'updated_at'];
}

View file

@ -0,0 +1,79 @@
<?php
/**
* Pterodactyl - Panel
* Copyright (c) 2015 - 2016 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\Repositories\Daemon;
use Pterodactyl\Models;
use Pterodactyl\Exceptions\DisplayException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
class CommandRepository {
protected $server;
protected $node;
protected $client;
public function __construct($server)
{
$this->server = ($server instanceof Models\Server) ? $server : Models\Server::findOrFail($server);
$this->node = Models\Node::getByID($this->server->node);
$this->client = Models\Node::guzzleRequest($this->server->node);
}
/**
* [send description]
* @param string $command
* @return boolean
* @throws DisplayException
* @throws RequestException
*/
public function send($command)
{
// We don't use the user's specific daemon secret here since we
// are assuming that a call to this function has been validated.
// Additionally not all calls to this will be from a logged in user.
// (e.g. task queue or API)
try {
$response = $this->client->request('POST', '/server/command', [
'headers' => [
'X-Access-Token' => $this->server->daemonSecret,
'X-Access-Server' => $this->server->uuid
],
'json' => [
'command' => $command
]
]);
if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
throw new DisplayException('Command sending responded with a non-200 error code.');
}
return true;
} catch (\Exception $ex) {
throw $ex;
}
}
}

View file

@ -27,7 +27,8 @@
"prologue/alerts": "^0.4.0", "prologue/alerts": "^0.4.0",
"s1lentium/iptools": "^1.0", "s1lentium/iptools": "^1.0",
"edvinaskrucas/settings": "^2.0", "edvinaskrucas/settings": "^2.0",
"igaster/laravel-theme": "^1.1" "igaster/laravel-theme": "^1.1",
"nesbot/carbon": "^1.21"
}, },
"require-dev": { "require-dev": {
"fzaninotto/faker": "~1.4", "fzaninotto/faker": "~1.4",

View file

@ -180,6 +180,7 @@ return [
'Blade' => Illuminate\Support\Facades\Blade::class, 'Blade' => Illuminate\Support\Facades\Blade::class,
'Bus' => Illuminate\Support\Facades\Bus::class, 'Bus' => Illuminate\Support\Facades\Bus::class,
'Cache' => Illuminate\Support\Facades\Cache::class, 'Cache' => Illuminate\Support\Facades\Cache::class,
'Carbon' => Carbon\Carbon::class,
'Config' => Illuminate\Support\Facades\Config::class, 'Config' => Illuminate\Support\Facades\Config::class,
'Cookie' => Illuminate\Support\Facades\Cookie::class, 'Cookie' => Illuminate\Support\Facades\Cookie::class,
'Crypt' => Illuminate\Support\Facades\Crypt::class, 'Crypt' => Illuminate\Support\Facades\Crypt::class,

View file

@ -0,0 +1,42 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddTasksTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tasks', function (Blueprint $table) {
$table->increments('id');
$table->integer('server')->unsigned();
$table->string('action');
$table->text('data');
$table->tinyInteger('queued')->unsigned()->default(0);
$table->integer('month')->default(0);
$table->integer('week')->default(0);
$table->integer('day')->default(0);
$table->integer('hour')->default(0);
$table->integer('minute')->default(0);
$table->integer('second')->default(0);
$table->timestamp('last_run');
$table->timestamp('next_run');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('tasks');
}
}

View file

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddTasksLogTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tasks_log', function (Blueprint $table) {
$table->increments('id');
$table->integer('task_id')->unsigned();
$table->timestamp('run_time');
$table->integer('run_status')->unsigned();
$table->text('response');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('tasks_log');
}
}