Skip to content

Commit

Permalink
feat: #3 create init swarm cluster task
Browse files Browse the repository at this point in the history
  • Loading branch information
bohdan-shulha committed Jun 20, 2024
1 parent b213970 commit f993456
Show file tree
Hide file tree
Showing 40 changed files with 1,008 additions and 36 deletions.
2 changes: 0 additions & 2 deletions api-nodes/Http/Controllers/EventController.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ public function started(Node $node, NodeData $data)

$node->save();

dd($node->data);

return [
'settings' => [
'poll_interval' => 5
Expand Down
54 changes: 54 additions & 0 deletions api-nodes/Http/Controllers/NextTaskController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace ApiNodes\Http\Controllers;

use App\Models\Node;
use App\Models\NodeTask\TaskStatus;
use App\Models\NodeTaskGroup;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Response;

class NextTaskController
{
public function __invoke(Node $node)
{
$taskGroup = $node->taskGroups()->inProgress()->first();

$task = $taskGroup ? $this->getNextTaskFromGroup($taskGroup) : $this->pickNextTask($node);
if ($task) {
return $task;
}

return new Response([
'message' => 'No task found.'
], 404);
}

protected function getNextTaskFromGroup(NodeTaskGroup $taskGroup)
{
if ($taskGroup->tasks()->running()->first()) {
return new Response([
'error_message' => 'Another task should be already running.'
], 409);
}

$task = $taskGroup->tasks()->pending()->first();

$task->start();

return $task;
}

protected function pickNextTask(Node $node)
{
$taskGroup = NodeTaskGroup::where('swarm_id', $node->swarm_id)->where(function (Builder $query) use ($node) {
return $query->where('node_id', $node->id)->orWhere('node_id', null);
})->pending()->first();

$task = $taskGroup->tasks()->first();

$taskGroup->startTask($node, $task);

return $task;
}
}
46 changes: 46 additions & 0 deletions api-nodes/Http/Controllers/TaskController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace ApiNodes\Http\Controllers;

use App\Casts\TaskResultCast;
use App\Models\NodeTask;
use App\Models\NodeTask\ErrorResult;
use Illuminate\Http\Request;
use Illuminate\Http\Response;

class TaskController
{
public function complete(NodeTask $task, Request $request)
{
if ($task->is_ended) {
return new Response(['error' => 'Already ended.'], 409);
}

if ($task->is_pending) {
return new Response(['error' => "Task didn't start yet."], 409);
}

$result = TaskResultCast::RESULT_BY_TYPE[$task->type]::validateAndCreate($request->all());

$task->complete($result);

return new Response('', 204);
}

public function fail(NodeTask $task, Request $request)
{
if ($task->is_ended) {
return new Response(['error' => 'Already ended.'], 409);
}

if ($task->is_pending) {
return new Response(['error' => "Task didn't start yet."], 409);
}

$result = ErrorResult::validateAndCreate($request->all());

$task->fail($result);

return new Response('', 204);
}
}
3 changes: 3 additions & 0 deletions api-nodes/Http/Middleware/AgentTokenAuth.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
namespace ApiNodes\Http\Middleware;

use App\Models\Node;
use App\Models\NodeTask;
use App\Models\Scopes\TeamScope;
use App\Models\Team;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
Expand Down Expand Up @@ -34,6 +36,7 @@ public function handle(Request $request, Closure $next): Response
$node->save();

app()->singleton(Node::class, fn() => $node);
app()->singleton(Team::class, fn() => $node->team);

return $next($request);
}
Expand Down
51 changes: 51 additions & 0 deletions app/Casts/TaskPayloadCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

namespace App\Casts;

use App\Models\NodeTask;
use App\Models\NodeTask\CreateNetworkTaskPayload;
use App\Models\NodeTask\InitSwarmTaskPayload;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
use InvalidArgumentException;

class TaskPayloadCast implements CastsAttributes
{
public const TYPE_BY_PAYLOAD = [
CreateNetworkTaskPayload::class => 0,
InitSwarmTaskPayload::class => 1
];

public const PAYLOAD_BY_TYPE = [
0 => CreateNetworkTaskPayload::class,
1 => InitSwarmTaskPayload::class
];

/**
* Cast the given value.
*
* @param array<string, mixed> $attributes
*/
public function get(Model $model, string $key, mixed $value, array $attributes): mixed
{
if (!($model instanceof NodeTask)) {
throw new InvalidArgumentException('Model must be an instance of NodeTask');
}

return self::PAYLOAD_BY_TYPE[$model->type]::from($value);
}

/**
* Prepare the given value for storage.
*
* @param array<string, mixed> $attributes
*/
public function set(Model $model, string $key, mixed $value, array $attributes): mixed
{
if (!($model instanceof NodeTask)) {
throw new InvalidArgumentException('Model must be an instance of NodeTask');
}

return $value->toJson();
}
}
61 changes: 61 additions & 0 deletions app/Casts/TaskResultCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace App\Casts;

use App\Models\NodeTask;
use App\Models\NodeTask\CreateNetworkTaskResult;
use App\Models\NodeTask\ErrorResult;
use App\Models\NodeTask\InitSwarmTaskResult;
use InvalidArgumentException;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;

class TaskResultCast implements CastsAttributes
{

public const TYPE_BY_RESULT = [
CreateNetworkTaskResult::class => 0,
InitSwarmTaskResult::class => 1
];

public const RESULT_BY_TYPE = [
0 => CreateNetworkTaskResult::class,
1 => InitSwarmTaskResult::class
];

/**
* Cast the given value.
*
* @param array<string, mixed> $attributes
*/
public function get(Model $model, string $key, mixed $value, array $attributes): mixed
{
if (!($model instanceof NodeTask)) {
throw new InvalidArgumentException('Model must be an instance of NodeTask');
}

if ($model->is_failed) {
return ErrorResult::from($value);
}

if ($model->is_ended) {
return self::RESULT_BY_TYPE[$model->type]::from($value);
}

return null;
}

/**
* Prepare the given value for storage.
*
* @param array<string, mixed> $attributes
*/
public function set(Model $model, string $key, mixed $value, array $attributes): mixed
{
if (!($model instanceof NodeTask)) {
throw new InvalidArgumentException('Model must be an instance of NodeTask');
}

return $value->toJson();
}
}
9 changes: 8 additions & 1 deletion app/Http/Controllers/NodeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use App\Http\Requests\StoreNodeRequest;
use App\Http\Requests\UpdateNodeRequest;
use App\Models\Node;
use App\Models\NodeTask;
use App\Models\NodeTask\InitSwarmTaskPayload;
use Inertia\Inertia;

class NodeController extends Controller
Expand Down Expand Up @@ -40,7 +42,12 @@ public function store(StoreNodeRequest $request)
*/
public function show(Node $node)
{
return Inertia::render('Nodes/Show', ['node' => $node]);
$initTaskGroup = $node->tasks()->inProgress()->ofType(InitSwarmTaskPayload::class)->first()?->taskGroup->with('tasks')->first();
if (!$initTaskGroup) {
$initTaskGroup = $node->tasks()->failed()->ofType(InitSwarmTaskPayload::class)->first()?->taskGroup->with('tasks')->first();
}

return Inertia::render('Nodes/Show', ['node' => $node, 'initTaskGroup' => $initTaskGroup]);
}

/**
Expand Down
39 changes: 39 additions & 0 deletions app/Http/Controllers/SwarmTaskController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace App\Http\Controllers;

use App\Http\Requests\NodeTask\InitClusterFormRequest;
use App\Models\Node;
use App\Models\NodeTask\CreateNetworkTaskPayload;
use App\Models\NodeTask\InitSwarmTaskPayload;
use App\Models\NodeTaskGroup;
use App\Models\Swarm;

class SwarmTaskController extends Controller
{
public function initCluster(InitClusterFormRequest $request)
{
$swarm = Swarm::create([
'name' => $request->name,
]);

Node::whereId($request->node_id)->update([
'swarm_id' => $swarm->id,
]);

$taskGroup = NodeTaskGroup::create([
'swarm_id' => $swarm->id,
'node_id' => $request->node_id,
'invoker_id' => auth()->user()->id,
]);

$taskGroup->tasks()->createMany([
[
'payload' => new CreateNetworkTaskPayload('ptah-net'),
],
[
'payload' => new InitSwarmTaskPayload($request->name, $request->force_new_cluster),
]
]);
}
}
23 changes: 23 additions & 0 deletions app/Http/Requests/NodeTask/InitClusterFormRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Http\Requests\NodeTask;

use Illuminate\Foundation\Http\FormRequest;

class InitClusterFormRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}

public function rules(): array
{
return [
'node_id' => ['required'],
'name' => ['required', 'string', 'max:255'],
'advertise_addr' => ['required', 'ipv4'],
'force_new_cluster' => ['boolean'],
];
}
}
15 changes: 13 additions & 2 deletions app/Models/Node.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

namespace App\Models;

use App\HasOwningTeam;
use App\Traits\HasOwningTeam;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Support\Str;

class Node extends Model
Expand All @@ -32,6 +33,16 @@ protected static function booted()
});
}

public function taskGroups(): HasMany
{
return $this->hasMany(NodeTaskGroup::class);
}

public function tasks(): HasManyThrough
{
return $this->hasManyThrough(NodeTask::class, NodeTaskGroup::class, 'node_id', 'task_group_id', 'id', 'id');
}

public function getOnlineAttribute()
{
return true;
Expand Down
Loading

0 comments on commit f993456

Please sign in to comment.